push_messaging_api.cc revision 3551c9c881056c480085172ff9840cab31610854
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_router.h" 17#include "chrome/browser/extensions/extension_service.h" 18#include "chrome/browser/extensions/extension_system.h" 19#include "chrome/browser/extensions/extension_system_factory.h" 20#include "chrome/browser/extensions/token_cache/token_cache_service.h" 21#include "chrome/browser/extensions/token_cache/token_cache_service_factory.h" 22#include "chrome/browser/invalidation/invalidation_service.h" 23#include "chrome/browser/invalidation/invalidation_service_factory.h" 24#include "chrome/browser/profiles/profile.h" 25#include "chrome/browser/signin/profile_oauth2_token_service.h" 26#include "chrome/browser/signin/profile_oauth2_token_service_factory.h" 27#include "chrome/browser/ui/webui/signin/login_ui_service_factory.h" 28#include "chrome/common/extensions/api/push_messaging.h" 29#include "chrome/common/extensions/extension.h" 30#include "chrome/common/extensions/extension_set.h" 31#include "chrome/common/extensions/permissions/api_permission.h" 32#include "content/public/browser/browser_thread.h" 33#include "content/public/browser/notification_details.h" 34#include "content/public/browser/notification_source.h" 35#include "google_apis/gaia/gaia_constants.h" 36#include "url/gurl.h" 37 38using content::BrowserThread; 39 40namespace { 41const char kChannelIdSeparator[] = "/"; 42const char kUserNotSignedIn[] = "The user is not signed in."; 43const char kUserAccessTokenFailure[] = 44 "Cannot obtain access token for the user."; 45const char kTokenServiceNotAvailable[] = "Failed to get token service."; 46const int kObfuscatedGaiaIdTimeoutInDays = 30; 47} 48 49namespace extensions { 50 51namespace glue = api::push_messaging; 52 53PushMessagingEventRouter::PushMessagingEventRouter(Profile* profile) 54 : profile_(profile) { 55} 56 57PushMessagingEventRouter::~PushMessagingEventRouter() {} 58 59void PushMessagingEventRouter::TriggerMessageForTest( 60 const std::string& extension_id, 61 int subchannel, 62 const std::string& payload) { 63 OnMessage(extension_id, subchannel, payload); 64} 65 66void PushMessagingEventRouter::OnMessage(const std::string& extension_id, 67 int subchannel, 68 const std::string& payload) { 69 glue::Message message; 70 message.subchannel_id = subchannel; 71 message.payload = payload; 72 73 DVLOG(2) << "PushMessagingEventRouter::OnMessage" 74 << " payload = '" << payload 75 << "' subchannel = '" << subchannel 76 << "' extension = '" << extension_id << "'"; 77 78 scoped_ptr<base::ListValue> args(glue::OnMessage::Create(message)); 79 scoped_ptr<extensions::Event> event(new extensions::Event( 80 glue::OnMessage::kEventName, args.Pass())); 81 event->restrict_to_profile = profile_; 82 ExtensionSystem::Get(profile_)->event_router()->DispatchEventToExtension( 83 extension_id, event.Pass()); 84} 85 86// GetChannelId class functions 87 88PushMessagingGetChannelIdFunction::PushMessagingGetChannelIdFunction() 89 : interactive_(false) {} 90 91PushMessagingGetChannelIdFunction::~PushMessagingGetChannelIdFunction() {} 92 93bool PushMessagingGetChannelIdFunction::RunImpl() { 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 if (!IsUserLoggedIn()) { 107 if (interactive_) { 108 ProfileOAuth2TokenServiceFactory::GetForProfile(profile()) 109 ->AddObserver(this); 110 LoginUIServiceFactory::GetForProfile(profile())->ShowLoginPopup(); 111 return true; 112 } else { 113 error_ = kUserNotSignedIn; 114 ReportResult(std::string(), error_); 115 return false; 116 } 117 } 118 119 DVLOG(2) << "Logged in profile name: " << profile()->GetProfileName(); 120 121 StartAccessTokenFetch(); 122 return true; 123} 124 125void PushMessagingGetChannelIdFunction::StartAccessTokenFetch() { 126 std::vector<std::string> scope_vector = 127 extensions::ObfuscatedGaiaIdFetcher::GetScopes(); 128 OAuth2TokenService::ScopeSet scopes(scope_vector.begin(), scope_vector.end()); 129 fetcher_access_token_request_ = 130 ProfileOAuth2TokenServiceFactory::GetForProfile(profile()) 131 ->StartRequest(scopes, this); 132} 133 134void PushMessagingGetChannelIdFunction::OnRefreshTokenAvailable( 135 const std::string& account_id) { 136 ProfileOAuth2TokenServiceFactory::GetForProfile(profile()) 137 ->RemoveObserver(this); 138 DVLOG(2) << "Newly logged in: " << profile()->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(BrowserThread::CurrentlyOn(BrowserThread::UI)); 171 net::URLRequestContextGetter* context = profile()->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(profile()); 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() const { 192 return ProfileOAuth2TokenServiceFactory::GetForProfile(profile()) 193 ->RefreshTokenIsAvailable(); 194} 195 196void 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(profile()); 209 token_cache->StoreToken(GaiaConstants::kObfuscatedGaiaId, gaia_id, 210 timeout); 211 } 212 213 // Balanced in RunImpl. 214 Release(); 215} 216 217void 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 240void PushMessagingGetChannelIdFunction::OnObfuscatedGaiaIdFetchSuccess( 241 const std::string& gaia_id) { 242 ReportResult(gaia_id, std::string()); 243} 244 245void 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(profile()); 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 278PushMessagingAPI::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 287PushMessagingAPI::~PushMessagingAPI() { 288} 289 290// static 291PushMessagingAPI* PushMessagingAPI::Get(Profile* profile) { 292 return ProfileKeyedAPIFactory<PushMessagingAPI>::GetForProfile(profile); 293} 294 295void PushMessagingAPI::Shutdown() { 296 event_router_.reset(); 297 handler_.reset(); 298} 299 300static base::LazyInstance<ProfileKeyedAPIFactory<PushMessagingAPI> > 301g_factory = LAZY_INSTANCE_INITIALIZER; 302 303// static 304ProfileKeyedAPIFactory<PushMessagingAPI>* 305PushMessagingAPI::GetFactoryInstance() { 306 return &g_factory.Get(); 307} 308 309void 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 354void PushMessagingAPI::SetMapperForTest( 355 scoped_ptr<PushMessagingInvalidationMapper> mapper) { 356 handler_ = mapper.Pass(); 357} 358 359template <> 360void ProfileKeyedAPIFactory<PushMessagingAPI>::DeclareFactoryDependencies() { 361 DependsOn(ExtensionSystemFactory::GetInstance()); 362 DependsOn(invalidation::InvalidationServiceFactory::GetInstance()); 363} 364 365} // namespace extensions 366