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