Merge Chromium + Blink git repositories
[chromium-blink-merge.git] / chrome / browser / extensions / api / identity / identity_api.cc
blob9aa4d7f65d9e8d2a400f1074ce54f7197921774c
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.
5 #include "chrome/browser/extensions/api/identity/identity_api.h"
7 #include <set>
8 #include <string>
9 #include <utility>
10 #include <vector>
12 #include "base/lazy_instance.h"
13 #include "base/strings/string_number_conversions.h"
14 #include "base/strings/stringprintf.h"
15 #include "base/trace_event/trace_event.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/account_tracker_service_factory.h"
23 #include "chrome/browser/signin/chrome_signin_client_factory.h"
24 #include "chrome/browser/signin/profile_oauth2_token_service_factory.h"
25 #include "chrome/browser/signin/signin_manager_factory.h"
26 #include "chrome/browser/ui/webui/signin/login_ui_service_factory.h"
27 #include "chrome/common/extensions/api/identity.h"
28 #include "chrome/common/url_constants.h"
29 #include "components/signin/core/browser/account_tracker_service.h"
30 #include "components/signin/core/browser/profile_oauth2_token_service.h"
31 #include "components/signin/core/browser/signin_manager.h"
32 #include "components/signin/core/common/profile_management_switches.h"
33 #include "extensions/browser/event_router.h"
34 #include "extensions/browser/extension_function_dispatcher.h"
35 #include "extensions/common/extension.h"
36 #include "extensions/common/extension_l10n_util.h"
37 #include "extensions/common/manifest_handlers/oauth2_manifest_handler.h"
38 #include "extensions/common/permissions/permission_set.h"
39 #include "extensions/common/permissions/permissions_data.h"
40 #include "google_apis/gaia/gaia_urls.h"
41 #include "url/gurl.h"
43 #if defined(OS_CHROMEOS)
44 #include "chrome/browser/chromeos/login/session/user_session_manager.h"
45 #include "chrome/browser/chromeos/policy/browser_policy_connector_chromeos.h"
46 #include "chrome/browser/chromeos/settings/device_oauth2_token_service.h"
47 #include "chrome/browser/chromeos/settings/device_oauth2_token_service_factory.h"
48 #include "components/user_manager/user_manager.h"
49 #include "google_apis/gaia/gaia_constants.h"
50 #endif
52 namespace extensions {
54 namespace identity_constants {
55 const char kInvalidClientId[] = "Invalid OAuth2 Client ID.";
56 const char kInvalidScopes[] = "Invalid OAuth2 scopes.";
57 const char kAuthFailure[] = "OAuth2 request failed: ";
58 const char kNoGrant[] = "OAuth2 not granted or revoked.";
59 const char kUserRejected[] = "The user did not approve access.";
60 const char kUserNotSignedIn[] = "The user is not signed in.";
61 const char kInteractionRequired[] = "User interaction required.";
62 const char kInvalidRedirect[] = "Did not redirect to the right URL.";
63 const char kOffTheRecord[] = "Identity API is disabled in incognito windows.";
64 const char kPageLoadFailure[] = "Authorization page could not be loaded.";
65 const char kCanceled[] = "canceled";
67 const int kCachedIssueAdviceTTLSeconds = 1;
68 } // namespace identity_constants
70 namespace {
72 static const char kChromiumDomainRedirectUrlPattern[] =
73 "https://%s.chromiumapp.org/";
75 std::string GetPrimaryAccountId(content::BrowserContext* context) {
76 SigninManagerBase* signin_manager =
77 SigninManagerFactory::GetForProfile(Profile::FromBrowserContext(context));
78 return signin_manager->GetAuthenticatedAccountId();
81 } // namespace
83 namespace identity = api::identity;
85 IdentityTokenCacheValue::IdentityTokenCacheValue()
86 : status_(CACHE_STATUS_NOTFOUND) {}
88 IdentityTokenCacheValue::IdentityTokenCacheValue(
89 const IssueAdviceInfo& issue_advice)
90 : status_(CACHE_STATUS_ADVICE), issue_advice_(issue_advice) {
91 expiration_time_ =
92 base::Time::Now() + base::TimeDelta::FromSeconds(
93 identity_constants::kCachedIssueAdviceTTLSeconds);
96 IdentityTokenCacheValue::IdentityTokenCacheValue(const std::string& token,
97 base::TimeDelta time_to_live)
98 : status_(CACHE_STATUS_TOKEN), token_(token) {
99 // Remove 20 minutes from the ttl so cached tokens will have some time
100 // to live any time they are returned.
101 time_to_live -= base::TimeDelta::FromMinutes(20);
103 base::TimeDelta zero_delta;
104 if (time_to_live < zero_delta)
105 time_to_live = zero_delta;
107 expiration_time_ = base::Time::Now() + time_to_live;
110 IdentityTokenCacheValue::~IdentityTokenCacheValue() {}
112 IdentityTokenCacheValue::CacheValueStatus IdentityTokenCacheValue::status()
113 const {
114 if (is_expired())
115 return IdentityTokenCacheValue::CACHE_STATUS_NOTFOUND;
116 else
117 return status_;
120 const IssueAdviceInfo& IdentityTokenCacheValue::issue_advice() const {
121 return issue_advice_;
124 const std::string& IdentityTokenCacheValue::token() const { return token_; }
126 bool IdentityTokenCacheValue::is_expired() const {
127 return status_ == CACHE_STATUS_NOTFOUND ||
128 expiration_time_ < base::Time::Now();
131 const base::Time& IdentityTokenCacheValue::expiration_time() const {
132 return expiration_time_;
135 IdentityAPI::IdentityAPI(content::BrowserContext* context)
136 : browser_context_(context),
137 profile_identity_provider_(
138 SigninManagerFactory::GetForProfile(
139 Profile::FromBrowserContext(context)),
140 ProfileOAuth2TokenServiceFactory::GetForProfile(
141 Profile::FromBrowserContext(context)),
142 LoginUIServiceFactory::GetShowLoginPopupCallbackForProfile(
143 Profile::FromBrowserContext(context))),
144 account_tracker_(&profile_identity_provider_,
145 g_browser_process->system_request_context()) {
146 account_tracker_.AddObserver(this);
149 IdentityAPI::~IdentityAPI() {}
151 IdentityMintRequestQueue* IdentityAPI::mint_queue() { return &mint_queue_; }
153 void IdentityAPI::SetCachedToken(const ExtensionTokenKey& key,
154 const IdentityTokenCacheValue& token_data) {
155 CachedTokens::iterator it = token_cache_.find(key);
156 if (it != token_cache_.end() && it->second.status() <= token_data.status())
157 token_cache_.erase(it);
159 token_cache_.insert(std::make_pair(key, token_data));
162 void IdentityAPI::EraseCachedToken(const std::string& extension_id,
163 const std::string& token) {
164 CachedTokens::iterator it;
165 for (it = token_cache_.begin(); it != token_cache_.end(); ++it) {
166 if (it->first.extension_id == extension_id &&
167 it->second.status() == IdentityTokenCacheValue::CACHE_STATUS_TOKEN &&
168 it->second.token() == token) {
169 token_cache_.erase(it);
170 break;
175 void IdentityAPI::EraseAllCachedTokens() { token_cache_.clear(); }
177 const IdentityTokenCacheValue& IdentityAPI::GetCachedToken(
178 const ExtensionTokenKey& key) {
179 return token_cache_[key];
182 const IdentityAPI::CachedTokens& IdentityAPI::GetAllCachedTokens() {
183 return token_cache_;
186 std::vector<std::string> IdentityAPI::GetAccounts() const {
187 const std::string primary_account_id = GetPrimaryAccountId(browser_context_);
188 const std::vector<gaia::AccountIds> ids = account_tracker_.GetAccounts();
189 std::vector<std::string> gaia_ids;
191 if (switches::IsExtensionsMultiAccount()) {
192 for (std::vector<gaia::AccountIds>::const_iterator it = ids.begin();
193 it != ids.end();
194 ++it) {
195 gaia_ids.push_back(it->gaia);
197 } else if (ids.size() >= 1) {
198 gaia_ids.push_back(ids[0].gaia);
201 return gaia_ids;
204 std::string IdentityAPI::FindAccountKeyByGaiaId(const std::string& gaia_id) {
205 return account_tracker_.FindAccountIdsByGaiaId(gaia_id).account_key;
208 void IdentityAPI::Shutdown() {
209 FOR_EACH_OBSERVER(ShutdownObserver, shutdown_observer_list_, OnShutdown());
210 account_tracker_.RemoveObserver(this);
211 account_tracker_.Shutdown();
214 static base::LazyInstance<BrowserContextKeyedAPIFactory<IdentityAPI> >
215 g_factory = LAZY_INSTANCE_INITIALIZER;
217 // static
218 BrowserContextKeyedAPIFactory<IdentityAPI>* IdentityAPI::GetFactoryInstance() {
219 return g_factory.Pointer();
222 void IdentityAPI::OnAccountAdded(const gaia::AccountIds& ids) {
225 void IdentityAPI::OnAccountRemoved(const gaia::AccountIds& ids) {
228 void IdentityAPI::OnAccountSignInChanged(const gaia::AccountIds& ids,
229 bool is_signed_in) {
230 api::identity::AccountInfo account_info;
231 account_info.id = ids.gaia;
233 scoped_ptr<base::ListValue> args =
234 api::identity::OnSignInChanged::Create(account_info, is_signed_in);
235 scoped_ptr<Event> event(new Event(events::IDENTITY_ON_SIGN_IN_CHANGED,
236 api::identity::OnSignInChanged::kEventName,
237 args.Pass(), browser_context_));
239 EventRouter::Get(browser_context_)->BroadcastEvent(event.Pass());
242 void IdentityAPI::AddShutdownObserver(ShutdownObserver* observer) {
243 shutdown_observer_list_.AddObserver(observer);
246 void IdentityAPI::RemoveShutdownObserver(ShutdownObserver* observer) {
247 shutdown_observer_list_.RemoveObserver(observer);
250 void IdentityAPI::SetAccountStateForTest(gaia::AccountIds ids,
251 bool is_signed_in) {
252 account_tracker_.SetAccountStateForTest(ids, is_signed_in);
255 template <>
256 void BrowserContextKeyedAPIFactory<IdentityAPI>::DeclareFactoryDependencies() {
257 DependsOn(ExtensionsBrowserClient::Get()->GetExtensionSystemFactory());
258 DependsOn(ProfileOAuth2TokenServiceFactory::GetInstance());
261 IdentityGetAccountsFunction::IdentityGetAccountsFunction() {
264 IdentityGetAccountsFunction::~IdentityGetAccountsFunction() {
267 ExtensionFunction::ResponseAction IdentityGetAccountsFunction::Run() {
268 if (GetProfile()->IsOffTheRecord()) {
269 return RespondNow(Error(identity_constants::kOffTheRecord));
272 std::vector<std::string> gaia_ids =
273 IdentityAPI::GetFactoryInstance()->Get(GetProfile())->GetAccounts();
274 DCHECK(gaia_ids.size() < 2 || switches::IsExtensionsMultiAccount());
276 base::ListValue* infos = new base::ListValue();
278 for (std::vector<std::string>::const_iterator it = gaia_ids.begin();
279 it != gaia_ids.end();
280 ++it) {
281 api::identity::AccountInfo account_info;
282 account_info.id = *it;
283 infos->Append(account_info.ToValue().release());
286 return RespondNow(OneArgument(infos));
289 IdentityGetAuthTokenFunction::IdentityGetAuthTokenFunction()
290 : OAuth2TokenService::Consumer("extensions_identity_api"),
291 interactive_(false),
292 should_prompt_for_scopes_(false),
293 should_prompt_for_signin_(false) {
296 IdentityGetAuthTokenFunction::~IdentityGetAuthTokenFunction() {
297 TRACE_EVENT_ASYNC_END0("identity", "IdentityGetAuthTokenFunction", this);
300 bool IdentityGetAuthTokenFunction::RunAsync() {
301 TRACE_EVENT_ASYNC_BEGIN1("identity",
302 "IdentityGetAuthTokenFunction",
303 this,
304 "extension",
305 extension()->id());
307 if (GetProfile()->IsOffTheRecord()) {
308 error_ = identity_constants::kOffTheRecord;
309 return false;
312 scoped_ptr<identity::GetAuthToken::Params> params(
313 identity::GetAuthToken::Params::Create(*args_));
314 EXTENSION_FUNCTION_VALIDATE(params.get());
315 interactive_ = params->details.get() &&
316 params->details->interactive.get() &&
317 *params->details->interactive;
319 should_prompt_for_scopes_ = interactive_;
320 should_prompt_for_signin_ = interactive_;
322 const OAuth2Info& oauth2_info = OAuth2Info::GetOAuth2Info(extension());
324 // Check that the necessary information is present in the manifest.
325 oauth2_client_id_ = GetOAuth2ClientId();
326 if (oauth2_client_id_.empty()) {
327 error_ = identity_constants::kInvalidClientId;
328 return false;
331 std::set<std::string> scopes(oauth2_info.scopes.begin(),
332 oauth2_info.scopes.end());
334 std::string account_key = GetPrimaryAccountId(GetProfile());
336 if (params->details.get()) {
337 if (params->details->account.get()) {
338 std::string detail_key =
339 extensions::IdentityAPI::GetFactoryInstance()
340 ->Get(GetProfile())
341 ->FindAccountKeyByGaiaId(params->details->account->id);
343 if (detail_key != account_key) {
344 if (detail_key.empty() || !switches::IsExtensionsMultiAccount()) {
345 // TODO(courage): should this be a different error?
346 error_ = identity_constants::kUserNotSignedIn;
347 return false;
350 account_key = detail_key;
354 if (params->details->scopes.get()) {
355 scopes = std::set<std::string>(params->details->scopes->begin(),
356 params->details->scopes->end());
360 if (scopes.size() == 0) {
361 error_ = identity_constants::kInvalidScopes;
362 return false;
365 token_key_.reset(
366 new ExtensionTokenKey(extension()->id(), account_key, scopes));
368 // From here on out, results must be returned asynchronously.
369 StartAsyncRun();
371 #if defined(OS_CHROMEOS)
372 policy::BrowserPolicyConnectorChromeOS* connector =
373 g_browser_process->platform_part()->browser_policy_connector_chromeos();
374 if (user_manager::UserManager::Get()->IsLoggedInAsKioskApp() &&
375 connector->IsEnterpriseManaged()) {
376 StartMintTokenFlow(IdentityMintRequestQueue::MINT_TYPE_NONINTERACTIVE);
377 return true;
379 #endif
381 if (!HasLoginToken()) {
382 if (!should_prompt_for_signin_) {
383 CompleteFunctionWithError(identity_constants::kUserNotSignedIn);
384 return true;
386 // Display a login prompt.
387 StartSigninFlow();
388 } else {
389 StartMintTokenFlow(IdentityMintRequestQueue::MINT_TYPE_NONINTERACTIVE);
392 return true;
395 void IdentityGetAuthTokenFunction::StartAsyncRun() {
396 // Balanced in CompleteAsyncRun
397 AddRef();
398 extensions::IdentityAPI::GetFactoryInstance()
399 ->Get(GetProfile())
400 ->AddShutdownObserver(this);
403 void IdentityGetAuthTokenFunction::CompleteAsyncRun(bool success) {
404 extensions::IdentityAPI::GetFactoryInstance()
405 ->Get(GetProfile())
406 ->RemoveShutdownObserver(this);
408 SendResponse(success);
409 Release(); // Balanced in StartAsyncRun
412 void IdentityGetAuthTokenFunction::CompleteFunctionWithResult(
413 const std::string& access_token) {
415 SetResult(new base::StringValue(access_token));
416 CompleteAsyncRun(true);
419 void IdentityGetAuthTokenFunction::CompleteFunctionWithError(
420 const std::string& error) {
421 TRACE_EVENT_ASYNC_STEP_PAST1("identity",
422 "IdentityGetAuthTokenFunction",
423 this,
424 "CompleteFunctionWithError",
425 "error",
426 error);
427 error_ = error;
428 CompleteAsyncRun(false);
431 void IdentityGetAuthTokenFunction::StartSigninFlow() {
432 // All cached tokens are invalid because the user is not signed in.
433 IdentityAPI* id_api =
434 extensions::IdentityAPI::GetFactoryInstance()->Get(GetProfile());
435 id_api->EraseAllCachedTokens();
436 // Display a login prompt. If the subsequent mint fails, don't display the
437 // login prompt again.
438 should_prompt_for_signin_ = false;
439 ShowLoginPopup();
442 void IdentityGetAuthTokenFunction::StartMintTokenFlow(
443 IdentityMintRequestQueue::MintType type) {
444 mint_token_flow_type_ = type;
446 // Flows are serialized to prevent excessive traffic to GAIA, and
447 // to consolidate UI pop-ups.
448 IdentityAPI* id_api =
449 extensions::IdentityAPI::GetFactoryInstance()->Get(GetProfile());
451 if (!should_prompt_for_scopes_) {
452 // Caller requested no interaction.
454 if (type == IdentityMintRequestQueue::MINT_TYPE_INTERACTIVE) {
455 // GAIA told us to do a consent UI.
456 CompleteFunctionWithError(identity_constants::kNoGrant);
457 return;
459 if (!id_api->mint_queue()->empty(
460 IdentityMintRequestQueue::MINT_TYPE_INTERACTIVE, *token_key_)) {
461 // Another call is going through a consent UI.
462 CompleteFunctionWithError(identity_constants::kNoGrant);
463 return;
466 id_api->mint_queue()->RequestStart(type, *token_key_, this);
469 void IdentityGetAuthTokenFunction::CompleteMintTokenFlow() {
470 IdentityMintRequestQueue::MintType type = mint_token_flow_type_;
472 extensions::IdentityAPI::GetFactoryInstance()
473 ->Get(GetProfile())
474 ->mint_queue()
475 ->RequestComplete(type, *token_key_, this);
478 void IdentityGetAuthTokenFunction::StartMintToken(
479 IdentityMintRequestQueue::MintType type) {
480 TRACE_EVENT_ASYNC_STEP_PAST1("identity",
481 "IdentityGetAuthTokenFunction",
482 this,
483 "StartMintToken",
484 "type",
485 type);
487 const OAuth2Info& oauth2_info = OAuth2Info::GetOAuth2Info(extension());
488 IdentityAPI* id_api = IdentityAPI::GetFactoryInstance()->Get(GetProfile());
489 IdentityTokenCacheValue cache_entry = id_api->GetCachedToken(*token_key_);
490 IdentityTokenCacheValue::CacheValueStatus cache_status =
491 cache_entry.status();
493 if (type == IdentityMintRequestQueue::MINT_TYPE_NONINTERACTIVE) {
494 switch (cache_status) {
495 case IdentityTokenCacheValue::CACHE_STATUS_NOTFOUND:
496 #if defined(OS_CHROMEOS)
497 // Always force minting token for ChromeOS kiosk app.
498 if (user_manager::UserManager::Get()->IsLoggedInAsKioskApp()) {
499 gaia_mint_token_mode_ = OAuth2MintTokenFlow::MODE_MINT_TOKEN_FORCE;
500 policy::BrowserPolicyConnectorChromeOS* connector =
501 g_browser_process->platform_part()
502 ->browser_policy_connector_chromeos();
503 if (connector->IsEnterpriseManaged()) {
504 StartDeviceLoginAccessTokenRequest();
505 } else {
506 StartLoginAccessTokenRequest();
508 return;
510 #endif
512 if (oauth2_info.auto_approve)
513 // oauth2_info.auto_approve is protected by a whitelist in
514 // _manifest_features.json hence only selected extensions take
515 // advantage of forcefully minting the token.
516 gaia_mint_token_mode_ = OAuth2MintTokenFlow::MODE_MINT_TOKEN_FORCE;
517 else
518 gaia_mint_token_mode_ = OAuth2MintTokenFlow::MODE_MINT_TOKEN_NO_FORCE;
519 StartLoginAccessTokenRequest();
520 break;
522 case IdentityTokenCacheValue::CACHE_STATUS_TOKEN:
523 CompleteMintTokenFlow();
524 CompleteFunctionWithResult(cache_entry.token());
525 break;
527 case IdentityTokenCacheValue::CACHE_STATUS_ADVICE:
528 CompleteMintTokenFlow();
529 should_prompt_for_signin_ = false;
530 issue_advice_ = cache_entry.issue_advice();
531 StartMintTokenFlow(IdentityMintRequestQueue::MINT_TYPE_INTERACTIVE);
532 break;
534 } else {
535 DCHECK(type == IdentityMintRequestQueue::MINT_TYPE_INTERACTIVE);
537 if (cache_status == IdentityTokenCacheValue::CACHE_STATUS_TOKEN) {
538 CompleteMintTokenFlow();
539 CompleteFunctionWithResult(cache_entry.token());
540 } else {
541 ShowOAuthApprovalDialog(issue_advice_);
546 void IdentityGetAuthTokenFunction::OnMintTokenSuccess(
547 const std::string& access_token, int time_to_live) {
548 TRACE_EVENT_ASYNC_STEP_PAST0("identity",
549 "IdentityGetAuthTokenFunction",
550 this,
551 "OnMintTokenSuccess");
553 IdentityTokenCacheValue token(access_token,
554 base::TimeDelta::FromSeconds(time_to_live));
555 IdentityAPI::GetFactoryInstance()->Get(GetProfile())->SetCachedToken(
556 *token_key_, token);
558 CompleteMintTokenFlow();
559 CompleteFunctionWithResult(access_token);
562 void IdentityGetAuthTokenFunction::OnMintTokenFailure(
563 const GoogleServiceAuthError& error) {
564 TRACE_EVENT_ASYNC_STEP_PAST1("identity",
565 "IdentityGetAuthTokenFunction",
566 this,
567 "OnMintTokenFailure",
568 "error",
569 error.ToString());
570 CompleteMintTokenFlow();
571 switch (error.state()) {
572 case GoogleServiceAuthError::SERVICE_ERROR:
573 if (interactive_) {
574 StartSigninFlow();
575 return;
577 break;
578 case GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS:
579 case GoogleServiceAuthError::ACCOUNT_DELETED:
580 case GoogleServiceAuthError::ACCOUNT_DISABLED:
581 // TODO(courage): flush ticket and retry once
582 if (should_prompt_for_signin_) {
583 // Display a login prompt and try again (once).
584 StartSigninFlow();
585 return;
587 break;
588 default:
589 // Return error to caller.
590 break;
593 CompleteFunctionWithError(
594 std::string(identity_constants::kAuthFailure) + error.ToString());
597 void IdentityGetAuthTokenFunction::OnIssueAdviceSuccess(
598 const IssueAdviceInfo& issue_advice) {
599 TRACE_EVENT_ASYNC_STEP_PAST0("identity",
600 "IdentityGetAuthTokenFunction",
601 this,
602 "OnIssueAdviceSuccess");
604 IdentityAPI::GetFactoryInstance()->Get(GetProfile())->SetCachedToken(
605 *token_key_, IdentityTokenCacheValue(issue_advice));
606 CompleteMintTokenFlow();
608 should_prompt_for_signin_ = false;
609 // Existing grant was revoked and we used NO_FORCE, so we got info back
610 // instead. Start a consent UI if we can.
611 issue_advice_ = issue_advice;
612 StartMintTokenFlow(IdentityMintRequestQueue::MINT_TYPE_INTERACTIVE);
615 void IdentityGetAuthTokenFunction::SigninSuccess() {
616 TRACE_EVENT_ASYNC_STEP_PAST0("identity",
617 "IdentityGetAuthTokenFunction",
618 this,
619 "SigninSuccess");
621 // If there was no account associated this profile before the
622 // sign-in, we may not have an account_id in the token_key yet.
623 if (token_key_->account_id.empty()) {
624 token_key_->account_id = GetPrimaryAccountId(GetProfile());
627 StartMintTokenFlow(IdentityMintRequestQueue::MINT_TYPE_NONINTERACTIVE);
630 void IdentityGetAuthTokenFunction::SigninFailed() {
631 TRACE_EVENT_ASYNC_STEP_PAST0("identity",
632 "IdentityGetAuthTokenFunction",
633 this,
634 "SigninFailed");
635 CompleteFunctionWithError(identity_constants::kUserNotSignedIn);
638 void IdentityGetAuthTokenFunction::OnGaiaFlowFailure(
639 GaiaWebAuthFlow::Failure failure,
640 GoogleServiceAuthError service_error,
641 const std::string& oauth_error) {
642 CompleteMintTokenFlow();
643 std::string error;
645 switch (failure) {
646 case GaiaWebAuthFlow::WINDOW_CLOSED:
647 error = identity_constants::kUserRejected;
648 break;
650 case GaiaWebAuthFlow::INVALID_REDIRECT:
651 error = identity_constants::kInvalidRedirect;
652 break;
654 case GaiaWebAuthFlow::SERVICE_AUTH_ERROR:
655 // If this is really an authentication error and not just a transient
656 // network error, and this is an interactive request for a signed-in
657 // user, then we show signin UI instead of failing.
658 if (service_error.state() != GoogleServiceAuthError::CONNECTION_FAILED &&
659 service_error.state() !=
660 GoogleServiceAuthError::SERVICE_UNAVAILABLE &&
661 interactive_ && HasLoginToken()) {
662 StartSigninFlow();
663 return;
665 error = std::string(identity_constants::kAuthFailure) +
666 service_error.ToString();
667 break;
669 case GaiaWebAuthFlow::OAUTH_ERROR:
670 error = MapOAuth2ErrorToDescription(oauth_error);
671 break;
673 case GaiaWebAuthFlow::LOAD_FAILED:
674 error = identity_constants::kPageLoadFailure;
675 break;
677 default:
678 NOTREACHED() << "Unexpected error from gaia web auth flow: " << failure;
679 error = identity_constants::kInvalidRedirect;
680 break;
683 CompleteFunctionWithError(error);
686 void IdentityGetAuthTokenFunction::OnGaiaFlowCompleted(
687 const std::string& access_token,
688 const std::string& expiration) {
689 TRACE_EVENT_ASYNC_STEP_PAST0("identity",
690 "IdentityGetAuthTokenFunction",
691 this,
692 "OnGaiaFlowCompleted");
693 int time_to_live;
694 if (!expiration.empty() && base::StringToInt(expiration, &time_to_live)) {
695 IdentityTokenCacheValue token_value(
696 access_token, base::TimeDelta::FromSeconds(time_to_live));
697 IdentityAPI::GetFactoryInstance()->Get(GetProfile())->SetCachedToken(
698 *token_key_, token_value);
701 CompleteMintTokenFlow();
702 CompleteFunctionWithResult(access_token);
705 void IdentityGetAuthTokenFunction::OnGetTokenSuccess(
706 const OAuth2TokenService::Request* request,
707 const std::string& access_token,
708 const base::Time& expiration_time) {
709 TRACE_EVENT_ASYNC_STEP_PAST1("identity",
710 "IdentityGetAuthTokenFunction",
711 this,
712 "OnGetTokenSuccess",
713 "account",
714 request->GetAccountId());
715 login_token_request_.reset();
716 StartGaiaRequest(access_token);
719 void IdentityGetAuthTokenFunction::OnGetTokenFailure(
720 const OAuth2TokenService::Request* request,
721 const GoogleServiceAuthError& error) {
722 TRACE_EVENT_ASYNC_STEP_PAST1("identity",
723 "IdentityGetAuthTokenFunction",
724 this,
725 "OnGetTokenFailure",
726 "error",
727 error.ToString());
728 login_token_request_.reset();
729 OnGaiaFlowFailure(GaiaWebAuthFlow::SERVICE_AUTH_ERROR, error, std::string());
732 void IdentityGetAuthTokenFunction::OnShutdown() {
733 gaia_web_auth_flow_.reset();
734 signin_flow_.reset();
735 login_token_request_.reset();
736 extensions::IdentityAPI::GetFactoryInstance()
737 ->Get(GetProfile())
738 ->mint_queue()
739 ->RequestCancel(*token_key_, this);
740 CompleteFunctionWithError(identity_constants::kCanceled);
743 #if defined(OS_CHROMEOS)
744 void IdentityGetAuthTokenFunction::StartDeviceLoginAccessTokenRequest() {
745 chromeos::DeviceOAuth2TokenService* service =
746 chromeos::DeviceOAuth2TokenServiceFactory::Get();
747 // Since robot account refresh tokens are scoped down to [any-api] only,
748 // request access token for [any-api] instead of login.
749 OAuth2TokenService::ScopeSet scopes;
750 scopes.insert(GaiaConstants::kAnyApiOAuth2Scope);
751 login_token_request_ =
752 service->StartRequest(service->GetRobotAccountId(),
753 scopes,
754 this);
756 #endif
758 void IdentityGetAuthTokenFunction::StartLoginAccessTokenRequest() {
759 ProfileOAuth2TokenService* service =
760 ProfileOAuth2TokenServiceFactory::GetForProfile(GetProfile());
761 #if defined(OS_CHROMEOS)
762 if (chrome::IsRunningInForcedAppMode()) {
763 std::string app_client_id;
764 std::string app_client_secret;
765 if (chromeos::UserSessionManager::GetInstance()->
766 GetAppModeChromeClientOAuthInfo(&app_client_id,
767 &app_client_secret)) {
768 login_token_request_ =
769 service->StartRequestForClient(token_key_->account_id,
770 app_client_id,
771 app_client_secret,
772 OAuth2TokenService::ScopeSet(),
773 this);
774 return;
777 #endif
778 login_token_request_ = service->StartRequest(
779 token_key_->account_id, OAuth2TokenService::ScopeSet(), this);
782 void IdentityGetAuthTokenFunction::StartGaiaRequest(
783 const std::string& login_access_token) {
784 DCHECK(!login_access_token.empty());
785 mint_token_flow_.reset(CreateMintTokenFlow());
786 mint_token_flow_->Start(GetProfile()->GetRequestContext(),
787 login_access_token);
790 void IdentityGetAuthTokenFunction::ShowLoginPopup() {
791 signin_flow_.reset(new IdentitySigninFlow(this, GetProfile()));
792 signin_flow_->Start();
795 void IdentityGetAuthTokenFunction::ShowOAuthApprovalDialog(
796 const IssueAdviceInfo& issue_advice) {
797 const std::string locale = extension_l10n_util::CurrentLocaleOrDefault();
799 gaia_web_auth_flow_.reset(new GaiaWebAuthFlow(
800 this, GetProfile(), token_key_.get(), oauth2_client_id_, locale));
801 gaia_web_auth_flow_->Start();
804 OAuth2MintTokenFlow* IdentityGetAuthTokenFunction::CreateMintTokenFlow() {
805 SigninClient* signin_client =
806 ChromeSigninClientFactory::GetForProfile(GetProfile());
807 std::string signin_scoped_device_id =
808 signin_client->GetSigninScopedDeviceId();
809 OAuth2MintTokenFlow* mint_token_flow = new OAuth2MintTokenFlow(
810 this,
811 OAuth2MintTokenFlow::Parameters(
812 extension()->id(),
813 oauth2_client_id_,
814 std::vector<std::string>(token_key_->scopes.begin(),
815 token_key_->scopes.end()),
816 signin_scoped_device_id,
817 gaia_mint_token_mode_));
818 return mint_token_flow;
821 bool IdentityGetAuthTokenFunction::HasLoginToken() const {
822 ProfileOAuth2TokenService* token_service =
823 ProfileOAuth2TokenServiceFactory::GetForProfile(GetProfile());
824 return token_service->RefreshTokenIsAvailable(token_key_->account_id);
827 std::string IdentityGetAuthTokenFunction::MapOAuth2ErrorToDescription(
828 const std::string& error) {
829 const char kOAuth2ErrorAccessDenied[] = "access_denied";
830 const char kOAuth2ErrorInvalidScope[] = "invalid_scope";
832 if (error == kOAuth2ErrorAccessDenied)
833 return std::string(identity_constants::kUserRejected);
834 else if (error == kOAuth2ErrorInvalidScope)
835 return std::string(identity_constants::kInvalidScopes);
836 else
837 return std::string(identity_constants::kAuthFailure) + error;
840 std::string IdentityGetAuthTokenFunction::GetOAuth2ClientId() const {
841 const OAuth2Info& oauth2_info = OAuth2Info::GetOAuth2Info(extension());
842 std::string client_id = oauth2_info.client_id;
844 // Component apps using auto_approve may use Chrome's client ID by
845 // omitting the field.
846 if (client_id.empty() && extension()->location() == Manifest::COMPONENT &&
847 oauth2_info.auto_approve) {
848 client_id = GaiaUrls::GetInstance()->oauth2_chrome_client_id();
850 return client_id;
853 IdentityGetProfileUserInfoFunction::IdentityGetProfileUserInfoFunction() {
856 IdentityGetProfileUserInfoFunction::~IdentityGetProfileUserInfoFunction() {
859 ExtensionFunction::ResponseAction IdentityGetProfileUserInfoFunction::Run() {
860 if (GetProfile()->IsOffTheRecord()) {
861 return RespondNow(Error(identity_constants::kOffTheRecord));
864 AccountInfo account =
865 AccountTrackerServiceFactory::GetForProfile(GetProfile())
866 ->GetAccountInfo(GetPrimaryAccountId(GetProfile()));
867 api::identity::ProfileUserInfo profile_user_info;
868 if (extension()->permissions_data()->HasAPIPermission(
869 APIPermission::kIdentityEmail)) {
870 profile_user_info.email = account.email;
871 profile_user_info.id = account.gaia;
874 return RespondNow(OneArgument(profile_user_info.ToValue().release()));
877 IdentityRemoveCachedAuthTokenFunction::IdentityRemoveCachedAuthTokenFunction() {
880 IdentityRemoveCachedAuthTokenFunction::
881 ~IdentityRemoveCachedAuthTokenFunction() {
884 bool IdentityRemoveCachedAuthTokenFunction::RunSync() {
885 if (GetProfile()->IsOffTheRecord()) {
886 error_ = identity_constants::kOffTheRecord;
887 return false;
890 scoped_ptr<identity::RemoveCachedAuthToken::Params> params(
891 identity::RemoveCachedAuthToken::Params::Create(*args_));
892 EXTENSION_FUNCTION_VALIDATE(params.get());
893 IdentityAPI::GetFactoryInstance()->Get(GetProfile())->EraseCachedToken(
894 extension()->id(), params->details.token);
895 return true;
898 IdentityLaunchWebAuthFlowFunction::IdentityLaunchWebAuthFlowFunction() {}
900 IdentityLaunchWebAuthFlowFunction::~IdentityLaunchWebAuthFlowFunction() {
901 if (auth_flow_)
902 auth_flow_.release()->DetachDelegateAndDelete();
905 bool IdentityLaunchWebAuthFlowFunction::RunAsync() {
906 if (GetProfile()->IsOffTheRecord()) {
907 error_ = identity_constants::kOffTheRecord;
908 return false;
911 scoped_ptr<identity::LaunchWebAuthFlow::Params> params(
912 identity::LaunchWebAuthFlow::Params::Create(*args_));
913 EXTENSION_FUNCTION_VALIDATE(params.get());
915 GURL auth_url(params->details.url);
916 WebAuthFlow::Mode mode =
917 params->details.interactive && *params->details.interactive ?
918 WebAuthFlow::INTERACTIVE : WebAuthFlow::SILENT;
920 // Set up acceptable target URLs. (Does not include chrome-extension
921 // scheme for this version of the API.)
922 InitFinalRedirectURLPrefix(extension()->id());
924 AddRef(); // Balanced in OnAuthFlowSuccess/Failure.
926 auth_flow_.reset(new WebAuthFlow(this, GetProfile(), auth_url, mode));
927 auth_flow_->Start();
928 return true;
931 void IdentityLaunchWebAuthFlowFunction::InitFinalRedirectURLPrefixForTest(
932 const std::string& extension_id) {
933 InitFinalRedirectURLPrefix(extension_id);
936 void IdentityLaunchWebAuthFlowFunction::InitFinalRedirectURLPrefix(
937 const std::string& extension_id) {
938 if (final_url_prefix_.is_empty()) {
939 final_url_prefix_ = GURL(base::StringPrintf(
940 kChromiumDomainRedirectUrlPattern, extension_id.c_str()));
944 void IdentityLaunchWebAuthFlowFunction::OnAuthFlowFailure(
945 WebAuthFlow::Failure failure) {
946 switch (failure) {
947 case WebAuthFlow::WINDOW_CLOSED:
948 error_ = identity_constants::kUserRejected;
949 break;
950 case WebAuthFlow::INTERACTION_REQUIRED:
951 error_ = identity_constants::kInteractionRequired;
952 break;
953 case WebAuthFlow::LOAD_FAILED:
954 error_ = identity_constants::kPageLoadFailure;
955 break;
956 default:
957 NOTREACHED() << "Unexpected error from web auth flow: " << failure;
958 error_ = identity_constants::kInvalidRedirect;
959 break;
961 SendResponse(false);
962 if (auth_flow_)
963 auth_flow_.release()->DetachDelegateAndDelete();
964 Release(); // Balanced in RunAsync.
967 void IdentityLaunchWebAuthFlowFunction::OnAuthFlowURLChange(
968 const GURL& redirect_url) {
969 if (redirect_url.GetWithEmptyPath() == final_url_prefix_) {
970 SetResult(new base::StringValue(redirect_url.spec()));
971 SendResponse(true);
972 if (auth_flow_)
973 auth_flow_.release()->DetachDelegateAndDelete();
974 Release(); // Balanced in RunAsync.
978 } // namespace extensions