identity_api.cc revision 4e180b6a0b4720a9b8e9e959a882386f690f08ff
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            chromeos::DeviceOAuth2TokenService* token_service =
223                chromeos::DeviceOAuth2TokenServiceFactory::Get();
224            device_token_request_ =
225                token_service->StartRequest(token_service->GetRobotAccountId(),
226                                            scope_set,
227                                            this);
228          } else {
229            gaia_mint_token_mode_ = OAuth2MintTokenFlow::MODE_MINT_TOKEN_FORCE;
230            StartLoginAccessTokenRequest();
231          }
232          return;
233        }
234#endif
235
236        if (oauth2_info.auto_approve)
237          // oauth2_info.auto_approve is protected by a whitelist in
238          // _manifest_features.json hence only selected extensions take
239          // advantage of forcefully minting the token.
240          gaia_mint_token_mode_ = OAuth2MintTokenFlow::MODE_MINT_TOKEN_FORCE;
241        else
242          gaia_mint_token_mode_ = OAuth2MintTokenFlow::MODE_MINT_TOKEN_NO_FORCE;
243        StartLoginAccessTokenRequest();
244        break;
245
246      case IdentityTokenCacheValue::CACHE_STATUS_TOKEN:
247        CompleteMintTokenFlow();
248        CompleteFunctionWithResult(cache_entry.token());
249        break;
250
251      case IdentityTokenCacheValue::CACHE_STATUS_ADVICE:
252        CompleteMintTokenFlow();
253        should_prompt_for_signin_ = false;
254        issue_advice_ = cache_entry.issue_advice();
255        StartMintTokenFlow(IdentityMintRequestQueue::MINT_TYPE_INTERACTIVE);
256        break;
257    }
258  } else {
259    DCHECK(type == IdentityMintRequestQueue::MINT_TYPE_INTERACTIVE);
260
261    if (cache_status == IdentityTokenCacheValue::CACHE_STATUS_TOKEN) {
262      CompleteMintTokenFlow();
263      CompleteFunctionWithResult(cache_entry.token());
264    } else {
265      ShowOAuthApprovalDialog(issue_advice_);
266    }
267  }
268}
269
270void IdentityGetAuthTokenFunction::OnMintTokenSuccess(
271    const std::string& access_token, int time_to_live) {
272  const OAuth2Info& oauth2_info = OAuth2Info::GetOAuth2Info(GetExtension());
273  IdentityTokenCacheValue token(access_token,
274                                base::TimeDelta::FromSeconds(time_to_live));
275  IdentityAPI::GetFactoryInstance()->GetForProfile(profile())->SetCachedToken(
276      GetExtension()->id(), oauth2_info.scopes, token);
277
278  CompleteMintTokenFlow();
279  CompleteFunctionWithResult(access_token);
280}
281
282void IdentityGetAuthTokenFunction::OnMintTokenFailure(
283    const GoogleServiceAuthError& error) {
284  CompleteMintTokenFlow();
285
286  switch (error.state()) {
287    case GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS:
288    case GoogleServiceAuthError::ACCOUNT_DELETED:
289    case GoogleServiceAuthError::ACCOUNT_DISABLED:
290      extensions::IdentityAPI::GetFactoryInstance()->GetForProfile(
291          profile())->ReportAuthError(error);
292      if (should_prompt_for_signin_) {
293        // Display a login prompt and try again (once).
294        StartSigninFlow();
295        return;
296      }
297      break;
298    default:
299      // Return error to caller.
300      break;
301  }
302
303  CompleteFunctionWithError(
304      std::string(identity_constants::kAuthFailure) + error.ToString());
305}
306
307void IdentityGetAuthTokenFunction::OnIssueAdviceSuccess(
308    const IssueAdviceInfo& issue_advice) {
309  const OAuth2Info& oauth2_info = OAuth2Info::GetOAuth2Info(GetExtension());
310  IdentityAPI::GetFactoryInstance()->GetForProfile(profile())->SetCachedToken(
311      GetExtension()->id(), oauth2_info.scopes,
312      IdentityTokenCacheValue(issue_advice));
313  CompleteMintTokenFlow();
314
315  should_prompt_for_signin_ = false;
316  // Existing grant was revoked and we used NO_FORCE, so we got info back
317  // instead. Start a consent UI if we can.
318  issue_advice_ = issue_advice;
319  StartMintTokenFlow(IdentityMintRequestQueue::MINT_TYPE_INTERACTIVE);
320}
321
322void IdentityGetAuthTokenFunction::SigninSuccess() {
323  StartMintTokenFlow(IdentityMintRequestQueue::MINT_TYPE_NONINTERACTIVE);
324}
325
326void IdentityGetAuthTokenFunction::SigninFailed() {
327  CompleteFunctionWithError(identity_constants::kUserNotSignedIn);
328}
329
330void IdentityGetAuthTokenFunction::OnGaiaFlowFailure(
331    GaiaWebAuthFlow::Failure failure,
332    GoogleServiceAuthError service_error,
333    const std::string& oauth_error) {
334  CompleteMintTokenFlow();
335  std::string error;
336
337  switch (failure) {
338    case GaiaWebAuthFlow::WINDOW_CLOSED:
339      error = identity_constants::kUserRejected;
340      break;
341
342    case GaiaWebAuthFlow::INVALID_REDIRECT:
343      error = identity_constants::kInvalidRedirect;
344      break;
345
346    case GaiaWebAuthFlow::SERVICE_AUTH_ERROR:
347      error = std::string(identity_constants::kAuthFailure) +
348          service_error.ToString();
349      break;
350
351    case GaiaWebAuthFlow::OAUTH_ERROR:
352      error = MapOAuth2ErrorToDescription(oauth_error);
353      break;
354
355    case GaiaWebAuthFlow::LOAD_FAILED:
356      error = identity_constants::kPageLoadFailure;
357      break;
358
359    default:
360      NOTREACHED() << "Unexpected error from gaia web auth flow: " << failure;
361      error = identity_constants::kInvalidRedirect;
362      break;
363  }
364
365  CompleteFunctionWithError(error);
366}
367
368void IdentityGetAuthTokenFunction::OnGaiaFlowCompleted(
369    const std::string& access_token,
370    const std::string& expiration) {
371
372  int time_to_live;
373  if (!expiration.empty() && base::StringToInt(expiration, &time_to_live)) {
374    const OAuth2Info& oauth2_info = OAuth2Info::GetOAuth2Info(GetExtension());
375    IdentityTokenCacheValue token_value(
376        access_token, base::TimeDelta::FromSeconds(time_to_live));
377    IdentityAPI::GetFactoryInstance()->GetForProfile(profile())
378        ->SetCachedToken(GetExtension()->id(), oauth2_info.scopes, token_value);
379  }
380
381  CompleteMintTokenFlow();
382  CompleteFunctionWithResult(access_token);
383}
384
385void IdentityGetAuthTokenFunction::OnGetTokenSuccess(
386    const OAuth2TokenService::Request* request,
387    const std::string& access_token,
388    const base::Time& expiration_time) {
389  if (login_token_request_.get() == request) {
390    login_token_request_.reset();
391    StartGaiaRequest(access_token);
392  } else {
393    DCHECK_EQ(device_token_request_.get(), request);
394    device_token_request_.reset();
395
396    const OAuth2Info& oauth2_info = OAuth2Info::GetOAuth2Info(GetExtension());
397    IdentityTokenCacheValue token(access_token,
398                                  expiration_time - base::Time::Now());
399    IdentityAPI::GetFactoryInstance()->GetForProfile(profile())->SetCachedToken(
400        GetExtension()->id(), oauth2_info.scopes, token);
401
402    CompleteMintTokenFlow();
403    CompleteFunctionWithResult(access_token);
404  }
405}
406
407void IdentityGetAuthTokenFunction::OnGetTokenFailure(
408    const OAuth2TokenService::Request* request,
409    const GoogleServiceAuthError& error) {
410  if (login_token_request_.get() == request) {
411    login_token_request_.reset();
412  } else {
413    DCHECK_EQ(device_token_request_.get(), request);
414    device_token_request_.reset();
415  }
416
417  OnGaiaFlowFailure(GaiaWebAuthFlow::SERVICE_AUTH_ERROR, error, std::string());
418}
419
420void IdentityGetAuthTokenFunction::StartLoginAccessTokenRequest() {
421  ProfileOAuth2TokenService* service =
422      ProfileOAuth2TokenServiceFactory::GetForProfile(profile());
423#if defined(OS_CHROMEOS)
424  if (chrome::IsRunningInForcedAppMode()) {
425    std::string app_client_id;
426    std::string app_client_secret;
427    if (chromeos::UserManager::Get()->GetAppModeChromeClientOAuthInfo(
428           &app_client_id, &app_client_secret)) {
429      login_token_request_ =
430          service->StartRequestForClient(service->GetPrimaryAccountId(),
431                                         app_client_id,
432                                         app_client_secret,
433                                         OAuth2TokenService::ScopeSet(),
434                                         this);
435      return;
436    }
437  }
438#endif
439  login_token_request_ = service->StartRequest(
440      service->GetPrimaryAccountId(), OAuth2TokenService::ScopeSet(), this);
441}
442
443void IdentityGetAuthTokenFunction::StartGaiaRequest(
444    const std::string& login_access_token) {
445  DCHECK(!login_access_token.empty());
446  mint_token_flow_.reset(CreateMintTokenFlow(login_access_token));
447  mint_token_flow_->Start();
448}
449
450void IdentityGetAuthTokenFunction::ShowLoginPopup() {
451  signin_flow_.reset(new IdentitySigninFlow(this, profile()));
452  signin_flow_->Start();
453}
454
455void IdentityGetAuthTokenFunction::ShowOAuthApprovalDialog(
456    const IssueAdviceInfo& issue_advice) {
457  const OAuth2Info& oauth2_info = OAuth2Info::GetOAuth2Info(GetExtension());
458  const std::string locale = g_browser_process->local_state()->GetString(
459      prefs::kApplicationLocale);
460
461  gaia_web_auth_flow_.reset(new GaiaWebAuthFlow(
462      this, profile(), GetExtension()->id(), oauth2_info, locale));
463  gaia_web_auth_flow_->Start();
464}
465
466OAuth2MintTokenFlow* IdentityGetAuthTokenFunction::CreateMintTokenFlow(
467    const std::string& login_access_token) {
468  const OAuth2Info& oauth2_info = OAuth2Info::GetOAuth2Info(GetExtension());
469
470  OAuth2MintTokenFlow* mint_token_flow =
471      new OAuth2MintTokenFlow(
472          profile()->GetRequestContext(),
473          this,
474          OAuth2MintTokenFlow::Parameters(
475              login_access_token,
476              GetExtension()->id(),
477              oauth2_client_id_,
478              oauth2_info.scopes,
479              gaia_mint_token_mode_));
480  return mint_token_flow;
481}
482
483bool IdentityGetAuthTokenFunction::HasLoginToken() const {
484  ProfileOAuth2TokenService* token_service =
485      ProfileOAuth2TokenServiceFactory::GetForProfile(profile());
486  return token_service->RefreshTokenIsAvailable(
487      token_service->GetPrimaryAccountId());
488}
489
490std::string IdentityGetAuthTokenFunction::MapOAuth2ErrorToDescription(
491    const std::string& error) {
492  const char kOAuth2ErrorAccessDenied[] = "access_denied";
493  const char kOAuth2ErrorInvalidScope[] = "invalid_scope";
494
495  if (error == kOAuth2ErrorAccessDenied)
496    return std::string(identity_constants::kUserRejected);
497  else if (error == kOAuth2ErrorInvalidScope)
498    return std::string(identity_constants::kInvalidScopes);
499  else
500    return std::string(identity_constants::kAuthFailure) + error;
501}
502
503std::string IdentityGetAuthTokenFunction::GetOAuth2ClientId() const {
504  const OAuth2Info& oauth2_info = OAuth2Info::GetOAuth2Info(GetExtension());
505  std::string client_id = oauth2_info.client_id;
506
507  // Component apps using auto_approve may use Chrome's client ID by
508  // omitting the field.
509  if (client_id.empty() && GetExtension()->location() == Manifest::COMPONENT &&
510      oauth2_info.auto_approve) {
511    client_id = GaiaUrls::GetInstance()->oauth2_chrome_client_id();
512  }
513  return client_id;
514}
515
516IdentityRemoveCachedAuthTokenFunction::IdentityRemoveCachedAuthTokenFunction() {
517}
518
519IdentityRemoveCachedAuthTokenFunction::
520    ~IdentityRemoveCachedAuthTokenFunction() {
521}
522
523bool IdentityRemoveCachedAuthTokenFunction::RunImpl() {
524  if (profile()->IsOffTheRecord()) {
525    error_ = identity_constants::kOffTheRecord;
526    return false;
527  }
528
529  scoped_ptr<identity::RemoveCachedAuthToken::Params> params(
530      identity::RemoveCachedAuthToken::Params::Create(*args_));
531  EXTENSION_FUNCTION_VALIDATE(params.get());
532  IdentityAPI::GetFactoryInstance()->GetForProfile(profile())->EraseCachedToken(
533      GetExtension()->id(), params->details.token);
534  return true;
535}
536
537IdentityLaunchWebAuthFlowFunction::IdentityLaunchWebAuthFlowFunction() {}
538
539IdentityLaunchWebAuthFlowFunction::~IdentityLaunchWebAuthFlowFunction() {
540  if (auth_flow_)
541    auth_flow_.release()->DetachDelegateAndDelete();
542}
543
544bool IdentityLaunchWebAuthFlowFunction::RunImpl() {
545  if (profile()->IsOffTheRecord()) {
546    error_ = identity_constants::kOffTheRecord;
547    return false;
548  }
549
550  scoped_ptr<identity::LaunchWebAuthFlow::Params> params(
551      identity::LaunchWebAuthFlow::Params::Create(*args_));
552  EXTENSION_FUNCTION_VALIDATE(params.get());
553
554  GURL auth_url(params->details.url);
555  WebAuthFlow::Mode mode =
556      params->details.interactive && *params->details.interactive ?
557      WebAuthFlow::INTERACTIVE : WebAuthFlow::SILENT;
558
559  // Set up acceptable target URLs. (Does not include chrome-extension
560  // scheme for this version of the API.)
561  InitFinalRedirectURLPrefix(GetExtension()->id());
562
563  AddRef();  // Balanced in OnAuthFlowSuccess/Failure.
564
565  auth_flow_.reset(new WebAuthFlow(this, profile(), auth_url, mode));
566  auth_flow_->Start();
567  return true;
568}
569
570void IdentityLaunchWebAuthFlowFunction::InitFinalRedirectURLPrefixForTest(
571    const std::string& extension_id) {
572  InitFinalRedirectURLPrefix(extension_id);
573}
574
575void IdentityLaunchWebAuthFlowFunction::InitFinalRedirectURLPrefix(
576    const std::string& extension_id) {
577  if (final_url_prefix_.is_empty()) {
578    final_url_prefix_ = GURL(base::StringPrintf(
579        kChromiumDomainRedirectUrlPattern, extension_id.c_str()));
580  }
581}
582
583void IdentityLaunchWebAuthFlowFunction::OnAuthFlowFailure(
584    WebAuthFlow::Failure failure) {
585  switch (failure) {
586    case WebAuthFlow::WINDOW_CLOSED:
587      error_ = identity_constants::kUserRejected;
588      break;
589    case WebAuthFlow::INTERACTION_REQUIRED:
590      error_ = identity_constants::kInteractionRequired;
591      break;
592    case WebAuthFlow::LOAD_FAILED:
593      error_ = identity_constants::kPageLoadFailure;
594      break;
595    default:
596      NOTREACHED() << "Unexpected error from web auth flow: " << failure;
597      error_ = identity_constants::kInvalidRedirect;
598      break;
599  }
600  SendResponse(false);
601  Release();  // Balanced in RunImpl.
602}
603
604void IdentityLaunchWebAuthFlowFunction::OnAuthFlowURLChange(
605    const GURL& redirect_url) {
606  if (redirect_url.GetWithEmptyPath() == final_url_prefix_) {
607    SetResult(new base::StringValue(redirect_url.spec()));
608    SendResponse(true);
609    Release();  // Balanced in RunImpl.
610  }
611}
612
613IdentityTokenCacheValue::IdentityTokenCacheValue()
614    : status_(CACHE_STATUS_NOTFOUND) {
615}
616
617IdentityTokenCacheValue::IdentityTokenCacheValue(
618    const IssueAdviceInfo& issue_advice) : status_(CACHE_STATUS_ADVICE),
619                                           issue_advice_(issue_advice) {
620  expiration_time_ = base::Time::Now() + base::TimeDelta::FromSeconds(
621      identity_constants::kCachedIssueAdviceTTLSeconds);
622}
623
624IdentityTokenCacheValue::IdentityTokenCacheValue(
625    const std::string& token, base::TimeDelta time_to_live)
626    : status_(CACHE_STATUS_TOKEN),
627      token_(token) {
628  // Remove 20 minutes from the ttl so cached tokens will have some time
629  // to live any time they are returned.
630  time_to_live -= base::TimeDelta::FromMinutes(20);
631
632  base::TimeDelta zero_delta;
633  if (time_to_live < zero_delta)
634    time_to_live = zero_delta;
635
636  expiration_time_ = base::Time::Now() + time_to_live;
637}
638
639IdentityTokenCacheValue::~IdentityTokenCacheValue() {
640}
641
642IdentityTokenCacheValue::CacheValueStatus
643    IdentityTokenCacheValue::status() const {
644  if (is_expired())
645    return IdentityTokenCacheValue::CACHE_STATUS_NOTFOUND;
646  else
647    return status_;
648}
649
650const IssueAdviceInfo& IdentityTokenCacheValue::issue_advice() const {
651  return issue_advice_;
652}
653
654const std::string& IdentityTokenCacheValue::token() const {
655  return token_;
656}
657
658bool IdentityTokenCacheValue::is_expired() const {
659  return status_ == CACHE_STATUS_NOTFOUND ||
660      expiration_time_ < base::Time::Now();
661}
662
663const base::Time& IdentityTokenCacheValue::expiration_time() const {
664  return expiration_time_;
665}
666
667IdentityAPI::IdentityAPI(Profile* profile)
668    : profile_(profile),
669      error_(GoogleServiceAuthError::NONE) {
670  SigninGlobalError::GetForProfile(profile_)->AddProvider(this);
671  ProfileOAuth2TokenServiceFactory::GetForProfile(profile_)->AddObserver(this);
672}
673
674IdentityAPI::~IdentityAPI() {
675}
676
677IdentityMintRequestQueue* IdentityAPI::mint_queue() {
678    return &mint_queue_;
679}
680
681void IdentityAPI::SetCachedToken(const std::string& extension_id,
682                                 const std::vector<std::string> scopes,
683                                 const IdentityTokenCacheValue& token_data) {
684  std::set<std::string> scopeset(scopes.begin(), scopes.end());
685  TokenCacheKey key(extension_id, scopeset);
686
687  CachedTokens::iterator it = token_cache_.find(key);
688  if (it != token_cache_.end() && it->second.status() <= token_data.status())
689    token_cache_.erase(it);
690
691  token_cache_.insert(std::make_pair(key, token_data));
692}
693
694void IdentityAPI::EraseCachedToken(const std::string& extension_id,
695                                   const std::string& token) {
696  CachedTokens::iterator it;
697  for (it = token_cache_.begin(); it != token_cache_.end(); ++it) {
698    if (it->first.extension_id == extension_id &&
699        it->second.status() == IdentityTokenCacheValue::CACHE_STATUS_TOKEN &&
700        it->second.token() == token) {
701      token_cache_.erase(it);
702      break;
703    }
704  }
705}
706
707void IdentityAPI::EraseAllCachedTokens() {
708  token_cache_.clear();
709}
710
711const IdentityTokenCacheValue& IdentityAPI::GetCachedToken(
712    const std::string& extension_id, const std::vector<std::string> scopes) {
713  std::set<std::string> scopeset(scopes.begin(), scopes.end());
714  TokenCacheKey key(extension_id, scopeset);
715  return token_cache_[key];
716}
717
718const IdentityAPI::CachedTokens& IdentityAPI::GetAllCachedTokens() {
719  return token_cache_;
720}
721
722void IdentityAPI::ReportAuthError(const GoogleServiceAuthError& error) {
723  error_ = error;
724  SigninGlobalError::GetForProfile(profile_)->AuthStatusChanged();
725}
726
727void IdentityAPI::Shutdown() {
728  SigninGlobalError::GetForProfile(profile_)->RemoveProvider(this);
729  ProfileOAuth2TokenServiceFactory::GetForProfile(profile_)->
730      RemoveObserver(this);
731}
732
733static base::LazyInstance<ProfileKeyedAPIFactory<IdentityAPI> >
734    g_factory = LAZY_INSTANCE_INITIALIZER;
735
736// static
737ProfileKeyedAPIFactory<IdentityAPI>* IdentityAPI::GetFactoryInstance() {
738  return &g_factory.Get();
739}
740
741std::string IdentityAPI::GetAccountId() const {
742  return ProfileOAuth2TokenServiceFactory::GetForProfile(profile_)->
743      GetPrimaryAccountId();
744}
745
746GoogleServiceAuthError IdentityAPI::GetAuthStatus() const {
747  return error_;
748}
749
750void IdentityAPI::OnRefreshTokenAvailable(const std::string& account_id) {
751  error_ = GoogleServiceAuthError::AuthErrorNone();
752}
753
754template <>
755void ProfileKeyedAPIFactory<IdentityAPI>::DeclareFactoryDependencies() {
756  DependsOn(ExtensionSystemFactory::GetInstance());
757  // Need dependency on ProfileOAuth2TokenServiceFactory because it owns
758  // the SigninGlobalError instance.
759  DependsOn(ProfileOAuth2TokenServiceFactory::GetInstance());
760}
761
762IdentityAPI::TokenCacheKey::TokenCacheKey(const std::string& extension_id,
763                                          const std::set<std::string> scopes)
764    : extension_id(extension_id),
765      scopes(scopes) {
766}
767
768IdentityAPI::TokenCacheKey::~TokenCacheKey() {
769}
770
771bool IdentityAPI::TokenCacheKey::operator<(
772    const IdentityAPI::TokenCacheKey& rhs) const {
773  if (extension_id < rhs.extension_id)
774    return true;
775  else if (rhs.extension_id < extension_id)
776    return false;
777
778  return scopes < rhs.scopes;
779}
780
781}  // namespace extensions
782