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/extension_service.h" 17 #include "chrome/browser/extensions/extension_system.h" 18 #include "chrome/browser/extensions/extension_system_factory.h" 19 #include "chrome/browser/extensions/token_cache/token_cache_service.h" 20 #include "chrome/browser/extensions/token_cache/token_cache_service_factory.h" 21 #include "chrome/browser/invalidation/invalidation_service.h" 22 #include "chrome/browser/invalidation/invalidation_service_factory.h" 23 #include "chrome/browser/profiles/profile.h" 24 #include "chrome/browser/signin/profile_oauth2_token_service.h" 25 #include "chrome/browser/signin/profile_oauth2_token_service_factory.h" 26 #include "chrome/browser/ui/webui/signin/login_ui_service_factory.h" 27 #include "chrome/common/extensions/api/push_messaging.h" 28 #include "chrome/common/extensions/extension_set.h" 29 #include "content/public/browser/browser_thread.h" 30 #include "content/public/browser/notification_details.h" 31 #include "content/public/browser/notification_source.h" 32 #include "extensions/browser/event_router.h" 33 #include "extensions/common/extension.h" 34 #include "extensions/common/permissions/api_permission.h" 35 #include "google_apis/gaia/gaia_constants.h" 36 #include "url/gurl.h" 37 38 using content::BrowserThread; 39 40 const char kChannelIdSeparator[] = "/"; 41 const char kUserNotSignedIn[] = "The user is not signed in."; 42 const char kUserAccessTokenFailure[] = 43 "Cannot obtain access token for the user."; 44 const int kObfuscatedGaiaIdTimeoutInDays = 30; 45 46 namespace extensions { 47 48 namespace glue = api::push_messaging; 49 50 PushMessagingEventRouter::PushMessagingEventRouter(Profile* profile) 51 : profile_(profile) { 52 } 53 54 PushMessagingEventRouter::~PushMessagingEventRouter() {} 55 56 void PushMessagingEventRouter::TriggerMessageForTest( 57 const std::string& extension_id, 58 int subchannel, 59 const std::string& payload) { 60 OnMessage(extension_id, subchannel, payload); 61 } 62 63 void PushMessagingEventRouter::OnMessage(const std::string& extension_id, 64 int subchannel, 65 const std::string& payload) { 66 glue::Message message; 67 message.subchannel_id = subchannel; 68 message.payload = payload; 69 70 DVLOG(2) << "PushMessagingEventRouter::OnMessage" 71 << " payload = '" << payload 72 << "' subchannel = '" << subchannel 73 << "' extension = '" << extension_id << "'"; 74 75 scoped_ptr<base::ListValue> args(glue::OnMessage::Create(message)); 76 scoped_ptr<extensions::Event> event(new extensions::Event( 77 glue::OnMessage::kEventName, args.Pass())); 78 event->restrict_to_browser_context = profile_; 79 ExtensionSystem::Get(profile_)->event_router()->DispatchEventToExtension( 80 extension_id, event.Pass()); 81 } 82 83 // GetChannelId class functions 84 85 PushMessagingGetChannelIdFunction::PushMessagingGetChannelIdFunction() 86 : interactive_(false) {} 87 88 PushMessagingGetChannelIdFunction::~PushMessagingGetChannelIdFunction() {} 89 90 bool PushMessagingGetChannelIdFunction::RunImpl() { 91 // Fetch the function arguments. 92 scoped_ptr<glue::GetChannelId::Params> params( 93 glue::GetChannelId::Params::Create(*args_)); 94 EXTENSION_FUNCTION_VALIDATE(params.get()); 95 96 if (params && params->interactive) { 97 interactive_ = *params->interactive; 98 } 99 100 // Balanced in ReportResult() 101 AddRef(); 102 103 if (!IsUserLoggedIn()) { 104 if (interactive_) { 105 ProfileOAuth2TokenServiceFactory::GetForProfile(GetProfile()) 106 ->AddObserver(this); 107 LoginUIServiceFactory::GetForProfile(GetProfile())->ShowLoginPopup(); 108 return true; 109 } else { 110 error_ = kUserNotSignedIn; 111 ReportResult(std::string(), error_); 112 return false; 113 } 114 } 115 116 DVLOG(2) << "Logged in profile name: " << GetProfile()->GetProfileName(); 117 118 StartAccessTokenFetch(); 119 return true; 120 } 121 122 void PushMessagingGetChannelIdFunction::StartAccessTokenFetch() { 123 std::vector<std::string> scope_vector = 124 extensions::ObfuscatedGaiaIdFetcher::GetScopes(); 125 OAuth2TokenService::ScopeSet scopes(scope_vector.begin(), scope_vector.end()); 126 ProfileOAuth2TokenService* token_service = 127 ProfileOAuth2TokenServiceFactory::GetForProfile(GetProfile()); 128 fetcher_access_token_request_ = token_service->StartRequest( 129 token_service->GetPrimaryAccountId(), scopes, this); 130 } 131 132 void PushMessagingGetChannelIdFunction::OnRefreshTokenAvailable( 133 const std::string& account_id) { 134 ProfileOAuth2TokenServiceFactory::GetForProfile(GetProfile()) 135 ->RemoveObserver(this); 136 DVLOG(2) << "Newly logged in: " << GetProfile()->GetProfileName(); 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 DVLOG(1) << "Cannot obtain access token for this user " 160 << error.error_message() << " " << error.state(); 161 error_ = kUserAccessTokenFailure; 162 ReportResult(std::string(), error_); 163 } 164 165 void PushMessagingGetChannelIdFunction::StartGaiaIdFetch( 166 const std::string& access_token) { 167 // Start the async fetch of the Gaia Id. 168 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 169 net::URLRequestContextGetter* context = GetProfile()->GetRequestContext(); 170 fetcher_.reset(new ObfuscatedGaiaIdFetcher(context, this, access_token)); 171 172 // Get the token cache and see if we have already cached a Gaia Id. 173 TokenCacheService* token_cache = 174 TokenCacheServiceFactory::GetForProfile(GetProfile()); 175 176 // Check the cache, if we already have a Gaia ID, use it instead of 177 // fetching the ID over the network. 178 const std::string& gaia_id = 179 token_cache->RetrieveToken(GaiaConstants::kObfuscatedGaiaId); 180 if (!gaia_id.empty()) { 181 ReportResult(gaia_id, std::string()); 182 return; 183 } 184 185 fetcher_->Start(); 186 } 187 188 // Check if the user is logged in. 189 bool PushMessagingGetChannelIdFunction::IsUserLoggedIn() const { 190 ProfileOAuth2TokenService* token_service = 191 ProfileOAuth2TokenServiceFactory::GetForProfile(GetProfile()); 192 return token_service->RefreshTokenIsAvailable( 193 token_service->GetPrimaryAccountId()); 194 } 195 196 void 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(GetProfile()); 209 token_cache->StoreToken(GaiaConstants::kObfuscatedGaiaId, gaia_id, 210 timeout); 211 } 212 213 // Balanced in RunImpl. 214 Release(); 215 } 216 217 void 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 240 void PushMessagingGetChannelIdFunction::OnObfuscatedGaiaIdFetchSuccess( 241 const std::string& gaia_id) { 242 ReportResult(gaia_id, std::string()); 243 } 244 245 void 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(GetProfile()); 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 278 PushMessagingAPI::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 287 PushMessagingAPI::~PushMessagingAPI() { 288 } 289 290 // static 291 PushMessagingAPI* PushMessagingAPI::Get(Profile* profile) { 292 return ProfileKeyedAPIFactory<PushMessagingAPI>::GetForProfile(profile); 293 } 294 295 void PushMessagingAPI::Shutdown() { 296 event_router_.reset(); 297 handler_.reset(); 298 } 299 300 static base::LazyInstance<ProfileKeyedAPIFactory<PushMessagingAPI> > 301 g_factory = LAZY_INSTANCE_INITIALIZER; 302 303 // static 304 ProfileKeyedAPIFactory<PushMessagingAPI>* 305 PushMessagingAPI::GetFactoryInstance() { 306 return &g_factory.Get(); 307 } 308 309 void 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 354 void PushMessagingAPI::SetMapperForTest( 355 scoped_ptr<PushMessagingInvalidationMapper> mapper) { 356 handler_ = mapper.Pass(); 357 } 358 359 template <> 360 void ProfileKeyedAPIFactory<PushMessagingAPI>::DeclareFactoryDependencies() { 361 DependsOn(ExtensionSystemFactory::GetInstance()); 362 DependsOn(invalidation::InvalidationServiceFactory::GetInstance()); 363 } 364 365 } // namespace extensions 366