identity_api.cc revision 1320f92c476a1ad9d19dba2a48c72b75566198e9
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
51namespace extensions {
52
53namespace identity_constants {
54const char kInvalidClientId[] = "Invalid OAuth2 Client ID.";
55const char kInvalidScopes[] = "Invalid OAuth2 scopes.";
56const char kAuthFailure[] = "OAuth2 request failed: ";
57const char kNoGrant[] = "OAuth2 not granted or revoked.";
58const char kUserRejected[] = "The user did not approve access.";
59const char kUserNotSignedIn[] = "The user is not signed in.";
60const char kInteractionRequired[] = "User interaction required.";
61const char kInvalidRedirect[] = "Did not redirect to the right URL.";
62const char kOffTheRecord[] = "Identity API is disabled in incognito windows.";
63const char kPageLoadFailure[] = "Authorization page could not be loaded.";
64const char kCanceled[] = "canceled";
65
66const int kCachedIssueAdviceTTLSeconds = 1;
67}  // namespace identity_constants
68
69namespace {
70
71static const char kChromiumDomainRedirectUrlPattern[] =
72    "https://%s.chromiumapp.org/";
73
74std::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
82namespace identity = api::identity;
83
84IdentityTokenCacheValue::IdentityTokenCacheValue()
85    : status_(CACHE_STATUS_NOTFOUND) {}
86
87IdentityTokenCacheValue::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
95IdentityTokenCacheValue::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
109IdentityTokenCacheValue::~IdentityTokenCacheValue() {}
110
111IdentityTokenCacheValue::CacheValueStatus IdentityTokenCacheValue::status()
112    const {
113  if (is_expired())
114    return IdentityTokenCacheValue::CACHE_STATUS_NOTFOUND;
115  else
116    return status_;
117}
118
119const IssueAdviceInfo& IdentityTokenCacheValue::issue_advice() const {
120  return issue_advice_;
121}
122
123const std::string& IdentityTokenCacheValue::token() const { return token_; }
124
125bool IdentityTokenCacheValue::is_expired() const {
126  return status_ == CACHE_STATUS_NOTFOUND ||
127         expiration_time_ < base::Time::Now();
128}
129
130const base::Time& IdentityTokenCacheValue::expiration_time() const {
131  return expiration_time_;
132}
133
134IdentityAPI::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
148IdentityAPI::~IdentityAPI() {}
149
150IdentityMintRequestQueue* IdentityAPI::mint_queue() { return &mint_queue_; }
151
152void 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
161void 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
174void IdentityAPI::EraseAllCachedTokens() { token_cache_.clear(); }
175
176const IdentityTokenCacheValue& IdentityAPI::GetCachedToken(
177    const ExtensionTokenKey& key) {
178  return token_cache_[key];
179}
180
181const IdentityAPI::CachedTokens& IdentityAPI::GetAllCachedTokens() {
182  return token_cache_;
183}
184
185std::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
203std::string IdentityAPI::FindAccountKeyByGaiaId(const std::string& gaia_id) {
204  return account_tracker_.FindAccountIdsByGaiaId(gaia_id).account_key;
205}
206
207void IdentityAPI::Shutdown() {
208  FOR_EACH_OBSERVER(ShutdownObserver, shutdown_observer_list_, OnShutdown());
209  account_tracker_.RemoveObserver(this);
210  account_tracker_.Shutdown();
211}
212
213static base::LazyInstance<BrowserContextKeyedAPIFactory<IdentityAPI> >
214    g_factory = LAZY_INSTANCE_INITIALIZER;
215
216// static
217BrowserContextKeyedAPIFactory<IdentityAPI>* IdentityAPI::GetFactoryInstance() {
218  return g_factory.Pointer();
219}
220
221void IdentityAPI::OnAccountAdded(const gaia::AccountIds& ids) {
222}
223
224void IdentityAPI::OnAccountRemoved(const gaia::AccountIds& ids) {
225}
226
227void 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
241void IdentityAPI::AddShutdownObserver(ShutdownObserver* observer) {
242  shutdown_observer_list_.AddObserver(observer);
243}
244
245void IdentityAPI::RemoveShutdownObserver(ShutdownObserver* observer) {
246  shutdown_observer_list_.RemoveObserver(observer);
247}
248
249void IdentityAPI::SetAccountStateForTest(gaia::AccountIds ids,
250                                         bool is_signed_in) {
251  account_tracker_.SetAccountStateForTest(ids, is_signed_in);
252}
253
254template <>
255void BrowserContextKeyedAPIFactory<IdentityAPI>::DeclareFactoryDependencies() {
256  DependsOn(ExtensionsBrowserClient::Get()->GetExtensionSystemFactory());
257  DependsOn(ProfileOAuth2TokenServiceFactory::GetInstance());
258}
259
260IdentityGetAccountsFunction::IdentityGetAccountsFunction() {
261}
262
263IdentityGetAccountsFunction::~IdentityGetAccountsFunction() {
264}
265
266ExtensionFunction::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
288IdentityGetAuthTokenFunction::IdentityGetAuthTokenFunction()
289    : OAuth2TokenService::Consumer("extensions_identity_api"),
290      should_prompt_for_scopes_(false),
291      should_prompt_for_signin_(false) {
292}
293
294IdentityGetAuthTokenFunction::~IdentityGetAuthTokenFunction() {
295  TRACE_EVENT_ASYNC_END0("identity", "IdentityGetAuthTokenFunction", this);
296}
297
298bool 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
393void IdentityGetAuthTokenFunction::StartAsyncRun() {
394  // Balanced in CompleteAsyncRun
395  AddRef();
396  extensions::IdentityAPI::GetFactoryInstance()
397      ->Get(GetProfile())
398      ->AddShutdownObserver(this);
399}
400
401void 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
410void IdentityGetAuthTokenFunction::CompleteFunctionWithResult(
411    const std::string& access_token) {
412
413  SetResult(new base::StringValue(access_token));
414  CompleteAsyncRun(true);
415}
416
417void 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
429void 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
440void 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
467void 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
476void 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
544void 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
560void 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
590void 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
608void 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
623void IdentityGetAuthTokenFunction::SigninFailed() {
624  TRACE_EVENT_ASYNC_STEP_PAST0("identity",
625                               "IdentityGetAuthTokenFunction",
626                               this,
627                               "SigninFailed");
628  CompleteFunctionWithError(identity_constants::kUserNotSignedIn);
629}
630
631void 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
669void 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
688void 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
702void 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
715void 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)
727void 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
741void 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
765void 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
772void IdentityGetAuthTokenFunction::ShowLoginPopup() {
773  signin_flow_.reset(new IdentitySigninFlow(this, GetProfile()));
774  signin_flow_->Start();
775}
776
777void 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
786OAuth2MintTokenFlow* 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
801bool IdentityGetAuthTokenFunction::HasLoginToken() const {
802  ProfileOAuth2TokenService* token_service =
803      ProfileOAuth2TokenServiceFactory::GetForProfile(GetProfile());
804  return token_service->RefreshTokenIsAvailable(token_key_->account_id);
805}
806
807std::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
820std::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
833IdentityGetProfileUserInfoFunction::IdentityGetProfileUserInfoFunction() {
834}
835
836IdentityGetProfileUserInfoFunction::~IdentityGetProfileUserInfoFunction() {
837}
838
839ExtensionFunction::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
856IdentityRemoveCachedAuthTokenFunction::IdentityRemoveCachedAuthTokenFunction() {
857}
858
859IdentityRemoveCachedAuthTokenFunction::
860    ~IdentityRemoveCachedAuthTokenFunction() {
861}
862
863bool 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
877IdentityLaunchWebAuthFlowFunction::IdentityLaunchWebAuthFlowFunction() {}
878
879IdentityLaunchWebAuthFlowFunction::~IdentityLaunchWebAuthFlowFunction() {
880  if (auth_flow_)
881    auth_flow_.release()->DetachDelegateAndDelete();
882}
883
884bool 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
910void IdentityLaunchWebAuthFlowFunction::InitFinalRedirectURLPrefixForTest(
911    const std::string& extension_id) {
912  InitFinalRedirectURLPrefix(extension_id);
913}
914
915void 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
923void 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
944void 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