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/extensions/api/push_messaging/push_messaging_invalidation_handler.h"
15 #include "chrome/browser/extensions/extension_service.h"
16 #include "chrome/browser/extensions/token_cache/token_cache_service.h"
17 #include "chrome/browser/extensions/token_cache/token_cache_service_factory.h"
18 #include "chrome/browser/invalidation/profile_invalidation_provider_factory.h"
19 #include "chrome/browser/profiles/profile.h"
20 #include "chrome/browser/signin/profile_oauth2_token_service_factory.h"
21 #include "chrome/browser/signin/signin_manager_factory.h"
22 #include "chrome/common/extensions/api/push_messaging.h"
23 #include "components/invalidation/invalidation_service.h"
24 #include "components/invalidation/profile_invalidation_provider.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 "extensions/browser/event_router.h"
29 #include "extensions/browser/extension_registry.h"
30 #include "extensions/browser/extension_registry_factory.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 "extensions/common/permissions/permissions_data.h"
36 #include "google_apis/gaia/gaia_constants.h"
37 #include "google_apis/gaia/identity_provider.h"
39 using content::BrowserThread;
41 namespace extensions {
44 const char kChannelIdSeparator[] = "/";
45 const char kUserNotSignedIn[] = "The user is not signed in.";
46 const char kUserAccessTokenFailure[] =
47 "Cannot obtain access token for the user.";
48 const char kAPINotAvailableForUser[] =
49 "The API is not available for this user.";
50 const int kObfuscatedGaiaIdTimeoutInDays = 30;
51 const char kDeprecationMessage[] =
52 "The chrome.pushMessaging API is deprecated. Use chrome.gcm API instead.";
55 namespace glue = api::push_messaging;
57 PushMessagingEventRouter::PushMessagingEventRouter(
58 content::BrowserContext* context)
59 : browser_context_(context) {
62 PushMessagingEventRouter::~PushMessagingEventRouter() {}
64 void PushMessagingEventRouter::TriggerMessageForTest(
65 const std::string& extension_id,
67 const std::string& payload) {
68 OnMessage(extension_id, subchannel, payload);
71 void PushMessagingEventRouter::OnMessage(const std::string& extension_id,
73 const std::string& payload) {
74 glue::Message message;
75 message.subchannel_id = subchannel;
76 message.payload = payload;
78 DVLOG(2) << "PushMessagingEventRouter::OnMessage"
79 << " payload = '" << payload
80 << "' subchannel = '" << subchannel
81 << "' extension = '" << extension_id << "'";
83 scoped_ptr<base::ListValue> args(glue::OnMessage::Create(message));
84 scoped_ptr<Event> event(new Event(glue::OnMessage::kEventName, args.Pass()));
85 event->restrict_to_browser_context = browser_context_;
86 EventRouter::Get(browser_context_)
87 ->DispatchEventToExtension(extension_id, event.Pass());
90 // GetChannelId class functions
92 PushMessagingGetChannelIdFunction::PushMessagingGetChannelIdFunction()
93 : OAuth2TokenService::Consumer("push_messaging"),
94 interactive_(false) {}
96 PushMessagingGetChannelIdFunction::~PushMessagingGetChannelIdFunction() {}
98 bool PushMessagingGetChannelIdFunction::RunAsync() {
99 // Issue a deprecation message.
100 WriteToConsole(content::CONSOLE_MESSAGE_LEVEL_WARNING, kDeprecationMessage);
102 // Fetch the function arguments.
103 scoped_ptr<glue::GetChannelId::Params> params(
104 glue::GetChannelId::Params::Create(*args_));
105 EXTENSION_FUNCTION_VALIDATE(params.get());
107 if (params && params->interactive) {
108 interactive_ = *params->interactive;
111 // Balanced in ReportResult()
114 invalidation::ProfileInvalidationProvider* invalidation_provider =
115 invalidation::ProfileInvalidationProviderFactory::GetForProfile(
117 if (!invalidation_provider) {
118 error_ = kAPINotAvailableForUser;
119 ReportResult(std::string(), error_);
123 IdentityProvider* identity_provider =
124 invalidation_provider->GetInvalidationService()->GetIdentityProvider();
125 if (!identity_provider->GetTokenService()->RefreshTokenIsAvailable(
126 identity_provider->GetActiveAccountId())) {
127 if (interactive_ && identity_provider->RequestLogin()) {
128 identity_provider->AddActiveAccountRefreshTokenObserver(this);
131 error_ = kUserNotSignedIn;
132 ReportResult(std::string(), error_);
137 DVLOG(2) << "Logged in profile name: " << GetProfile()->GetProfileName();
139 StartAccessTokenFetch();
143 void PushMessagingGetChannelIdFunction::StartAccessTokenFetch() {
144 invalidation::ProfileInvalidationProvider* invalidation_provider =
145 invalidation::ProfileInvalidationProviderFactory::GetForProfile(
147 CHECK(invalidation_provider);
148 IdentityProvider* identity_provider =
149 invalidation_provider->GetInvalidationService()->GetIdentityProvider();
151 OAuth2TokenService::ScopeSet scopes = ObfuscatedGaiaIdFetcher::GetScopes();
152 fetcher_access_token_request_ =
153 identity_provider->GetTokenService()->StartRequest(
154 identity_provider->GetActiveAccountId(), scopes, this);
157 void PushMessagingGetChannelIdFunction::OnRefreshTokenAvailable(
158 const std::string& account_id) {
159 invalidation::ProfileInvalidationProvider* invalidation_provider =
160 invalidation::ProfileInvalidationProviderFactory::GetForProfile(
162 CHECK(invalidation_provider);
163 invalidation_provider->GetInvalidationService()->GetIdentityProvider()->
164 RemoveActiveAccountRefreshTokenObserver(this);
165 DVLOG(2) << "Newly logged in: " << GetProfile()->GetProfileName();
166 StartAccessTokenFetch();
169 void PushMessagingGetChannelIdFunction::OnGetTokenSuccess(
170 const OAuth2TokenService::Request* request,
171 const std::string& access_token,
172 const base::Time& expiration_time) {
173 DCHECK_EQ(fetcher_access_token_request_.get(), request);
174 fetcher_access_token_request_.reset();
176 StartGaiaIdFetch(access_token);
179 void PushMessagingGetChannelIdFunction::OnGetTokenFailure(
180 const OAuth2TokenService::Request* request,
181 const GoogleServiceAuthError& error) {
182 DCHECK_EQ(fetcher_access_token_request_.get(), request);
183 fetcher_access_token_request_.reset();
185 // TODO(fgorski): We are currently ignoring the error passed in upon failure.
186 // It should be revisited when we are working on improving general error
187 // handling for the identity related code.
188 DVLOG(1) << "Cannot obtain access token for this user "
189 << error.error_message() << " " << error.state();
190 error_ = kUserAccessTokenFailure;
191 ReportResult(std::string(), error_);
194 void PushMessagingGetChannelIdFunction::StartGaiaIdFetch(
195 const std::string& access_token) {
196 // Start the async fetch of the Gaia Id.
197 DCHECK_CURRENTLY_ON(BrowserThread::UI);
198 fetcher_.reset(new ObfuscatedGaiaIdFetcher(this));
200 // Get the token cache and see if we have already cached a Gaia Id.
201 TokenCacheService* token_cache =
202 TokenCacheServiceFactory::GetForProfile(GetProfile());
204 // Check the cache, if we already have a Gaia ID, use it instead of
205 // fetching the ID over the network.
206 const std::string& gaia_id =
207 token_cache->RetrieveToken(GaiaConstants::kObfuscatedGaiaId);
208 if (!gaia_id.empty()) {
209 ReportResult(gaia_id, std::string());
213 net::URLRequestContextGetter* context = GetProfile()->GetRequestContext();
214 fetcher_->Start(context, access_token);
217 void PushMessagingGetChannelIdFunction::ReportResult(
218 const std::string& gaia_id, const std::string& error_string) {
219 DCHECK_CURRENTLY_ON(BrowserThread::UI);
221 BuildAndSendResult(gaia_id, error_string);
223 // Cache the obfuscated ID locally. It never changes for this user,
224 // and if we call the web API too often, we get errors due to rate limiting.
225 if (!gaia_id.empty()) {
226 base::TimeDelta timeout =
227 base::TimeDelta::FromDays(kObfuscatedGaiaIdTimeoutInDays);
228 TokenCacheService* token_cache =
229 TokenCacheServiceFactory::GetForProfile(GetProfile());
230 token_cache->StoreToken(GaiaConstants::kObfuscatedGaiaId, gaia_id,
234 // Balanced in RunAsync.
238 void PushMessagingGetChannelIdFunction::BuildAndSendResult(
239 const std::string& gaia_id, const std::string& error_message) {
240 std::string channel_id;
241 if (!gaia_id.empty()) {
242 channel_id = gaia_id;
243 channel_id += kChannelIdSeparator;
244 channel_id += extension_id();
247 // TODO(petewil): It may be a good idea to further
248 // obfuscate the channel ID to prevent the user's obfuscated Gaia Id
249 // from being readily obtained. Security review will tell us if we need to.
251 // Create a ChannelId results object and set the fields.
252 glue::ChannelIdResult result;
253 result.channel_id = channel_id;
254 SetError(error_message);
255 results_ = glue::GetChannelId::Results::Create(result);
257 bool success = error_message.empty() && !gaia_id.empty();
258 SendResponse(success);
261 void PushMessagingGetChannelIdFunction::OnObfuscatedGaiaIdFetchSuccess(
262 const std::string& gaia_id) {
263 ReportResult(gaia_id, std::string());
266 void PushMessagingGetChannelIdFunction::OnObfuscatedGaiaIdFetchFailure(
267 const GoogleServiceAuthError& error) {
268 std::string error_text = error.error_message();
269 // If the error message is blank, see if we can set it from the state.
270 if (error_text.empty() &&
271 (0 != error.state())) {
272 error_text = base::IntToString(error.state());
275 DVLOG(1) << "GetChannelId status: '" << error_text << "'";
277 // If we had bad credentials, try the logon again.
278 switch (error.state()) {
279 case GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS:
280 case GoogleServiceAuthError::ACCOUNT_DELETED:
281 case GoogleServiceAuthError::ACCOUNT_DISABLED: {
282 invalidation::ProfileInvalidationProvider* invalidation_provider =
283 invalidation::ProfileInvalidationProviderFactory::GetForProfile(
285 CHECK(invalidation_provider);
286 if (!interactive_ || !invalidation_provider->GetInvalidationService()->
287 GetIdentityProvider()->RequestLogin()) {
288 ReportResult(std::string(), error_text);
293 // Return error to caller.
294 ReportResult(std::string(), error_text);
299 PushMessagingAPI::PushMessagingAPI(content::BrowserContext* context)
300 : extension_registry_observer_(this), browser_context_(context) {
301 extension_registry_observer_.Add(ExtensionRegistry::Get(browser_context_));
304 PushMessagingAPI::~PushMessagingAPI() {
308 PushMessagingAPI* PushMessagingAPI::Get(content::BrowserContext* context) {
309 return BrowserContextKeyedAPIFactory<PushMessagingAPI>::Get(context);
312 void PushMessagingAPI::Shutdown() {
313 event_router_.reset();
317 static base::LazyInstance<BrowserContextKeyedAPIFactory<PushMessagingAPI> >
318 g_factory = LAZY_INSTANCE_INITIALIZER;
321 BrowserContextKeyedAPIFactory<PushMessagingAPI>*
322 PushMessagingAPI::GetFactoryInstance() {
323 return g_factory.Pointer();
326 bool PushMessagingAPI::InitEventRouterAndHandler() {
327 invalidation::ProfileInvalidationProvider* invalidation_provider =
328 invalidation::ProfileInvalidationProviderFactory::GetForProfile(
329 Profile::FromBrowserContext(browser_context_));
330 if (!invalidation_provider)
334 event_router_.reset(new PushMessagingEventRouter(browser_context_));
336 handler_.reset(new PushMessagingInvalidationHandler(
337 invalidation_provider->GetInvalidationService(),
338 event_router_.get()));
344 void PushMessagingAPI::OnExtensionLoaded(
345 content::BrowserContext* browser_context,
346 const Extension* extension) {
347 if (!InitEventRouterAndHandler())
350 if (extension->permissions_data()->HasAPIPermission(
351 APIPermission::kPushMessaging)) {
352 handler_->RegisterExtension(extension->id());
356 void PushMessagingAPI::OnExtensionUnloaded(
357 content::BrowserContext* browser_context,
358 const Extension* extension,
359 UnloadedExtensionInfo::Reason reason) {
360 if (!InitEventRouterAndHandler())
363 if (extension->permissions_data()->HasAPIPermission(
364 APIPermission::kPushMessaging)) {
365 handler_->UnregisterExtension(extension->id());
369 void PushMessagingAPI::OnExtensionWillBeInstalled(
370 content::BrowserContext* browser_context,
371 const Extension* extension,
374 const std::string& old_name) {
375 if (InitEventRouterAndHandler() &&
376 extension->permissions_data()->HasAPIPermission(
377 APIPermission::kPushMessaging)) {
378 handler_->SuppressInitialInvalidationsForExtension(extension->id());
382 void PushMessagingAPI::SetMapperForTest(
383 scoped_ptr<PushMessagingInvalidationMapper> mapper) {
384 handler_ = mapper.Pass();
389 BrowserContextKeyedAPIFactory<PushMessagingAPI>::DeclareFactoryDependencies() {
390 DependsOn(ExtensionRegistryFactory::GetInstance());
391 DependsOn(ExtensionsBrowserClient::Get()->GetExtensionSystemFactory());
392 DependsOn(invalidation::ProfileInvalidationProviderFactory::GetInstance());
395 } // namespace extensions