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
24const char GaiaAuthFetcher::kClientLoginFormat[] =
25    "Email=%s&"
26    "Passwd=%s&"
27    "PersistentCookie=%s&"
28    "accountType=%s&"
29    "source=%s&"
30    "service=%s";
31// static
32const 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
42const char GaiaAuthFetcher::kIssueAuthTokenFormat[] =
43    "SID=%s&"
44    "LSID=%s&"
45    "service=%s&"
46    "Session=%s";
47// static
48const char GaiaAuthFetcher::kGetUserInfoFormat[] =
49    "LSID=%s";
50
51// static
52const char GaiaAuthFetcher::kAccountDeletedError[] = "AccountDeleted";
53// static
54const char GaiaAuthFetcher::kAccountDisabledError[] = "AccountDisabled";
55// static
56const char GaiaAuthFetcher::kBadAuthenticationError[] = "BadAuthentication";
57// static
58const char GaiaAuthFetcher::kCaptchaError[] = "CaptchaRequired";
59// static
60const char GaiaAuthFetcher::kServiceUnavailableError[] =
61    "ServiceUnavailable";
62// static
63const char GaiaAuthFetcher::kErrorParam[] = "Error";
64// static
65const char GaiaAuthFetcher::kErrorUrlParam[] = "Url";
66// static
67const char GaiaAuthFetcher::kCaptchaUrlParam[] = "CaptchaUrl";
68// static
69const char GaiaAuthFetcher::kCaptchaTokenParam[] = "CaptchaToken";
70// static
71const char GaiaAuthFetcher::kCaptchaUrlPrefix[] =
72    "http://www.google.com/accounts/";
73
74// static
75const char GaiaAuthFetcher::kCookiePersistence[] = "true";
76// static
77// TODO(johnnyg): When hosted accounts are supported by sync,
78// we can always use "HOSTED_OR_GOOGLE"
79const char GaiaAuthFetcher::kAccountTypeHostedOrGoogle[] =
80    "HOSTED_OR_GOOGLE";
81const char GaiaAuthFetcher::kAccountTypeGoogle[] =
82    "GOOGLE";
83
84// static
85const 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.
89const char GaiaAuthFetcher::kClientLoginUrl[] =
90    "https://www.google.com/accounts/ClientLogin";
91const char GaiaAuthFetcher::kIssueAuthTokenUrl[] =
92    "https://www.google.com/accounts/IssueAuthToken";
93const char GaiaAuthFetcher::kGetUserInfoUrl[] =
94    "https://www.google.com/accounts/GetUserInfo";
95
96GaiaAuthFetcher::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
107GaiaAuthFetcher::~GaiaAuthFetcher() {}
108
109bool GaiaAuthFetcher::HasPendingFetch() {
110  return fetch_pending_;
111}
112
113void GaiaAuthFetcher::CancelRequest() {
114  fetcher_.reset();
115  fetch_pending_ = false;
116}
117
118// static
119URLFetcher* 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
137std::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
177std::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
197std::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
204void 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
227void 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
252void 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
282void 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
299void 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
315GoogleServiceAuthError 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
366void 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
382void 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
396void 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
420void 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
439bool GaiaAuthFetcher::IsSecondFactorSuccess(
440    const std::string& alleged_error) {
441  return alleged_error.find(kSecondFactor) !=
442      std::string::npos;
443}
444