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