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