1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "google_apis/gaia/gaia_auth_fetcher.h"
11 #include "base/json/json_reader.h"
12 #include "base/json/json_writer.h"
13 #include "base/profiler/scoped_tracker.h"
14 #include "base/strings/string_split.h"
15 #include "base/strings/string_util.h"
16 #include "base/strings/stringprintf.h"
17 #include "base/values.h"
18 #include "google_apis/gaia/gaia_auth_consumer.h"
19 #include "google_apis/gaia/gaia_constants.h"
20 #include "google_apis/gaia/gaia_urls.h"
21 #include "google_apis/gaia/google_service_auth_error.h"
22 #include "net/base/escape.h"
23 #include "net/base/load_flags.h"
24 #include "net/http/http_response_headers.h"
25 #include "net/http/http_status_code.h"
26 #include "net/url_request/url_fetcher.h"
27 #include "net/url_request/url_request_context_getter.h"
28 #include "net/url_request/url_request_status.h"
31 const int kLoadFlagsIgnoreCookies
= net::LOAD_DO_NOT_SEND_COOKIES
|
32 net::LOAD_DO_NOT_SAVE_COOKIES
;
34 static bool CookiePartsContains(const std::vector
<std::string
>& parts
,
36 for (std::vector
<std::string
>::const_iterator it
= parts
.begin();
37 it
!= parts
.end(); ++it
) {
38 if (base::LowerCaseEqualsASCII(*it
, part
))
44 // From the JSON string |data|, extract the |access_token| and |expires_in_secs|
45 // both of which must exist. If the |refresh_token| is non-NULL, then it also
46 // must exist and is extraced; if it's NULL, then no extraction is attempted.
47 bool ExtractOAuth2TokenPairResponse(const std::string
& data
,
48 std::string
* refresh_token
,
49 std::string
* access_token
,
50 int* expires_in_secs
) {
52 DCHECK(expires_in_secs
);
54 scoped_ptr
<base::Value
> value
= base::JSONReader::Read(data
);
55 if (!value
.get() || value
->GetType() != base::Value::TYPE_DICTIONARY
)
58 base::DictionaryValue
* dict
=
59 static_cast<base::DictionaryValue
*>(value
.get());
61 if (!dict
->GetStringWithoutPathExpansion("access_token", access_token
) ||
62 !dict
->GetIntegerWithoutPathExpansion("expires_in", expires_in_secs
)) {
66 // Refresh token may not be required.
68 if (!dict
->GetStringWithoutPathExpansion("refresh_token", refresh_token
))
74 const char kListIdpServiceRequested
[] = "list_idp";
75 const char kGetTokenResponseRequested
[] = "get_token";
80 const char GaiaAuthFetcher::kIssueAuthTokenFormat
[] =
86 const char GaiaAuthFetcher::kClientLoginToOAuth2URLFormat
[] =
87 "?scope=%s&client_id=%s";
89 const char GaiaAuthFetcher::kOAuth2CodeToTokenPairBodyFormat
[] =
91 "grant_type=authorization_code&"
96 const char GaiaAuthFetcher::kOAuth2CodeToTokenPairDeviceIdParam
[] =
97 "device_id=%s&device_type=chrome";
99 const char GaiaAuthFetcher::kOAuth2RevokeTokenBodyFormat
[] =
102 const char GaiaAuthFetcher::kGetUserInfoFormat
[] =
105 const char GaiaAuthFetcher::kMergeSessionFormat
[] =
110 const char GaiaAuthFetcher::kUberAuthTokenURLFormat
[] =
114 const char GaiaAuthFetcher::kOAuthLoginFormat
[] = "service=%s&source=%s";
117 const char GaiaAuthFetcher::kAccountDeletedError
[] = "AccountDeleted";
119 const char GaiaAuthFetcher::kAccountDisabledError
[] = "AccountDisabled";
121 const char GaiaAuthFetcher::kBadAuthenticationError
[] = "BadAuthentication";
123 const char GaiaAuthFetcher::kCaptchaError
[] = "CaptchaRequired";
125 const char GaiaAuthFetcher::kServiceUnavailableError
[] =
126 "ServiceUnavailable";
128 const char GaiaAuthFetcher::kErrorParam
[] = "Error";
130 const char GaiaAuthFetcher::kErrorUrlParam
[] = "Url";
132 const char GaiaAuthFetcher::kCaptchaUrlParam
[] = "CaptchaUrl";
134 const char GaiaAuthFetcher::kCaptchaTokenParam
[] = "CaptchaToken";
137 const char GaiaAuthFetcher::kSecondFactor
[] = "Info=InvalidSecondFactor";
139 const char GaiaAuthFetcher::kWebLoginRequired
[] = "Info=WebLoginRequired";
142 const char GaiaAuthFetcher::kAuthHeaderFormat
[] =
143 "Authorization: GoogleLogin auth=%s";
145 const char GaiaAuthFetcher::kOAuthHeaderFormat
[] = "Authorization: OAuth %s";
147 const char GaiaAuthFetcher::kOAuth2BearerHeaderFormat
[] =
148 "Authorization: Bearer %s";
150 const char GaiaAuthFetcher::kDeviceIdHeaderFormat
[] = "X-Device-ID: %s";
152 const char GaiaAuthFetcher::kClientLoginToOAuth2CookiePartSecure
[] = "secure";
154 const char GaiaAuthFetcher::kClientLoginToOAuth2CookiePartHttpOnly
[] =
157 const char GaiaAuthFetcher::kClientLoginToOAuth2CookiePartCodePrefix
[] =
160 const int GaiaAuthFetcher::kClientLoginToOAuth2CookiePartCodePrefixLength
=
161 arraysize(GaiaAuthFetcher::kClientLoginToOAuth2CookiePartCodePrefix
) - 1;
163 GaiaAuthFetcher::GaiaAuthFetcher(GaiaAuthConsumer
* consumer
,
164 const std::string
& source
,
165 net::URLRequestContextGetter
* getter
)
166 : consumer_(consumer
),
169 issue_auth_token_gurl_(GaiaUrls::GetInstance()->issue_auth_token_url()),
170 oauth2_token_gurl_(GaiaUrls::GetInstance()->oauth2_token_url()),
171 oauth2_revoke_gurl_(GaiaUrls::GetInstance()->oauth2_revoke_url()),
172 get_user_info_gurl_(GaiaUrls::GetInstance()->get_user_info_url()),
173 merge_session_gurl_(GaiaUrls::GetInstance()->merge_session_url()),
174 uberauth_token_gurl_(GaiaUrls::GetInstance()->oauth1_login_url().Resolve(
175 base::StringPrintf(kUberAuthTokenURLFormat
, source
.c_str()))),
176 oauth_login_gurl_(GaiaUrls::GetInstance()->oauth1_login_url()),
178 GaiaUrls::GetInstance()->ListAccountsURLWithSource(source
)),
179 logout_gurl_(GaiaUrls::GetInstance()->LogOutURLWithSource(source
)),
180 get_check_connection_info_url_(
181 GaiaUrls::GetInstance()->GetCheckConnectionInfoURLWithSource(source
)),
182 oauth2_iframe_url_(GaiaUrls::GetInstance()->oauth2_iframe_url()),
183 client_login_to_oauth2_gurl_(
184 GaiaUrls::GetInstance()->client_login_to_oauth2_url()),
185 fetch_pending_(false) {
188 GaiaAuthFetcher::~GaiaAuthFetcher() {}
190 bool GaiaAuthFetcher::HasPendingFetch() {
191 return fetch_pending_
;
194 void GaiaAuthFetcher::SetPendingFetch(bool pending_fetch
) {
195 fetch_pending_
= pending_fetch
;
198 void GaiaAuthFetcher::CancelRequest() {
200 fetch_pending_
= false;
203 void GaiaAuthFetcher::CreateAndStartGaiaFetcher(const std::string
& body
,
204 const std::string
& headers
,
205 const GURL
& gaia_gurl
,
207 DCHECK(!fetch_pending_
) << "Tried to fetch two things at once!";
208 fetcher_
= net::URLFetcher::Create(
209 0, gaia_gurl
, body
.empty() ? net::URLFetcher::GET
: net::URLFetcher::POST
,
211 fetcher_
->SetRequestContext(getter_
);
212 fetcher_
->SetUploadData("application/x-www-form-urlencoded", body
);
214 DVLOG(2) << "Gaia fetcher URL: " << gaia_gurl
.spec();
215 DVLOG(2) << "Gaia fetcher headers: " << headers
;
216 DVLOG(2) << "Gaia fetcher body: " << body
;
218 // The Gaia token exchange requests do not require any cookie-based
219 // identification as part of requests. We suppress sending any cookies to
220 // maintain a separation between the user's browsing and Chrome's internal
221 // services. Where such mixing is desired (MergeSession or OAuthLogin), it
222 // will be done explicitly.
223 fetcher_
->SetLoadFlags(load_flags
);
225 // Fetchers are sometimes cancelled because a network change was detected,
226 // especially at startup and after sign-in on ChromeOS. Retrying once should
227 // be enough in those cases; let the fetcher retry up to 3 times just in case.
228 // http://crbug.com/163710
229 fetcher_
->SetAutomaticallyRetryOnNetworkChanges(3);
231 if (!headers
.empty())
232 fetcher_
->SetExtraRequestHeaders(headers
);
234 fetch_pending_
= true;
239 std::string
GaiaAuthFetcher::MakeIssueAuthTokenBody(
240 const std::string
& sid
,
241 const std::string
& lsid
,
242 const char* const service
) {
243 std::string encoded_sid
= net::EscapeUrlEncodedData(sid
, true);
244 std::string encoded_lsid
= net::EscapeUrlEncodedData(lsid
, true);
246 // All tokens should be session tokens except the gaia auth token.
248 if (!strcmp(service
, GaiaConstants::kGaiaService
))
251 return base::StringPrintf(kIssueAuthTokenFormat
,
253 encoded_lsid
.c_str(),
255 session
? "true" : "false");
259 std::string
GaiaAuthFetcher::MakeGetTokenPairBody(
260 const std::string
& auth_code
,
261 const std::string
& device_id
) {
262 std::string encoded_scope
= net::EscapeUrlEncodedData(
263 GaiaConstants::kOAuth1LoginScope
, true);
264 std::string encoded_client_id
= net::EscapeUrlEncodedData(
265 GaiaUrls::GetInstance()->oauth2_chrome_client_id(), true);
266 std::string encoded_client_secret
= net::EscapeUrlEncodedData(
267 GaiaUrls::GetInstance()->oauth2_chrome_client_secret(), true);
268 std::string encoded_auth_code
= net::EscapeUrlEncodedData(auth_code
, true);
269 std::string body
= base::StringPrintf(
270 kOAuth2CodeToTokenPairBodyFormat
, encoded_scope
.c_str(),
271 encoded_client_id
.c_str(), encoded_client_secret
.c_str(),
272 encoded_auth_code
.c_str());
273 if (!device_id
.empty()) {
274 body
+= "&" + base::StringPrintf(kOAuth2CodeToTokenPairDeviceIdParam
,
281 std::string
GaiaAuthFetcher::MakeRevokeTokenBody(
282 const std::string
& auth_token
) {
283 return base::StringPrintf(kOAuth2RevokeTokenBodyFormat
, auth_token
.c_str());
287 std::string
GaiaAuthFetcher::MakeGetUserInfoBody(const std::string
& lsid
) {
288 std::string encoded_lsid
= net::EscapeUrlEncodedData(lsid
, true);
289 return base::StringPrintf(kGetUserInfoFormat
, encoded_lsid
.c_str());
293 std::string
GaiaAuthFetcher::MakeMergeSessionBody(
294 const std::string
& auth_token
,
295 const std::string
& external_cc_result
,
296 const std::string
& continue_url
,
297 const std::string
& source
) {
298 std::string encoded_auth_token
= net::EscapeUrlEncodedData(auth_token
, true);
299 std::string encoded_continue_url
= net::EscapeUrlEncodedData(continue_url
,
301 std::string encoded_source
= net::EscapeUrlEncodedData(source
, true);
302 std::string result
= base::StringPrintf(kMergeSessionFormat
,
303 encoded_auth_token
.c_str(),
304 encoded_continue_url
.c_str(),
305 encoded_source
.c_str());
306 if (!external_cc_result
.empty()) {
307 base::StringAppendF(&result
, "&externalCcResult=%s",
308 net::EscapeUrlEncodedData(
309 external_cc_result
, true).c_str());
316 std::string
GaiaAuthFetcher::MakeGetAuthCodeHeader(
317 const std::string
& auth_token
) {
318 return base::StringPrintf(kAuthHeaderFormat
, auth_token
.c_str());
321 // Helper method that extracts tokens from a successful reply.
323 void GaiaAuthFetcher::ParseClientLoginResponse(const std::string
& data
,
326 std::string
* token
) {
333 base::StringPairs tokens
;
334 base::SplitStringIntoKeyValuePairs(data
, '=', '\n', &tokens
);
335 for (base::StringPairs::iterator i
= tokens
.begin();
336 i
!= tokens
.end(); ++i
) {
337 if (i
->first
== "SID") {
338 sid
->assign(i
->second
);
339 } else if (i
->first
== "LSID") {
340 lsid
->assign(i
->second
);
341 } else if (i
->first
== "Auth") {
342 token
->assign(i
->second
);
345 // If this was a request for uberauth token, then that's all we've got in
347 if (sid
->empty() && lsid
->empty() && token
->empty())
352 std::string
GaiaAuthFetcher::MakeOAuthLoginBody(const std::string
& service
,
353 const std::string
& source
) {
354 std::string encoded_service
= net::EscapeUrlEncodedData(service
, true);
355 std::string encoded_source
= net::EscapeUrlEncodedData(source
, true);
356 return base::StringPrintf(kOAuthLoginFormat
,
357 encoded_service
.c_str(),
358 encoded_source
.c_str());
362 std::string
GaiaAuthFetcher::MakeListIDPSessionsBody(
363 const std::string
& scopes
,
364 const std::string
& domain
) {
365 static const char getTokenResponseBodyFormat
[] =
366 "action=listSessions&"
370 std::string encoded_client_id
= net::EscapeUrlEncodedData(
371 GaiaUrls::GetInstance()->oauth2_chrome_client_id(), true);
372 return base::StringPrintf(getTokenResponseBodyFormat
,
373 encoded_client_id
.c_str(),
378 std::string
GaiaAuthFetcher::MakeGetTokenResponseBody(
379 const std::string
& scopes
,
380 const std::string
& domain
,
381 const std::string
& login_hint
) {
382 static const char getTokenResponseBodyFormat
[] =
387 "response_type=token&"
389 std::string encoded_client_id
= net::EscapeUrlEncodedData(
390 GaiaUrls::GetInstance()->oauth2_chrome_client_id(), true);
391 return base::StringPrintf(getTokenResponseBodyFormat
,
392 encoded_client_id
.c_str(),
399 void GaiaAuthFetcher::ParseClientLoginFailure(const std::string
& data
,
401 std::string
* error_url
,
402 std::string
* captcha_url
,
403 std::string
* captcha_token
) {
408 base::StringPairs tokens
;
409 base::SplitStringIntoKeyValuePairs(data
, '=', '\n', &tokens
);
410 for (base::StringPairs::iterator i
= tokens
.begin();
411 i
!= tokens
.end(); ++i
) {
412 if (i
->first
== kErrorParam
) {
413 error
->assign(i
->second
);
414 } else if (i
->first
== kErrorUrlParam
) {
415 error_url
->assign(i
->second
);
416 } else if (i
->first
== kCaptchaUrlParam
) {
417 captcha_url
->assign(i
->second
);
418 } else if (i
->first
== kCaptchaTokenParam
) {
419 captcha_token
->assign(i
->second
);
425 bool GaiaAuthFetcher::ParseClientLoginToOAuth2Response(
426 const net::ResponseCookies
& cookies
,
427 std::string
* auth_code
) {
429 net::ResponseCookies::const_iterator iter
;
430 for (iter
= cookies
.begin(); iter
!= cookies
.end(); ++iter
) {
431 if (ParseClientLoginToOAuth2Cookie(*iter
, auth_code
))
438 bool GaiaAuthFetcher::ParseClientLoginToOAuth2Cookie(const std::string
& cookie
,
439 std::string
* auth_code
) {
440 std::vector
<std::string
> parts
= base::SplitString(
441 cookie
, ";", base::TRIM_WHITESPACE
, base::SPLIT_WANT_ALL
);
442 // Per documentation, the cookie should have Secure and HttpOnly.
443 if (!CookiePartsContains(parts
, kClientLoginToOAuth2CookiePartSecure
) ||
444 !CookiePartsContains(parts
, kClientLoginToOAuth2CookiePartHttpOnly
)) {
448 std::vector
<std::string
>::const_iterator iter
;
449 for (iter
= parts
.begin(); iter
!= parts
.end(); ++iter
) {
450 const std::string
& part
= *iter
;
451 if (base::StartsWith(part
, kClientLoginToOAuth2CookiePartCodePrefix
,
452 base::CompareCase::INSENSITIVE_ASCII
)) {
453 auth_code
->assign(part
.substr(
454 kClientLoginToOAuth2CookiePartCodePrefixLength
));
462 bool GaiaAuthFetcher::ParseListIdpSessionsResponse(const std::string
& data
,
463 std::string
* login_hint
) {
466 scoped_ptr
<base::Value
> value
= base::JSONReader::Read(data
);
467 if (!value
.get() || value
->GetType() != base::Value::TYPE_DICTIONARY
)
470 base::DictionaryValue
* dict
=
471 static_cast<base::DictionaryValue
*>(value
.get());
473 base::ListValue
* sessionsList
;
474 if (!dict
->GetList("sessions", &sessionsList
))
477 // Find the first login_hint present in any session.
478 for (base::ListValue::iterator iter
= sessionsList
->begin();
479 iter
!= sessionsList
->end();
481 base::DictionaryValue
* sessionDictionary
;
482 if (!(*iter
)->GetAsDictionary(&sessionDictionary
))
485 if (sessionDictionary
->GetString("login_hint", login_hint
))
489 if (login_hint
->empty())
494 void GaiaAuthFetcher::StartIssueAuthToken(const std::string
& sid
,
495 const std::string
& lsid
,
496 const char* const service
) {
497 DCHECK(!fetch_pending_
) << "Tried to fetch two things at once!";
499 DVLOG(1) << "Starting IssueAuthToken for: " << service
;
500 requested_service_
= service
;
501 request_body_
= MakeIssueAuthTokenBody(sid
, lsid
, service
);
502 CreateAndStartGaiaFetcher(request_body_
, std::string(),
503 issue_auth_token_gurl_
, kLoadFlagsIgnoreCookies
);
506 void GaiaAuthFetcher::StartRevokeOAuth2Token(const std::string
& auth_token
) {
507 DCHECK(!fetch_pending_
) << "Tried to fetch two things at once!";
509 DVLOG(1) << "Starting OAuth2 token revocation";
510 request_body_
= MakeRevokeTokenBody(auth_token
);
511 CreateAndStartGaiaFetcher(request_body_
, std::string(), oauth2_revoke_gurl_
,
512 kLoadFlagsIgnoreCookies
);
515 void GaiaAuthFetcher::StartCookieForOAuthLoginTokenExchange(
516 const std::string
& session_index
) {
517 StartCookieForOAuthLoginTokenExchangeWithDeviceId(session_index
,
521 void GaiaAuthFetcher::StartCookieForOAuthLoginTokenExchangeWithDeviceId(
522 const std::string
& session_index
,
523 const std::string
& device_id
) {
524 DCHECK(!fetch_pending_
) << "Tried to fetch two things at once!";
526 DVLOG(1) << "Starting OAuth login token fetch with cookie jar";
528 std::string encoded_scope
= net::EscapeUrlEncodedData(
529 GaiaConstants::kOAuth1LoginScope
, true);
530 std::string encoded_client_id
= net::EscapeUrlEncodedData(
531 GaiaUrls::GetInstance()->oauth2_chrome_client_id(), true);
532 std::string query_string
=
533 base::StringPrintf(kClientLoginToOAuth2URLFormat
, encoded_scope
.c_str(),
534 encoded_client_id
.c_str());
535 if (!device_id
.empty())
536 query_string
+= "&device_type=chrome";
537 if (!session_index
.empty())
538 query_string
+= "&authuser=" + session_index
;
540 std::string device_id_header
;
541 if (!device_id
.empty()) {
543 base::StringPrintf(kDeviceIdHeaderFormat
, device_id
.c_str());
546 CreateAndStartGaiaFetcher(std::string(), device_id_header
,
547 client_login_to_oauth2_gurl_
.Resolve(query_string
),
551 void GaiaAuthFetcher::StartAuthCodeForOAuth2TokenExchange(
552 const std::string
& auth_code
) {
553 StartAuthCodeForOAuth2TokenExchangeWithDeviceId(auth_code
, std::string());
556 void GaiaAuthFetcher::StartAuthCodeForOAuth2TokenExchangeWithDeviceId(
557 const std::string
& auth_code
,
558 const std::string
& device_id
) {
559 DCHECK(!fetch_pending_
) << "Tried to fetch two things at once!";
561 DVLOG(1) << "Starting OAuth token pair fetch";
562 request_body_
= MakeGetTokenPairBody(auth_code
, device_id
);
563 CreateAndStartGaiaFetcher(request_body_
, std::string(), oauth2_token_gurl_
,
564 kLoadFlagsIgnoreCookies
);
567 void GaiaAuthFetcher::StartGetUserInfo(const std::string
& lsid
) {
568 DCHECK(!fetch_pending_
) << "Tried to fetch two things at once!";
570 DVLOG(1) << "Starting GetUserInfo for lsid=" << lsid
;
571 request_body_
= MakeGetUserInfoBody(lsid
);
572 CreateAndStartGaiaFetcher(request_body_
, std::string(), get_user_info_gurl_
,
573 kLoadFlagsIgnoreCookies
);
576 void GaiaAuthFetcher::StartMergeSession(const std::string
& uber_token
,
577 const std::string
& external_cc_result
) {
578 DCHECK(!fetch_pending_
) << "Tried to fetch two things at once!";
580 DVLOG(1) << "Starting MergeSession with uber_token=" << uber_token
;
582 // The continue URL is a required parameter of the MergeSession API, but in
583 // this case we don't actually need or want to navigate to it. Setting it to
584 // an arbitrary Google URL.
586 // In order for the new session to be merged correctly, the server needs to
587 // know what sessions already exist in the browser. The fetcher needs to be
588 // created such that it sends the cookies with the request, which is
589 // different from all other requests the fetcher can make.
590 std::string
continue_url("http://www.google.com");
591 request_body_
= MakeMergeSessionBody(uber_token
, external_cc_result
,
592 continue_url
, source_
);
593 CreateAndStartGaiaFetcher(request_body_
, std::string(), merge_session_gurl_
,
597 void GaiaAuthFetcher::StartTokenFetchForUberAuthExchange(
598 const std::string
& access_token
) {
599 DCHECK(!fetch_pending_
) << "Tried to fetch two things at once!";
601 DVLOG(1) << "Starting StartTokenFetchForUberAuthExchange with access_token="
603 std::string authentication_header
=
604 base::StringPrintf(kOAuthHeaderFormat
, access_token
.c_str());
605 CreateAndStartGaiaFetcher(std::string(), authentication_header
,
606 uberauth_token_gurl_
, net::LOAD_NORMAL
);
609 void GaiaAuthFetcher::StartOAuthLogin(const std::string
& access_token
,
610 const std::string
& service
) {
611 DCHECK(!fetch_pending_
) << "Tried to fetch two things at once!";
613 request_body_
= MakeOAuthLoginBody(service
, source_
);
614 std::string authentication_header
=
615 base::StringPrintf(kOAuth2BearerHeaderFormat
, access_token
.c_str());
616 CreateAndStartGaiaFetcher(request_body_
, authentication_header
,
617 oauth_login_gurl_
, net::LOAD_NORMAL
);
620 void GaiaAuthFetcher::StartListAccounts() {
621 DCHECK(!fetch_pending_
) << "Tried to fetch two things at once!";
623 CreateAndStartGaiaFetcher(" ", // To force an HTTP POST.
624 "Origin: https://www.google.com",
625 list_accounts_gurl_
, net::LOAD_NORMAL
);
628 void GaiaAuthFetcher::StartLogOut() {
629 DCHECK(!fetch_pending_
) << "Tried to fetch two things at once!";
631 CreateAndStartGaiaFetcher(std::string(), std::string(), logout_gurl_
,
635 void GaiaAuthFetcher::StartGetCheckConnectionInfo() {
636 DCHECK(!fetch_pending_
) << "Tried to fetch two things at once!";
638 CreateAndStartGaiaFetcher(std::string(), std::string(),
639 get_check_connection_info_url_
,
640 kLoadFlagsIgnoreCookies
);
643 void GaiaAuthFetcher::StartListIDPSessions(const std::string
& scopes
,
644 const std::string
& domain
) {
645 DCHECK(!fetch_pending_
) << "Tried to fetch two things at once!";
647 request_body_
= MakeListIDPSessionsBody(scopes
, domain
);
648 requested_service_
= kListIdpServiceRequested
;
649 CreateAndStartGaiaFetcher(request_body_
, std::string(), oauth2_iframe_url_
,
653 void GaiaAuthFetcher::StartGetTokenResponse(const std::string
& scopes
,
654 const std::string
& domain
,
655 const std::string
& login_hint
) {
656 DCHECK(!fetch_pending_
) << "Tried to fetch two things at once!";
658 request_body_
= MakeGetTokenResponseBody(scopes
, domain
, login_hint
);
659 requested_service_
= kGetTokenResponseRequested
;
660 CreateAndStartGaiaFetcher(request_body_
, std::string(), oauth2_iframe_url_
,
665 GoogleServiceAuthError
GaiaAuthFetcher::GenerateAuthError(
666 const std::string
& data
,
667 const net::URLRequestStatus
& status
) {
668 if (!status
.is_success()) {
669 if (status
.status() == net::URLRequestStatus::CANCELED
) {
670 return GoogleServiceAuthError(GoogleServiceAuthError::REQUEST_CANCELED
);
672 DLOG(WARNING
) << "Could not reach Google Accounts servers: errno "
674 return GoogleServiceAuthError::FromConnectionError(status
.error());
677 if (IsSecondFactorSuccess(data
))
678 return GoogleServiceAuthError(GoogleServiceAuthError::TWO_FACTOR
);
680 if (IsWebLoginRequiredSuccess(data
))
681 return GoogleServiceAuthError(GoogleServiceAuthError::WEB_LOGIN_REQUIRED
);
685 std::string captcha_url
;
686 std::string captcha_token
;
687 ParseClientLoginFailure(data
, &error
, &url
, &captcha_url
, &captcha_token
);
688 DLOG(WARNING
) << "ClientLogin failed with " << error
;
690 if (error
== kCaptchaError
) {
691 return GoogleServiceAuthError::FromClientLoginCaptchaChallenge(
693 GURL(GaiaUrls::GetInstance()->captcha_base_url().Resolve(captcha_url
)),
696 if (error
== kAccountDeletedError
)
697 return GoogleServiceAuthError(GoogleServiceAuthError::ACCOUNT_DELETED
);
698 if (error
== kAccountDisabledError
)
699 return GoogleServiceAuthError(GoogleServiceAuthError::ACCOUNT_DISABLED
);
700 if (error
== kBadAuthenticationError
) {
701 return GoogleServiceAuthError(
702 GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS
);
704 if (error
== kServiceUnavailableError
) {
705 return GoogleServiceAuthError(
706 GoogleServiceAuthError::SERVICE_UNAVAILABLE
);
709 DLOG(WARNING
) << "Incomprehensible response from Google Accounts servers.";
710 return GoogleServiceAuthError(GoogleServiceAuthError::SERVICE_UNAVAILABLE
);
713 void GaiaAuthFetcher::OnIssueAuthTokenFetched(
714 const std::string
& data
,
715 const net::URLRequestStatus
& status
,
717 if (status
.is_success() && response_code
== net::HTTP_OK
) {
718 // Only the bare token is returned in the body of this Gaia call
719 // without any padding.
720 consumer_
->OnIssueAuthTokenSuccess(requested_service_
, data
);
722 consumer_
->OnIssueAuthTokenFailure(requested_service_
,
723 GenerateAuthError(data
, status
));
727 void GaiaAuthFetcher::OnClientLoginToOAuth2Fetched(
728 const std::string
& data
,
729 const net::ResponseCookies
& cookies
,
730 const net::URLRequestStatus
& status
,
732 if (status
.is_success() && response_code
== net::HTTP_OK
) {
733 std::string auth_code
;
734 if (ParseClientLoginToOAuth2Response(cookies
, &auth_code
)) {
735 StartAuthCodeForOAuth2TokenExchange(auth_code
);
737 GoogleServiceAuthError
auth_error(
738 GoogleServiceAuthError::FromUnexpectedServiceResponse(
739 "ClientLogin response cookies didn't contain an auth code"));
740 consumer_
->OnClientOAuthFailure(auth_error
);
743 GoogleServiceAuthError
auth_error(GenerateAuthError(data
, status
));
744 consumer_
->OnClientOAuthFailure(auth_error
);
748 void GaiaAuthFetcher::OnOAuth2TokenPairFetched(
749 const std::string
& data
,
750 const net::URLRequestStatus
& status
,
752 std::string refresh_token
;
753 std::string access_token
;
754 int expires_in_secs
= 0;
756 bool success
= false;
757 if (status
.is_success() && response_code
== net::HTTP_OK
) {
758 success
= ExtractOAuth2TokenPairResponse(data
, &refresh_token
,
759 &access_token
, &expires_in_secs
);
763 consumer_
->OnClientOAuthSuccess(
764 GaiaAuthConsumer::ClientOAuthResult(refresh_token
, access_token
,
767 consumer_
->OnClientOAuthFailure(GenerateAuthError(data
, status
));
771 void GaiaAuthFetcher::OnOAuth2RevokeTokenFetched(
772 const std::string
& data
,
773 const net::URLRequestStatus
& status
,
775 consumer_
->OnOAuth2RevokeTokenCompleted();
778 void GaiaAuthFetcher::OnListAccountsFetched(const std::string
& data
,
779 const net::URLRequestStatus
& status
,
781 if (status
.is_success() && response_code
== net::HTTP_OK
) {
782 consumer_
->OnListAccountsSuccess(data
);
784 consumer_
->OnListAccountsFailure(GenerateAuthError(data
, status
));
788 void GaiaAuthFetcher::OnLogOutFetched(const std::string
& data
,
789 const net::URLRequestStatus
& status
,
791 if (status
.is_success() && response_code
== net::HTTP_OK
) {
792 consumer_
->OnLogOutSuccess();
794 consumer_
->OnLogOutFailure(GenerateAuthError(data
, status
));
798 void GaiaAuthFetcher::OnGetUserInfoFetched(
799 const std::string
& data
,
800 const net::URLRequestStatus
& status
,
802 if (status
.is_success() && response_code
== net::HTTP_OK
) {
803 base::StringPairs tokens
;
805 base::SplitStringIntoKeyValuePairs(data
, '=', '\n', &tokens
);
806 base::StringPairs::iterator i
;
807 for (i
= tokens
.begin(); i
!= tokens
.end(); ++i
) {
808 matches
[i
->first
] = i
->second
;
810 consumer_
->OnGetUserInfoSuccess(matches
);
812 consumer_
->OnGetUserInfoFailure(GenerateAuthError(data
, status
));
816 void GaiaAuthFetcher::OnMergeSessionFetched(const std::string
& data
,
817 const net::URLRequestStatus
& status
,
819 if (status
.is_success() && response_code
== net::HTTP_OK
) {
820 consumer_
->OnMergeSessionSuccess(data
);
822 consumer_
->OnMergeSessionFailure(GenerateAuthError(data
, status
));
826 void GaiaAuthFetcher::OnUberAuthTokenFetch(const std::string
& data
,
827 const net::URLRequestStatus
& status
,
829 if (status
.is_success() && response_code
== net::HTTP_OK
) {
830 consumer_
->OnUberAuthTokenSuccess(data
);
832 consumer_
->OnUberAuthTokenFailure(GenerateAuthError(data
, status
));
836 void GaiaAuthFetcher::OnOAuthLoginFetched(const std::string
& data
,
837 const net::URLRequestStatus
& status
,
839 if (status
.is_success() && response_code
== net::HTTP_OK
) {
840 DVLOG(1) << "ClientLogin successful!";
844 ParseClientLoginResponse(data
, &sid
, &lsid
, &token
);
845 consumer_
->OnClientLoginSuccess(
846 GaiaAuthConsumer::ClientLoginResult(sid
, lsid
, token
, data
));
848 consumer_
->OnClientLoginFailure(GenerateAuthError(data
, status
));
852 void GaiaAuthFetcher::OnGetCheckConnectionInfoFetched(
853 const std::string
& data
,
854 const net::URLRequestStatus
& status
,
856 if (status
.is_success() && response_code
== net::HTTP_OK
) {
857 consumer_
->OnGetCheckConnectionInfoSuccess(data
);
859 consumer_
->OnGetCheckConnectionInfoError(GenerateAuthError(data
, status
));
863 void GaiaAuthFetcher::OnListIdpSessionsFetched(
864 const std::string
& data
,
865 const net::URLRequestStatus
& status
,
867 if (status
.is_success() && response_code
== net::HTTP_OK
) {
868 DVLOG(1) << "ListIdpSessions successful!";
869 std::string login_hint
;
870 if (ParseListIdpSessionsResponse(data
, &login_hint
)) {
871 consumer_
->OnListIdpSessionsSuccess(login_hint
);
873 GoogleServiceAuthError
auth_error(
874 GoogleServiceAuthError::FromUnexpectedServiceResponse(
875 "List Sessions response didn't contain a login_hint."));
876 consumer_
->OnListIdpSessionsError(auth_error
);
879 consumer_
->OnListIdpSessionsError(GenerateAuthError(data
, status
));
883 void GaiaAuthFetcher::OnGetTokenResponseFetched(
884 const std::string
& data
,
885 const net::URLRequestStatus
& status
,
887 std::string access_token
;
888 int expires_in_secs
= 0;
889 bool success
= false;
890 if (status
.is_success() && response_code
== net::HTTP_OK
) {
891 DVLOG(1) << "GetTokenResponse successful!";
892 success
= ExtractOAuth2TokenPairResponse(data
, NULL
,
893 &access_token
, &expires_in_secs
);
897 consumer_
->OnGetTokenResponseSuccess(
898 GaiaAuthConsumer::ClientOAuthResult(std::string(), access_token
,
901 consumer_
->OnGetTokenResponseError(GenerateAuthError(data
, status
));
905 void GaiaAuthFetcher::OnURLFetchComplete(const net::URLFetcher
* source
) {
906 fetch_pending_
= false;
907 // Some of the GAIA requests perform redirects, which results in the final
908 // URL of the fetcher not being the original URL requested. Therefore use
909 // the original URL when determining which OnXXX function to call.
910 const GURL
& url
= source
->GetOriginalURL();
911 const net::URLRequestStatus
& status
= source
->GetStatus();
912 int response_code
= source
->GetResponseCode();
914 source
->GetResponseAsString(&data
);
916 // Retrieve the response headers from the request. Must only be called after
917 // the OnURLFetchComplete callback has run.
920 if (source
->GetResponseHeaders())
921 source
->GetResponseHeaders()->GetNormalizedHeaders(&headers
);
922 DVLOG(2) << "Response " << url
.spec() << ", code = " << response_code
<< "\n"
924 DVLOG(2) << "data: " << data
<< "\n";
927 DispatchFetchedRequest(url
, data
, source
->GetCookies(), status
,
931 void GaiaAuthFetcher::DispatchFetchedRequest(
933 const std::string
& data
,
934 const net::ResponseCookies
& cookies
,
935 const net::URLRequestStatus
& status
,
937 if (url
== issue_auth_token_gurl_
) {
938 OnIssueAuthTokenFetched(data
, status
, response_code
);
939 } else if (base::StartsWith(url
.spec(),
940 client_login_to_oauth2_gurl_
.spec(),
941 base::CompareCase::SENSITIVE
)) {
942 OnClientLoginToOAuth2Fetched(data
, cookies
, status
, response_code
);
943 } else if (url
== oauth2_token_gurl_
) {
944 OnOAuth2TokenPairFetched(data
, status
, response_code
);
945 } else if (url
== get_user_info_gurl_
) {
946 OnGetUserInfoFetched(data
, status
, response_code
);
947 } else if (url
== merge_session_gurl_
) {
948 OnMergeSessionFetched(data
, status
, response_code
);
949 } else if (url
== uberauth_token_gurl_
) {
950 OnUberAuthTokenFetch(data
, status
, response_code
);
951 } else if (url
== oauth_login_gurl_
) {
952 OnOAuthLoginFetched(data
, status
, response_code
);
953 } else if (url
== oauth2_revoke_gurl_
) {
954 OnOAuth2RevokeTokenFetched(data
, status
, response_code
);
955 } else if (url
== list_accounts_gurl_
) {
956 OnListAccountsFetched(data
, status
, response_code
);
957 } else if (url
== logout_gurl_
) {
958 OnLogOutFetched(data
, status
, response_code
);
959 } else if (url
== get_check_connection_info_url_
) {
960 OnGetCheckConnectionInfoFetched(data
, status
, response_code
);
961 } else if (url
== oauth2_iframe_url_
) {
962 if (requested_service_
== kListIdpServiceRequested
)
963 OnListIdpSessionsFetched(data
, status
, response_code
);
964 else if (requested_service_
== kGetTokenResponseRequested
)
965 OnGetTokenResponseFetched(data
, status
, response_code
);
974 bool GaiaAuthFetcher::IsSecondFactorSuccess(
975 const std::string
& alleged_error
) {
976 return alleged_error
.find(kSecondFactor
) !=
981 bool GaiaAuthFetcher::IsWebLoginRequiredSuccess(
982 const std::string
& alleged_error
) {
983 return alleged_error
.find(kWebLoginRequired
) !=