OLD | NEW |
| (Empty) |
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 "chrome/common/net/gaia/gaia_authenticator.h" | |
6 | |
7 #include <string> | |
8 #include <utility> | |
9 #include <vector> | |
10 | |
11 #include "base/basictypes.h" | |
12 #include "base/port.h" | |
13 #include "base/string_split.h" | |
14 #include "googleurl/src/gurl.h" | |
15 #include "net/base/escape.h" | |
16 #include "net/http/http_status_code.h" | |
17 | |
18 using std::pair; | |
19 using std::string; | |
20 using std::vector; | |
21 | |
22 namespace gaia { | |
23 | |
24 static const char kGaiaV1IssueAuthTokenPath[] = "/accounts/IssueAuthToken"; | |
25 | |
26 static const char kGetUserInfoPath[] = "/accounts/GetUserInfo"; | |
27 | |
28 GaiaAuthenticator::AuthResults::AuthResults() : auth_error(None) {} | |
29 | |
30 GaiaAuthenticator::AuthResults::AuthResults(const AuthResults& other) | |
31 : email(other.email), | |
32 password(other.password), | |
33 sid(other.sid), | |
34 lsid(other.lsid), | |
35 auth_token(other.auth_token), | |
36 primary_email(other.primary_email), | |
37 error_msg(other.error_msg), | |
38 auth_error(other.auth_error), | |
39 auth_error_url(other.auth_error_url), | |
40 captcha_token(other.captcha_token), | |
41 captcha_url(other.captcha_url) { | |
42 } | |
43 | |
44 GaiaAuthenticator::AuthResults::~AuthResults() {} | |
45 | |
46 GaiaAuthenticator::AuthParams::AuthParams() : authenticator(NULL), | |
47 request_id(0) {} | |
48 | |
49 GaiaAuthenticator::AuthParams::~AuthParams() {} | |
50 | |
51 // Sole constructor with initializers for all fields. | |
52 GaiaAuthenticator::GaiaAuthenticator(const string& user_agent, | |
53 const string& service_id, | |
54 const string& gaia_url) | |
55 : user_agent_(user_agent), | |
56 service_id_(service_id), | |
57 gaia_url_(gaia_url), | |
58 request_count_(0), | |
59 delay_(0), | |
60 next_allowed_auth_attempt_time_(0), | |
61 early_auth_attempt_count_(0), | |
62 message_loop_(NULL) { | |
63 } | |
64 | |
65 GaiaAuthenticator::~GaiaAuthenticator() { | |
66 } | |
67 | |
68 // mutex_ must be entered before calling this function. | |
69 GaiaAuthenticator::AuthParams GaiaAuthenticator::MakeParams( | |
70 const string& user_name, | |
71 const string& password, | |
72 const string& captcha_token, | |
73 const string& captcha_value) { | |
74 AuthParams params; | |
75 params.request_id = ++request_count_; | |
76 params.email = user_name; | |
77 params.password = password; | |
78 params.captcha_token = captcha_token; | |
79 params.captcha_value = captcha_value; | |
80 params.authenticator = this; | |
81 return params; | |
82 } | |
83 | |
84 bool GaiaAuthenticator::Authenticate(const string& user_name, | |
85 const string& password, | |
86 const string& captcha_token, | |
87 const string& captcha_value) { | |
88 DCHECK_EQ(MessageLoop::current(), message_loop_); | |
89 | |
90 AuthParams const params = | |
91 MakeParams(user_name, password, captcha_token, captcha_value); | |
92 return AuthenticateImpl(params); | |
93 } | |
94 | |
95 bool GaiaAuthenticator::AuthenticateWithLsid(const string& lsid) { | |
96 auth_results_.lsid = lsid; | |
97 // We need to lookup the email associated with this LSID cookie in order to | |
98 // update |auth_results_| with the correct values. | |
99 if (LookupEmail(&auth_results_)) { | |
100 auth_results_.email = auth_results_.primary_email; | |
101 return IssueAuthToken(&auth_results_, service_id_); | |
102 } | |
103 return false; | |
104 } | |
105 | |
106 bool GaiaAuthenticator::AuthenticateImpl(const AuthParams& params) { | |
107 DCHECK_EQ(MessageLoop::current(), message_loop_); | |
108 AuthResults results; | |
109 const bool succeeded = AuthenticateImpl(params, &results); | |
110 return succeeded; | |
111 } | |
112 | |
113 // This method makes an HTTP request to the Gaia server, and calls other | |
114 // methods to help parse the response. If authentication succeeded, then | |
115 // Gaia-issued cookies are available in the respective variables; if | |
116 // authentication failed, then the exact error is available as an enum. If the | |
117 // client wishes to save the credentials, the last parameter must be true. | |
118 // If a subsequent request is made with fresh credentials, the saved credentials | |
119 // are wiped out; any subsequent request to the zero-parameter overload of this | |
120 // method preserves the saved credentials. | |
121 bool GaiaAuthenticator::AuthenticateImpl(const AuthParams& params, | |
122 AuthResults* results) { | |
123 DCHECK_EQ(MessageLoop::current(), message_loop_); | |
124 results->auth_error = ConnectionUnavailable; | |
125 results->email = params.email.data(); | |
126 results->password = params.password; | |
127 | |
128 // The aim of this code is to start failing requests if due to a logic error | |
129 // in the program we're hammering GAIA. | |
130 #if defined(OS_WIN) | |
131 __time32_t now = _time32(0); | |
132 #else // defined(OS_WIN) | |
133 time_t now = time(0); | |
134 #endif // defined(OS_WIN) | |
135 | |
136 if (now > next_allowed_auth_attempt_time_) { | |
137 next_allowed_auth_attempt_time_ = now + 1; | |
138 // If we're more than 2 minutes past the allowed time we reset the early | |
139 // attempt count. | |
140 if (now - next_allowed_auth_attempt_time_ > 2 * 60) { | |
141 delay_ = 1; | |
142 early_auth_attempt_count_ = 0; | |
143 } | |
144 } else { | |
145 ++early_auth_attempt_count_; | |
146 // Allow 3 attempts, but then limit. | |
147 if (early_auth_attempt_count_ > 3) { | |
148 delay_ = GetBackoffDelaySeconds(delay_); | |
149 next_allowed_auth_attempt_time_ = now + delay_; | |
150 return false; | |
151 } | |
152 } | |
153 | |
154 return PerformGaiaRequest(params, results); | |
155 } | |
156 | |
157 bool GaiaAuthenticator::PerformGaiaRequest(const AuthParams& params, | |
158 AuthResults* results) { | |
159 DCHECK_EQ(MessageLoop::current(), message_loop_); | |
160 GURL gaia_auth_url(gaia_url_); | |
161 | |
162 string post_body; | |
163 post_body += "Email=" + net::EscapeUrlEncodedData(params.email, true); | |
164 post_body += "&Passwd=" + net::EscapeUrlEncodedData(params.password, true); | |
165 post_body += "&source=" + net::EscapeUrlEncodedData(user_agent_, true); | |
166 post_body += "&service=" + service_id_; | |
167 if (!params.captcha_token.empty() && !params.captcha_value.empty()) { | |
168 post_body += "&logintoken=" + | |
169 net::EscapeUrlEncodedData(params.captcha_token, true); | |
170 post_body += "&logincaptcha=" + | |
171 net::EscapeUrlEncodedData(params.captcha_value, true); | |
172 } | |
173 post_body += "&PersistentCookie=true"; | |
174 // We set it to GOOGLE (and not HOSTED or HOSTED_OR_GOOGLE) because we only | |
175 // allow consumer logins. | |
176 post_body += "&accountType=GOOGLE"; | |
177 | |
178 string message_text; | |
179 unsigned long server_response_code; | |
180 if (!Post(gaia_auth_url, post_body, &server_response_code, &message_text)) { | |
181 results->auth_error = ConnectionUnavailable; | |
182 return false; | |
183 } | |
184 | |
185 // Parse reply in two different ways, depending on if request failed or | |
186 // succeeded. | |
187 if (net::HTTP_FORBIDDEN == server_response_code) { | |
188 ExtractAuthErrorFrom(message_text, results); | |
189 return false; | |
190 } else if (net::HTTP_OK == server_response_code) { | |
191 ExtractTokensFrom(message_text, results); | |
192 if (!IssueAuthToken(results, service_id_)) { | |
193 return false; | |
194 } | |
195 | |
196 return LookupEmail(results); | |
197 } else { | |
198 results->auth_error = Unknown; | |
199 return false; | |
200 } | |
201 } | |
202 | |
203 bool GaiaAuthenticator::Post(const GURL& url, | |
204 const std::string& post_body, | |
205 unsigned long* response_code, | |
206 std::string* response_body) { | |
207 return false; | |
208 } | |
209 | |
210 bool GaiaAuthenticator::LookupEmail(AuthResults* results) { | |
211 DCHECK_EQ(MessageLoop::current(), message_loop_); | |
212 // Use the provided Gaia server, but change the path to what V1 expects. | |
213 GURL url(gaia_url_); // Gaia server. | |
214 GURL::Replacements repl; | |
215 // Needs to stay in scope till GURL is out of scope. | |
216 string path(kGetUserInfoPath); | |
217 repl.SetPathStr(path); | |
218 url = url.ReplaceComponents(repl); | |
219 | |
220 string post_body; | |
221 post_body += "LSID="; | |
222 post_body += net::EscapeUrlEncodedData(results->lsid, true); | |
223 | |
224 unsigned long server_response_code; | |
225 string message_text; | |
226 if (!Post(url, post_body, &server_response_code, &message_text)) { | |
227 return false; | |
228 } | |
229 | |
230 // Check if we received a valid AuthToken; if not, ignore it. | |
231 if (net::HTTP_FORBIDDEN == server_response_code) { | |
232 // Server says we're not authenticated. | |
233 ExtractAuthErrorFrom(message_text, results); | |
234 return false; | |
235 } else if (net::HTTP_OK == server_response_code) { | |
236 typedef vector<pair<string, string> > Tokens; | |
237 Tokens tokens; | |
238 base::SplitStringIntoKeyValuePairs(message_text, '=', '\n', &tokens); | |
239 for (Tokens::iterator i = tokens.begin(); i != tokens.end(); ++i) { | |
240 if ("accountType" == i->first) { | |
241 // We never authenticate an email as a hosted account. | |
242 DCHECK_EQ("GOOGLE", i->second); | |
243 } else if ("email" == i->first) { | |
244 results->primary_email = i->second; | |
245 } | |
246 } | |
247 return true; | |
248 } | |
249 return false; | |
250 } | |
251 | |
252 int GaiaAuthenticator::GetBackoffDelaySeconds(int current_backoff_delay) { | |
253 NOTREACHED(); | |
254 return current_backoff_delay; | |
255 } | |
256 | |
257 // We need to call this explicitly when we need to obtain a long-lived session | |
258 // token. | |
259 bool GaiaAuthenticator::IssueAuthToken(AuthResults* results, | |
260 const string& service_id) { | |
261 DCHECK_EQ(MessageLoop::current(), message_loop_); | |
262 // Use the provided Gaia server, but change the path to what V1 expects. | |
263 GURL url(gaia_url_); // Gaia server. | |
264 GURL::Replacements repl; | |
265 // Needs to stay in scope till GURL is out of scope. | |
266 string path(kGaiaV1IssueAuthTokenPath); | |
267 repl.SetPathStr(path); | |
268 url = url.ReplaceComponents(repl); | |
269 | |
270 string post_body; | |
271 post_body += "LSID="; | |
272 post_body += net::EscapeUrlEncodedData(results->lsid, true); | |
273 post_body += "&service=" + service_id; | |
274 post_body += "&Session=true"; | |
275 | |
276 unsigned long server_response_code; | |
277 string message_text; | |
278 if (!Post(url, post_body, &server_response_code, &message_text)) { | |
279 return false; | |
280 } | |
281 | |
282 // Check if we received a valid AuthToken; if not, ignore it. | |
283 if (net::HTTP_FORBIDDEN == server_response_code) { | |
284 // Server says we're not authenticated. | |
285 ExtractAuthErrorFrom(message_text, results); | |
286 return false; | |
287 } else if (net::HTTP_OK == server_response_code) { | |
288 // Note that the format of message_text is different from what is returned | |
289 // in the first request, or to the sole request that is made to Gaia V2. | |
290 // Specifically, the entire string is the AuthToken, and looks like: | |
291 // "<token>" rather than "AuthToken=<token>". Thus, we need not use | |
292 // ExtractTokensFrom(...), but simply assign the token. | |
293 int last_index = message_text.length() - 1; | |
294 if ('\n' == message_text[last_index]) | |
295 message_text.erase(last_index); | |
296 results->auth_token = message_text; | |
297 return true; | |
298 } | |
299 return false; | |
300 } | |
301 | |
302 // Helper method that extracts tokens from a successful reply, and saves them | |
303 // in the right fields. | |
304 void GaiaAuthenticator::ExtractTokensFrom(const string& response, | |
305 AuthResults* results) { | |
306 vector<pair<string, string> > tokens; | |
307 base::SplitStringIntoKeyValuePairs(response, '=', '\n', &tokens); | |
308 for (vector<pair<string, string> >::iterator i = tokens.begin(); | |
309 i != tokens.end(); ++i) { | |
310 if (i->first == "SID") { | |
311 results->sid = i->second; | |
312 } else if (i->first == "LSID") { | |
313 results->lsid = i->second; | |
314 } else if (i->first == "Auth") { | |
315 results->auth_token = i->second; | |
316 } | |
317 } | |
318 } | |
319 | |
320 // Helper method that extracts tokens from a failure response, and saves them | |
321 // in the right fields. | |
322 void GaiaAuthenticator::ExtractAuthErrorFrom(const string& response, | |
323 AuthResults* results) { | |
324 vector<pair<string, string> > tokens; | |
325 base::SplitStringIntoKeyValuePairs(response, '=', '\n', &tokens); | |
326 for (vector<pair<string, string> >::iterator i = tokens.begin(); | |
327 i != tokens.end(); ++i) { | |
328 if (i->first == "Error") { | |
329 results->error_msg = i->second; | |
330 } else if (i->first == "Url") { | |
331 results->auth_error_url = i->second; | |
332 } else if (i->first == "CaptchaToken") { | |
333 results->captcha_token = i->second; | |
334 } else if (i->first == "CaptchaUrl") { | |
335 results->captcha_url = i->second; | |
336 } | |
337 } | |
338 | |
339 // Convert string error messages to enum values. Each case has two different | |
340 // strings; the first one is the most current and the second one is | |
341 // deprecated, but available. | |
342 const string& error_msg = results->error_msg; | |
343 if (error_msg == "BadAuthentication" || error_msg == "badauth") { | |
344 results->auth_error = BadAuthentication; | |
345 } else if (error_msg == "NotVerified" || error_msg == "nv") { | |
346 results->auth_error = NotVerified; | |
347 } else if (error_msg == "TermsNotAgreed" || error_msg == "tna") { | |
348 results->auth_error = TermsNotAgreed; | |
349 } else if (error_msg == "Unknown" || error_msg == "unknown") { | |
350 results->auth_error = Unknown; | |
351 } else if (error_msg == "AccountDeleted" || error_msg == "adel") { | |
352 results->auth_error = AccountDeleted; | |
353 } else if (error_msg == "AccountDisabled" || error_msg == "adis") { | |
354 results->auth_error = AccountDisabled; | |
355 } else if (error_msg == "CaptchaRequired" || error_msg == "cr") { | |
356 results->auth_error = CaptchaRequired; | |
357 } else if (error_msg == "ServiceUnavailable" || error_msg == "ire") { | |
358 results->auth_error = ServiceUnavailable; | |
359 } | |
360 } | |
361 | |
362 // Reset all stored credentials, perhaps in preparation for letting a different | |
363 // user sign in. | |
364 void GaiaAuthenticator::ResetCredentials() { | |
365 DCHECK_EQ(MessageLoop::current(), message_loop_); | |
366 AuthResults blank; | |
367 auth_results_ = blank; | |
368 } | |
369 | |
370 void GaiaAuthenticator::SetUsernamePassword(const string& username, | |
371 const string& password) { | |
372 DCHECK_EQ(MessageLoop::current(), message_loop_); | |
373 auth_results_.password = password; | |
374 auth_results_.email = username; | |
375 } | |
376 | |
377 void GaiaAuthenticator::SetUsername(const string& username) { | |
378 DCHECK_EQ(MessageLoop::current(), message_loop_); | |
379 auth_results_.email = username; | |
380 } | |
381 | |
382 void GaiaAuthenticator::RenewAuthToken(const string& auth_token) { | |
383 DCHECK_EQ(MessageLoop::current(), message_loop_); | |
384 DCHECK(!this->auth_token().empty()); | |
385 auth_results_.auth_token = auth_token; | |
386 } | |
387 void GaiaAuthenticator::SetAuthToken(const string& auth_token) { | |
388 DCHECK_EQ(MessageLoop::current(), message_loop_); | |
389 auth_results_.auth_token = auth_token; | |
390 } | |
391 | |
392 bool GaiaAuthenticator::Authenticate(const string& user_name, | |
393 const string& password) { | |
394 DCHECK_EQ(MessageLoop::current(), message_loop_); | |
395 const string empty; | |
396 return Authenticate(user_name, password, empty, | |
397 empty); | |
398 } | |
399 | |
400 } // namespace gaia | |
OLD | NEW |