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