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