push_messaging_api.cc revision 5d1f7b1de12d16ceb2c938c56701a3e8bfa558f7
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.h" 20#include "chrome/browser/invalidation/invalidation_service_factory.h" 21#include "chrome/browser/profiles/profile.h" 22#include "chrome/browser/signin/profile_oauth2_token_service.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/browser/ui/webui/signin/login_ui_service_factory.h" 27#include "chrome/common/extensions/api/push_messaging.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 if (interactive_) { 107 ProfileOAuth2TokenServiceFactory::GetForProfile(GetProfile()) 108 ->AddObserver(this); 109 LoginUIServiceFactory::GetForProfile(GetProfile())->ShowLoginPopup(); 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 ProfileOAuth2TokenService* token_service = 129 ProfileOAuth2TokenServiceFactory::GetForProfile(GetProfile()); 130 SigninManagerBase* signin_manager = 131 SigninManagerFactory::GetForProfile(GetProfile()); 132 fetcher_access_token_request_ = token_service->StartRequest( 133 signin_manager->GetAuthenticatedAccountId(), scopes, this); 134} 135 136void PushMessagingGetChannelIdFunction::OnRefreshTokenAvailable( 137 const std::string& account_id) { 138 ProfileOAuth2TokenServiceFactory::GetForProfile(GetProfile()) 139 ->RemoveObserver(this); 140 DVLOG(2) << "Newly logged in: " << GetProfile()->GetProfileName(); 141 StartAccessTokenFetch(); 142} 143 144void PushMessagingGetChannelIdFunction::OnGetTokenSuccess( 145 const OAuth2TokenService::Request* request, 146 const std::string& access_token, 147 const base::Time& expiration_time) { 148 DCHECK_EQ(fetcher_access_token_request_.get(), request); 149 fetcher_access_token_request_.reset(); 150 151 StartGaiaIdFetch(access_token); 152} 153 154void PushMessagingGetChannelIdFunction::OnGetTokenFailure( 155 const OAuth2TokenService::Request* request, 156 const GoogleServiceAuthError& error) { 157 DCHECK_EQ(fetcher_access_token_request_.get(), request); 158 fetcher_access_token_request_.reset(); 159 160 // TODO(fgorski): We are currently ignoring the error passed in upon failure. 161 // It should be revisited when we are working on improving general error 162 // handling for the identity related code. 163 DVLOG(1) << "Cannot obtain access token for this user " 164 << error.error_message() << " " << error.state(); 165 error_ = kUserAccessTokenFailure; 166 ReportResult(std::string(), error_); 167} 168 169void PushMessagingGetChannelIdFunction::StartGaiaIdFetch( 170 const std::string& access_token) { 171 // Start the async fetch of the Gaia Id. 172 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 173 net::URLRequestContextGetter* context = GetProfile()->GetRequestContext(); 174 fetcher_.reset(new ObfuscatedGaiaIdFetcher(context, this, access_token)); 175 176 // Get the token cache and see if we have already cached a Gaia Id. 177 TokenCacheService* token_cache = 178 TokenCacheServiceFactory::GetForProfile(GetProfile()); 179 180 // Check the cache, if we already have a Gaia ID, use it instead of 181 // fetching the ID over the network. 182 const std::string& gaia_id = 183 token_cache->RetrieveToken(GaiaConstants::kObfuscatedGaiaId); 184 if (!gaia_id.empty()) { 185 ReportResult(gaia_id, std::string()); 186 return; 187 } 188 189 fetcher_->Start(); 190} 191 192// Check if the user is logged in. 193bool PushMessagingGetChannelIdFunction::IsUserLoggedIn() const { 194 ProfileOAuth2TokenService* token_service = 195 ProfileOAuth2TokenServiceFactory::GetForProfile(GetProfile()); 196 SigninManagerBase* signin_manager = 197 SigninManagerFactory::GetForProfile(GetProfile()); 198 return token_service->RefreshTokenIsAvailable( 199 signin_manager->GetAuthenticatedAccountId()); 200} 201 202void PushMessagingGetChannelIdFunction::ReportResult( 203 const std::string& gaia_id, const std::string& error_string) { 204 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 205 206 BuildAndSendResult(gaia_id, error_string); 207 208 // Cache the obfuscated ID locally. It never changes for this user, 209 // and if we call the web API too often, we get errors due to rate limiting. 210 if (!gaia_id.empty()) { 211 base::TimeDelta timeout = 212 base::TimeDelta::FromDays(kObfuscatedGaiaIdTimeoutInDays); 213 TokenCacheService* token_cache = 214 TokenCacheServiceFactory::GetForProfile(GetProfile()); 215 token_cache->StoreToken(GaiaConstants::kObfuscatedGaiaId, gaia_id, 216 timeout); 217 } 218 219 // Balanced in RunImpl. 220 Release(); 221} 222 223void PushMessagingGetChannelIdFunction::BuildAndSendResult( 224 const std::string& gaia_id, const std::string& error_message) { 225 std::string channel_id; 226 if (!gaia_id.empty()) { 227 channel_id = gaia_id; 228 channel_id += kChannelIdSeparator; 229 channel_id += extension_id(); 230 } 231 232 // TODO(petewil): It may be a good idea to further 233 // obfuscate the channel ID to prevent the user's obfuscated Gaia Id 234 // from being readily obtained. Security review will tell us if we need to. 235 236 // Create a ChannelId results object and set the fields. 237 glue::ChannelIdResult result; 238 result.channel_id = channel_id; 239 SetError(error_message); 240 results_ = glue::GetChannelId::Results::Create(result); 241 242 bool success = error_message.empty() && !gaia_id.empty(); 243 SendResponse(success); 244} 245 246void PushMessagingGetChannelIdFunction::OnObfuscatedGaiaIdFetchSuccess( 247 const std::string& gaia_id) { 248 ReportResult(gaia_id, std::string()); 249} 250 251void PushMessagingGetChannelIdFunction::OnObfuscatedGaiaIdFetchFailure( 252 const GoogleServiceAuthError& error) { 253 std::string error_text = error.error_message(); 254 // If the error message is blank, see if we can set it from the state. 255 if (error_text.empty() && 256 (0 != error.state())) { 257 error_text = base::IntToString(error.state()); 258 } 259 260 DVLOG(1) << "GetChannelId status: '" << error_text << "'"; 261 262 // If we had bad credentials, try the logon again. 263 switch (error.state()) { 264 case GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS: 265 case GoogleServiceAuthError::ACCOUNT_DELETED: 266 case GoogleServiceAuthError::ACCOUNT_DISABLED: { 267 if (interactive_) { 268 LoginUIService* login_ui_service = 269 LoginUIServiceFactory::GetForProfile(GetProfile()); 270 // content::NotificationObserver will be called if token is issued. 271 login_ui_service->ShowLoginPopup(); 272 } else { 273 ReportResult(std::string(), error_text); 274 } 275 return; 276 } 277 default: 278 // Return error to caller. 279 ReportResult(std::string(), error_text); 280 return; 281 } 282} 283 284PushMessagingAPI::PushMessagingAPI(Profile* profile) : profile_(profile) { 285 registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_INSTALLED, 286 content::Source<Profile>(profile_->GetOriginalProfile())); 287 registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_LOADED, 288 content::Source<Profile>(profile_->GetOriginalProfile())); 289 registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_UNLOADED, 290 content::Source<Profile>(profile_->GetOriginalProfile())); 291} 292 293PushMessagingAPI::~PushMessagingAPI() { 294} 295 296// static 297PushMessagingAPI* PushMessagingAPI::Get(Profile* profile) { 298 return ProfileKeyedAPIFactory<PushMessagingAPI>::GetForProfile(profile); 299} 300 301void PushMessagingAPI::Shutdown() { 302 event_router_.reset(); 303 handler_.reset(); 304} 305 306static base::LazyInstance<ProfileKeyedAPIFactory<PushMessagingAPI> > 307g_factory = LAZY_INSTANCE_INITIALIZER; 308 309// static 310ProfileKeyedAPIFactory<PushMessagingAPI>* 311PushMessagingAPI::GetFactoryInstance() { 312 return g_factory.Pointer(); 313} 314 315void PushMessagingAPI::Observe(int type, 316 const content::NotificationSource& source, 317 const content::NotificationDetails& details) { 318 invalidation::InvalidationService* invalidation_service = 319 invalidation::InvalidationServiceFactory::GetForProfile(profile_); 320 // This may be NULL; for example, for the ChromeOS guest user. In these cases, 321 // just return without setting up anything, since it won't work anyway. 322 if (!invalidation_service) 323 return; 324 325 if (!event_router_) 326 event_router_.reset(new PushMessagingEventRouter(profile_)); 327 if (!handler_) { 328 handler_.reset(new PushMessagingInvalidationHandler( 329 invalidation_service, event_router_.get())); 330 } 331 switch (type) { 332 case chrome::NOTIFICATION_EXTENSION_INSTALLED: { 333 const Extension* extension = 334 content::Details<const InstalledExtensionInfo>(details)->extension; 335 if (extension->HasAPIPermission(APIPermission::kPushMessaging)) { 336 handler_->SuppressInitialInvalidationsForExtension(extension->id()); 337 } 338 break; 339 } 340 case chrome::NOTIFICATION_EXTENSION_LOADED: { 341 const Extension* extension = content::Details<Extension>(details).ptr(); 342 if (extension->HasAPIPermission(APIPermission::kPushMessaging)) { 343 handler_->RegisterExtension(extension->id()); 344 } 345 break; 346 } 347 case chrome::NOTIFICATION_EXTENSION_UNLOADED: { 348 const Extension* extension = 349 content::Details<UnloadedExtensionInfo>(details)->extension; 350 if (extension->HasAPIPermission(APIPermission::kPushMessaging)) { 351 handler_->UnregisterExtension(extension->id()); 352 } 353 break; 354 } 355 default: 356 NOTREACHED(); 357 } 358} 359 360void PushMessagingAPI::SetMapperForTest( 361 scoped_ptr<PushMessagingInvalidationMapper> mapper) { 362 handler_ = mapper.Pass(); 363} 364 365template <> 366void ProfileKeyedAPIFactory<PushMessagingAPI>::DeclareFactoryDependencies() { 367 DependsOn(ExtensionsBrowserClient::Get()->GetExtensionSystemFactory()); 368 DependsOn(invalidation::InvalidationServiceFactory::GetInstance()); 369} 370 371} // namespace extensions 372