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