summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--chrome/chrome_common.gypi3
-rw-r--r--chrome/chrome_tests.gypi1
-rw-r--r--chrome/common/net/gaia/gaia_auth_consumer.h5
-rw-r--r--chrome/common/net/gaia/gaia_auth_fetcher.cc227
-rw-r--r--chrome/common/net/gaia/gaia_auth_fetcher.h61
-rw-r--r--chrome/common/net/gaia/gaia_auth_fetcher_unittest.cc224
-rw-r--r--chrome/common/net/gaia/gaia_urls.cc35
-rw-r--r--chrome/common/net/gaia/gaia_urls.h10
-rw-r--r--chrome/common/net/gaia/oauth2_access_token_consumer.h23
-rw-r--r--chrome/common/net/gaia/oauth2_access_token_fetcher.cc185
-rw-r--r--chrome/common/net/gaia/oauth2_access_token_fetcher.h94
-rw-r--r--chrome/common/net/gaia/oauth2_access_token_fetcher_unittest.cc172
12 files changed, 1036 insertions, 4 deletions
diff --git a/chrome/chrome_common.gypi b/chrome/chrome_common.gypi
index 6ecfc50..0111b2d 100644
--- a/chrome/chrome_common.gypi
+++ b/chrome/chrome_common.gypi
@@ -375,6 +375,9 @@
'common/net/gaia/google_service_auth_error.h',
'common/net/gaia/oauth_request_signer.cc',
'common/net/gaia/oauth_request_signer.h',
+ 'common/net/gaia/oauth2_access_token_consumer.h',
+ 'common/net/gaia/oauth2_access_token_fetcher.cc',
+ 'common/net/gaia/oauth2_access_token_fetcher.h',
'common/net/x509_certificate_model.cc',
'common/net/x509_certificate_model_nss.cc',
'common/net/x509_certificate_model_openssl.cc',
diff --git a/chrome/chrome_tests.gypi b/chrome/chrome_tests.gypi
index 26fbe2f..a1a4179 100644
--- a/chrome/chrome_tests.gypi
+++ b/chrome/chrome_tests.gypi
@@ -1966,6 +1966,7 @@
'common/net/gaia/gaia_oauth_client_unittest.cc',
'common/net/gaia/google_service_auth_error_unittest.cc',
'common/net/gaia/oauth_request_signer_unittest.cc',
+ 'common/net/gaia/oauth2_access_token_fetcher_unittest.cc',
'common/random_unittest.cc',
'common/service_process_util_unittest.cc',
'common/string_ordinal_unittest.cc',
diff --git a/chrome/common/net/gaia/gaia_auth_consumer.h b/chrome/common/net/gaia/gaia_auth_consumer.h
index fdca0ab..915ba40 100644
--- a/chrome/common/net/gaia/gaia_auth_consumer.h
+++ b/chrome/common/net/gaia/gaia_auth_consumer.h
@@ -42,6 +42,11 @@ class GaiaAuthConsumer {
virtual void OnIssueAuthTokenFailure(const std::string& service,
const GoogleServiceAuthError& error) {}
+ virtual void OnOAuthLoginTokenSuccess(const std::string& refresh_token,
+ const std::string& access_token,
+ int expires_in_secs) {}
+ virtual void OnOAuthLoginTokenFailure(const GoogleServiceAuthError& error) {}
+
virtual void OnGetUserInfoSuccess(const std::string& key,
const std::string& value) {}
virtual void OnGetUserInfoKeyNotFound(const std::string& key) {}
diff --git a/chrome/common/net/gaia/gaia_auth_fetcher.cc b/chrome/common/net/gaia/gaia_auth_fetcher.cc
index 0a861e6..f3f86d3 100644
--- a/chrome/common/net/gaia/gaia_auth_fetcher.cc
+++ b/chrome/common/net/gaia/gaia_auth_fetcher.cc
@@ -4,13 +4,16 @@
#include "chrome/common/net/gaia/gaia_auth_fetcher.h"
+#include <algorithm>
#include <string>
#include <utility>
#include <vector>
+#include "base/json/json_reader.h"
#include "base/string_split.h"
#include "base/string_util.h"
#include "base/stringprintf.h"
+#include "base/values.h"
#include "chrome/common/net/gaia/gaia_auth_consumer.h"
#include "chrome/common/net/gaia/gaia_constants.h"
#include "chrome/common/net/gaia/gaia_urls.h"
@@ -22,6 +25,13 @@
#include "net/url_request/url_request_context_getter.h"
#include "net/url_request/url_request_status.h"
+namespace {
+static bool CookiePartsContains(const std::vector<std::string>& parts,
+ const char* part) {
+ return std::find(parts.begin(), parts.end(), part) != parts.end();
+}
+} // namespace
+
// TODO(chron): Add sourceless version of this formatter.
// static
const char GaiaAuthFetcher::kClientLoginFormat[] =
@@ -48,6 +58,16 @@ const char GaiaAuthFetcher::kIssueAuthTokenFormat[] =
"service=%s&"
"Session=%s";
// static
+const char GaiaAuthFetcher::kClientLoginToOAuth2BodyFormat[] =
+ "scope=%s&client_id=%s";
+// static
+const char GaiaAuthFetcher::kOAuth2CodeToTokenPairBodyFormat[] =
+ "scope=%s&"
+ "grant_type=authorization_code&"
+ "client_id=%s&"
+ "client_secret=%s&"
+ "code=%s";
+// static
const char GaiaAuthFetcher::kGetUserInfoFormat[] =
"LSID=%s";
// static
@@ -100,6 +120,27 @@ const char GaiaAuthFetcher::kAccountTypeGoogle[] =
// static
const char GaiaAuthFetcher::kSecondFactor[] = "Info=InvalidSecondFactor";
+// static
+const char GaiaAuthFetcher::kAuthHeaderFormat[] =
+ "Authorization: GoogleLogin auth=%s";
+// static
+const char GaiaAuthFetcher::kClientLoginToOAuth2CookiePartSecure[] = "Secure";
+// static
+const char GaiaAuthFetcher::kClientLoginToOAuth2CookiePartHttpOnly[] =
+ "HttpOnly";
+// static
+const char GaiaAuthFetcher::kClientLoginToOAuth2CookiePartCodePrefix[] =
+ "oauth_code=";
+// static
+const int GaiaAuthFetcher::kClientLoginToOAuth2CookiePartCodePrefixLength =
+ arraysize(GaiaAuthFetcher::kClientLoginToOAuth2CookiePartCodePrefix) - 1;
+// static
+const char GaiaAuthFetcher::kOAuth2RefreshTokenKey[] = "refresh_token";
+// static
+const char GaiaAuthFetcher::kOAuth2AccessTokenKey[] = "access_token";
+// static
+const char GaiaAuthFetcher::kOAuth2ExpiresInKey[] = "expires_in";
+
GaiaAuthFetcher::GaiaAuthFetcher(GaiaAuthConsumer* consumer,
const std::string& source,
net::URLRequestContextGetter* getter)
@@ -108,6 +149,9 @@ GaiaAuthFetcher::GaiaAuthFetcher(GaiaAuthConsumer* consumer,
source_(source),
client_login_gurl_(GaiaUrls::GetInstance()->client_login_url()),
issue_auth_token_gurl_(GaiaUrls::GetInstance()->issue_auth_token_url()),
+ client_login_to_oauth2_gurl_(
+ GaiaUrls::GetInstance()->client_login_to_oauth2_url()),
+ oauth2_token_gurl_(GaiaUrls::GetInstance()->oauth2_token_url()),
get_user_info_gurl_(GaiaUrls::GetInstance()->get_user_info_url()),
token_auth_gurl_(GaiaUrls::GetInstance()->token_auth_url()),
merge_session_gurl_(GaiaUrls::GetInstance()->merge_session_url()),
@@ -128,8 +172,9 @@ void GaiaAuthFetcher::CancelRequest() {
content::URLFetcher* GaiaAuthFetcher::CreateGaiaFetcher(
net::URLRequestContextGetter* getter,
const std::string& body,
+ const std::string& headers,
const GURL& gaia_gurl,
- bool send_cookies,
+ bool use_cookies,
content::URLFetcherDelegate* delegate) {
content::URLFetcher* to_return = content::URLFetcher::Create(
0, gaia_gurl, content::URLFetcher::POST, delegate);
@@ -141,8 +186,12 @@ content::URLFetcher* GaiaAuthFetcher::CreateGaiaFetcher(
// maintain a separation between the user's browsing and Chrome's internal
// services. Where such mixing is desired (MergeSession), it will be done
// explicitly.
- if (!send_cookies)
- to_return->SetLoadFlags(net::LOAD_DO_NOT_SEND_COOKIES);
+ if (!use_cookies) {
+ to_return->SetLoadFlags(net::LOAD_DO_NOT_SEND_COOKIES |
+ net::LOAD_DO_NOT_SAVE_COOKIES);
+ }
+ if (!headers.empty())
+ to_return->SetExtraRequestHeaders(headers);
return to_return;
}
@@ -209,6 +258,34 @@ std::string GaiaAuthFetcher::MakeIssueAuthTokenBody(
}
// static
+std::string GaiaAuthFetcher::MakeGetAuthCodeBody() {
+ std::string encoded_scope = net::EscapeUrlEncodedData(
+ GaiaUrls::GetInstance()->oauth1_login_scope(), true);
+ std::string encoded_client_id = net::EscapeUrlEncodedData(
+ GaiaUrls::GetInstance()->oauth2_chrome_client_id(), true);
+ return StringPrintf(kClientLoginToOAuth2BodyFormat,
+ encoded_scope.c_str(),
+ encoded_client_id.c_str());
+}
+
+// static
+std::string GaiaAuthFetcher::MakeGetTokenPairBody(
+ const std::string& auth_code) {
+ std::string encoded_scope = net::EscapeUrlEncodedData(
+ GaiaUrls::GetInstance()->oauth1_login_scope(), true);
+ std::string encoded_client_id = net::EscapeUrlEncodedData(
+ GaiaUrls::GetInstance()->oauth2_chrome_client_id(), true);
+ std::string encoded_client_secret = net::EscapeUrlEncodedData(
+ GaiaUrls::GetInstance()->oauth2_chrome_client_secret(), true);
+ std::string encoded_auth_code = net::EscapeUrlEncodedData(auth_code, true);
+ return StringPrintf(kOAuth2CodeToTokenPairBodyFormat,
+ encoded_scope.c_str(),
+ encoded_client_id.c_str(),
+ encoded_client_secret.c_str(),
+ encoded_auth_code.c_str());
+}
+
+// static
std::string GaiaAuthFetcher::MakeGetUserInfoBody(const std::string& lsid) {
std::string encoded_lsid = net::EscapeUrlEncodedData(lsid, true);
return base::StringPrintf(kGetUserInfoFormat, encoded_lsid.c_str());
@@ -243,6 +320,12 @@ std::string GaiaAuthFetcher::MakeMergeSessionBody(
encoded_source.c_str());
}
+// static
+std::string GaiaAuthFetcher::MakeGetAuthCodeHeader(
+ const std::string& auth_token) {
+ return StringPrintf(kAuthHeaderFormat, auth_token.c_str());
+}
+
// Helper method that extracts tokens from a successful reply.
// static
void GaiaAuthFetcher::ParseClientLoginResponse(const std::string& data,
@@ -293,6 +376,72 @@ void GaiaAuthFetcher::ParseClientLoginFailure(const std::string& data,
}
}
+// static
+bool GaiaAuthFetcher::ParseClientLoginToOAuth2Response(
+ const net::ResponseCookies& cookies,
+ std::string* auth_code) {
+ DCHECK(auth_code);
+ net::ResponseCookies::const_iterator iter;
+ for (iter = cookies.begin(); iter != cookies.end(); ++iter) {
+ if (ParseClientLoginToOAuth2Cookie(*iter, auth_code))
+ return true;
+ }
+ return false;
+}
+
+// static
+bool GaiaAuthFetcher::ParseOAuth2TokenPairResponse(const std::string& data,
+ std::string* refresh_token,
+ std::string* access_token,
+ int* expires_in_secs) {
+ DCHECK(refresh_token);
+ DCHECK(access_token);
+ base::JSONReader reader;
+ scoped_ptr<base::Value> value(reader.Read(data, false));
+ if (!value.get() || value->GetType() != base::Value::TYPE_DICTIONARY)
+ return false;
+
+ DictionaryValue* dict = static_cast<DictionaryValue*>(value.get());
+ std::string rt;
+ std::string at;
+ int exp;
+
+ if (!dict->GetStringWithoutPathExpansion(kOAuth2RefreshTokenKey, &rt) ||
+ !dict->GetStringWithoutPathExpansion(kOAuth2AccessTokenKey, &at) ||
+ !dict->GetIntegerWithoutPathExpansion(kOAuth2ExpiresInKey, &exp)) {
+ return false;
+ }
+
+ refresh_token->assign(rt);
+ access_token->assign(at);
+ *expires_in_secs = exp;
+ return true;
+}
+
+// static
+bool GaiaAuthFetcher::ParseClientLoginToOAuth2Cookie(const std::string& cookie,
+ std::string* auth_code) {
+ std::vector<std::string> parts;
+ base::SplitString(cookie, ';', &parts);
+ // Per documentation, the cookie should have Secure and HttpOnly.
+ if (!CookiePartsContains(parts, kClientLoginToOAuth2CookiePartSecure) ||
+ !CookiePartsContains(parts, kClientLoginToOAuth2CookiePartHttpOnly)) {
+ return false;
+ }
+
+ std::vector<std::string>::const_iterator iter;
+ for (iter = parts.begin(); iter != parts.end(); ++iter) {
+ const std::string& part = *iter;
+ if (StartsWithASCII(
+ part, kClientLoginToOAuth2CookiePartCodePrefix, false)) {
+ auth_code->assign(part.substr(
+ kClientLoginToOAuth2CookiePartCodePrefixLength));
+ return true;
+ }
+ }
+ return false;
+}
+
void GaiaAuthFetcher::StartClientLogin(
const std::string& username,
const std::string& password,
@@ -317,6 +466,7 @@ void GaiaAuthFetcher::StartClientLogin(
allow_hosted_accounts);
fetcher_.reset(CreateGaiaFetcher(getter_,
request_body_,
+ "",
client_login_gurl_,
false,
this));
@@ -334,6 +484,7 @@ void GaiaAuthFetcher::StartIssueAuthToken(const std::string& sid,
request_body_ = MakeIssueAuthTokenBody(sid, lsid, service);
fetcher_.reset(CreateGaiaFetcher(getter_,
request_body_,
+ "",
issue_auth_token_gurl_,
false,
this));
@@ -341,6 +492,22 @@ void GaiaAuthFetcher::StartIssueAuthToken(const std::string& sid,
fetcher_->Start();
}
+void GaiaAuthFetcher::StartOAuthLoginTokenFetch(
+ const std::string& auth_token) {
+ DCHECK(!fetch_pending_) << "Tried to fetch two things at once!";
+
+ DVLOG(1) << "Starting OAuth login token fetch";
+ request_body_ = MakeGetAuthCodeBody();
+ fetcher_.reset(CreateGaiaFetcher(getter_,
+ request_body_,
+ MakeGetAuthCodeHeader(auth_token),
+ client_login_to_oauth2_gurl_,
+ false,
+ this));
+ fetch_pending_ = true;
+ fetcher_->Start();
+}
+
void GaiaAuthFetcher::StartGetUserInfo(const std::string& lsid,
const std::string& info_key) {
DCHECK(!fetch_pending_) << "Tried to fetch two things at once!";
@@ -349,6 +516,7 @@ void GaiaAuthFetcher::StartGetUserInfo(const std::string& lsid,
request_body_ = MakeGetUserInfoBody(lsid);
fetcher_.reset(CreateGaiaFetcher(getter_,
request_body_,
+ "",
get_user_info_gurl_,
false,
this));
@@ -369,6 +537,7 @@ void GaiaAuthFetcher::StartTokenAuth(const std::string& auth_token) {
request_body_ = MakeTokenAuthBody(auth_token, continue_url, source_);
fetcher_.reset(CreateGaiaFetcher(getter_,
request_body_,
+ "",
token_auth_gurl_,
false,
this));
@@ -393,6 +562,7 @@ void GaiaAuthFetcher::StartMergeSession(const std::string& auth_token) {
request_body_ = MakeMergeSessionBody(auth_token, continue_url, source_);
fetcher_.reset(CreateGaiaFetcher(getter_,
request_body_,
+ "",
merge_session_gurl_,
true,
this));
@@ -536,6 +706,52 @@ void GaiaAuthFetcher::OnIssueAuthTokenFetched(
}
}
+void GaiaAuthFetcher::OnClientLoginToOAuth2Fetched(
+ const std::string& data,
+ const net::ResponseCookies& cookies,
+ const net::URLRequestStatus& status,
+ int response_code) {
+ if (status.is_success() && response_code == RC_REQUEST_OK) {
+ std::string auth_code;
+ ParseClientLoginToOAuth2Response(cookies, &auth_code);
+ StartOAuth2TokenPairFetch(auth_code);
+ } else {
+ consumer_->OnOAuthLoginTokenFailure(GenerateAuthError(data, status));
+ }
+}
+
+void GaiaAuthFetcher::StartOAuth2TokenPairFetch(const std::string& auth_code) {
+ DCHECK(!fetch_pending_) << "Tried to fetch two things at once!";
+
+ DVLOG(1) << "Starting OAuth token pair fetch";
+ request_body_ = MakeGetTokenPairBody(auth_code);
+ fetcher_.reset(CreateGaiaFetcher(getter_,
+ request_body_,
+ "",
+ oauth2_token_gurl_,
+ false,
+ this));
+ fetch_pending_ = true;
+ fetcher_->Start();
+}
+
+void GaiaAuthFetcher::OnOAuth2TokenPairFetched(
+ const std::string& data,
+ const net::URLRequestStatus& status,
+ int response_code) {
+ if (status.is_success() && response_code == RC_REQUEST_OK) {
+ std::string refresh_token;
+ std::string access_token;
+ int expires_in_secs;
+ ParseOAuth2TokenPairResponse(
+ data, &refresh_token, &access_token, &expires_in_secs);
+ consumer_->OnOAuthLoginTokenSuccess(
+ refresh_token, access_token, expires_in_secs);
+ } else {
+ consumer_->OnOAuthLoginTokenFailure(GenerateAuthError(data, status));
+ }
+}
+
void GaiaAuthFetcher::OnGetUserInfoFetched(
const std::string& data,
const net::URLRequestStatus& status,
@@ -591,6 +807,11 @@ void GaiaAuthFetcher::OnURLFetchComplete(const content::URLFetcher* source) {
OnClientLoginFetched(data, status, response_code);
} else if (url == issue_auth_token_gurl_) {
OnIssueAuthTokenFetched(data, status, response_code);
+ } else if (url == client_login_to_oauth2_gurl_) {
+ OnClientLoginToOAuth2Fetched(
+ data, source->GetCookies(), status, response_code);
+ } else if (url == oauth2_token_gurl_) {
+ OnOAuth2TokenPairFetched(data, status, response_code);
} else if (url == get_user_info_gurl_) {
OnGetUserInfoFetched(data, status, response_code);
} else if (url == token_auth_gurl_) {
diff --git a/chrome/common/net/gaia/gaia_auth_fetcher.h b/chrome/common/net/gaia/gaia_auth_fetcher.h
index 646ed95..fa4d3cc 100644
--- a/chrome/common/net/gaia/gaia_auth_fetcher.h
+++ b/chrome/common/net/gaia/gaia_auth_fetcher.h
@@ -7,6 +7,7 @@
#pragma once
#include <string>
+#include <vector>
#include "base/gtest_prod_util.h"
#include "base/memory/scoped_ptr.h"
@@ -29,6 +30,7 @@ class GaiaAuthFetcherTest;
namespace net {
class URLRequestContextGetter;
class URLRequestStatus;
+typedef std::vector<std::string> ResponseCookies;
}
class GaiaAuthFetcher : public content::URLFetcherDelegate {
@@ -66,6 +68,12 @@ class GaiaAuthFetcher : public content::URLFetcherDelegate {
const std::string& lsid,
const char* const service);
+ // Start fetching OAuth login scoped token from the given ClientLogin token
+ // for "lso" service.
+ // Either OnOAuthLoginTokenSuccess or OnOAuthLoginTokenFailure method will be
+ // called on the consumer with results.
+ void StartOAuthLoginTokenFetch(const std::string& auth_token);
+
// Start a request to get a particular key from user info.
// GaiaAuthConsumer will be called back on the same thread when
// results come back.
@@ -109,6 +117,10 @@ class GaiaAuthFetcher : public content::URLFetcherDelegate {
static const char kClientLoginCaptchaFormat[];
// The format of the POST body for IssueAuthToken.
static const char kIssueAuthTokenFormat[];
+ // The format of the POST body to get OAuth2 auth code from auth token.
+ static const char kClientLoginToOAuth2BodyFormat[];
+ // The format of the POST body to get OAuth2 token pair from auth code.
+ static const char kOAuth2CodeToTokenPairBodyFormat[];
// The format of the POST body for GetUserInfo.
static const char kGetUserInfoFormat[];
// The format of the POST body for TokenAuth.
@@ -132,6 +144,16 @@ class GaiaAuthFetcher : public content::URLFetcherDelegate {
static const char kCaptchaUrlParam[];
static const char kCaptchaTokenParam[];
+ // Constants for request/response for OAuth2 requests.
+ static const char kAuthHeaderFormat[];
+ static const char kClientLoginToOAuth2CookiePartSecure[];
+ static const char kClientLoginToOAuth2CookiePartHttpOnly[];
+ static const char kClientLoginToOAuth2CookiePartCodePrefix[];
+ static const int kClientLoginToOAuth2CookiePartCodePrefixLength;
+ static const char kOAuth2RefreshTokenKey[];
+ static const char kOAuth2AccessTokenKey[];
+ static const char kOAuth2ExpiresInKey[];
+
// Process the results of a ClientLogin fetch.
void OnClientLoginFetched(const std::string& data,
const net::URLRequestStatus& status,
@@ -141,6 +163,15 @@ class GaiaAuthFetcher : public content::URLFetcherDelegate {
const net::URLRequestStatus& status,
int response_code);
+ void OnClientLoginToOAuth2Fetched(const std::string& data,
+ const net::ResponseCookies& cookies,
+ const net::URLRequestStatus& status,
+ int response_code);
+
+ void OnOAuth2TokenPairFetched(const std::string& data,
+ const net::URLRequestStatus& status,
+ int response_code);
+
void OnGetUserInfoFetched(const std::string& data,
const net::URLRequestStatus& status,
int response_code);
@@ -165,6 +196,20 @@ class GaiaAuthFetcher : public content::URLFetcherDelegate {
std::string* captcha_url,
std::string* captcha_token);
+ // Parse ClientLogin to OAuth2 response.
+ static bool ParseClientLoginToOAuth2Response(
+ const net::ResponseCookies& cookies,
+ std::string* auth_code);
+
+ // Parse OAuth2 token pairresponse.
+ static bool ParseOAuth2TokenPairResponse(const std::string& data,
+ std::string* refresh_token,
+ std::string* access_token,
+ int* expires_in_secs);
+
+ static bool ParseClientLoginToOAuth2Cookie(const std::string& cookie,
+ std::string* auth_code);
+
// Is this a special case Gaia error for TwoFactor auth?
static bool IsSecondFactorSuccess(const std::string& alleged_error);
@@ -182,6 +227,10 @@ class GaiaAuthFetcher : public content::URLFetcherDelegate {
static std::string MakeIssueAuthTokenBody(const std::string& sid,
const std::string& lsid,
const char* const service);
+ // Create body to get OAuth2 auth code.
+ static std::string MakeGetAuthCodeBody();
+ // Given auth code, create body to get OAuth2 token pair.
+ static std::string MakeGetTokenPairBody(const std::string& auth_code);
// Supply the lsid returned from ClientLogin in order to fetch
// user information.
static std::string MakeGetUserInfoBody(const std::string& lsid);
@@ -196,12 +245,17 @@ class GaiaAuthFetcher : public content::URLFetcherDelegate {
const std::string& continue_url,
const std::string& source);
+ static std::string MakeGetAuthCodeHeader(const std::string& auth_token);
+
+ void StartOAuth2TokenPairFetch(const std::string& auth_code);
+
// Create a fetcher useable for making any Gaia request.
static content::URLFetcher* CreateGaiaFetcher(
net::URLRequestContextGetter* getter,
const std::string& body,
+ const std::string& headers,
const GURL& gaia_gurl,
- bool send_cookies,
+ bool use_cookies,
content::URLFetcherDelegate* delegate);
// From a URLFetcher result, generate an appropriate error.
@@ -217,6 +271,8 @@ class GaiaAuthFetcher : public content::URLFetcherDelegate {
std::string source_;
const GURL client_login_gurl_;
const GURL issue_auth_token_gurl_;
+ const GURL client_login_to_oauth2_gurl_;
+ const GURL oauth2_token_gurl_;
const GURL get_user_info_gurl_;
const GURL token_auth_gurl_;
const GURL merge_session_gurl_;
@@ -238,6 +294,9 @@ class GaiaAuthFetcher : public content::URLFetcherDelegate {
FRIEND_TEST_ALL_PREFIXES(GaiaAuthFetcherTest, CheckNormalErrorCode);
FRIEND_TEST_ALL_PREFIXES(GaiaAuthFetcherTest, CheckTwoFactorResponse);
FRIEND_TEST_ALL_PREFIXES(GaiaAuthFetcherTest, LoginNetFailure);
+ FRIEND_TEST_ALL_PREFIXES(GaiaAuthFetcherTest,
+ ParseClientLoginToOAuth2Response);
+ FRIEND_TEST_ALL_PREFIXES(GaiaAuthFetcherTest, ParseOAuth2TokenPairResponse);
DISALLOW_COPY_AND_ASSIGN(GaiaAuthFetcher);
};
diff --git a/chrome/common/net/gaia/gaia_auth_fetcher_unittest.cc b/chrome/common/net/gaia/gaia_auth_fetcher_unittest.cc
index f1f209f..8a17a83 100644
--- a/chrome/common/net/gaia/gaia_auth_fetcher_unittest.cc
+++ b/chrome/common/net/gaia/gaia_auth_fetcher_unittest.cc
@@ -28,6 +28,42 @@
using ::testing::_;
+namespace {
+static const char kGetAuthCodeValidCookie[] =
+ "oauth_code=test-code; Path=/test; Secure; HttpOnly";
+static const char kGetAuthCodeCookieNoSecure[] =
+ "oauth_code=test-code; Path=/test; HttpOnly";
+static const char kGetAuthCodeCookieNoHttpOnly[] =
+ "oauth_code=test-code; Path=/test; Secure";
+static const char kGetAuthCodeCookieNoOAuthCode[] =
+ "Path=/test; Secure; HttpOnly";
+static const char kGetTokenPairValidResponse[] =
+ "{"
+ " \"refresh_token\": \"rt1\","
+ " \"access_token\": \"at1\","
+ " \"expires_in\": 3600,"
+ " \"token_type\": \"Bearer\""
+ "}";
+static const char kGetTokenPairResponseNoRefreshToken[] =
+ "{"
+ " \"access_token\": \"at1\","
+ " \"expires_in\": 3600,"
+ " \"token_type\": \"Bearer\""
+ "}";
+static const char kGetTokenPairResponseNoAccessToken[] =
+ "{"
+ " \"refresh_token\": \"rt1\","
+ " \"expires_in\": 3600,"
+ " \"token_type\": \"Bearer\""
+ "}";
+static const char kGetTokenPairResponseNoExpiresIn[] =
+ "{"
+ " \"refresh_token\": \"rt1\","
+ " \"access_token\": \"at1\","
+ " \"token_type\": \"Bearer\""
+ "}";
+} // namespace
+
MockFetcher::MockFetcher(bool success,
const GURL& url,
const std::string& results,
@@ -76,6 +112,9 @@ class GaiaAuthFetcherTest : public testing::Test {
: client_login_source_(GaiaUrls::GetInstance()->client_login_url()),
issue_auth_token_source_(
GaiaUrls::GetInstance()->issue_auth_token_url()),
+ client_login_to_oauth2_source_(
+ GaiaUrls::GetInstance()->client_login_to_oauth2_url()),
+ oauth2_token_source_(GaiaUrls::GetInstance()->oauth2_token_url()),
token_auth_source_(GaiaUrls::GetInstance()->token_auth_url()),
merge_session_source_(GaiaUrls::GetInstance()->merge_session_url()) {}
@@ -120,6 +159,8 @@ class GaiaAuthFetcherTest : public testing::Test {
net::ResponseCookies cookies_;
GURL client_login_source_;
GURL issue_auth_token_source_;
+ GURL client_login_to_oauth2_source_;
+ GURL oauth2_token_source_;
GURL token_auth_source_;
GURL merge_session_source_;
TestingProfile profile_;
@@ -135,12 +176,18 @@ class MockGaiaConsumer : public GaiaAuthConsumer {
MOCK_METHOD1(OnClientLoginSuccess, void(const ClientLoginResult& result));
MOCK_METHOD2(OnIssueAuthTokenSuccess, void(const std::string& service,
const std::string& token));
+ MOCK_METHOD3(OnOAuthLoginTokenSuccess,
+ void(const std::string& refresh_token,
+ const std::string& access_token,
+ int expires_in_secs));
MOCK_METHOD1(OnTokenAuthSuccess, void(const std::string& data));
MOCK_METHOD1(OnMergeSessionSuccess, void(const std::string& data));
MOCK_METHOD1(OnClientLoginFailure,
void(const GoogleServiceAuthError& error));
MOCK_METHOD2(OnIssueAuthTokenFailure, void(const std::string& service,
const GoogleServiceAuthError& error));
+ MOCK_METHOD1(OnOAuthLoginTokenFailure,
+ void(const GoogleServiceAuthError& error));
MOCK_METHOD1(OnTokenAuthFailure, void(const GoogleServiceAuthError& error));
MOCK_METHOD1(OnMergeSessionFailure, void(
const GoogleServiceAuthError& error));
@@ -523,6 +570,91 @@ TEST_F(GaiaAuthFetcherTest, FullTokenFailure) {
EXPECT_FALSE(auth.HasPendingFetch());
}
+TEST_F(GaiaAuthFetcherTest, OAuthLoginTokenSuccess) {
+ MockGaiaConsumer consumer;
+ EXPECT_CALL(consumer, OnOAuthLoginTokenSuccess("rt1", "at1", 3600))
+ .Times(1);
+
+ TestingProfile profile;
+
+ TestURLFetcherFactory factory;
+ GaiaAuthFetcher auth(&consumer, std::string(),
+ profile_.GetRequestContext());
+ auth.StartOAuthLoginTokenFetch("lso_token");
+
+ net::ResponseCookies cookies;
+ cookies.push_back(kGetAuthCodeValidCookie);
+ EXPECT_TRUE(auth.HasPendingFetch());
+ MockFetcher mock_fetcher1(
+ client_login_to_oauth2_source_,
+ net::URLRequestStatus(net::URLRequestStatus::SUCCESS, 0),
+ RC_REQUEST_OK, cookies, "",
+ content::URLFetcher::POST, &auth);
+ auth.OnURLFetchComplete(&mock_fetcher1);
+ EXPECT_TRUE(auth.HasPendingFetch());
+ MockFetcher mock_fetcher2(
+ oauth2_token_source_,
+ net::URLRequestStatus(net::URLRequestStatus::SUCCESS, 0),
+ RC_REQUEST_OK, cookies_, kGetTokenPairValidResponse,
+ content::URLFetcher::POST, &auth);
+ auth.OnURLFetchComplete(&mock_fetcher2);
+ EXPECT_FALSE(auth.HasPendingFetch());
+}
+
+TEST_F(GaiaAuthFetcherTest, OAuthLoginTokenClientLoginToOAuth2Failure) {
+ MockGaiaConsumer consumer;
+ EXPECT_CALL(consumer, OnOAuthLoginTokenFailure(_))
+ .Times(1);
+
+ TestingProfile profile;
+
+ TestURLFetcherFactory factory;
+ GaiaAuthFetcher auth(&consumer, std::string(),
+ profile_.GetRequestContext());
+ auth.StartOAuthLoginTokenFetch("lso_token");
+
+ net::ResponseCookies cookies;
+ EXPECT_TRUE(auth.HasPendingFetch());
+ MockFetcher mock_fetcher(
+ client_login_to_oauth2_source_,
+ net::URLRequestStatus(net::URLRequestStatus::SUCCESS, 0),
+ RC_FORBIDDEN, cookies, "",
+ content::URLFetcher::POST, &auth);
+ auth.OnURLFetchComplete(&mock_fetcher);
+ EXPECT_FALSE(auth.HasPendingFetch());
+}
+
+TEST_F(GaiaAuthFetcherTest, OAuthLoginTokenOAuth2TokenPairFailure) {
+ MockGaiaConsumer consumer;
+ EXPECT_CALL(consumer, OnOAuthLoginTokenFailure(_))
+ .Times(1);
+
+ TestingProfile profile;
+
+ TestURLFetcherFactory factory;
+ GaiaAuthFetcher auth(&consumer, std::string(),
+ profile_.GetRequestContext());
+ auth.StartOAuthLoginTokenFetch("lso_token");
+
+ net::ResponseCookies cookies;
+ cookies.push_back(kGetAuthCodeValidCookie);
+ EXPECT_TRUE(auth.HasPendingFetch());
+ MockFetcher mock_fetcher1(
+ client_login_to_oauth2_source_,
+ net::URLRequestStatus(net::URLRequestStatus::SUCCESS, 0),
+ RC_REQUEST_OK, cookies, "",
+ content::URLFetcher::POST, &auth);
+ auth.OnURLFetchComplete(&mock_fetcher1);
+ EXPECT_TRUE(auth.HasPendingFetch());
+ MockFetcher mock_fetcher2(
+ oauth2_token_source_,
+ net::URLRequestStatus(net::URLRequestStatus::SUCCESS, 0),
+ RC_FORBIDDEN, cookies_, "",
+ content::URLFetcher::POST, &auth);
+ auth.OnURLFetchComplete(&mock_fetcher2);
+ EXPECT_FALSE(auth.HasPendingFetch());
+}
+
TEST_F(GaiaAuthFetcherTest, TokenAuthSuccess) {
MockGaiaConsumer consumer;
EXPECT_CALL(consumer, OnTokenAuthSuccess("<html></html>"))
@@ -639,3 +771,95 @@ TEST_F(GaiaAuthFetcherTest, MergeSessionSuccessRedirect) {
auth.OnURLFetchComplete(test_fetcher);
EXPECT_FALSE(auth.HasPendingFetch());
}
+
+TEST_F(GaiaAuthFetcherTest, ParseClientLoginToOAuth2Response) {
+ { // No cookies.
+ std::string auth_code;
+ net::ResponseCookies cookies;
+ EXPECT_FALSE(GaiaAuthFetcher::ParseClientLoginToOAuth2Response(
+ cookies, &auth_code));
+ EXPECT_EQ("", auth_code);
+ }
+ { // Few cookies, nothing appropriate.
+ std::string auth_code;
+ net::ResponseCookies cookies;
+ cookies.push_back(kGetAuthCodeCookieNoSecure);
+ cookies.push_back(kGetAuthCodeCookieNoHttpOnly);
+ cookies.push_back(kGetAuthCodeCookieNoOAuthCode);
+ EXPECT_FALSE(GaiaAuthFetcher::ParseClientLoginToOAuth2Response(
+ cookies, &auth_code));
+ EXPECT_EQ("", auth_code);
+ }
+ { // Few cookies, one of them is valid.
+ std::string auth_code;
+ net::ResponseCookies cookies;
+ cookies.push_back(kGetAuthCodeCookieNoSecure);
+ cookies.push_back(kGetAuthCodeCookieNoHttpOnly);
+ cookies.push_back(kGetAuthCodeCookieNoOAuthCode);
+ cookies.push_back(kGetAuthCodeValidCookie);
+ EXPECT_TRUE(GaiaAuthFetcher::ParseClientLoginToOAuth2Response(
+ cookies, &auth_code));
+ EXPECT_EQ("test-code", auth_code);
+ }
+ { // Single valid cookie (like in real responses).
+ std::string auth_code;
+ net::ResponseCookies cookies;
+ cookies.push_back(kGetAuthCodeValidCookie);
+ EXPECT_TRUE(GaiaAuthFetcher::ParseClientLoginToOAuth2Response(
+ cookies, &auth_code));
+ EXPECT_EQ("test-code", auth_code);
+ }
+}
+
+TEST_F(GaiaAuthFetcherTest, ParseOAuth2TokenPairResponse) {
+ { // No data.
+ std::string rt;
+ std::string at;
+ int exp = -1;
+ EXPECT_FALSE(GaiaAuthFetcher::ParseOAuth2TokenPairResponse(
+ "", &rt, &at, &exp));
+ EXPECT_EQ("", rt);
+ EXPECT_EQ("", at);
+ EXPECT_EQ(-1, exp);
+ }
+ { // No refresh token.
+ std::string rt;
+ std::string at;
+ int exp = -1;
+ EXPECT_FALSE(GaiaAuthFetcher::ParseOAuth2TokenPairResponse(
+ kGetTokenPairResponseNoRefreshToken, &rt, &at, &exp));
+ EXPECT_EQ("", rt);
+ EXPECT_EQ("", at);
+ EXPECT_EQ(-1, exp);
+ }
+ { // No access token.
+ std::string rt;
+ std::string at;
+ int exp = -1;
+ EXPECT_FALSE(GaiaAuthFetcher::ParseOAuth2TokenPairResponse(
+ kGetTokenPairResponseNoAccessToken, &rt, &at, &exp));
+ EXPECT_EQ("", rt);
+ EXPECT_EQ("", at);
+ EXPECT_EQ(-1, exp);
+ }
+ { // No expiration.
+ std::string rt;
+ std::string at;
+ int exp = -1;
+ EXPECT_FALSE(GaiaAuthFetcher::ParseOAuth2TokenPairResponse(
+ kGetTokenPairResponseNoExpiresIn, &rt, &at, &exp));
+ EXPECT_EQ("", rt);
+ EXPECT_EQ("", at);
+ EXPECT_EQ(-1, exp);
+ }
+ { // Valid response.
+ std::string rt;
+ std::string at;
+ int exp = -1;
+ EXPECT_TRUE(GaiaAuthFetcher::ParseOAuth2TokenPairResponse(
+ kGetTokenPairValidResponse, &rt, &at, &exp));
+ EXPECT_EQ("rt1", rt);
+ EXPECT_EQ("at1", at);
+ EXPECT_EQ(3600, exp);
+ }
+}
diff --git a/chrome/common/net/gaia/gaia_urls.cc b/chrome/common/net/gaia/gaia_urls.cc
index 0d1f424..49a33c0 100644
--- a/chrome/common/net/gaia/gaia_urls.cc
+++ b/chrome/common/net/gaia/gaia_urls.cc
@@ -24,6 +24,20 @@ const char kOAuthWrapBridgeUrlSuffix[] = "/accounts/OAuthWrapBridge";
const char kOAuth1LoginUrlSuffix[] = "/accounts/OAuthLogin";
const char kOAuthRevokeTokenUrlSuffix[] = "/accounts/AuthSubRevokeToken";
+// OAuth2 client id for Google Chrome which is registered as an
+// installed application.
+static const char kOAuth2ChromeClientId[] =
+ "77185425430.apps.googleusercontent.com";
+// For an installed application, client secret is not really a secret since
+// it is expected to be embeeded in the application.
+// See documentation at
+// http://code.google.com/apis/accounts/docs/OAuth2InstalledApp.html
+static const char kOAuth2ChromeClientSecret[] =
+ "OTJgUOQcT7lO7GsGZq2G4IlT";
+const char kClientLoginToOAuth2Url[] =
+ "https://accounts.google.com/o/oauth2/programmatic_auth";
+const char kOAuth2TokenUrl[] =
+ "https://accounts.google.com/o/oauth2/token";
} // namespacce
GaiaUrls* GaiaUrls::GetInstance() {
@@ -61,6 +75,11 @@ GaiaUrls::GaiaUrls() {
oauth_user_info_url_ = "https://www.googleapis.com/oauth2/v1/userinfo";
oauth_wrap_bridge_user_info_scope_ =
"https://www.googleapis.com/auth/userinfo.email";
+
+ oauth2_chrome_client_id_ = kOAuth2ChromeClientId;
+ oauth2_chrome_client_secret_ = kOAuth2ChromeClientSecret;
+ client_login_to_oauth2_url_ = kClientLoginToOAuth2Url;
+ oauth2_token_url_ = kOAuth2TokenUrl;
}
GaiaUrls::~GaiaUrls() {
@@ -125,3 +144,19 @@ const std::string& GaiaUrls::oauth1_login_scope() {
const std::string& GaiaUrls::oauth_wrap_bridge_user_info_scope() {
return oauth_wrap_bridge_user_info_scope_;
}
+
+const std::string& GaiaUrls::oauth2_chrome_client_id() {
+ return oauth2_chrome_client_id_;
+}
+
+const std::string& GaiaUrls::oauth2_chrome_client_secret() {
+ return oauth2_chrome_client_secret_;
+}
+
+const std::string& GaiaUrls::client_login_to_oauth2_url() {
+ return client_login_to_oauth2_url_;
+}
+
+const std::string& GaiaUrls::oauth2_token_url() {
+ return oauth2_token_url_;
+}
diff --git a/chrome/common/net/gaia/gaia_urls.h b/chrome/common/net/gaia/gaia_urls.h
index 50121a5..c8c1b46 100644
--- a/chrome/common/net/gaia/gaia_urls.h
+++ b/chrome/common/net/gaia/gaia_urls.h
@@ -34,6 +34,11 @@ class GaiaUrls {
const std::string& oauth1_login_scope();
const std::string& oauth_wrap_bridge_user_info_scope();
+ const std::string& oauth2_chrome_client_id();
+ const std::string& oauth2_chrome_client_secret();
+ const std::string& client_login_to_oauth2_url();
+ const std::string& oauth2_token_url();
+
private:
GaiaUrls();
~GaiaUrls();
@@ -58,6 +63,11 @@ class GaiaUrls {
std::string oauth1_login_scope_;
std::string oauth_wrap_bridge_user_info_scope_;
+ std::string oauth2_chrome_client_id_;
+ std::string oauth2_chrome_client_secret_;
+ std::string client_login_to_oauth2_url_;
+ std::string oauth2_token_url_;
+
DISALLOW_COPY_AND_ASSIGN(GaiaUrls);
};
diff --git a/chrome/common/net/gaia/oauth2_access_token_consumer.h b/chrome/common/net/gaia/oauth2_access_token_consumer.h
new file mode 100644
index 0000000..cf7eb34
--- /dev/null
+++ b/chrome/common/net/gaia/oauth2_access_token_consumer.h
@@ -0,0 +1,23 @@
+// Copyright (c) 2011 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.
+
+#ifndef CHROME_COMMON_NET_GAIA_OAUTH2_ACCESS_TOKEN_CONSUMER_H_
+#define CHROME_COMMON_NET_GAIA_OAUTH2_ACCESS_TOKEN_CONSUMER_H_
+#pragma once
+
+#include <string>
+
+class GoogleServiceAuthError;
+
+// An interface that defines the callbacks for consumers to which
+// OAuth2AccessTokenFetcher can return results.
+class OAuth2AccessTokenConsumer {
+ public:
+ virtual ~OAuth2AccessTokenConsumer() {}
+
+ virtual void OnGetTokenSuccess(const std::string& access_token) {}
+ virtual void OnGetTokenFailure(const GoogleServiceAuthError& error) {}
+};
+
+#endif // CHROME_COMMON_NET_GAIA_OAUTH2_ACCESS_TOKEN_CONSUMER_H_
diff --git a/chrome/common/net/gaia/oauth2_access_token_fetcher.cc b/chrome/common/net/gaia/oauth2_access_token_fetcher.cc
new file mode 100644
index 0000000..7f4e256b07
--- /dev/null
+++ b/chrome/common/net/gaia/oauth2_access_token_fetcher.cc
@@ -0,0 +1,185 @@
+// Copyright (c) 2011 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/common/net/gaia/oauth2_access_token_fetcher.h"
+
+#include <algorithm>
+#include <string>
+
+#include "base/json/json_reader.h"
+#include "base/stringprintf.h"
+#include "base/values.h"
+#include "chrome/common/net/gaia/gaia_urls.h"
+#include "chrome/common/net/gaia/google_service_auth_error.h"
+#include "chrome/common/net/http_return.h"
+#include "net/base/escape.h"
+#include "net/base/load_flags.h"
+#include "net/url_request/url_request_context_getter.h"
+#include "net/url_request/url_request_status.h"
+
+using content::URLFetcher;
+using content::URLFetcherDelegate;
+using net::ResponseCookies;
+using net::URLRequestContextGetter;
+using net::URLRequestStatus;
+
+namespace {
+static const char kGetAccessTokenBodyFormat[] =
+ "client_id=%s&"
+ "client_secret=%s"
+ "grant_type=refresh_token&"
+ "refresh_token=%s";
+
+static const char kAccessTokenKey[] = "access_token";
+
+static bool GetStringFromDictionary(const DictionaryValue* dict,
+ const std::string& key,
+ std::string* value) {
+ Value* json_value;
+ if (!dict->Get(key, &json_value))
+ return false;
+ if (json_value->GetType() != base::Value::TYPE_STRING)
+ return false;
+
+ StringValue* json_str_value = static_cast<StringValue*>(json_value);
+ json_str_value->GetAsString(value);
+ return true;
+}
+
+static GoogleServiceAuthError CreateAuthError(URLRequestStatus status) {
+ CHECK(!status.is_success());
+ if (status.status() == URLRequestStatus::CANCELED) {
+ return GoogleServiceAuthError(GoogleServiceAuthError::REQUEST_CANCELED);
+ } else {
+ DLOG(WARNING) << "Could not reach Google Accounts servers: errno "
+ << status.error();
+ return GoogleServiceAuthError::FromConnectionError(status.error());
+ }
+}
+
+static URLFetcher* CreateFetcher(URLRequestContextGetter* getter,
+ const GURL& url,
+ const std::string& body,
+ URLFetcherDelegate* delegate) {
+ bool empty_body = body.empty();
+ URLFetcher* result = URLFetcher::Create(
+ 0, url,
+ empty_body ? URLFetcher::GET : URLFetcher::POST,
+ delegate);
+
+ result->SetRequestContext(getter);
+ result->SetLoadFlags(net::LOAD_DO_NOT_SEND_COOKIES |
+ net::LOAD_DO_NOT_SAVE_COOKIES);
+
+ if (!empty_body)
+ result->SetUploadData("application/x-www-form-urlencoded", body);
+
+ return result;
+}
+} // namespace
+
+OAuth2AccessTokenFetcher::OAuth2AccessTokenFetcher(
+ OAuth2AccessTokenConsumer* consumer,
+ URLRequestContextGetter* getter,
+ const std::string& source)
+ : consumer_(consumer),
+ getter_(getter),
+ source_(source),
+ state_(INITIAL) { }
+
+OAuth2AccessTokenFetcher::~OAuth2AccessTokenFetcher() { }
+
+void OAuth2AccessTokenFetcher::CancelRequest() {
+ fetcher_.reset();
+}
+
+void OAuth2AccessTokenFetcher::Start(const std::string& refresh_token) {
+ refresh_token_ = refresh_token;
+ StartGetAccessToken();
+}
+
+void OAuth2AccessTokenFetcher::StartGetAccessToken() {
+ CHECK_EQ(INITIAL, state_);
+ state_ = GET_ACCESS_TOKEN_STARTED;
+ fetcher_.reset(CreateFetcher(
+ getter_,
+ MakeGetAccessTokenUrl(),
+ MakeGetAccessTokenBody(refresh_token_),
+ this));
+ fetcher_->Start(); // OnURLFetchComplete will be called.
+}
+
+void OAuth2AccessTokenFetcher::EndGetAccessToken(const URLFetcher* source) {
+ CHECK_EQ(GET_ACCESS_TOKEN_STARTED, state_);
+ state_ = GET_ACCESS_TOKEN_DONE;
+
+ URLRequestStatus status = source->GetStatus();
+ if (!status.is_success()) {
+ OnGetTokenFailure(CreateAuthError(status));
+ return;
+ }
+
+ if (source->GetResponseCode() != RC_REQUEST_OK) {
+ OnGetTokenFailure(GoogleServiceAuthError(
+ GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS));
+ return;
+ }
+
+ // The request was successfully fetched and it returned OK.
+ // Parse out the access token.
+ std::string access_token;
+ ParseGetAccessTokenResponse(source, &access_token);
+ OnGetTokenSuccess(access_token);
+}
+
+void OAuth2AccessTokenFetcher::OnGetTokenSuccess(
+ const std::string& access_token) {
+ consumer_->OnGetTokenSuccess(access_token);
+}
+
+void OAuth2AccessTokenFetcher::OnGetTokenFailure(GoogleServiceAuthError error) {
+ state_ = ERROR_STATE;
+ consumer_->OnGetTokenFailure(error);
+}
+
+void OAuth2AccessTokenFetcher::OnURLFetchComplete(const URLFetcher* source) {
+ CHECK(source);
+ CHECK(state_ == GET_ACCESS_TOKEN_STARTED);
+ EndGetAccessToken(source);
+}
+
+// static
+GURL OAuth2AccessTokenFetcher::MakeGetAccessTokenUrl() {
+ return GURL(GaiaUrls::GetInstance()->oauth2_token_url());
+}
+
+// static
+std::string OAuth2AccessTokenFetcher::MakeGetAccessTokenBody(
+ const std::string& refresh_token) {
+ return StringPrintf(
+ kGetAccessTokenBodyFormat,
+ net::EscapeUrlEncodedData(
+ GaiaUrls::GetInstance()->oauth2_chrome_client_id(), true).c_str(),
+ net::EscapeUrlEncodedData(
+ GaiaUrls::GetInstance()->oauth2_chrome_client_secret(),
+ true).c_str(),
+ net::EscapeUrlEncodedData(refresh_token, true).c_str());
+}
+
+// static
+bool OAuth2AccessTokenFetcher::ParseGetAccessTokenResponse(
+ const URLFetcher* source,
+ std::string* access_token) {
+ CHECK(source);
+ CHECK(access_token);
+ std::string data;
+ source->GetResponseAsString(&data);
+ base::JSONReader reader;
+ scoped_ptr<base::Value> value(reader.Read(data, false));
+ if (!value.get() || value->GetType() != base::Value::TYPE_DICTIONARY)
+ return false;
+
+ DictionaryValue* dict = static_cast<DictionaryValue*>(value.get());
+ return GetStringFromDictionary(dict, kAccessTokenKey, access_token);
+}
diff --git a/chrome/common/net/gaia/oauth2_access_token_fetcher.h b/chrome/common/net/gaia/oauth2_access_token_fetcher.h
new file mode 100644
index 0000000..5ba4af7
--- /dev/null
+++ b/chrome/common/net/gaia/oauth2_access_token_fetcher.h
@@ -0,0 +1,94 @@
+// Copyright (c) 2011 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.
+
+#ifndef CHROME_COMMON_NET_GAIA_OAUTH2_ACCESS_TOKEN_FETCHER_H_
+#define CHROME_COMMON_NET_GAIA_OAUTH2_ACCESS_TOKEN_FETCHER_H_
+#pragma once
+
+#include <string>
+
+#include "base/gtest_prod_util.h"
+#include "base/memory/scoped_ptr.h"
+#include "chrome/common/net/gaia/oauth2_access_token_consumer.h"
+#include "content/public/common/url_fetcher.h"
+#include "content/public/common/url_fetcher_delegate.h"
+#include "googleurl/src/gurl.h"
+
+class OAuth2AccessTokenFetcherTest;
+
+namespace net {
+class URLRequestContextGetter;
+class URLRequestStatus;
+}
+
+// Abstracts the details to get OAuth2 access token token from
+// OAuth2 refresh token.
+//
+// This class should be used on a single thread, but it can be whichever thread
+// that you like.
+// Also, do not reuse the same instance. Once Start() is called, the instance
+// should not be reused.
+//
+// Usage:
+// * Create an instance with a consumer.
+// * Call Start()
+// * The consumer passed in the constructor will be called on the same
+// thread Start was called with the results.
+//
+// This class can handle one request at a time. To parallelize requests,
+// create multiple instances.
+class OAuth2AccessTokenFetcher : public content::URLFetcherDelegate {
+ public:
+ OAuth2AccessTokenFetcher(OAuth2AccessTokenConsumer* consumer,
+ net::URLRequestContextGetter* getter,
+ const std::string& source);
+ virtual ~OAuth2AccessTokenFetcher();
+
+ void Start(const std::string& refresh_token);
+
+ void CancelRequest();
+
+ // Implementation of content::URLFetcherDelegate
+ virtual void OnURLFetchComplete(const content::URLFetcher* source) OVERRIDE;
+
+ private:
+ enum State {
+ INITIAL,
+ GET_ACCESS_TOKEN_STARTED,
+ GET_ACCESS_TOKEN_DONE,
+ ERROR_STATE,
+ };
+
+ // Helper methods for the flow.
+ void StartGetAccessToken();
+ void EndGetAccessToken(const content::URLFetcher* source);
+
+ // Helper mehtods for reporting back results.
+ void OnGetTokenSuccess(const std::string& access_token);
+ void OnGetTokenFailure(GoogleServiceAuthError error);
+
+ // Other helpers.
+ static GURL MakeGetAccessTokenUrl();
+ static std::string MakeGetAccessTokenBody(const std::string& refresh_token);
+ static bool ParseGetAccessTokenResponse(const content::URLFetcher* source,
+ std::string* access_token);
+
+ // State that is set during construction.
+ OAuth2AccessTokenConsumer* const consumer_;
+ net::URLRequestContextGetter* const getter_;
+ std::string source_;
+ State state_;
+
+ // While a fetch is in progress.
+ scoped_ptr<content::URLFetcher> fetcher_;
+ std::string refresh_token_;
+
+ friend class OAuth2AccessTokenFetcherTest;
+ FRIEND_TEST_ALL_PREFIXES(OAuth2AccessTokenFetcherTest,
+ ParseGetAccessTokenResponse);
+
+ DISALLOW_COPY_AND_ASSIGN(OAuth2AccessTokenFetcher);
+};
+
+#endif // CHROME_COMMON_NET_GAIA_OAUTH2_ACCESS_TOKEN_FETCHER_H_
diff --git a/chrome/common/net/gaia/oauth2_access_token_fetcher_unittest.cc b/chrome/common/net/gaia/oauth2_access_token_fetcher_unittest.cc
new file mode 100644
index 0000000..cbf3be5
--- /dev/null
+++ b/chrome/common/net/gaia/oauth2_access_token_fetcher_unittest.cc
@@ -0,0 +1,172 @@
+// Copyright (c) 2011 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.
+//
+// A complete set of unit tests for OAuth2AccessTokenFetcher.
+
+#include <string>
+
+#include "base/memory/scoped_ptr.h"
+#include "base/message_loop.h"
+#include "chrome/common/net/gaia/gaia_urls.h"
+#include "chrome/common/net/gaia/google_service_auth_error.h"
+#include "chrome/common/net/http_return.h"
+#include "chrome/common/net/gaia/oauth2_access_token_consumer.h"
+#include "chrome/common/net/gaia/oauth2_access_token_fetcher.h"
+#include "chrome/test/base/testing_profile.h"
+#include "content/public/common/url_fetcher.h"
+#include "content/public/common/url_fetcher_delegate.h"
+#include "content/public/common/url_fetcher_factory.h"
+#include "content/test/test_browser_thread.h"
+#include "content/test/test_url_fetcher_factory.h"
+#include "googleurl/src/gurl.h"
+#include "net/url_request/url_request.h"
+#include "net/url_request/url_request_status.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using content::BrowserThread;
+using content::URLFetcher;
+using content::URLFetcherDelegate;
+using content::URLFetcherFactory;
+using net::ResponseCookies;
+using net::URLRequestStatus;
+using testing::_;
+using testing::Return;
+
+namespace {
+static const char kValidTokenResponse[] =
+ "{"
+ " \"access_token\": \"at1\","
+ " \"expires_in\": 3600,"
+ " \"token_type\": \"Bearer\""
+ "}";
+static const char kTokenResponseNoAccessToken[] =
+ "{"
+ " \"expires_in\": 3600,"
+ " \"token_type\": \"Bearer\""
+ "}";
+}
+
+class MockUrlFetcherFactory : public ScopedURLFetcherFactory,
+ public URLFetcherFactory {
+public:
+ MockUrlFetcherFactory()
+ : ScopedURLFetcherFactory(ALLOW_THIS_IN_INITIALIZER_LIST(this)) {
+ }
+ virtual ~MockUrlFetcherFactory() {}
+
+ MOCK_METHOD4(
+ CreateURLFetcher,
+ URLFetcher* (int id,
+ const GURL& url,
+ URLFetcher::RequestType request_type,
+ URLFetcherDelegate* d));
+};
+
+class MockOAuth2AccessTokenConsumer : public OAuth2AccessTokenConsumer {
+ public:
+ MockOAuth2AccessTokenConsumer() {}
+ ~MockOAuth2AccessTokenConsumer() {}
+
+ MOCK_METHOD1(OnGetTokenSuccess, void(const std::string& access_token));
+ MOCK_METHOD1(OnGetTokenFailure,
+ void(const GoogleServiceAuthError& error));
+};
+
+class OAuth2AccessTokenFetcherTest : public testing::Test {
+ public:
+ OAuth2AccessTokenFetcherTest()
+ : ui_thread_(BrowserThread::UI, &message_loop_),
+ fetcher_(&consumer_, profile_.GetRequestContext(), "test") {
+ }
+
+ virtual ~OAuth2AccessTokenFetcherTest() { }
+
+ virtual TestURLFetcher* SetupGetAccessToken(
+ bool fetch_succeeds, int response_code, const std::string& body) {
+ GURL url(GaiaUrls::GetInstance()->oauth2_token_url());
+ TestURLFetcher* url_fetcher = new TestURLFetcher(0, url, &fetcher_);
+ URLRequestStatus::Status status =
+ fetch_succeeds ? URLRequestStatus::SUCCESS : URLRequestStatus::FAILED;
+ url_fetcher->set_status(URLRequestStatus(status, 0));
+
+ if (response_code != 0)
+ url_fetcher->set_response_code(response_code);
+
+ if (!body.empty())
+ url_fetcher->SetResponseString(body);
+
+ EXPECT_CALL(factory_, CreateURLFetcher(_, url, _, _))
+ .WillOnce(Return(url_fetcher));
+ return url_fetcher;
+ }
+
+ protected:
+ MessageLoop message_loop_;
+ content::TestBrowserThread ui_thread_;
+ MockUrlFetcherFactory factory_;
+ MockOAuth2AccessTokenConsumer consumer_;
+ TestingProfile profile_;
+ OAuth2AccessTokenFetcher fetcher_;
+};
+
+TEST_F(OAuth2AccessTokenFetcherTest, GetAccessTokenRequestFailure) {
+ TestURLFetcher* url_fetcher = SetupGetAccessToken(false, 0, "");
+ EXPECT_CALL(consumer_, OnGetTokenFailure(_)).Times(1);
+ fetcher_.Start("refresh_token");
+ fetcher_.OnURLFetchComplete(url_fetcher);
+}
+
+TEST_F(OAuth2AccessTokenFetcherTest, GetAccessTokenResponseCodeFailure) {
+ TestURLFetcher* url_fetcher = SetupGetAccessToken(true, RC_FORBIDDEN, "");
+ EXPECT_CALL(consumer_, OnGetTokenFailure(_)).Times(1);
+ fetcher_.Start("refresh_token");
+ fetcher_.OnURLFetchComplete(url_fetcher);
+}
+
+TEST_F(OAuth2AccessTokenFetcherTest, Success) {
+ TestURLFetcher* url_fetcher = SetupGetAccessToken(
+ true, RC_REQUEST_OK, kValidTokenResponse);
+ EXPECT_CALL(consumer_, OnGetTokenSuccess("at1")).Times(1);
+ fetcher_.Start("refresh_token");
+ fetcher_.OnURLFetchComplete(url_fetcher);
+}
+
+TEST_F(OAuth2AccessTokenFetcherTest, ParseGetAccessTokenResponse) {
+ { // No body.
+ TestURLFetcher url_fetcher(0, GURL("www.google.com"), NULL);
+
+ std::string at;
+ EXPECT_FALSE(OAuth2AccessTokenFetcher::ParseGetAccessTokenResponse(
+ &url_fetcher, &at));
+ EXPECT_TRUE(at.empty());
+ }
+ { // Bad json.
+ TestURLFetcher url_fetcher(0, GURL("www.google.com"), NULL);
+ url_fetcher.SetResponseString("foo");
+
+ std::string at;
+ EXPECT_FALSE(OAuth2AccessTokenFetcher::ParseGetAccessTokenResponse(
+ &url_fetcher, &at));
+ EXPECT_TRUE(at.empty());
+ }
+ { // Valid json: access token missing.
+ TestURLFetcher url_fetcher(0, GURL("www.google.com"), NULL);
+ url_fetcher.SetResponseString(kTokenResponseNoAccessToken);
+
+ std::string at;
+ EXPECT_FALSE(OAuth2AccessTokenFetcher::ParseGetAccessTokenResponse(
+ &url_fetcher, &at));
+ EXPECT_TRUE(at.empty());
+ }
+ { // Valid json: all good.
+ TestURLFetcher url_fetcher(0, GURL("www.google.com"), NULL);
+ url_fetcher.SetResponseString(kValidTokenResponse);
+
+ std::string at;
+ EXPECT_TRUE(OAuth2AccessTokenFetcher::ParseGetAccessTokenResponse(
+ &url_fetcher, &at));
+ EXPECT_EQ("at1", at);
+ }
+}