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