identity_api.cc revision 23730a6e56a168d1879203e4b3819bb36e3d8f1f
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_service.h"
21#include "chrome/browser/profiles/profile.h"
22#include "chrome/browser/signin/profile_oauth2_token_service_factory.h"
23#include "chrome/browser/signin/signin_global_error.h"
24#include "chrome/browser/signin/signin_manager.h"
25#include "chrome/browser/signin/signin_manager_factory.h"
26#include "chrome/common/extensions/api/identity.h"
27#include "chrome/common/extensions/api/identity/oauth2_manifest_handler.h"
28#include "chrome/common/pref_names.h"
29#include "chrome/common/url_constants.h"
30#include "components/signin/core/profile_oauth2_token_service.h"
31#include "extensions/browser/event_router.h"
32#include "extensions/browser/extension_function_dispatcher.h"
33#include "extensions/browser/extension_system.h"
34#include "extensions/common/extension.h"
35#include "google_apis/gaia/gaia_urls.h"
36#include "url/gurl.h"
37
38#if defined(OS_CHROMEOS)
39#include "chrome/browser/chromeos/login/user_manager.h"
40#include "chrome/browser/chromeos/policy/browser_policy_connector_chromeos.h"
41#include "chrome/browser/chromeos/settings/device_oauth2_token_service.h"
42#include "chrome/browser/chromeos/settings/device_oauth2_token_service_factory.h"
43#include "google_apis/gaia/gaia_constants.h"
44#endif
45
46namespace extensions {
47
48namespace identity_constants {
49const char kInvalidClientId[] = "Invalid OAuth2 Client ID.";
50const char kInvalidScopes[] = "Invalid OAuth2 scopes.";
51const char kAuthFailure[] = "OAuth2 request failed: ";
52const char kNoGrant[] = "OAuth2 not granted or revoked.";
53const char kUserRejected[] = "The user did not approve access.";
54const char kUserNotSignedIn[] = "The user is not signed in.";
55const char kInteractionRequired[] = "User interaction required.";
56const char kInvalidRedirect[] = "Did not redirect to the right URL.";
57const char kOffTheRecord[] = "Identity API is disabled in incognito windows.";
58const char kPageLoadFailure[] = "Authorization page could not be loaded.";
59
60const int kCachedIssueAdviceTTLSeconds = 1;
61}  // namespace identity_constants
62
63namespace {
64
65static const char kChromiumDomainRedirectUrlPattern[] =
66    "https://%s.chromiumapp.org/";
67
68std::string GetPrimaryAccountId(content::BrowserContext* context) {
69  SigninManagerBase* signin_manager =
70      SigninManagerFactory::GetForProfile(Profile::FromBrowserContext(context));
71  return signin_manager->GetAuthenticatedAccountId();
72}
73
74}  // namespace
75
76namespace identity = api::identity;
77
78IdentityGetAuthTokenFunction::IdentityGetAuthTokenFunction()
79    : OAuth2TokenService::Consumer("extensions_identity_api"),
80      should_prompt_for_scopes_(false),
81      should_prompt_for_signin_(false) {}
82
83IdentityGetAuthTokenFunction::~IdentityGetAuthTokenFunction() {}
84
85bool IdentityGetAuthTokenFunction::RunImpl() {
86  if (GetProfile()->IsOffTheRecord()) {
87    error_ = identity_constants::kOffTheRecord;
88    return false;
89  }
90
91  scoped_ptr<identity::GetAuthToken::Params> params(
92      identity::GetAuthToken::Params::Create(*args_));
93  EXTENSION_FUNCTION_VALIDATE(params.get());
94  bool interactive = params->details.get() &&
95      params->details->interactive.get() &&
96      *params->details->interactive;
97
98  should_prompt_for_scopes_ = interactive;
99  should_prompt_for_signin_ = interactive;
100
101  const OAuth2Info& oauth2_info = OAuth2Info::GetOAuth2Info(GetExtension());
102
103  // Check that the necessary information is present in the manifest.
104  oauth2_client_id_ = GetOAuth2ClientId();
105  if (oauth2_client_id_.empty()) {
106    error_ = identity_constants::kInvalidClientId;
107    return false;
108  }
109
110  if (oauth2_info.scopes.size() == 0) {
111    error_ = identity_constants::kInvalidScopes;
112    return false;
113  }
114
115  std::set<std::string> scopes(oauth2_info.scopes.begin(),
116                               oauth2_info.scopes.end());
117  token_key_.reset(new ExtensionTokenKey(
118      GetExtension()->id(), GetPrimaryAccountId(GetProfile()), scopes));
119
120  // Balanced in CompleteFunctionWithResult|CompleteFunctionWithError
121  AddRef();
122
123#if defined(OS_CHROMEOS)
124  policy::BrowserPolicyConnectorChromeOS* connector =
125      g_browser_process->platform_part()->browser_policy_connector_chromeos();
126  if (chromeos::UserManager::Get()->IsLoggedInAsKioskApp() &&
127      connector->IsEnterpriseManaged()) {
128    StartMintTokenFlow(IdentityMintRequestQueue::MINT_TYPE_NONINTERACTIVE);
129    return true;
130  }
131#endif
132
133  if (!HasLoginToken()) {
134    if (!should_prompt_for_signin_) {
135      error_ = identity_constants::kUserNotSignedIn;
136      Release();
137      return false;
138    }
139    // Display a login prompt.
140    StartSigninFlow();
141  } else {
142    StartMintTokenFlow(IdentityMintRequestQueue::MINT_TYPE_NONINTERACTIVE);
143  }
144
145  return true;
146}
147
148void IdentityGetAuthTokenFunction::CompleteFunctionWithResult(
149    const std::string& access_token) {
150  SetResult(new base::StringValue(access_token));
151  SendResponse(true);
152  Release();  // Balanced in RunImpl.
153}
154
155void IdentityGetAuthTokenFunction::CompleteFunctionWithError(
156    const std::string& error) {
157  error_ = error;
158  SendResponse(false);
159  Release();  // Balanced in RunImpl.
160}
161
162void IdentityGetAuthTokenFunction::StartSigninFlow() {
163  // All cached tokens are invalid because the user is not signed in.
164  IdentityAPI* id_api =
165      extensions::IdentityAPI::GetFactoryInstance()->Get(GetProfile());
166  id_api->EraseAllCachedTokens();
167  // Display a login prompt. If the subsequent mint fails, don't display the
168  // login prompt again.
169  should_prompt_for_signin_ = false;
170  ShowLoginPopup();
171}
172
173void IdentityGetAuthTokenFunction::StartMintTokenFlow(
174    IdentityMintRequestQueue::MintType type) {
175  mint_token_flow_type_ = type;
176
177  // Flows are serialized to prevent excessive traffic to GAIA, and
178  // to consolidate UI pop-ups.
179  IdentityAPI* id_api =
180      extensions::IdentityAPI::GetFactoryInstance()->Get(GetProfile());
181
182  if (!should_prompt_for_scopes_) {
183    // Caller requested no interaction.
184
185    if (type == IdentityMintRequestQueue::MINT_TYPE_INTERACTIVE) {
186      // GAIA told us to do a consent UI.
187      CompleteFunctionWithError(identity_constants::kNoGrant);
188      return;
189    }
190    if (!id_api->mint_queue()->empty(
191            IdentityMintRequestQueue::MINT_TYPE_INTERACTIVE, *token_key_)) {
192      // Another call is going through a consent UI.
193      CompleteFunctionWithError(identity_constants::kNoGrant);
194      return;
195    }
196  }
197  id_api->mint_queue()->RequestStart(type, *token_key_, this);
198}
199
200void IdentityGetAuthTokenFunction::CompleteMintTokenFlow() {
201  IdentityMintRequestQueue::MintType type = mint_token_flow_type_;
202
203  const OAuth2Info& oauth2_info = OAuth2Info::GetOAuth2Info(GetExtension());
204  std::set<std::string> scopes(oauth2_info.scopes.begin(),
205                               oauth2_info.scopes.end());
206
207  extensions::IdentityAPI::GetFactoryInstance()
208      ->Get(GetProfile())
209      ->mint_queue()
210      ->RequestComplete(type, *token_key_, this);
211}
212
213void IdentityGetAuthTokenFunction::StartMintToken(
214    IdentityMintRequestQueue::MintType type) {
215  const OAuth2Info& oauth2_info = OAuth2Info::GetOAuth2Info(GetExtension());
216  IdentityAPI* id_api = IdentityAPI::GetFactoryInstance()->Get(GetProfile());
217  IdentityTokenCacheValue cache_entry = id_api->GetCachedToken(*token_key_);
218  IdentityTokenCacheValue::CacheValueStatus cache_status =
219      cache_entry.status();
220
221  if (type == IdentityMintRequestQueue::MINT_TYPE_NONINTERACTIVE) {
222    switch (cache_status) {
223      case IdentityTokenCacheValue::CACHE_STATUS_NOTFOUND:
224#if defined(OS_CHROMEOS)
225        // Always force minting token for ChromeOS kiosk app.
226        if (chromeos::UserManager::Get()->IsLoggedInAsKioskApp()) {
227          gaia_mint_token_mode_ = OAuth2MintTokenFlow::MODE_MINT_TOKEN_FORCE;
228          policy::BrowserPolicyConnectorChromeOS* connector =
229              g_browser_process->platform_part()
230                  ->browser_policy_connector_chromeos();
231          if (connector->IsEnterpriseManaged()) {
232            StartDeviceLoginAccessTokenRequest();
233          } else {
234            StartLoginAccessTokenRequest();
235          }
236          return;
237        }
238#endif
239
240        if (oauth2_info.auto_approve)
241          // oauth2_info.auto_approve is protected by a whitelist in
242          // _manifest_features.json hence only selected extensions take
243          // advantage of forcefully minting the token.
244          gaia_mint_token_mode_ = OAuth2MintTokenFlow::MODE_MINT_TOKEN_FORCE;
245        else
246          gaia_mint_token_mode_ = OAuth2MintTokenFlow::MODE_MINT_TOKEN_NO_FORCE;
247        StartLoginAccessTokenRequest();
248        break;
249
250      case IdentityTokenCacheValue::CACHE_STATUS_TOKEN:
251        CompleteMintTokenFlow();
252        CompleteFunctionWithResult(cache_entry.token());
253        break;
254
255      case IdentityTokenCacheValue::CACHE_STATUS_ADVICE:
256        CompleteMintTokenFlow();
257        should_prompt_for_signin_ = false;
258        issue_advice_ = cache_entry.issue_advice();
259        StartMintTokenFlow(IdentityMintRequestQueue::MINT_TYPE_INTERACTIVE);
260        break;
261    }
262  } else {
263    DCHECK(type == IdentityMintRequestQueue::MINT_TYPE_INTERACTIVE);
264
265    if (cache_status == IdentityTokenCacheValue::CACHE_STATUS_TOKEN) {
266      CompleteMintTokenFlow();
267      CompleteFunctionWithResult(cache_entry.token());
268    } else {
269      ShowOAuthApprovalDialog(issue_advice_);
270    }
271  }
272}
273
274void IdentityGetAuthTokenFunction::OnMintTokenSuccess(
275    const std::string& access_token, int time_to_live) {
276  IdentityTokenCacheValue token(access_token,
277                                base::TimeDelta::FromSeconds(time_to_live));
278  IdentityAPI::GetFactoryInstance()->Get(GetProfile())->SetCachedToken(
279      *token_key_, token);
280
281  CompleteMintTokenFlow();
282  CompleteFunctionWithResult(access_token);
283}
284
285void IdentityGetAuthTokenFunction::OnMintTokenFailure(
286    const GoogleServiceAuthError& error) {
287  CompleteMintTokenFlow();
288
289  switch (error.state()) {
290    case GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS:
291    case GoogleServiceAuthError::ACCOUNT_DELETED:
292    case GoogleServiceAuthError::ACCOUNT_DISABLED:
293      extensions::IdentityAPI::GetFactoryInstance()
294          ->Get(GetProfile())
295          ->ReportAuthError(error);
296      if (should_prompt_for_signin_) {
297        // Display a login prompt and try again (once).
298        StartSigninFlow();
299        return;
300      }
301      break;
302    default:
303      // Return error to caller.
304      break;
305  }
306
307  CompleteFunctionWithError(
308      std::string(identity_constants::kAuthFailure) + error.ToString());
309}
310
311void IdentityGetAuthTokenFunction::OnIssueAdviceSuccess(
312    const IssueAdviceInfo& issue_advice) {
313  IdentityAPI::GetFactoryInstance()->Get(GetProfile())->SetCachedToken(
314      *token_key_, IdentityTokenCacheValue(issue_advice));
315  CompleteMintTokenFlow();
316
317  should_prompt_for_signin_ = false;
318  // Existing grant was revoked and we used NO_FORCE, so we got info back
319  // instead. Start a consent UI if we can.
320  issue_advice_ = issue_advice;
321  StartMintTokenFlow(IdentityMintRequestQueue::MINT_TYPE_INTERACTIVE);
322}
323
324void IdentityGetAuthTokenFunction::SigninSuccess() {
325  StartMintTokenFlow(IdentityMintRequestQueue::MINT_TYPE_NONINTERACTIVE);
326}
327
328void IdentityGetAuthTokenFunction::SigninFailed() {
329  CompleteFunctionWithError(identity_constants::kUserNotSignedIn);
330}
331
332void IdentityGetAuthTokenFunction::OnGaiaFlowFailure(
333    GaiaWebAuthFlow::Failure failure,
334    GoogleServiceAuthError service_error,
335    const std::string& oauth_error) {
336  CompleteMintTokenFlow();
337  std::string error;
338
339  switch (failure) {
340    case GaiaWebAuthFlow::WINDOW_CLOSED:
341      error = identity_constants::kUserRejected;
342      break;
343
344    case GaiaWebAuthFlow::INVALID_REDIRECT:
345      error = identity_constants::kInvalidRedirect;
346      break;
347
348    case GaiaWebAuthFlow::SERVICE_AUTH_ERROR:
349      error = std::string(identity_constants::kAuthFailure) +
350          service_error.ToString();
351      break;
352
353    case GaiaWebAuthFlow::OAUTH_ERROR:
354      error = MapOAuth2ErrorToDescription(oauth_error);
355      break;
356
357    case GaiaWebAuthFlow::LOAD_FAILED:
358      error = identity_constants::kPageLoadFailure;
359      break;
360
361    default:
362      NOTREACHED() << "Unexpected error from gaia web auth flow: " << failure;
363      error = identity_constants::kInvalidRedirect;
364      break;
365  }
366
367  CompleteFunctionWithError(error);
368}
369
370void IdentityGetAuthTokenFunction::OnGaiaFlowCompleted(
371    const std::string& access_token,
372    const std::string& expiration) {
373
374  int time_to_live;
375  if (!expiration.empty() && base::StringToInt(expiration, &time_to_live)) {
376    IdentityTokenCacheValue token_value(
377        access_token, base::TimeDelta::FromSeconds(time_to_live));
378    IdentityAPI::GetFactoryInstance()->Get(GetProfile())->SetCachedToken(
379        *token_key_, token_value);
380  }
381
382  CompleteMintTokenFlow();
383  CompleteFunctionWithResult(access_token);
384}
385
386void IdentityGetAuthTokenFunction::OnGetTokenSuccess(
387    const OAuth2TokenService::Request* request,
388    const std::string& access_token,
389    const base::Time& expiration_time) {
390  login_token_request_.reset();
391  StartGaiaRequest(access_token);
392}
393
394void IdentityGetAuthTokenFunction::OnGetTokenFailure(
395    const OAuth2TokenService::Request* request,
396    const GoogleServiceAuthError& error) {
397  login_token_request_.reset();
398  OnGaiaFlowFailure(GaiaWebAuthFlow::SERVICE_AUTH_ERROR, error, std::string());
399}
400
401#if defined(OS_CHROMEOS)
402void IdentityGetAuthTokenFunction::StartDeviceLoginAccessTokenRequest() {
403  chromeos::DeviceOAuth2TokenService* service =
404      chromeos::DeviceOAuth2TokenServiceFactory::Get();
405  // Since robot account refresh tokens are scoped down to [any-api] only,
406  // request access token for [any-api] instead of login.
407  OAuth2TokenService::ScopeSet scopes;
408  scopes.insert(GaiaConstants::kAnyApiOAuth2Scope);
409  login_token_request_ =
410      service->StartRequest(service->GetRobotAccountId(),
411                            scopes,
412                            this);
413}
414#endif
415
416void IdentityGetAuthTokenFunction::StartLoginAccessTokenRequest() {
417  ProfileOAuth2TokenService* service =
418      ProfileOAuth2TokenServiceFactory::GetForProfile(GetProfile());
419  const std::string primary_account_id = GetPrimaryAccountId(GetProfile());
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(primary_account_id,
428                                         app_client_id,
429                                         app_client_secret,
430                                         OAuth2TokenService::ScopeSet(),
431                                         this);
432      return;
433    }
434  }
435#endif
436  login_token_request_ = service->StartRequest(
437      primary_account_id, OAuth2TokenService::ScopeSet(), 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, GetProfile()));
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, GetProfile(), 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 = new OAuth2MintTokenFlow(
468      GetProfile()->GetRequestContext(),
469      this,
470      OAuth2MintTokenFlow::Parameters(login_access_token,
471                                      GetExtension()->id(),
472                                      oauth2_client_id_,
473                                      oauth2_info.scopes,
474                                      gaia_mint_token_mode_));
475  return mint_token_flow;
476}
477
478bool IdentityGetAuthTokenFunction::HasLoginToken() const {
479  ProfileOAuth2TokenService* token_service =
480      ProfileOAuth2TokenServiceFactory::GetForProfile(GetProfile());
481  return token_service->RefreshTokenIsAvailable(
482      GetPrimaryAccountId(GetProfile()));
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 (GetProfile()->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()->Get(GetProfile())->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 (GetProfile()->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, GetProfile(), 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(new base::StringValue(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  // Remove 20 minutes from the ttl so cached tokens will have some time
624  // to live any time they are returned.
625  time_to_live -= base::TimeDelta::FromMinutes(20);
626
627  base::TimeDelta zero_delta;
628  if (time_to_live < zero_delta)
629    time_to_live = zero_delta;
630
631  expiration_time_ = base::Time::Now() + time_to_live;
632}
633
634IdentityTokenCacheValue::~IdentityTokenCacheValue() {
635}
636
637IdentityTokenCacheValue::CacheValueStatus
638    IdentityTokenCacheValue::status() const {
639  if (is_expired())
640    return IdentityTokenCacheValue::CACHE_STATUS_NOTFOUND;
641  else
642    return status_;
643}
644
645const IssueAdviceInfo& IdentityTokenCacheValue::issue_advice() const {
646  return issue_advice_;
647}
648
649const std::string& IdentityTokenCacheValue::token() const {
650  return token_;
651}
652
653bool IdentityTokenCacheValue::is_expired() const {
654  return status_ == CACHE_STATUS_NOTFOUND ||
655      expiration_time_ < base::Time::Now();
656}
657
658const base::Time& IdentityTokenCacheValue::expiration_time() const {
659  return expiration_time_;
660}
661
662IdentityAPI::IdentityAPI(content::BrowserContext* context)
663    : browser_context_(context),
664      account_tracker_(Profile::FromBrowserContext(context)) {
665  account_tracker_.AddObserver(this);
666}
667
668IdentityAPI::~IdentityAPI() {}
669
670IdentityMintRequestQueue* IdentityAPI::mint_queue() {
671    return &mint_queue_;
672}
673
674void IdentityAPI::SetCachedToken(const ExtensionTokenKey& key,
675                                 const IdentityTokenCacheValue& token_data) {
676  CachedTokens::iterator it = token_cache_.find(key);
677  if (it != token_cache_.end() && it->second.status() <= token_data.status())
678    token_cache_.erase(it);
679
680  token_cache_.insert(std::make_pair(key, token_data));
681}
682
683void IdentityAPI::EraseCachedToken(const std::string& extension_id,
684                                   const std::string& token) {
685  CachedTokens::iterator it;
686  for (it = token_cache_.begin(); it != token_cache_.end(); ++it) {
687    if (it->first.extension_id == extension_id &&
688        it->second.status() == IdentityTokenCacheValue::CACHE_STATUS_TOKEN &&
689        it->second.token() == token) {
690      token_cache_.erase(it);
691      break;
692    }
693  }
694}
695
696void IdentityAPI::EraseAllCachedTokens() {
697  token_cache_.clear();
698}
699
700const IdentityTokenCacheValue& IdentityAPI::GetCachedToken(
701    const ExtensionTokenKey& key) {
702  return token_cache_[key];
703}
704
705const IdentityAPI::CachedTokens& IdentityAPI::GetAllCachedTokens() {
706  return token_cache_;
707}
708
709void IdentityAPI::ReportAuthError(const GoogleServiceAuthError& error) {
710  account_tracker_.ReportAuthError(GetPrimaryAccountId(browser_context_),
711                                   error);
712}
713
714GoogleServiceAuthError IdentityAPI::GetAuthStatusForTest() const {
715  return account_tracker_.GetAuthStatus();
716}
717
718void IdentityAPI::Shutdown() {
719  account_tracker_.RemoveObserver(this);
720  account_tracker_.Shutdown();
721}
722
723static base::LazyInstance<BrowserContextKeyedAPIFactory<IdentityAPI> >
724    g_factory = LAZY_INSTANCE_INITIALIZER;
725
726// static
727BrowserContextKeyedAPIFactory<IdentityAPI>* IdentityAPI::GetFactoryInstance() {
728  return g_factory.Pointer();
729}
730
731void IdentityAPI::OnAccountAdded(const AccountIds& ids) {}
732
733void IdentityAPI::OnAccountRemoved(const AccountIds& ids) {}
734
735void IdentityAPI::OnAccountSignInChanged(const AccountIds& ids,
736                                         bool is_signed_in) {
737  api::identity::AccountInfo account_info;
738  account_info.id = ids.gaia;
739
740  scoped_ptr<base::ListValue> args =
741      api::identity::OnSignInChanged::Create(account_info, is_signed_in);
742  scoped_ptr<Event> event(new Event(api::identity::OnSignInChanged::kEventName,
743                                    args.Pass(),
744                                    browser_context_));
745
746  ExtensionSystem::Get(browser_context_)->event_router()->BroadcastEvent(
747      event.Pass());
748}
749
750template <>
751void BrowserContextKeyedAPIFactory<IdentityAPI>::DeclareFactoryDependencies() {
752  DependsOn(ExtensionsBrowserClient::Get()->GetExtensionSystemFactory());
753  DependsOn(ProfileOAuth2TokenServiceFactory::GetInstance());
754}
755
756}  // namespace extensions
757