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/identity/identity_api.h" 6 7 #include <set> 8 #include <string> 9 #include <utility> 10 #include <vector> 11 12 #include "base/lazy_instance.h" 13 #include "base/prefs/pref_service.h" 14 #include "base/strings/string_number_conversions.h" 15 #include "base/strings/stringprintf.h" 16 #include "base/values.h" 17 #include "chrome/browser/app_mode/app_mode_utils.h" 18 #include "chrome/browser/browser_process.h" 19 #include "chrome/browser/chrome_notification_types.h" 20 #include "chrome/browser/extensions/extension_service.h" 21 #include "chrome/browser/profiles/profile.h" 22 #include "chrome/browser/signin/profile_oauth2_token_service_factory.h" 23 #include "chrome/browser/signin/signin_manager_factory.h" 24 #include "chrome/common/extensions/api/identity.h" 25 #include "chrome/common/extensions/api/identity/oauth2_manifest_handler.h" 26 #include "chrome/common/pref_names.h" 27 #include "chrome/common/url_constants.h" 28 #include "components/signin/core/browser/profile_oauth2_token_service.h" 29 #include "components/signin/core/browser/signin_manager.h" 30 #include "components/signin/core/common/profile_management_switches.h" 31 #include "extensions/browser/event_router.h" 32 #include "extensions/browser/extension_function_dispatcher.h" 33 #include "extensions/common/extension.h" 34 #include "extensions/common/permissions/permission_set.h" 35 #include "extensions/common/permissions/permissions_data.h" 36 #include "google_apis/gaia/gaia_urls.h" 37 #include "url/gurl.h" 38 39 #if defined(OS_CHROMEOS) 40 #include "chrome/browser/chromeos/login/users/user_manager.h" 41 #include "chrome/browser/chromeos/policy/browser_policy_connector_chromeos.h" 42 #include "chrome/browser/chromeos/settings/device_oauth2_token_service.h" 43 #include "chrome/browser/chromeos/settings/device_oauth2_token_service_factory.h" 44 #include "google_apis/gaia/gaia_constants.h" 45 #endif 46 47 namespace extensions { 48 49 namespace identity_constants { 50 const char kInvalidClientId[] = "Invalid OAuth2 Client ID."; 51 const char kInvalidScopes[] = "Invalid OAuth2 scopes."; 52 const char kAuthFailure[] = "OAuth2 request failed: "; 53 const char kNoGrant[] = "OAuth2 not granted or revoked."; 54 const char kUserRejected[] = "The user did not approve access."; 55 const char kUserNotSignedIn[] = "The user is not signed in."; 56 const char kInteractionRequired[] = "User interaction required."; 57 const char kInvalidRedirect[] = "Did not redirect to the right URL."; 58 const char kOffTheRecord[] = "Identity API is disabled in incognito windows."; 59 const char kPageLoadFailure[] = "Authorization page could not be loaded."; 60 const char kCanceled[] = "canceled"; 61 62 const int kCachedIssueAdviceTTLSeconds = 1; 63 } // namespace identity_constants 64 65 namespace { 66 67 static const char kChromiumDomainRedirectUrlPattern[] = 68 "https://%s.chromiumapp.org/"; 69 70 std::string GetPrimaryAccountId(content::BrowserContext* context) { 71 SigninManagerBase* signin_manager = 72 SigninManagerFactory::GetForProfile(Profile::FromBrowserContext(context)); 73 return signin_manager->GetAuthenticatedAccountId(); 74 } 75 76 } // namespace 77 78 namespace identity = api::identity; 79 80 IdentityTokenCacheValue::IdentityTokenCacheValue() 81 : status_(CACHE_STATUS_NOTFOUND) {} 82 83 IdentityTokenCacheValue::IdentityTokenCacheValue( 84 const IssueAdviceInfo& issue_advice) 85 : status_(CACHE_STATUS_ADVICE), issue_advice_(issue_advice) { 86 expiration_time_ = 87 base::Time::Now() + base::TimeDelta::FromSeconds( 88 identity_constants::kCachedIssueAdviceTTLSeconds); 89 } 90 91 IdentityTokenCacheValue::IdentityTokenCacheValue(const std::string& token, 92 base::TimeDelta time_to_live) 93 : status_(CACHE_STATUS_TOKEN), token_(token) { 94 // Remove 20 minutes from the ttl so cached tokens will have some time 95 // to live any time they are returned. 96 time_to_live -= base::TimeDelta::FromMinutes(20); 97 98 base::TimeDelta zero_delta; 99 if (time_to_live < zero_delta) 100 time_to_live = zero_delta; 101 102 expiration_time_ = base::Time::Now() + time_to_live; 103 } 104 105 IdentityTokenCacheValue::~IdentityTokenCacheValue() {} 106 107 IdentityTokenCacheValue::CacheValueStatus IdentityTokenCacheValue::status() 108 const { 109 if (is_expired()) 110 return IdentityTokenCacheValue::CACHE_STATUS_NOTFOUND; 111 else 112 return status_; 113 } 114 115 const IssueAdviceInfo& IdentityTokenCacheValue::issue_advice() const { 116 return issue_advice_; 117 } 118 119 const std::string& IdentityTokenCacheValue::token() const { return token_; } 120 121 bool IdentityTokenCacheValue::is_expired() const { 122 return status_ == CACHE_STATUS_NOTFOUND || 123 expiration_time_ < base::Time::Now(); 124 } 125 126 const base::Time& IdentityTokenCacheValue::expiration_time() const { 127 return expiration_time_; 128 } 129 130 IdentityAPI::IdentityAPI(content::BrowserContext* context) 131 : browser_context_(context), 132 account_tracker_(Profile::FromBrowserContext(context)) { 133 account_tracker_.AddObserver(this); 134 } 135 136 IdentityAPI::~IdentityAPI() {} 137 138 IdentityMintRequestQueue* IdentityAPI::mint_queue() { return &mint_queue_; } 139 140 void IdentityAPI::SetCachedToken(const ExtensionTokenKey& key, 141 const IdentityTokenCacheValue& token_data) { 142 CachedTokens::iterator it = token_cache_.find(key); 143 if (it != token_cache_.end() && it->second.status() <= token_data.status()) 144 token_cache_.erase(it); 145 146 token_cache_.insert(std::make_pair(key, token_data)); 147 } 148 149 void IdentityAPI::EraseCachedToken(const std::string& extension_id, 150 const std::string& token) { 151 CachedTokens::iterator it; 152 for (it = token_cache_.begin(); it != token_cache_.end(); ++it) { 153 if (it->first.extension_id == extension_id && 154 it->second.status() == IdentityTokenCacheValue::CACHE_STATUS_TOKEN && 155 it->second.token() == token) { 156 token_cache_.erase(it); 157 break; 158 } 159 } 160 } 161 162 void IdentityAPI::EraseAllCachedTokens() { token_cache_.clear(); } 163 164 const IdentityTokenCacheValue& IdentityAPI::GetCachedToken( 165 const ExtensionTokenKey& key) { 166 return token_cache_[key]; 167 } 168 169 const IdentityAPI::CachedTokens& IdentityAPI::GetAllCachedTokens() { 170 return token_cache_; 171 } 172 173 std::vector<std::string> IdentityAPI::GetAccounts() const { 174 const std::string primary_account_id = GetPrimaryAccountId(browser_context_); 175 const std::vector<AccountIds> ids = account_tracker_.GetAccounts(); 176 std::vector<std::string> gaia_ids; 177 178 if (switches::IsExtensionsMultiAccount()) { 179 for (std::vector<AccountIds>::const_iterator it = ids.begin(); 180 it != ids.end(); 181 ++it) { 182 gaia_ids.push_back(it->gaia); 183 } 184 } else if (ids.size() >= 1) { 185 gaia_ids.push_back(ids[0].gaia); 186 } 187 188 return gaia_ids; 189 } 190 191 std::string IdentityAPI::FindAccountKeyByGaiaId(const std::string& gaia_id) { 192 return account_tracker_.FindAccountKeyByGaiaId(gaia_id); 193 } 194 195 void IdentityAPI::ReportAuthError(const GoogleServiceAuthError& error) { 196 account_tracker_.ReportAuthError(GetPrimaryAccountId(browser_context_), 197 error); 198 } 199 200 GoogleServiceAuthError IdentityAPI::GetAuthStatusForTest() const { 201 return account_tracker_.GetAuthStatus(); 202 } 203 204 void IdentityAPI::Shutdown() { 205 FOR_EACH_OBSERVER(ShutdownObserver, shutdown_observer_list_, OnShutdown()); 206 account_tracker_.RemoveObserver(this); 207 account_tracker_.Shutdown(); 208 } 209 210 static base::LazyInstance<BrowserContextKeyedAPIFactory<IdentityAPI> > 211 g_factory = LAZY_INSTANCE_INITIALIZER; 212 213 // static 214 BrowserContextKeyedAPIFactory<IdentityAPI>* IdentityAPI::GetFactoryInstance() { 215 return g_factory.Pointer(); 216 } 217 218 void IdentityAPI::OnAccountAdded(const AccountIds& ids) {} 219 220 void IdentityAPI::OnAccountRemoved(const AccountIds& ids) {} 221 222 void IdentityAPI::OnAccountSignInChanged(const AccountIds& ids, 223 bool is_signed_in) { 224 api::identity::AccountInfo account_info; 225 account_info.id = ids.gaia; 226 227 scoped_ptr<base::ListValue> args = 228 api::identity::OnSignInChanged::Create(account_info, is_signed_in); 229 scoped_ptr<Event> event(new Event(api::identity::OnSignInChanged::kEventName, 230 args.Pass(), 231 browser_context_)); 232 233 EventRouter::Get(browser_context_)->BroadcastEvent(event.Pass()); 234 } 235 236 void IdentityAPI::AddShutdownObserver(ShutdownObserver* observer) { 237 shutdown_observer_list_.AddObserver(observer); 238 } 239 240 void IdentityAPI::RemoveShutdownObserver(ShutdownObserver* observer) { 241 shutdown_observer_list_.RemoveObserver(observer); 242 } 243 244 void IdentityAPI::SetAccountStateForTest(AccountIds ids, bool is_signed_in) { 245 account_tracker_.SetAccountStateForTest(ids, is_signed_in); 246 } 247 248 template <> 249 void BrowserContextKeyedAPIFactory<IdentityAPI>::DeclareFactoryDependencies() { 250 DependsOn(ExtensionsBrowserClient::Get()->GetExtensionSystemFactory()); 251 DependsOn(ProfileOAuth2TokenServiceFactory::GetInstance()); 252 } 253 254 IdentityGetAccountsFunction::IdentityGetAccountsFunction() { 255 } 256 257 IdentityGetAccountsFunction::~IdentityGetAccountsFunction() { 258 } 259 260 ExtensionFunction::ResponseAction IdentityGetAccountsFunction::Run() { 261 if (GetProfile()->IsOffTheRecord()) { 262 return RespondNow(Error(identity_constants::kOffTheRecord)); 263 } 264 265 std::vector<std::string> gaia_ids = 266 IdentityAPI::GetFactoryInstance()->Get(GetProfile())->GetAccounts(); 267 DCHECK(gaia_ids.size() < 2 || switches::IsExtensionsMultiAccount()); 268 269 base::ListValue* infos = new base::ListValue(); 270 271 for (std::vector<std::string>::const_iterator it = gaia_ids.begin(); 272 it != gaia_ids.end(); 273 ++it) { 274 api::identity::AccountInfo account_info; 275 account_info.id = *it; 276 infos->Append(account_info.ToValue().release()); 277 } 278 279 return RespondNow(OneArgument(infos)); 280 } 281 282 IdentityGetAuthTokenFunction::IdentityGetAuthTokenFunction() 283 : OAuth2TokenService::Consumer("extensions_identity_api"), 284 should_prompt_for_scopes_(false), 285 should_prompt_for_signin_(false) {} 286 287 IdentityGetAuthTokenFunction::~IdentityGetAuthTokenFunction() {} 288 289 bool IdentityGetAuthTokenFunction::RunAsync() { 290 if (GetProfile()->IsOffTheRecord()) { 291 error_ = identity_constants::kOffTheRecord; 292 return false; 293 } 294 295 scoped_ptr<identity::GetAuthToken::Params> params( 296 identity::GetAuthToken::Params::Create(*args_)); 297 EXTENSION_FUNCTION_VALIDATE(params.get()); 298 bool interactive = params->details.get() && 299 params->details->interactive.get() && 300 *params->details->interactive; 301 302 should_prompt_for_scopes_ = interactive; 303 should_prompt_for_signin_ = interactive; 304 305 const OAuth2Info& oauth2_info = OAuth2Info::GetOAuth2Info(GetExtension()); 306 307 // Check that the necessary information is present in the manifest. 308 oauth2_client_id_ = GetOAuth2ClientId(); 309 if (oauth2_client_id_.empty()) { 310 error_ = identity_constants::kInvalidClientId; 311 return false; 312 } 313 314 std::set<std::string> scopes(oauth2_info.scopes.begin(), 315 oauth2_info.scopes.end()); 316 317 std::string account_key = GetPrimaryAccountId(GetProfile()); 318 319 if (params->details.get()) { 320 if (params->details->account.get()) { 321 std::string detail_key = 322 extensions::IdentityAPI::GetFactoryInstance() 323 ->Get(GetProfile()) 324 ->FindAccountKeyByGaiaId(params->details->account->id); 325 326 if (detail_key != account_key) { 327 if (detail_key.empty() || !switches::IsExtensionsMultiAccount()) { 328 // TODO(courage): should this be a different error? 329 error_ = identity_constants::kUserNotSignedIn; 330 return false; 331 } 332 333 account_key = detail_key; 334 } 335 } 336 337 if (params->details->scopes.get()) { 338 scopes = std::set<std::string>(params->details->scopes->begin(), 339 params->details->scopes->end()); 340 } 341 } 342 343 if (scopes.size() == 0) { 344 error_ = identity_constants::kInvalidScopes; 345 return false; 346 } 347 348 token_key_.reset( 349 new ExtensionTokenKey(GetExtension()->id(), account_key, scopes)); 350 351 // From here on out, results must be returned asynchronously. 352 StartAsyncRun(); 353 354 #if defined(OS_CHROMEOS) 355 policy::BrowserPolicyConnectorChromeOS* connector = 356 g_browser_process->platform_part()->browser_policy_connector_chromeos(); 357 if (chromeos::UserManager::Get()->IsLoggedInAsKioskApp() && 358 connector->IsEnterpriseManaged()) { 359 StartMintTokenFlow(IdentityMintRequestQueue::MINT_TYPE_NONINTERACTIVE); 360 return true; 361 } 362 #endif 363 364 if (!HasLoginToken()) { 365 if (!should_prompt_for_signin_) { 366 CompleteFunctionWithError(identity_constants::kUserNotSignedIn); 367 return true; 368 } 369 // Display a login prompt. 370 StartSigninFlow(); 371 } else { 372 StartMintTokenFlow(IdentityMintRequestQueue::MINT_TYPE_NONINTERACTIVE); 373 } 374 375 return true; 376 } 377 378 void IdentityGetAuthTokenFunction::StartAsyncRun() { 379 // Balanced in CompleteAsyncRun 380 AddRef(); 381 extensions::IdentityAPI::GetFactoryInstance() 382 ->Get(GetProfile()) 383 ->AddShutdownObserver(this); 384 } 385 386 void IdentityGetAuthTokenFunction::CompleteAsyncRun(bool success) { 387 extensions::IdentityAPI::GetFactoryInstance() 388 ->Get(GetProfile()) 389 ->RemoveShutdownObserver(this); 390 391 SendResponse(success); 392 Release(); // Balanced in StartAsyncRun 393 } 394 395 void IdentityGetAuthTokenFunction::CompleteFunctionWithResult( 396 const std::string& access_token) { 397 398 SetResult(new base::StringValue(access_token)); 399 CompleteAsyncRun(true); 400 } 401 402 void IdentityGetAuthTokenFunction::CompleteFunctionWithError( 403 const std::string& error) { 404 error_ = error; 405 CompleteAsyncRun(false); 406 } 407 408 void IdentityGetAuthTokenFunction::StartSigninFlow() { 409 // All cached tokens are invalid because the user is not signed in. 410 IdentityAPI* id_api = 411 extensions::IdentityAPI::GetFactoryInstance()->Get(GetProfile()); 412 id_api->EraseAllCachedTokens(); 413 // Display a login prompt. If the subsequent mint fails, don't display the 414 // login prompt again. 415 should_prompt_for_signin_ = false; 416 ShowLoginPopup(); 417 } 418 419 void IdentityGetAuthTokenFunction::StartMintTokenFlow( 420 IdentityMintRequestQueue::MintType type) { 421 mint_token_flow_type_ = type; 422 423 // Flows are serialized to prevent excessive traffic to GAIA, and 424 // to consolidate UI pop-ups. 425 IdentityAPI* id_api = 426 extensions::IdentityAPI::GetFactoryInstance()->Get(GetProfile()); 427 428 if (!should_prompt_for_scopes_) { 429 // Caller requested no interaction. 430 431 if (type == IdentityMintRequestQueue::MINT_TYPE_INTERACTIVE) { 432 // GAIA told us to do a consent UI. 433 CompleteFunctionWithError(identity_constants::kNoGrant); 434 return; 435 } 436 if (!id_api->mint_queue()->empty( 437 IdentityMintRequestQueue::MINT_TYPE_INTERACTIVE, *token_key_)) { 438 // Another call is going through a consent UI. 439 CompleteFunctionWithError(identity_constants::kNoGrant); 440 return; 441 } 442 } 443 id_api->mint_queue()->RequestStart(type, *token_key_, this); 444 } 445 446 void IdentityGetAuthTokenFunction::CompleteMintTokenFlow() { 447 IdentityMintRequestQueue::MintType type = mint_token_flow_type_; 448 449 extensions::IdentityAPI::GetFactoryInstance() 450 ->Get(GetProfile()) 451 ->mint_queue() 452 ->RequestComplete(type, *token_key_, this); 453 } 454 455 void IdentityGetAuthTokenFunction::StartMintToken( 456 IdentityMintRequestQueue::MintType type) { 457 const OAuth2Info& oauth2_info = OAuth2Info::GetOAuth2Info(GetExtension()); 458 IdentityAPI* id_api = IdentityAPI::GetFactoryInstance()->Get(GetProfile()); 459 IdentityTokenCacheValue cache_entry = id_api->GetCachedToken(*token_key_); 460 IdentityTokenCacheValue::CacheValueStatus cache_status = 461 cache_entry.status(); 462 463 if (type == IdentityMintRequestQueue::MINT_TYPE_NONINTERACTIVE) { 464 switch (cache_status) { 465 case IdentityTokenCacheValue::CACHE_STATUS_NOTFOUND: 466 #if defined(OS_CHROMEOS) 467 // Always force minting token for ChromeOS kiosk app. 468 if (chromeos::UserManager::Get()->IsLoggedInAsKioskApp()) { 469 gaia_mint_token_mode_ = OAuth2MintTokenFlow::MODE_MINT_TOKEN_FORCE; 470 policy::BrowserPolicyConnectorChromeOS* connector = 471 g_browser_process->platform_part() 472 ->browser_policy_connector_chromeos(); 473 if (connector->IsEnterpriseManaged()) { 474 StartDeviceLoginAccessTokenRequest(); 475 } else { 476 StartLoginAccessTokenRequest(); 477 } 478 return; 479 } 480 #endif 481 482 if (oauth2_info.auto_approve) 483 // oauth2_info.auto_approve is protected by a whitelist in 484 // _manifest_features.json hence only selected extensions take 485 // advantage of forcefully minting the token. 486 gaia_mint_token_mode_ = OAuth2MintTokenFlow::MODE_MINT_TOKEN_FORCE; 487 else 488 gaia_mint_token_mode_ = OAuth2MintTokenFlow::MODE_MINT_TOKEN_NO_FORCE; 489 StartLoginAccessTokenRequest(); 490 break; 491 492 case IdentityTokenCacheValue::CACHE_STATUS_TOKEN: 493 CompleteMintTokenFlow(); 494 CompleteFunctionWithResult(cache_entry.token()); 495 break; 496 497 case IdentityTokenCacheValue::CACHE_STATUS_ADVICE: 498 CompleteMintTokenFlow(); 499 should_prompt_for_signin_ = false; 500 issue_advice_ = cache_entry.issue_advice(); 501 StartMintTokenFlow(IdentityMintRequestQueue::MINT_TYPE_INTERACTIVE); 502 break; 503 } 504 } else { 505 DCHECK(type == IdentityMintRequestQueue::MINT_TYPE_INTERACTIVE); 506 507 if (cache_status == IdentityTokenCacheValue::CACHE_STATUS_TOKEN) { 508 CompleteMintTokenFlow(); 509 CompleteFunctionWithResult(cache_entry.token()); 510 } else { 511 ShowOAuthApprovalDialog(issue_advice_); 512 } 513 } 514 } 515 516 void IdentityGetAuthTokenFunction::OnMintTokenSuccess( 517 const std::string& access_token, int time_to_live) { 518 IdentityTokenCacheValue token(access_token, 519 base::TimeDelta::FromSeconds(time_to_live)); 520 IdentityAPI::GetFactoryInstance()->Get(GetProfile())->SetCachedToken( 521 *token_key_, token); 522 523 CompleteMintTokenFlow(); 524 CompleteFunctionWithResult(access_token); 525 } 526 527 void IdentityGetAuthTokenFunction::OnMintTokenFailure( 528 const GoogleServiceAuthError& error) { 529 CompleteMintTokenFlow(); 530 531 switch (error.state()) { 532 case GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS: 533 case GoogleServiceAuthError::ACCOUNT_DELETED: 534 case GoogleServiceAuthError::ACCOUNT_DISABLED: 535 extensions::IdentityAPI::GetFactoryInstance() 536 ->Get(GetProfile()) 537 ->ReportAuthError(error); 538 if (should_prompt_for_signin_) { 539 // Display a login prompt and try again (once). 540 StartSigninFlow(); 541 return; 542 } 543 break; 544 default: 545 // Return error to caller. 546 break; 547 } 548 549 CompleteFunctionWithError( 550 std::string(identity_constants::kAuthFailure) + error.ToString()); 551 } 552 553 void IdentityGetAuthTokenFunction::OnIssueAdviceSuccess( 554 const IssueAdviceInfo& issue_advice) { 555 IdentityAPI::GetFactoryInstance()->Get(GetProfile())->SetCachedToken( 556 *token_key_, IdentityTokenCacheValue(issue_advice)); 557 CompleteMintTokenFlow(); 558 559 should_prompt_for_signin_ = false; 560 // Existing grant was revoked and we used NO_FORCE, so we got info back 561 // instead. Start a consent UI if we can. 562 issue_advice_ = issue_advice; 563 StartMintTokenFlow(IdentityMintRequestQueue::MINT_TYPE_INTERACTIVE); 564 } 565 566 void IdentityGetAuthTokenFunction::SigninSuccess() { 567 StartMintTokenFlow(IdentityMintRequestQueue::MINT_TYPE_NONINTERACTIVE); 568 } 569 570 void IdentityGetAuthTokenFunction::SigninFailed() { 571 CompleteFunctionWithError(identity_constants::kUserNotSignedIn); 572 } 573 574 void IdentityGetAuthTokenFunction::OnGaiaFlowFailure( 575 GaiaWebAuthFlow::Failure failure, 576 GoogleServiceAuthError service_error, 577 const std::string& oauth_error) { 578 CompleteMintTokenFlow(); 579 std::string error; 580 581 switch (failure) { 582 case GaiaWebAuthFlow::WINDOW_CLOSED: 583 error = identity_constants::kUserRejected; 584 break; 585 586 case GaiaWebAuthFlow::INVALID_REDIRECT: 587 error = identity_constants::kInvalidRedirect; 588 break; 589 590 case GaiaWebAuthFlow::SERVICE_AUTH_ERROR: 591 error = std::string(identity_constants::kAuthFailure) + 592 service_error.ToString(); 593 break; 594 595 case GaiaWebAuthFlow::OAUTH_ERROR: 596 error = MapOAuth2ErrorToDescription(oauth_error); 597 break; 598 599 case GaiaWebAuthFlow::LOAD_FAILED: 600 error = identity_constants::kPageLoadFailure; 601 break; 602 603 default: 604 NOTREACHED() << "Unexpected error from gaia web auth flow: " << failure; 605 error = identity_constants::kInvalidRedirect; 606 break; 607 } 608 609 CompleteFunctionWithError(error); 610 } 611 612 void IdentityGetAuthTokenFunction::OnGaiaFlowCompleted( 613 const std::string& access_token, 614 const std::string& expiration) { 615 616 int time_to_live; 617 if (!expiration.empty() && base::StringToInt(expiration, &time_to_live)) { 618 IdentityTokenCacheValue token_value( 619 access_token, base::TimeDelta::FromSeconds(time_to_live)); 620 IdentityAPI::GetFactoryInstance()->Get(GetProfile())->SetCachedToken( 621 *token_key_, token_value); 622 } 623 624 CompleteMintTokenFlow(); 625 CompleteFunctionWithResult(access_token); 626 } 627 628 void IdentityGetAuthTokenFunction::OnGetTokenSuccess( 629 const OAuth2TokenService::Request* request, 630 const std::string& access_token, 631 const base::Time& expiration_time) { 632 login_token_request_.reset(); 633 StartGaiaRequest(access_token); 634 } 635 636 void IdentityGetAuthTokenFunction::OnGetTokenFailure( 637 const OAuth2TokenService::Request* request, 638 const GoogleServiceAuthError& error) { 639 login_token_request_.reset(); 640 OnGaiaFlowFailure(GaiaWebAuthFlow::SERVICE_AUTH_ERROR, error, std::string()); 641 } 642 643 void IdentityGetAuthTokenFunction::OnShutdown() { 644 gaia_web_auth_flow_.reset(); 645 signin_flow_.reset(); 646 login_token_request_.reset(); 647 extensions::IdentityAPI::GetFactoryInstance() 648 ->Get(GetProfile()) 649 ->mint_queue() 650 ->RequestCancel(*token_key_, this); 651 CompleteFunctionWithError(identity_constants::kCanceled); 652 } 653 654 #if defined(OS_CHROMEOS) 655 void IdentityGetAuthTokenFunction::StartDeviceLoginAccessTokenRequest() { 656 chromeos::DeviceOAuth2TokenService* service = 657 chromeos::DeviceOAuth2TokenServiceFactory::Get(); 658 // Since robot account refresh tokens are scoped down to [any-api] only, 659 // request access token for [any-api] instead of login. 660 OAuth2TokenService::ScopeSet scopes; 661 scopes.insert(GaiaConstants::kAnyApiOAuth2Scope); 662 login_token_request_ = 663 service->StartRequest(service->GetRobotAccountId(), 664 scopes, 665 this); 666 } 667 #endif 668 669 void IdentityGetAuthTokenFunction::StartLoginAccessTokenRequest() { 670 ProfileOAuth2TokenService* service = 671 ProfileOAuth2TokenServiceFactory::GetForProfile(GetProfile()); 672 #if defined(OS_CHROMEOS) 673 if (chrome::IsRunningInForcedAppMode()) { 674 std::string app_client_id; 675 std::string app_client_secret; 676 if (chromeos::UserManager::Get()->GetAppModeChromeClientOAuthInfo( 677 &app_client_id, &app_client_secret)) { 678 login_token_request_ = 679 service->StartRequestForClient(token_key_->account_id, 680 app_client_id, 681 app_client_secret, 682 OAuth2TokenService::ScopeSet(), 683 this); 684 return; 685 } 686 } 687 #endif 688 login_token_request_ = service->StartRequest( 689 token_key_->account_id, OAuth2TokenService::ScopeSet(), this); 690 } 691 692 void IdentityGetAuthTokenFunction::StartGaiaRequest( 693 const std::string& login_access_token) { 694 DCHECK(!login_access_token.empty()); 695 mint_token_flow_.reset(CreateMintTokenFlow(login_access_token)); 696 mint_token_flow_->Start(); 697 } 698 699 void IdentityGetAuthTokenFunction::ShowLoginPopup() { 700 signin_flow_.reset(new IdentitySigninFlow(this, GetProfile())); 701 signin_flow_->Start(); 702 } 703 704 void IdentityGetAuthTokenFunction::ShowOAuthApprovalDialog( 705 const IssueAdviceInfo& issue_advice) { 706 const std::string locale = g_browser_process->local_state()->GetString( 707 prefs::kApplicationLocale); 708 709 gaia_web_auth_flow_.reset(new GaiaWebAuthFlow( 710 this, GetProfile(), token_key_.get(), oauth2_client_id_, locale)); 711 gaia_web_auth_flow_->Start(); 712 } 713 714 OAuth2MintTokenFlow* IdentityGetAuthTokenFunction::CreateMintTokenFlow( 715 const std::string& login_access_token) { 716 OAuth2MintTokenFlow* mint_token_flow = new OAuth2MintTokenFlow( 717 GetProfile()->GetRequestContext(), 718 this, 719 OAuth2MintTokenFlow::Parameters( 720 login_access_token, 721 GetExtension()->id(), 722 oauth2_client_id_, 723 std::vector<std::string>(token_key_->scopes.begin(), 724 token_key_->scopes.end()), 725 gaia_mint_token_mode_)); 726 return mint_token_flow; 727 } 728 729 bool IdentityGetAuthTokenFunction::HasLoginToken() const { 730 ProfileOAuth2TokenService* token_service = 731 ProfileOAuth2TokenServiceFactory::GetForProfile(GetProfile()); 732 return token_service->RefreshTokenIsAvailable(token_key_->account_id); 733 } 734 735 std::string IdentityGetAuthTokenFunction::MapOAuth2ErrorToDescription( 736 const std::string& error) { 737 const char kOAuth2ErrorAccessDenied[] = "access_denied"; 738 const char kOAuth2ErrorInvalidScope[] = "invalid_scope"; 739 740 if (error == kOAuth2ErrorAccessDenied) 741 return std::string(identity_constants::kUserRejected); 742 else if (error == kOAuth2ErrorInvalidScope) 743 return std::string(identity_constants::kInvalidScopes); 744 else 745 return std::string(identity_constants::kAuthFailure) + error; 746 } 747 748 std::string IdentityGetAuthTokenFunction::GetOAuth2ClientId() const { 749 const OAuth2Info& oauth2_info = OAuth2Info::GetOAuth2Info(GetExtension()); 750 std::string client_id = oauth2_info.client_id; 751 752 // Component apps using auto_approve may use Chrome's client ID by 753 // omitting the field. 754 if (client_id.empty() && GetExtension()->location() == Manifest::COMPONENT && 755 oauth2_info.auto_approve) { 756 client_id = GaiaUrls::GetInstance()->oauth2_chrome_client_id(); 757 } 758 return client_id; 759 } 760 761 IdentityGetProfileUserInfoFunction::IdentityGetProfileUserInfoFunction() { 762 } 763 764 IdentityGetProfileUserInfoFunction::~IdentityGetProfileUserInfoFunction() { 765 } 766 767 ExtensionFunction::ResponseAction IdentityGetProfileUserInfoFunction::Run() { 768 if (GetProfile()->IsOffTheRecord()) { 769 return RespondNow(Error(identity_constants::kOffTheRecord)); 770 } 771 772 api::identity::ProfileUserInfo profile_user_info; 773 if (GetExtension()->permissions_data()->HasAPIPermission( 774 APIPermission::kIdentityEmail)) { 775 profile_user_info.email = 776 GetProfile()->GetPrefs()->GetString(prefs::kGoogleServicesUsername); 777 } 778 profile_user_info.id = 779 GetProfile()->GetPrefs()->GetString(prefs::kGoogleServicesUserAccountId); 780 781 return RespondNow(OneArgument(profile_user_info.ToValue().release())); 782 } 783 784 IdentityRemoveCachedAuthTokenFunction::IdentityRemoveCachedAuthTokenFunction() { 785 } 786 787 IdentityRemoveCachedAuthTokenFunction:: 788 ~IdentityRemoveCachedAuthTokenFunction() { 789 } 790 791 bool IdentityRemoveCachedAuthTokenFunction::RunSync() { 792 if (GetProfile()->IsOffTheRecord()) { 793 error_ = identity_constants::kOffTheRecord; 794 return false; 795 } 796 797 scoped_ptr<identity::RemoveCachedAuthToken::Params> params( 798 identity::RemoveCachedAuthToken::Params::Create(*args_)); 799 EXTENSION_FUNCTION_VALIDATE(params.get()); 800 IdentityAPI::GetFactoryInstance()->Get(GetProfile())->EraseCachedToken( 801 GetExtension()->id(), params->details.token); 802 return true; 803 } 804 805 IdentityLaunchWebAuthFlowFunction::IdentityLaunchWebAuthFlowFunction() {} 806 807 IdentityLaunchWebAuthFlowFunction::~IdentityLaunchWebAuthFlowFunction() { 808 if (auth_flow_) 809 auth_flow_.release()->DetachDelegateAndDelete(); 810 } 811 812 bool IdentityLaunchWebAuthFlowFunction::RunAsync() { 813 if (GetProfile()->IsOffTheRecord()) { 814 error_ = identity_constants::kOffTheRecord; 815 return false; 816 } 817 818 scoped_ptr<identity::LaunchWebAuthFlow::Params> params( 819 identity::LaunchWebAuthFlow::Params::Create(*args_)); 820 EXTENSION_FUNCTION_VALIDATE(params.get()); 821 822 GURL auth_url(params->details.url); 823 WebAuthFlow::Mode mode = 824 params->details.interactive && *params->details.interactive ? 825 WebAuthFlow::INTERACTIVE : WebAuthFlow::SILENT; 826 827 // Set up acceptable target URLs. (Does not include chrome-extension 828 // scheme for this version of the API.) 829 InitFinalRedirectURLPrefix(GetExtension()->id()); 830 831 AddRef(); // Balanced in OnAuthFlowSuccess/Failure. 832 833 auth_flow_.reset(new WebAuthFlow(this, GetProfile(), auth_url, mode)); 834 auth_flow_->Start(); 835 return true; 836 } 837 838 void IdentityLaunchWebAuthFlowFunction::InitFinalRedirectURLPrefixForTest( 839 const std::string& extension_id) { 840 InitFinalRedirectURLPrefix(extension_id); 841 } 842 843 void IdentityLaunchWebAuthFlowFunction::InitFinalRedirectURLPrefix( 844 const std::string& extension_id) { 845 if (final_url_prefix_.is_empty()) { 846 final_url_prefix_ = GURL(base::StringPrintf( 847 kChromiumDomainRedirectUrlPattern, extension_id.c_str())); 848 } 849 } 850 851 void IdentityLaunchWebAuthFlowFunction::OnAuthFlowFailure( 852 WebAuthFlow::Failure failure) { 853 switch (failure) { 854 case WebAuthFlow::WINDOW_CLOSED: 855 error_ = identity_constants::kUserRejected; 856 break; 857 case WebAuthFlow::INTERACTION_REQUIRED: 858 error_ = identity_constants::kInteractionRequired; 859 break; 860 case WebAuthFlow::LOAD_FAILED: 861 error_ = identity_constants::kPageLoadFailure; 862 break; 863 default: 864 NOTREACHED() << "Unexpected error from web auth flow: " << failure; 865 error_ = identity_constants::kInvalidRedirect; 866 break; 867 } 868 SendResponse(false); 869 Release(); // Balanced in RunAsync. 870 } 871 872 void IdentityLaunchWebAuthFlowFunction::OnAuthFlowURLChange( 873 const GURL& redirect_url) { 874 if (redirect_url.GetWithEmptyPath() == final_url_prefix_) { 875 SetResult(new base::StringValue(redirect_url.spec())); 876 SendResponse(true); 877 Release(); // Balanced in RunAsync. 878 } 879 } 880 881 } // namespace extensions 882