Upstream version 6.35.121.0
[platform/framework/web/crosswalk.git] / src / chrome / browser / extensions / api / identity / identity_api.cc
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/browser/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
46 namespace extensions {
47
48 namespace identity_constants {
49 const char kInvalidClientId[] = "Invalid OAuth2 Client ID.";
50 const char kInvalidScopes[] = "Invalid OAuth2 scopes.";
51 const char kAuthFailure[] = "OAuth2 request failed: ";
52 const char kNoGrant[] = "OAuth2 not granted or revoked.";
53 const char kUserRejected[] = "The user did not approve access.";
54 const char kUserNotSignedIn[] = "The user is not signed in.";
55 const char kInteractionRequired[] = "User interaction required.";
56 const char kInvalidRedirect[] = "Did not redirect to the right URL.";
57 const char kOffTheRecord[] = "Identity API is disabled in incognito windows.";
58 const char kPageLoadFailure[] = "Authorization page could not be loaded.";
59
60 const int kCachedIssueAdviceTTLSeconds = 1;
61 }  // namespace identity_constants
62
63 namespace {
64
65 static const char kChromiumDomainRedirectUrlPattern[] =
66     "https://%s.chromiumapp.org/";
67
68 std::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
76 namespace identity = api::identity;
77
78 IdentityGetAuthTokenFunction::IdentityGetAuthTokenFunction()
79     : OAuth2TokenService::Consumer("extensions_identity_api"),
80       should_prompt_for_scopes_(false),
81       should_prompt_for_signin_(false) {}
82
83 IdentityGetAuthTokenFunction::~IdentityGetAuthTokenFunction() {}
84
85 bool 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
148 void 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
155 void IdentityGetAuthTokenFunction::CompleteFunctionWithError(
156     const std::string& error) {
157   error_ = error;
158   SendResponse(false);
159   Release();  // Balanced in RunImpl.
160 }
161
162 void 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
173 void 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
200 void 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
213 void 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
274 void 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
285 void 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
311 void 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
324 void IdentityGetAuthTokenFunction::SigninSuccess() {
325   StartMintTokenFlow(IdentityMintRequestQueue::MINT_TYPE_NONINTERACTIVE);
326 }
327
328 void IdentityGetAuthTokenFunction::SigninFailed() {
329   CompleteFunctionWithError(identity_constants::kUserNotSignedIn);
330 }
331
332 void 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
370 void 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
386 void 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
394 void 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)
402 void 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
416 void 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
440 void 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
447 void IdentityGetAuthTokenFunction::ShowLoginPopup() {
448   signin_flow_.reset(new IdentitySigninFlow(this, GetProfile()));
449   signin_flow_->Start();
450 }
451
452 void 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
463 OAuth2MintTokenFlow* 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
478 bool IdentityGetAuthTokenFunction::HasLoginToken() const {
479   ProfileOAuth2TokenService* token_service =
480       ProfileOAuth2TokenServiceFactory::GetForProfile(GetProfile());
481   return token_service->RefreshTokenIsAvailable(
482       GetPrimaryAccountId(GetProfile()));
483 }
484
485 std::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
498 std::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
511 IdentityRemoveCachedAuthTokenFunction::IdentityRemoveCachedAuthTokenFunction() {
512 }
513
514 IdentityRemoveCachedAuthTokenFunction::
515     ~IdentityRemoveCachedAuthTokenFunction() {
516 }
517
518 bool 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
532 IdentityLaunchWebAuthFlowFunction::IdentityLaunchWebAuthFlowFunction() {}
533
534 IdentityLaunchWebAuthFlowFunction::~IdentityLaunchWebAuthFlowFunction() {
535   if (auth_flow_)
536     auth_flow_.release()->DetachDelegateAndDelete();
537 }
538
539 bool 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
565 void IdentityLaunchWebAuthFlowFunction::InitFinalRedirectURLPrefixForTest(
566     const std::string& extension_id) {
567   InitFinalRedirectURLPrefix(extension_id);
568 }
569
570 void 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
578 void 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
599 void 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
608 IdentityTokenCacheValue::IdentityTokenCacheValue()
609     : status_(CACHE_STATUS_NOTFOUND) {
610 }
611
612 IdentityTokenCacheValue::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
619 IdentityTokenCacheValue::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
634 IdentityTokenCacheValue::~IdentityTokenCacheValue() {
635 }
636
637 IdentityTokenCacheValue::CacheValueStatus
638     IdentityTokenCacheValue::status() const {
639   if (is_expired())
640     return IdentityTokenCacheValue::CACHE_STATUS_NOTFOUND;
641   else
642     return status_;
643 }
644
645 const IssueAdviceInfo& IdentityTokenCacheValue::issue_advice() const {
646   return issue_advice_;
647 }
648
649 const std::string& IdentityTokenCacheValue::token() const {
650   return token_;
651 }
652
653 bool IdentityTokenCacheValue::is_expired() const {
654   return status_ == CACHE_STATUS_NOTFOUND ||
655       expiration_time_ < base::Time::Now();
656 }
657
658 const base::Time& IdentityTokenCacheValue::expiration_time() const {
659   return expiration_time_;
660 }
661
662 IdentityAPI::IdentityAPI(content::BrowserContext* context)
663     : browser_context_(context),
664       account_tracker_(Profile::FromBrowserContext(context)) {
665   account_tracker_.AddObserver(this);
666 }
667
668 IdentityAPI::~IdentityAPI() {}
669
670 IdentityMintRequestQueue* IdentityAPI::mint_queue() {
671     return &mint_queue_;
672 }
673
674 void 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
683 void 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
696 void IdentityAPI::EraseAllCachedTokens() {
697   token_cache_.clear();
698 }
699
700 const IdentityTokenCacheValue& IdentityAPI::GetCachedToken(
701     const ExtensionTokenKey& key) {
702   return token_cache_[key];
703 }
704
705 const IdentityAPI::CachedTokens& IdentityAPI::GetAllCachedTokens() {
706   return token_cache_;
707 }
708
709 void IdentityAPI::ReportAuthError(const GoogleServiceAuthError& error) {
710   account_tracker_.ReportAuthError(GetPrimaryAccountId(browser_context_),
711                                    error);
712 }
713
714 GoogleServiceAuthError IdentityAPI::GetAuthStatusForTest() const {
715   return account_tracker_.GetAuthStatus();
716 }
717
718 void IdentityAPI::Shutdown() {
719   account_tracker_.RemoveObserver(this);
720   account_tracker_.Shutdown();
721 }
722
723 static base::LazyInstance<BrowserContextKeyedAPIFactory<IdentityAPI> >
724     g_factory = LAZY_INSTANCE_INITIALIZER;
725
726 // static
727 BrowserContextKeyedAPIFactory<IdentityAPI>* IdentityAPI::GetFactoryInstance() {
728   return g_factory.Pointer();
729 }
730
731 void IdentityAPI::OnAccountAdded(const AccountIds& ids) {}
732
733 void IdentityAPI::OnAccountRemoved(const AccountIds& ids) {}
734
735 void 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
750 template <>
751 void BrowserContextKeyedAPIFactory<IdentityAPI>::DeclareFactoryDependencies() {
752   DependsOn(ExtensionsBrowserClient::Get()->GetExtensionSystemFactory());
753   DependsOn(ProfileOAuth2TokenServiceFactory::GetInstance());
754 }
755
756 }  // namespace extensions