• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright (c) 2011 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 "chrome/common/net/gaia/gaia_auth_fetcher.h"
6 
7 #include <string>
8 #include <utility>
9 #include <vector>
10 
11 #include "base/string_split.h"
12 #include "base/string_util.h"
13 #include "chrome/common/net/gaia/gaia_auth_consumer.h"
14 #include "chrome/common/net/gaia/gaia_constants.h"
15 #include "chrome/common/net/gaia/google_service_auth_error.h"
16 #include "chrome/common/net/http_return.h"
17 #include "net/base/load_flags.h"
18 #include "net/url_request/url_request_context_getter.h"
19 #include "net/url_request/url_request_status.h"
20 #include "third_party/libjingle/source/talk/base/urlencode.h"
21 
22 // TODO(chron): Add sourceless version of this formatter.
23 // static
24 const char GaiaAuthFetcher::kClientLoginFormat[] =
25     "Email=%s&"
26     "Passwd=%s&"
27     "PersistentCookie=%s&"
28     "accountType=%s&"
29     "source=%s&"
30     "service=%s";
31 // static
32 const char GaiaAuthFetcher::kClientLoginCaptchaFormat[] =
33     "Email=%s&"
34     "Passwd=%s&"
35     "PersistentCookie=%s&"
36     "accountType=%s&"
37     "source=%s&"
38     "service=%s&"
39     "logintoken=%s&"
40     "logincaptcha=%s";
41 // static
42 const char GaiaAuthFetcher::kIssueAuthTokenFormat[] =
43     "SID=%s&"
44     "LSID=%s&"
45     "service=%s&"
46     "Session=%s";
47 // static
48 const char GaiaAuthFetcher::kGetUserInfoFormat[] =
49     "LSID=%s";
50 
51 // static
52 const char GaiaAuthFetcher::kAccountDeletedError[] = "AccountDeleted";
53 // static
54 const char GaiaAuthFetcher::kAccountDisabledError[] = "AccountDisabled";
55 // static
56 const char GaiaAuthFetcher::kBadAuthenticationError[] = "BadAuthentication";
57 // static
58 const char GaiaAuthFetcher::kCaptchaError[] = "CaptchaRequired";
59 // static
60 const char GaiaAuthFetcher::kServiceUnavailableError[] =
61     "ServiceUnavailable";
62 // static
63 const char GaiaAuthFetcher::kErrorParam[] = "Error";
64 // static
65 const char GaiaAuthFetcher::kErrorUrlParam[] = "Url";
66 // static
67 const char GaiaAuthFetcher::kCaptchaUrlParam[] = "CaptchaUrl";
68 // static
69 const char GaiaAuthFetcher::kCaptchaTokenParam[] = "CaptchaToken";
70 // static
71 const char GaiaAuthFetcher::kCaptchaUrlPrefix[] =
72     "http://www.google.com/accounts/";
73 
74 // static
75 const char GaiaAuthFetcher::kCookiePersistence[] = "true";
76 // static
77 // TODO(johnnyg): When hosted accounts are supported by sync,
78 // we can always use "HOSTED_OR_GOOGLE"
79 const char GaiaAuthFetcher::kAccountTypeHostedOrGoogle[] =
80     "HOSTED_OR_GOOGLE";
81 const char GaiaAuthFetcher::kAccountTypeGoogle[] =
82     "GOOGLE";
83 
84 // static
85 const char GaiaAuthFetcher::kSecondFactor[] = "Info=InvalidSecondFactor";
86 
87 // TODO(chron): These urls are also in auth_response_handler.h.
88 // The URLs for different calls in the Google Accounts programmatic login API.
89 const char GaiaAuthFetcher::kClientLoginUrl[] =
90     "https://www.google.com/accounts/ClientLogin";
91 const char GaiaAuthFetcher::kIssueAuthTokenUrl[] =
92     "https://www.google.com/accounts/IssueAuthToken";
93 const char GaiaAuthFetcher::kGetUserInfoUrl[] =
94     "https://www.google.com/accounts/GetUserInfo";
95 
GaiaAuthFetcher(GaiaAuthConsumer * consumer,const std::string & source,net::URLRequestContextGetter * getter)96 GaiaAuthFetcher::GaiaAuthFetcher(GaiaAuthConsumer* consumer,
97                                        const std::string& source,
98                                        net::URLRequestContextGetter* getter)
99     : consumer_(consumer),
100       getter_(getter),
101       source_(source),
102       client_login_gurl_(kClientLoginUrl),
103       issue_auth_token_gurl_(kIssueAuthTokenUrl),
104       get_user_info_gurl_(kGetUserInfoUrl),
105       fetch_pending_(false) {}
106 
~GaiaAuthFetcher()107 GaiaAuthFetcher::~GaiaAuthFetcher() {}
108 
HasPendingFetch()109 bool GaiaAuthFetcher::HasPendingFetch() {
110   return fetch_pending_;
111 }
112 
CancelRequest()113 void GaiaAuthFetcher::CancelRequest() {
114   fetcher_.reset();
115   fetch_pending_ = false;
116 }
117 
118 // static
CreateGaiaFetcher(net::URLRequestContextGetter * getter,const std::string & body,const GURL & gaia_gurl,URLFetcher::Delegate * delegate)119 URLFetcher* GaiaAuthFetcher::CreateGaiaFetcher(
120     net::URLRequestContextGetter* getter,
121     const std::string& body,
122     const GURL& gaia_gurl,
123     URLFetcher::Delegate* delegate) {
124 
125   URLFetcher* to_return =
126       URLFetcher::Create(0,
127                          gaia_gurl,
128                          URLFetcher::POST,
129                          delegate);
130   to_return->set_request_context(getter);
131   to_return->set_load_flags(net::LOAD_DO_NOT_SEND_COOKIES);
132   to_return->set_upload_data("application/x-www-form-urlencoded", body);
133   return to_return;
134 }
135 
136 // 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)137 std::string GaiaAuthFetcher::MakeClientLoginBody(
138     const std::string& username,
139     const std::string& password,
140     const std::string& source,
141     const char* service,
142     const std::string& login_token,
143     const std::string& login_captcha,
144     HostedAccountsSetting allow_hosted_accounts) {
145   std::string encoded_username = UrlEncodeString(username);
146   std::string encoded_password = UrlEncodeString(password);
147   std::string encoded_login_token = UrlEncodeString(login_token);
148   std::string encoded_login_captcha = UrlEncodeString(login_captcha);
149 
150   const char* account_type = allow_hosted_accounts == HostedAccountsAllowed ?
151       kAccountTypeHostedOrGoogle :
152       kAccountTypeGoogle;
153 
154   if (login_token.empty() || login_captcha.empty()) {
155     return base::StringPrintf(kClientLoginFormat,
156                               encoded_username.c_str(),
157                               encoded_password.c_str(),
158                               kCookiePersistence,
159                               account_type,
160                               source.c_str(),
161                               service);
162   }
163 
164   return base::StringPrintf(kClientLoginCaptchaFormat,
165                             encoded_username.c_str(),
166                             encoded_password.c_str(),
167                             kCookiePersistence,
168                             account_type,
169                             source.c_str(),
170                             service,
171                             encoded_login_token.c_str(),
172                             encoded_login_captcha.c_str());
173 
174 }
175 
176 // static
MakeIssueAuthTokenBody(const std::string & sid,const std::string & lsid,const char * const service)177 std::string GaiaAuthFetcher::MakeIssueAuthTokenBody(
178     const std::string& sid,
179     const std::string& lsid,
180     const char* const service) {
181   std::string encoded_sid = UrlEncodeString(sid);
182   std::string encoded_lsid = UrlEncodeString(lsid);
183 
184   // All tokens should be session tokens except the gaia auth token.
185   bool session = true;
186   if (!strcmp(service, GaiaConstants::kGaiaService))
187     session = false;
188 
189   return base::StringPrintf(kIssueAuthTokenFormat,
190                             encoded_sid.c_str(),
191                             encoded_lsid.c_str(),
192                             service,
193                             session ? "true" : "false");
194 }
195 
196 // static
MakeGetUserInfoBody(const std::string & lsid)197 std::string GaiaAuthFetcher::MakeGetUserInfoBody(const std::string& lsid) {
198   std::string encoded_lsid = UrlEncodeString(lsid);
199   return base::StringPrintf(kGetUserInfoFormat, encoded_lsid.c_str());
200 }
201 
202 // Helper method that extracts tokens from a successful reply.
203 // static
ParseClientLoginResponse(const std::string & data,std::string * sid,std::string * lsid,std::string * token)204 void GaiaAuthFetcher::ParseClientLoginResponse(const std::string& data,
205                                                   std::string* sid,
206                                                   std::string* lsid,
207                                                   std::string* token) {
208   using std::vector;
209   using std::pair;
210   using std::string;
211 
212   vector<pair<string, string> > tokens;
213   base::SplitStringIntoKeyValuePairs(data, '=', '\n', &tokens);
214   for (vector<pair<string, string> >::iterator i = tokens.begin();
215       i != tokens.end(); ++i) {
216     if (i->first == "SID") {
217       sid->assign(i->second);
218     } else if (i->first == "LSID") {
219       lsid->assign(i->second);
220     } else if (i->first == "Auth") {
221       token->assign(i->second);
222     }
223   }
224 }
225 
226 // static
ParseClientLoginFailure(const std::string & data,std::string * error,std::string * error_url,std::string * captcha_url,std::string * captcha_token)227 void GaiaAuthFetcher::ParseClientLoginFailure(const std::string& data,
228                                               std::string* error,
229                                               std::string* error_url,
230                                               std::string* captcha_url,
231                                               std::string* captcha_token) {
232   using std::vector;
233   using std::pair;
234   using std::string;
235 
236   vector<pair<string, string> > tokens;
237   base::SplitStringIntoKeyValuePairs(data, '=', '\n', &tokens);
238   for (vector<pair<string, string> >::iterator i = tokens.begin();
239        i != tokens.end(); ++i) {
240     if (i->first == kErrorParam) {
241       error->assign(i->second);
242     } else if (i->first == kErrorUrlParam) {
243       error_url->assign(i->second);
244     } else if (i->first == kCaptchaUrlParam) {
245       captcha_url->assign(i->second);
246     } else if (i->first == kCaptchaTokenParam) {
247       captcha_token->assign(i->second);
248     }
249   }
250 }
251 
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)252 void GaiaAuthFetcher::StartClientLogin(
253     const std::string& username,
254     const std::string& password,
255     const char* const service,
256     const std::string& login_token,
257     const std::string& login_captcha,
258     HostedAccountsSetting allow_hosted_accounts) {
259 
260   DCHECK(!fetch_pending_) << "Tried to fetch two things at once!";
261 
262   // This class is thread agnostic, so be sure to call this only on the
263   // same thread each time.
264   VLOG(1) << "Starting new ClientLogin fetch for:" << username;
265 
266   // Must outlive fetcher_.
267   request_body_ = MakeClientLoginBody(username,
268                                       password,
269                                       source_,
270                                       service,
271                                       login_token,
272                                       login_captcha,
273                                       allow_hosted_accounts);
274   fetcher_.reset(CreateGaiaFetcher(getter_,
275                                    request_body_,
276                                    client_login_gurl_,
277                                    this));
278   fetch_pending_ = true;
279   fetcher_->Start();
280 }
281 
StartIssueAuthToken(const std::string & sid,const std::string & lsid,const char * const service)282 void GaiaAuthFetcher::StartIssueAuthToken(const std::string& sid,
283                                           const std::string& lsid,
284                                           const char* const service) {
285 
286   DCHECK(!fetch_pending_) << "Tried to fetch two things at once!";
287 
288   VLOG(1) << "Starting IssueAuthToken for: " << service;
289   requested_service_ = service;
290   request_body_ = MakeIssueAuthTokenBody(sid, lsid, service);
291   fetcher_.reset(CreateGaiaFetcher(getter_,
292                                    request_body_,
293                                    issue_auth_token_gurl_,
294                                    this));
295   fetch_pending_ = true;
296   fetcher_->Start();
297 }
298 
StartGetUserInfo(const std::string & lsid,const std::string & info_key)299 void GaiaAuthFetcher::StartGetUserInfo(const std::string& lsid,
300                                        const std::string& info_key) {
301   DCHECK(!fetch_pending_) << "Tried to fetch two things at once!";
302 
303   VLOG(1) << "Starting GetUserInfo for lsid=" << lsid;
304   request_body_ = MakeGetUserInfoBody(lsid);
305   fetcher_.reset(CreateGaiaFetcher(getter_,
306                                    request_body_,
307                                    get_user_info_gurl_,
308                                    this));
309   fetch_pending_ = true;
310   requested_info_key_ = info_key;
311   fetcher_->Start();
312 }
313 
314 // static
GenerateAuthError(const std::string & data,const net::URLRequestStatus & status)315 GoogleServiceAuthError GaiaAuthFetcher::GenerateAuthError(
316     const std::string& data,
317     const net::URLRequestStatus& status) {
318   if (!status.is_success()) {
319     if (status.status() == net::URLRequestStatus::CANCELED) {
320       return GoogleServiceAuthError(GoogleServiceAuthError::REQUEST_CANCELED);
321     } else {
322       LOG(WARNING) << "Could not reach Google Accounts servers: errno "
323           << status.os_error();
324       return GoogleServiceAuthError::FromConnectionError(status.os_error());
325     }
326   } else {
327     if (IsSecondFactorSuccess(data)) {
328       return GoogleServiceAuthError(GoogleServiceAuthError::TWO_FACTOR);
329     }
330 
331     std::string error;
332     std::string url;
333     std::string captcha_url;
334     std::string captcha_token;
335     ParseClientLoginFailure(data, &error, &url, &captcha_url, &captcha_token);
336     LOG(WARNING) << "ClientLogin failed with " << error;
337 
338     if (error == kCaptchaError) {
339       GURL image_url(kCaptchaUrlPrefix + captcha_url);
340       GURL unlock_url(url);
341       return GoogleServiceAuthError::FromCaptchaChallenge(
342           captcha_token, image_url, unlock_url);
343     }
344     if (error == kAccountDeletedError)
345       return GoogleServiceAuthError(GoogleServiceAuthError::ACCOUNT_DELETED);
346     if (error == kAccountDisabledError)
347       return GoogleServiceAuthError(GoogleServiceAuthError::ACCOUNT_DISABLED);
348     if (error == kBadAuthenticationError) {
349       return GoogleServiceAuthError(
350           GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS);
351     }
352     if (error == kServiceUnavailableError) {
353       return GoogleServiceAuthError(
354           GoogleServiceAuthError::SERVICE_UNAVAILABLE);
355     }
356 
357     LOG(WARNING) << "Incomprehensible response from Google Accounts servers.";
358     return GoogleServiceAuthError(
359         GoogleServiceAuthError::SERVICE_UNAVAILABLE);
360   }
361 
362   NOTREACHED();
363   return GoogleServiceAuthError(GoogleServiceAuthError::SERVICE_UNAVAILABLE);
364 }
365 
OnClientLoginFetched(const std::string & data,const net::URLRequestStatus & status,int response_code)366 void GaiaAuthFetcher::OnClientLoginFetched(const std::string& data,
367                                            const net::URLRequestStatus& status,
368                                            int response_code) {
369   if (status.is_success() && response_code == RC_REQUEST_OK) {
370     VLOG(1) << "ClientLogin successful!";
371     std::string sid;
372     std::string lsid;
373     std::string token;
374     ParseClientLoginResponse(data, &sid, &lsid, &token);
375     consumer_->OnClientLoginSuccess(
376         GaiaAuthConsumer::ClientLoginResult(sid, lsid, token, data));
377   } else {
378     consumer_->OnClientLoginFailure(GenerateAuthError(data, status));
379   }
380 }
381 
OnIssueAuthTokenFetched(const std::string & data,const net::URLRequestStatus & status,int response_code)382 void GaiaAuthFetcher::OnIssueAuthTokenFetched(
383     const std::string& data,
384     const net::URLRequestStatus& status,
385     int response_code) {
386   if (status.is_success() && response_code == RC_REQUEST_OK) {
387     // Only the bare token is returned in the body of this Gaia call
388     // without any padding.
389     consumer_->OnIssueAuthTokenSuccess(requested_service_, data);
390   } else {
391     consumer_->OnIssueAuthTokenFailure(requested_service_,
392         GenerateAuthError(data, status));
393   }
394 }
395 
OnGetUserInfoFetched(const std::string & data,const net::URLRequestStatus & status,int response_code)396 void GaiaAuthFetcher::OnGetUserInfoFetched(
397     const std::string& data,
398     const net::URLRequestStatus& status,
399     int response_code) {
400   using std::vector;
401   using std::string;
402   using std::pair;
403 
404   if (status.is_success() && response_code == RC_REQUEST_OK) {
405     vector<pair<string, string> > tokens;
406     base::SplitStringIntoKeyValuePairs(data, '=', '\n', &tokens);
407     for (vector<pair<string, string> >::iterator i = tokens.begin();
408          i != tokens.end(); ++i) {
409       if (i->first == requested_info_key_) {
410         consumer_->OnGetUserInfoSuccess(i->first, i->second);
411         return;
412       }
413     }
414     consumer_->OnGetUserInfoKeyNotFound(requested_info_key_);
415   } else {
416     consumer_->OnGetUserInfoFailure(GenerateAuthError(data, status));
417   }
418 }
419 
OnURLFetchComplete(const URLFetcher * source,const GURL & url,const net::URLRequestStatus & status,int response_code,const ResponseCookies & cookies,const std::string & data)420 void GaiaAuthFetcher::OnURLFetchComplete(const URLFetcher* source,
421                                          const GURL& url,
422                                          const net::URLRequestStatus& status,
423                                          int response_code,
424                                          const ResponseCookies& cookies,
425                                          const std::string& data) {
426   fetch_pending_ = false;
427   if (url == client_login_gurl_) {
428     OnClientLoginFetched(data, status, response_code);
429   } else if (url == issue_auth_token_gurl_) {
430     OnIssueAuthTokenFetched(data, status, response_code);
431   } else if (url == get_user_info_gurl_) {
432     OnGetUserInfoFetched(data, status, response_code);
433   } else {
434     NOTREACHED();
435   }
436 }
437 
438 // static
IsSecondFactorSuccess(const std::string & alleged_error)439 bool GaiaAuthFetcher::IsSecondFactorSuccess(
440     const std::string& alleged_error) {
441   return alleged_error.find(kSecondFactor) !=
442       std::string::npos;
443 }
444