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