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