| // Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "chrome/browser/net/gaia/gaia_oauth_fetcher.h" |
| |
| #include <string> |
| #include <utility> |
| #include <vector> |
| |
| #include "base/json/json_reader.h" |
| #include "base/strings/string_split.h" |
| #include "base/strings/string_util.h" |
| #include "base/values.h" |
| #include "chrome/browser/net/gaia/gaia_oauth_consumer.h" |
| #include "google_apis/gaia/gaia_auth_fetcher.h" |
| #include "google_apis/gaia/gaia_constants.h" |
| #include "google_apis/gaia/gaia_urls.h" |
| #include "google_apis/gaia/oauth_request_signer.h" |
| #include "grit/chromium_strings.h" |
| #include "net/base/load_flags.h" |
| #include "net/cookies/parsed_cookie.h" |
| #include "net/http/http_status_code.h" |
| #include "net/url_request/url_fetcher.h" |
| #include "net/url_request/url_request_context_getter.h" |
| #include "net/url_request/url_request_status.h" |
| #include "ui/base/l10n/l10n_util.h" |
| |
| static const char kOAuthTokenCookie[] = "oauth_token"; |
| |
| GaiaOAuthFetcher::GaiaOAuthFetcher(GaiaOAuthConsumer* consumer, |
| net::URLRequestContextGetter* getter, |
| const std::string& service_scope) |
| : consumer_(consumer), |
| getter_(getter), |
| service_scope_(service_scope), |
| fetch_pending_(false), |
| auto_fetch_limit_(USER_INFO) {} |
| |
| GaiaOAuthFetcher::~GaiaOAuthFetcher() {} |
| |
| bool GaiaOAuthFetcher::HasPendingFetch() const { |
| return fetch_pending_; |
| } |
| |
| void GaiaOAuthFetcher::CancelRequest() { |
| fetcher_.reset(); |
| fetch_pending_ = false; |
| } |
| |
| // static |
| net::URLFetcher* GaiaOAuthFetcher::CreateGaiaFetcher( |
| net::URLRequestContextGetter* getter, |
| const GURL& gaia_gurl, |
| const std::string& body, |
| const std::string& headers, |
| bool send_cookies, |
| net::URLFetcherDelegate* delegate) { |
| bool empty_body = body.empty(); |
| net::URLFetcher* result = net::URLFetcher::Create( |
| 0, gaia_gurl, |
| empty_body ? net::URLFetcher::GET : net::URLFetcher::POST, |
| delegate); |
| result->SetRequestContext(getter); |
| // Fetchers are sometimes cancelled because a network change was detected, |
| // especially at startup and after sign-in on ChromeOS. Retrying once should |
| // be enough in those cases; let the fetcher retry up to 3 times just in case. |
| // http://crbug.com/163710 |
| result->SetAutomaticallyRetryOnNetworkChanges(3); |
| |
| // The Gaia/OAuth token exchange requests do not require any cookie-based |
| // identification as part of requests. We suppress sending any cookies to |
| // maintain a separation between the user's browsing and Chrome's internal |
| // services. Where such mixing is desired (prelogin, autologin |
| // or chromeos login), it will be done explicitly. |
| if (!send_cookies) |
| result->SetLoadFlags(net::LOAD_DO_NOT_SEND_COOKIES); |
| |
| if (!empty_body) |
| result->SetUploadData("application/x-www-form-urlencoded", body); |
| if (!headers.empty()) |
| result->SetExtraRequestHeaders(headers); |
| |
| return result; |
| } |
| |
| // static |
| GURL GaiaOAuthFetcher::MakeGetOAuthTokenUrl( |
| const std::string& oauth1_login_scope, |
| const std::string& product_name) { |
| return GaiaUrls::GetInstance()->get_oauth_token_url().Resolve( |
| "?scope=" + oauth1_login_scope + |
| "&xoauth_display_name=" + |
| OAuthRequestSigner::Encode(product_name)); |
| } |
| |
| // static |
| std::string GaiaOAuthFetcher::MakeOAuthLoginBody( |
| const char* source, |
| const char* service, |
| const std::string& oauth1_access_token, |
| const std::string& oauth1_access_token_secret) { |
| OAuthRequestSigner::Parameters parameters; |
| parameters["service"] = service; |
| parameters["source"] = source; |
| std::string signed_request; |
| bool is_signed = OAuthRequestSigner::SignURL( |
| GaiaUrls::GetInstance()->oauth1_login_url(), |
| parameters, |
| OAuthRequestSigner::HMAC_SHA1_SIGNATURE, |
| OAuthRequestSigner::POST_METHOD, |
| "anonymous", // oauth_consumer_key |
| "anonymous", // consumer secret |
| oauth1_access_token, // oauth_token |
| oauth1_access_token_secret, // token secret |
| &signed_request); |
| DCHECK(is_signed); |
| return signed_request; |
| } |
| |
| // static |
| std::string GaiaOAuthFetcher::MakeOAuthGetAccessTokenBody( |
| const std::string& oauth1_request_token) { |
| OAuthRequestSigner::Parameters empty_parameters; |
| std::string signed_request; |
| bool is_signed = OAuthRequestSigner::SignURL( |
| GaiaUrls::GetInstance()->oauth_get_access_token_url(), |
| empty_parameters, |
| OAuthRequestSigner::HMAC_SHA1_SIGNATURE, |
| OAuthRequestSigner::POST_METHOD, |
| "anonymous", // oauth_consumer_key |
| "anonymous", // consumer secret |
| oauth1_request_token, // oauth_token |
| "", // token secret |
| &signed_request); |
| DCHECK(is_signed); |
| return signed_request; |
| } |
| |
| // static |
| std::string GaiaOAuthFetcher::MakeOAuthWrapBridgeBody( |
| const std::string& oauth1_access_token, |
| const std::string& oauth1_access_token_secret, |
| const std::string& wrap_token_duration, |
| const std::string& oauth2_scope) { |
| OAuthRequestSigner::Parameters parameters; |
| parameters["wrap_token_duration"] = wrap_token_duration; |
| parameters["wrap_scope"] = oauth2_scope; |
| std::string signed_request; |
| bool is_signed = OAuthRequestSigner::SignURL( |
| GaiaUrls::GetInstance()->oauth_wrap_bridge_url(), |
| parameters, |
| OAuthRequestSigner::HMAC_SHA1_SIGNATURE, |
| OAuthRequestSigner::POST_METHOD, |
| "anonymous", // oauth_consumer_key |
| "anonymous", // consumer secret |
| oauth1_access_token, // oauth_token |
| oauth1_access_token_secret, // token secret |
| &signed_request); |
| DCHECK(is_signed); |
| return signed_request; |
| } |
| |
| // Helper method that extracts tokens from a successful reply. |
| // static |
| void GaiaOAuthFetcher::ParseOAuthLoginResponse( |
| const std::string& data, |
| std::string* sid, |
| std::string* lsid, |
| std::string* auth) { |
| using std::vector; |
| using std::pair; |
| using std::string; |
| vector<pair<string, string> > tokens; |
| base::SplitStringIntoKeyValuePairs(data, '=', '\n', &tokens); |
| for (vector<pair<string, string> >::iterator i = tokens.begin(); |
| i != tokens.end(); ++i) { |
| if (i->first == "SID") { |
| *sid = i->second; |
| } else if (i->first == "LSID") { |
| *lsid = i->second; |
| } else if (i->first == "Auth") { |
| *auth = i->second; |
| } |
| } |
| } |
| |
| // Helper method that extracts tokens from a successful reply. |
| // static |
| void GaiaOAuthFetcher::ParseOAuthGetAccessTokenResponse( |
| const std::string& data, |
| std::string* token, |
| std::string* secret) { |
| using std::vector; |
| using std::pair; |
| using std::string; |
| |
| vector<pair<string, string> > tokens; |
| base::SplitStringIntoKeyValuePairs(data, '=', '&', &tokens); |
| for (vector<pair<string, string> >::iterator i = tokens.begin(); |
| i != tokens.end(); ++i) { |
| if (i->first == "oauth_token") { |
| std::string decoded; |
| if (OAuthRequestSigner::Decode(i->second, &decoded)) |
| token->assign(decoded); |
| } else if (i->first == "oauth_token_secret") { |
| std::string decoded; |
| if (OAuthRequestSigner::Decode(i->second, &decoded)) |
| secret->assign(decoded); |
| } |
| } |
| } |
| |
| // Helper method that extracts tokens from a successful reply. |
| // static |
| void GaiaOAuthFetcher::ParseOAuthWrapBridgeResponse(const std::string& data, |
| std::string* token, |
| std::string* expires_in) { |
| using std::vector; |
| using std::pair; |
| using std::string; |
| |
| vector<pair<string, string> > tokens; |
| base::SplitStringIntoKeyValuePairs(data, '=', '&', &tokens); |
| for (vector<pair<string, string> >::iterator i = tokens.begin(); |
| i != tokens.end(); ++i) { |
| if (i->first == "wrap_access_token") { |
| std::string decoded; |
| if (OAuthRequestSigner::Decode(i->second, &decoded)) |
| token->assign(decoded); |
| } else if (i->first == "wrap_access_token_expires_in") { |
| std::string decoded; |
| if (OAuthRequestSigner::Decode(i->second, &decoded)) |
| expires_in->assign(decoded); |
| } |
| } |
| } |
| |
| // Helper method that extracts tokens from a successful reply. |
| // static |
| void GaiaOAuthFetcher::ParseUserInfoResponse(const std::string& data, |
| std::string* email_result) { |
| scoped_ptr<base::Value> value(base::JSONReader::Read(data)); |
| if (value->GetType() == base::Value::TYPE_DICTIONARY) { |
| Value* email_value; |
| DictionaryValue* dict = static_cast<DictionaryValue*>(value.get()); |
| if (dict->Get("email", &email_value)) { |
| if (email_value->GetType() == base::Value::TYPE_STRING) { |
| email_value->GetAsString(email_result); |
| } |
| } |
| } |
| } |
| |
| void GaiaOAuthFetcher::StartOAuthLogin( |
| const char* source, |
| const char* service, |
| const std::string& oauth1_access_token, |
| const std::string& oauth1_access_token_secret) { |
| DCHECK(!fetch_pending_) << "Tried to fetch two things at once!"; |
| |
| request_type_ = OAUTH1_LOGIN; |
| // Must outlive fetcher_. |
| request_body_ = MakeOAuthLoginBody(source, service, oauth1_access_token, |
| oauth1_access_token_secret); |
| request_headers_ = ""; |
| GURL url(GaiaUrls::GetInstance()->oauth1_login_url()); |
| fetcher_.reset(CreateGaiaFetcher(getter_, url, request_body_, |
| request_headers_, false, this)); |
| fetch_pending_ = true; |
| fetcher_->Start(); |
| } |
| |
| void GaiaOAuthFetcher::StartGetOAuthTokenRequest() { |
| DCHECK(!fetch_pending_) << "Tried to fetch two things at once!"; |
| |
| request_type_ = OAUTH1_REQUEST_TOKEN; |
| // Must outlive fetcher_. |
| request_body_ = ""; |
| request_headers_ = ""; |
| fetcher_.reset(CreateGaiaFetcher(getter_, |
| MakeGetOAuthTokenUrl(GaiaUrls::GetInstance()->oauth1_login_scope(), |
| l10n_util::GetStringUTF8(IDS_PRODUCT_NAME)), |
| std::string(), |
| std::string(), |
| true, // send_cookies |
| this)); |
| fetch_pending_ = true; |
| fetcher_->Start(); |
| } |
| |
| void GaiaOAuthFetcher::StartOAuthGetAccessToken( |
| const std::string& oauth1_request_token) { |
| DCHECK(!fetch_pending_) << "Tried to fetch two things at once!"; |
| |
| request_type_ = OAUTH1_ALL_ACCESS_TOKEN; |
| // Must outlive fetcher_. |
| request_body_ = MakeOAuthGetAccessTokenBody(oauth1_request_token); |
| request_headers_ = ""; |
| GURL url(GaiaUrls::GetInstance()->oauth_get_access_token_url()); |
| fetcher_.reset(CreateGaiaFetcher(getter_, url, request_body_, |
| request_headers_, false, this)); |
| fetch_pending_ = true; |
| fetcher_->Start(); |
| } |
| |
| void GaiaOAuthFetcher::StartOAuthWrapBridge( |
| const std::string& oauth1_access_token, |
| const std::string& oauth1_access_token_secret, |
| const std::string& wrap_token_duration, |
| const std::string& service_scope) { |
| DCHECK(!fetch_pending_) << "Tried to fetch two things at once!"; |
| |
| request_type_ = OAUTH2_SERVICE_ACCESS_TOKEN; |
| VLOG(1) << "Starting OAuthWrapBridge for: " << service_scope; |
| std::string combined_scope = service_scope + " " + |
| GaiaUrls::GetInstance()->oauth_wrap_bridge_user_info_scope(); |
| service_scope_ = service_scope; |
| |
| // Must outlive fetcher_. |
| request_body_ = MakeOAuthWrapBridgeBody( |
| oauth1_access_token, |
| oauth1_access_token_secret, |
| wrap_token_duration, |
| combined_scope); |
| |
| request_headers_ = ""; |
| GURL url(GaiaUrls::GetInstance()->oauth_wrap_bridge_url()); |
| fetcher_.reset(CreateGaiaFetcher(getter_, url, request_body_, |
| request_headers_, false, this)); |
| fetch_pending_ = true; |
| fetcher_->Start(); |
| } |
| |
| void GaiaOAuthFetcher::StartUserInfo(const std::string& oauth2_access_token) { |
| DCHECK(!fetch_pending_) << "Tried to fetch two things at once!"; |
| |
| request_type_ = USER_INFO; |
| // Must outlive fetcher_. |
| request_body_ = ""; |
| request_headers_ = "Authorization: OAuth " + oauth2_access_token; |
| GURL url(GaiaUrls::GetInstance()->oauth_user_info_url()); |
| fetcher_.reset(CreateGaiaFetcher(getter_, url, request_body_, |
| request_headers_, false, this)); |
| fetch_pending_ = true; |
| fetcher_->Start(); |
| } |
| |
| void GaiaOAuthFetcher::StartOAuthRevokeAccessToken(const std::string& token, |
| const std::string& secret) { |
| DCHECK(!fetch_pending_) << "Tried to fetch two things at once!"; |
| |
| request_type_ = OAUTH2_REVOKE_TOKEN; |
| // Must outlive fetcher_. |
| request_body_ = ""; |
| |
| OAuthRequestSigner::Parameters empty_parameters; |
| std::string auth_header; |
| bool is_signed = OAuthRequestSigner::SignAuthHeader( |
| GaiaUrls::GetInstance()->oauth_revoke_token_url(), |
| empty_parameters, |
| OAuthRequestSigner::HMAC_SHA1_SIGNATURE, |
| OAuthRequestSigner::GET_METHOD, |
| "anonymous", |
| "anonymous", |
| token, |
| secret, |
| &auth_header); |
| DCHECK(is_signed); |
| request_headers_ = "Authorization: " + auth_header; |
| GURL url(GaiaUrls::GetInstance()->oauth_revoke_token_url()); |
| fetcher_.reset(CreateGaiaFetcher(getter_, url, request_body_, |
| request_headers_, false, this)); |
| fetch_pending_ = true; |
| fetcher_->Start(); |
| } |
| |
| void GaiaOAuthFetcher::StartOAuthRevokeWrapToken(const std::string& token) { |
| DCHECK(!fetch_pending_) << "Tried to fetch two things at once!"; |
| |
| request_type_ = OAUTH2_REVOKE_TOKEN; |
| // Must outlive fetcher_. |
| request_body_ = ""; |
| |
| request_headers_ = "Authorization: Bearer " + token; |
| GURL url(GaiaUrls::GetInstance()->oauth_revoke_token_url()); |
| fetcher_.reset(CreateGaiaFetcher(getter_, url, request_body_, |
| request_headers_, false, this)); |
| fetch_pending_ = true; |
| fetcher_->Start(); |
| } |
| |
| // static |
| GoogleServiceAuthError GaiaOAuthFetcher::GenerateAuthError( |
| const std::string& data, |
| const net::URLRequestStatus& status, |
| int response_code) { |
| if (!status.is_success()) { |
| if (status.status() == net::URLRequestStatus::CANCELED) { |
| return GoogleServiceAuthError(GoogleServiceAuthError::REQUEST_CANCELED); |
| } else { |
| LOG(WARNING) << "Could not reach Google Accounts servers: errno " |
| << status.error(); |
| return GoogleServiceAuthError::FromConnectionError(status.error()); |
| } |
| } else { |
| LOG(WARNING) << "Unrecognized response from Google Accounts servers " |
| << "code " << response_code << " data " << data; |
| return GoogleServiceAuthError( |
| GoogleServiceAuthError::SERVICE_UNAVAILABLE); |
| } |
| |
| NOTREACHED(); |
| return GoogleServiceAuthError(GoogleServiceAuthError::SERVICE_UNAVAILABLE); |
| } |
| |
| void GaiaOAuthFetcher::OnGetOAuthTokenUrlFetched( |
| const net::ResponseCookies& cookies, |
| const net::URLRequestStatus& status, |
| int response_code) { |
| if (status.is_success() && response_code == net::HTTP_OK) { |
| for (net::ResponseCookies::const_iterator iter = cookies.begin(); |
| iter != cookies.end(); ++iter) { |
| net::ParsedCookie cookie(*iter); |
| if (cookie.Name() == kOAuthTokenCookie) { |
| std::string token = cookie.Value(); |
| consumer_->OnGetOAuthTokenSuccess(token); |
| if (ShouldAutoFetch(OAUTH1_ALL_ACCESS_TOKEN)) |
| StartOAuthGetAccessToken(token); |
| return; |
| } |
| } |
| } |
| consumer_->OnGetOAuthTokenFailure( |
| GoogleServiceAuthError(GoogleServiceAuthError::SERVICE_UNAVAILABLE)); |
| } |
| |
| void GaiaOAuthFetcher::OnOAuthLoginFetched( |
| const std::string& data, |
| const net::URLRequestStatus& status, |
| int response_code) { |
| if (status.is_success() && response_code == net::HTTP_OK) { |
| std::string sid; |
| std::string lsid; |
| std::string auth; |
| ParseOAuthLoginResponse(data, &sid, &lsid, &auth); |
| if (!sid.empty() && !lsid.empty() && !auth.empty()) { |
| consumer_->OnOAuthLoginSuccess(sid, lsid, auth); |
| return; |
| } |
| } |
| // OAuthLogin returns error messages that are identical to ClientLogin, |
| // so we use GaiaAuthFetcher::GenerateAuthError to parse the response |
| // instead. |
| consumer_->OnOAuthLoginFailure( |
| GaiaAuthFetcher::GenerateOAuthLoginError(data, status)); |
| } |
| |
| void GaiaOAuthFetcher::OnOAuthGetAccessTokenFetched( |
| const std::string& data, |
| const net::URLRequestStatus& status, |
| int response_code) { |
| if (status.is_success() && response_code == net::HTTP_OK) { |
| VLOG(1) << "OAuth1 access token fetched."; |
| std::string secret; |
| std::string token; |
| ParseOAuthGetAccessTokenResponse(data, &token, &secret); |
| if (!token.empty() && !secret.empty()) { |
| consumer_->OnOAuthGetAccessTokenSuccess(token, secret); |
| if (ShouldAutoFetch(OAUTH2_SERVICE_ACCESS_TOKEN)) |
| StartOAuthWrapBridge( |
| token, secret, GaiaConstants::kGaiaOAuthDuration, service_scope_); |
| return; |
| } |
| } |
| consumer_->OnOAuthGetAccessTokenFailure(GenerateAuthError(data, status, |
| response_code)); |
| } |
| |
| void GaiaOAuthFetcher::OnOAuthWrapBridgeFetched( |
| const std::string& data, |
| const net::URLRequestStatus& status, |
| int response_code) { |
| if (status.is_success() && response_code == net::HTTP_OK) { |
| VLOG(1) << "OAuth2 access token fetched."; |
| std::string token; |
| std::string expires_in; |
| ParseOAuthWrapBridgeResponse(data, &token, &expires_in); |
| if (!token.empty() && !expires_in.empty()) { |
| consumer_->OnOAuthWrapBridgeSuccess(service_scope_, token, expires_in); |
| if (ShouldAutoFetch(USER_INFO)) |
| StartUserInfo(token); |
| return; |
| } |
| } |
| consumer_->OnOAuthWrapBridgeFailure(service_scope_, |
| GenerateAuthError(data, status, |
| response_code)); |
| } |
| |
| void GaiaOAuthFetcher::OnOAuthRevokeTokenFetched( |
| const std::string& data, |
| const net::URLRequestStatus& status, |
| int response_code) { |
| if (status.is_success() && response_code == net::HTTP_OK) { |
| consumer_->OnOAuthRevokeTokenSuccess(); |
| } else { |
| LOG(ERROR) << "Token revocation failure " << response_code << ": " << data; |
| consumer_->OnOAuthRevokeTokenFailure(GenerateAuthError(data, status, |
| response_code)); |
| } |
| } |
| |
| void GaiaOAuthFetcher::OnUserInfoFetched( |
| const std::string& data, |
| const net::URLRequestStatus& status, |
| int response_code) { |
| if (status.is_success() && response_code == net::HTTP_OK) { |
| std::string email; |
| ParseUserInfoResponse(data, &email); |
| if (!email.empty()) { |
| VLOG(1) << "GAIA user info fetched for " << email << "."; |
| consumer_->OnUserInfoSuccess(email); |
| return; |
| } |
| } |
| consumer_->OnUserInfoFailure(GenerateAuthError(data, status, |
| response_code)); |
| } |
| |
| void GaiaOAuthFetcher::OnURLFetchComplete(const net::URLFetcher* source) { |
| // Keep |fetcher_| around to avoid invalidating its |status| (accessed below). |
| scoped_ptr<net::URLFetcher> current_fetcher(fetcher_.release()); |
| fetch_pending_ = false; |
| std::string data; |
| source->GetResponseAsString(&data); |
| net::URLRequestStatus status = source->GetStatus(); |
| int response_code = source->GetResponseCode(); |
| |
| switch (request_type_) { |
| case OAUTH1_LOGIN: |
| OnOAuthLoginFetched(data, status, response_code); |
| break; |
| case OAUTH1_REQUEST_TOKEN: |
| OnGetOAuthTokenUrlFetched(source->GetCookies(), status, response_code); |
| break; |
| case OAUTH1_ALL_ACCESS_TOKEN: |
| OnOAuthGetAccessTokenFetched(data, status, response_code); |
| break; |
| case OAUTH2_SERVICE_ACCESS_TOKEN: |
| OnOAuthWrapBridgeFetched(data, status, response_code); |
| break; |
| case USER_INFO: |
| OnUserInfoFetched(data, status, response_code); |
| break; |
| case OAUTH2_REVOKE_TOKEN: |
| OnOAuthRevokeTokenFetched(data, status, response_code); |
| break; |
| } |
| } |
| |
| bool GaiaOAuthFetcher::ShouldAutoFetch(RequestType fetch_step) { |
| return fetch_step <= auto_fetch_limit_; |
| } |