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