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