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