push_messaging_api.cc revision 2a99a7e74a7f215066514fe81d2bfa6639d9eddd
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/profiles/profile.h" 23#include "chrome/browser/signin/token_service.h" 24#include "chrome/browser/signin/token_service_factory.h" 25#include "chrome/browser/sync/profile_sync_service.h" 26#include "chrome/browser/sync/profile_sync_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 login_ui_service->AddObserver(this); 110 // OnLoginUICLosed will be called when UI is closed. 111 login_ui_service->ShowLoginPopup(); 112 return true; 113 } else { 114 error_ = kUserNotSignedIn; 115 ReportResult(std::string(), error_); 116 return false; 117 } 118 } 119 120 return StartGaiaIdFetch(); 121} 122 123bool PushMessagingGetChannelIdFunction::StartGaiaIdFetch() { 124 // Start the async fetch of the GAIA ID. 125 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 126 net::URLRequestContextGetter* context = profile()->GetRequestContext(); 127 TokenService* token_service = TokenServiceFactory::GetForProfile(profile()); 128 if (!token_service) { 129 ReportResult(std::string(), std::string(kTokenServiceNotAvailable)); 130 return false; 131 } 132 const std::string& refresh_token = 133 token_service->GetOAuth2LoginRefreshToken(); 134 fetcher_.reset(new ObfuscatedGaiaIdFetcher(context, this, refresh_token)); 135 136 // Get the token cache and see if we have already cached a Gaia Id. 137 TokenCacheService* token_cache = 138 TokenCacheServiceFactory::GetForProfile(profile()); 139 140 // Check the cache, if we already have a gaia ID, use it instead of 141 // fetching the ID over the network. 142 const std::string& gaia_id = 143 token_cache->RetrieveToken(GaiaConstants::kObfuscatedGaiaId); 144 if (!gaia_id.empty()) { 145 ReportResult(gaia_id, std::string()); 146 return true; 147 } 148 149 fetcher_->Start(); 150 151 // Will finish asynchronously. 152 return true; 153} 154 155// Check if the user is logged in. 156bool PushMessagingGetChannelIdFunction::IsUserLoggedIn() const { 157 TokenService* token_service = TokenServiceFactory::GetForProfile(profile()); 158 if (!token_service) 159 return false; 160 return token_service->HasOAuthLoginToken(); 161} 162 163void PushMessagingGetChannelIdFunction::OnLoginUIShown( 164 LoginUIService::LoginUI* ui) { 165 // Do nothing when login ui is shown. 166} 167 168// If the login succeeds, continue with our logic to fetch the ChannelId. 169void PushMessagingGetChannelIdFunction::OnLoginUIClosed( 170 LoginUIService::LoginUI* ui) { 171 LoginUIService* login_ui_service = 172 LoginUIServiceFactory::GetForProfile(profile()); 173 login_ui_service->RemoveObserver(this); 174 if (!StartGaiaIdFetch()) { 175 SendResponse(false); 176 } 177} 178 179void PushMessagingGetChannelIdFunction::ReportResult( 180 const std::string& gaia_id, const std::string& error_string) { 181 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 182 183 BuildAndSendResult(gaia_id, error_string); 184 185 // Cache the obfuscated ID locally. It never changes for this user, 186 // and if we call the web API too often, we get errors due to rate limiting. 187 if (!gaia_id.empty()) { 188 base::TimeDelta timeout = 189 base::TimeDelta::FromDays(kObfuscatedGaiaIdTimeoutInDays); 190 TokenCacheService* token_cache = 191 TokenCacheServiceFactory::GetForProfile(profile()); 192 token_cache->StoreToken(GaiaConstants::kObfuscatedGaiaId, gaia_id, 193 timeout); 194 } 195 196 // Balanced in RunImpl. 197 Release(); 198} 199 200void PushMessagingGetChannelIdFunction::BuildAndSendResult( 201 const std::string& gaia_id, const std::string& error_message) { 202 std::string channel_id; 203 if (!gaia_id.empty()) { 204 channel_id = gaia_id; 205 channel_id += kChannelIdSeparator; 206 channel_id += extension_id(); 207 } 208 209 // TODO(petewil): It may be a good idea to further 210 // obfuscate the channel ID to prevent the user's obfuscated GAIA ID 211 // from being readily obtained. Security review will tell us if we need to. 212 213 // Create a ChannelId results object and set the fields. 214 glue::ChannelIdResult result; 215 result.channel_id = channel_id; 216 SetError(error_message); 217 results_ = glue::GetChannelId::Results::Create(result); 218 219 bool success = error_message.empty() && !gaia_id.empty(); 220 SendResponse(success); 221} 222 223void PushMessagingGetChannelIdFunction::OnObfuscatedGaiaIdFetchSuccess( 224 const std::string& gaia_id) { 225 ReportResult(gaia_id, std::string()); 226} 227 228void PushMessagingGetChannelIdFunction::OnObfuscatedGaiaIdFetchFailure( 229 const GoogleServiceAuthError& error) { 230 std::string error_text = error.error_message(); 231 // If the error message is blank, see if we can set it from the state. 232 if (error_text.empty() && 233 (0 != error.state())) { 234 error_text = base::IntToString(error.state()); 235 } 236 237 ReportResult(std::string(), error_text); 238 DVLOG(1) << "GetChannelId status: '" << error_text << "'"; 239} 240 241PushMessagingAPI::PushMessagingAPI(Profile* profile) : profile_(profile) { 242 registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_INSTALLED, 243 content::Source<Profile>(profile_->GetOriginalProfile())); 244 registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_LOADED, 245 content::Source<Profile>(profile_->GetOriginalProfile())); 246 registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_UNLOADED, 247 content::Source<Profile>(profile_->GetOriginalProfile())); 248} 249 250PushMessagingAPI::~PushMessagingAPI() { 251} 252 253// static 254PushMessagingAPI* PushMessagingAPI::Get(Profile* profile) { 255 return ProfileKeyedAPIFactory<PushMessagingAPI>::GetForProfile(profile); 256} 257 258void PushMessagingAPI::Shutdown() { 259 event_router_.reset(); 260 handler_.reset(); 261} 262 263static base::LazyInstance<ProfileKeyedAPIFactory<PushMessagingAPI> > 264g_factory = LAZY_INSTANCE_INITIALIZER; 265 266// static 267ProfileKeyedAPIFactory<PushMessagingAPI>* 268PushMessagingAPI::GetFactoryInstance() { 269 return &g_factory.Get(); 270} 271 272void PushMessagingAPI::Observe(int type, 273 const content::NotificationSource& source, 274 const content::NotificationDetails& details) { 275 ProfileSyncService* pss = ProfileSyncServiceFactory::GetForProfile(profile_); 276 // This may be NULL; for example, for the ChromeOS guest user. In these cases, 277 // just return without setting up anything, since it won't work anyway. 278 if (!pss) 279 return; 280 281 if (!event_router_) 282 event_router_.reset(new PushMessagingEventRouter(profile_)); 283 if (!handler_) { 284 handler_.reset(new PushMessagingInvalidationHandler( 285 pss, event_router_.get())); 286 } 287 switch (type) { 288 case chrome::NOTIFICATION_EXTENSION_INSTALLED: { 289 const Extension* extension = content::Details<Extension>(details).ptr(); 290 if (extension->HasAPIPermission(APIPermission::kPushMessaging)) { 291 handler_->SuppressInitialInvalidationsForExtension(extension->id()); 292 } 293 break; 294 } 295 case chrome::NOTIFICATION_EXTENSION_LOADED: { 296 const Extension* extension = content::Details<Extension>(details).ptr(); 297 if (extension->HasAPIPermission(APIPermission::kPushMessaging)) { 298 handler_->RegisterExtension(extension->id()); 299 } 300 break; 301 } 302 case chrome::NOTIFICATION_EXTENSION_UNLOADED: { 303 const Extension* extension = 304 content::Details<UnloadedExtensionInfo>(details)->extension; 305 if (extension->HasAPIPermission(APIPermission::kPushMessaging)) { 306 handler_->UnregisterExtension(extension->id()); 307 } 308 break; 309 } 310 default: 311 NOTREACHED(); 312 } 313} 314 315void PushMessagingAPI::SetMapperForTest( 316 scoped_ptr<PushMessagingInvalidationMapper> mapper) { 317 handler_ = mapper.Pass(); 318} 319 320template <> 321void ProfileKeyedAPIFactory<PushMessagingAPI>::DeclareFactoryDependencies() { 322 DependsOn(ExtensionSystemFactory::GetInstance()); 323 DependsOn(ProfileSyncServiceFactory::GetInstance()); 324} 325 326} // namespace extensions 327