// 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 #include #include #include "base/json/json_reader.h" #include "base/string_split.h" #include "base/string_util.h" #include "base/values.h" #include "chrome/browser/net/gaia/gaia_oauth_consumer.h" #include "chrome/common/net/url_util.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 GURL(GaiaUrls::GetInstance()->get_oauth_token_url() + "?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( GURL(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( GURL(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( GURL(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 > tokens; base::SplitStringIntoKeyValuePairs(data, '=', '\n', &tokens); for (vector >::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 > tokens; base::SplitStringIntoKeyValuePairs(data, '=', '&', &tokens); for (vector >::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 > tokens; base::SplitStringIntoKeyValuePairs(data, '=', '&', &tokens); for (vector >::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 value(base::JSONReader::Read(data)); if (value->GetType() == base::Value::TYPE_DICTIONARY) { Value* email_value; DictionaryValue* dict = static_cast(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( GURL(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 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_; }