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