1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "chrome/browser/extensions/api/push_messaging/push_messaging_api.h"
10 #include "base/lazy_instance.h"
11 #include "base/logging.h"
12 #include "base/strings/string_number_conversions.h"
13 #include "base/values.h"
14 #include "chrome/browser/chrome_notification_types.h"
15 #include "chrome/browser/extensions/api/push_messaging/push_messaging_invalidation_handler.h"
16 #include "chrome/browser/extensions/extension_service.h"
17 #include "chrome/browser/extensions/token_cache/token_cache_service.h"
18 #include "chrome/browser/extensions/token_cache/token_cache_service_factory.h"
19 #include "chrome/browser/invalidation/invalidation_service_factory.h"
20 #include "chrome/browser/profiles/profile.h"
21 #include "chrome/browser/signin/profile_oauth2_token_service_factory.h"
22 #include "chrome/browser/signin/signin_manager_factory.h"
23 #include "chrome/common/extensions/api/push_messaging.h"
24 #include "components/invalidation/invalidation_service.h"
25 #include "components/signin/core/browser/profile_oauth2_token_service.h"
26 #include "components/signin/core/browser/signin_manager.h"
27 #include "content/public/browser/browser_thread.h"
28 #include "content/public/browser/notification_details.h"
29 #include "content/public/browser/notification_source.h"
30 #include "extensions/browser/event_router.h"
31 #include "extensions/browser/extension_system_provider.h"
32 #include "extensions/browser/extensions_browser_client.h"
33 #include "extensions/common/extension.h"
34 #include "extensions/common/permissions/api_permission.h"
35 #include "google_apis/gaia/gaia_constants.h"
36 #include "google_apis/gaia/identity_provider.h"
38 using content::BrowserThread;
40 const char kChannelIdSeparator[] = "/";
41 const char kUserNotSignedIn[] = "The user is not signed in.";
42 const char kUserAccessTokenFailure[] =
43 "Cannot obtain access token for the user.";
44 const char kAPINotAvailableForUser[] =
45 "The API is not available for this user.";
46 const int kObfuscatedGaiaIdTimeoutInDays = 30;
48 namespace extensions {
50 namespace glue = api::push_messaging;
52 PushMessagingEventRouter::PushMessagingEventRouter(Profile* profile)
56 PushMessagingEventRouter::~PushMessagingEventRouter() {}
58 void PushMessagingEventRouter::TriggerMessageForTest(
59 const std::string& extension_id,
61 const std::string& payload) {
62 OnMessage(extension_id, subchannel, payload);
65 void PushMessagingEventRouter::OnMessage(const std::string& extension_id,
67 const std::string& payload) {
68 glue::Message message;
69 message.subchannel_id = subchannel;
70 message.payload = payload;
72 DVLOG(2) << "PushMessagingEventRouter::OnMessage"
73 << " payload = '" << payload
74 << "' subchannel = '" << subchannel
75 << "' extension = '" << extension_id << "'";
77 scoped_ptr<base::ListValue> args(glue::OnMessage::Create(message));
78 scoped_ptr<extensions::Event> event(
79 new extensions::Event(glue::OnMessage::kEventName, args.Pass()));
80 event->restrict_to_browser_context = profile_;
81 EventRouter::Get(profile_)->DispatchEventToExtension(
82 extension_id, event.Pass());
85 // GetChannelId class functions
87 PushMessagingGetChannelIdFunction::PushMessagingGetChannelIdFunction()
88 : OAuth2TokenService::Consumer("push_messaging"),
89 interactive_(false) {}
91 PushMessagingGetChannelIdFunction::~PushMessagingGetChannelIdFunction() {}
93 bool PushMessagingGetChannelIdFunction::RunAsync() {
94 // Fetch the function arguments.
95 scoped_ptr<glue::GetChannelId::Params> params(
96 glue::GetChannelId::Params::Create(*args_));
97 EXTENSION_FUNCTION_VALIDATE(params.get());
99 if (params && params->interactive) {
100 interactive_ = *params->interactive;
103 // Balanced in ReportResult()
106 invalidation::InvalidationService* invalidation_service =
107 invalidation::InvalidationServiceFactory::GetForProfile(GetProfile());
108 if (!invalidation_service) {
109 error_ = kAPINotAvailableForUser;
110 ReportResult(std::string(), error_);
114 IdentityProvider* identity_provider =
115 invalidation_service->GetIdentityProvider();
116 if (!identity_provider->GetTokenService()->RefreshTokenIsAvailable(
117 identity_provider->GetActiveAccountId())) {
118 if (interactive_ && identity_provider->RequestLogin()) {
119 identity_provider->AddActiveAccountRefreshTokenObserver(this);
122 error_ = kUserNotSignedIn;
123 ReportResult(std::string(), error_);
128 DVLOG(2) << "Logged in profile name: " << GetProfile()->GetProfileName();
130 StartAccessTokenFetch();
134 void PushMessagingGetChannelIdFunction::StartAccessTokenFetch() {
135 invalidation::InvalidationService* invalidation_service =
136 invalidation::InvalidationServiceFactory::GetForProfile(GetProfile());
137 CHECK(invalidation_service);
138 IdentityProvider* identity_provider =
139 invalidation_service->GetIdentityProvider();
141 std::vector<std::string> scope_vector =
142 extensions::ObfuscatedGaiaIdFetcher::GetScopes();
143 OAuth2TokenService::ScopeSet scopes(scope_vector.begin(), scope_vector.end());
144 fetcher_access_token_request_ =
145 identity_provider->GetTokenService()->StartRequest(
146 identity_provider->GetActiveAccountId(), scopes, this);
149 void PushMessagingGetChannelIdFunction::OnRefreshTokenAvailable(
150 const std::string& account_id) {
151 invalidation::InvalidationService* invalidation_service =
152 invalidation::InvalidationServiceFactory::GetForProfile(GetProfile());
153 CHECK(invalidation_service);
154 invalidation_service->GetIdentityProvider()->
155 RemoveActiveAccountRefreshTokenObserver(this);
156 DVLOG(2) << "Newly logged in: " << GetProfile()->GetProfileName();
157 StartAccessTokenFetch();
160 void PushMessagingGetChannelIdFunction::OnGetTokenSuccess(
161 const OAuth2TokenService::Request* request,
162 const std::string& access_token,
163 const base::Time& expiration_time) {
164 DCHECK_EQ(fetcher_access_token_request_.get(), request);
165 fetcher_access_token_request_.reset();
167 StartGaiaIdFetch(access_token);
170 void PushMessagingGetChannelIdFunction::OnGetTokenFailure(
171 const OAuth2TokenService::Request* request,
172 const GoogleServiceAuthError& error) {
173 DCHECK_EQ(fetcher_access_token_request_.get(), request);
174 fetcher_access_token_request_.reset();
176 // TODO(fgorski): We are currently ignoring the error passed in upon failure.
177 // It should be revisited when we are working on improving general error
178 // handling for the identity related code.
179 DVLOG(1) << "Cannot obtain access token for this user "
180 << error.error_message() << " " << error.state();
181 error_ = kUserAccessTokenFailure;
182 ReportResult(std::string(), error_);
185 void PushMessagingGetChannelIdFunction::StartGaiaIdFetch(
186 const std::string& access_token) {
187 // Start the async fetch of the Gaia Id.
188 DCHECK_CURRENTLY_ON(BrowserThread::UI);
189 net::URLRequestContextGetter* context = GetProfile()->GetRequestContext();
190 fetcher_.reset(new ObfuscatedGaiaIdFetcher(context, this, access_token));
192 // Get the token cache and see if we have already cached a Gaia Id.
193 TokenCacheService* token_cache =
194 TokenCacheServiceFactory::GetForProfile(GetProfile());
196 // Check the cache, if we already have a Gaia ID, use it instead of
197 // fetching the ID over the network.
198 const std::string& gaia_id =
199 token_cache->RetrieveToken(GaiaConstants::kObfuscatedGaiaId);
200 if (!gaia_id.empty()) {
201 ReportResult(gaia_id, std::string());
208 void PushMessagingGetChannelIdFunction::ReportResult(
209 const std::string& gaia_id, const std::string& error_string) {
210 DCHECK_CURRENTLY_ON(BrowserThread::UI);
212 BuildAndSendResult(gaia_id, error_string);
214 // Cache the obfuscated ID locally. It never changes for this user,
215 // and if we call the web API too often, we get errors due to rate limiting.
216 if (!gaia_id.empty()) {
217 base::TimeDelta timeout =
218 base::TimeDelta::FromDays(kObfuscatedGaiaIdTimeoutInDays);
219 TokenCacheService* token_cache =
220 TokenCacheServiceFactory::GetForProfile(GetProfile());
221 token_cache->StoreToken(GaiaConstants::kObfuscatedGaiaId, gaia_id,
225 // Balanced in RunAsync.
229 void PushMessagingGetChannelIdFunction::BuildAndSendResult(
230 const std::string& gaia_id, const std::string& error_message) {
231 std::string channel_id;
232 if (!gaia_id.empty()) {
233 channel_id = gaia_id;
234 channel_id += kChannelIdSeparator;
235 channel_id += extension_id();
238 // TODO(petewil): It may be a good idea to further
239 // obfuscate the channel ID to prevent the user's obfuscated Gaia Id
240 // from being readily obtained. Security review will tell us if we need to.
242 // Create a ChannelId results object and set the fields.
243 glue::ChannelIdResult result;
244 result.channel_id = channel_id;
245 SetError(error_message);
246 results_ = glue::GetChannelId::Results::Create(result);
248 bool success = error_message.empty() && !gaia_id.empty();
249 SendResponse(success);
252 void PushMessagingGetChannelIdFunction::OnObfuscatedGaiaIdFetchSuccess(
253 const std::string& gaia_id) {
254 ReportResult(gaia_id, std::string());
257 void PushMessagingGetChannelIdFunction::OnObfuscatedGaiaIdFetchFailure(
258 const GoogleServiceAuthError& error) {
259 std::string error_text = error.error_message();
260 // If the error message is blank, see if we can set it from the state.
261 if (error_text.empty() &&
262 (0 != error.state())) {
263 error_text = base::IntToString(error.state());
266 DVLOG(1) << "GetChannelId status: '" << error_text << "'";
268 // If we had bad credentials, try the logon again.
269 switch (error.state()) {
270 case GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS:
271 case GoogleServiceAuthError::ACCOUNT_DELETED:
272 case GoogleServiceAuthError::ACCOUNT_DISABLED: {
273 invalidation::InvalidationService* invalidation_service =
274 invalidation::InvalidationServiceFactory::GetForProfile(GetProfile());
275 CHECK(invalidation_service);
277 !invalidation_service->GetIdentityProvider()->RequestLogin()) {
278 ReportResult(std::string(), error_text);
283 // Return error to caller.
284 ReportResult(std::string(), error_text);
289 PushMessagingAPI::PushMessagingAPI(content::BrowserContext* context)
290 : profile_(Profile::FromBrowserContext(context)) {
291 registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_INSTALLED,
292 content::Source<Profile>(profile_->GetOriginalProfile()));
294 chrome::NOTIFICATION_EXTENSION_LOADED_DEPRECATED,
295 content::Source<Profile>(profile_->GetOriginalProfile()));
296 registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_UNLOADED_DEPRECATED,
297 content::Source<Profile>(profile_->GetOriginalProfile()));
300 PushMessagingAPI::~PushMessagingAPI() {
304 PushMessagingAPI* PushMessagingAPI::Get(content::BrowserContext* context) {
305 return BrowserContextKeyedAPIFactory<PushMessagingAPI>::Get(context);
308 void PushMessagingAPI::Shutdown() {
309 event_router_.reset();
313 static base::LazyInstance<BrowserContextKeyedAPIFactory<PushMessagingAPI> >
314 g_factory = LAZY_INSTANCE_INITIALIZER;
317 BrowserContextKeyedAPIFactory<PushMessagingAPI>*
318 PushMessagingAPI::GetFactoryInstance() {
319 return g_factory.Pointer();
322 void PushMessagingAPI::Observe(int type,
323 const content::NotificationSource& source,
324 const content::NotificationDetails& details) {
325 invalidation::InvalidationService* invalidation_service =
326 invalidation::InvalidationServiceFactory::GetForProfile(profile_);
327 if (!invalidation_service)
331 event_router_.reset(new PushMessagingEventRouter(profile_));
333 handler_.reset(new PushMessagingInvalidationHandler(
334 invalidation_service, event_router_.get()));
337 case chrome::NOTIFICATION_EXTENSION_INSTALLED: {
338 const Extension* extension =
339 content::Details<const InstalledExtensionInfo>(details)->extension;
340 if (extension->HasAPIPermission(APIPermission::kPushMessaging)) {
341 handler_->SuppressInitialInvalidationsForExtension(extension->id());
345 case chrome::NOTIFICATION_EXTENSION_LOADED_DEPRECATED: {
346 const Extension* extension = content::Details<Extension>(details).ptr();
347 if (extension->HasAPIPermission(APIPermission::kPushMessaging)) {
348 handler_->RegisterExtension(extension->id());
352 case chrome::NOTIFICATION_EXTENSION_UNLOADED_DEPRECATED: {
353 const Extension* extension =
354 content::Details<UnloadedExtensionInfo>(details)->extension;
355 if (extension->HasAPIPermission(APIPermission::kPushMessaging)) {
356 handler_->UnregisterExtension(extension->id());
365 void PushMessagingAPI::SetMapperForTest(
366 scoped_ptr<PushMessagingInvalidationMapper> mapper) {
367 handler_ = mapper.Pass();
372 BrowserContextKeyedAPIFactory<PushMessagingAPI>::DeclareFactoryDependencies() {
373 DependsOn(ExtensionsBrowserClient::Get()->GetExtensionSystemFactory());
374 DependsOn(invalidation::InvalidationServiceFactory::GetInstance());
377 } // namespace extensions