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"
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"
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"
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
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();
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
) {
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()
115 return IdentityTokenCacheValue::CACHE_STATUS_NOTFOUND
;
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
);
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() {
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();
195 gaia_ids
.push_back(it
->gaia
);
197 } else if (ids
.size() >= 1) {
198 gaia_ids
.push_back(ids
[0].gaia
);
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
;
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
,
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
,
252 account_tracker_
.SetAccountStateForTest(ids
, is_signed_in
);
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();
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"),
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",
307 if (GetProfile()->IsOffTheRecord()) {
308 error_
= identity_constants::kOffTheRecord
;
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
;
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()
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
;
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
;
366 new ExtensionTokenKey(extension()->id(), account_key
, scopes
));
368 // From here on out, results must be returned asynchronously.
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
);
381 if (!HasLoginToken()) {
382 if (!should_prompt_for_signin_
) {
383 CompleteFunctionWithError(identity_constants::kUserNotSignedIn
);
386 // Display a login prompt.
389 StartMintTokenFlow(IdentityMintRequestQueue::MINT_TYPE_NONINTERACTIVE
);
395 void IdentityGetAuthTokenFunction::StartAsyncRun() {
396 // Balanced in CompleteAsyncRun
398 extensions::IdentityAPI::GetFactoryInstance()
400 ->AddShutdownObserver(this);
403 void IdentityGetAuthTokenFunction::CompleteAsyncRun(bool success
) {
404 extensions::IdentityAPI::GetFactoryInstance()
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",
424 "CompleteFunctionWithError",
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;
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
);
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
);
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()
475 ->RequestComplete(type
, *token_key_
, this);
478 void IdentityGetAuthTokenFunction::StartMintToken(
479 IdentityMintRequestQueue::MintType type
) {
480 TRACE_EVENT_ASYNC_STEP_PAST1("identity",
481 "IdentityGetAuthTokenFunction",
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();
506 StartLoginAccessTokenRequest();
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
;
518 gaia_mint_token_mode_
= OAuth2MintTokenFlow::MODE_MINT_TOKEN_NO_FORCE
;
519 StartLoginAccessTokenRequest();
522 case IdentityTokenCacheValue::CACHE_STATUS_TOKEN
:
523 CompleteMintTokenFlow();
524 CompleteFunctionWithResult(cache_entry
.token());
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
);
535 DCHECK(type
== IdentityMintRequestQueue::MINT_TYPE_INTERACTIVE
);
537 if (cache_status
== IdentityTokenCacheValue::CACHE_STATUS_TOKEN
) {
538 CompleteMintTokenFlow();
539 CompleteFunctionWithResult(cache_entry
.token());
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",
551 "OnMintTokenSuccess");
553 IdentityTokenCacheValue
token(access_token
,
554 base::TimeDelta::FromSeconds(time_to_live
));
555 IdentityAPI::GetFactoryInstance()->Get(GetProfile())->SetCachedToken(
558 CompleteMintTokenFlow();
559 CompleteFunctionWithResult(access_token
);
562 void IdentityGetAuthTokenFunction::OnMintTokenFailure(
563 const GoogleServiceAuthError
& error
) {
564 TRACE_EVENT_ASYNC_STEP_PAST1("identity",
565 "IdentityGetAuthTokenFunction",
567 "OnMintTokenFailure",
570 CompleteMintTokenFlow();
571 switch (error
.state()) {
572 case GoogleServiceAuthError::SERVICE_ERROR
:
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).
589 // Return error to caller.
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",
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",
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",
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();
646 case GaiaWebAuthFlow::WINDOW_CLOSED
:
647 error
= identity_constants::kUserRejected
;
650 case GaiaWebAuthFlow::INVALID_REDIRECT
:
651 error
= identity_constants::kInvalidRedirect
;
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()) {
665 error
= std::string(identity_constants::kAuthFailure
) +
666 service_error
.ToString();
669 case GaiaWebAuthFlow::OAUTH_ERROR
:
670 error
= MapOAuth2ErrorToDescription(oauth_error
);
673 case GaiaWebAuthFlow::LOAD_FAILED
:
674 error
= identity_constants::kPageLoadFailure
;
678 NOTREACHED() << "Unexpected error from gaia web auth flow: " << failure
;
679 error
= identity_constants::kInvalidRedirect
;
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",
692 "OnGaiaFlowCompleted");
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",
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",
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()
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(),
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
,
772 OAuth2TokenService::ScopeSet(),
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(),
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(
811 OAuth2MintTokenFlow::Parameters(
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
);
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();
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
;
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
);
898 IdentityLaunchWebAuthFlowFunction::IdentityLaunchWebAuthFlowFunction() {}
900 IdentityLaunchWebAuthFlowFunction::~IdentityLaunchWebAuthFlowFunction() {
902 auth_flow_
.release()->DetachDelegateAndDelete();
905 bool IdentityLaunchWebAuthFlowFunction::RunAsync() {
906 if (GetProfile()->IsOffTheRecord()) {
907 error_
= identity_constants::kOffTheRecord
;
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
));
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
) {
947 case WebAuthFlow::WINDOW_CLOSED
:
948 error_
= identity_constants::kUserRejected
;
950 case WebAuthFlow::INTERACTION_REQUIRED
:
951 error_
= identity_constants::kInteractionRequired
;
953 case WebAuthFlow::LOAD_FAILED
:
954 error_
= identity_constants::kPageLoadFailure
;
957 NOTREACHED() << "Unexpected error from web auth flow: " << failure
;
958 error_
= identity_constants::kInvalidRedirect
;
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()));
973 auth_flow_
.release()->DetachDelegateAndDelete();
974 Release(); // Balanced in RunAsync.
978 } // namespace extensions