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