Home | History | Annotate | Download | only in gaia
      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/net/gaia/gaia_oauth_fetcher.h"
      6 
      7 #include <string>
      8 #include <utility>
      9 #include <vector>
     10 
     11 #include "base/json/json_reader.h"
     12 #include "base/strings/string_split.h"
     13 #include "base/strings/string_util.h"
     14 #include "base/values.h"
     15 #include "chrome/browser/net/gaia/gaia_oauth_consumer.h"
     16 #include "google_apis/gaia/gaia_auth_fetcher.h"
     17 #include "google_apis/gaia/gaia_constants.h"
     18 #include "google_apis/gaia/gaia_urls.h"
     19 #include "google_apis/gaia/oauth_request_signer.h"
     20 #include "grit/chromium_strings.h"
     21 #include "net/base/load_flags.h"
     22 #include "net/cookies/parsed_cookie.h"
     23 #include "net/http/http_status_code.h"
     24 #include "net/url_request/url_fetcher.h"
     25 #include "net/url_request/url_request_context_getter.h"
     26 #include "net/url_request/url_request_status.h"
     27 #include "ui/base/l10n/l10n_util.h"
     28 
     29 static const char kOAuthTokenCookie[] = "oauth_token";
     30 
     31 GaiaOAuthFetcher::GaiaOAuthFetcher(GaiaOAuthConsumer* consumer,
     32                                    net::URLRequestContextGetter* getter,
     33                                    const std::string& service_scope)
     34     : consumer_(consumer),
     35       getter_(getter),
     36       service_scope_(service_scope),
     37       fetch_pending_(false),
     38       auto_fetch_limit_(USER_INFO) {}
     39 
     40 GaiaOAuthFetcher::~GaiaOAuthFetcher() {}
     41 
     42 bool GaiaOAuthFetcher::HasPendingFetch() const {
     43   return fetch_pending_;
     44 }
     45 
     46 void GaiaOAuthFetcher::CancelRequest() {
     47   fetcher_.reset();
     48   fetch_pending_ = false;
     49 }
     50 
     51 // static
     52 net::URLFetcher* GaiaOAuthFetcher::CreateGaiaFetcher(
     53     net::URLRequestContextGetter* getter,
     54     const GURL& gaia_gurl,
     55     const std::string& body,
     56     const std::string& headers,
     57     bool send_cookies,
     58     net::URLFetcherDelegate* delegate) {
     59   bool empty_body = body.empty();
     60   net::URLFetcher* result = net::URLFetcher::Create(
     61       0, gaia_gurl,
     62       empty_body ? net::URLFetcher::GET : net::URLFetcher::POST,
     63       delegate);
     64   result->SetRequestContext(getter);
     65   // Fetchers are sometimes cancelled because a network change was detected,
     66   // especially at startup and after sign-in on ChromeOS. Retrying once should
     67   // be enough in those cases; let the fetcher retry up to 3 times just in case.
     68   // http://crbug.com/163710
     69   result->SetAutomaticallyRetryOnNetworkChanges(3);
     70 
     71   // The Gaia/OAuth token exchange requests do not require any cookie-based
     72   // identification as part of requests.  We suppress sending any cookies to
     73   // maintain a separation between the user's browsing and Chrome's internal
     74   // services.  Where such mixing is desired (prelogin, autologin
     75   // or chromeos login), it will be done explicitly.
     76   if (!send_cookies)
     77     result->SetLoadFlags(net::LOAD_DO_NOT_SEND_COOKIES);
     78 
     79   if (!empty_body)
     80     result->SetUploadData("application/x-www-form-urlencoded", body);
     81   if (!headers.empty())
     82     result->SetExtraRequestHeaders(headers);
     83 
     84   return result;
     85 }
     86 
     87 // static
     88 GURL GaiaOAuthFetcher::MakeGetOAuthTokenUrl(
     89     const std::string& oauth1_login_scope,
     90     const std::string& product_name) {
     91   return GaiaUrls::GetInstance()->get_oauth_token_url().Resolve(
     92       "?scope=" + oauth1_login_scope +
     93       "&xoauth_display_name=" +
     94       OAuthRequestSigner::Encode(product_name));
     95 }
     96 
     97 // static
     98 std::string GaiaOAuthFetcher::MakeOAuthLoginBody(
     99     const char* source,
    100     const char* service,
    101     const std::string& oauth1_access_token,
    102     const std::string& oauth1_access_token_secret) {
    103   OAuthRequestSigner::Parameters parameters;
    104   parameters["service"] = service;
    105   parameters["source"] = source;
    106   std::string signed_request;
    107   bool is_signed = OAuthRequestSigner::SignURL(
    108       GaiaUrls::GetInstance()->oauth1_login_url(),
    109       parameters,
    110       OAuthRequestSigner::HMAC_SHA1_SIGNATURE,
    111       OAuthRequestSigner::POST_METHOD,
    112       "anonymous",  // oauth_consumer_key
    113       "anonymous",  // consumer secret
    114       oauth1_access_token,  // oauth_token
    115       oauth1_access_token_secret,  // token secret
    116       &signed_request);
    117   DCHECK(is_signed);
    118   return signed_request;
    119 }
    120 
    121 // static
    122 std::string GaiaOAuthFetcher::MakeOAuthGetAccessTokenBody(
    123     const std::string& oauth1_request_token) {
    124   OAuthRequestSigner::Parameters empty_parameters;
    125   std::string signed_request;
    126   bool is_signed = OAuthRequestSigner::SignURL(
    127       GaiaUrls::GetInstance()->oauth_get_access_token_url(),
    128       empty_parameters,
    129       OAuthRequestSigner::HMAC_SHA1_SIGNATURE,
    130       OAuthRequestSigner::POST_METHOD,
    131       "anonymous",  // oauth_consumer_key
    132       "anonymous",  // consumer secret
    133       oauth1_request_token,  // oauth_token
    134       "",  // token secret
    135       &signed_request);
    136   DCHECK(is_signed);
    137   return signed_request;
    138 }
    139 
    140 // static
    141 std::string GaiaOAuthFetcher::MakeOAuthWrapBridgeBody(
    142     const std::string& oauth1_access_token,
    143     const std::string& oauth1_access_token_secret,
    144     const std::string& wrap_token_duration,
    145     const std::string& oauth2_scope) {
    146   OAuthRequestSigner::Parameters parameters;
    147   parameters["wrap_token_duration"] = wrap_token_duration;
    148   parameters["wrap_scope"] = oauth2_scope;
    149   std::string signed_request;
    150   bool is_signed = OAuthRequestSigner::SignURL(
    151       GaiaUrls::GetInstance()->oauth_wrap_bridge_url(),
    152       parameters,
    153       OAuthRequestSigner::HMAC_SHA1_SIGNATURE,
    154       OAuthRequestSigner::POST_METHOD,
    155       "anonymous",  // oauth_consumer_key
    156       "anonymous",  // consumer secret
    157       oauth1_access_token,  // oauth_token
    158       oauth1_access_token_secret,  // token secret
    159       &signed_request);
    160   DCHECK(is_signed);
    161   return signed_request;
    162 }
    163 
    164 // Helper method that extracts tokens from a successful reply.
    165 // static
    166 void GaiaOAuthFetcher::ParseOAuthLoginResponse(
    167     const std::string& data,
    168     std::string* sid,
    169     std::string* lsid,
    170     std::string* auth) {
    171   using std::vector;
    172   using std::pair;
    173   using std::string;
    174   vector<pair<string, string> > tokens;
    175   base::SplitStringIntoKeyValuePairs(data, '=', '\n', &tokens);
    176   for (vector<pair<string, string> >::iterator i = tokens.begin();
    177       i != tokens.end(); ++i) {
    178     if (i->first == "SID") {
    179       *sid = i->second;
    180     } else if (i->first == "LSID") {
    181       *lsid = i->second;
    182     } else if (i->first == "Auth") {
    183       *auth = i->second;
    184     }
    185   }
    186 }
    187 
    188 // Helper method that extracts tokens from a successful reply.
    189 // static
    190 void GaiaOAuthFetcher::ParseOAuthGetAccessTokenResponse(
    191     const std::string& data,
    192     std::string* token,
    193     std::string* secret) {
    194   using std::vector;
    195   using std::pair;
    196   using std::string;
    197 
    198   vector<pair<string, string> > tokens;
    199   base::SplitStringIntoKeyValuePairs(data, '=', '&', &tokens);
    200   for (vector<pair<string, string> >::iterator i = tokens.begin();
    201        i != tokens.end(); ++i) {
    202     if (i->first == "oauth_token") {
    203       std::string decoded;
    204       if (OAuthRequestSigner::Decode(i->second, &decoded))
    205         token->assign(decoded);
    206     } else if (i->first == "oauth_token_secret") {
    207       std::string decoded;
    208       if (OAuthRequestSigner::Decode(i->second, &decoded))
    209         secret->assign(decoded);
    210     }
    211   }
    212 }
    213 
    214 // Helper method that extracts tokens from a successful reply.
    215 // static
    216 void GaiaOAuthFetcher::ParseOAuthWrapBridgeResponse(const std::string& data,
    217                                                     std::string* token,
    218                                                     std::string* expires_in) {
    219   using std::vector;
    220   using std::pair;
    221   using std::string;
    222 
    223   vector<pair<string, string> > tokens;
    224   base::SplitStringIntoKeyValuePairs(data, '=', '&', &tokens);
    225   for (vector<pair<string, string> >::iterator i = tokens.begin();
    226        i != tokens.end(); ++i) {
    227     if (i->first == "wrap_access_token") {
    228       std::string decoded;
    229       if (OAuthRequestSigner::Decode(i->second, &decoded))
    230         token->assign(decoded);
    231     } else if (i->first == "wrap_access_token_expires_in") {
    232       std::string decoded;
    233       if (OAuthRequestSigner::Decode(i->second, &decoded))
    234         expires_in->assign(decoded);
    235     }
    236   }
    237 }
    238 
    239 // Helper method that extracts tokens from a successful reply.
    240 // static
    241 void GaiaOAuthFetcher::ParseUserInfoResponse(const std::string& data,
    242                                              std::string* email_result) {
    243   scoped_ptr<base::Value> value(base::JSONReader::Read(data));
    244   if (value->GetType() == base::Value::TYPE_DICTIONARY) {
    245     Value* email_value;
    246     DictionaryValue* dict = static_cast<DictionaryValue*>(value.get());
    247     if (dict->Get("email", &email_value)) {
    248       if (email_value->GetType() == base::Value::TYPE_STRING) {
    249         email_value->GetAsString(email_result);
    250       }
    251     }
    252   }
    253 }
    254 
    255 void GaiaOAuthFetcher::StartOAuthLogin(
    256     const char* source,
    257     const char* service,
    258     const std::string& oauth1_access_token,
    259     const std::string& oauth1_access_token_secret) {
    260   DCHECK(!fetch_pending_) << "Tried to fetch two things at once!";
    261 
    262   request_type_ = OAUTH1_LOGIN;
    263   // Must outlive fetcher_.
    264   request_body_ = MakeOAuthLoginBody(source, service, oauth1_access_token,
    265                                      oauth1_access_token_secret);
    266   request_headers_ = "";
    267   GURL url(GaiaUrls::GetInstance()->oauth1_login_url());
    268   fetcher_.reset(CreateGaiaFetcher(getter_, url, request_body_,
    269                                    request_headers_, false, this));
    270   fetch_pending_ = true;
    271   fetcher_->Start();
    272 }
    273 
    274 void GaiaOAuthFetcher::StartGetOAuthTokenRequest() {
    275   DCHECK(!fetch_pending_) << "Tried to fetch two things at once!";
    276 
    277   request_type_ = OAUTH1_REQUEST_TOKEN;
    278   // Must outlive fetcher_.
    279   request_body_ = "";
    280   request_headers_ = "";
    281   fetcher_.reset(CreateGaiaFetcher(getter_,
    282       MakeGetOAuthTokenUrl(GaiaUrls::GetInstance()->oauth1_login_scope(),
    283                            l10n_util::GetStringUTF8(IDS_PRODUCT_NAME)),
    284       std::string(),
    285       std::string(),
    286       true,           // send_cookies
    287       this));
    288   fetch_pending_ = true;
    289   fetcher_->Start();
    290 }
    291 
    292 void GaiaOAuthFetcher::StartOAuthGetAccessToken(
    293     const std::string& oauth1_request_token) {
    294   DCHECK(!fetch_pending_) << "Tried to fetch two things at once!";
    295 
    296   request_type_ = OAUTH1_ALL_ACCESS_TOKEN;
    297   // Must outlive fetcher_.
    298   request_body_ = MakeOAuthGetAccessTokenBody(oauth1_request_token);
    299   request_headers_ = "";
    300   GURL url(GaiaUrls::GetInstance()->oauth_get_access_token_url());
    301   fetcher_.reset(CreateGaiaFetcher(getter_, url, request_body_,
    302                                    request_headers_, false, this));
    303   fetch_pending_ = true;
    304   fetcher_->Start();
    305 }
    306 
    307 void GaiaOAuthFetcher::StartOAuthWrapBridge(
    308     const std::string& oauth1_access_token,
    309     const std::string& oauth1_access_token_secret,
    310     const std::string& wrap_token_duration,
    311     const std::string& service_scope) {
    312   DCHECK(!fetch_pending_) << "Tried to fetch two things at once!";
    313 
    314   request_type_ = OAUTH2_SERVICE_ACCESS_TOKEN;
    315   VLOG(1) << "Starting OAuthWrapBridge for: " << service_scope;
    316   std::string combined_scope = service_scope + " " +
    317       GaiaUrls::GetInstance()->oauth_wrap_bridge_user_info_scope();
    318   service_scope_ = service_scope;
    319 
    320   // Must outlive fetcher_.
    321   request_body_ = MakeOAuthWrapBridgeBody(
    322       oauth1_access_token,
    323       oauth1_access_token_secret,
    324       wrap_token_duration,
    325       combined_scope);
    326 
    327   request_headers_ = "";
    328   GURL url(GaiaUrls::GetInstance()->oauth_wrap_bridge_url());
    329   fetcher_.reset(CreateGaiaFetcher(getter_, url, request_body_,
    330                                    request_headers_, false, this));
    331   fetch_pending_ = true;
    332   fetcher_->Start();
    333 }
    334 
    335 void GaiaOAuthFetcher::StartUserInfo(const std::string& oauth2_access_token) {
    336   DCHECK(!fetch_pending_) << "Tried to fetch two things at once!";
    337 
    338   request_type_ = USER_INFO;
    339   // Must outlive fetcher_.
    340   request_body_ = "";
    341   request_headers_ = "Authorization: OAuth " + oauth2_access_token;
    342   GURL url(GaiaUrls::GetInstance()->oauth_user_info_url());
    343   fetcher_.reset(CreateGaiaFetcher(getter_, url, request_body_,
    344                                    request_headers_, false, this));
    345   fetch_pending_ = true;
    346   fetcher_->Start();
    347 }
    348 
    349 void GaiaOAuthFetcher::StartOAuthRevokeAccessToken(const std::string& token,
    350                                                    const std::string& secret) {
    351   DCHECK(!fetch_pending_) << "Tried to fetch two things at once!";
    352 
    353   request_type_ = OAUTH2_REVOKE_TOKEN;
    354   // Must outlive fetcher_.
    355   request_body_ = "";
    356 
    357   OAuthRequestSigner::Parameters empty_parameters;
    358   std::string auth_header;
    359   bool is_signed = OAuthRequestSigner::SignAuthHeader(
    360       GaiaUrls::GetInstance()->oauth_revoke_token_url(),
    361       empty_parameters,
    362       OAuthRequestSigner::HMAC_SHA1_SIGNATURE,
    363       OAuthRequestSigner::GET_METHOD,
    364       "anonymous",
    365       "anonymous",
    366       token,
    367       secret,
    368       &auth_header);
    369   DCHECK(is_signed);
    370   request_headers_ = "Authorization: " + auth_header;
    371   GURL url(GaiaUrls::GetInstance()->oauth_revoke_token_url());
    372   fetcher_.reset(CreateGaiaFetcher(getter_, url, request_body_,
    373                                    request_headers_, false, this));
    374   fetch_pending_ = true;
    375   fetcher_->Start();
    376 }
    377 
    378 void GaiaOAuthFetcher::StartOAuthRevokeWrapToken(const std::string& token) {
    379   DCHECK(!fetch_pending_) << "Tried to fetch two things at once!";
    380 
    381   request_type_ = OAUTH2_REVOKE_TOKEN;
    382   // Must outlive fetcher_.
    383   request_body_ = "";
    384 
    385   request_headers_ = "Authorization: Bearer " + token;
    386   GURL url(GaiaUrls::GetInstance()->oauth_revoke_token_url());
    387   fetcher_.reset(CreateGaiaFetcher(getter_, url, request_body_,
    388                                    request_headers_, false, this));
    389   fetch_pending_ = true;
    390   fetcher_->Start();
    391 }
    392 
    393 // static
    394 GoogleServiceAuthError GaiaOAuthFetcher::GenerateAuthError(
    395     const std::string& data,
    396     const net::URLRequestStatus& status,
    397     int response_code) {
    398   if (!status.is_success()) {
    399     if (status.status() == net::URLRequestStatus::CANCELED) {
    400       return GoogleServiceAuthError(GoogleServiceAuthError::REQUEST_CANCELED);
    401     } else {
    402       LOG(WARNING) << "Could not reach Google Accounts servers: errno "
    403                    << status.error();
    404       return GoogleServiceAuthError::FromConnectionError(status.error());
    405     }
    406   } else {
    407     LOG(WARNING) << "Unrecognized response from Google Accounts servers "
    408                  << "code " << response_code << " data " << data;
    409     return GoogleServiceAuthError(
    410         GoogleServiceAuthError::SERVICE_UNAVAILABLE);
    411   }
    412 
    413   NOTREACHED();
    414   return GoogleServiceAuthError(GoogleServiceAuthError::SERVICE_UNAVAILABLE);
    415 }
    416 
    417 void GaiaOAuthFetcher::OnGetOAuthTokenUrlFetched(
    418     const net::ResponseCookies& cookies,
    419     const net::URLRequestStatus& status,
    420     int response_code) {
    421   if (status.is_success() && response_code == net::HTTP_OK) {
    422     for (net::ResponseCookies::const_iterator iter = cookies.begin();
    423         iter != cookies.end(); ++iter) {
    424       net::ParsedCookie cookie(*iter);
    425       if (cookie.Name() == kOAuthTokenCookie) {
    426         std::string token = cookie.Value();
    427         consumer_->OnGetOAuthTokenSuccess(token);
    428         if (ShouldAutoFetch(OAUTH1_ALL_ACCESS_TOKEN))
    429           StartOAuthGetAccessToken(token);
    430         return;
    431       }
    432     }
    433   }
    434   consumer_->OnGetOAuthTokenFailure(
    435       GoogleServiceAuthError(GoogleServiceAuthError::SERVICE_UNAVAILABLE));
    436 }
    437 
    438 void GaiaOAuthFetcher::OnOAuthLoginFetched(
    439     const std::string& data,
    440     const net::URLRequestStatus& status,
    441     int response_code) {
    442   if (status.is_success() && response_code == net::HTTP_OK) {
    443     std::string sid;
    444     std::string lsid;
    445     std::string auth;
    446     ParseOAuthLoginResponse(data, &sid, &lsid, &auth);
    447     if (!sid.empty() && !lsid.empty() && !auth.empty()) {
    448       consumer_->OnOAuthLoginSuccess(sid, lsid, auth);
    449       return;
    450     }
    451   }
    452   // OAuthLogin returns error messages that are identical to ClientLogin,
    453   // so we use GaiaAuthFetcher::GenerateAuthError to parse the response
    454   // instead.
    455   consumer_->OnOAuthLoginFailure(
    456       GaiaAuthFetcher::GenerateOAuthLoginError(data, status));
    457 }
    458 
    459 void GaiaOAuthFetcher::OnOAuthGetAccessTokenFetched(
    460     const std::string& data,
    461     const net::URLRequestStatus& status,
    462     int response_code) {
    463   if (status.is_success() && response_code == net::HTTP_OK) {
    464     VLOG(1) << "OAuth1 access token fetched.";
    465     std::string secret;
    466     std::string token;
    467     ParseOAuthGetAccessTokenResponse(data, &token, &secret);
    468     if (!token.empty() && !secret.empty()) {
    469       consumer_->OnOAuthGetAccessTokenSuccess(token, secret);
    470       if (ShouldAutoFetch(OAUTH2_SERVICE_ACCESS_TOKEN))
    471         StartOAuthWrapBridge(
    472             token, secret, GaiaConstants::kGaiaOAuthDuration, service_scope_);
    473       return;
    474     }
    475   }
    476   consumer_->OnOAuthGetAccessTokenFailure(GenerateAuthError(data, status,
    477                                                             response_code));
    478 }
    479 
    480 void GaiaOAuthFetcher::OnOAuthWrapBridgeFetched(
    481     const std::string& data,
    482     const net::URLRequestStatus& status,
    483     int response_code) {
    484   if (status.is_success() && response_code == net::HTTP_OK) {
    485     VLOG(1) << "OAuth2 access token fetched.";
    486     std::string token;
    487     std::string expires_in;
    488     ParseOAuthWrapBridgeResponse(data, &token, &expires_in);
    489     if (!token.empty() && !expires_in.empty()) {
    490       consumer_->OnOAuthWrapBridgeSuccess(service_scope_, token, expires_in);
    491       if (ShouldAutoFetch(USER_INFO))
    492         StartUserInfo(token);
    493       return;
    494     }
    495   }
    496   consumer_->OnOAuthWrapBridgeFailure(service_scope_,
    497                                       GenerateAuthError(data, status,
    498                                                         response_code));
    499 }
    500 
    501 void GaiaOAuthFetcher::OnOAuthRevokeTokenFetched(
    502     const std::string& data,
    503     const net::URLRequestStatus& status,
    504     int response_code) {
    505   if (status.is_success() && response_code == net::HTTP_OK) {
    506     consumer_->OnOAuthRevokeTokenSuccess();
    507   } else {
    508     LOG(ERROR) << "Token revocation failure " << response_code << ": " << data;
    509     consumer_->OnOAuthRevokeTokenFailure(GenerateAuthError(data, status,
    510                                                            response_code));
    511   }
    512 }
    513 
    514 void GaiaOAuthFetcher::OnUserInfoFetched(
    515     const std::string& data,
    516     const net::URLRequestStatus& status,
    517     int response_code) {
    518   if (status.is_success() && response_code == net::HTTP_OK) {
    519     std::string email;
    520     ParseUserInfoResponse(data, &email);
    521     if (!email.empty()) {
    522       VLOG(1) << "GAIA user info fetched for " << email << ".";
    523       consumer_->OnUserInfoSuccess(email);
    524       return;
    525     }
    526   }
    527   consumer_->OnUserInfoFailure(GenerateAuthError(data, status,
    528                                                  response_code));
    529 }
    530 
    531 void GaiaOAuthFetcher::OnURLFetchComplete(const net::URLFetcher* source) {
    532   // Keep |fetcher_| around to avoid invalidating its |status| (accessed below).
    533   scoped_ptr<net::URLFetcher> current_fetcher(fetcher_.release());
    534   fetch_pending_ = false;
    535   std::string data;
    536   source->GetResponseAsString(&data);
    537   net::URLRequestStatus status = source->GetStatus();
    538   int response_code = source->GetResponseCode();
    539 
    540   switch (request_type_) {
    541     case OAUTH1_LOGIN:
    542       OnOAuthLoginFetched(data, status, response_code);
    543       break;
    544     case OAUTH1_REQUEST_TOKEN:
    545       OnGetOAuthTokenUrlFetched(source->GetCookies(), status, response_code);
    546       break;
    547     case OAUTH1_ALL_ACCESS_TOKEN:
    548       OnOAuthGetAccessTokenFetched(data, status, response_code);
    549       break;
    550     case OAUTH2_SERVICE_ACCESS_TOKEN:
    551       OnOAuthWrapBridgeFetched(data, status, response_code);
    552       break;
    553     case USER_INFO:
    554       OnUserInfoFetched(data, status, response_code);
    555       break;
    556     case OAUTH2_REVOKE_TOKEN:
    557       OnOAuthRevokeTokenFetched(data, status, response_code);
    558       break;
    559   }
    560 }
    561 
    562 bool GaiaOAuthFetcher::ShouldAutoFetch(RequestType fetch_step) {
    563   return fetch_step <= auto_fetch_limit_;
    564 }
    565