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