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