identity_api.cc revision 5c02ac1a9c1b504631c0a3d2b6e737b5d738bae1
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_global_error.h"
24#include "chrome/browser/signin/signin_manager_factory.h"
25#include "chrome/common/extensions/api/identity.h"
26#include "chrome/common/extensions/api/identity/oauth2_manifest_handler.h"
27#include "chrome/common/pref_names.h"
28#include "chrome/common/url_constants.h"
29#include "components/signin/core/browser/profile_oauth2_token_service.h"
30#include "components/signin/core/browser/signin_manager.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/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
171void IdentityAPI::ReportAuthError(const GoogleServiceAuthError& error) {
172  account_tracker_.ReportAuthError(GetPrimaryAccountId(browser_context_),
173                                   error);
174}
175
176GoogleServiceAuthError IdentityAPI::GetAuthStatusForTest() const {
177  return account_tracker_.GetAuthStatus();
178}
179
180void IdentityAPI::Shutdown() {
181  FOR_EACH_OBSERVER(ShutdownObserver, shutdown_observer_list_, OnShutdown());
182  account_tracker_.RemoveObserver(this);
183  account_tracker_.Shutdown();
184}
185
186static base::LazyInstance<BrowserContextKeyedAPIFactory<IdentityAPI> >
187    g_factory = LAZY_INSTANCE_INITIALIZER;
188
189// static
190BrowserContextKeyedAPIFactory<IdentityAPI>* IdentityAPI::GetFactoryInstance() {
191  return g_factory.Pointer();
192}
193
194void IdentityAPI::OnAccountAdded(const AccountIds& ids) {}
195
196void IdentityAPI::OnAccountRemoved(const AccountIds& ids) {}
197
198void IdentityAPI::OnAccountSignInChanged(const AccountIds& ids,
199                                         bool is_signed_in) {
200  api::identity::AccountInfo account_info;
201  account_info.id = ids.gaia;
202
203  scoped_ptr<base::ListValue> args =
204      api::identity::OnSignInChanged::Create(account_info, is_signed_in);
205  scoped_ptr<Event> event(new Event(api::identity::OnSignInChanged::kEventName,
206                                    args.Pass(),
207                                    browser_context_));
208
209  EventRouter::Get(browser_context_)->BroadcastEvent(event.Pass());
210}
211
212void IdentityAPI::AddShutdownObserver(ShutdownObserver* observer) {
213  shutdown_observer_list_.AddObserver(observer);
214}
215
216void IdentityAPI::RemoveShutdownObserver(ShutdownObserver* observer) {
217  shutdown_observer_list_.RemoveObserver(observer);
218}
219
220template <>
221void BrowserContextKeyedAPIFactory<IdentityAPI>::DeclareFactoryDependencies() {
222  DependsOn(ExtensionsBrowserClient::Get()->GetExtensionSystemFactory());
223  DependsOn(ProfileOAuth2TokenServiceFactory::GetInstance());
224}
225
226IdentityGetAuthTokenFunction::IdentityGetAuthTokenFunction()
227    : OAuth2TokenService::Consumer("extensions_identity_api"),
228      should_prompt_for_scopes_(false),
229      should_prompt_for_signin_(false) {}
230
231IdentityGetAuthTokenFunction::~IdentityGetAuthTokenFunction() {}
232
233bool IdentityGetAuthTokenFunction::RunImpl() {
234  if (GetProfile()->IsOffTheRecord()) {
235    error_ = identity_constants::kOffTheRecord;
236    return false;
237  }
238
239  scoped_ptr<identity::GetAuthToken::Params> params(
240      identity::GetAuthToken::Params::Create(*args_));
241  EXTENSION_FUNCTION_VALIDATE(params.get());
242  bool interactive = params->details.get() &&
243      params->details->interactive.get() &&
244      *params->details->interactive;
245
246  should_prompt_for_scopes_ = interactive;
247  should_prompt_for_signin_ = interactive;
248
249  const OAuth2Info& oauth2_info = OAuth2Info::GetOAuth2Info(GetExtension());
250
251  // Check that the necessary information is present in the manifest.
252  oauth2_client_id_ = GetOAuth2ClientId();
253  if (oauth2_client_id_.empty()) {
254    error_ = identity_constants::kInvalidClientId;
255    return false;
256  }
257
258  if (oauth2_info.scopes.size() == 0) {
259    error_ = identity_constants::kInvalidScopes;
260    return false;
261  }
262
263  std::set<std::string> scopes(oauth2_info.scopes.begin(),
264                               oauth2_info.scopes.end());
265  token_key_.reset(new ExtensionTokenKey(
266      GetExtension()->id(), GetPrimaryAccountId(GetProfile()), scopes));
267
268  // From here on out, results must be returned asynchronously.
269  StartAsyncRun();
270
271#if defined(OS_CHROMEOS)
272  policy::BrowserPolicyConnectorChromeOS* connector =
273      g_browser_process->platform_part()->browser_policy_connector_chromeos();
274  if (chromeos::UserManager::Get()->IsLoggedInAsKioskApp() &&
275      connector->IsEnterpriseManaged()) {
276    StartMintTokenFlow(IdentityMintRequestQueue::MINT_TYPE_NONINTERACTIVE);
277    return true;
278  }
279#endif
280
281  if (!HasLoginToken()) {
282    if (!should_prompt_for_signin_) {
283      CompleteFunctionWithError(identity_constants::kUserNotSignedIn);
284      return true;
285    }
286    // Display a login prompt.
287    StartSigninFlow();
288  } else {
289    StartMintTokenFlow(IdentityMintRequestQueue::MINT_TYPE_NONINTERACTIVE);
290  }
291
292  return true;
293}
294
295void IdentityGetAuthTokenFunction::StartAsyncRun() {
296  // Balanced in CompleteAsyncRun
297  AddRef();
298  extensions::IdentityAPI::GetFactoryInstance()
299      ->Get(GetProfile())
300      ->AddShutdownObserver(this);
301}
302
303void IdentityGetAuthTokenFunction::CompleteAsyncRun(bool success) {
304  extensions::IdentityAPI::GetFactoryInstance()
305      ->Get(GetProfile())
306      ->RemoveShutdownObserver(this);
307
308  SendResponse(success);
309  Release();  // Balanced in StartAsyncRun
310}
311
312void IdentityGetAuthTokenFunction::CompleteFunctionWithResult(
313    const std::string& access_token) {
314
315  SetResult(new base::StringValue(access_token));
316  CompleteAsyncRun(true);
317}
318
319void IdentityGetAuthTokenFunction::CompleteFunctionWithError(
320    const std::string& error) {
321  error_ = error;
322  CompleteAsyncRun(false);
323}
324
325void IdentityGetAuthTokenFunction::StartSigninFlow() {
326  // All cached tokens are invalid because the user is not signed in.
327  IdentityAPI* id_api =
328      extensions::IdentityAPI::GetFactoryInstance()->Get(GetProfile());
329  id_api->EraseAllCachedTokens();
330  // Display a login prompt. If the subsequent mint fails, don't display the
331  // login prompt again.
332  should_prompt_for_signin_ = false;
333  ShowLoginPopup();
334}
335
336void IdentityGetAuthTokenFunction::StartMintTokenFlow(
337    IdentityMintRequestQueue::MintType type) {
338  mint_token_flow_type_ = type;
339
340  // Flows are serialized to prevent excessive traffic to GAIA, and
341  // to consolidate UI pop-ups.
342  IdentityAPI* id_api =
343      extensions::IdentityAPI::GetFactoryInstance()->Get(GetProfile());
344
345  if (!should_prompt_for_scopes_) {
346    // Caller requested no interaction.
347
348    if (type == IdentityMintRequestQueue::MINT_TYPE_INTERACTIVE) {
349      // GAIA told us to do a consent UI.
350      CompleteFunctionWithError(identity_constants::kNoGrant);
351      return;
352    }
353    if (!id_api->mint_queue()->empty(
354            IdentityMintRequestQueue::MINT_TYPE_INTERACTIVE, *token_key_)) {
355      // Another call is going through a consent UI.
356      CompleteFunctionWithError(identity_constants::kNoGrant);
357      return;
358    }
359  }
360  id_api->mint_queue()->RequestStart(type, *token_key_, this);
361}
362
363void IdentityGetAuthTokenFunction::CompleteMintTokenFlow() {
364  IdentityMintRequestQueue::MintType type = mint_token_flow_type_;
365
366  const OAuth2Info& oauth2_info = OAuth2Info::GetOAuth2Info(GetExtension());
367  std::set<std::string> scopes(oauth2_info.scopes.begin(),
368                               oauth2_info.scopes.end());
369
370  extensions::IdentityAPI::GetFactoryInstance()
371      ->Get(GetProfile())
372      ->mint_queue()
373      ->RequestComplete(type, *token_key_, this);
374}
375
376void IdentityGetAuthTokenFunction::StartMintToken(
377    IdentityMintRequestQueue::MintType type) {
378  const OAuth2Info& oauth2_info = OAuth2Info::GetOAuth2Info(GetExtension());
379  IdentityAPI* id_api = IdentityAPI::GetFactoryInstance()->Get(GetProfile());
380  IdentityTokenCacheValue cache_entry = id_api->GetCachedToken(*token_key_);
381  IdentityTokenCacheValue::CacheValueStatus cache_status =
382      cache_entry.status();
383
384  if (type == IdentityMintRequestQueue::MINT_TYPE_NONINTERACTIVE) {
385    switch (cache_status) {
386      case IdentityTokenCacheValue::CACHE_STATUS_NOTFOUND:
387#if defined(OS_CHROMEOS)
388        // Always force minting token for ChromeOS kiosk app.
389        if (chromeos::UserManager::Get()->IsLoggedInAsKioskApp()) {
390          gaia_mint_token_mode_ = OAuth2MintTokenFlow::MODE_MINT_TOKEN_FORCE;
391          policy::BrowserPolicyConnectorChromeOS* connector =
392              g_browser_process->platform_part()
393                  ->browser_policy_connector_chromeos();
394          if (connector->IsEnterpriseManaged()) {
395            StartDeviceLoginAccessTokenRequest();
396          } else {
397            StartLoginAccessTokenRequest();
398          }
399          return;
400        }
401#endif
402
403        if (oauth2_info.auto_approve)
404          // oauth2_info.auto_approve is protected by a whitelist in
405          // _manifest_features.json hence only selected extensions take
406          // advantage of forcefully minting the token.
407          gaia_mint_token_mode_ = OAuth2MintTokenFlow::MODE_MINT_TOKEN_FORCE;
408        else
409          gaia_mint_token_mode_ = OAuth2MintTokenFlow::MODE_MINT_TOKEN_NO_FORCE;
410        StartLoginAccessTokenRequest();
411        break;
412
413      case IdentityTokenCacheValue::CACHE_STATUS_TOKEN:
414        CompleteMintTokenFlow();
415        CompleteFunctionWithResult(cache_entry.token());
416        break;
417
418      case IdentityTokenCacheValue::CACHE_STATUS_ADVICE:
419        CompleteMintTokenFlow();
420        should_prompt_for_signin_ = false;
421        issue_advice_ = cache_entry.issue_advice();
422        StartMintTokenFlow(IdentityMintRequestQueue::MINT_TYPE_INTERACTIVE);
423        break;
424    }
425  } else {
426    DCHECK(type == IdentityMintRequestQueue::MINT_TYPE_INTERACTIVE);
427
428    if (cache_status == IdentityTokenCacheValue::CACHE_STATUS_TOKEN) {
429      CompleteMintTokenFlow();
430      CompleteFunctionWithResult(cache_entry.token());
431    } else {
432      ShowOAuthApprovalDialog(issue_advice_);
433    }
434  }
435}
436
437void IdentityGetAuthTokenFunction::OnMintTokenSuccess(
438    const std::string& access_token, int time_to_live) {
439  IdentityTokenCacheValue token(access_token,
440                                base::TimeDelta::FromSeconds(time_to_live));
441  IdentityAPI::GetFactoryInstance()->Get(GetProfile())->SetCachedToken(
442      *token_key_, token);
443
444  CompleteMintTokenFlow();
445  CompleteFunctionWithResult(access_token);
446}
447
448void IdentityGetAuthTokenFunction::OnMintTokenFailure(
449    const GoogleServiceAuthError& error) {
450  CompleteMintTokenFlow();
451
452  switch (error.state()) {
453    case GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS:
454    case GoogleServiceAuthError::ACCOUNT_DELETED:
455    case GoogleServiceAuthError::ACCOUNT_DISABLED:
456      extensions::IdentityAPI::GetFactoryInstance()
457          ->Get(GetProfile())
458          ->ReportAuthError(error);
459      if (should_prompt_for_signin_) {
460        // Display a login prompt and try again (once).
461        StartSigninFlow();
462        return;
463      }
464      break;
465    default:
466      // Return error to caller.
467      break;
468  }
469
470  CompleteFunctionWithError(
471      std::string(identity_constants::kAuthFailure) + error.ToString());
472}
473
474void IdentityGetAuthTokenFunction::OnIssueAdviceSuccess(
475    const IssueAdviceInfo& issue_advice) {
476  IdentityAPI::GetFactoryInstance()->Get(GetProfile())->SetCachedToken(
477      *token_key_, IdentityTokenCacheValue(issue_advice));
478  CompleteMintTokenFlow();
479
480  should_prompt_for_signin_ = false;
481  // Existing grant was revoked and we used NO_FORCE, so we got info back
482  // instead. Start a consent UI if we can.
483  issue_advice_ = issue_advice;
484  StartMintTokenFlow(IdentityMintRequestQueue::MINT_TYPE_INTERACTIVE);
485}
486
487void IdentityGetAuthTokenFunction::SigninSuccess() {
488  StartMintTokenFlow(IdentityMintRequestQueue::MINT_TYPE_NONINTERACTIVE);
489}
490
491void IdentityGetAuthTokenFunction::SigninFailed() {
492  CompleteFunctionWithError(identity_constants::kUserNotSignedIn);
493}
494
495void IdentityGetAuthTokenFunction::OnGaiaFlowFailure(
496    GaiaWebAuthFlow::Failure failure,
497    GoogleServiceAuthError service_error,
498    const std::string& oauth_error) {
499  CompleteMintTokenFlow();
500  std::string error;
501
502  switch (failure) {
503    case GaiaWebAuthFlow::WINDOW_CLOSED:
504      error = identity_constants::kUserRejected;
505      break;
506
507    case GaiaWebAuthFlow::INVALID_REDIRECT:
508      error = identity_constants::kInvalidRedirect;
509      break;
510
511    case GaiaWebAuthFlow::SERVICE_AUTH_ERROR:
512      error = std::string(identity_constants::kAuthFailure) +
513          service_error.ToString();
514      break;
515
516    case GaiaWebAuthFlow::OAUTH_ERROR:
517      error = MapOAuth2ErrorToDescription(oauth_error);
518      break;
519
520    case GaiaWebAuthFlow::LOAD_FAILED:
521      error = identity_constants::kPageLoadFailure;
522      break;
523
524    default:
525      NOTREACHED() << "Unexpected error from gaia web auth flow: " << failure;
526      error = identity_constants::kInvalidRedirect;
527      break;
528  }
529
530  CompleteFunctionWithError(error);
531}
532
533void IdentityGetAuthTokenFunction::OnGaiaFlowCompleted(
534    const std::string& access_token,
535    const std::string& expiration) {
536
537  int time_to_live;
538  if (!expiration.empty() && base::StringToInt(expiration, &time_to_live)) {
539    IdentityTokenCacheValue token_value(
540        access_token, base::TimeDelta::FromSeconds(time_to_live));
541    IdentityAPI::GetFactoryInstance()->Get(GetProfile())->SetCachedToken(
542        *token_key_, token_value);
543  }
544
545  CompleteMintTokenFlow();
546  CompleteFunctionWithResult(access_token);
547}
548
549void IdentityGetAuthTokenFunction::OnGetTokenSuccess(
550    const OAuth2TokenService::Request* request,
551    const std::string& access_token,
552    const base::Time& expiration_time) {
553  login_token_request_.reset();
554  StartGaiaRequest(access_token);
555}
556
557void IdentityGetAuthTokenFunction::OnGetTokenFailure(
558    const OAuth2TokenService::Request* request,
559    const GoogleServiceAuthError& error) {
560  login_token_request_.reset();
561  OnGaiaFlowFailure(GaiaWebAuthFlow::SERVICE_AUTH_ERROR, error, std::string());
562}
563
564void IdentityGetAuthTokenFunction::OnShutdown() {
565  gaia_web_auth_flow_.reset();
566  signin_flow_.reset();
567  login_token_request_.reset();
568  extensions::IdentityAPI::GetFactoryInstance()
569      ->Get(GetProfile())
570      ->mint_queue()
571      ->RequestCancel(*token_key_, this);
572  CompleteFunctionWithError(identity_constants::kCanceled);
573}
574
575#if defined(OS_CHROMEOS)
576void IdentityGetAuthTokenFunction::StartDeviceLoginAccessTokenRequest() {
577  chromeos::DeviceOAuth2TokenService* service =
578      chromeos::DeviceOAuth2TokenServiceFactory::Get();
579  // Since robot account refresh tokens are scoped down to [any-api] only,
580  // request access token for [any-api] instead of login.
581  OAuth2TokenService::ScopeSet scopes;
582  scopes.insert(GaiaConstants::kAnyApiOAuth2Scope);
583  login_token_request_ =
584      service->StartRequest(service->GetRobotAccountId(),
585                            scopes,
586                            this);
587}
588#endif
589
590void IdentityGetAuthTokenFunction::StartLoginAccessTokenRequest() {
591  ProfileOAuth2TokenService* service =
592      ProfileOAuth2TokenServiceFactory::GetForProfile(GetProfile());
593  const std::string primary_account_id = GetPrimaryAccountId(GetProfile());
594#if defined(OS_CHROMEOS)
595  if (chrome::IsRunningInForcedAppMode()) {
596    std::string app_client_id;
597    std::string app_client_secret;
598    if (chromeos::UserManager::Get()->GetAppModeChromeClientOAuthInfo(
599           &app_client_id, &app_client_secret)) {
600      login_token_request_ =
601          service->StartRequestForClient(primary_account_id,
602                                         app_client_id,
603                                         app_client_secret,
604                                         OAuth2TokenService::ScopeSet(),
605                                         this);
606      return;
607    }
608  }
609#endif
610  login_token_request_ = service->StartRequest(
611      primary_account_id, OAuth2TokenService::ScopeSet(), this);
612}
613
614void IdentityGetAuthTokenFunction::StartGaiaRequest(
615    const std::string& login_access_token) {
616  DCHECK(!login_access_token.empty());
617  mint_token_flow_.reset(CreateMintTokenFlow(login_access_token));
618  mint_token_flow_->Start();
619}
620
621void IdentityGetAuthTokenFunction::ShowLoginPopup() {
622  signin_flow_.reset(new IdentitySigninFlow(this, GetProfile()));
623  signin_flow_->Start();
624}
625
626void IdentityGetAuthTokenFunction::ShowOAuthApprovalDialog(
627    const IssueAdviceInfo& issue_advice) {
628  const OAuth2Info& oauth2_info = OAuth2Info::GetOAuth2Info(GetExtension());
629  const std::string locale = g_browser_process->local_state()->GetString(
630      prefs::kApplicationLocale);
631
632  gaia_web_auth_flow_.reset(new GaiaWebAuthFlow(
633      this, GetProfile(), GetExtension()->id(), oauth2_info, locale));
634  gaia_web_auth_flow_->Start();
635}
636
637OAuth2MintTokenFlow* IdentityGetAuthTokenFunction::CreateMintTokenFlow(
638    const std::string& login_access_token) {
639  const OAuth2Info& oauth2_info = OAuth2Info::GetOAuth2Info(GetExtension());
640
641  OAuth2MintTokenFlow* mint_token_flow = new OAuth2MintTokenFlow(
642      GetProfile()->GetRequestContext(),
643      this,
644      OAuth2MintTokenFlow::Parameters(login_access_token,
645                                      GetExtension()->id(),
646                                      oauth2_client_id_,
647                                      oauth2_info.scopes,
648                                      gaia_mint_token_mode_));
649  return mint_token_flow;
650}
651
652bool IdentityGetAuthTokenFunction::HasLoginToken() const {
653  ProfileOAuth2TokenService* token_service =
654      ProfileOAuth2TokenServiceFactory::GetForProfile(GetProfile());
655  return token_service->RefreshTokenIsAvailable(
656      GetPrimaryAccountId(GetProfile()));
657}
658
659std::string IdentityGetAuthTokenFunction::MapOAuth2ErrorToDescription(
660    const std::string& error) {
661  const char kOAuth2ErrorAccessDenied[] = "access_denied";
662  const char kOAuth2ErrorInvalidScope[] = "invalid_scope";
663
664  if (error == kOAuth2ErrorAccessDenied)
665    return std::string(identity_constants::kUserRejected);
666  else if (error == kOAuth2ErrorInvalidScope)
667    return std::string(identity_constants::kInvalidScopes);
668  else
669    return std::string(identity_constants::kAuthFailure) + error;
670}
671
672std::string IdentityGetAuthTokenFunction::GetOAuth2ClientId() const {
673  const OAuth2Info& oauth2_info = OAuth2Info::GetOAuth2Info(GetExtension());
674  std::string client_id = oauth2_info.client_id;
675
676  // Component apps using auto_approve may use Chrome's client ID by
677  // omitting the field.
678  if (client_id.empty() && GetExtension()->location() == Manifest::COMPONENT &&
679      oauth2_info.auto_approve) {
680    client_id = GaiaUrls::GetInstance()->oauth2_chrome_client_id();
681  }
682  return client_id;
683}
684
685IdentityRemoveCachedAuthTokenFunction::IdentityRemoveCachedAuthTokenFunction() {
686}
687
688IdentityRemoveCachedAuthTokenFunction::
689    ~IdentityRemoveCachedAuthTokenFunction() {
690}
691
692bool IdentityRemoveCachedAuthTokenFunction::RunSync() {
693  if (GetProfile()->IsOffTheRecord()) {
694    error_ = identity_constants::kOffTheRecord;
695    return false;
696  }
697
698  scoped_ptr<identity::RemoveCachedAuthToken::Params> params(
699      identity::RemoveCachedAuthToken::Params::Create(*args_));
700  EXTENSION_FUNCTION_VALIDATE(params.get());
701  IdentityAPI::GetFactoryInstance()->Get(GetProfile())->EraseCachedToken(
702      GetExtension()->id(), params->details.token);
703  return true;
704}
705
706IdentityLaunchWebAuthFlowFunction::IdentityLaunchWebAuthFlowFunction() {}
707
708IdentityLaunchWebAuthFlowFunction::~IdentityLaunchWebAuthFlowFunction() {
709  if (auth_flow_)
710    auth_flow_.release()->DetachDelegateAndDelete();
711}
712
713bool IdentityLaunchWebAuthFlowFunction::RunImpl() {
714  if (GetProfile()->IsOffTheRecord()) {
715    error_ = identity_constants::kOffTheRecord;
716    return false;
717  }
718
719  scoped_ptr<identity::LaunchWebAuthFlow::Params> params(
720      identity::LaunchWebAuthFlow::Params::Create(*args_));
721  EXTENSION_FUNCTION_VALIDATE(params.get());
722
723  GURL auth_url(params->details.url);
724  WebAuthFlow::Mode mode =
725      params->details.interactive && *params->details.interactive ?
726      WebAuthFlow::INTERACTIVE : WebAuthFlow::SILENT;
727
728  // Set up acceptable target URLs. (Does not include chrome-extension
729  // scheme for this version of the API.)
730  InitFinalRedirectURLPrefix(GetExtension()->id());
731
732  AddRef();  // Balanced in OnAuthFlowSuccess/Failure.
733
734  auth_flow_.reset(new WebAuthFlow(this, GetProfile(), auth_url, mode));
735  auth_flow_->Start();
736  return true;
737}
738
739void IdentityLaunchWebAuthFlowFunction::InitFinalRedirectURLPrefixForTest(
740    const std::string& extension_id) {
741  InitFinalRedirectURLPrefix(extension_id);
742}
743
744void IdentityLaunchWebAuthFlowFunction::InitFinalRedirectURLPrefix(
745    const std::string& extension_id) {
746  if (final_url_prefix_.is_empty()) {
747    final_url_prefix_ = GURL(base::StringPrintf(
748        kChromiumDomainRedirectUrlPattern, extension_id.c_str()));
749  }
750}
751
752void IdentityLaunchWebAuthFlowFunction::OnAuthFlowFailure(
753    WebAuthFlow::Failure failure) {
754  switch (failure) {
755    case WebAuthFlow::WINDOW_CLOSED:
756      error_ = identity_constants::kUserRejected;
757      break;
758    case WebAuthFlow::INTERACTION_REQUIRED:
759      error_ = identity_constants::kInteractionRequired;
760      break;
761    case WebAuthFlow::LOAD_FAILED:
762      error_ = identity_constants::kPageLoadFailure;
763      break;
764    default:
765      NOTREACHED() << "Unexpected error from web auth flow: " << failure;
766      error_ = identity_constants::kInvalidRedirect;
767      break;
768  }
769  SendResponse(false);
770  Release();  // Balanced in RunImpl.
771}
772
773void IdentityLaunchWebAuthFlowFunction::OnAuthFlowURLChange(
774    const GURL& redirect_url) {
775  if (redirect_url.GetWithEmptyPath() == final_url_prefix_) {
776    SetResult(new base::StringValue(redirect_url.spec()));
777    SendResponse(true);
778    Release();  // Balanced in RunImpl.
779  }
780}
781
782}  // namespace extensions
783