Home | History | Annotate | Download | only in push_messaging
      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