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