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