Index: chrome/common/net/gaia/authentication_fetcher_oauth.cc |
diff --git a/chrome/common/net/gaia/authentication_fetcher_oauth.cc b/chrome/common/net/gaia/authentication_fetcher_oauth.cc |
new file mode 100644 |
index 0000000000000000000000000000000000000000..f4ad49cf45bc3aca9cf06c65b29d5d8c219500c0 |
--- /dev/null |
+++ b/chrome/common/net/gaia/authentication_fetcher_oauth.cc |
@@ -0,0 +1,444 @@ |
+// 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/authentication_fetcher_oauth.h" |
+ |
+#include <string> |
+#include <utility> |
+#include <vector> |
+ |
+#include "base/string_split.h" |
+#include "base/string_util.h" |
+#include "chrome/common/net/gaia/authentication_consumer_oauth.h" |
+#include "chrome/common/net/gaia/gaia_constants.h" |
+#include "chrome/common/net/gaia/google_service_auth_error.h" |
+#include "chrome/common/net/http_return.h" |
+#include "net/base/load_flags.h" |
+#include "net/url_request/url_request_context_getter.h" |
+#include "net/url_request/url_request_status.h" |
+#include "third_party/libjingle/source/talk/base/urlencode.h" |
+ |
+// TODO(chron): Add sourceless version of this formatter. |
+// static |
+const char AuthenticationFetcherOAuth::kAuthenticationFormat[] = |
+ "Email=%s&" |
+ "Passwd=%s&" |
+ "PersistentCookie=%s&" |
+ "accountType=%s&" |
+ "source=%s&" |
+ "service=%s"; |
+// static |
+const char AuthenticationFetcherOAuth::kAuthenticationCaptchaFormat[] = |
+ "Email=%s&" |
+ "Passwd=%s&" |
+ "PersistentCookie=%s&" |
+ "accountType=%s&" |
+ "source=%s&" |
+ "service=%s&" |
+ "logintoken=%s&" |
+ "logincaptcha=%s"; |
+// static |
+const char AuthenticationFetcherOAuth::kIssueAuthTokenFormat[] = |
+ "SID=%s&" |
+ "LSID=%s&" |
+ "service=%s&" |
+ "Session=%s"; |
+// static |
+const char AuthenticationFetcherOAuth::kGetUserInfoFormat[] = |
+ "LSID=%s"; |
+ |
+// static |
+const char AuthenticationFetcherOAuth::kAccountDeletedError[] = |
+ "AccountDeleted"; |
+// static |
+const char AuthenticationFetcherOAuth::kAccountDisabledError[] = |
+ "AccountDisabled"; |
+// static |
+const char AuthenticationFetcherOAuth::kBadAuthenticationError[] = |
+ "BadAuthentication"; |
+// static |
+const char AuthenticationFetcherOAuth::kCaptchaError[] = "CaptchaRequired"; |
+// static |
+const char AuthenticationFetcherOAuth::kServiceUnavailableError[] = |
+ "ServiceUnavailable"; |
+// static |
+const char AuthenticationFetcherOAuth::kErrorParam[] = "Error"; |
+// static |
+const char AuthenticationFetcherOAuth::kErrorUrlParam[] = "Url"; |
+// static |
+const char AuthenticationFetcherOAuth::kCaptchaUrlParam[] = "CaptchaUrl"; |
+// static |
+const char AuthenticationFetcherOAuth::kCaptchaTokenParam[] = "CaptchaToken"; |
+// static |
+const char AuthenticationFetcherOAuth::kCaptchaUrlPrefix[] = |
+ "http://www.google.com/accounts/"; |
+ |
+// static |
+const char AuthenticationFetcherOAuth::kCookiePersistence[] = "true"; |
+// static |
+// TODO(johnnyg): When hosted accounts are supported by sync, |
+// we can always use "HOSTED_OR_GOOGLE" |
+const char AuthenticationFetcherOAuth::kAccountTypeHostedOrGoogle[] = |
+ "HOSTED_OR_GOOGLE"; |
+const char AuthenticationFetcherOAuth::kAccountTypeGoogle[] = |
+ "GOOGLE"; |
+ |
+// static |
+const char AuthenticationFetcherOAuth::kSecondFactor[] = |
+ "Info=InvalidSecondFactor"; |
+ |
+// TODO(chron): These urls are also in auth_response_handler.h. |
+// The URLs for different calls in the Google Accounts programmatic login API. |
+const char AuthenticationFetcherOAuth::kAuthenticationUrl[] = |
+ "https://www.google.com/accounts/ClientLogin"; |
+const char AuthenticationFetcherOAuth::kIssueAuthTokenUrl[] = |
+ "https://www.google.com/accounts/IssueAuthToken"; |
+const char AuthenticationFetcherOAuth::kGetUserInfoUrl[] = |
+ "https://www.google.com/accounts/GetUserInfo"; |
+ |
+AuthenticationFetcherOAuth::AuthenticationFetcherOAuth( |
+ AuthenticationConsumerOAuth* consumer, |
+ const std::string& source, |
+ net::URLRequestContextGetter* getter) |
+ : AuthenticationFetcher(consumer, source, getter) {} |
+ |
+AuthenticationFetcherOAuth::~AuthenticationFetcherOAuth() {} |
+ |
+// static |
+URLFetcher* AuthenticationFetcherOAuth::CreateAuthenticationFetcherOAuth( |
+ net::URLRequestContextGetter* getter, |
+ const std::string& body, |
+ const GURL& gaia_gurl, |
+ URLFetcher::Delegate* delegate) { |
+ |
+ URLFetcher* to_return = |
+ URLFetcher::Create(0, |
+ gaia_gurl, |
+ URLFetcher::POST, |
+ delegate); |
+ to_return->set_request_context(getter); |
+ to_return->set_load_flags(net::LOAD_DO_NOT_SEND_COOKIES); |
+ to_return->set_upload_data("application/x-www-form-urlencoded", body); |
+ return to_return; |
+} |
+ |
+// static |
+std::string AuthenticationFetcherOAuth::MakeAuthenticationBody( |
+ const std::string& username, |
+ const std::string& password, |
+ const std::string& source, |
+ const char* service, |
+ const std::string& login_token, |
+ const std::string& login_captcha, |
+ HostedAccountsSetting allow_hosted_accounts) { |
+ std::string encoded_username = UrlEncodeString(username); |
+ std::string encoded_password = UrlEncodeString(password); |
+ std::string encoded_login_token = UrlEncodeString(login_token); |
+ std::string encoded_login_captcha = UrlEncodeString(login_captcha); |
+ |
+ const char* account_type = allow_hosted_accounts == HostedAccountsAllowed ? |
+ kAccountTypeHostedOrGoogle : |
+ kAccountTypeGoogle; |
+ |
+ if (login_token.empty() || login_captcha.empty()) { |
+ return base::StringPrintf(kAuthenticationFormat, |
+ encoded_username.c_str(), |
+ encoded_password.c_str(), |
+ kCookiePersistence, |
+ account_type, |
+ source.c_str(), |
+ service); |
+ } |
+ |
+ return base::StringPrintf(kAuthenticationCaptchaFormat, |
+ encoded_username.c_str(), |
+ encoded_password.c_str(), |
+ kCookiePersistence, |
+ account_type, |
+ source.c_str(), |
+ service, |
+ encoded_login_token.c_str(), |
+ encoded_login_captcha.c_str()); |
+ |
+} |
+ |
+// static |
+std::string AuthenticationFetcherOAuth::MakeIssueAuthTokenBody( |
+ const AuthenticationConsumer::AuthenticationResult& credentials, |
+ const char* const service) { |
+ const AuthenticationConsumerOAuth::AuthenticationResult& cred = |
+ static_cast<const AuthenticationConsumerOAuth::AuthenticationResult&>( |
+ credentials); |
+ std::string encoded_sid = UrlEncodeString(cred.sid); |
+ std::string encoded_lsid = UrlEncodeString(cred.lsid); |
+ |
+ // All tokens should be session tokens except the gaia auth token. |
+ bool session = true; |
+ if (!strcmp(service, GaiaConstants::kGaiaService)) |
+ session = false; |
+ |
+ return base::StringPrintf(kIssueAuthTokenFormat, |
+ encoded_sid.c_str(), |
+ encoded_lsid.c_str(), |
+ service, |
+ session ? "true" : "false"); |
+} |
+ |
+// static |
+std::string AuthenticationFetcherOAuth::MakeGetUserInfoBody( |
+ const std::string& lsid) { |
+ std::string encoded_lsid = UrlEncodeString(lsid); |
+ return base::StringPrintf(kGetUserInfoFormat, encoded_lsid.c_str()); |
+} |
+ |
+// Helper method that extracts tokens from a successful reply. |
+// static |
+void AuthenticationFetcherOAuth::ParseAuthenticationResponse( |
+ const std::string& data, |
+ std::string* sid, |
+ std::string* lsid, |
+ std::string* token) { |
+ 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->assign(i->second); |
+ } else if (i->first == "LSID") { |
+ lsid->assign(i->second); |
+ } else if (i->first == "Auth") { |
+ token->assign(i->second); |
+ } |
+ } |
+} |
+ |
+// static |
+void AuthenticationFetcherOAuth::ParseAuthenticationFailure( |
+ const std::string& data, |
+ std::string* error, |
+ std::string* error_url, |
+ std::string* captcha_url, |
+ std::string* captcha_token) { |
+ 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 == kErrorParam) { |
+ error->assign(i->second); |
+ } else if (i->first == kErrorUrlParam) { |
+ error_url->assign(i->second); |
+ } else if (i->first == kCaptchaUrlParam) { |
+ captcha_url->assign(i->second); |
+ } else if (i->first == kCaptchaTokenParam) { |
+ captcha_token->assign(i->second); |
+ } |
+ } |
+} |
+ |
+void AuthenticationFetcherOAuth::StartAuthentication( |
+ const std::string& username, |
+ const std::string& password, |
+ const char* const service, |
+ const std::string& login_token, |
+ const std::string& login_captcha, |
+ HostedAccountsSetting allow_hosted_accounts) { |
+ |
+ DCHECK(!HasPendingFetch()) << "Tried to fetch two things at once!"; |
+ |
+ // This class is thread agnostic, so be sure to call this only on the |
+ // same thread each time. |
+ VLOG(1) << "Starting new Authentication fetch for:" << username; |
+ |
+ // Must outlive fetcher_. |
+ request_body_ = MakeAuthenticationBody(username, |
+ password, |
+ source_, |
+ service, |
+ login_token, |
+ login_captcha, |
+ allow_hosted_accounts); |
+ fetcher_.reset(CreateAuthenticationFetcherOAuth(getter_, |
+ request_body_, |
+ oauth_gurl_, |
+ this)); |
+ SetPending(); |
+ fetcher_->Start(); |
+} |
+ |
+void AuthenticationFetcherOAuth::StartIssueAuthToken( |
+ const AuthenticationConsumer::AuthenticationResult& credentials, |
+ const char* const service) { |
+ |
+ DCHECK(!HasPendingFetch()) << "Tried to fetch two things at once!"; |
+ |
+ VLOG(1) << "Starting IssueAuthToken for: " << service; |
+ requested_service_ = service; |
+ |
+ request_body_ = MakeIssueAuthTokenBody(credentials, service); |
+ |
+ fetcher_.reset(CreateAuthenticationFetcherOAuth(getter_, |
+ request_body_, |
+ issue_auth_token_gurl_, |
+ this)); |
+ SetPending(); |
+ fetcher_->Start(); |
+} |
+ |
+void AuthenticationFetcherOAuth::StartGetUserInfo(const std::string& lsid, |
+ const std::string& info_key) { |
+ DCHECK(!HasPendingFetch()) << "Tried to fetch two things at once!"; |
+ |
+ VLOG(1) << "Starting GetUserInfo for lsid=" << lsid; |
+ request_body_ = MakeGetUserInfoBody(lsid); |
+ fetcher_.reset(CreateAuthenticationFetcherOAuth(getter_, |
+ request_body_, |
+ get_user_info_gurl_, |
+ this)); |
+ SetPending(); |
+ requested_info_key_ = info_key; |
+ fetcher_->Start(); |
+} |
+ |
+// static |
+GoogleServiceAuthError AuthenticationFetcherOAuth::GenerateAuthError( |
+ const std::string& data, |
+ const net::URLRequestStatus& status) { |
+ |
+ 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.os_error(); |
+ return GoogleServiceAuthError::FromConnectionError(status.os_error()); |
+ } |
+ } else { |
+ if (IsSecondFactorSuccess(data)) { |
+ return GoogleServiceAuthError(GoogleServiceAuthError::TWO_FACTOR); |
+ } |
+ |
+ std::string error; |
+ std::string url; |
+ std::string captcha_url; |
+ std::string captcha_token; |
+ ParseAuthenticationFailure( |
+ data, &error, &url, &captcha_url, &captcha_token); |
+ LOG(WARNING) << "Authentication failed with " << error; |
+ |
+ if (error == kCaptchaError) { |
+ GURL image_url(kCaptchaUrlPrefix + captcha_url); |
+ GURL unlock_url(url); |
+ return GoogleServiceAuthError::FromCaptchaChallenge( |
+ captcha_token, image_url, unlock_url); |
+ } |
+ if (error == kAccountDeletedError) |
+ return GoogleServiceAuthError(GoogleServiceAuthError::ACCOUNT_DELETED); |
+ if (error == kAccountDisabledError) |
+ return GoogleServiceAuthError(GoogleServiceAuthError::ACCOUNT_DISABLED); |
+ if (error == kBadAuthenticationError) { |
+ return GoogleServiceAuthError( |
+ GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS); |
+ } |
+ if (error == kServiceUnavailableError) { |
+ return GoogleServiceAuthError( |
+ GoogleServiceAuthError::SERVICE_UNAVAILABLE); |
+ } |
+ |
+ LOG(WARNING) << "Incomprehensible response from Google Accounts servers."; |
+ return GoogleServiceAuthError( |
+ GoogleServiceAuthError::SERVICE_UNAVAILABLE); |
+ } |
+ |
+ NOTREACHED(); |
+ return GoogleServiceAuthError(GoogleServiceAuthError::SERVICE_UNAVAILABLE); |
+} |
+ |
+void AuthenticationFetcherOAuth::OnAuthenticationFetched( |
+ const std::string& data, |
+ const net::URLRequestStatus& status, |
+ int response_code) { |
+ if (status.is_success() && response_code == RC_REQUEST_OK) { |
+ VLOG(1) << "Authentication successful!"; |
+ std::string sid; |
+ std::string lsid; |
+ std::string token; |
+ ParseAuthenticationResponse(data, &sid, &lsid, &token); |
+ consumer_->OnAuthenticationSuccess( |
+ AuthenticationConsumerOAuth::AuthenticationResult( |
+ sid, lsid, token, data)); |
+ } else { |
+ consumer_->OnAuthenticationFailure(GenerateAuthError(data, status)); |
+ } |
+} |
+ |
+void AuthenticationFetcherOAuth::OnIssueAuthTokenFetched( |
+ const std::string& data, |
+ const net::URLRequestStatus& status, |
+ int response_code) { |
+ if (status.is_success() && response_code == RC_REQUEST_OK) { |
+ // Only the bare token is returned in the body of this Gaia call |
+ // without any padding. |
+ consumer_->OnIssueTokenSuccess(requested_service_, data); |
+ } else { |
+ consumer_->OnIssueTokenFailure(requested_service_, |
+ GenerateAuthError(data, status)); |
+ } |
+} |
+ |
+void AuthenticationFetcherOAuth::OnGetUserInfoFetched( |
+ const std::string& data, |
+ const net::URLRequestStatus& status, |
+ int response_code) { |
+ using std::vector; |
+ using std::string; |
+ using std::pair; |
+ |
+ if (status.is_success() && response_code == RC_REQUEST_OK) { |
+ 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 == requested_info_key_) { |
+ consumer_->OnGetUserInfoSuccess(i->first, i->second); |
+ return; |
+ } |
+ } |
+ consumer_->OnGetUserInfoKeyNotFound(requested_info_key_); |
+ } else { |
+ consumer_->OnGetUserInfoFailure(GenerateAuthError(data, status)); |
+ } |
+} |
+ |
+void AuthenticationFetcherOAuth::OnURLFetchComplete(const URLFetcher* source, |
+ const GURL& url, |
+ const net::URLRequestStatus& status, |
+ int response_code, |
+ const ResponseCookies& cookies, |
+ const std::string& data) { |
+ ClearPending(); |
+ if (url == oauth_gurl_) { |
+ OnAuthenticationFetched(data, status, response_code); |
+ } else if (url == issue_auth_token_gurl_) { |
+ OnIssueAuthTokenFetched(data, status, response_code); |
+ } else if (url == get_user_info_gurl_) { |
+ OnGetUserInfoFetched(data, status, response_code); |
+ } else { |
+ NOTREACHED(); |
+ } |
+} |
+ |
+// static |
+bool AuthenticationFetcherOAuth::IsSecondFactorSuccess( |
+ const std::string& alleged_error) { |
+ return alleged_error.find(kSecondFactor) != |
+ std::string::npos; |
+} |