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