Merge Chromium + Blink git repositories
[chromium-blink-merge.git] / google_apis / gaia / gaia_auth_fetcher.cc
blob1423359b4ae44df62c08ce8164605be744540373
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"
7 #include <string>
8 #include <utility>
9 #include <vector>
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"
30 namespace {
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,
35 const char* part) {
36 for (std::vector<std::string>::const_iterator it = parts.begin();
37 it != parts.end(); ++it) {
38 if (base::LowerCaseEqualsASCII(*it, part))
39 return true;
41 return false;
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) {
51 DCHECK(access_token);
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)
56 return false;
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)) {
63 return false;
66 // Refresh token may not be required.
67 if (refresh_token) {
68 if (!dict->GetStringWithoutPathExpansion("refresh_token", refresh_token))
69 return false;
71 return true;
74 const char kListIdpServiceRequested[] = "list_idp";
75 const char kGetTokenResponseRequested[] = "get_token";
77 } // namespace
79 // static
80 const char GaiaAuthFetcher::kIssueAuthTokenFormat[] =
81 "SID=%s&"
82 "LSID=%s&"
83 "service=%s&"
84 "Session=%s";
85 // static
86 const char GaiaAuthFetcher::kClientLoginToOAuth2URLFormat[] =
87 "?scope=%s&client_id=%s";
88 // static
89 const char GaiaAuthFetcher::kOAuth2CodeToTokenPairBodyFormat[] =
90 "scope=%s&"
91 "grant_type=authorization_code&"
92 "client_id=%s&"
93 "client_secret=%s&"
94 "code=%s";
95 // static
96 const char GaiaAuthFetcher::kOAuth2CodeToTokenPairDeviceIdParam[] =
97 "device_id=%s&device_type=chrome";
98 // static
99 const char GaiaAuthFetcher::kOAuth2RevokeTokenBodyFormat[] =
100 "token=%s";
101 // static
102 const char GaiaAuthFetcher::kGetUserInfoFormat[] =
103 "LSID=%s";
104 // static
105 const char GaiaAuthFetcher::kMergeSessionFormat[] =
106 "uberauth=%s&"
107 "continue=%s&"
108 "source=%s";
109 // static
110 const char GaiaAuthFetcher::kUberAuthTokenURLFormat[] =
111 "?source=%s&"
112 "issueuberauth=1";
114 const char GaiaAuthFetcher::kOAuthLoginFormat[] = "service=%s&source=%s";
116 // static
117 const char GaiaAuthFetcher::kAccountDeletedError[] = "AccountDeleted";
118 // static
119 const char GaiaAuthFetcher::kAccountDisabledError[] = "AccountDisabled";
120 // static
121 const char GaiaAuthFetcher::kBadAuthenticationError[] = "BadAuthentication";
122 // static
123 const char GaiaAuthFetcher::kCaptchaError[] = "CaptchaRequired";
124 // static
125 const char GaiaAuthFetcher::kServiceUnavailableError[] =
126 "ServiceUnavailable";
127 // static
128 const char GaiaAuthFetcher::kErrorParam[] = "Error";
129 // static
130 const char GaiaAuthFetcher::kErrorUrlParam[] = "Url";
131 // static
132 const char GaiaAuthFetcher::kCaptchaUrlParam[] = "CaptchaUrl";
133 // static
134 const char GaiaAuthFetcher::kCaptchaTokenParam[] = "CaptchaToken";
136 // static
137 const char GaiaAuthFetcher::kSecondFactor[] = "Info=InvalidSecondFactor";
138 // static
139 const char GaiaAuthFetcher::kWebLoginRequired[] = "Info=WebLoginRequired";
141 // static
142 const char GaiaAuthFetcher::kAuthHeaderFormat[] =
143 "Authorization: GoogleLogin auth=%s";
144 // static
145 const char GaiaAuthFetcher::kOAuthHeaderFormat[] = "Authorization: OAuth %s";
146 // static
147 const char GaiaAuthFetcher::kOAuth2BearerHeaderFormat[] =
148 "Authorization: Bearer %s";
149 // static
150 const char GaiaAuthFetcher::kDeviceIdHeaderFormat[] = "X-Device-ID: %s";
151 // static
152 const char GaiaAuthFetcher::kClientLoginToOAuth2CookiePartSecure[] = "secure";
153 // static
154 const char GaiaAuthFetcher::kClientLoginToOAuth2CookiePartHttpOnly[] =
155 "httponly";
156 // static
157 const char GaiaAuthFetcher::kClientLoginToOAuth2CookiePartCodePrefix[] =
158 "oauth_code=";
159 // static
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),
167 getter_(getter),
168 source_(source),
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()),
177 list_accounts_gurl_(
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() {
199 fetcher_.reset();
200 fetch_pending_ = false;
203 void GaiaAuthFetcher::CreateAndStartGaiaFetcher(const std::string& body,
204 const std::string& headers,
205 const GURL& gaia_gurl,
206 int load_flags) {
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,
210 this);
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;
235 fetcher_->Start();
238 // static
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.
247 bool session = true;
248 if (!strcmp(service, GaiaConstants::kGaiaService))
249 session = false;
251 return base::StringPrintf(kIssueAuthTokenFormat,
252 encoded_sid.c_str(),
253 encoded_lsid.c_str(),
254 service,
255 session ? "true" : "false");
258 // static
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,
275 device_id.c_str());
277 return body;
280 // static
281 std::string GaiaAuthFetcher::MakeRevokeTokenBody(
282 const std::string& auth_token) {
283 return base::StringPrintf(kOAuth2RevokeTokenBodyFormat, auth_token.c_str());
286 // static
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());
292 // static
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,
300 true);
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());
312 return result;
315 // static
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.
322 // static
323 void GaiaAuthFetcher::ParseClientLoginResponse(const std::string& data,
324 std::string* sid,
325 std::string* lsid,
326 std::string* token) {
327 using std::vector;
328 using std::pair;
329 using std::string;
330 sid->clear();
331 lsid->clear();
332 token->clear();
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
346 // data.
347 if (sid->empty() && lsid->empty() && token->empty())
348 token->assign(data);
351 // static
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());
361 // static
362 std::string GaiaAuthFetcher::MakeListIDPSessionsBody(
363 const std::string& scopes,
364 const std::string& domain) {
365 static const char getTokenResponseBodyFormat[] =
366 "action=listSessions&"
367 "client_id=%s&"
368 "origin=%s&"
369 "scope=%s";
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(),
374 domain.c_str(),
375 scopes.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[] =
383 "action=issueToken&"
384 "client_id=%s&"
385 "login_hint=%s&"
386 "origin=%s&"
387 "response_type=token&"
388 "scope=%s";
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(),
393 login_hint.c_str(),
394 domain.c_str(),
395 scopes.c_str());
398 // static
399 void GaiaAuthFetcher::ParseClientLoginFailure(const std::string& data,
400 std::string* error,
401 std::string* error_url,
402 std::string* captcha_url,
403 std::string* captcha_token) {
404 using std::vector;
405 using std::pair;
406 using std::string;
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);
424 // static
425 bool GaiaAuthFetcher::ParseClientLoginToOAuth2Response(
426 const net::ResponseCookies& cookies,
427 std::string* auth_code) {
428 DCHECK(auth_code);
429 net::ResponseCookies::const_iterator iter;
430 for (iter = cookies.begin(); iter != cookies.end(); ++iter) {
431 if (ParseClientLoginToOAuth2Cookie(*iter, auth_code))
432 return true;
434 return false;
437 // static
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)) {
445 return false;
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));
455 return true;
458 return false;
461 // static
462 bool GaiaAuthFetcher::ParseListIdpSessionsResponse(const std::string& data,
463 std::string* login_hint) {
464 DCHECK(login_hint);
466 scoped_ptr<base::Value> value = base::JSONReader::Read(data);
467 if (!value.get() || value->GetType() != base::Value::TYPE_DICTIONARY)
468 return false;
470 base::DictionaryValue* dict =
471 static_cast<base::DictionaryValue*>(value.get());
473 base::ListValue* sessionsList;
474 if (!dict->GetList("sessions", &sessionsList))
475 return false;
477 // Find the first login_hint present in any session.
478 for (base::ListValue::iterator iter = sessionsList->begin();
479 iter != sessionsList->end();
480 iter++) {
481 base::DictionaryValue* sessionDictionary;
482 if (!(*iter)->GetAsDictionary(&sessionDictionary))
483 continue;
485 if (sessionDictionary->GetString("login_hint", login_hint))
486 break;
489 if (login_hint->empty())
490 return false;
491 return true;
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,
518 std::string());
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()) {
542 device_id_header =
543 base::StringPrintf(kDeviceIdHeaderFormat, device_id.c_str());
546 CreateAndStartGaiaFetcher(std::string(), device_id_header,
547 client_login_to_oauth2_gurl_.Resolve(query_string),
548 net::LOAD_NORMAL);
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_,
594 net::LOAD_NORMAL);
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="
602 << 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_,
632 net::LOAD_NORMAL);
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_,
650 net::LOAD_NORMAL);
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_,
661 net::LOAD_NORMAL);
664 // static
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 "
673 << status.error();
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);
683 std::string error;
684 std::string url;
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(
692 captcha_token,
693 GURL(GaiaUrls::GetInstance()->captcha_base_url().Resolve(captcha_url)),
694 GURL(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,
716 int response_code) {
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);
721 } else {
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,
731 int response_code) {
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);
736 } else {
737 GoogleServiceAuthError auth_error(
738 GoogleServiceAuthError::FromUnexpectedServiceResponse(
739 "ClientLogin response cookies didn't contain an auth code"));
740 consumer_->OnClientOAuthFailure(auth_error);
742 } else {
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,
751 int response_code) {
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);
762 if (success) {
763 consumer_->OnClientOAuthSuccess(
764 GaiaAuthConsumer::ClientOAuthResult(refresh_token, access_token,
765 expires_in_secs));
766 } else {
767 consumer_->OnClientOAuthFailure(GenerateAuthError(data, status));
771 void GaiaAuthFetcher::OnOAuth2RevokeTokenFetched(
772 const std::string& data,
773 const net::URLRequestStatus& status,
774 int response_code) {
775 consumer_->OnOAuth2RevokeTokenCompleted();
778 void GaiaAuthFetcher::OnListAccountsFetched(const std::string& data,
779 const net::URLRequestStatus& status,
780 int response_code) {
781 if (status.is_success() && response_code == net::HTTP_OK) {
782 consumer_->OnListAccountsSuccess(data);
783 } else {
784 consumer_->OnListAccountsFailure(GenerateAuthError(data, status));
788 void GaiaAuthFetcher::OnLogOutFetched(const std::string& data,
789 const net::URLRequestStatus& status,
790 int response_code) {
791 if (status.is_success() && response_code == net::HTTP_OK) {
792 consumer_->OnLogOutSuccess();
793 } else {
794 consumer_->OnLogOutFailure(GenerateAuthError(data, status));
798 void GaiaAuthFetcher::OnGetUserInfoFetched(
799 const std::string& data,
800 const net::URLRequestStatus& status,
801 int response_code) {
802 if (status.is_success() && response_code == net::HTTP_OK) {
803 base::StringPairs tokens;
804 UserInfoMap matches;
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);
811 } else {
812 consumer_->OnGetUserInfoFailure(GenerateAuthError(data, status));
816 void GaiaAuthFetcher::OnMergeSessionFetched(const std::string& data,
817 const net::URLRequestStatus& status,
818 int response_code) {
819 if (status.is_success() && response_code == net::HTTP_OK) {
820 consumer_->OnMergeSessionSuccess(data);
821 } else {
822 consumer_->OnMergeSessionFailure(GenerateAuthError(data, status));
826 void GaiaAuthFetcher::OnUberAuthTokenFetch(const std::string& data,
827 const net::URLRequestStatus& status,
828 int response_code) {
829 if (status.is_success() && response_code == net::HTTP_OK) {
830 consumer_->OnUberAuthTokenSuccess(data);
831 } else {
832 consumer_->OnUberAuthTokenFailure(GenerateAuthError(data, status));
836 void GaiaAuthFetcher::OnOAuthLoginFetched(const std::string& data,
837 const net::URLRequestStatus& status,
838 int response_code) {
839 if (status.is_success() && response_code == net::HTTP_OK) {
840 DVLOG(1) << "ClientLogin successful!";
841 std::string sid;
842 std::string lsid;
843 std::string token;
844 ParseClientLoginResponse(data, &sid, &lsid, &token);
845 consumer_->OnClientLoginSuccess(
846 GaiaAuthConsumer::ClientLoginResult(sid, lsid, token, data));
847 } else {
848 consumer_->OnClientLoginFailure(GenerateAuthError(data, status));
852 void GaiaAuthFetcher::OnGetCheckConnectionInfoFetched(
853 const std::string& data,
854 const net::URLRequestStatus& status,
855 int response_code) {
856 if (status.is_success() && response_code == net::HTTP_OK) {
857 consumer_->OnGetCheckConnectionInfoSuccess(data);
858 } else {
859 consumer_->OnGetCheckConnectionInfoError(GenerateAuthError(data, status));
863 void GaiaAuthFetcher::OnListIdpSessionsFetched(
864 const std::string& data,
865 const net::URLRequestStatus& status,
866 int response_code) {
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);
872 } else {
873 GoogleServiceAuthError auth_error(
874 GoogleServiceAuthError::FromUnexpectedServiceResponse(
875 "List Sessions response didn't contain a login_hint."));
876 consumer_->OnListIdpSessionsError(auth_error);
878 } else {
879 consumer_->OnListIdpSessionsError(GenerateAuthError(data, status));
883 void GaiaAuthFetcher::OnGetTokenResponseFetched(
884 const std::string& data,
885 const net::URLRequestStatus& status,
886 int response_code) {
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);
896 if (success) {
897 consumer_->OnGetTokenResponseSuccess(
898 GaiaAuthConsumer::ClientOAuthResult(std::string(), access_token,
899 expires_in_secs));
900 } else {
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();
913 std::string data;
914 source->GetResponseAsString(&data);
916 // Retrieve the response headers from the request. Must only be called after
917 // the OnURLFetchComplete callback has run.
918 #ifndef NDEBUG
919 std::string headers;
920 if (source->GetResponseHeaders())
921 source->GetResponseHeaders()->GetNormalizedHeaders(&headers);
922 DVLOG(2) << "Response " << url.spec() << ", code = " << response_code << "\n"
923 << headers << "\n";
924 DVLOG(2) << "data: " << data << "\n";
925 #endif
927 DispatchFetchedRequest(url, data, source->GetCookies(), status,
928 response_code);
931 void GaiaAuthFetcher::DispatchFetchedRequest(
932 const GURL& url,
933 const std::string& data,
934 const net::ResponseCookies& cookies,
935 const net::URLRequestStatus& status,
936 int response_code) {
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);
966 else
967 NOTREACHED();
968 } else {
969 NOTREACHED();
973 // static
974 bool GaiaAuthFetcher::IsSecondFactorSuccess(
975 const std::string& alleged_error) {
976 return alleged_error.find(kSecondFactor) !=
977 std::string::npos;
980 // static
981 bool GaiaAuthFetcher::IsWebLoginRequiredSuccess(
982 const std::string& alleged_error) {
983 return alleged_error.find(kWebLoginRequired) !=
984 std::string::npos;