Home | History | Annotate | Download | only in identity
      1 // Copyright (c) 2013 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/gaia_web_auth_flow.h"
      6 
      7 #include "base/debug/trace_event.h"
      8 #include "base/strings/string_number_conversions.h"
      9 #include "base/strings/string_split.h"
     10 #include "base/strings/string_util.h"
     11 #include "base/strings/stringprintf.h"
     12 #include "chrome/browser/profiles/profile.h"
     13 #include "chrome/browser/signin/chrome_signin_client_factory.h"
     14 #include "chrome/browser/signin/profile_oauth2_token_service_factory.h"
     15 #include "chrome/browser/signin/signin_manager_factory.h"
     16 #include "components/signin/core/browser/profile_oauth2_token_service.h"
     17 #include "components/signin/core/browser/signin_manager.h"
     18 #include "google_apis/gaia/gaia_urls.h"
     19 #include "net/base/escape.h"
     20 
     21 namespace extensions {
     22 
     23 GaiaWebAuthFlow::GaiaWebAuthFlow(Delegate* delegate,
     24                                  Profile* profile,
     25                                  const ExtensionTokenKey* token_key,
     26                                  const std::string& oauth2_client_id,
     27                                  const std::string& locale)
     28     : delegate_(delegate),
     29       profile_(profile),
     30       account_id_(token_key->account_id) {
     31   TRACE_EVENT_ASYNC_BEGIN2("identity",
     32                            "GaiaWebAuthFlow",
     33                            this,
     34                            "extension_id",
     35                            token_key->extension_id,
     36                            "account_id",
     37                            token_key->account_id);
     38 
     39   const char kOAuth2RedirectPathFormat[] = "/%s#";
     40   const char kOAuth2AuthorizeFormat[] =
     41       "?response_type=token&approval_prompt=force&authuser=0&"
     42       "client_id=%s&"
     43       "scope=%s&"
     44       "origin=chrome-extension://%s/&"
     45       "redirect_uri=%s:/%s&"
     46       "hl=%s";
     47   // Additional parameters to pass if device_id is enabled.
     48   const char kOAuth2AuthorizeFormatDeviceIdAddendum[] =
     49       "&device_id=%s&"
     50       "device_type=chrome";
     51 
     52   std::vector<std::string> scopes(token_key->scopes.begin(),
     53                                   token_key->scopes.end());
     54   std::vector<std::string> client_id_parts;
     55   base::SplitString(oauth2_client_id, '.', &client_id_parts);
     56   std::reverse(client_id_parts.begin(), client_id_parts.end());
     57   redirect_scheme_ = JoinString(client_id_parts, '.');
     58   std::string signin_scoped_device_id;
     59   // profile_ can be nullptr in unittests.
     60   SigninClient* signin_client =
     61       profile_ ? ChromeSigninClientFactory::GetForProfile(profile_) : nullptr;
     62   if (signin_client)
     63     signin_scoped_device_id = signin_client->GetSigninScopedDeviceId();
     64 
     65   redirect_path_prefix_ = base::StringPrintf(kOAuth2RedirectPathFormat,
     66                                              token_key->extension_id.c_str());
     67 
     68   std::string oauth2_authorize_params = base::StringPrintf(
     69       kOAuth2AuthorizeFormat,
     70       oauth2_client_id.c_str(),
     71       net::EscapeUrlEncodedData(JoinString(scopes, ' '), true).c_str(),
     72       token_key->extension_id.c_str(),
     73       redirect_scheme_.c_str(),
     74       token_key->extension_id.c_str(),
     75       locale.c_str());
     76   if (!signin_scoped_device_id.empty()) {
     77     oauth2_authorize_params += base::StringPrintf(
     78         kOAuth2AuthorizeFormatDeviceIdAddendum,
     79         net::EscapeUrlEncodedData(signin_scoped_device_id, true).c_str());
     80   }
     81   auth_url_ = GaiaUrls::GetInstance()->oauth2_auth_url().Resolve(
     82       oauth2_authorize_params);
     83 }
     84 
     85 GaiaWebAuthFlow::~GaiaWebAuthFlow() {
     86   TRACE_EVENT_ASYNC_END0("identity", "GaiaWebAuthFlow", this);
     87 
     88   if (web_flow_)
     89     web_flow_.release()->DetachDelegateAndDelete();
     90 }
     91 
     92 void GaiaWebAuthFlow::Start() {
     93   ProfileOAuth2TokenService* token_service =
     94       ProfileOAuth2TokenServiceFactory::GetForProfile(profile_);
     95   ubertoken_fetcher_.reset(new UbertokenFetcher(token_service,
     96                                                 this,
     97                                                 profile_->GetRequestContext()));
     98   ubertoken_fetcher_->StartFetchingToken(account_id_);
     99 }
    100 
    101 void GaiaWebAuthFlow::OnUbertokenSuccess(const std::string& token) {
    102   TRACE_EVENT_ASYNC_STEP_PAST0(
    103       "identity", "GaiaWebAuthFlow", this, "OnUbertokenSuccess");
    104 
    105   const char kMergeSessionQueryFormat[] = "?uberauth=%s&"
    106                                           "continue=%s&"
    107                                           "source=appsv2";
    108 
    109   std::string merge_query = base::StringPrintf(
    110       kMergeSessionQueryFormat,
    111       net::EscapeUrlEncodedData(token, true).c_str(),
    112       net::EscapeUrlEncodedData(auth_url_.spec(), true).c_str());
    113   GURL merge_url(
    114       GaiaUrls::GetInstance()->merge_session_url().Resolve(merge_query));
    115 
    116   web_flow_ = CreateWebAuthFlow(merge_url);
    117   web_flow_->Start();
    118 }
    119 
    120 void GaiaWebAuthFlow::OnUbertokenFailure(const GoogleServiceAuthError& error) {
    121   TRACE_EVENT_ASYNC_STEP_PAST1("identity",
    122                                "GaiaWebAuthFlow",
    123                                this,
    124                                "OnUbertokenSuccess",
    125                                "error",
    126                                error.ToString());
    127 
    128   DVLOG(1) << "OnUbertokenFailure: " << error.error_message();
    129   delegate_->OnGaiaFlowFailure(
    130       GaiaWebAuthFlow::SERVICE_AUTH_ERROR, error, std::string());
    131 }
    132 
    133 void GaiaWebAuthFlow::OnAuthFlowFailure(WebAuthFlow::Failure failure) {
    134   GaiaWebAuthFlow::Failure gaia_failure;
    135 
    136   switch (failure) {
    137     case WebAuthFlow::WINDOW_CLOSED:
    138       gaia_failure = GaiaWebAuthFlow::WINDOW_CLOSED;
    139       break;
    140     case WebAuthFlow::LOAD_FAILED:
    141       DVLOG(1) << "OnAuthFlowFailure LOAD_FAILED";
    142       gaia_failure = GaiaWebAuthFlow::LOAD_FAILED;
    143       break;
    144     default:
    145       NOTREACHED() << "Unexpected error from web auth flow: " << failure;
    146       gaia_failure = GaiaWebAuthFlow::LOAD_FAILED;
    147       break;
    148   }
    149 
    150   TRACE_EVENT_ASYNC_STEP_PAST1("identity",
    151                                "GaiaWebAuthFlow",
    152                                this,
    153                                "OnAuthFlowFailure",
    154                                "error",
    155                                gaia_failure);
    156 
    157   delegate_->OnGaiaFlowFailure(
    158       gaia_failure,
    159       GoogleServiceAuthError(GoogleServiceAuthError::NONE),
    160       std::string());
    161 }
    162 
    163 void GaiaWebAuthFlow::OnAuthFlowURLChange(const GURL& url) {
    164   TRACE_EVENT_ASYNC_STEP_PAST0("identity",
    165                                "GaiaWebAuthFlow",
    166                                this,
    167                                "OnAuthFlowURLChange");
    168 
    169   const char kOAuth2RedirectAccessTokenKey[] = "access_token";
    170   const char kOAuth2RedirectErrorKey[] = "error";
    171   const char kOAuth2ExpiresInKey[] = "expires_in";
    172 
    173   // The format of the target URL is:
    174   //     reversed.oauth.client.id:/extensionid#access_token=TOKEN
    175   //
    176   // Because there is no double slash, everything after the scheme is
    177   // interpreted as a path, including the fragment.
    178 
    179   if (url.scheme() == redirect_scheme_ && !url.has_host() && !url.has_port() &&
    180       StartsWithASCII(url.GetContent(), redirect_path_prefix_, true)) {
    181     web_flow_.release()->DetachDelegateAndDelete();
    182 
    183     std::string fragment = url.GetContent().substr(
    184         redirect_path_prefix_.length(), std::string::npos);
    185     base::StringPairs pairs;
    186     base::SplitStringIntoKeyValuePairs(fragment, '=', '&', &pairs);
    187     std::string access_token;
    188     std::string error;
    189     std::string expiration;
    190 
    191     for (base::StringPairs::iterator it = pairs.begin();
    192          it != pairs.end();
    193          ++it) {
    194       if (it->first == kOAuth2RedirectAccessTokenKey)
    195         access_token = it->second;
    196       else if (it->first == kOAuth2RedirectErrorKey)
    197         error = it->second;
    198       else if (it->first == kOAuth2ExpiresInKey)
    199         expiration = it->second;
    200     }
    201 
    202     if (access_token.empty() && error.empty()) {
    203       delegate_->OnGaiaFlowFailure(
    204           GaiaWebAuthFlow::INVALID_REDIRECT,
    205           GoogleServiceAuthError(GoogleServiceAuthError::NONE),
    206           std::string());
    207     } else if (!error.empty()) {
    208       delegate_->OnGaiaFlowFailure(
    209           GaiaWebAuthFlow::OAUTH_ERROR,
    210           GoogleServiceAuthError(GoogleServiceAuthError::NONE),
    211           error);
    212     } else {
    213       delegate_->OnGaiaFlowCompleted(access_token, expiration);
    214     }
    215   }
    216 }
    217 
    218 void GaiaWebAuthFlow::OnAuthFlowTitleChange(const std::string& title) {
    219   // On the final page the title will be "Loading <redirect-url>".
    220   // Treat it as though we'd really been redirected to <redirect-url>.
    221   const char kRedirectPrefix[] = "Loading ";
    222   std::string prefix(kRedirectPrefix);
    223 
    224   if (StartsWithASCII(title, prefix, true)) {
    225     GURL url(title.substr(prefix.length(), std::string::npos));
    226     if (url.is_valid())
    227       OnAuthFlowURLChange(url);
    228   }
    229 }
    230 
    231 scoped_ptr<WebAuthFlow> GaiaWebAuthFlow::CreateWebAuthFlow(GURL url) {
    232   return scoped_ptr<WebAuthFlow>(new WebAuthFlow(this,
    233                                                  profile_,
    234                                                  url,
    235                                                  WebAuthFlow::INTERACTIVE));
    236 }
    237 
    238 }  // namespace extensions
    239