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 39 using content::BrowserThread; 40 41 namespace extensions { 42 43 namespace { 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 } 52 53 namespace glue = api::push_messaging; 54 55 PushMessagingEventRouter::PushMessagingEventRouter( 56 content::BrowserContext* context) 57 : browser_context_(context) { 58 } 59 60 PushMessagingEventRouter::~PushMessagingEventRouter() {} 61 62 void PushMessagingEventRouter::TriggerMessageForTest( 63 const std::string& extension_id, 64 int subchannel, 65 const std::string& payload) { 66 OnMessage(extension_id, subchannel, payload); 67 } 68 69 void 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 90 PushMessagingGetChannelIdFunction::PushMessagingGetChannelIdFunction() 91 : OAuth2TokenService::Consumer("push_messaging"), 92 interactive_(false) {} 93 94 PushMessagingGetChannelIdFunction::~PushMessagingGetChannelIdFunction() {} 95 96 bool 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 138 void 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 153 void 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 165 void 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 175 void 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 190 void 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 213 void 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 234 void 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 257 void PushMessagingGetChannelIdFunction::OnObfuscatedGaiaIdFetchSuccess( 258 const std::string& gaia_id) { 259 ReportResult(gaia_id, std::string()); 260 } 261 262 void 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 295 PushMessagingAPI::PushMessagingAPI(content::BrowserContext* context) 296 : extension_registry_observer_(this), browser_context_(context) { 297 extension_registry_observer_.Add(ExtensionRegistry::Get(browser_context_)); 298 } 299 300 PushMessagingAPI::~PushMessagingAPI() { 301 } 302 303 // static 304 PushMessagingAPI* PushMessagingAPI::Get(content::BrowserContext* context) { 305 return BrowserContextKeyedAPIFactory<PushMessagingAPI>::Get(context); 306 } 307 308 void PushMessagingAPI::Shutdown() { 309 event_router_.reset(); 310 handler_.reset(); 311 } 312 313 static base::LazyInstance<BrowserContextKeyedAPIFactory<PushMessagingAPI> > 314 g_factory = LAZY_INSTANCE_INITIALIZER; 315 316 // static 317 BrowserContextKeyedAPIFactory<PushMessagingAPI>* 318 PushMessagingAPI::GetFactoryInstance() { 319 return g_factory.Pointer(); 320 } 321 322 bool 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 340 void 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 352 void 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 365 void 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 378 void PushMessagingAPI::SetMapperForTest( 379 scoped_ptr<PushMessagingInvalidationMapper> mapper) { 380 handler_ = mapper.Pass(); 381 } 382 383 template <> 384 void 385 BrowserContextKeyedAPIFactory<PushMessagingAPI>::DeclareFactoryDependencies() { 386 DependsOn(ExtensionRegistryFactory::GetInstance()); 387 DependsOn(ExtensionsBrowserClient::Get()->GetExtensionSystemFactory()); 388 DependsOn(invalidation::ProfileInvalidationProviderFactory::GetInstance()); 389 } 390 391 } // namespace extensions 392