| OLD | NEW |
| (Empty) |
| 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_invalidati
on_handler.h" | |
| 15 #include "chrome/browser/extensions/extension_service.h" | |
| 16 #include "chrome/browser/extensions/token_cache/token_cache_service.h" | |
| 17 #include "chrome/browser/extensions/token_cache/token_cache_service_factory.h" | |
| 18 #include "chrome/browser/invalidation/profile_invalidation_provider_factory.h" | |
| 19 #include "chrome/browser/profiles/profile.h" | |
| 20 #include "chrome/browser/signin/profile_oauth2_token_service_factory.h" | |
| 21 #include "chrome/browser/signin/signin_manager_factory.h" | |
| 22 #include "chrome/common/extensions/api/push_messaging.h" | |
| 23 #include "components/invalidation/invalidation_service.h" | |
| 24 #include "components/invalidation/profile_invalidation_provider.h" | |
| 25 #include "components/signin/core/browser/profile_oauth2_token_service.h" | |
| 26 #include "components/signin/core/browser/signin_manager.h" | |
| 27 #include "content/public/browser/browser_thread.h" | |
| 28 #include "extensions/browser/event_router.h" | |
| 29 #include "extensions/browser/extension_registry.h" | |
| 30 #include "extensions/browser/extension_registry_factory.h" | |
| 31 #include "extensions/browser/extension_system_provider.h" | |
| 32 #include "extensions/browser/extensions_browser_client.h" | |
| 33 #include "extensions/common/extension.h" | |
| 34 #include "extensions/common/permissions/api_permission.h" | |
| 35 #include "extensions/common/permissions/permissions_data.h" | |
| 36 #include "google_apis/gaia/gaia_constants.h" | |
| 37 #include "google_apis/gaia/identity_provider.h" | |
| 38 | |
| 39 using content::BrowserThread; | |
| 40 | |
| 41 namespace extensions { | |
| 42 | |
| 43 namespace { | |
| 44 const char kChannelIdSeparator[] = "/"; | |
| 45 const char kUserNotSignedIn[] = "The user is not signed in."; | |
| 46 const char kUserAccessTokenFailure[] = | |
| 47 "Cannot obtain access token for the user."; | |
| 48 const char kAPINotAvailableForUser[] = | |
| 49 "The API is not available for this user."; | |
| 50 const int kObfuscatedGaiaIdTimeoutInDays = 30; | |
| 51 const char kDeprecationMessage[] = | |
| 52 "The chrome.pushMessaging API is deprecated. Use chrome.gcm API instead."; | |
| 53 } // namespace | |
| 54 | |
| 55 namespace glue = api::push_messaging; | |
| 56 | |
| 57 PushMessagingEventRouter::PushMessagingEventRouter( | |
| 58 content::BrowserContext* context) | |
| 59 : browser_context_(context) { | |
| 60 } | |
| 61 | |
| 62 PushMessagingEventRouter::~PushMessagingEventRouter() {} | |
| 63 | |
| 64 void PushMessagingEventRouter::TriggerMessageForTest( | |
| 65 const std::string& extension_id, | |
| 66 int subchannel, | |
| 67 const std::string& payload) { | |
| 68 OnMessage(extension_id, subchannel, payload); | |
| 69 } | |
| 70 | |
| 71 void PushMessagingEventRouter::OnMessage(const std::string& extension_id, | |
| 72 int subchannel, | |
| 73 const std::string& payload) { | |
| 74 glue::Message message; | |
| 75 message.subchannel_id = subchannel; | |
| 76 message.payload = payload; | |
| 77 | |
| 78 DVLOG(2) << "PushMessagingEventRouter::OnMessage" | |
| 79 << " payload = '" << payload | |
| 80 << "' subchannel = '" << subchannel | |
| 81 << "' extension = '" << extension_id << "'"; | |
| 82 | |
| 83 scoped_ptr<base::ListValue> args(glue::OnMessage::Create(message)); | |
| 84 scoped_ptr<Event> event(new Event(glue::OnMessage::kEventName, args.Pass())); | |
| 85 event->restrict_to_browser_context = browser_context_; | |
| 86 EventRouter::Get(browser_context_) | |
| 87 ->DispatchEventToExtension(extension_id, event.Pass()); | |
| 88 } | |
| 89 | |
| 90 // GetChannelId class functions | |
| 91 | |
| 92 PushMessagingGetChannelIdFunction::PushMessagingGetChannelIdFunction() | |
| 93 : OAuth2TokenService::Consumer("push_messaging"), | |
| 94 interactive_(false) {} | |
| 95 | |
| 96 PushMessagingGetChannelIdFunction::~PushMessagingGetChannelIdFunction() {} | |
| 97 | |
| 98 bool PushMessagingGetChannelIdFunction::RunAsync() { | |
| 99 // Issue a deprecation message. | |
| 100 WriteToConsole(content::CONSOLE_MESSAGE_LEVEL_WARNING, kDeprecationMessage); | |
| 101 | |
| 102 // Fetch the function arguments. | |
| 103 scoped_ptr<glue::GetChannelId::Params> params( | |
| 104 glue::GetChannelId::Params::Create(*args_)); | |
| 105 EXTENSION_FUNCTION_VALIDATE(params.get()); | |
| 106 | |
| 107 if (params && params->interactive) { | |
| 108 interactive_ = *params->interactive; | |
| 109 } | |
| 110 | |
| 111 // Balanced in ReportResult() | |
| 112 AddRef(); | |
| 113 | |
| 114 invalidation::ProfileInvalidationProvider* invalidation_provider = | |
| 115 invalidation::ProfileInvalidationProviderFactory::GetForProfile( | |
| 116 GetProfile()); | |
| 117 if (!invalidation_provider) { | |
| 118 error_ = kAPINotAvailableForUser; | |
| 119 ReportResult(std::string(), error_); | |
| 120 return false; | |
| 121 } | |
| 122 | |
| 123 IdentityProvider* identity_provider = | |
| 124 invalidation_provider->GetInvalidationService()->GetIdentityProvider(); | |
| 125 if (!identity_provider->GetTokenService()->RefreshTokenIsAvailable( | |
| 126 identity_provider->GetActiveAccountId())) { | |
| 127 if (interactive_ && identity_provider->RequestLogin()) { | |
| 128 identity_provider->AddActiveAccountRefreshTokenObserver(this); | |
| 129 return true; | |
| 130 } else { | |
| 131 error_ = kUserNotSignedIn; | |
| 132 ReportResult(std::string(), error_); | |
| 133 return false; | |
| 134 } | |
| 135 } | |
| 136 | |
| 137 DVLOG(2) << "Logged in profile name: " << GetProfile()->GetProfileName(); | |
| 138 | |
| 139 StartAccessTokenFetch(); | |
| 140 return true; | |
| 141 } | |
| 142 | |
| 143 void PushMessagingGetChannelIdFunction::StartAccessTokenFetch() { | |
| 144 invalidation::ProfileInvalidationProvider* invalidation_provider = | |
| 145 invalidation::ProfileInvalidationProviderFactory::GetForProfile( | |
| 146 GetProfile()); | |
| 147 CHECK(invalidation_provider); | |
| 148 IdentityProvider* identity_provider = | |
| 149 invalidation_provider->GetInvalidationService()->GetIdentityProvider(); | |
| 150 | |
| 151 OAuth2TokenService::ScopeSet scopes = ObfuscatedGaiaIdFetcher::GetScopes(); | |
| 152 fetcher_access_token_request_ = | |
| 153 identity_provider->GetTokenService()->StartRequest( | |
| 154 identity_provider->GetActiveAccountId(), scopes, this); | |
| 155 } | |
| 156 | |
| 157 void PushMessagingGetChannelIdFunction::OnRefreshTokenAvailable( | |
| 158 const std::string& account_id) { | |
| 159 invalidation::ProfileInvalidationProvider* invalidation_provider = | |
| 160 invalidation::ProfileInvalidationProviderFactory::GetForProfile( | |
| 161 GetProfile()); | |
| 162 CHECK(invalidation_provider); | |
| 163 invalidation_provider->GetInvalidationService()->GetIdentityProvider()-> | |
| 164 RemoveActiveAccountRefreshTokenObserver(this); | |
| 165 DVLOG(2) << "Newly logged in: " << GetProfile()->GetProfileName(); | |
| 166 StartAccessTokenFetch(); | |
| 167 } | |
| 168 | |
| 169 void PushMessagingGetChannelIdFunction::OnGetTokenSuccess( | |
| 170 const OAuth2TokenService::Request* request, | |
| 171 const std::string& access_token, | |
| 172 const base::Time& expiration_time) { | |
| 173 DCHECK_EQ(fetcher_access_token_request_.get(), request); | |
| 174 fetcher_access_token_request_.reset(); | |
| 175 | |
| 176 StartGaiaIdFetch(access_token); | |
| 177 } | |
| 178 | |
| 179 void PushMessagingGetChannelIdFunction::OnGetTokenFailure( | |
| 180 const OAuth2TokenService::Request* request, | |
| 181 const GoogleServiceAuthError& error) { | |
| 182 DCHECK_EQ(fetcher_access_token_request_.get(), request); | |
| 183 fetcher_access_token_request_.reset(); | |
| 184 | |
| 185 // TODO(fgorski): We are currently ignoring the error passed in upon failure. | |
| 186 // It should be revisited when we are working on improving general error | |
| 187 // handling for the identity related code. | |
| 188 DVLOG(1) << "Cannot obtain access token for this user " | |
| 189 << error.error_message() << " " << error.state(); | |
| 190 error_ = kUserAccessTokenFailure; | |
| 191 ReportResult(std::string(), error_); | |
| 192 } | |
| 193 | |
| 194 void PushMessagingGetChannelIdFunction::StartGaiaIdFetch( | |
| 195 const std::string& access_token) { | |
| 196 // Start the async fetch of the Gaia Id. | |
| 197 DCHECK_CURRENTLY_ON(BrowserThread::UI); | |
| 198 fetcher_.reset(new ObfuscatedGaiaIdFetcher(this)); | |
| 199 | |
| 200 // Get the token cache and see if we have already cached a Gaia Id. | |
| 201 TokenCacheService* token_cache = | |
| 202 TokenCacheServiceFactory::GetForProfile(GetProfile()); | |
| 203 | |
| 204 // Check the cache, if we already have a Gaia ID, use it instead of | |
| 205 // fetching the ID over the network. | |
| 206 const std::string& gaia_id = | |
| 207 token_cache->RetrieveToken(GaiaConstants::kObfuscatedGaiaId); | |
| 208 if (!gaia_id.empty()) { | |
| 209 ReportResult(gaia_id, std::string()); | |
| 210 return; | |
| 211 } | |
| 212 | |
| 213 net::URLRequestContextGetter* context = GetProfile()->GetRequestContext(); | |
| 214 fetcher_->Start(context, access_token); | |
| 215 } | |
| 216 | |
| 217 void PushMessagingGetChannelIdFunction::ReportResult( | |
| 218 const std::string& gaia_id, const std::string& error_string) { | |
| 219 DCHECK_CURRENTLY_ON(BrowserThread::UI); | |
| 220 | |
| 221 BuildAndSendResult(gaia_id, error_string); | |
| 222 | |
| 223 // Cache the obfuscated ID locally. It never changes for this user, | |
| 224 // and if we call the web API too often, we get errors due to rate limiting. | |
| 225 if (!gaia_id.empty()) { | |
| 226 base::TimeDelta timeout = | |
| 227 base::TimeDelta::FromDays(kObfuscatedGaiaIdTimeoutInDays); | |
| 228 TokenCacheService* token_cache = | |
| 229 TokenCacheServiceFactory::GetForProfile(GetProfile()); | |
| 230 token_cache->StoreToken(GaiaConstants::kObfuscatedGaiaId, gaia_id, | |
| 231 timeout); | |
| 232 } | |
| 233 | |
| 234 // Balanced in RunAsync. | |
| 235 Release(); | |
| 236 } | |
| 237 | |
| 238 void PushMessagingGetChannelIdFunction::BuildAndSendResult( | |
| 239 const std::string& gaia_id, const std::string& error_message) { | |
| 240 std::string channel_id; | |
| 241 if (!gaia_id.empty()) { | |
| 242 channel_id = gaia_id; | |
| 243 channel_id += kChannelIdSeparator; | |
| 244 channel_id += extension_id(); | |
| 245 } | |
| 246 | |
| 247 // TODO(petewil): It may be a good idea to further | |
| 248 // obfuscate the channel ID to prevent the user's obfuscated Gaia Id | |
| 249 // from being readily obtained. Security review will tell us if we need to. | |
| 250 | |
| 251 // Create a ChannelId results object and set the fields. | |
| 252 glue::ChannelIdResult result; | |
| 253 result.channel_id = channel_id; | |
| 254 SetError(error_message); | |
| 255 results_ = glue::GetChannelId::Results::Create(result); | |
| 256 | |
| 257 bool success = error_message.empty() && !gaia_id.empty(); | |
| 258 SendResponse(success); | |
| 259 } | |
| 260 | |
| 261 void PushMessagingGetChannelIdFunction::OnObfuscatedGaiaIdFetchSuccess( | |
| 262 const std::string& gaia_id) { | |
| 263 ReportResult(gaia_id, std::string()); | |
| 264 } | |
| 265 | |
| 266 void PushMessagingGetChannelIdFunction::OnObfuscatedGaiaIdFetchFailure( | |
| 267 const GoogleServiceAuthError& error) { | |
| 268 std::string error_text = error.error_message(); | |
| 269 // If the error message is blank, see if we can set it from the state. | |
| 270 if (error_text.empty() && | |
| 271 (0 != error.state())) { | |
| 272 error_text = base::IntToString(error.state()); | |
| 273 } | |
| 274 | |
| 275 DVLOG(1) << "GetChannelId status: '" << error_text << "'"; | |
| 276 | |
| 277 // If we had bad credentials, try the logon again. | |
| 278 switch (error.state()) { | |
| 279 case GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS: | |
| 280 case GoogleServiceAuthError::ACCOUNT_DELETED: | |
| 281 case GoogleServiceAuthError::ACCOUNT_DISABLED: { | |
| 282 invalidation::ProfileInvalidationProvider* invalidation_provider = | |
| 283 invalidation::ProfileInvalidationProviderFactory::GetForProfile( | |
| 284 GetProfile()); | |
| 285 CHECK(invalidation_provider); | |
| 286 if (!interactive_ || !invalidation_provider->GetInvalidationService()-> | |
| 287 GetIdentityProvider()->RequestLogin()) { | |
| 288 ReportResult(std::string(), error_text); | |
| 289 } | |
| 290 return; | |
| 291 } | |
| 292 default: | |
| 293 // Return error to caller. | |
| 294 ReportResult(std::string(), error_text); | |
| 295 return; | |
| 296 } | |
| 297 } | |
| 298 | |
| 299 PushMessagingAPI::PushMessagingAPI(content::BrowserContext* context) | |
| 300 : extension_registry_observer_(this), browser_context_(context) { | |
| 301 extension_registry_observer_.Add(ExtensionRegistry::Get(browser_context_)); | |
| 302 } | |
| 303 | |
| 304 PushMessagingAPI::~PushMessagingAPI() { | |
| 305 } | |
| 306 | |
| 307 // static | |
| 308 PushMessagingAPI* PushMessagingAPI::Get(content::BrowserContext* context) { | |
| 309 return BrowserContextKeyedAPIFactory<PushMessagingAPI>::Get(context); | |
| 310 } | |
| 311 | |
| 312 void PushMessagingAPI::Shutdown() { | |
| 313 event_router_.reset(); | |
| 314 handler_.reset(); | |
| 315 } | |
| 316 | |
| 317 static base::LazyInstance<BrowserContextKeyedAPIFactory<PushMessagingAPI> > | |
| 318 g_factory = LAZY_INSTANCE_INITIALIZER; | |
| 319 | |
| 320 // static | |
| 321 BrowserContextKeyedAPIFactory<PushMessagingAPI>* | |
| 322 PushMessagingAPI::GetFactoryInstance() { | |
| 323 return g_factory.Pointer(); | |
| 324 } | |
| 325 | |
| 326 bool PushMessagingAPI::InitEventRouterAndHandler() { | |
| 327 invalidation::ProfileInvalidationProvider* invalidation_provider = | |
| 328 invalidation::ProfileInvalidationProviderFactory::GetForProfile( | |
| 329 Profile::FromBrowserContext(browser_context_)); | |
| 330 if (!invalidation_provider) | |
| 331 return false; | |
| 332 | |
| 333 if (!event_router_) | |
| 334 event_router_.reset(new PushMessagingEventRouter(browser_context_)); | |
| 335 if (!handler_) { | |
| 336 handler_.reset(new PushMessagingInvalidationHandler( | |
| 337 invalidation_provider->GetInvalidationService(), | |
| 338 event_router_.get())); | |
| 339 } | |
| 340 | |
| 341 return true; | |
| 342 } | |
| 343 | |
| 344 void PushMessagingAPI::OnExtensionLoaded( | |
| 345 content::BrowserContext* browser_context, | |
| 346 const Extension* extension) { | |
| 347 if (!InitEventRouterAndHandler()) | |
| 348 return; | |
| 349 | |
| 350 if (extension->permissions_data()->HasAPIPermission( | |
| 351 APIPermission::kPushMessaging)) { | |
| 352 handler_->RegisterExtension(extension->id()); | |
| 353 } | |
| 354 } | |
| 355 | |
| 356 void PushMessagingAPI::OnExtensionUnloaded( | |
| 357 content::BrowserContext* browser_context, | |
| 358 const Extension* extension, | |
| 359 UnloadedExtensionInfo::Reason reason) { | |
| 360 if (!InitEventRouterAndHandler()) | |
| 361 return; | |
| 362 | |
| 363 if (extension->permissions_data()->HasAPIPermission( | |
| 364 APIPermission::kPushMessaging)) { | |
| 365 handler_->UnregisterExtension(extension->id()); | |
| 366 } | |
| 367 } | |
| 368 | |
| 369 void PushMessagingAPI::OnExtensionWillBeInstalled( | |
| 370 content::BrowserContext* browser_context, | |
| 371 const Extension* extension, | |
| 372 bool is_update, | |
| 373 bool from_ephemeral, | |
| 374 const std::string& old_name) { | |
| 375 if (InitEventRouterAndHandler() && | |
| 376 extension->permissions_data()->HasAPIPermission( | |
| 377 APIPermission::kPushMessaging)) { | |
| 378 handler_->SuppressInitialInvalidationsForExtension(extension->id()); | |
| 379 } | |
| 380 } | |
| 381 | |
| 382 void PushMessagingAPI::SetMapperForTest( | |
| 383 scoped_ptr<PushMessagingInvalidationMapper> mapper) { | |
| 384 handler_ = mapper.Pass(); | |
| 385 } | |
| 386 | |
| 387 template <> | |
| 388 void | |
| 389 BrowserContextKeyedAPIFactory<PushMessagingAPI>::DeclareFactoryDependencies() { | |
| 390 DependsOn(ExtensionRegistryFactory::GetInstance()); | |
| 391 DependsOn(ExtensionsBrowserClient::Get()->GetExtensionSystemFactory()); | |
| 392 DependsOn(invalidation::ProfileInvalidationProviderFactory::GetInstance()); | |
| 393 } | |
| 394 | |
| 395 } // namespace extensions | |
| OLD | NEW |