Re-subimission of https://codereview.chromium.org/1041213003/
[chromium-blink-merge.git] / google_apis / gaia / gaia_auth_fetcher.cc
bloba2f7aa0e3c9d1730f6466a2ff2dc66cda08c8771
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 (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 // TODO(chron): Add sourceless version of this formatter.
80 // static
81 const char GaiaAuthFetcher::kClientLoginFormat[] =
82 "Email=%s&"
83 "Passwd=%s&"
84 "PersistentCookie=%s&"
85 "accountType=%s&"
86 "source=%s&"
87 "service=%s";
88 // static
89 const char GaiaAuthFetcher::kClientLoginCaptchaFormat[] =
90 "Email=%s&"
91 "Passwd=%s&"
92 "PersistentCookie=%s&"
93 "accountType=%s&"
94 "source=%s&"
95 "service=%s&"
96 "logintoken=%s&"
97 "logincaptcha=%s";
98 // static
99 const char GaiaAuthFetcher::kIssueAuthTokenFormat[] =
100 "SID=%s&"
101 "LSID=%s&"
102 "service=%s&"
103 "Session=%s";
104 // static
105 const char GaiaAuthFetcher::kClientLoginToOAuth2BodyFormat[] =
106 "scope=%s&client_id=%s";
107 // static
108 const char GaiaAuthFetcher::kClientLoginToOAuth2WithDeviceTypeBodyFormat[] =
109 "scope=%s&client_id=%s&device_type=chrome";
110 // static
111 const char GaiaAuthFetcher::kOAuth2CodeToTokenPairBodyFormat[] =
112 "scope=%s&"
113 "grant_type=authorization_code&"
114 "client_id=%s&"
115 "client_secret=%s&"
116 "code=%s";
117 // static
118 const char GaiaAuthFetcher::kOAuth2RevokeTokenBodyFormat[] =
119 "token=%s";
120 // static
121 const char GaiaAuthFetcher::kGetUserInfoFormat[] =
122 "LSID=%s";
123 // static
124 const char GaiaAuthFetcher::kMergeSessionFormat[] =
125 "uberauth=%s&"
126 "continue=%s&"
127 "source=%s";
128 // static
129 const char GaiaAuthFetcher::kUberAuthTokenURLFormat[] =
130 "?source=%s&"
131 "issueuberauth=1";
133 const char GaiaAuthFetcher::kOAuthLoginFormat[] = "service=%s&source=%s";
135 // static
136 const char GaiaAuthFetcher::kAccountDeletedError[] = "AccountDeleted";
137 // static
138 const char GaiaAuthFetcher::kAccountDisabledError[] = "AccountDisabled";
139 // static
140 const char GaiaAuthFetcher::kBadAuthenticationError[] = "BadAuthentication";
141 // static
142 const char GaiaAuthFetcher::kCaptchaError[] = "CaptchaRequired";
143 // static
144 const char GaiaAuthFetcher::kServiceUnavailableError[] =
145 "ServiceUnavailable";
146 // static
147 const char GaiaAuthFetcher::kErrorParam[] = "Error";
148 // static
149 const char GaiaAuthFetcher::kErrorUrlParam[] = "Url";
150 // static
151 const char GaiaAuthFetcher::kCaptchaUrlParam[] = "CaptchaUrl";
152 // static
153 const char GaiaAuthFetcher::kCaptchaTokenParam[] = "CaptchaToken";
155 // static
156 const char GaiaAuthFetcher::kCookiePersistence[] = "true";
157 // static
158 // TODO(johnnyg): When hosted accounts are supported by sync,
159 // we can always use "HOSTED_OR_GOOGLE"
160 const char GaiaAuthFetcher::kAccountTypeHostedOrGoogle[] =
161 "HOSTED_OR_GOOGLE";
162 const char GaiaAuthFetcher::kAccountTypeGoogle[] =
163 "GOOGLE";
165 // static
166 const char GaiaAuthFetcher::kSecondFactor[] = "Info=InvalidSecondFactor";
167 // static
168 const char GaiaAuthFetcher::kWebLoginRequired[] = "Info=WebLoginRequired";
170 // static
171 const char GaiaAuthFetcher::kAuthHeaderFormat[] =
172 "Authorization: GoogleLogin auth=%s";
173 // static
174 const char GaiaAuthFetcher::kOAuthHeaderFormat[] = "Authorization: OAuth %s";
175 // static
176 const char GaiaAuthFetcher::kOAuth2BearerHeaderFormat[] =
177 "Authorization: Bearer %s";
178 // static
179 const char GaiaAuthFetcher::kDeviceIdHeaderFormat[] = "X-Device-ID: %s";
180 // static
181 const char GaiaAuthFetcher::kClientLoginToOAuth2CookiePartSecure[] = "secure";
182 // static
183 const char GaiaAuthFetcher::kClientLoginToOAuth2CookiePartHttpOnly[] =
184 "httponly";
185 // static
186 const char GaiaAuthFetcher::kClientLoginToOAuth2CookiePartCodePrefix[] =
187 "oauth_code=";
188 // static
189 const int GaiaAuthFetcher::kClientLoginToOAuth2CookiePartCodePrefixLength =
190 arraysize(GaiaAuthFetcher::kClientLoginToOAuth2CookiePartCodePrefix) - 1;
192 GaiaAuthFetcher::GaiaAuthFetcher(GaiaAuthConsumer* consumer,
193 const std::string& source,
194 net::URLRequestContextGetter* getter)
195 : consumer_(consumer),
196 getter_(getter),
197 source_(source),
198 client_login_gurl_(GaiaUrls::GetInstance()->client_login_url()),
199 issue_auth_token_gurl_(GaiaUrls::GetInstance()->issue_auth_token_url()),
200 oauth2_token_gurl_(GaiaUrls::GetInstance()->oauth2_token_url()),
201 oauth2_revoke_gurl_(GaiaUrls::GetInstance()->oauth2_revoke_url()),
202 get_user_info_gurl_(GaiaUrls::GetInstance()->get_user_info_url()),
203 merge_session_gurl_(GaiaUrls::GetInstance()->merge_session_url()),
204 uberauth_token_gurl_(GaiaUrls::GetInstance()->oauth1_login_url().Resolve(
205 base::StringPrintf(kUberAuthTokenURLFormat, source.c_str()))),
206 oauth_login_gurl_(GaiaUrls::GetInstance()->oauth1_login_url()),
207 list_accounts_gurl_(
208 GaiaUrls::GetInstance()->ListAccountsURLWithSource(source)),
209 get_check_connection_info_url_(
210 GaiaUrls::GetInstance()->GetCheckConnectionInfoURLWithSource(source)),
211 oauth2_iframe_url_(GaiaUrls::GetInstance()->oauth2_iframe_url()),
212 client_login_to_oauth2_gurl_(
213 GaiaUrls::GetInstance()->client_login_to_oauth2_url()),
214 fetch_pending_(false) {}
216 GaiaAuthFetcher::~GaiaAuthFetcher() {}
218 bool GaiaAuthFetcher::HasPendingFetch() {
219 return fetch_pending_;
222 void GaiaAuthFetcher::CancelRequest() {
223 fetcher_.reset();
224 fetch_pending_ = false;
227 // static
228 net::URLFetcher* GaiaAuthFetcher::CreateGaiaFetcher(
229 net::URLRequestContextGetter* getter,
230 const std::string& body,
231 const std::string& headers,
232 const GURL& gaia_gurl,
233 int load_flags,
234 net::URLFetcherDelegate* delegate) {
235 net::URLFetcher* to_return = net::URLFetcher::Create(
236 0, gaia_gurl,
237 body.empty() ? net::URLFetcher::GET : net::URLFetcher::POST,
238 delegate);
239 to_return->SetRequestContext(getter);
240 to_return->SetUploadData("application/x-www-form-urlencoded", body);
242 DVLOG(2) << "Gaia fetcher URL: " << gaia_gurl.spec();
243 DVLOG(2) << "Gaia fetcher headers: " << headers;
244 DVLOG(2) << "Gaia fetcher body: " << body;
246 // The Gaia token exchange requests do not require any cookie-based
247 // identification as part of requests. We suppress sending any cookies to
248 // maintain a separation between the user's browsing and Chrome's internal
249 // services. Where such mixing is desired (MergeSession or OAuthLogin), it
250 // will be done explicitly.
251 to_return->SetLoadFlags(load_flags);
253 // Fetchers are sometimes cancelled because a network change was detected,
254 // especially at startup and after sign-in on ChromeOS. Retrying once should
255 // be enough in those cases; let the fetcher retry up to 3 times just in case.
256 // http://crbug.com/163710
257 to_return->SetAutomaticallyRetryOnNetworkChanges(3);
259 if (!headers.empty())
260 to_return->SetExtraRequestHeaders(headers);
262 return to_return;
265 // static
266 std::string GaiaAuthFetcher::MakeClientLoginBody(
267 const std::string& username,
268 const std::string& password,
269 const std::string& source,
270 const char* service,
271 const std::string& login_token,
272 const std::string& login_captcha,
273 HostedAccountsSetting allow_hosted_accounts) {
274 std::string encoded_username = net::EscapeUrlEncodedData(username, true);
275 std::string encoded_password = net::EscapeUrlEncodedData(password, true);
276 std::string encoded_login_token = net::EscapeUrlEncodedData(login_token,
277 true);
278 std::string encoded_login_captcha = net::EscapeUrlEncodedData(login_captcha,
279 true);
281 const char* account_type = allow_hosted_accounts == HostedAccountsAllowed ?
282 kAccountTypeHostedOrGoogle :
283 kAccountTypeGoogle;
285 if (login_token.empty() || login_captcha.empty()) {
286 return base::StringPrintf(kClientLoginFormat,
287 encoded_username.c_str(),
288 encoded_password.c_str(),
289 kCookiePersistence,
290 account_type,
291 source.c_str(),
292 service);
295 return base::StringPrintf(kClientLoginCaptchaFormat,
296 encoded_username.c_str(),
297 encoded_password.c_str(),
298 kCookiePersistence,
299 account_type,
300 source.c_str(),
301 service,
302 encoded_login_token.c_str(),
303 encoded_login_captcha.c_str());
306 // static
307 std::string GaiaAuthFetcher::MakeIssueAuthTokenBody(
308 const std::string& sid,
309 const std::string& lsid,
310 const char* const service) {
311 std::string encoded_sid = net::EscapeUrlEncodedData(sid, true);
312 std::string encoded_lsid = net::EscapeUrlEncodedData(lsid, true);
314 // All tokens should be session tokens except the gaia auth token.
315 bool session = true;
316 if (!strcmp(service, GaiaConstants::kGaiaService))
317 session = false;
319 return base::StringPrintf(kIssueAuthTokenFormat,
320 encoded_sid.c_str(),
321 encoded_lsid.c_str(),
322 service,
323 session ? "true" : "false");
326 // static
327 std::string GaiaAuthFetcher::MakeGetAuthCodeBody(bool include_device_type) {
328 std::string encoded_scope = net::EscapeUrlEncodedData(
329 GaiaConstants::kOAuth1LoginScope, true);
330 std::string encoded_client_id = net::EscapeUrlEncodedData(
331 GaiaUrls::GetInstance()->oauth2_chrome_client_id(), true);
332 if (include_device_type) {
333 return base::StringPrintf(kClientLoginToOAuth2WithDeviceTypeBodyFormat,
334 encoded_scope.c_str(),
335 encoded_client_id.c_str());
336 } else {
337 return base::StringPrintf(kClientLoginToOAuth2BodyFormat,
338 encoded_scope.c_str(),
339 encoded_client_id.c_str());
343 // static
344 std::string GaiaAuthFetcher::MakeGetTokenPairBody(
345 const std::string& auth_code) {
346 std::string encoded_scope = net::EscapeUrlEncodedData(
347 GaiaConstants::kOAuth1LoginScope, true);
348 std::string encoded_client_id = net::EscapeUrlEncodedData(
349 GaiaUrls::GetInstance()->oauth2_chrome_client_id(), true);
350 std::string encoded_client_secret = net::EscapeUrlEncodedData(
351 GaiaUrls::GetInstance()->oauth2_chrome_client_secret(), true);
352 std::string encoded_auth_code = net::EscapeUrlEncodedData(auth_code, true);
353 return base::StringPrintf(kOAuth2CodeToTokenPairBodyFormat,
354 encoded_scope.c_str(),
355 encoded_client_id.c_str(),
356 encoded_client_secret.c_str(),
357 encoded_auth_code.c_str());
360 // static
361 std::string GaiaAuthFetcher::MakeRevokeTokenBody(
362 const std::string& auth_token) {
363 return base::StringPrintf(kOAuth2RevokeTokenBodyFormat, auth_token.c_str());
366 // static
367 std::string GaiaAuthFetcher::MakeGetUserInfoBody(const std::string& lsid) {
368 std::string encoded_lsid = net::EscapeUrlEncodedData(lsid, true);
369 return base::StringPrintf(kGetUserInfoFormat, encoded_lsid.c_str());
372 // static
373 std::string GaiaAuthFetcher::MakeMergeSessionBody(
374 const std::string& auth_token,
375 const std::string& external_cc_result,
376 const std::string& continue_url,
377 const std::string& source) {
378 std::string encoded_auth_token = net::EscapeUrlEncodedData(auth_token, true);
379 std::string encoded_continue_url = net::EscapeUrlEncodedData(continue_url,
380 true);
381 std::string encoded_source = net::EscapeUrlEncodedData(source, true);
382 std::string result = base::StringPrintf(kMergeSessionFormat,
383 encoded_auth_token.c_str(),
384 encoded_continue_url.c_str(),
385 encoded_source.c_str());
386 if (!external_cc_result.empty()) {
387 base::StringAppendF(&result, "&externalCcResult=%s",
388 net::EscapeUrlEncodedData(
389 external_cc_result, true).c_str());
392 return result;
395 // static
396 std::string GaiaAuthFetcher::MakeGetAuthCodeHeader(
397 const std::string& auth_token) {
398 return base::StringPrintf(kAuthHeaderFormat, auth_token.c_str());
401 // Helper method that extracts tokens from a successful reply.
402 // static
403 void GaiaAuthFetcher::ParseClientLoginResponse(const std::string& data,
404 std::string* sid,
405 std::string* lsid,
406 std::string* token) {
407 using std::vector;
408 using std::pair;
409 using std::string;
410 sid->clear();
411 lsid->clear();
412 token->clear();
413 vector<pair<string, string> > tokens;
414 base::SplitStringIntoKeyValuePairs(data, '=', '\n', &tokens);
415 for (vector<pair<string, string> >::iterator i = tokens.begin();
416 i != tokens.end(); ++i) {
417 if (i->first == "SID") {
418 sid->assign(i->second);
419 } else if (i->first == "LSID") {
420 lsid->assign(i->second);
421 } else if (i->first == "Auth") {
422 token->assign(i->second);
425 // If this was a request for uberauth token, then that's all we've got in
426 // data.
427 if (sid->empty() && lsid->empty() && token->empty())
428 token->assign(data);
431 // static
432 std::string GaiaAuthFetcher::MakeOAuthLoginBody(const std::string& service,
433 const std::string& source) {
434 std::string encoded_service = net::EscapeUrlEncodedData(service, true);
435 std::string encoded_source = net::EscapeUrlEncodedData(source, true);
436 return base::StringPrintf(kOAuthLoginFormat,
437 encoded_service.c_str(),
438 encoded_source.c_str());
441 // static
442 std::string GaiaAuthFetcher::MakeListIDPSessionsBody(
443 const std::string& scopes,
444 const std::string& domain) {
445 static const char getTokenResponseBodyFormat[] =
446 "action=listSessions&"
447 "client_id=%s&"
448 "origin=%s&"
449 "scope=%s";
450 std::string encoded_client_id = net::EscapeUrlEncodedData(
451 GaiaUrls::GetInstance()->oauth2_chrome_client_id(), true);
452 return base::StringPrintf(getTokenResponseBodyFormat,
453 encoded_client_id.c_str(),
454 domain.c_str(),
455 scopes.c_str());
458 std::string GaiaAuthFetcher::MakeGetTokenResponseBody(
459 const std::string& scopes,
460 const std::string& domain,
461 const std::string& login_hint) {
462 static const char getTokenResponseBodyFormat[] =
463 "action=issueToken&"
464 "client_id=%s&"
465 "login_hint=%s&"
466 "origin=%s&"
467 "response_type=token&"
468 "scope=%s";
469 std::string encoded_client_id = net::EscapeUrlEncodedData(
470 GaiaUrls::GetInstance()->oauth2_chrome_client_id(), true);
471 return base::StringPrintf(getTokenResponseBodyFormat,
472 encoded_client_id.c_str(),
473 login_hint.c_str(),
474 domain.c_str(),
475 scopes.c_str());
478 // static
479 void GaiaAuthFetcher::ParseClientLoginFailure(const std::string& data,
480 std::string* error,
481 std::string* error_url,
482 std::string* captcha_url,
483 std::string* captcha_token) {
484 using std::vector;
485 using std::pair;
486 using std::string;
488 vector<pair<string, string> > tokens;
489 base::SplitStringIntoKeyValuePairs(data, '=', '\n', &tokens);
490 for (vector<pair<string, string> >::iterator i = tokens.begin();
491 i != tokens.end(); ++i) {
492 if (i->first == kErrorParam) {
493 error->assign(i->second);
494 } else if (i->first == kErrorUrlParam) {
495 error_url->assign(i->second);
496 } else if (i->first == kCaptchaUrlParam) {
497 captcha_url->assign(i->second);
498 } else if (i->first == kCaptchaTokenParam) {
499 captcha_token->assign(i->second);
504 // static
505 bool GaiaAuthFetcher::ParseClientLoginToOAuth2Response(
506 const net::ResponseCookies& cookies,
507 std::string* auth_code) {
508 DCHECK(auth_code);
509 net::ResponseCookies::const_iterator iter;
510 for (iter = cookies.begin(); iter != cookies.end(); ++iter) {
511 if (ParseClientLoginToOAuth2Cookie(*iter, auth_code))
512 return true;
514 return false;
517 // static
518 bool GaiaAuthFetcher::ParseClientLoginToOAuth2Cookie(const std::string& cookie,
519 std::string* auth_code) {
520 std::vector<std::string> parts;
521 base::SplitString(cookie, ';', &parts);
522 // Per documentation, the cookie should have Secure and HttpOnly.
523 if (!CookiePartsContains(parts, kClientLoginToOAuth2CookiePartSecure) ||
524 !CookiePartsContains(parts, kClientLoginToOAuth2CookiePartHttpOnly)) {
525 return false;
528 std::vector<std::string>::const_iterator iter;
529 for (iter = parts.begin(); iter != parts.end(); ++iter) {
530 const std::string& part = *iter;
531 if (StartsWithASCII(
532 part, kClientLoginToOAuth2CookiePartCodePrefix, false)) {
533 auth_code->assign(part.substr(
534 kClientLoginToOAuth2CookiePartCodePrefixLength));
535 return true;
538 return false;
541 // static
542 bool GaiaAuthFetcher::ParseListIdpSessionsResponse(const std::string& data,
543 std::string* login_hint) {
544 DCHECK(login_hint);
546 scoped_ptr<base::Value> value(base::JSONReader::Read(data));
547 if (!value.get() || value->GetType() != base::Value::TYPE_DICTIONARY)
548 return false;
550 base::DictionaryValue* dict =
551 static_cast<base::DictionaryValue*>(value.get());
553 base::ListValue* sessionsList;
554 if (!dict->GetList("sessions", &sessionsList))
555 return false;
557 // Find the first login_hint present in any session.
558 for (base::ListValue::iterator iter = sessionsList->begin();
559 iter != sessionsList->end();
560 iter++) {
561 base::DictionaryValue* sessionDictionary;
562 if (!(*iter)->GetAsDictionary(&sessionDictionary))
563 continue;
565 if (sessionDictionary->GetString("login_hint", login_hint))
566 break;
569 if (login_hint->empty())
570 return false;
571 return true;
574 void GaiaAuthFetcher::StartClientLogin(
575 const std::string& username,
576 const std::string& password,
577 const char* const service,
578 const std::string& login_token,
579 const std::string& login_captcha,
580 HostedAccountsSetting allow_hosted_accounts) {
582 DCHECK(!fetch_pending_) << "Tried to fetch two things at once!";
584 // This class is thread agnostic, so be sure to call this only on the
585 // same thread each time.
586 DVLOG(1) << "Starting new ClientLogin fetch for:" << username;
588 // Must outlive fetcher_.
589 request_body_ = MakeClientLoginBody(username,
590 password,
591 source_,
592 service,
593 login_token,
594 login_captcha,
595 allow_hosted_accounts);
596 fetcher_.reset(CreateGaiaFetcher(getter_,
597 request_body_,
598 std::string(),
599 client_login_gurl_,
600 kLoadFlagsIgnoreCookies,
601 this));
602 fetch_pending_ = true;
603 fetcher_->Start();
606 void GaiaAuthFetcher::StartIssueAuthToken(const std::string& sid,
607 const std::string& lsid,
608 const char* const service) {
609 DCHECK(!fetch_pending_) << "Tried to fetch two things at once!";
611 DVLOG(1) << "Starting IssueAuthToken for: " << service;
612 requested_service_ = service;
613 request_body_ = MakeIssueAuthTokenBody(sid, lsid, service);
614 fetcher_.reset(CreateGaiaFetcher(getter_,
615 request_body_,
616 std::string(),
617 issue_auth_token_gurl_,
618 kLoadFlagsIgnoreCookies,
619 this));
620 fetch_pending_ = true;
621 fetcher_->Start();
624 void GaiaAuthFetcher::StartLsoForOAuthLoginTokenExchange(
625 const std::string& auth_token) {
626 DCHECK(!fetch_pending_) << "Tried to fetch two things at once!";
628 DVLOG(1) << "Starting OAuth login token exchange with auth_token";
629 request_body_ = MakeGetAuthCodeBody(false);
630 client_login_to_oauth2_gurl_ =
631 GaiaUrls::GetInstance()->client_login_to_oauth2_url();
633 fetcher_.reset(CreateGaiaFetcher(getter_,
634 request_body_,
635 MakeGetAuthCodeHeader(auth_token),
636 client_login_to_oauth2_gurl_,
637 kLoadFlagsIgnoreCookies,
638 this));
639 fetch_pending_ = true;
640 fetcher_->Start();
643 void GaiaAuthFetcher::StartRevokeOAuth2Token(const std::string& auth_token) {
644 DCHECK(!fetch_pending_) << "Tried to fetch two things at once!";
646 DVLOG(1) << "Starting OAuth2 token revocation";
647 request_body_ = MakeRevokeTokenBody(auth_token);
648 fetcher_.reset(CreateGaiaFetcher(getter_,
649 request_body_,
650 std::string(),
651 oauth2_revoke_gurl_,
652 kLoadFlagsIgnoreCookies,
653 this));
654 fetch_pending_ = true;
655 fetcher_->Start();
658 void GaiaAuthFetcher::StartCookieForOAuthLoginTokenExchange(
659 const std::string& session_index) {
660 StartCookieForOAuthLoginTokenExchangeWithDeviceId(session_index,
661 std::string());
664 void GaiaAuthFetcher::StartCookieForOAuthLoginTokenExchangeWithDeviceId(
665 const std::string& session_index,
666 const std::string& device_id) {
667 DCHECK(!fetch_pending_) << "Tried to fetch two things at once!";
669 DVLOG(1) << "Starting OAuth login token fetch with cookie jar";
670 request_body_ = MakeGetAuthCodeBody(!device_id.empty());
672 client_login_to_oauth2_gurl_ =
673 GaiaUrls::GetInstance()->client_login_to_oauth2_url();
674 if (!session_index.empty()) {
675 client_login_to_oauth2_gurl_ =
676 client_login_to_oauth2_gurl_.Resolve("?authuser=" + session_index);
679 std::string device_id_header;
680 if (!device_id.empty()) {
681 device_id_header =
682 base::StringPrintf(kDeviceIdHeaderFormat, device_id.c_str());
685 fetcher_.reset(CreateGaiaFetcher(getter_,
686 request_body_,
687 device_id_header,
688 client_login_to_oauth2_gurl_,
689 net::LOAD_NORMAL,
690 this));
691 fetch_pending_ = true;
692 fetcher_->Start();
695 void GaiaAuthFetcher::StartAuthCodeForOAuth2TokenExchange(
696 const std::string& auth_code) {
697 DCHECK(!fetch_pending_) << "Tried to fetch two things at once!";
699 DVLOG(1) << "Starting OAuth token pair fetch";
700 request_body_ = MakeGetTokenPairBody(auth_code);
701 fetcher_.reset(CreateGaiaFetcher(getter_,
702 request_body_,
703 std::string(),
704 oauth2_token_gurl_,
705 kLoadFlagsIgnoreCookies,
706 this));
707 fetch_pending_ = true;
708 fetcher_->Start();
711 void GaiaAuthFetcher::StartGetUserInfo(const std::string& lsid) {
712 DCHECK(!fetch_pending_) << "Tried to fetch two things at once!";
714 DVLOG(1) << "Starting GetUserInfo for lsid=" << lsid;
715 request_body_ = MakeGetUserInfoBody(lsid);
716 fetcher_.reset(CreateGaiaFetcher(getter_,
717 request_body_,
718 std::string(),
719 get_user_info_gurl_,
720 kLoadFlagsIgnoreCookies,
721 this));
722 fetch_pending_ = true;
723 fetcher_->Start();
726 void GaiaAuthFetcher::StartMergeSession(const std::string& uber_token,
727 const std::string& external_cc_result) {
728 DCHECK(!fetch_pending_) << "Tried to fetch two things at once!";
730 DVLOG(1) << "Starting MergeSession with uber_token=" << uber_token;
732 // The continue URL is a required parameter of the MergeSession API, but in
733 // this case we don't actually need or want to navigate to it. Setting it to
734 // an arbitrary Google URL.
736 // In order for the new session to be merged correctly, the server needs to
737 // know what sessions already exist in the browser. The fetcher needs to be
738 // created such that it sends the cookies with the request, which is
739 // different from all other requests the fetcher can make.
740 std::string continue_url("http://www.google.com");
741 request_body_ = MakeMergeSessionBody(uber_token, external_cc_result,
742 continue_url, source_);
743 fetcher_.reset(CreateGaiaFetcher(getter_,
744 request_body_,
745 std::string(),
746 merge_session_gurl_,
747 net::LOAD_NORMAL,
748 this));
749 fetch_pending_ = true;
750 fetcher_->Start();
753 void GaiaAuthFetcher::StartTokenFetchForUberAuthExchange(
754 const std::string& access_token) {
755 DCHECK(!fetch_pending_) << "Tried to fetch two things at once!";
757 DVLOG(1) << "Starting StartTokenFetchForUberAuthExchange with access_token="
758 << access_token;
759 std::string authentication_header =
760 base::StringPrintf(kOAuthHeaderFormat, access_token.c_str());
761 fetcher_.reset(CreateGaiaFetcher(getter_,
762 std::string(),
763 authentication_header,
764 uberauth_token_gurl_,
765 net::LOAD_NORMAL,
766 this));
767 fetch_pending_ = true;
768 fetcher_->Start();
771 void GaiaAuthFetcher::StartOAuthLogin(const std::string& access_token,
772 const std::string& service) {
773 DCHECK(!fetch_pending_) << "Tried to fetch two things at once!";
775 request_body_ = MakeOAuthLoginBody(service, source_);
776 std::string authentication_header =
777 base::StringPrintf(kOAuth2BearerHeaderFormat, access_token.c_str());
778 fetcher_.reset(CreateGaiaFetcher(getter_,
779 request_body_,
780 authentication_header,
781 oauth_login_gurl_,
782 net::LOAD_NORMAL,
783 this));
784 fetch_pending_ = true;
785 fetcher_->Start();
788 void GaiaAuthFetcher::StartListAccounts() {
789 DCHECK(!fetch_pending_) << "Tried to fetch two things at once!";
791 fetcher_.reset(CreateGaiaFetcher(getter_,
792 " ", // To force an HTTP POST.
793 "Origin: https://www.google.com",
794 list_accounts_gurl_,
795 net::LOAD_NORMAL,
796 this));
797 fetch_pending_ = true;
798 fetcher_->Start();
801 void GaiaAuthFetcher::StartGetCheckConnectionInfo() {
802 DCHECK(!fetch_pending_) << "Tried to fetch two things at once!";
804 fetcher_.reset(CreateGaiaFetcher(getter_,
805 std::string(),
806 std::string(),
807 get_check_connection_info_url_,
808 kLoadFlagsIgnoreCookies,
809 this));
810 fetch_pending_ = true;
811 fetcher_->Start();
814 void GaiaAuthFetcher::StartListIDPSessions(const std::string& scopes,
815 const std::string& domain) {
816 DCHECK(!fetch_pending_) << "Tried to fetch two things at once!";
818 request_body_ = MakeListIDPSessionsBody(scopes, domain);
819 fetcher_.reset(CreateGaiaFetcher(getter_,
820 request_body_,
821 std::string(),
822 oauth2_iframe_url_,
823 net::LOAD_NORMAL,
824 this));
825 requested_service_ = kListIdpServiceRequested;
826 fetch_pending_ = true;
827 fetcher_->Start();
830 void GaiaAuthFetcher::StartGetTokenResponse(const std::string& scopes,
831 const std::string& domain,
832 const std::string& login_hint) {
833 DCHECK(!fetch_pending_) << "Tried to fetch two things at once!";
835 request_body_ = MakeGetTokenResponseBody(scopes, domain, login_hint);
836 fetcher_.reset(CreateGaiaFetcher(getter_,
837 request_body_,
838 std::string(),
839 oauth2_iframe_url_,
840 net::LOAD_NORMAL,
841 this));
843 requested_service_ = kGetTokenResponseRequested;
844 fetch_pending_ = true;
845 fetcher_->Start();
848 // static
849 GoogleServiceAuthError GaiaAuthFetcher::GenerateAuthError(
850 const std::string& data,
851 const net::URLRequestStatus& status) {
852 if (!status.is_success()) {
853 if (status.status() == net::URLRequestStatus::CANCELED) {
854 return GoogleServiceAuthError(GoogleServiceAuthError::REQUEST_CANCELED);
856 DLOG(WARNING) << "Could not reach Google Accounts servers: errno "
857 << status.error();
858 return GoogleServiceAuthError::FromConnectionError(status.error());
861 if (IsSecondFactorSuccess(data))
862 return GoogleServiceAuthError(GoogleServiceAuthError::TWO_FACTOR);
864 if (IsWebLoginRequiredSuccess(data))
865 return GoogleServiceAuthError(GoogleServiceAuthError::WEB_LOGIN_REQUIRED);
867 std::string error;
868 std::string url;
869 std::string captcha_url;
870 std::string captcha_token;
871 ParseClientLoginFailure(data, &error, &url, &captcha_url, &captcha_token);
872 DLOG(WARNING) << "ClientLogin failed with " << error;
874 if (error == kCaptchaError) {
875 return GoogleServiceAuthError::FromClientLoginCaptchaChallenge(
876 captcha_token,
877 GURL(GaiaUrls::GetInstance()->captcha_base_url().Resolve(captcha_url)),
878 GURL(url));
880 if (error == kAccountDeletedError)
881 return GoogleServiceAuthError(GoogleServiceAuthError::ACCOUNT_DELETED);
882 if (error == kAccountDisabledError)
883 return GoogleServiceAuthError(GoogleServiceAuthError::ACCOUNT_DISABLED);
884 if (error == kBadAuthenticationError) {
885 return GoogleServiceAuthError(
886 GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS);
888 if (error == kServiceUnavailableError) {
889 return GoogleServiceAuthError(
890 GoogleServiceAuthError::SERVICE_UNAVAILABLE);
893 DLOG(WARNING) << "Incomprehensible response from Google Accounts servers.";
894 return GoogleServiceAuthError(GoogleServiceAuthError::SERVICE_UNAVAILABLE);
897 void GaiaAuthFetcher::OnClientLoginFetched(const std::string& data,
898 const net::URLRequestStatus& status,
899 int response_code) {
900 if (status.is_success() && response_code == net::HTTP_OK) {
901 DVLOG(1) << "ClientLogin successful!";
902 std::string sid;
903 std::string lsid;
904 std::string token;
905 ParseClientLoginResponse(data, &sid, &lsid, &token);
906 consumer_->OnClientLoginSuccess(
907 GaiaAuthConsumer::ClientLoginResult(sid, lsid, token, data));
908 } else {
909 consumer_->OnClientLoginFailure(GenerateAuthError(data, status));
913 void GaiaAuthFetcher::OnIssueAuthTokenFetched(
914 const std::string& data,
915 const net::URLRequestStatus& status,
916 int response_code) {
917 if (status.is_success() && response_code == net::HTTP_OK) {
918 // Only the bare token is returned in the body of this Gaia call
919 // without any padding.
920 consumer_->OnIssueAuthTokenSuccess(requested_service_, data);
921 } else {
922 consumer_->OnIssueAuthTokenFailure(requested_service_,
923 GenerateAuthError(data, status));
927 void GaiaAuthFetcher::OnClientLoginToOAuth2Fetched(
928 const std::string& data,
929 const net::ResponseCookies& cookies,
930 const net::URLRequestStatus& status,
931 int response_code) {
932 if (status.is_success() && response_code == net::HTTP_OK) {
933 std::string auth_code;
934 if (ParseClientLoginToOAuth2Response(cookies, &auth_code)) {
935 StartAuthCodeForOAuth2TokenExchange(auth_code);
936 } else {
937 GoogleServiceAuthError auth_error(
938 GoogleServiceAuthError::FromUnexpectedServiceResponse(
939 "ClientLogin response cookies didn't contain an auth code"));
940 consumer_->OnClientOAuthFailure(auth_error);
942 } else {
943 GoogleServiceAuthError auth_error(GenerateAuthError(data, status));
944 consumer_->OnClientOAuthFailure(auth_error);
948 void GaiaAuthFetcher::OnOAuth2TokenPairFetched(
949 const std::string& data,
950 const net::URLRequestStatus& status,
951 int response_code) {
952 std::string refresh_token;
953 std::string access_token;
954 int expires_in_secs = 0;
956 bool success = false;
957 if (status.is_success() && response_code == net::HTTP_OK) {
958 success = ExtractOAuth2TokenPairResponse(data, &refresh_token,
959 &access_token, &expires_in_secs);
962 if (success) {
963 consumer_->OnClientOAuthSuccess(
964 GaiaAuthConsumer::ClientOAuthResult(refresh_token, access_token,
965 expires_in_secs));
966 } else {
967 consumer_->OnClientOAuthFailure(GenerateAuthError(data, status));
971 void GaiaAuthFetcher::OnOAuth2RevokeTokenFetched(
972 const std::string& data,
973 const net::URLRequestStatus& status,
974 int response_code) {
975 consumer_->OnOAuth2RevokeTokenCompleted();
978 void GaiaAuthFetcher::OnListAccountsFetched(const std::string& data,
979 const net::URLRequestStatus& status,
980 int response_code) {
981 if (status.is_success() && response_code == net::HTTP_OK) {
982 consumer_->OnListAccountsSuccess(data);
983 } else {
984 consumer_->OnListAccountsFailure(GenerateAuthError(data, status));
988 void GaiaAuthFetcher::OnGetUserInfoFetched(
989 const std::string& data,
990 const net::URLRequestStatus& status,
991 int response_code) {
992 if (status.is_success() && response_code == net::HTTP_OK) {
993 base::StringPairs tokens;
994 UserInfoMap matches;
995 base::SplitStringIntoKeyValuePairs(data, '=', '\n', &tokens);
996 base::StringPairs::iterator i;
997 for (i = tokens.begin(); i != tokens.end(); ++i) {
998 matches[i->first] = i->second;
1000 consumer_->OnGetUserInfoSuccess(matches);
1001 } else {
1002 consumer_->OnGetUserInfoFailure(GenerateAuthError(data, status));
1006 void GaiaAuthFetcher::OnMergeSessionFetched(const std::string& data,
1007 const net::URLRequestStatus& status,
1008 int response_code) {
1009 if (status.is_success() && response_code == net::HTTP_OK) {
1010 consumer_->OnMergeSessionSuccess(data);
1011 } else {
1012 consumer_->OnMergeSessionFailure(GenerateAuthError(data, status));
1016 void GaiaAuthFetcher::OnUberAuthTokenFetch(const std::string& data,
1017 const net::URLRequestStatus& status,
1018 int response_code) {
1019 if (status.is_success() && response_code == net::HTTP_OK) {
1020 consumer_->OnUberAuthTokenSuccess(data);
1021 } else {
1022 consumer_->OnUberAuthTokenFailure(GenerateAuthError(data, status));
1026 void GaiaAuthFetcher::OnOAuthLoginFetched(const std::string& data,
1027 const net::URLRequestStatus& status,
1028 int response_code) {
1029 if (status.is_success() && response_code == net::HTTP_OK) {
1030 DVLOG(1) << "ClientLogin successful!";
1031 std::string sid;
1032 std::string lsid;
1033 std::string token;
1034 ParseClientLoginResponse(data, &sid, &lsid, &token);
1035 consumer_->OnClientLoginSuccess(
1036 GaiaAuthConsumer::ClientLoginResult(sid, lsid, token, data));
1037 } else {
1038 consumer_->OnClientLoginFailure(GenerateAuthError(data, status));
1042 void GaiaAuthFetcher::OnGetCheckConnectionInfoFetched(
1043 const std::string& data,
1044 const net::URLRequestStatus& status,
1045 int response_code) {
1046 if (status.is_success() && response_code == net::HTTP_OK) {
1047 consumer_->OnGetCheckConnectionInfoSuccess(data);
1048 } else {
1049 consumer_->OnGetCheckConnectionInfoError(GenerateAuthError(data, status));
1053 void GaiaAuthFetcher::OnListIdpSessionsFetched(
1054 const std::string& data,
1055 const net::URLRequestStatus& status,
1056 int response_code) {
1057 if (status.is_success() && response_code == net::HTTP_OK) {
1058 DVLOG(1) << "ListIdpSessions successful!";
1059 std::string login_hint;
1060 if (ParseListIdpSessionsResponse(data, &login_hint)) {
1061 consumer_->OnListIdpSessionsSuccess(login_hint);
1062 } else {
1063 GoogleServiceAuthError auth_error(
1064 GoogleServiceAuthError::FromUnexpectedServiceResponse(
1065 "List Sessions response didn't contain a login_hint."));
1066 consumer_->OnListIdpSessionsError(auth_error);
1068 } else {
1069 consumer_->OnListIdpSessionsError(GenerateAuthError(data, status));
1073 void GaiaAuthFetcher::OnGetTokenResponseFetched(
1074 const std::string& data,
1075 const net::URLRequestStatus& status,
1076 int response_code) {
1077 std::string access_token;
1078 int expires_in_secs = 0;
1079 bool success = false;
1080 if (status.is_success() && response_code == net::HTTP_OK) {
1081 DVLOG(1) << "GetTokenResponse successful!";
1082 success = ExtractOAuth2TokenPairResponse(data, NULL,
1083 &access_token, &expires_in_secs);
1086 if (success) {
1087 consumer_->OnGetTokenResponseSuccess(
1088 GaiaAuthConsumer::ClientOAuthResult(std::string(), access_token,
1089 expires_in_secs));
1090 } else {
1091 consumer_->OnGetTokenResponseError(GenerateAuthError(data, status));
1095 void GaiaAuthFetcher::OnURLFetchComplete(const net::URLFetcher* source) {
1096 fetch_pending_ = false;
1097 // Some of the GAIA requests perform redirects, which results in the final
1098 // URL of the fetcher not being the original URL requested. Therefore use
1099 // the original URL when determining which OnXXX function to call.
1100 const GURL& url = source->GetOriginalURL();
1101 const net::URLRequestStatus& status = source->GetStatus();
1102 int response_code = source->GetResponseCode();
1103 std::string data;
1104 source->GetResponseAsString(&data);
1105 #ifndef NDEBUG
1106 std::string headers;
1107 if (source->GetResponseHeaders())
1108 source->GetResponseHeaders()->GetNormalizedHeaders(&headers);
1109 DVLOG(2) << "Response " << url.spec() << ", code = " << response_code << "\n"
1110 << headers << "\n";
1111 DVLOG(2) << "data: " << data << "\n";
1112 #endif
1113 // Retrieve the response headers from the request. Must only be called after
1114 // the OnURLFetchComplete callback has run.
1115 if (url == client_login_gurl_) {
1116 OnClientLoginFetched(data, status, response_code);
1117 } else if (url == issue_auth_token_gurl_) {
1118 OnIssueAuthTokenFetched(data, status, response_code);
1119 } else if (url == client_login_to_oauth2_gurl_) {
1120 OnClientLoginToOAuth2Fetched(
1121 data, source->GetCookies(), status, response_code);
1122 } else if (url == oauth2_token_gurl_) {
1123 OnOAuth2TokenPairFetched(data, status, response_code);
1124 } else if (url == get_user_info_gurl_) {
1125 OnGetUserInfoFetched(data, status, response_code);
1126 } else if (url == merge_session_gurl_) {
1127 OnMergeSessionFetched(data, status, response_code);
1128 } else if (url == uberauth_token_gurl_) {
1129 OnUberAuthTokenFetch(data, status, response_code);
1130 } else if (url == oauth_login_gurl_) {
1131 OnOAuthLoginFetched(data, status, response_code);
1132 } else if (url == oauth2_revoke_gurl_) {
1133 OnOAuth2RevokeTokenFetched(data, status, response_code);
1134 } else if (url == list_accounts_gurl_) {
1135 OnListAccountsFetched(data, status, response_code);
1136 } else if (url == get_check_connection_info_url_) {
1137 OnGetCheckConnectionInfoFetched(data, status, response_code);
1138 } else if (url == oauth2_iframe_url_) {
1139 if (requested_service_ == kListIdpServiceRequested)
1140 OnListIdpSessionsFetched(data, status, response_code);
1141 else if (requested_service_ == kGetTokenResponseRequested)
1142 OnGetTokenResponseFetched(data, status, response_code);
1143 else
1144 NOTREACHED();
1145 } else {
1146 NOTREACHED();
1150 // static
1151 bool GaiaAuthFetcher::IsSecondFactorSuccess(
1152 const std::string& alleged_error) {
1153 return alleged_error.find(kSecondFactor) !=
1154 std::string::npos;
1157 // static
1158 bool GaiaAuthFetcher::IsWebLoginRequiredSuccess(
1159 const std::string& alleged_error) {
1160 return alleged_error.find(kWebLoginRequired) !=
1161 std::string::npos;