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