• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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