push_messaging_api.cc revision 1320f92c476a1ad9d19dba2a48c72b75566198e9
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. 4 5#include "chrome/browser/extensions/api/push_messaging/push_messaging_api.h" 6 7#include <set> 8 9#include "base/bind.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" 38 39using content::BrowserThread; 40 41namespace extensions { 42 43namespace { 44const char kChannelIdSeparator[] = "/"; 45const char kUserNotSignedIn[] = "The user is not signed in."; 46const char kUserAccessTokenFailure[] = 47 "Cannot obtain access token for the user."; 48const char kAPINotAvailableForUser[] = 49 "The API is not available for this user."; 50const int kObfuscatedGaiaIdTimeoutInDays = 30; 51const char kDeprecationMessage[] = 52 "The chrome.pushMessaging API is deprecated. Use chrome.gcm API instead."; 53} // namespace 54 55namespace glue = api::push_messaging; 56 57PushMessagingEventRouter::PushMessagingEventRouter( 58 content::BrowserContext* context) 59 : browser_context_(context) { 60} 61 62PushMessagingEventRouter::~PushMessagingEventRouter() {} 63 64void PushMessagingEventRouter::TriggerMessageForTest( 65 const std::string& extension_id, 66 int subchannel, 67 const std::string& payload) { 68 OnMessage(extension_id, subchannel, payload); 69} 70 71void PushMessagingEventRouter::OnMessage(const std::string& extension_id, 72 int subchannel, 73 const std::string& payload) { 74 glue::Message message; 75 message.subchannel_id = subchannel; 76 message.payload = payload; 77 78 DVLOG(2) << "PushMessagingEventRouter::OnMessage" 79 << " payload = '" << payload 80 << "' subchannel = '" << subchannel 81 << "' extension = '" << extension_id << "'"; 82 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()); 88} 89 90// GetChannelId class functions 91 92PushMessagingGetChannelIdFunction::PushMessagingGetChannelIdFunction() 93 : OAuth2TokenService::Consumer("push_messaging"), 94 interactive_(false) {} 95 96PushMessagingGetChannelIdFunction::~PushMessagingGetChannelIdFunction() {} 97 98bool PushMessagingGetChannelIdFunction::RunAsync() { 99 // Issue a deprecation message. 100 WriteToConsole(content::CONSOLE_MESSAGE_LEVEL_WARNING, kDeprecationMessage); 101 102 // Fetch the function arguments. 103 scoped_ptr<glue::GetChannelId::Params> params( 104 glue::GetChannelId::Params::Create(*args_)); 105 EXTENSION_FUNCTION_VALIDATE(params.get()); 106 107 if (params && params->interactive) { 108 interactive_ = *params->interactive; 109 } 110 111 // Balanced in ReportResult() 112 AddRef(); 113 114 invalidation::ProfileInvalidationProvider* invalidation_provider = 115 invalidation::ProfileInvalidationProviderFactory::GetForProfile( 116 GetProfile()); 117 if (!invalidation_provider) { 118 error_ = kAPINotAvailableForUser; 119 ReportResult(std::string(), error_); 120 return false; 121 } 122 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); 129 return true; 130 } else { 131 error_ = kUserNotSignedIn; 132 ReportResult(std::string(), error_); 133 return false; 134 } 135 } 136 137 DVLOG(2) << "Logged in profile name: " << GetProfile()->GetProfileName(); 138 139 StartAccessTokenFetch(); 140 return true; 141} 142 143void PushMessagingGetChannelIdFunction::StartAccessTokenFetch() { 144 invalidation::ProfileInvalidationProvider* invalidation_provider = 145 invalidation::ProfileInvalidationProviderFactory::GetForProfile( 146 GetProfile()); 147 CHECK(invalidation_provider); 148 IdentityProvider* identity_provider = 149 invalidation_provider->GetInvalidationService()->GetIdentityProvider(); 150 151 std::vector<std::string> scope_vector = ObfuscatedGaiaIdFetcher::GetScopes(); 152 OAuth2TokenService::ScopeSet scopes(scope_vector.begin(), scope_vector.end()); 153 fetcher_access_token_request_ = 154 identity_provider->GetTokenService()->StartRequest( 155 identity_provider->GetActiveAccountId(), scopes, this); 156} 157 158void PushMessagingGetChannelIdFunction::OnRefreshTokenAvailable( 159 const std::string& account_id) { 160 invalidation::ProfileInvalidationProvider* invalidation_provider = 161 invalidation::ProfileInvalidationProviderFactory::GetForProfile( 162 GetProfile()); 163 CHECK(invalidation_provider); 164 invalidation_provider->GetInvalidationService()->GetIdentityProvider()-> 165 RemoveActiveAccountRefreshTokenObserver(this); 166 DVLOG(2) << "Newly logged in: " << GetProfile()->GetProfileName(); 167 StartAccessTokenFetch(); 168} 169 170void PushMessagingGetChannelIdFunction::OnGetTokenSuccess( 171 const OAuth2TokenService::Request* request, 172 const std::string& access_token, 173 const base::Time& expiration_time) { 174 DCHECK_EQ(fetcher_access_token_request_.get(), request); 175 fetcher_access_token_request_.reset(); 176 177 StartGaiaIdFetch(access_token); 178} 179 180void PushMessagingGetChannelIdFunction::OnGetTokenFailure( 181 const OAuth2TokenService::Request* request, 182 const GoogleServiceAuthError& error) { 183 DCHECK_EQ(fetcher_access_token_request_.get(), request); 184 fetcher_access_token_request_.reset(); 185 186 // TODO(fgorski): We are currently ignoring the error passed in upon failure. 187 // It should be revisited when we are working on improving general error 188 // handling for the identity related code. 189 DVLOG(1) << "Cannot obtain access token for this user " 190 << error.error_message() << " " << error.state(); 191 error_ = kUserAccessTokenFailure; 192 ReportResult(std::string(), error_); 193} 194 195void PushMessagingGetChannelIdFunction::StartGaiaIdFetch( 196 const std::string& access_token) { 197 // Start the async fetch of the Gaia Id. 198 DCHECK_CURRENTLY_ON(BrowserThread::UI); 199 net::URLRequestContextGetter* context = GetProfile()->GetRequestContext(); 200 fetcher_.reset(new ObfuscatedGaiaIdFetcher(context, this, access_token)); 201 202 // Get the token cache and see if we have already cached a Gaia Id. 203 TokenCacheService* token_cache = 204 TokenCacheServiceFactory::GetForProfile(GetProfile()); 205 206 // Check the cache, if we already have a Gaia ID, use it instead of 207 // fetching the ID over the network. 208 const std::string& gaia_id = 209 token_cache->RetrieveToken(GaiaConstants::kObfuscatedGaiaId); 210 if (!gaia_id.empty()) { 211 ReportResult(gaia_id, std::string()); 212 return; 213 } 214 215 fetcher_->Start(); 216} 217 218void PushMessagingGetChannelIdFunction::ReportResult( 219 const std::string& gaia_id, const std::string& error_string) { 220 DCHECK_CURRENTLY_ON(BrowserThread::UI); 221 222 BuildAndSendResult(gaia_id, error_string); 223 224 // Cache the obfuscated ID locally. It never changes for this user, 225 // and if we call the web API too often, we get errors due to rate limiting. 226 if (!gaia_id.empty()) { 227 base::TimeDelta timeout = 228 base::TimeDelta::FromDays(kObfuscatedGaiaIdTimeoutInDays); 229 TokenCacheService* token_cache = 230 TokenCacheServiceFactory::GetForProfile(GetProfile()); 231 token_cache->StoreToken(GaiaConstants::kObfuscatedGaiaId, gaia_id, 232 timeout); 233 } 234 235 // Balanced in RunAsync. 236 Release(); 237} 238 239void PushMessagingGetChannelIdFunction::BuildAndSendResult( 240 const std::string& gaia_id, const std::string& error_message) { 241 std::string channel_id; 242 if (!gaia_id.empty()) { 243 channel_id = gaia_id; 244 channel_id += kChannelIdSeparator; 245 channel_id += extension_id(); 246 } 247 248 // TODO(petewil): It may be a good idea to further 249 // obfuscate the channel ID to prevent the user's obfuscated Gaia Id 250 // from being readily obtained. Security review will tell us if we need to. 251 252 // Create a ChannelId results object and set the fields. 253 glue::ChannelIdResult result; 254 result.channel_id = channel_id; 255 SetError(error_message); 256 results_ = glue::GetChannelId::Results::Create(result); 257 258 bool success = error_message.empty() && !gaia_id.empty(); 259 SendResponse(success); 260} 261 262void PushMessagingGetChannelIdFunction::OnObfuscatedGaiaIdFetchSuccess( 263 const std::string& gaia_id) { 264 ReportResult(gaia_id, std::string()); 265} 266 267void PushMessagingGetChannelIdFunction::OnObfuscatedGaiaIdFetchFailure( 268 const GoogleServiceAuthError& error) { 269 std::string error_text = error.error_message(); 270 // If the error message is blank, see if we can set it from the state. 271 if (error_text.empty() && 272 (0 != error.state())) { 273 error_text = base::IntToString(error.state()); 274 } 275 276 DVLOG(1) << "GetChannelId status: '" << error_text << "'"; 277 278 // If we had bad credentials, try the logon again. 279 switch (error.state()) { 280 case GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS: 281 case GoogleServiceAuthError::ACCOUNT_DELETED: 282 case GoogleServiceAuthError::ACCOUNT_DISABLED: { 283 invalidation::ProfileInvalidationProvider* invalidation_provider = 284 invalidation::ProfileInvalidationProviderFactory::GetForProfile( 285 GetProfile()); 286 CHECK(invalidation_provider); 287 if (!interactive_ || !invalidation_provider->GetInvalidationService()-> 288 GetIdentityProvider()->RequestLogin()) { 289 ReportResult(std::string(), error_text); 290 } 291 return; 292 } 293 default: 294 // Return error to caller. 295 ReportResult(std::string(), error_text); 296 return; 297 } 298} 299 300PushMessagingAPI::PushMessagingAPI(content::BrowserContext* context) 301 : extension_registry_observer_(this), browser_context_(context) { 302 extension_registry_observer_.Add(ExtensionRegistry::Get(browser_context_)); 303} 304 305PushMessagingAPI::~PushMessagingAPI() { 306} 307 308// static 309PushMessagingAPI* PushMessagingAPI::Get(content::BrowserContext* context) { 310 return BrowserContextKeyedAPIFactory<PushMessagingAPI>::Get(context); 311} 312 313void PushMessagingAPI::Shutdown() { 314 event_router_.reset(); 315 handler_.reset(); 316} 317 318static base::LazyInstance<BrowserContextKeyedAPIFactory<PushMessagingAPI> > 319 g_factory = LAZY_INSTANCE_INITIALIZER; 320 321// static 322BrowserContextKeyedAPIFactory<PushMessagingAPI>* 323PushMessagingAPI::GetFactoryInstance() { 324 return g_factory.Pointer(); 325} 326 327bool PushMessagingAPI::InitEventRouterAndHandler() { 328 invalidation::ProfileInvalidationProvider* invalidation_provider = 329 invalidation::ProfileInvalidationProviderFactory::GetForProfile( 330 Profile::FromBrowserContext(browser_context_)); 331 if (!invalidation_provider) 332 return false; 333 334 if (!event_router_) 335 event_router_.reset(new PushMessagingEventRouter(browser_context_)); 336 if (!handler_) { 337 handler_.reset(new PushMessagingInvalidationHandler( 338 invalidation_provider->GetInvalidationService(), 339 event_router_.get())); 340 } 341 342 return true; 343} 344 345void PushMessagingAPI::OnExtensionLoaded( 346 content::BrowserContext* browser_context, 347 const Extension* extension) { 348 if (!InitEventRouterAndHandler()) 349 return; 350 351 if (extension->permissions_data()->HasAPIPermission( 352 APIPermission::kPushMessaging)) { 353 handler_->RegisterExtension(extension->id()); 354 } 355} 356 357void PushMessagingAPI::OnExtensionUnloaded( 358 content::BrowserContext* browser_context, 359 const Extension* extension, 360 UnloadedExtensionInfo::Reason reason) { 361 if (!InitEventRouterAndHandler()) 362 return; 363 364 if (extension->permissions_data()->HasAPIPermission( 365 APIPermission::kPushMessaging)) { 366 handler_->UnregisterExtension(extension->id()); 367 } 368} 369 370void PushMessagingAPI::OnExtensionWillBeInstalled( 371 content::BrowserContext* browser_context, 372 const Extension* extension, 373 bool is_update, 374 bool from_ephemeral, 375 const std::string& old_name) { 376 if (InitEventRouterAndHandler() && 377 extension->permissions_data()->HasAPIPermission( 378 APIPermission::kPushMessaging)) { 379 handler_->SuppressInitialInvalidationsForExtension(extension->id()); 380 } 381} 382 383void PushMessagingAPI::SetMapperForTest( 384 scoped_ptr<PushMessagingInvalidationMapper> mapper) { 385 handler_ = mapper.Pass(); 386} 387 388template <> 389void 390BrowserContextKeyedAPIFactory<PushMessagingAPI>::DeclareFactoryDependencies() { 391 DependsOn(ExtensionRegistryFactory::GetInstance()); 392 DependsOn(ExtensionsBrowserClient::Get()->GetExtensionSystemFactory()); 393 DependsOn(invalidation::ProfileInvalidationProviderFactory::GetInstance()); 394} 395 396} // namespace extensions 397