// Copyright (c) 2012 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "chrome/browser/extensions/api/push_messaging/push_messaging_api.h" #include #include "base/bind.h" #include "base/lazy_instance.h" #include "base/logging.h" #include "base/strings/string_number_conversions.h" #include "base/values.h" #include "chrome/browser/chrome_notification_types.h" #include "chrome/browser/extensions/api/push_messaging/push_messaging_invalidation_handler.h" #include "chrome/browser/extensions/extension_service.h" #include "chrome/browser/extensions/token_cache/token_cache_service.h" #include "chrome/browser/extensions/token_cache/token_cache_service_factory.h" #include "chrome/browser/invalidation/invalidation_service_factory.h" #include "chrome/browser/profiles/profile.h" #include "chrome/browser/signin/profile_oauth2_token_service_factory.h" #include "chrome/browser/signin/signin_manager_factory.h" #include "chrome/common/extensions/api/push_messaging.h" #include "components/invalidation/invalidation_service.h" #include "components/signin/core/browser/profile_oauth2_token_service.h" #include "components/signin/core/browser/signin_manager.h" #include "content/public/browser/browser_thread.h" #include "content/public/browser/notification_details.h" #include "content/public/browser/notification_source.h" #include "extensions/browser/event_router.h" #include "extensions/browser/extension_registry.h" #include "extensions/browser/extension_system_provider.h" #include "extensions/browser/extensions_browser_client.h" #include "extensions/common/extension.h" #include "extensions/common/permissions/api_permission.h" #include "google_apis/gaia/gaia_constants.h" #include "google_apis/gaia/identity_provider.h" using content::BrowserThread; const char kChannelIdSeparator[] = "/"; const char kUserNotSignedIn[] = "The user is not signed in."; const char kUserAccessTokenFailure[] = "Cannot obtain access token for the user."; const char kAPINotAvailableForUser[] = "The API is not available for this user."; const int kObfuscatedGaiaIdTimeoutInDays = 30; namespace extensions { namespace glue = api::push_messaging; PushMessagingEventRouter::PushMessagingEventRouter(Profile* profile) : profile_(profile) { } PushMessagingEventRouter::~PushMessagingEventRouter() {} void PushMessagingEventRouter::TriggerMessageForTest( const std::string& extension_id, int subchannel, const std::string& payload) { OnMessage(extension_id, subchannel, payload); } void PushMessagingEventRouter::OnMessage(const std::string& extension_id, int subchannel, const std::string& payload) { glue::Message message; message.subchannel_id = subchannel; message.payload = payload; DVLOG(2) << "PushMessagingEventRouter::OnMessage" << " payload = '" << payload << "' subchannel = '" << subchannel << "' extension = '" << extension_id << "'"; scoped_ptr args(glue::OnMessage::Create(message)); scoped_ptr event(new Event(glue::OnMessage::kEventName, args.Pass())); event->restrict_to_browser_context = profile_; EventRouter::Get(profile_)->DispatchEventToExtension( extension_id, event.Pass()); } // GetChannelId class functions PushMessagingGetChannelIdFunction::PushMessagingGetChannelIdFunction() : OAuth2TokenService::Consumer("push_messaging"), interactive_(false) {} PushMessagingGetChannelIdFunction::~PushMessagingGetChannelIdFunction() {} bool PushMessagingGetChannelIdFunction::RunAsync() { // Fetch the function arguments. scoped_ptr params( glue::GetChannelId::Params::Create(*args_)); EXTENSION_FUNCTION_VALIDATE(params.get()); if (params && params->interactive) { interactive_ = *params->interactive; } // Balanced in ReportResult() AddRef(); invalidation::InvalidationService* invalidation_service = invalidation::InvalidationServiceFactory::GetForProfile(GetProfile()); if (!invalidation_service) { error_ = kAPINotAvailableForUser; ReportResult(std::string(), error_); return false; } IdentityProvider* identity_provider = invalidation_service->GetIdentityProvider(); if (!identity_provider->GetTokenService()->RefreshTokenIsAvailable( identity_provider->GetActiveAccountId())) { if (interactive_ && identity_provider->RequestLogin()) { identity_provider->AddActiveAccountRefreshTokenObserver(this); return true; } else { error_ = kUserNotSignedIn; ReportResult(std::string(), error_); return false; } } DVLOG(2) << "Logged in profile name: " << GetProfile()->GetProfileName(); StartAccessTokenFetch(); return true; } void PushMessagingGetChannelIdFunction::StartAccessTokenFetch() { invalidation::InvalidationService* invalidation_service = invalidation::InvalidationServiceFactory::GetForProfile(GetProfile()); CHECK(invalidation_service); IdentityProvider* identity_provider = invalidation_service->GetIdentityProvider(); std::vector scope_vector = extensions::ObfuscatedGaiaIdFetcher::GetScopes(); OAuth2TokenService::ScopeSet scopes(scope_vector.begin(), scope_vector.end()); fetcher_access_token_request_ = identity_provider->GetTokenService()->StartRequest( identity_provider->GetActiveAccountId(), scopes, this); } void PushMessagingGetChannelIdFunction::OnRefreshTokenAvailable( const std::string& account_id) { invalidation::InvalidationService* invalidation_service = invalidation::InvalidationServiceFactory::GetForProfile(GetProfile()); CHECK(invalidation_service); invalidation_service->GetIdentityProvider()-> RemoveActiveAccountRefreshTokenObserver(this); DVLOG(2) << "Newly logged in: " << GetProfile()->GetProfileName(); StartAccessTokenFetch(); } void PushMessagingGetChannelIdFunction::OnGetTokenSuccess( const OAuth2TokenService::Request* request, const std::string& access_token, const base::Time& expiration_time) { DCHECK_EQ(fetcher_access_token_request_.get(), request); fetcher_access_token_request_.reset(); StartGaiaIdFetch(access_token); } void PushMessagingGetChannelIdFunction::OnGetTokenFailure( const OAuth2TokenService::Request* request, const GoogleServiceAuthError& error) { DCHECK_EQ(fetcher_access_token_request_.get(), request); fetcher_access_token_request_.reset(); // TODO(fgorski): We are currently ignoring the error passed in upon failure. // It should be revisited when we are working on improving general error // handling for the identity related code. DVLOG(1) << "Cannot obtain access token for this user " << error.error_message() << " " << error.state(); error_ = kUserAccessTokenFailure; ReportResult(std::string(), error_); } void PushMessagingGetChannelIdFunction::StartGaiaIdFetch( const std::string& access_token) { // Start the async fetch of the Gaia Id. DCHECK_CURRENTLY_ON(BrowserThread::UI); net::URLRequestContextGetter* context = GetProfile()->GetRequestContext(); fetcher_.reset(new ObfuscatedGaiaIdFetcher(context, this, access_token)); // Get the token cache and see if we have already cached a Gaia Id. TokenCacheService* token_cache = TokenCacheServiceFactory::GetForProfile(GetProfile()); // Check the cache, if we already have a Gaia ID, use it instead of // fetching the ID over the network. const std::string& gaia_id = token_cache->RetrieveToken(GaiaConstants::kObfuscatedGaiaId); if (!gaia_id.empty()) { ReportResult(gaia_id, std::string()); return; } fetcher_->Start(); } void PushMessagingGetChannelIdFunction::ReportResult( const std::string& gaia_id, const std::string& error_string) { DCHECK_CURRENTLY_ON(BrowserThread::UI); BuildAndSendResult(gaia_id, error_string); // Cache the obfuscated ID locally. It never changes for this user, // and if we call the web API too often, we get errors due to rate limiting. if (!gaia_id.empty()) { base::TimeDelta timeout = base::TimeDelta::FromDays(kObfuscatedGaiaIdTimeoutInDays); TokenCacheService* token_cache = TokenCacheServiceFactory::GetForProfile(GetProfile()); token_cache->StoreToken(GaiaConstants::kObfuscatedGaiaId, gaia_id, timeout); } // Balanced in RunAsync. Release(); } void PushMessagingGetChannelIdFunction::BuildAndSendResult( const std::string& gaia_id, const std::string& error_message) { std::string channel_id; if (!gaia_id.empty()) { channel_id = gaia_id; channel_id += kChannelIdSeparator; channel_id += extension_id(); } // TODO(petewil): It may be a good idea to further // obfuscate the channel ID to prevent the user's obfuscated Gaia Id // from being readily obtained. Security review will tell us if we need to. // Create a ChannelId results object and set the fields. glue::ChannelIdResult result; result.channel_id = channel_id; SetError(error_message); results_ = glue::GetChannelId::Results::Create(result); bool success = error_message.empty() && !gaia_id.empty(); SendResponse(success); } void PushMessagingGetChannelIdFunction::OnObfuscatedGaiaIdFetchSuccess( const std::string& gaia_id) { ReportResult(gaia_id, std::string()); } void PushMessagingGetChannelIdFunction::OnObfuscatedGaiaIdFetchFailure( const GoogleServiceAuthError& error) { std::string error_text = error.error_message(); // If the error message is blank, see if we can set it from the state. if (error_text.empty() && (0 != error.state())) { error_text = base::IntToString(error.state()); } DVLOG(1) << "GetChannelId status: '" << error_text << "'"; // If we had bad credentials, try the logon again. switch (error.state()) { case GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS: case GoogleServiceAuthError::ACCOUNT_DELETED: case GoogleServiceAuthError::ACCOUNT_DISABLED: { invalidation::InvalidationService* invalidation_service = invalidation::InvalidationServiceFactory::GetForProfile(GetProfile()); CHECK(invalidation_service); if (!interactive_ || !invalidation_service->GetIdentityProvider()->RequestLogin()) { ReportResult(std::string(), error_text); } return; } default: // Return error to caller. ReportResult(std::string(), error_text); return; } } PushMessagingAPI::PushMessagingAPI(content::BrowserContext* context) : extension_registry_observer_(this), profile_(Profile::FromBrowserContext(context)) { extension_registry_observer_.Add(ExtensionRegistry::Get(profile_)); registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_INSTALLED_DEPRECATED, content::Source(profile_->GetOriginalProfile())); } PushMessagingAPI::~PushMessagingAPI() { } // static PushMessagingAPI* PushMessagingAPI::Get(content::BrowserContext* context) { return BrowserContextKeyedAPIFactory::Get(context); } void PushMessagingAPI::Shutdown() { event_router_.reset(); handler_.reset(); } static base::LazyInstance > g_factory = LAZY_INSTANCE_INITIALIZER; // static BrowserContextKeyedAPIFactory* PushMessagingAPI::GetFactoryInstance() { return g_factory.Pointer(); } bool PushMessagingAPI::InitEventRouterAndHandler() { invalidation::InvalidationService* invalidation_service = invalidation::InvalidationServiceFactory::GetForProfile(profile_); if (!invalidation_service) return false; if (!event_router_) event_router_.reset(new PushMessagingEventRouter(profile_)); if (!handler_) { handler_.reset(new PushMessagingInvalidationHandler(invalidation_service, event_router_.get())); } return true; } void PushMessagingAPI::OnExtensionLoaded( content::BrowserContext* browser_context, const Extension* extension) { if (!InitEventRouterAndHandler()) return; if (extension->HasAPIPermission(APIPermission::kPushMessaging)) { handler_->RegisterExtension(extension->id()); } } void PushMessagingAPI::OnExtensionUnloaded( content::BrowserContext* browser_context, const Extension* extension, UnloadedExtensionInfo::Reason reason) { if (!InitEventRouterAndHandler()) return; if (extension->HasAPIPermission(APIPermission::kPushMessaging)) { handler_->UnregisterExtension(extension->id()); } } void PushMessagingAPI::Observe(int type, const content::NotificationSource& source, const content::NotificationDetails& details) { DCHECK_EQ(type, chrome::NOTIFICATION_EXTENSION_INSTALLED_DEPRECATED); if (!InitEventRouterAndHandler()) return; const Extension* extension = content::Details(details)->extension; if (extension->HasAPIPermission(APIPermission::kPushMessaging)) { handler_->SuppressInitialInvalidationsForExtension(extension->id()); } } void PushMessagingAPI::SetMapperForTest( scoped_ptr mapper) { handler_ = mapper.Pass(); } template <> void BrowserContextKeyedAPIFactory::DeclareFactoryDependencies() { DependsOn(ExtensionsBrowserClient::Get()->GetExtensionSystemFactory()); DependsOn(invalidation::InvalidationServiceFactory::GetInstance()); } } // namespace extensions