push_messaging_api.cc revision eb525c5499e34cc9c4b825d6d9e75bb07cc06ace
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/extensions/api/push_messaging/push_messaging_invalidation_handler.h" 15#include "chrome/browser/extensions/event_names.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/token_service.h" 26#include "chrome/browser/signin/token_service_factory.h" 27#include "chrome/browser/ui/webui/signin/login_ui_service_factory.h" 28#include "chrome/common/chrome_notification_types.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 "googleurl/src/gurl.h" 38 39using content::BrowserThread; 40 41namespace { 42const char kChannelIdSeparator[] = "/"; 43const char kUserNotSignedIn[] = "The user is not signed in."; 44const char kTokenServiceNotAvailable[] = "Failed to get token service."; 45const int kObfuscatedGaiaIdTimeoutInDays = 30; 46} 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(new extensions::Event( 79 event_names::kOnPushMessage, args.Pass())); 80 event->restrict_to_profile = profile_; 81 ExtensionSystem::Get(profile_)->event_router()->DispatchEventToExtension( 82 extension_id, event.Pass()); 83} 84 85// GetChannelId class functions 86 87PushMessagingGetChannelIdFunction::PushMessagingGetChannelIdFunction() 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 LoginUIService* login_ui_service = 108 LoginUIServiceFactory::GetForProfile(profile()); 109 TokenService* token_service = TokenServiceFactory::GetForProfile( 110 profile()); 111 // Register for token available so we can tell when the logon is done. 112 // Observe() will be called if token is issued. 113 registrar_.Add(this, 114 chrome::NOTIFICATION_TOKEN_AVAILABLE, 115 content::Source<TokenService>(token_service)); 116 login_ui_service->ShowLoginPopup(); 117 return true; 118 } else { 119 error_ = kUserNotSignedIn; 120 ReportResult(std::string(), error_); 121 return false; 122 } 123 } 124 125 return StartGaiaIdFetch(); 126} 127 128// If we are in an interactive login and it succeeds, start token fetch. 129void PushMessagingGetChannelIdFunction::Observe( 130 int type, 131 const content::NotificationSource& source, 132 const content::NotificationDetails& details) { 133 DCHECK_EQ(chrome::NOTIFICATION_TOKEN_AVAILABLE, type); 134 135 TokenService::TokenAvailableDetails* token_details = 136 content::Details<TokenService::TokenAvailableDetails>(details).ptr(); 137 if (token_details->service() == GaiaConstants::kGaiaOAuth2LoginRefreshToken) { 138 TokenService* token_service = TokenServiceFactory::GetForProfile(profile()); 139 registrar_.Remove(this, 140 chrome::NOTIFICATION_TOKEN_AVAILABLE, 141 content::Source<TokenService>(token_service)); 142 // If we got a token, the logon succeeded, continue fetching Obfuscated 143 // Gaia Id. 144 if (!StartGaiaIdFetch()) 145 SendResponse(false); 146 } 147} 148 149bool PushMessagingGetChannelIdFunction::StartGaiaIdFetch() { 150 // Start the async fetch of the Gaia Id. 151 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 152 net::URLRequestContextGetter* context = profile()->GetRequestContext(); 153 TokenService* token_service = TokenServiceFactory::GetForProfile(profile()); 154 if (!token_service) { 155 ReportResult(std::string(), std::string(kTokenServiceNotAvailable)); 156 return false; 157 } 158 const std::string& refresh_token = 159 token_service->GetOAuth2LoginRefreshToken(); 160 fetcher_.reset(new ObfuscatedGaiaIdFetcher(context, this, refresh_token)); 161 162 // Get the token cache and see if we have already cached a Gaia Id. 163 TokenCacheService* token_cache = 164 TokenCacheServiceFactory::GetForProfile(profile()); 165 166 // Check the cache, if we already have a Gaia ID, use it instead of 167 // fetching the ID over the network. 168 const std::string& gaia_id = 169 token_cache->RetrieveToken(GaiaConstants::kObfuscatedGaiaId); 170 if (!gaia_id.empty()) { 171 ReportResult(gaia_id, std::string()); 172 return true; 173 } 174 175 fetcher_->Start(); 176 177 // Will finish asynchronously. 178 return true; 179} 180 181// Check if the user is logged in. 182bool PushMessagingGetChannelIdFunction::IsUserLoggedIn() const { 183 TokenService* token_service = TokenServiceFactory::GetForProfile(profile()); 184 if (!token_service) 185 return false; 186 return token_service->HasOAuthLoginToken(); 187} 188 189void PushMessagingGetChannelIdFunction::ReportResult( 190 const std::string& gaia_id, const std::string& error_string) { 191 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 192 193 BuildAndSendResult(gaia_id, error_string); 194 195 // Cache the obfuscated ID locally. It never changes for this user, 196 // and if we call the web API too often, we get errors due to rate limiting. 197 if (!gaia_id.empty()) { 198 base::TimeDelta timeout = 199 base::TimeDelta::FromDays(kObfuscatedGaiaIdTimeoutInDays); 200 TokenCacheService* token_cache = 201 TokenCacheServiceFactory::GetForProfile(profile()); 202 token_cache->StoreToken(GaiaConstants::kObfuscatedGaiaId, gaia_id, 203 timeout); 204 } 205 206 // Balanced in RunImpl. 207 Release(); 208} 209 210void PushMessagingGetChannelIdFunction::BuildAndSendResult( 211 const std::string& gaia_id, const std::string& error_message) { 212 std::string channel_id; 213 if (!gaia_id.empty()) { 214 channel_id = gaia_id; 215 channel_id += kChannelIdSeparator; 216 channel_id += extension_id(); 217 } 218 219 // TODO(petewil): It may be a good idea to further 220 // obfuscate the channel ID to prevent the user's obfuscated Gaia Id 221 // from being readily obtained. Security review will tell us if we need to. 222 223 // Create a ChannelId results object and set the fields. 224 glue::ChannelIdResult result; 225 result.channel_id = channel_id; 226 SetError(error_message); 227 results_ = glue::GetChannelId::Results::Create(result); 228 229 bool success = error_message.empty() && !gaia_id.empty(); 230 SendResponse(success); 231} 232 233void PushMessagingGetChannelIdFunction::OnObfuscatedGaiaIdFetchSuccess( 234 const std::string& gaia_id) { 235 ReportResult(gaia_id, std::string()); 236} 237 238void PushMessagingGetChannelIdFunction::OnObfuscatedGaiaIdFetchFailure( 239 const GoogleServiceAuthError& error) { 240 std::string error_text = error.error_message(); 241 // If the error message is blank, see if we can set it from the state. 242 if (error_text.empty() && 243 (0 != error.state())) { 244 error_text = base::IntToString(error.state()); 245 } 246 247 DVLOG(1) << "GetChannelId status: '" << error_text << "'"; 248 249 // If we had bad credentials, try the logon again. 250 switch (error.state()) { 251 case GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS: 252 case GoogleServiceAuthError::ACCOUNT_DELETED: 253 case GoogleServiceAuthError::ACCOUNT_DISABLED: { 254 if (interactive_) { 255 LoginUIService* login_ui_service = 256 LoginUIServiceFactory::GetForProfile(profile()); 257 // content::NotificationObserver will be called if token is issued. 258 login_ui_service->ShowLoginPopup(); 259 } else { 260 ReportResult(std::string(), error_text); 261 } 262 return; 263 } 264 default: 265 // Return error to caller. 266 ReportResult(std::string(), error_text); 267 return; 268 } 269} 270 271PushMessagingAPI::PushMessagingAPI(Profile* profile) : profile_(profile) { 272 registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_INSTALLED, 273 content::Source<Profile>(profile_->GetOriginalProfile())); 274 registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_LOADED, 275 content::Source<Profile>(profile_->GetOriginalProfile())); 276 registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_UNLOADED, 277 content::Source<Profile>(profile_->GetOriginalProfile())); 278} 279 280PushMessagingAPI::~PushMessagingAPI() { 281} 282 283// static 284PushMessagingAPI* PushMessagingAPI::Get(Profile* profile) { 285 return ProfileKeyedAPIFactory<PushMessagingAPI>::GetForProfile(profile); 286} 287 288void PushMessagingAPI::Shutdown() { 289 event_router_.reset(); 290 handler_.reset(); 291} 292 293static base::LazyInstance<ProfileKeyedAPIFactory<PushMessagingAPI> > 294g_factory = LAZY_INSTANCE_INITIALIZER; 295 296// static 297ProfileKeyedAPIFactory<PushMessagingAPI>* 298PushMessagingAPI::GetFactoryInstance() { 299 return &g_factory.Get(); 300} 301 302void PushMessagingAPI::Observe(int type, 303 const content::NotificationSource& source, 304 const content::NotificationDetails& details) { 305 invalidation::InvalidationService* invalidation_service = 306 invalidation::InvalidationServiceFactory::GetForProfile(profile_); 307 // This may be NULL; for example, for the ChromeOS guest user. In these cases, 308 // just return without setting up anything, since it won't work anyway. 309 if (!invalidation_service) 310 return; 311 312 if (!event_router_) 313 event_router_.reset(new PushMessagingEventRouter(profile_)); 314 if (!handler_) { 315 handler_.reset(new PushMessagingInvalidationHandler( 316 invalidation_service, event_router_.get())); 317 } 318 switch (type) { 319 case chrome::NOTIFICATION_EXTENSION_INSTALLED: { 320 const Extension* extension = 321 content::Details<const InstalledExtensionInfo>(details)->extension; 322 if (extension->HasAPIPermission(APIPermission::kPushMessaging)) { 323 handler_->SuppressInitialInvalidationsForExtension(extension->id()); 324 } 325 break; 326 } 327 case chrome::NOTIFICATION_EXTENSION_LOADED: { 328 const Extension* extension = content::Details<Extension>(details).ptr(); 329 if (extension->HasAPIPermission(APIPermission::kPushMessaging)) { 330 handler_->RegisterExtension(extension->id()); 331 } 332 break; 333 } 334 case chrome::NOTIFICATION_EXTENSION_UNLOADED: { 335 const Extension* extension = 336 content::Details<UnloadedExtensionInfo>(details)->extension; 337 if (extension->HasAPIPermission(APIPermission::kPushMessaging)) { 338 handler_->UnregisterExtension(extension->id()); 339 } 340 break; 341 } 342 default: 343 NOTREACHED(); 344 } 345} 346 347void PushMessagingAPI::SetMapperForTest( 348 scoped_ptr<PushMessagingInvalidationMapper> mapper) { 349 handler_ = mapper.Pass(); 350} 351 352template <> 353void ProfileKeyedAPIFactory<PushMessagingAPI>::DeclareFactoryDependencies() { 354 DependsOn(ExtensionSystemFactory::GetInstance()); 355 DependsOn(invalidation::InvalidationServiceFactory::GetInstance()); 356} 357 358} // namespace extensions 359