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