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