Commit efc601eb authored by mlerman's avatar mlerman Committed by Commit bot

Implement the calls to GAIA for the IDP IFrame protocol.

This CL implements Fetchers which can call endpoints defined for the
IDP IFrame protocol, specifically listSessions and getToken. This will
be used as part of the Cross-Device Promo so that Chrome can mint
access tokens of a particular scope based on cookies.

BUG=463611

Review URL: https://codereview.chromium.org/973953002

Cr-Commit-Position: refs/heads/master@{#319829}
parent 0c368f47
...@@ -89,6 +89,12 @@ class GaiaAuthConsumer { ...@@ -89,6 +89,12 @@ class GaiaAuthConsumer {
virtual void OnGetCheckConnectionInfoSuccess(const std::string& data) {} virtual void OnGetCheckConnectionInfoSuccess(const std::string& data) {}
virtual void OnGetCheckConnectionInfoError( virtual void OnGetCheckConnectionInfoError(
const GoogleServiceAuthError& error) {} const GoogleServiceAuthError& error) {}
virtual void OnListIdpSessionsSuccess(const std::string& login_hint) {}
virtual void OnListIdpSessionsError(const GoogleServiceAuthError& error) {}
virtual void OnGetTokenResponseSuccess(const ClientOAuthResult& result) {}
virtual void OnGetTokenResponseError(const GoogleServiceAuthError& error) {}
}; };
#endif // GOOGLE_APIS_GAIA_GAIA_AUTH_CONSUMER_H_ #endif // GOOGLE_APIS_GAIA_GAIA_AUTH_CONSUMER_H_
...@@ -41,23 +41,39 @@ static bool CookiePartsContains(const std::vector<std::string>& parts, ...@@ -41,23 +41,39 @@ static bool CookiePartsContains(const std::vector<std::string>& parts,
return false; return false;
} }
bool ExtractOAuth2TokenPairResponse(base::DictionaryValue* dict, // From the JSON string |data|, extract the |access_token| and |expires_in_secs|
// both of which must exist. If the |refresh_token| is non-NULL, then it also
// must exist and is extraced; if it's NULL, then no extraction is attempted.
bool ExtractOAuth2TokenPairResponse(const std::string& data,
std::string* refresh_token, std::string* refresh_token,
std::string* access_token, std::string* access_token,
int* expires_in_secs) { int* expires_in_secs) {
DCHECK(refresh_token);
DCHECK(access_token); DCHECK(access_token);
DCHECK(expires_in_secs); DCHECK(expires_in_secs);
if (!dict->GetStringWithoutPathExpansion("refresh_token", refresh_token) || scoped_ptr<base::Value> value(base::JSONReader::Read(data));
!dict->GetStringWithoutPathExpansion("access_token", access_token) || if (!value.get() || value->GetType() != base::Value::TYPE_DICTIONARY)
return false;
base::DictionaryValue* dict =
static_cast<base::DictionaryValue*>(value.get());
if (!dict->GetStringWithoutPathExpansion("access_token", access_token) ||
!dict->GetIntegerWithoutPathExpansion("expires_in", expires_in_secs)) { !dict->GetIntegerWithoutPathExpansion("expires_in", expires_in_secs)) {
return false; return false;
} }
// Refresh token may not be required.
if (refresh_token) {
if (!dict->GetStringWithoutPathExpansion("refresh_token", refresh_token))
return false;
}
return true; return true;
} }
const char kListIdpServiceRequested[] = "list_idp";
const char kGetTokenResponseRequested[] = "get_token";
} // namespace } // namespace
// TODO(chron): Add sourceless version of this formatter. // TODO(chron): Add sourceless version of this formatter.
...@@ -192,6 +208,7 @@ GaiaAuthFetcher::GaiaAuthFetcher(GaiaAuthConsumer* consumer, ...@@ -192,6 +208,7 @@ GaiaAuthFetcher::GaiaAuthFetcher(GaiaAuthConsumer* consumer,
GaiaUrls::GetInstance()->ListAccountsURLWithSource(source)), GaiaUrls::GetInstance()->ListAccountsURLWithSource(source)),
get_check_connection_info_url_( get_check_connection_info_url_(
GaiaUrls::GetInstance()->GetCheckConnectionInfoURLWithSource(source)), GaiaUrls::GetInstance()->GetCheckConnectionInfoURLWithSource(source)),
oauth2_iframe_url_(GaiaUrls::GetInstance()->oauth2_iframe_url()),
client_login_to_oauth2_gurl_( client_login_to_oauth2_gurl_(
GaiaUrls::GetInstance()->client_login_to_oauth2_url()), GaiaUrls::GetInstance()->client_login_to_oauth2_url()),
fetch_pending_(false) {} fetch_pending_(false) {}
...@@ -421,6 +438,45 @@ std::string GaiaAuthFetcher::MakeOAuthLoginBody(const std::string& service, ...@@ -421,6 +438,45 @@ std::string GaiaAuthFetcher::MakeOAuthLoginBody(const std::string& service,
encoded_source.c_str()); encoded_source.c_str());
} }
// static
std::string GaiaAuthFetcher::MakeListIDPSessionsBody(
const std::string& scopes,
const std::string& domain) {
static const char getTokenResponseBodyFormat[] =
"action=listSessions&"
"client_id=%s&"
"e=3100087&" // temporarily enable the experiment.
"origin=%s&"
"scope=%s";
std::string encoded_client_id = net::EscapeUrlEncodedData(
GaiaUrls::GetInstance()->oauth2_chrome_client_id(), true);
return base::StringPrintf(getTokenResponseBodyFormat,
encoded_client_id.c_str(),
domain.c_str(),
scopes.c_str());
}
std::string GaiaAuthFetcher::MakeGetTokenResponseBody(
const std::string& scopes,
const std::string& domain,
const std::string& login_hint) {
static const char getTokenResponseBodyFormat[] =
"action=issueToken&"
"client_id=%s&"
"login_hint=%s&"
"origin=%s&"
"e=3100087&" // temporarily enable the experiment.
"response_type=token&"
"scope=%s";
std::string encoded_client_id = net::EscapeUrlEncodedData(
GaiaUrls::GetInstance()->oauth2_chrome_client_id(), true);
return base::StringPrintf(getTokenResponseBodyFormat,
encoded_client_id.c_str(),
login_hint.c_str(),
domain.c_str(),
scopes.c_str());
}
// static // static
void GaiaAuthFetcher::ParseClientLoginFailure(const std::string& data, void GaiaAuthFetcher::ParseClientLoginFailure(const std::string& data,
std::string* error, std::string* error,
...@@ -484,6 +540,39 @@ bool GaiaAuthFetcher::ParseClientLoginToOAuth2Cookie(const std::string& cookie, ...@@ -484,6 +540,39 @@ bool GaiaAuthFetcher::ParseClientLoginToOAuth2Cookie(const std::string& cookie,
return false; return false;
} }
// static
bool GaiaAuthFetcher::ParseListIdpSessionsResponse(const std::string& data,
std::string* login_hint) {
DCHECK(login_hint);
scoped_ptr<base::Value> value(base::JSONReader::Read(data));
if (!value.get() || value->GetType() != base::Value::TYPE_DICTIONARY)
return false;
base::DictionaryValue* dict =
static_cast<base::DictionaryValue*>(value.get());
base::ListValue* sessionsList;
if (!dict->GetList("sessions", &sessionsList))
return false;
// Find the first login_hint present in any session.
for (base::ListValue::iterator iter = sessionsList->begin();
iter != sessionsList->end();
iter++) {
base::DictionaryValue* sessionDictionary;
if (!(*iter)->GetAsDictionary(&sessionDictionary))
continue;
if (sessionDictionary->GetString("login_hint", login_hint))
break;
}
if (login_hint->empty())
return false;
return true;
}
void GaiaAuthFetcher::StartClientLogin( void GaiaAuthFetcher::StartClientLogin(
const std::string& username, const std::string& username,
const std::string& password, const std::string& password,
...@@ -724,6 +813,40 @@ void GaiaAuthFetcher::StartGetCheckConnectionInfo() { ...@@ -724,6 +813,40 @@ void GaiaAuthFetcher::StartGetCheckConnectionInfo() {
fetcher_->Start(); fetcher_->Start();
} }
void GaiaAuthFetcher::StartListIDPSessions(const std::string& scopes,
const std::string& domain) {
DCHECK(!fetch_pending_) << "Tried to fetch two things at once!";
request_body_ = MakeListIDPSessionsBody(scopes, domain);
fetcher_.reset(CreateGaiaFetcher(getter_,
request_body_,
std::string(),
oauth2_iframe_url_,
net::LOAD_NORMAL,
this));
requested_service_ = kListIdpServiceRequested;
fetch_pending_ = true;
fetcher_->Start();
}
void GaiaAuthFetcher::StartGetTokenResponse(const std::string& scopes,
const std::string& domain,
const std::string& login_hint) {
DCHECK(!fetch_pending_) << "Tried to fetch two things at once!";
request_body_ = MakeGetTokenResponseBody(scopes, domain, login_hint);
fetcher_.reset(CreateGaiaFetcher(getter_,
request_body_,
std::string(),
oauth2_iframe_url_,
net::LOAD_NORMAL,
this));
requested_service_ = kGetTokenResponseRequested;
fetch_pending_ = true;
fetcher_->Start();
}
// static // static
GoogleServiceAuthError GaiaAuthFetcher::GenerateAuthError( GoogleServiceAuthError GaiaAuthFetcher::GenerateAuthError(
const std::string& data, const std::string& data,
...@@ -834,13 +957,8 @@ void GaiaAuthFetcher::OnOAuth2TokenPairFetched( ...@@ -834,13 +957,8 @@ void GaiaAuthFetcher::OnOAuth2TokenPairFetched(
bool success = false; bool success = false;
if (status.is_success() && response_code == net::HTTP_OK) { if (status.is_success() && response_code == net::HTTP_OK) {
scoped_ptr<base::Value> value(base::JSONReader::Read(data)); success = ExtractOAuth2TokenPairResponse(data, &refresh_token,
if (value.get() && value->GetType() == base::Value::TYPE_DICTIONARY) {
base::DictionaryValue* dict =
static_cast<base::DictionaryValue*>(value.get());
success = ExtractOAuth2TokenPairResponse(dict, &refresh_token,
&access_token, &expires_in_secs); &access_token, &expires_in_secs);
}
} }
if (success) { if (success) {
...@@ -934,6 +1052,48 @@ void GaiaAuthFetcher::OnGetCheckConnectionInfoFetched( ...@@ -934,6 +1052,48 @@ void GaiaAuthFetcher::OnGetCheckConnectionInfoFetched(
} }
} }
void GaiaAuthFetcher::OnListIdpSessionsFetched(
const std::string& data,
const net::URLRequestStatus& status,
int response_code) {
if (status.is_success() && response_code == net::HTTP_OK) {
DVLOG(1) << "ListIdpSessions successful!";
std::string login_hint;
if (ParseListIdpSessionsResponse(data, &login_hint)) {
consumer_->OnListIdpSessionsSuccess(login_hint);
} else {
GoogleServiceAuthError auth_error(
GoogleServiceAuthError::FromUnexpectedServiceResponse(
"List Sessions response didn't contain a login_hint."));
consumer_->OnListIdpSessionsError(auth_error);
}
} else {
consumer_->OnListIdpSessionsError(GenerateAuthError(data, status));
}
}
void GaiaAuthFetcher::OnGetTokenResponseFetched(
const std::string& data,
const net::URLRequestStatus& status,
int response_code) {
std::string access_token;
int expires_in_secs = 0;
bool success = false;
if (status.is_success() && response_code == net::HTTP_OK) {
DVLOG(1) << "GetTokenResponse successful!";
success = ExtractOAuth2TokenPairResponse(data, NULL,
&access_token, &expires_in_secs);
}
if (success) {
consumer_->OnGetTokenResponseSuccess(
GaiaAuthConsumer::ClientOAuthResult(std::string(), access_token,
expires_in_secs));
} else {
consumer_->OnGetTokenResponseError(GenerateAuthError(data, status));
}
}
void GaiaAuthFetcher::OnURLFetchComplete(const net::URLFetcher* source) { void GaiaAuthFetcher::OnURLFetchComplete(const net::URLFetcher* source) {
fetch_pending_ = false; fetch_pending_ = false;
// Some of the GAIA requests perform redirects, which results in the final // Some of the GAIA requests perform redirects, which results in the final
...@@ -977,6 +1137,13 @@ void GaiaAuthFetcher::OnURLFetchComplete(const net::URLFetcher* source) { ...@@ -977,6 +1137,13 @@ void GaiaAuthFetcher::OnURLFetchComplete(const net::URLFetcher* source) {
OnListAccountsFetched(data, status, response_code); OnListAccountsFetched(data, status, response_code);
} else if (url == get_check_connection_info_url_) { } else if (url == get_check_connection_info_url_) {
OnGetCheckConnectionInfoFetched(data, status, response_code); OnGetCheckConnectionInfoFetched(data, status, response_code);
} else if (url == oauth2_iframe_url_) {
if (requested_service_ == kListIdpServiceRequested)
OnListIdpSessionsFetched(data, status, response_code);
else if (requested_service_ == kGetTokenResponseRequested)
OnGetTokenResponseFetched(data, status, response_code);
else
NOTREACHED();
} else { } else {
NOTREACHED(); NOTREACHED();
} }
......
...@@ -191,6 +191,18 @@ class GaiaAuthFetcher : public net::URLFetcherDelegate { ...@@ -191,6 +191,18 @@ class GaiaAuthFetcher : public net::URLFetcherDelegate {
// /MergeSession requests. // /MergeSession requests.
void StartGetCheckConnectionInfo(); void StartGetCheckConnectionInfo();
// Starts listing any sessions that exist for the IDP. If all requested scopes
// have been approved by the session user, then a login hint is included in
// the response.
void StartListIDPSessions(const std::string& scopes,
const std::string& domain);
// Generates an access token for the session, specifying the scopes and
// |login_hint|.
void StartGetTokenResponse(const std::string& scopes,
const std::string& domain,
const std::string& login_hint);
// Implementation of net::URLFetcherDelegate // Implementation of net::URLFetcherDelegate
void OnURLFetchComplete(const net::URLFetcher* source) override; void OnURLFetchComplete(const net::URLFetcher* source) override;
...@@ -314,6 +326,14 @@ class GaiaAuthFetcher : public net::URLFetcherDelegate { ...@@ -314,6 +326,14 @@ class GaiaAuthFetcher : public net::URLFetcherDelegate {
const net::URLRequestStatus& status, const net::URLRequestStatus& status,
int response_code); int response_code);
void OnListIdpSessionsFetched(const std::string& data,
const net::URLRequestStatus& status,
int response_code);
void OnGetTokenResponseFetched(const std::string& data,
const net::URLRequestStatus& status,
int response_code);
// Tokenize the results of a ClientLogin fetch. // Tokenize the results of a ClientLogin fetch.
static void ParseClientLoginResponse(const std::string& data, static void ParseClientLoginResponse(const std::string& data,
std::string* sid, std::string* sid,
...@@ -334,6 +354,9 @@ class GaiaAuthFetcher : public net::URLFetcherDelegate { ...@@ -334,6 +354,9 @@ class GaiaAuthFetcher : public net::URLFetcherDelegate {
static bool ParseClientLoginToOAuth2Cookie(const std::string& cookie, static bool ParseClientLoginToOAuth2Cookie(const std::string& cookie,
std::string* auth_code); std::string* auth_code);
static bool ParseListIdpSessionsResponse(const std::string& data,
std::string* login_hint);
// Is this a special case Gaia error for TwoFactor auth? // Is this a special case Gaia error for TwoFactor auth?
static bool IsSecondFactorSuccess(const std::string& alleged_error); static bool IsSecondFactorSuccess(const std::string& alleged_error);
...@@ -375,6 +398,13 @@ class GaiaAuthFetcher : public net::URLFetcherDelegate { ...@@ -375,6 +398,13 @@ class GaiaAuthFetcher : public net::URLFetcherDelegate {
static std::string MakeOAuthLoginBody(const std::string& service, static std::string MakeOAuthLoginBody(const std::string& service,
const std::string& source); const std::string& source);
static std::string MakeListIDPSessionsBody(const std::string& scopes,
const std::string& domain);
static std::string MakeGetTokenResponseBody(const std::string& scopes,
const std::string& domain,
const std::string& login_hint);
// Create a fetcher usable for making any Gaia request. |body| is used // Create a fetcher usable for making any Gaia request. |body| is used
// as the body of the POST request sent to GAIA. Any strings listed in // as the body of the POST request sent to GAIA. Any strings listed in
// |headers| are added as extra HTTP headers in the request. // |headers| are added as extra HTTP headers in the request.
...@@ -410,12 +440,13 @@ class GaiaAuthFetcher : public net::URLFetcherDelegate { ...@@ -410,12 +440,13 @@ class GaiaAuthFetcher : public net::URLFetcherDelegate {
const GURL oauth_login_gurl_; const GURL oauth_login_gurl_;
const GURL list_accounts_gurl_; const GURL list_accounts_gurl_;
const GURL get_check_connection_info_url_; const GURL get_check_connection_info_url_;
const GURL oauth2_iframe_url_;
// While a fetch is going on: // While a fetch is going on:
scoped_ptr<net::URLFetcher> fetcher_; scoped_ptr<net::URLFetcher> fetcher_;
GURL client_login_to_oauth2_gurl_; GURL client_login_to_oauth2_gurl_;
std::string request_body_; std::string request_body_;
std::string requested_service_; // Currently tracked for IssueAuthToken only. std::string requested_service_;
bool fetch_pending_; bool fetch_pending_;
friend class GaiaAuthFetcherTest; friend class GaiaAuthFetcherTest;
......
...@@ -189,6 +189,9 @@ class MockGaiaConsumer : public GaiaAuthConsumer { ...@@ -189,6 +189,9 @@ class MockGaiaConsumer : public GaiaAuthConsumer {
const GoogleServiceAuthError& error)); const GoogleServiceAuthError& error));
MOCK_METHOD1(OnListAccountsSuccess, void(const std::string& data)); MOCK_METHOD1(OnListAccountsSuccess, void(const std::string& data));
MOCK_METHOD1(OnGetCheckConnectionInfoSuccess, void(const std::string& data)); MOCK_METHOD1(OnGetCheckConnectionInfoSuccess, void(const std::string& data));
MOCK_METHOD1(OnListIdpSessionsSuccess, void(const std::string& data));
MOCK_METHOD1(OnGetTokenResponseSuccess,
void(const GaiaAuthConsumer::ClientOAuthResult& result));
}; };
#if defined(OS_WIN) #if defined(OS_WIN)
...@@ -827,3 +830,37 @@ TEST_F(GaiaAuthFetcherTest, GetCheckConnectionInfo) { ...@@ -827,3 +830,37 @@ TEST_F(GaiaAuthFetcherTest, GetCheckConnectionInfo) {
status, net::HTTP_OK, cookies_, data, net::URLFetcher::GET, &auth); status, net::HTTP_OK, cookies_, data, net::URLFetcher::GET, &auth);
auth.OnURLFetchComplete(&mock_fetcher); auth.OnURLFetchComplete(&mock_fetcher);
} }
TEST_F(GaiaAuthFetcherTest, ListIDPSessions) {
std::string data("{\"sessions\":[{\"login_hint\":\"abcdefghijklmnop\"}]}");
MockGaiaConsumer consumer;
EXPECT_CALL(consumer, OnListIdpSessionsSuccess("abcdefghijklmnop")).Times(1);
GaiaAuthFetcher auth(&consumer, std::string(), GetRequestContext());
auth.StartListIDPSessions(std::string(), std::string());
net::URLRequestStatus status(net::URLRequestStatus::SUCCESS, 0);
MockFetcher mock_fetcher(
GaiaUrls::GetInstance()->oauth2_iframe_url(),
status, net::HTTP_OK, cookies_, data, net::URLFetcher::GET, &auth);
auth.OnURLFetchComplete(&mock_fetcher);
}
TEST_F(GaiaAuthFetcherTest, GetTokenResponse) {
MockGaiaConsumer consumer;
EXPECT_CALL(consumer,
OnGetTokenResponseSuccess(
GaiaAuthConsumer::ClientOAuthResult(std::string(),
"at1",
3600))).Times(1);
GaiaAuthFetcher auth(&consumer, std::string(), GetRequestContext());
auth.StartGetTokenResponse(std::string(), std::string(), std::string());
net::URLRequestStatus status(net::URLRequestStatus::SUCCESS, 0);
MockFetcher mock_fetcher(
GaiaUrls::GetInstance()->oauth2_iframe_url(),
status, net::HTTP_OK, cookies_, kGetTokenPairValidResponse,
net::URLFetcher::GET, &auth);
auth.OnURLFetchComplete(&mock_fetcher);
}
...@@ -40,6 +40,7 @@ const char kClientLoginToOAuth2UrlSuffix[] = "o/oauth2/programmatic_auth"; ...@@ -40,6 +40,7 @@ const char kClientLoginToOAuth2UrlSuffix[] = "o/oauth2/programmatic_auth";
const char kOAuth2AuthUrlSuffix[] = "o/oauth2/auth"; const char kOAuth2AuthUrlSuffix[] = "o/oauth2/auth";
const char kOAuth2RevokeUrlSuffix[] = "o/oauth2/revoke"; const char kOAuth2RevokeUrlSuffix[] = "o/oauth2/revoke";
const char kOAuth2TokenUrlSuffix[] = "o/oauth2/token"; const char kOAuth2TokenUrlSuffix[] = "o/oauth2/token";
const char kOAuth2IFrameUrlSuffix[] = "o/oauth2/iframerpc";
// API calls from www.googleapis.com // API calls from www.googleapis.com
const char kOAuth2IssueTokenUrlSuffix[] = "oauth2/v2/IssueToken"; const char kOAuth2IssueTokenUrlSuffix[] = "oauth2/v2/IssueToken";
...@@ -116,6 +117,8 @@ GaiaUrls::GaiaUrls() { ...@@ -116,6 +117,8 @@ GaiaUrls::GaiaUrls() {
oauth2_auth_url_ = lso_origin_url_.Resolve(kOAuth2AuthUrlSuffix); oauth2_auth_url_ = lso_origin_url_.Resolve(kOAuth2AuthUrlSuffix);
oauth2_token_url_ = lso_origin_url_.Resolve(kOAuth2TokenUrlSuffix); oauth2_token_url_ = lso_origin_url_.Resolve(kOAuth2TokenUrlSuffix);
oauth2_revoke_url_ = lso_origin_url_.Resolve(kOAuth2RevokeUrlSuffix); oauth2_revoke_url_ = lso_origin_url_.Resolve(kOAuth2RevokeUrlSuffix);
oauth2_iframe_url_ =
lso_origin_url_.Resolve(kOAuth2IFrameUrlSuffix);
// URLs from www.googleapis.com. // URLs from www.googleapis.com.
oauth2_issue_token_url_ = oauth2_issue_token_url_ =
...@@ -191,6 +194,10 @@ const GURL& GaiaUrls::oauth_revoke_token_url() const { ...@@ -191,6 +194,10 @@ const GURL& GaiaUrls::oauth_revoke_token_url() const {
return oauth_revoke_token_url_; return oauth_revoke_token_url_;
} }
const GURL& GaiaUrls::oauth2_iframe_url() const {
return oauth2_iframe_url_;
}
const GURL& GaiaUrls::oauth1_login_url() const { const GURL& GaiaUrls::oauth1_login_url() const {
return oauth1_login_url_; return oauth1_login_url_;
} }
......
...@@ -43,6 +43,7 @@ class GaiaUrls { ...@@ -43,6 +43,7 @@ class GaiaUrls {
const GURL& oauth2_issue_token_url() const; const GURL& oauth2_issue_token_url() const;
const GURL& oauth2_token_info_url() const; const GURL& oauth2_token_info_url() const;
const GURL& oauth2_revoke_url() const; const GURL& oauth2_revoke_url() const;
const GURL& oauth2_iframe_url() const;
const GURL& gaia_login_form_realm() const; const GURL& gaia_login_form_realm() const;
...@@ -89,6 +90,7 @@ class GaiaUrls { ...@@ -89,6 +90,7 @@ class GaiaUrls {
GURL oauth2_issue_token_url_; GURL oauth2_issue_token_url_;
GURL oauth2_token_info_url_; GURL oauth2_token_info_url_;
GURL oauth2_revoke_url_; GURL oauth2_revoke_url_;
GURL oauth2_iframe_url_;
GURL gaia_login_form_realm_; GURL gaia_login_form_realm_;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment