• 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/debug/trace_event.h"
13 #include "base/lazy_instance.h"
14 #include "base/prefs/pref_service.h"
15 #include "base/strings/string_number_conversions.h"
16 #include "base/strings/stringprintf.h"
17 #include "base/values.h"
18 #include "chrome/browser/app_mode/app_mode_utils.h"
19 #include "chrome/browser/browser_process.h"
20 #include "chrome/browser/chrome_notification_types.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_factory.h"
24 #include "chrome/browser/signin/signin_manager_factory.h"
25 #include "chrome/browser/ui/webui/signin/login_ui_service_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 "components/signin/core/browser/signin_manager.h"
32 #include "components/signin/core/common/profile_management_switches.h"
33 #include "extensions/browser/event_router.h"
34 #include "extensions/browser/extension_function_dispatcher.h"
35 #include "extensions/common/extension.h"
36 #include "extensions/common/extension_l10n_util.h"
37 #include "extensions/common/permissions/permission_set.h"
38 #include "extensions/common/permissions/permissions_data.h"
39 #include "google_apis/gaia/gaia_urls.h"
40 #include "url/gurl.h"
41 
42 #if defined(OS_CHROMEOS)
43 #include "chrome/browser/chromeos/login/session/user_session_manager.h"
44 #include "chrome/browser/chromeos/policy/browser_policy_connector_chromeos.h"
45 #include "chrome/browser/chromeos/settings/device_oauth2_token_service.h"
46 #include "chrome/browser/chromeos/settings/device_oauth2_token_service_factory.h"
47 #include "components/user_manager/user_manager.h"
48 #include "google_apis/gaia/gaia_constants.h"
49 #endif
50 
51 namespace extensions {
52 
53 namespace identity_constants {
54 const char kInvalidClientId[] = "Invalid OAuth2 Client ID.";
55 const char kInvalidScopes[] = "Invalid OAuth2 scopes.";
56 const char kAuthFailure[] = "OAuth2 request failed: ";
57 const char kNoGrant[] = "OAuth2 not granted or revoked.";
58 const char kUserRejected[] = "The user did not approve access.";
59 const char kUserNotSignedIn[] = "The user is not signed in.";
60 const char kInteractionRequired[] = "User interaction required.";
61 const char kInvalidRedirect[] = "Did not redirect to the right URL.";
62 const char kOffTheRecord[] = "Identity API is disabled in incognito windows.";
63 const char kPageLoadFailure[] = "Authorization page could not be loaded.";
64 const char kCanceled[] = "canceled";
65 
66 const int kCachedIssueAdviceTTLSeconds = 1;
67 }  // namespace identity_constants
68 
69 namespace {
70 
71 static const char kChromiumDomainRedirectUrlPattern[] =
72     "https://%s.chromiumapp.org/";
73 
GetPrimaryAccountId(content::BrowserContext * context)74 std::string GetPrimaryAccountId(content::BrowserContext* context) {
75   SigninManagerBase* signin_manager =
76       SigninManagerFactory::GetForProfile(Profile::FromBrowserContext(context));
77   return signin_manager->GetAuthenticatedAccountId();
78 }
79 
80 }  // namespace
81 
82 namespace identity = api::identity;
83 
IdentityTokenCacheValue()84 IdentityTokenCacheValue::IdentityTokenCacheValue()
85     : status_(CACHE_STATUS_NOTFOUND) {}
86 
IdentityTokenCacheValue(const IssueAdviceInfo & issue_advice)87 IdentityTokenCacheValue::IdentityTokenCacheValue(
88     const IssueAdviceInfo& issue_advice)
89     : status_(CACHE_STATUS_ADVICE), issue_advice_(issue_advice) {
90   expiration_time_ =
91       base::Time::Now() + base::TimeDelta::FromSeconds(
92                               identity_constants::kCachedIssueAdviceTTLSeconds);
93 }
94 
IdentityTokenCacheValue(const std::string & token,base::TimeDelta time_to_live)95 IdentityTokenCacheValue::IdentityTokenCacheValue(const std::string& token,
96                                                  base::TimeDelta time_to_live)
97     : status_(CACHE_STATUS_TOKEN), token_(token) {
98   // Remove 20 minutes from the ttl so cached tokens will have some time
99   // to live any time they are returned.
100   time_to_live -= base::TimeDelta::FromMinutes(20);
101 
102   base::TimeDelta zero_delta;
103   if (time_to_live < zero_delta)
104     time_to_live = zero_delta;
105 
106   expiration_time_ = base::Time::Now() + time_to_live;
107 }
108 
~IdentityTokenCacheValue()109 IdentityTokenCacheValue::~IdentityTokenCacheValue() {}
110 
status() const111 IdentityTokenCacheValue::CacheValueStatus IdentityTokenCacheValue::status()
112     const {
113   if (is_expired())
114     return IdentityTokenCacheValue::CACHE_STATUS_NOTFOUND;
115   else
116     return status_;
117 }
118 
issue_advice() const119 const IssueAdviceInfo& IdentityTokenCacheValue::issue_advice() const {
120   return issue_advice_;
121 }
122 
token() const123 const std::string& IdentityTokenCacheValue::token() const { return token_; }
124 
is_expired() const125 bool IdentityTokenCacheValue::is_expired() const {
126   return status_ == CACHE_STATUS_NOTFOUND ||
127          expiration_time_ < base::Time::Now();
128 }
129 
expiration_time() const130 const base::Time& IdentityTokenCacheValue::expiration_time() const {
131   return expiration_time_;
132 }
133 
IdentityAPI(content::BrowserContext * context)134 IdentityAPI::IdentityAPI(content::BrowserContext* context)
135     : browser_context_(context),
136       profile_identity_provider_(
137           SigninManagerFactory::GetForProfile(
138               Profile::FromBrowserContext(context)),
139           ProfileOAuth2TokenServiceFactory::GetForProfile(
140               Profile::FromBrowserContext(context)),
141           LoginUIServiceFactory::GetForProfile(
142               Profile::FromBrowserContext(context))),
143       account_tracker_(&profile_identity_provider_,
144                        g_browser_process->system_request_context()) {
145   account_tracker_.AddObserver(this);
146 }
147 
~IdentityAPI()148 IdentityAPI::~IdentityAPI() {}
149 
mint_queue()150 IdentityMintRequestQueue* IdentityAPI::mint_queue() { return &mint_queue_; }
151 
SetCachedToken(const ExtensionTokenKey & key,const IdentityTokenCacheValue & token_data)152 void IdentityAPI::SetCachedToken(const ExtensionTokenKey& key,
153                                  const IdentityTokenCacheValue& token_data) {
154   CachedTokens::iterator it = token_cache_.find(key);
155   if (it != token_cache_.end() && it->second.status() <= token_data.status())
156     token_cache_.erase(it);
157 
158   token_cache_.insert(std::make_pair(key, token_data));
159 }
160 
EraseCachedToken(const std::string & extension_id,const std::string & token)161 void IdentityAPI::EraseCachedToken(const std::string& extension_id,
162                                    const std::string& token) {
163   CachedTokens::iterator it;
164   for (it = token_cache_.begin(); it != token_cache_.end(); ++it) {
165     if (it->first.extension_id == extension_id &&
166         it->second.status() == IdentityTokenCacheValue::CACHE_STATUS_TOKEN &&
167         it->second.token() == token) {
168       token_cache_.erase(it);
169       break;
170     }
171   }
172 }
173 
EraseAllCachedTokens()174 void IdentityAPI::EraseAllCachedTokens() { token_cache_.clear(); }
175 
GetCachedToken(const ExtensionTokenKey & key)176 const IdentityTokenCacheValue& IdentityAPI::GetCachedToken(
177     const ExtensionTokenKey& key) {
178   return token_cache_[key];
179 }
180 
GetAllCachedTokens()181 const IdentityAPI::CachedTokens& IdentityAPI::GetAllCachedTokens() {
182   return token_cache_;
183 }
184 
GetAccounts() const185 std::vector<std::string> IdentityAPI::GetAccounts() const {
186   const std::string primary_account_id = GetPrimaryAccountId(browser_context_);
187   const std::vector<gaia::AccountIds> ids = account_tracker_.GetAccounts();
188   std::vector<std::string> gaia_ids;
189 
190   if (switches::IsExtensionsMultiAccount()) {
191     for (std::vector<gaia::AccountIds>::const_iterator it = ids.begin();
192          it != ids.end();
193          ++it) {
194       gaia_ids.push_back(it->gaia);
195     }
196   } else if (ids.size() >= 1) {
197     gaia_ids.push_back(ids[0].gaia);
198   }
199 
200   return gaia_ids;
201 }
202 
FindAccountKeyByGaiaId(const std::string & gaia_id)203 std::string IdentityAPI::FindAccountKeyByGaiaId(const std::string& gaia_id) {
204   return account_tracker_.FindAccountIdsByGaiaId(gaia_id).account_key;
205 }
206 
Shutdown()207 void IdentityAPI::Shutdown() {
208   FOR_EACH_OBSERVER(ShutdownObserver, shutdown_observer_list_, OnShutdown());
209   account_tracker_.RemoveObserver(this);
210   account_tracker_.Shutdown();
211 }
212 
213 static base::LazyInstance<BrowserContextKeyedAPIFactory<IdentityAPI> >
214     g_factory = LAZY_INSTANCE_INITIALIZER;
215 
216 // static
GetFactoryInstance()217 BrowserContextKeyedAPIFactory<IdentityAPI>* IdentityAPI::GetFactoryInstance() {
218   return g_factory.Pointer();
219 }
220 
OnAccountAdded(const gaia::AccountIds & ids)221 void IdentityAPI::OnAccountAdded(const gaia::AccountIds& ids) {
222 }
223 
OnAccountRemoved(const gaia::AccountIds & ids)224 void IdentityAPI::OnAccountRemoved(const gaia::AccountIds& ids) {
225 }
226 
OnAccountSignInChanged(const gaia::AccountIds & ids,bool is_signed_in)227 void IdentityAPI::OnAccountSignInChanged(const gaia::AccountIds& ids,
228                                          bool is_signed_in) {
229   api::identity::AccountInfo account_info;
230   account_info.id = ids.gaia;
231 
232   scoped_ptr<base::ListValue> args =
233       api::identity::OnSignInChanged::Create(account_info, is_signed_in);
234   scoped_ptr<Event> event(new Event(api::identity::OnSignInChanged::kEventName,
235                                     args.Pass(),
236                                     browser_context_));
237 
238   EventRouter::Get(browser_context_)->BroadcastEvent(event.Pass());
239 }
240 
AddShutdownObserver(ShutdownObserver * observer)241 void IdentityAPI::AddShutdownObserver(ShutdownObserver* observer) {
242   shutdown_observer_list_.AddObserver(observer);
243 }
244 
RemoveShutdownObserver(ShutdownObserver * observer)245 void IdentityAPI::RemoveShutdownObserver(ShutdownObserver* observer) {
246   shutdown_observer_list_.RemoveObserver(observer);
247 }
248 
SetAccountStateForTest(gaia::AccountIds ids,bool is_signed_in)249 void IdentityAPI::SetAccountStateForTest(gaia::AccountIds ids,
250                                          bool is_signed_in) {
251   account_tracker_.SetAccountStateForTest(ids, is_signed_in);
252 }
253 
254 template <>
DeclareFactoryDependencies()255 void BrowserContextKeyedAPIFactory<IdentityAPI>::DeclareFactoryDependencies() {
256   DependsOn(ExtensionsBrowserClient::Get()->GetExtensionSystemFactory());
257   DependsOn(ProfileOAuth2TokenServiceFactory::GetInstance());
258 }
259 
IdentityGetAccountsFunction()260 IdentityGetAccountsFunction::IdentityGetAccountsFunction() {
261 }
262 
~IdentityGetAccountsFunction()263 IdentityGetAccountsFunction::~IdentityGetAccountsFunction() {
264 }
265 
Run()266 ExtensionFunction::ResponseAction IdentityGetAccountsFunction::Run() {
267   if (GetProfile()->IsOffTheRecord()) {
268     return RespondNow(Error(identity_constants::kOffTheRecord));
269   }
270 
271   std::vector<std::string> gaia_ids =
272       IdentityAPI::GetFactoryInstance()->Get(GetProfile())->GetAccounts();
273   DCHECK(gaia_ids.size() < 2 || switches::IsExtensionsMultiAccount());
274 
275   base::ListValue* infos = new base::ListValue();
276 
277   for (std::vector<std::string>::const_iterator it = gaia_ids.begin();
278        it != gaia_ids.end();
279        ++it) {
280     api::identity::AccountInfo account_info;
281     account_info.id = *it;
282     infos->Append(account_info.ToValue().release());
283   }
284 
285   return RespondNow(OneArgument(infos));
286 }
287 
IdentityGetAuthTokenFunction()288 IdentityGetAuthTokenFunction::IdentityGetAuthTokenFunction()
289     : OAuth2TokenService::Consumer("extensions_identity_api"),
290       should_prompt_for_scopes_(false),
291       should_prompt_for_signin_(false) {
292 }
293 
~IdentityGetAuthTokenFunction()294 IdentityGetAuthTokenFunction::~IdentityGetAuthTokenFunction() {
295   TRACE_EVENT_ASYNC_END0("identity", "IdentityGetAuthTokenFunction", this);
296 }
297 
RunAsync()298 bool IdentityGetAuthTokenFunction::RunAsync() {
299   TRACE_EVENT_ASYNC_BEGIN1("identity",
300                            "IdentityGetAuthTokenFunction",
301                            this,
302                            "extension",
303                            extension()->id());
304 
305   if (GetProfile()->IsOffTheRecord()) {
306     error_ = identity_constants::kOffTheRecord;
307     return false;
308   }
309 
310   scoped_ptr<identity::GetAuthToken::Params> params(
311       identity::GetAuthToken::Params::Create(*args_));
312   EXTENSION_FUNCTION_VALIDATE(params.get());
313   bool interactive = params->details.get() &&
314       params->details->interactive.get() &&
315       *params->details->interactive;
316 
317   should_prompt_for_scopes_ = interactive;
318   should_prompt_for_signin_ = interactive;
319 
320   const OAuth2Info& oauth2_info = OAuth2Info::GetOAuth2Info(extension());
321 
322   // Check that the necessary information is present in the manifest.
323   oauth2_client_id_ = GetOAuth2ClientId();
324   if (oauth2_client_id_.empty()) {
325     error_ = identity_constants::kInvalidClientId;
326     return false;
327   }
328 
329   std::set<std::string> scopes(oauth2_info.scopes.begin(),
330                                oauth2_info.scopes.end());
331 
332   std::string account_key = GetPrimaryAccountId(GetProfile());
333 
334   if (params->details.get()) {
335     if (params->details->account.get()) {
336       std::string detail_key =
337           extensions::IdentityAPI::GetFactoryInstance()
338               ->Get(GetProfile())
339               ->FindAccountKeyByGaiaId(params->details->account->id);
340 
341       if (detail_key != account_key) {
342         if (detail_key.empty() || !switches::IsExtensionsMultiAccount()) {
343           // TODO(courage): should this be a different error?
344           error_ = identity_constants::kUserNotSignedIn;
345           return false;
346         }
347 
348         account_key = detail_key;
349       }
350     }
351 
352     if (params->details->scopes.get()) {
353       scopes = std::set<std::string>(params->details->scopes->begin(),
354                                      params->details->scopes->end());
355     }
356   }
357 
358   if (scopes.size() == 0) {
359     error_ = identity_constants::kInvalidScopes;
360     return false;
361   }
362 
363   token_key_.reset(
364       new ExtensionTokenKey(extension()->id(), account_key, scopes));
365 
366   // From here on out, results must be returned asynchronously.
367   StartAsyncRun();
368 
369 #if defined(OS_CHROMEOS)
370   policy::BrowserPolicyConnectorChromeOS* connector =
371       g_browser_process->platform_part()->browser_policy_connector_chromeos();
372   if (user_manager::UserManager::Get()->IsLoggedInAsKioskApp() &&
373       connector->IsEnterpriseManaged()) {
374     StartMintTokenFlow(IdentityMintRequestQueue::MINT_TYPE_NONINTERACTIVE);
375     return true;
376   }
377 #endif
378 
379   if (!HasLoginToken()) {
380     if (!should_prompt_for_signin_) {
381       CompleteFunctionWithError(identity_constants::kUserNotSignedIn);
382       return true;
383     }
384     // Display a login prompt.
385     StartSigninFlow();
386   } else {
387     StartMintTokenFlow(IdentityMintRequestQueue::MINT_TYPE_NONINTERACTIVE);
388   }
389 
390   return true;
391 }
392 
StartAsyncRun()393 void IdentityGetAuthTokenFunction::StartAsyncRun() {
394   // Balanced in CompleteAsyncRun
395   AddRef();
396   extensions::IdentityAPI::GetFactoryInstance()
397       ->Get(GetProfile())
398       ->AddShutdownObserver(this);
399 }
400 
CompleteAsyncRun(bool success)401 void IdentityGetAuthTokenFunction::CompleteAsyncRun(bool success) {
402   extensions::IdentityAPI::GetFactoryInstance()
403       ->Get(GetProfile())
404       ->RemoveShutdownObserver(this);
405 
406   SendResponse(success);
407   Release();  // Balanced in StartAsyncRun
408 }
409 
CompleteFunctionWithResult(const std::string & access_token)410 void IdentityGetAuthTokenFunction::CompleteFunctionWithResult(
411     const std::string& access_token) {
412 
413   SetResult(new base::StringValue(access_token));
414   CompleteAsyncRun(true);
415 }
416 
CompleteFunctionWithError(const std::string & error)417 void IdentityGetAuthTokenFunction::CompleteFunctionWithError(
418     const std::string& error) {
419   TRACE_EVENT_ASYNC_STEP_PAST1("identity",
420                                "IdentityGetAuthTokenFunction",
421                                this,
422                                "CompleteFunctionWithError",
423                                "error",
424                                error);
425   error_ = error;
426   CompleteAsyncRun(false);
427 }
428 
StartSigninFlow()429 void IdentityGetAuthTokenFunction::StartSigninFlow() {
430   // All cached tokens are invalid because the user is not signed in.
431   IdentityAPI* id_api =
432       extensions::IdentityAPI::GetFactoryInstance()->Get(GetProfile());
433   id_api->EraseAllCachedTokens();
434   // Display a login prompt. If the subsequent mint fails, don't display the
435   // login prompt again.
436   should_prompt_for_signin_ = false;
437   ShowLoginPopup();
438 }
439 
StartMintTokenFlow(IdentityMintRequestQueue::MintType type)440 void IdentityGetAuthTokenFunction::StartMintTokenFlow(
441     IdentityMintRequestQueue::MintType type) {
442   mint_token_flow_type_ = type;
443 
444   // Flows are serialized to prevent excessive traffic to GAIA, and
445   // to consolidate UI pop-ups.
446   IdentityAPI* id_api =
447       extensions::IdentityAPI::GetFactoryInstance()->Get(GetProfile());
448 
449   if (!should_prompt_for_scopes_) {
450     // Caller requested no interaction.
451 
452     if (type == IdentityMintRequestQueue::MINT_TYPE_INTERACTIVE) {
453       // GAIA told us to do a consent UI.
454       CompleteFunctionWithError(identity_constants::kNoGrant);
455       return;
456     }
457     if (!id_api->mint_queue()->empty(
458             IdentityMintRequestQueue::MINT_TYPE_INTERACTIVE, *token_key_)) {
459       // Another call is going through a consent UI.
460       CompleteFunctionWithError(identity_constants::kNoGrant);
461       return;
462     }
463   }
464   id_api->mint_queue()->RequestStart(type, *token_key_, this);
465 }
466 
CompleteMintTokenFlow()467 void IdentityGetAuthTokenFunction::CompleteMintTokenFlow() {
468   IdentityMintRequestQueue::MintType type = mint_token_flow_type_;
469 
470   extensions::IdentityAPI::GetFactoryInstance()
471       ->Get(GetProfile())
472       ->mint_queue()
473       ->RequestComplete(type, *token_key_, this);
474 }
475 
StartMintToken(IdentityMintRequestQueue::MintType type)476 void IdentityGetAuthTokenFunction::StartMintToken(
477     IdentityMintRequestQueue::MintType type) {
478   TRACE_EVENT_ASYNC_STEP_PAST1("identity",
479                                "IdentityGetAuthTokenFunction",
480                                this,
481                                "StartMintToken",
482                                "type",
483                                type);
484 
485   const OAuth2Info& oauth2_info = OAuth2Info::GetOAuth2Info(extension());
486   IdentityAPI* id_api = IdentityAPI::GetFactoryInstance()->Get(GetProfile());
487   IdentityTokenCacheValue cache_entry = id_api->GetCachedToken(*token_key_);
488   IdentityTokenCacheValue::CacheValueStatus cache_status =
489       cache_entry.status();
490 
491   if (type == IdentityMintRequestQueue::MINT_TYPE_NONINTERACTIVE) {
492     switch (cache_status) {
493       case IdentityTokenCacheValue::CACHE_STATUS_NOTFOUND:
494 #if defined(OS_CHROMEOS)
495         // Always force minting token for ChromeOS kiosk app.
496         if (user_manager::UserManager::Get()->IsLoggedInAsKioskApp()) {
497           gaia_mint_token_mode_ = OAuth2MintTokenFlow::MODE_MINT_TOKEN_FORCE;
498           policy::BrowserPolicyConnectorChromeOS* connector =
499               g_browser_process->platform_part()
500                   ->browser_policy_connector_chromeos();
501           if (connector->IsEnterpriseManaged()) {
502             StartDeviceLoginAccessTokenRequest();
503           } else {
504             StartLoginAccessTokenRequest();
505           }
506           return;
507         }
508 #endif
509 
510         if (oauth2_info.auto_approve)
511           // oauth2_info.auto_approve is protected by a whitelist in
512           // _manifest_features.json hence only selected extensions take
513           // advantage of forcefully minting the token.
514           gaia_mint_token_mode_ = OAuth2MintTokenFlow::MODE_MINT_TOKEN_FORCE;
515         else
516           gaia_mint_token_mode_ = OAuth2MintTokenFlow::MODE_MINT_TOKEN_NO_FORCE;
517         StartLoginAccessTokenRequest();
518         break;
519 
520       case IdentityTokenCacheValue::CACHE_STATUS_TOKEN:
521         CompleteMintTokenFlow();
522         CompleteFunctionWithResult(cache_entry.token());
523         break;
524 
525       case IdentityTokenCacheValue::CACHE_STATUS_ADVICE:
526         CompleteMintTokenFlow();
527         should_prompt_for_signin_ = false;
528         issue_advice_ = cache_entry.issue_advice();
529         StartMintTokenFlow(IdentityMintRequestQueue::MINT_TYPE_INTERACTIVE);
530         break;
531     }
532   } else {
533     DCHECK(type == IdentityMintRequestQueue::MINT_TYPE_INTERACTIVE);
534 
535     if (cache_status == IdentityTokenCacheValue::CACHE_STATUS_TOKEN) {
536       CompleteMintTokenFlow();
537       CompleteFunctionWithResult(cache_entry.token());
538     } else {
539       ShowOAuthApprovalDialog(issue_advice_);
540     }
541   }
542 }
543 
OnMintTokenSuccess(const std::string & access_token,int time_to_live)544 void IdentityGetAuthTokenFunction::OnMintTokenSuccess(
545     const std::string& access_token, int time_to_live) {
546   TRACE_EVENT_ASYNC_STEP_PAST0("identity",
547                                "IdentityGetAuthTokenFunction",
548                                this,
549                                "OnMintTokenSuccess");
550 
551   IdentityTokenCacheValue token(access_token,
552                                 base::TimeDelta::FromSeconds(time_to_live));
553   IdentityAPI::GetFactoryInstance()->Get(GetProfile())->SetCachedToken(
554       *token_key_, token);
555 
556   CompleteMintTokenFlow();
557   CompleteFunctionWithResult(access_token);
558 }
559 
OnMintTokenFailure(const GoogleServiceAuthError & error)560 void IdentityGetAuthTokenFunction::OnMintTokenFailure(
561     const GoogleServiceAuthError& error) {
562   TRACE_EVENT_ASYNC_STEP_PAST1("identity",
563                                "IdentityGetAuthTokenFunction",
564                                this,
565                                "OnMintTokenFailure",
566                                "error",
567                                error.ToString());
568   CompleteMintTokenFlow();
569 
570   switch (error.state()) {
571     case GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS:
572     case GoogleServiceAuthError::ACCOUNT_DELETED:
573     case GoogleServiceAuthError::ACCOUNT_DISABLED:
574       // TODO(courage): flush ticket and retry once
575       if (should_prompt_for_signin_) {
576         // Display a login prompt and try again (once).
577         StartSigninFlow();
578         return;
579       }
580       break;
581     default:
582       // Return error to caller.
583       break;
584   }
585 
586   CompleteFunctionWithError(
587       std::string(identity_constants::kAuthFailure) + error.ToString());
588 }
589 
OnIssueAdviceSuccess(const IssueAdviceInfo & issue_advice)590 void IdentityGetAuthTokenFunction::OnIssueAdviceSuccess(
591     const IssueAdviceInfo& issue_advice) {
592   TRACE_EVENT_ASYNC_STEP_PAST0("identity",
593                                "IdentityGetAuthTokenFunction",
594                                this,
595                                "OnIssueAdviceSuccess");
596 
597   IdentityAPI::GetFactoryInstance()->Get(GetProfile())->SetCachedToken(
598       *token_key_, IdentityTokenCacheValue(issue_advice));
599   CompleteMintTokenFlow();
600 
601   should_prompt_for_signin_ = false;
602   // Existing grant was revoked and we used NO_FORCE, so we got info back
603   // instead. Start a consent UI if we can.
604   issue_advice_ = issue_advice;
605   StartMintTokenFlow(IdentityMintRequestQueue::MINT_TYPE_INTERACTIVE);
606 }
607 
SigninSuccess()608 void IdentityGetAuthTokenFunction::SigninSuccess() {
609   TRACE_EVENT_ASYNC_STEP_PAST0("identity",
610                                "IdentityGetAuthTokenFunction",
611                                this,
612                                "SigninSuccess");
613 
614   // If there was no account associated this profile before the
615   // sign-in, we may not have an account_id in the token_key yet.
616   if (token_key_->account_id.empty()) {
617     token_key_->account_id = GetPrimaryAccountId(GetProfile());
618   }
619 
620   StartMintTokenFlow(IdentityMintRequestQueue::MINT_TYPE_NONINTERACTIVE);
621 }
622 
SigninFailed()623 void IdentityGetAuthTokenFunction::SigninFailed() {
624   TRACE_EVENT_ASYNC_STEP_PAST0("identity",
625                                "IdentityGetAuthTokenFunction",
626                                this,
627                                "SigninFailed");
628   CompleteFunctionWithError(identity_constants::kUserNotSignedIn);
629 }
630 
OnGaiaFlowFailure(GaiaWebAuthFlow::Failure failure,GoogleServiceAuthError service_error,const std::string & oauth_error)631 void IdentityGetAuthTokenFunction::OnGaiaFlowFailure(
632     GaiaWebAuthFlow::Failure failure,
633     GoogleServiceAuthError service_error,
634     const std::string& oauth_error) {
635   CompleteMintTokenFlow();
636   std::string error;
637 
638   switch (failure) {
639     case GaiaWebAuthFlow::WINDOW_CLOSED:
640       error = identity_constants::kUserRejected;
641       break;
642 
643     case GaiaWebAuthFlow::INVALID_REDIRECT:
644       error = identity_constants::kInvalidRedirect;
645       break;
646 
647     case GaiaWebAuthFlow::SERVICE_AUTH_ERROR:
648       error = std::string(identity_constants::kAuthFailure) +
649           service_error.ToString();
650       break;
651 
652     case GaiaWebAuthFlow::OAUTH_ERROR:
653       error = MapOAuth2ErrorToDescription(oauth_error);
654       break;
655 
656     case GaiaWebAuthFlow::LOAD_FAILED:
657       error = identity_constants::kPageLoadFailure;
658       break;
659 
660     default:
661       NOTREACHED() << "Unexpected error from gaia web auth flow: " << failure;
662       error = identity_constants::kInvalidRedirect;
663       break;
664   }
665 
666   CompleteFunctionWithError(error);
667 }
668 
OnGaiaFlowCompleted(const std::string & access_token,const std::string & expiration)669 void IdentityGetAuthTokenFunction::OnGaiaFlowCompleted(
670     const std::string& access_token,
671     const std::string& expiration) {
672   TRACE_EVENT_ASYNC_STEP_PAST0("identity",
673                                "IdentityGetAuthTokenFunction",
674                                this,
675                                "OnGaiaFlowCompleted");
676   int time_to_live;
677   if (!expiration.empty() && base::StringToInt(expiration, &time_to_live)) {
678     IdentityTokenCacheValue token_value(
679         access_token, base::TimeDelta::FromSeconds(time_to_live));
680     IdentityAPI::GetFactoryInstance()->Get(GetProfile())->SetCachedToken(
681         *token_key_, token_value);
682   }
683 
684   CompleteMintTokenFlow();
685   CompleteFunctionWithResult(access_token);
686 }
687 
OnGetTokenSuccess(const OAuth2TokenService::Request * request,const std::string & access_token,const base::Time & expiration_time)688 void IdentityGetAuthTokenFunction::OnGetTokenSuccess(
689     const OAuth2TokenService::Request* request,
690     const std::string& access_token,
691     const base::Time& expiration_time) {
692   TRACE_EVENT_ASYNC_STEP_PAST1("identity",
693                                "IdentityGetAuthTokenFunction",
694                                this,
695                                "OnGetTokenSuccess",
696                                "account",
697                                request->GetAccountId());
698   login_token_request_.reset();
699   StartGaiaRequest(access_token);
700 }
701 
OnGetTokenFailure(const OAuth2TokenService::Request * request,const GoogleServiceAuthError & error)702 void IdentityGetAuthTokenFunction::OnGetTokenFailure(
703     const OAuth2TokenService::Request* request,
704     const GoogleServiceAuthError& error) {
705   TRACE_EVENT_ASYNC_STEP_PAST1("identity",
706                                "IdentityGetAuthTokenFunction",
707                                this,
708                                "OnGetTokenFailure",
709                                "error",
710                                error.ToString());
711   login_token_request_.reset();
712   OnGaiaFlowFailure(GaiaWebAuthFlow::SERVICE_AUTH_ERROR, error, std::string());
713 }
714 
OnShutdown()715 void IdentityGetAuthTokenFunction::OnShutdown() {
716   gaia_web_auth_flow_.reset();
717   signin_flow_.reset();
718   login_token_request_.reset();
719   extensions::IdentityAPI::GetFactoryInstance()
720       ->Get(GetProfile())
721       ->mint_queue()
722       ->RequestCancel(*token_key_, this);
723   CompleteFunctionWithError(identity_constants::kCanceled);
724 }
725 
726 #if defined(OS_CHROMEOS)
StartDeviceLoginAccessTokenRequest()727 void IdentityGetAuthTokenFunction::StartDeviceLoginAccessTokenRequest() {
728   chromeos::DeviceOAuth2TokenService* service =
729       chromeos::DeviceOAuth2TokenServiceFactory::Get();
730   // Since robot account refresh tokens are scoped down to [any-api] only,
731   // request access token for [any-api] instead of login.
732   OAuth2TokenService::ScopeSet scopes;
733   scopes.insert(GaiaConstants::kAnyApiOAuth2Scope);
734   login_token_request_ =
735       service->StartRequest(service->GetRobotAccountId(),
736                             scopes,
737                             this);
738 }
739 #endif
740 
StartLoginAccessTokenRequest()741 void IdentityGetAuthTokenFunction::StartLoginAccessTokenRequest() {
742   ProfileOAuth2TokenService* service =
743       ProfileOAuth2TokenServiceFactory::GetForProfile(GetProfile());
744 #if defined(OS_CHROMEOS)
745   if (chrome::IsRunningInForcedAppMode()) {
746     std::string app_client_id;
747     std::string app_client_secret;
748     if (chromeos::UserSessionManager::GetInstance()->
749             GetAppModeChromeClientOAuthInfo(&app_client_id,
750                                             &app_client_secret)) {
751       login_token_request_ =
752           service->StartRequestForClient(token_key_->account_id,
753                                          app_client_id,
754                                          app_client_secret,
755                                          OAuth2TokenService::ScopeSet(),
756                                          this);
757       return;
758     }
759   }
760 #endif
761   login_token_request_ = service->StartRequest(
762       token_key_->account_id, OAuth2TokenService::ScopeSet(), this);
763 }
764 
StartGaiaRequest(const std::string & login_access_token)765 void IdentityGetAuthTokenFunction::StartGaiaRequest(
766     const std::string& login_access_token) {
767   DCHECK(!login_access_token.empty());
768   mint_token_flow_.reset(CreateMintTokenFlow(login_access_token));
769   mint_token_flow_->Start();
770 }
771 
ShowLoginPopup()772 void IdentityGetAuthTokenFunction::ShowLoginPopup() {
773   signin_flow_.reset(new IdentitySigninFlow(this, GetProfile()));
774   signin_flow_->Start();
775 }
776 
ShowOAuthApprovalDialog(const IssueAdviceInfo & issue_advice)777 void IdentityGetAuthTokenFunction::ShowOAuthApprovalDialog(
778     const IssueAdviceInfo& issue_advice) {
779   const std::string locale = extension_l10n_util::CurrentLocaleOrDefault();
780 
781   gaia_web_auth_flow_.reset(new GaiaWebAuthFlow(
782       this, GetProfile(), token_key_.get(), oauth2_client_id_, locale));
783   gaia_web_auth_flow_->Start();
784 }
785 
CreateMintTokenFlow(const std::string & login_access_token)786 OAuth2MintTokenFlow* IdentityGetAuthTokenFunction::CreateMintTokenFlow(
787     const std::string& login_access_token) {
788   OAuth2MintTokenFlow* mint_token_flow = new OAuth2MintTokenFlow(
789       GetProfile()->GetRequestContext(),
790       this,
791       OAuth2MintTokenFlow::Parameters(
792           login_access_token,
793           extension()->id(),
794           oauth2_client_id_,
795           std::vector<std::string>(token_key_->scopes.begin(),
796                                    token_key_->scopes.end()),
797           gaia_mint_token_mode_));
798   return mint_token_flow;
799 }
800 
HasLoginToken() const801 bool IdentityGetAuthTokenFunction::HasLoginToken() const {
802   ProfileOAuth2TokenService* token_service =
803       ProfileOAuth2TokenServiceFactory::GetForProfile(GetProfile());
804   return token_service->RefreshTokenIsAvailable(token_key_->account_id);
805 }
806 
MapOAuth2ErrorToDescription(const std::string & error)807 std::string IdentityGetAuthTokenFunction::MapOAuth2ErrorToDescription(
808     const std::string& error) {
809   const char kOAuth2ErrorAccessDenied[] = "access_denied";
810   const char kOAuth2ErrorInvalidScope[] = "invalid_scope";
811 
812   if (error == kOAuth2ErrorAccessDenied)
813     return std::string(identity_constants::kUserRejected);
814   else if (error == kOAuth2ErrorInvalidScope)
815     return std::string(identity_constants::kInvalidScopes);
816   else
817     return std::string(identity_constants::kAuthFailure) + error;
818 }
819 
GetOAuth2ClientId() const820 std::string IdentityGetAuthTokenFunction::GetOAuth2ClientId() const {
821   const OAuth2Info& oauth2_info = OAuth2Info::GetOAuth2Info(extension());
822   std::string client_id = oauth2_info.client_id;
823 
824   // Component apps using auto_approve may use Chrome's client ID by
825   // omitting the field.
826   if (client_id.empty() && extension()->location() == Manifest::COMPONENT &&
827       oauth2_info.auto_approve) {
828     client_id = GaiaUrls::GetInstance()->oauth2_chrome_client_id();
829   }
830   return client_id;
831 }
832 
IdentityGetProfileUserInfoFunction()833 IdentityGetProfileUserInfoFunction::IdentityGetProfileUserInfoFunction() {
834 }
835 
~IdentityGetProfileUserInfoFunction()836 IdentityGetProfileUserInfoFunction::~IdentityGetProfileUserInfoFunction() {
837 }
838 
Run()839 ExtensionFunction::ResponseAction IdentityGetProfileUserInfoFunction::Run() {
840   if (GetProfile()->IsOffTheRecord()) {
841     return RespondNow(Error(identity_constants::kOffTheRecord));
842   }
843 
844   api::identity::ProfileUserInfo profile_user_info;
845   if (extension()->permissions_data()->HasAPIPermission(
846           APIPermission::kIdentityEmail)) {
847     profile_user_info.email =
848         GetProfile()->GetPrefs()->GetString(prefs::kGoogleServicesUsername);
849   }
850   profile_user_info.id =
851       GetProfile()->GetPrefs()->GetString(prefs::kGoogleServicesUserAccountId);
852 
853   return RespondNow(OneArgument(profile_user_info.ToValue().release()));
854 }
855 
IdentityRemoveCachedAuthTokenFunction()856 IdentityRemoveCachedAuthTokenFunction::IdentityRemoveCachedAuthTokenFunction() {
857 }
858 
859 IdentityRemoveCachedAuthTokenFunction::
~IdentityRemoveCachedAuthTokenFunction()860     ~IdentityRemoveCachedAuthTokenFunction() {
861 }
862 
RunSync()863 bool IdentityRemoveCachedAuthTokenFunction::RunSync() {
864   if (GetProfile()->IsOffTheRecord()) {
865     error_ = identity_constants::kOffTheRecord;
866     return false;
867   }
868 
869   scoped_ptr<identity::RemoveCachedAuthToken::Params> params(
870       identity::RemoveCachedAuthToken::Params::Create(*args_));
871   EXTENSION_FUNCTION_VALIDATE(params.get());
872   IdentityAPI::GetFactoryInstance()->Get(GetProfile())->EraseCachedToken(
873       extension()->id(), params->details.token);
874   return true;
875 }
876 
IdentityLaunchWebAuthFlowFunction()877 IdentityLaunchWebAuthFlowFunction::IdentityLaunchWebAuthFlowFunction() {}
878 
~IdentityLaunchWebAuthFlowFunction()879 IdentityLaunchWebAuthFlowFunction::~IdentityLaunchWebAuthFlowFunction() {
880   if (auth_flow_)
881     auth_flow_.release()->DetachDelegateAndDelete();
882 }
883 
RunAsync()884 bool IdentityLaunchWebAuthFlowFunction::RunAsync() {
885   if (GetProfile()->IsOffTheRecord()) {
886     error_ = identity_constants::kOffTheRecord;
887     return false;
888   }
889 
890   scoped_ptr<identity::LaunchWebAuthFlow::Params> params(
891       identity::LaunchWebAuthFlow::Params::Create(*args_));
892   EXTENSION_FUNCTION_VALIDATE(params.get());
893 
894   GURL auth_url(params->details.url);
895   WebAuthFlow::Mode mode =
896       params->details.interactive && *params->details.interactive ?
897       WebAuthFlow::INTERACTIVE : WebAuthFlow::SILENT;
898 
899   // Set up acceptable target URLs. (Does not include chrome-extension
900   // scheme for this version of the API.)
901   InitFinalRedirectURLPrefix(extension()->id());
902 
903   AddRef();  // Balanced in OnAuthFlowSuccess/Failure.
904 
905   auth_flow_.reset(new WebAuthFlow(this, GetProfile(), auth_url, mode));
906   auth_flow_->Start();
907   return true;
908 }
909 
InitFinalRedirectURLPrefixForTest(const std::string & extension_id)910 void IdentityLaunchWebAuthFlowFunction::InitFinalRedirectURLPrefixForTest(
911     const std::string& extension_id) {
912   InitFinalRedirectURLPrefix(extension_id);
913 }
914 
InitFinalRedirectURLPrefix(const std::string & extension_id)915 void IdentityLaunchWebAuthFlowFunction::InitFinalRedirectURLPrefix(
916     const std::string& extension_id) {
917   if (final_url_prefix_.is_empty()) {
918     final_url_prefix_ = GURL(base::StringPrintf(
919         kChromiumDomainRedirectUrlPattern, extension_id.c_str()));
920   }
921 }
922 
OnAuthFlowFailure(WebAuthFlow::Failure failure)923 void IdentityLaunchWebAuthFlowFunction::OnAuthFlowFailure(
924     WebAuthFlow::Failure failure) {
925   switch (failure) {
926     case WebAuthFlow::WINDOW_CLOSED:
927       error_ = identity_constants::kUserRejected;
928       break;
929     case WebAuthFlow::INTERACTION_REQUIRED:
930       error_ = identity_constants::kInteractionRequired;
931       break;
932     case WebAuthFlow::LOAD_FAILED:
933       error_ = identity_constants::kPageLoadFailure;
934       break;
935     default:
936       NOTREACHED() << "Unexpected error from web auth flow: " << failure;
937       error_ = identity_constants::kInvalidRedirect;
938       break;
939   }
940   SendResponse(false);
941   Release();  // Balanced in RunAsync.
942 }
943 
OnAuthFlowURLChange(const GURL & redirect_url)944 void IdentityLaunchWebAuthFlowFunction::OnAuthFlowURLChange(
945     const GURL& redirect_url) {
946   if (redirect_url.GetWithEmptyPath() == final_url_prefix_) {
947     SetResult(new base::StringValue(redirect_url.spec()));
948     SendResponse(true);
949     Release();  // Balanced in RunAsync.
950   }
951 }
952 
953 }  // namespace extensions
954