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