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