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.
4
5 #include "google_apis/gaia/gaia_auth_fetcher.h"
6
7 #include <string>
8 #include <utility>
9 #include <vector>
10
11 #include "base/json/json_reader.h"
12 #include "base/json/json_writer.h"
13 #include "base/strings/string_split.h"
14 #include "base/strings/string_util.h"
15 #include "base/strings/stringprintf.h"
16 #include "base/values.h"
17 #include "google_apis/gaia/gaia_auth_consumer.h"
18 #include "google_apis/gaia/gaia_constants.h"
19 #include "google_apis/gaia/gaia_urls.h"
20 #include "google_apis/gaia/google_service_auth_error.h"
21 #include "net/base/escape.h"
22 #include "net/base/load_flags.h"
23 #include "net/http/http_response_headers.h"
24 #include "net/http/http_status_code.h"
25 #include "net/url_request/url_fetcher.h"
26 #include "net/url_request/url_request_context_getter.h"
27 #include "net/url_request/url_request_status.h"
28
29 namespace {
30 const int kLoadFlagsIgnoreCookies = net::LOAD_DO_NOT_SEND_COOKIES |
31 net::LOAD_DO_NOT_SAVE_COOKIES;
32
CookiePartsContains(const std::vector<std::string> & parts,const char * part)33 static bool CookiePartsContains(const std::vector<std::string>& parts,
34 const char* part) {
35 for (std::vector<std::string>::const_iterator it = parts.begin();
36 it != parts.end(); ++it) {
37 if (LowerCaseEqualsASCII(*it, part))
38 return true;
39 }
40 return false;
41 }
42
ExtractOAuth2TokenPairResponse(base::DictionaryValue * dict,std::string * refresh_token,std::string * access_token,int * expires_in_secs)43 bool ExtractOAuth2TokenPairResponse(base::DictionaryValue* dict,
44 std::string* refresh_token,
45 std::string* access_token,
46 int* expires_in_secs) {
47 DCHECK(refresh_token);
48 DCHECK(access_token);
49 DCHECK(expires_in_secs);
50
51 if (!dict->GetStringWithoutPathExpansion("refresh_token", refresh_token) ||
52 !dict->GetStringWithoutPathExpansion("access_token", access_token) ||
53 !dict->GetIntegerWithoutPathExpansion("expires_in", expires_in_secs)) {
54 return false;
55 }
56
57 return true;
58 }
59
60 } // namespace
61
62 // TODO(chron): Add sourceless version of this formatter.
63 // static
64 const char GaiaAuthFetcher::kClientLoginFormat[] =
65 "Email=%s&"
66 "Passwd=%s&"
67 "PersistentCookie=%s&"
68 "accountType=%s&"
69 "source=%s&"
70 "service=%s";
71 // static
72 const char GaiaAuthFetcher::kClientLoginCaptchaFormat[] =
73 "Email=%s&"
74 "Passwd=%s&"
75 "PersistentCookie=%s&"
76 "accountType=%s&"
77 "source=%s&"
78 "service=%s&"
79 "logintoken=%s&"
80 "logincaptcha=%s";
81 // static
82 const char GaiaAuthFetcher::kIssueAuthTokenFormat[] =
83 "SID=%s&"
84 "LSID=%s&"
85 "service=%s&"
86 "Session=%s";
87 // static
88 const char GaiaAuthFetcher::kClientLoginToOAuth2BodyFormat[] =
89 "scope=%s&client_id=%s";
90 // static
91 const char GaiaAuthFetcher::kClientLoginToOAuth2WithDeviceTypeBodyFormat[] =
92 "scope=%s&client_id=%s&device_type=chrome";
93 // static
94 const char GaiaAuthFetcher::kOAuth2CodeToTokenPairBodyFormat[] =
95 "scope=%s&"
96 "grant_type=authorization_code&"
97 "client_id=%s&"
98 "client_secret=%s&"
99 "code=%s";
100 // static
101 const char GaiaAuthFetcher::kOAuth2RevokeTokenBodyFormat[] =
102 "token=%s";
103 // static
104 const char GaiaAuthFetcher::kGetUserInfoFormat[] =
105 "LSID=%s";
106 // static
107 const char GaiaAuthFetcher::kMergeSessionFormat[] =
108 "uberauth=%s&"
109 "continue=%s&"
110 "source=%s";
111 // static
112 const char GaiaAuthFetcher::kUberAuthTokenURLFormat[] =
113 "?source=%s&"
114 "issueuberauth=1";
115
116 const char GaiaAuthFetcher::kOAuthLoginFormat[] = "service=%s&source=%s";
117
118 // static
119 const char GaiaAuthFetcher::kAccountDeletedError[] = "AccountDeleted";
120 // static
121 const char GaiaAuthFetcher::kAccountDisabledError[] = "AccountDisabled";
122 // static
123 const char GaiaAuthFetcher::kBadAuthenticationError[] = "BadAuthentication";
124 // static
125 const char GaiaAuthFetcher::kCaptchaError[] = "CaptchaRequired";
126 // static
127 const char GaiaAuthFetcher::kServiceUnavailableError[] =
128 "ServiceUnavailable";
129 // static
130 const char GaiaAuthFetcher::kErrorParam[] = "Error";
131 // static
132 const char GaiaAuthFetcher::kErrorUrlParam[] = "Url";
133 // static
134 const char GaiaAuthFetcher::kCaptchaUrlParam[] = "CaptchaUrl";
135 // static
136 const char GaiaAuthFetcher::kCaptchaTokenParam[] = "CaptchaToken";
137
138 // static
139 const char GaiaAuthFetcher::kCookiePersistence[] = "true";
140 // static
141 // TODO(johnnyg): When hosted accounts are supported by sync,
142 // we can always use "HOSTED_OR_GOOGLE"
143 const char GaiaAuthFetcher::kAccountTypeHostedOrGoogle[] =
144 "HOSTED_OR_GOOGLE";
145 const char GaiaAuthFetcher::kAccountTypeGoogle[] =
146 "GOOGLE";
147
148 // static
149 const char GaiaAuthFetcher::kSecondFactor[] = "Info=InvalidSecondFactor";
150
151 // static
152 const char GaiaAuthFetcher::kAuthHeaderFormat[] =
153 "Authorization: GoogleLogin auth=%s";
154 // static
155 const char GaiaAuthFetcher::kOAuthHeaderFormat[] = "Authorization: OAuth %s";
156 // static
157 const char GaiaAuthFetcher::kOAuth2BearerHeaderFormat[] =
158 "Authorization: Bearer %s";
159 // static
160 const char GaiaAuthFetcher::kDeviceIdHeaderFormat[] = "X-Device-ID: %s";
161 // static
162 const char GaiaAuthFetcher::kClientLoginToOAuth2CookiePartSecure[] = "secure";
163 // static
164 const char GaiaAuthFetcher::kClientLoginToOAuth2CookiePartHttpOnly[] =
165 "httponly";
166 // static
167 const char GaiaAuthFetcher::kClientLoginToOAuth2CookiePartCodePrefix[] =
168 "oauth_code=";
169 // static
170 const int GaiaAuthFetcher::kClientLoginToOAuth2CookiePartCodePrefixLength =
171 arraysize(GaiaAuthFetcher::kClientLoginToOAuth2CookiePartCodePrefix) - 1;
172
GaiaAuthFetcher(GaiaAuthConsumer * consumer,const std::string & source,net::URLRequestContextGetter * getter)173 GaiaAuthFetcher::GaiaAuthFetcher(GaiaAuthConsumer* consumer,
174 const std::string& source,
175 net::URLRequestContextGetter* getter)
176 : consumer_(consumer),
177 getter_(getter),
178 source_(source),
179 client_login_gurl_(GaiaUrls::GetInstance()->client_login_url()),
180 issue_auth_token_gurl_(GaiaUrls::GetInstance()->issue_auth_token_url()),
181 oauth2_token_gurl_(GaiaUrls::GetInstance()->oauth2_token_url()),
182 oauth2_revoke_gurl_(GaiaUrls::GetInstance()->oauth2_revoke_url()),
183 get_user_info_gurl_(GaiaUrls::GetInstance()->get_user_info_url()),
184 merge_session_gurl_(GaiaUrls::GetInstance()->merge_session_url()),
185 uberauth_token_gurl_(GaiaUrls::GetInstance()->oauth1_login_url().Resolve(
186 base::StringPrintf(kUberAuthTokenURLFormat, source.c_str()))),
187 oauth_login_gurl_(GaiaUrls::GetInstance()->oauth1_login_url()),
188 list_accounts_gurl_(GaiaUrls::GetInstance()->list_accounts_url()),
189 client_login_to_oauth2_gurl_(
190 GaiaUrls::GetInstance()->client_login_to_oauth2_url()),
191 fetch_pending_(false) {}
192
~GaiaAuthFetcher()193 GaiaAuthFetcher::~GaiaAuthFetcher() {}
194
HasPendingFetch()195 bool GaiaAuthFetcher::HasPendingFetch() {
196 return fetch_pending_;
197 }
198
CancelRequest()199 void GaiaAuthFetcher::CancelRequest() {
200 fetcher_.reset();
201 fetch_pending_ = false;
202 }
203
204 // static
CreateGaiaFetcher(net::URLRequestContextGetter * getter,const std::string & body,const std::string & headers,const GURL & gaia_gurl,int load_flags,net::URLFetcherDelegate * delegate)205 net::URLFetcher* GaiaAuthFetcher::CreateGaiaFetcher(
206 net::URLRequestContextGetter* getter,
207 const std::string& body,
208 const std::string& headers,
209 const GURL& gaia_gurl,
210 int load_flags,
211 net::URLFetcherDelegate* delegate) {
212 net::URLFetcher* to_return = net::URLFetcher::Create(
213 0, gaia_gurl,
214 body == "" ? net::URLFetcher::GET : net::URLFetcher::POST,
215 delegate);
216 to_return->SetRequestContext(getter);
217 to_return->SetUploadData("application/x-www-form-urlencoded", body);
218
219 DVLOG(2) << "Gaia fetcher URL: " << gaia_gurl.spec();
220 DVLOG(2) << "Gaia fetcher headers: " << headers;
221 DVLOG(2) << "Gaia fetcher body: " << body;
222
223 // The Gaia token exchange requests do not require any cookie-based
224 // identification as part of requests. We suppress sending any cookies to
225 // maintain a separation between the user's browsing and Chrome's internal
226 // services. Where such mixing is desired (MergeSession or OAuthLogin), it
227 // will be done explicitly.
228 to_return->SetLoadFlags(load_flags);
229
230 // Fetchers are sometimes cancelled because a network change was detected,
231 // especially at startup and after sign-in on ChromeOS. Retrying once should
232 // be enough in those cases; let the fetcher retry up to 3 times just in case.
233 // http://crbug.com/163710
234 to_return->SetAutomaticallyRetryOnNetworkChanges(3);
235
236 if (!headers.empty())
237 to_return->SetExtraRequestHeaders(headers);
238
239 return to_return;
240 }
241
242 // static
MakeClientLoginBody(const std::string & username,const std::string & password,const std::string & source,const char * service,const std::string & login_token,const std::string & login_captcha,HostedAccountsSetting allow_hosted_accounts)243 std::string GaiaAuthFetcher::MakeClientLoginBody(
244 const std::string& username,
245 const std::string& password,
246 const std::string& source,
247 const char* service,
248 const std::string& login_token,
249 const std::string& login_captcha,
250 HostedAccountsSetting allow_hosted_accounts) {
251 std::string encoded_username = net::EscapeUrlEncodedData(username, true);
252 std::string encoded_password = net::EscapeUrlEncodedData(password, true);
253 std::string encoded_login_token = net::EscapeUrlEncodedData(login_token,
254 true);
255 std::string encoded_login_captcha = net::EscapeUrlEncodedData(login_captcha,
256 true);
257
258 const char* account_type = allow_hosted_accounts == HostedAccountsAllowed ?
259 kAccountTypeHostedOrGoogle :
260 kAccountTypeGoogle;
261
262 if (login_token.empty() || login_captcha.empty()) {
263 return base::StringPrintf(kClientLoginFormat,
264 encoded_username.c_str(),
265 encoded_password.c_str(),
266 kCookiePersistence,
267 account_type,
268 source.c_str(),
269 service);
270 }
271
272 return base::StringPrintf(kClientLoginCaptchaFormat,
273 encoded_username.c_str(),
274 encoded_password.c_str(),
275 kCookiePersistence,
276 account_type,
277 source.c_str(),
278 service,
279 encoded_login_token.c_str(),
280 encoded_login_captcha.c_str());
281 }
282
283 // static
MakeIssueAuthTokenBody(const std::string & sid,const std::string & lsid,const char * const service)284 std::string GaiaAuthFetcher::MakeIssueAuthTokenBody(
285 const std::string& sid,
286 const std::string& lsid,
287 const char* const service) {
288 std::string encoded_sid = net::EscapeUrlEncodedData(sid, true);
289 std::string encoded_lsid = net::EscapeUrlEncodedData(lsid, true);
290
291 // All tokens should be session tokens except the gaia auth token.
292 bool session = true;
293 if (!strcmp(service, GaiaConstants::kGaiaService))
294 session = false;
295
296 return base::StringPrintf(kIssueAuthTokenFormat,
297 encoded_sid.c_str(),
298 encoded_lsid.c_str(),
299 service,
300 session ? "true" : "false");
301 }
302
303 // static
MakeGetAuthCodeBody(bool include_device_type)304 std::string GaiaAuthFetcher::MakeGetAuthCodeBody(bool include_device_type) {
305 std::string encoded_scope = net::EscapeUrlEncodedData(
306 GaiaConstants::kOAuth1LoginScope, true);
307 std::string encoded_client_id = net::EscapeUrlEncodedData(
308 GaiaUrls::GetInstance()->oauth2_chrome_client_id(), true);
309 if (include_device_type) {
310 return base::StringPrintf(kClientLoginToOAuth2WithDeviceTypeBodyFormat,
311 encoded_scope.c_str(),
312 encoded_client_id.c_str());
313 } else {
314 return base::StringPrintf(kClientLoginToOAuth2BodyFormat,
315 encoded_scope.c_str(),
316 encoded_client_id.c_str());
317 }
318 }
319
320 // static
MakeGetTokenPairBody(const std::string & auth_code)321 std::string GaiaAuthFetcher::MakeGetTokenPairBody(
322 const std::string& auth_code) {
323 std::string encoded_scope = net::EscapeUrlEncodedData(
324 GaiaConstants::kOAuth1LoginScope, true);
325 std::string encoded_client_id = net::EscapeUrlEncodedData(
326 GaiaUrls::GetInstance()->oauth2_chrome_client_id(), true);
327 std::string encoded_client_secret = net::EscapeUrlEncodedData(
328 GaiaUrls::GetInstance()->oauth2_chrome_client_secret(), true);
329 std::string encoded_auth_code = net::EscapeUrlEncodedData(auth_code, true);
330 return base::StringPrintf(kOAuth2CodeToTokenPairBodyFormat,
331 encoded_scope.c_str(),
332 encoded_client_id.c_str(),
333 encoded_client_secret.c_str(),
334 encoded_auth_code.c_str());
335 }
336
337 // static
MakeRevokeTokenBody(const std::string & auth_token)338 std::string GaiaAuthFetcher::MakeRevokeTokenBody(
339 const std::string& auth_token) {
340 return base::StringPrintf(kOAuth2RevokeTokenBodyFormat, auth_token.c_str());
341 }
342
343 // static
MakeGetUserInfoBody(const std::string & lsid)344 std::string GaiaAuthFetcher::MakeGetUserInfoBody(const std::string& lsid) {
345 std::string encoded_lsid = net::EscapeUrlEncodedData(lsid, true);
346 return base::StringPrintf(kGetUserInfoFormat, encoded_lsid.c_str());
347 }
348
349 // static
MakeMergeSessionBody(const std::string & auth_token,const std::string & continue_url,const std::string & source)350 std::string GaiaAuthFetcher::MakeMergeSessionBody(
351 const std::string& auth_token,
352 const std::string& continue_url,
353 const std::string& source) {
354 std::string encoded_auth_token = net::EscapeUrlEncodedData(auth_token, true);
355 std::string encoded_continue_url = net::EscapeUrlEncodedData(continue_url,
356 true);
357 std::string encoded_source = net::EscapeUrlEncodedData(source, true);
358 return base::StringPrintf(kMergeSessionFormat,
359 encoded_auth_token.c_str(),
360 encoded_continue_url.c_str(),
361 encoded_source.c_str());
362 }
363
364 // static
MakeGetAuthCodeHeader(const std::string & auth_token)365 std::string GaiaAuthFetcher::MakeGetAuthCodeHeader(
366 const std::string& auth_token) {
367 return base::StringPrintf(kAuthHeaderFormat, auth_token.c_str());
368 }
369
370 // Helper method that extracts tokens from a successful reply.
371 // static
ParseClientLoginResponse(const std::string & data,std::string * sid,std::string * lsid,std::string * token)372 void GaiaAuthFetcher::ParseClientLoginResponse(const std::string& data,
373 std::string* sid,
374 std::string* lsid,
375 std::string* token) {
376 using std::vector;
377 using std::pair;
378 using std::string;
379 sid->clear();
380 lsid->clear();
381 token->clear();
382 vector<pair<string, string> > tokens;
383 base::SplitStringIntoKeyValuePairs(data, '=', '\n', &tokens);
384 for (vector<pair<string, string> >::iterator i = tokens.begin();
385 i != tokens.end(); ++i) {
386 if (i->first == "SID") {
387 sid->assign(i->second);
388 } else if (i->first == "LSID") {
389 lsid->assign(i->second);
390 } else if (i->first == "Auth") {
391 token->assign(i->second);
392 }
393 }
394 // If this was a request for uberauth token, then that's all we've got in
395 // data.
396 if (sid->empty() && lsid->empty() && token->empty())
397 token->assign(data);
398 }
399
400 // static
MakeOAuthLoginBody(const std::string & service,const std::string & source)401 std::string GaiaAuthFetcher::MakeOAuthLoginBody(const std::string& service,
402 const std::string& source) {
403 std::string encoded_service = net::EscapeUrlEncodedData(service, true);
404 std::string encoded_source = net::EscapeUrlEncodedData(source, true);
405 return base::StringPrintf(kOAuthLoginFormat,
406 encoded_service.c_str(),
407 encoded_source.c_str());
408 }
409
410 // static
ParseClientLoginFailure(const std::string & data,std::string * error,std::string * error_url,std::string * captcha_url,std::string * captcha_token)411 void GaiaAuthFetcher::ParseClientLoginFailure(const std::string& data,
412 std::string* error,
413 std::string* error_url,
414 std::string* captcha_url,
415 std::string* captcha_token) {
416 using std::vector;
417 using std::pair;
418 using std::string;
419
420 vector<pair<string, string> > tokens;
421 base::SplitStringIntoKeyValuePairs(data, '=', '\n', &tokens);
422 for (vector<pair<string, string> >::iterator i = tokens.begin();
423 i != tokens.end(); ++i) {
424 if (i->first == kErrorParam) {
425 error->assign(i->second);
426 } else if (i->first == kErrorUrlParam) {
427 error_url->assign(i->second);
428 } else if (i->first == kCaptchaUrlParam) {
429 captcha_url->assign(i->second);
430 } else if (i->first == kCaptchaTokenParam) {
431 captcha_token->assign(i->second);
432 }
433 }
434 }
435
436 // static
ParseClientLoginToOAuth2Response(const net::ResponseCookies & cookies,std::string * auth_code)437 bool GaiaAuthFetcher::ParseClientLoginToOAuth2Response(
438 const net::ResponseCookies& cookies,
439 std::string* auth_code) {
440 DCHECK(auth_code);
441 net::ResponseCookies::const_iterator iter;
442 for (iter = cookies.begin(); iter != cookies.end(); ++iter) {
443 if (ParseClientLoginToOAuth2Cookie(*iter, auth_code))
444 return true;
445 }
446 return false;
447 }
448
449 // static
ParseClientLoginToOAuth2Cookie(const std::string & cookie,std::string * auth_code)450 bool GaiaAuthFetcher::ParseClientLoginToOAuth2Cookie(const std::string& cookie,
451 std::string* auth_code) {
452 std::vector<std::string> parts;
453 base::SplitString(cookie, ';', &parts);
454 // Per documentation, the cookie should have Secure and HttpOnly.
455 if (!CookiePartsContains(parts, kClientLoginToOAuth2CookiePartSecure) ||
456 !CookiePartsContains(parts, kClientLoginToOAuth2CookiePartHttpOnly)) {
457 return false;
458 }
459
460 std::vector<std::string>::const_iterator iter;
461 for (iter = parts.begin(); iter != parts.end(); ++iter) {
462 const std::string& part = *iter;
463 if (StartsWithASCII(
464 part, kClientLoginToOAuth2CookiePartCodePrefix, false)) {
465 auth_code->assign(part.substr(
466 kClientLoginToOAuth2CookiePartCodePrefixLength));
467 return true;
468 }
469 }
470 return false;
471 }
472
StartClientLogin(const std::string & username,const std::string & password,const char * const service,const std::string & login_token,const std::string & login_captcha,HostedAccountsSetting allow_hosted_accounts)473 void GaiaAuthFetcher::StartClientLogin(
474 const std::string& username,
475 const std::string& password,
476 const char* const service,
477 const std::string& login_token,
478 const std::string& login_captcha,
479 HostedAccountsSetting allow_hosted_accounts) {
480
481 DCHECK(!fetch_pending_) << "Tried to fetch two things at once!";
482
483 // This class is thread agnostic, so be sure to call this only on the
484 // same thread each time.
485 DVLOG(1) << "Starting new ClientLogin fetch for:" << username;
486
487 // Must outlive fetcher_.
488 request_body_ = MakeClientLoginBody(username,
489 password,
490 source_,
491 service,
492 login_token,
493 login_captcha,
494 allow_hosted_accounts);
495 fetcher_.reset(CreateGaiaFetcher(getter_,
496 request_body_,
497 std::string(),
498 client_login_gurl_,
499 kLoadFlagsIgnoreCookies,
500 this));
501 fetch_pending_ = true;
502 fetcher_->Start();
503 }
504
StartIssueAuthToken(const std::string & sid,const std::string & lsid,const char * const service)505 void GaiaAuthFetcher::StartIssueAuthToken(const std::string& sid,
506 const std::string& lsid,
507 const char* const service) {
508 DCHECK(!fetch_pending_) << "Tried to fetch two things at once!";
509
510 DVLOG(1) << "Starting IssueAuthToken for: " << service;
511 requested_service_ = service;
512 request_body_ = MakeIssueAuthTokenBody(sid, lsid, service);
513 fetcher_.reset(CreateGaiaFetcher(getter_,
514 request_body_,
515 std::string(),
516 issue_auth_token_gurl_,
517 kLoadFlagsIgnoreCookies,
518 this));
519 fetch_pending_ = true;
520 fetcher_->Start();
521 }
522
StartLsoForOAuthLoginTokenExchange(const std::string & auth_token)523 void GaiaAuthFetcher::StartLsoForOAuthLoginTokenExchange(
524 const std::string& auth_token) {
525 DCHECK(!fetch_pending_) << "Tried to fetch two things at once!";
526
527 DVLOG(1) << "Starting OAuth login token exchange with auth_token";
528 request_body_ = MakeGetAuthCodeBody(false);
529 client_login_to_oauth2_gurl_ =
530 GaiaUrls::GetInstance()->client_login_to_oauth2_url();
531
532 fetcher_.reset(CreateGaiaFetcher(getter_,
533 request_body_,
534 MakeGetAuthCodeHeader(auth_token),
535 client_login_to_oauth2_gurl_,
536 kLoadFlagsIgnoreCookies,
537 this));
538 fetch_pending_ = true;
539 fetcher_->Start();
540 }
541
StartRevokeOAuth2Token(const std::string & auth_token)542 void GaiaAuthFetcher::StartRevokeOAuth2Token(const std::string& auth_token) {
543 DCHECK(!fetch_pending_) << "Tried to fetch two things at once!";
544
545 DVLOG(1) << "Starting OAuth2 token revocation";
546 request_body_ = MakeRevokeTokenBody(auth_token);
547 fetcher_.reset(CreateGaiaFetcher(getter_,
548 request_body_,
549 std::string(),
550 oauth2_revoke_gurl_,
551 kLoadFlagsIgnoreCookies,
552 this));
553 fetch_pending_ = true;
554 fetcher_->Start();
555 }
556
StartCookieForOAuthLoginTokenExchange(const std::string & session_index)557 void GaiaAuthFetcher::StartCookieForOAuthLoginTokenExchange(
558 const std::string& session_index) {
559 StartCookieForOAuthLoginTokenExchangeWithDeviceId(session_index,
560 std::string());
561 }
562
StartCookieForOAuthLoginTokenExchangeWithDeviceId(const std::string & session_index,const std::string & device_id)563 void GaiaAuthFetcher::StartCookieForOAuthLoginTokenExchangeWithDeviceId(
564 const std::string& session_index,
565 const std::string& device_id) {
566 DCHECK(!fetch_pending_) << "Tried to fetch two things at once!";
567
568 DVLOG(1) << "Starting OAuth login token fetch with cookie jar";
569 request_body_ = MakeGetAuthCodeBody(!device_id.empty());
570
571 client_login_to_oauth2_gurl_ =
572 GaiaUrls::GetInstance()->client_login_to_oauth2_url();
573 if (!session_index.empty()) {
574 client_login_to_oauth2_gurl_ =
575 client_login_to_oauth2_gurl_.Resolve("?authuser=" + session_index);
576 }
577
578 std::string device_id_header;
579 if (!device_id.empty()) {
580 device_id_header =
581 base::StringPrintf(kDeviceIdHeaderFormat, device_id.c_str());
582 }
583
584 fetcher_.reset(CreateGaiaFetcher(getter_,
585 request_body_,
586 device_id_header,
587 client_login_to_oauth2_gurl_,
588 net::LOAD_NORMAL,
589 this));
590 fetch_pending_ = true;
591 fetcher_->Start();
592 }
593
StartAuthCodeForOAuth2TokenExchange(const std::string & auth_code)594 void GaiaAuthFetcher::StartAuthCodeForOAuth2TokenExchange(
595 const std::string& auth_code) {
596 DCHECK(!fetch_pending_) << "Tried to fetch two things at once!";
597
598 DVLOG(1) << "Starting OAuth token pair fetch";
599 request_body_ = MakeGetTokenPairBody(auth_code);
600 fetcher_.reset(CreateGaiaFetcher(getter_,
601 request_body_,
602 std::string(),
603 oauth2_token_gurl_,
604 kLoadFlagsIgnoreCookies,
605 this));
606 fetch_pending_ = true;
607 fetcher_->Start();
608 }
609
StartGetUserInfo(const std::string & lsid)610 void GaiaAuthFetcher::StartGetUserInfo(const std::string& lsid) {
611 DCHECK(!fetch_pending_) << "Tried to fetch two things at once!";
612
613 DVLOG(1) << "Starting GetUserInfo for lsid=" << lsid;
614 request_body_ = MakeGetUserInfoBody(lsid);
615 fetcher_.reset(CreateGaiaFetcher(getter_,
616 request_body_,
617 std::string(),
618 get_user_info_gurl_,
619 kLoadFlagsIgnoreCookies,
620 this));
621 fetch_pending_ = true;
622 fetcher_->Start();
623 }
624
StartMergeSession(const std::string & uber_token)625 void GaiaAuthFetcher::StartMergeSession(const std::string& uber_token) {
626 DCHECK(!fetch_pending_) << "Tried to fetch two things at once!";
627
628 DVLOG(1) << "Starting MergeSession with uber_token=" << uber_token;
629
630 // The continue URL is a required parameter of the MergeSession API, but in
631 // this case we don't actually need or want to navigate to it. Setting it to
632 // an arbitrary Google URL.
633 //
634 // In order for the new session to be merged correctly, the server needs to
635 // know what sessions already exist in the browser. The fetcher needs to be
636 // created such that it sends the cookies with the request, which is
637 // different from all other requests the fetcher can make.
638 std::string continue_url("http://www.google.com");
639 request_body_ = MakeMergeSessionBody(uber_token, continue_url, source_);
640 fetcher_.reset(CreateGaiaFetcher(getter_,
641 request_body_,
642 std::string(),
643 merge_session_gurl_,
644 net::LOAD_NORMAL,
645 this));
646 fetch_pending_ = true;
647 fetcher_->Start();
648 }
649
StartTokenFetchForUberAuthExchange(const std::string & access_token)650 void GaiaAuthFetcher::StartTokenFetchForUberAuthExchange(
651 const std::string& access_token) {
652 DCHECK(!fetch_pending_) << "Tried to fetch two things at once!";
653
654 DVLOG(1) << "Starting StartTokenFetchForUberAuthExchange with access_token="
655 << access_token;
656 std::string authentication_header =
657 base::StringPrintf(kOAuthHeaderFormat, access_token.c_str());
658 fetcher_.reset(CreateGaiaFetcher(getter_,
659 std::string(),
660 authentication_header,
661 uberauth_token_gurl_,
662 net::LOAD_NORMAL,
663 this));
664 fetch_pending_ = true;
665 fetcher_->Start();
666 }
667
StartOAuthLogin(const std::string & access_token,const std::string & service)668 void GaiaAuthFetcher::StartOAuthLogin(const std::string& access_token,
669 const std::string& service) {
670 DCHECK(!fetch_pending_) << "Tried to fetch two things at once!";
671
672 request_body_ = MakeOAuthLoginBody(service, source_);
673 std::string authentication_header =
674 base::StringPrintf(kOAuth2BearerHeaderFormat, access_token.c_str());
675 fetcher_.reset(CreateGaiaFetcher(getter_,
676 request_body_,
677 authentication_header,
678 oauth_login_gurl_,
679 net::LOAD_NORMAL,
680 this));
681 fetch_pending_ = true;
682 fetcher_->Start();
683 }
684
StartListAccounts()685 void GaiaAuthFetcher::StartListAccounts() {
686 DCHECK(!fetch_pending_) << "Tried to fetch two things at once!";
687
688 fetcher_.reset(CreateGaiaFetcher(getter_,
689 " ", // To force an HTTP POST.
690 "Origin: https://www.google.com",
691 list_accounts_gurl_,
692 net::LOAD_NORMAL,
693 this));
694 fetch_pending_ = true;
695 fetcher_->Start();
696 }
697
698 // static
GenerateAuthError(const std::string & data,const net::URLRequestStatus & status)699 GoogleServiceAuthError GaiaAuthFetcher::GenerateAuthError(
700 const std::string& data,
701 const net::URLRequestStatus& status) {
702 if (!status.is_success()) {
703 if (status.status() == net::URLRequestStatus::CANCELED) {
704 return GoogleServiceAuthError(GoogleServiceAuthError::REQUEST_CANCELED);
705 }
706 DLOG(WARNING) << "Could not reach Google Accounts servers: errno "
707 << status.error();
708 return GoogleServiceAuthError::FromConnectionError(status.error());
709 }
710
711 if (IsSecondFactorSuccess(data))
712 return GoogleServiceAuthError(GoogleServiceAuthError::TWO_FACTOR);
713
714 std::string error;
715 std::string url;
716 std::string captcha_url;
717 std::string captcha_token;
718 ParseClientLoginFailure(data, &error, &url, &captcha_url, &captcha_token);
719 DLOG(WARNING) << "ClientLogin failed with " << error;
720
721 if (error == kCaptchaError) {
722 return GoogleServiceAuthError::FromClientLoginCaptchaChallenge(
723 captcha_token,
724 GURL(GaiaUrls::GetInstance()->captcha_base_url().Resolve(captcha_url)),
725 GURL(url));
726 }
727 if (error == kAccountDeletedError)
728 return GoogleServiceAuthError(GoogleServiceAuthError::ACCOUNT_DELETED);
729 if (error == kAccountDisabledError)
730 return GoogleServiceAuthError(GoogleServiceAuthError::ACCOUNT_DISABLED);
731 if (error == kBadAuthenticationError) {
732 return GoogleServiceAuthError(
733 GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS);
734 }
735 if (error == kServiceUnavailableError) {
736 return GoogleServiceAuthError(
737 GoogleServiceAuthError::SERVICE_UNAVAILABLE);
738 }
739
740 DLOG(WARNING) << "Incomprehensible response from Google Accounts servers.";
741 return GoogleServiceAuthError(GoogleServiceAuthError::SERVICE_UNAVAILABLE);
742 }
743
OnClientLoginFetched(const std::string & data,const net::URLRequestStatus & status,int response_code)744 void GaiaAuthFetcher::OnClientLoginFetched(const std::string& data,
745 const net::URLRequestStatus& status,
746 int response_code) {
747 if (status.is_success() && response_code == net::HTTP_OK) {
748 DVLOG(1) << "ClientLogin successful!";
749 std::string sid;
750 std::string lsid;
751 std::string token;
752 ParseClientLoginResponse(data, &sid, &lsid, &token);
753 consumer_->OnClientLoginSuccess(
754 GaiaAuthConsumer::ClientLoginResult(sid, lsid, token, data));
755 } else {
756 consumer_->OnClientLoginFailure(GenerateAuthError(data, status));
757 }
758 }
759
OnIssueAuthTokenFetched(const std::string & data,const net::URLRequestStatus & status,int response_code)760 void GaiaAuthFetcher::OnIssueAuthTokenFetched(
761 const std::string& data,
762 const net::URLRequestStatus& status,
763 int response_code) {
764 if (status.is_success() && response_code == net::HTTP_OK) {
765 // Only the bare token is returned in the body of this Gaia call
766 // without any padding.
767 consumer_->OnIssueAuthTokenSuccess(requested_service_, data);
768 } else {
769 consumer_->OnIssueAuthTokenFailure(requested_service_,
770 GenerateAuthError(data, status));
771 }
772 }
773
OnClientLoginToOAuth2Fetched(const std::string & data,const net::ResponseCookies & cookies,const net::URLRequestStatus & status,int response_code)774 void GaiaAuthFetcher::OnClientLoginToOAuth2Fetched(
775 const std::string& data,
776 const net::ResponseCookies& cookies,
777 const net::URLRequestStatus& status,
778 int response_code) {
779 if (status.is_success() && response_code == net::HTTP_OK) {
780 std::string auth_code;
781 ParseClientLoginToOAuth2Response(cookies, &auth_code);
782 StartAuthCodeForOAuth2TokenExchange(auth_code);
783 } else {
784 GoogleServiceAuthError auth_error(GenerateAuthError(data, status));
785 consumer_->OnClientOAuthFailure(auth_error);
786 }
787 }
788
OnOAuth2TokenPairFetched(const std::string & data,const net::URLRequestStatus & status,int response_code)789 void GaiaAuthFetcher::OnOAuth2TokenPairFetched(
790 const std::string& data,
791 const net::URLRequestStatus& status,
792 int response_code) {
793 std::string refresh_token;
794 std::string access_token;
795 int expires_in_secs = 0;
796
797 bool success = false;
798 if (status.is_success() && response_code == net::HTTP_OK) {
799 scoped_ptr<base::Value> value(base::JSONReader::Read(data));
800 if (value.get() && value->GetType() == base::Value::TYPE_DICTIONARY) {
801 base::DictionaryValue* dict =
802 static_cast<base::DictionaryValue*>(value.get());
803 success = ExtractOAuth2TokenPairResponse(dict, &refresh_token,
804 &access_token, &expires_in_secs);
805 }
806 }
807
808 if (success) {
809 consumer_->OnClientOAuthSuccess(
810 GaiaAuthConsumer::ClientOAuthResult(refresh_token, access_token,
811 expires_in_secs));
812 } else {
813 consumer_->OnClientOAuthFailure(GenerateAuthError(data, status));
814 }
815 }
816
OnOAuth2RevokeTokenFetched(const std::string & data,const net::URLRequestStatus & status,int response_code)817 void GaiaAuthFetcher::OnOAuth2RevokeTokenFetched(
818 const std::string& data,
819 const net::URLRequestStatus& status,
820 int response_code) {
821 consumer_->OnOAuth2RevokeTokenCompleted();
822 }
823
OnListAccountsFetched(const std::string & data,const net::URLRequestStatus & status,int response_code)824 void GaiaAuthFetcher::OnListAccountsFetched(const std::string& data,
825 const net::URLRequestStatus& status,
826 int response_code) {
827 if (status.is_success() && response_code == net::HTTP_OK) {
828 consumer_->OnListAccountsSuccess(data);
829 } else {
830 consumer_->OnListAccountsFailure(GenerateAuthError(data, status));
831 }
832 }
833
OnGetUserInfoFetched(const std::string & data,const net::URLRequestStatus & status,int response_code)834 void GaiaAuthFetcher::OnGetUserInfoFetched(
835 const std::string& data,
836 const net::URLRequestStatus& status,
837 int response_code) {
838 if (status.is_success() && response_code == net::HTTP_OK) {
839 std::vector<std::pair<std::string, std::string> > tokens;
840 UserInfoMap matches;
841 base::SplitStringIntoKeyValuePairs(data, '=', '\n', &tokens);
842 std::vector<std::pair<std::string, std::string> >::iterator i;
843 for (i = tokens.begin(); i != tokens.end(); ++i) {
844 matches[i->first] = i->second;
845 }
846 consumer_->OnGetUserInfoSuccess(matches);
847 } else {
848 consumer_->OnGetUserInfoFailure(GenerateAuthError(data, status));
849 }
850 }
851
OnMergeSessionFetched(const std::string & data,const net::URLRequestStatus & status,int response_code)852 void GaiaAuthFetcher::OnMergeSessionFetched(const std::string& data,
853 const net::URLRequestStatus& status,
854 int response_code) {
855 if (status.is_success() && response_code == net::HTTP_OK) {
856 consumer_->OnMergeSessionSuccess(data);
857 } else {
858 consumer_->OnMergeSessionFailure(GenerateAuthError(data, status));
859 }
860 }
861
OnUberAuthTokenFetch(const std::string & data,const net::URLRequestStatus & status,int response_code)862 void GaiaAuthFetcher::OnUberAuthTokenFetch(const std::string& data,
863 const net::URLRequestStatus& status,
864 int response_code) {
865 if (status.is_success() && response_code == net::HTTP_OK) {
866 consumer_->OnUberAuthTokenSuccess(data);
867 } else {
868 consumer_->OnUberAuthTokenFailure(GenerateAuthError(data, status));
869 }
870 }
871
OnOAuthLoginFetched(const std::string & data,const net::URLRequestStatus & status,int response_code)872 void GaiaAuthFetcher::OnOAuthLoginFetched(const std::string& data,
873 const net::URLRequestStatus& status,
874 int response_code) {
875 if (status.is_success() && response_code == net::HTTP_OK) {
876 DVLOG(1) << "ClientLogin successful!";
877 std::string sid;
878 std::string lsid;
879 std::string token;
880 ParseClientLoginResponse(data, &sid, &lsid, &token);
881 consumer_->OnClientLoginSuccess(
882 GaiaAuthConsumer::ClientLoginResult(sid, lsid, token, data));
883 } else {
884 consumer_->OnClientLoginFailure(GenerateAuthError(data, status));
885 }
886 }
887
OnURLFetchComplete(const net::URLFetcher * source)888 void GaiaAuthFetcher::OnURLFetchComplete(const net::URLFetcher* source) {
889 fetch_pending_ = false;
890 // Some of the GAIA requests perform redirects, which results in the final
891 // URL of the fetcher not being the original URL requested. Therefore use
892 // the original URL when determining which OnXXX function to call.
893 const GURL& url = source->GetOriginalURL();
894 const net::URLRequestStatus& status = source->GetStatus();
895 int response_code = source->GetResponseCode();
896 std::string data;
897 source->GetResponseAsString(&data);
898 #ifndef NDEBUG
899 std::string headers;
900 if (source->GetResponseHeaders())
901 source->GetResponseHeaders()->GetNormalizedHeaders(&headers);
902 DVLOG(2) << "Response " << url.spec() << ", code = " << response_code << "\n"
903 << headers << "\n";
904 DVLOG(2) << "data: " << data << "\n";
905 #endif
906 // Retrieve the response headers from the request. Must only be called after
907 // the OnURLFetchComplete callback has run.
908 if (url == client_login_gurl_) {
909 OnClientLoginFetched(data, status, response_code);
910 } else if (url == issue_auth_token_gurl_) {
911 OnIssueAuthTokenFetched(data, status, response_code);
912 } else if (url == client_login_to_oauth2_gurl_) {
913 OnClientLoginToOAuth2Fetched(
914 data, source->GetCookies(), status, response_code);
915 } else if (url == oauth2_token_gurl_) {
916 OnOAuth2TokenPairFetched(data, status, response_code);
917 } else if (url == get_user_info_gurl_) {
918 OnGetUserInfoFetched(data, status, response_code);
919 } else if (url == merge_session_gurl_) {
920 OnMergeSessionFetched(data, status, response_code);
921 } else if (url == uberauth_token_gurl_) {
922 OnUberAuthTokenFetch(data, status, response_code);
923 } else if (url == oauth_login_gurl_) {
924 OnOAuthLoginFetched(data, status, response_code);
925 } else if (url == oauth2_revoke_gurl_) {
926 OnOAuth2RevokeTokenFetched(data, status, response_code);
927 } else if (url == list_accounts_gurl_) {
928 OnListAccountsFetched(data, status, response_code);
929 } else {
930 NOTREACHED();
931 }
932 }
933
934 // static
IsSecondFactorSuccess(const std::string & alleged_error)935 bool GaiaAuthFetcher::IsSecondFactorSuccess(
936 const std::string& alleged_error) {
937 return alleged_error.find(kSecondFactor) !=
938 std::string::npos;
939 }
940