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