Home | History | Annotate | Download | only in identity
      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 
     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 
     84 IdentityTokenCacheValue::IdentityTokenCacheValue()
     85     : status_(CACHE_STATUS_NOTFOUND) {}
     86 
     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 
     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 
    109 IdentityTokenCacheValue::~IdentityTokenCacheValue() {}
    110 
    111 IdentityTokenCacheValue::CacheValueStatus IdentityTokenCacheValue::status()
    112     const {
    113   if (is_expired())
    114     return IdentityTokenCacheValue::CACHE_STATUS_NOTFOUND;
    115   else
    116     return status_;
    117 }
    118 
    119 const IssueAdviceInfo& IdentityTokenCacheValue::issue_advice() const {
    120   return issue_advice_;
    121 }
    122 
    123 const std::string& IdentityTokenCacheValue::token() const { return token_; }
    124 
    125 bool IdentityTokenCacheValue::is_expired() const {
    126   return status_ == CACHE_STATUS_NOTFOUND ||
    127          expiration_time_ < base::Time::Now();
    128 }
    129 
    130 const base::Time& IdentityTokenCacheValue::expiration_time() const {
    131   return expiration_time_;
    132 }
    133 
    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 
    148 IdentityAPI::~IdentityAPI() {}
    149 
    150 IdentityMintRequestQueue* IdentityAPI::mint_queue() { return &mint_queue_; }
    151 
    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 
    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 
    174 void IdentityAPI::EraseAllCachedTokens() { token_cache_.clear(); }
    175 
    176 const IdentityTokenCacheValue& IdentityAPI::GetCachedToken(
    177     const ExtensionTokenKey& key) {
    178   return token_cache_[key];
    179 }
    180 
    181 const IdentityAPI::CachedTokens& IdentityAPI::GetAllCachedTokens() {
    182   return token_cache_;
    183 }
    184 
    185 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 
    203 std::string IdentityAPI::FindAccountKeyByGaiaId(const std::string& gaia_id) {
    204   return account_tracker_.FindAccountIdsByGaiaId(gaia_id).account_key;
    205 }
    206 
    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
    217 BrowserContextKeyedAPIFactory<IdentityAPI>* IdentityAPI::GetFactoryInstance() {
    218   return g_factory.Pointer();
    219 }
    220 
    221 void IdentityAPI::OnAccountAdded(const gaia::AccountIds& ids) {
    222 }
    223 
    224 void IdentityAPI::OnAccountRemoved(const gaia::AccountIds& ids) {
    225 }
    226 
    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 
    241 void IdentityAPI::AddShutdownObserver(ShutdownObserver* observer) {
    242   shutdown_observer_list_.AddObserver(observer);
    243 }
    244 
    245 void IdentityAPI::RemoveShutdownObserver(ShutdownObserver* observer) {
    246   shutdown_observer_list_.RemoveObserver(observer);
    247 }
    248 
    249 void IdentityAPI::SetAccountStateForTest(gaia::AccountIds ids,
    250                                          bool is_signed_in) {
    251   account_tracker_.SetAccountStateForTest(ids, is_signed_in);
    252 }
    253 
    254 template <>
    255 void BrowserContextKeyedAPIFactory<IdentityAPI>::DeclareFactoryDependencies() {
    256   DependsOn(ExtensionsBrowserClient::Get()->GetExtensionSystemFactory());
    257   DependsOn(ProfileOAuth2TokenServiceFactory::GetInstance());
    258 }
    259 
    260 IdentityGetAccountsFunction::IdentityGetAccountsFunction() {
    261 }
    262 
    263 IdentityGetAccountsFunction::~IdentityGetAccountsFunction() {
    264 }
    265 
    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 
    288 IdentityGetAuthTokenFunction::IdentityGetAuthTokenFunction()
    289     : OAuth2TokenService::Consumer("extensions_identity_api"),
    290       should_prompt_for_scopes_(false),
    291       should_prompt_for_signin_(false) {
    292 }
    293 
    294 IdentityGetAuthTokenFunction::~IdentityGetAuthTokenFunction() {
    295   TRACE_EVENT_ASYNC_END0("identity", "IdentityGetAuthTokenFunction", this);
    296 }
    297 
    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 
    393 void IdentityGetAuthTokenFunction::StartAsyncRun() {
    394   // Balanced in CompleteAsyncRun
    395   AddRef();
    396   extensions::IdentityAPI::GetFactoryInstance()
    397       ->Get(GetProfile())
    398       ->AddShutdownObserver(this);
    399 }
    400 
    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 
    410 void IdentityGetAuthTokenFunction::CompleteFunctionWithResult(
    411     const std::string& access_token) {
    412 
    413   SetResult(new base::StringValue(access_token));
    414   CompleteAsyncRun(true);
    415 }
    416 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    623 void IdentityGetAuthTokenFunction::SigninFailed() {
    624   TRACE_EVENT_ASYNC_STEP_PAST0("identity",
    625                                "IdentityGetAuthTokenFunction",
    626                                this,
    627                                "SigninFailed");
    628   CompleteFunctionWithError(identity_constants::kUserNotSignedIn);
    629 }
    630 
    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 
    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 
    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 
    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 
    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)
    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 
    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 
    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 
    772 void IdentityGetAuthTokenFunction::ShowLoginPopup() {
    773   signin_flow_.reset(new IdentitySigninFlow(this, GetProfile()));
    774   signin_flow_->Start();
    775 }
    776 
    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 
    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 
    801 bool IdentityGetAuthTokenFunction::HasLoginToken() const {
    802   ProfileOAuth2TokenService* token_service =
    803       ProfileOAuth2TokenServiceFactory::GetForProfile(GetProfile());
    804   return token_service->RefreshTokenIsAvailable(token_key_->account_id);
    805 }
    806 
    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 
    820 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 
    833 IdentityGetProfileUserInfoFunction::IdentityGetProfileUserInfoFunction() {
    834 }
    835 
    836 IdentityGetProfileUserInfoFunction::~IdentityGetProfileUserInfoFunction() {
    837 }
    838 
    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 
    856 IdentityRemoveCachedAuthTokenFunction::IdentityRemoveCachedAuthTokenFunction() {
    857 }
    858 
    859 IdentityRemoveCachedAuthTokenFunction::
    860     ~IdentityRemoveCachedAuthTokenFunction() {
    861 }
    862 
    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 
    877 IdentityLaunchWebAuthFlowFunction::IdentityLaunchWebAuthFlowFunction() {}
    878 
    879 IdentityLaunchWebAuthFlowFunction::~IdentityLaunchWebAuthFlowFunction() {
    880   if (auth_flow_)
    881     auth_flow_.release()->DetachDelegateAndDelete();
    882 }
    883 
    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 
    910 void IdentityLaunchWebAuthFlowFunction::InitFinalRedirectURLPrefixForTest(
    911     const std::string& extension_id) {
    912   InitFinalRedirectURLPrefix(extension_id);
    913 }
    914 
    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 
    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 
    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