Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(163)

Side by Side Diff: chrome/browser/sync/engine/net/gaia_authenticator.cc

Issue 194065: Initial commit of sync engine code to browser/sync.... (Closed) Base URL: svn://chrome-svn/chrome/trunk/src/
Patch Set: Fixes to gtest include path, reverted syncapi. Created 11 years, 3 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
Property Changes:
Added: svn:eol-style
+ LF
OLDNEW
(Empty)
1 // Copyright (c) 2009 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/browser/sync/engine/net/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_util.h"
14 #include "chrome/browser/sync/engine/all_status.h"
15 #include "chrome/browser/sync/engine/net/http_return.h"
16 #include "chrome/browser/sync/engine/net/url_translator.h"
17 #include "chrome/browser/sync/util/event_sys-inl.h"
18 #include "googleurl/src/gurl.h"
19
20 using std::pair;
21 using std::string;
22 using std::vector;
23
24 // TODO(timsteele): Integrate the following two functions to string_util.h or
25 // somewhere that makes them unit-testable.
26 bool SplitStringIntoKeyValues(const string& line,
27 char key_value_delimiter,
28 string* key, vector<string>* values) {
29 key->clear();
30 values->clear();
31
32 // find the key string
33 int end_key_pos = line.find_first_of(key_value_delimiter);
34 if (end_key_pos == string::npos) {
35 DLOG(INFO) << "cannot parse key from line: " << line;
36 return false; // no key
37 }
38 key->assign(line, 0, end_key_pos);
39
40 // find the values string
41 string remains(line, end_key_pos, line.size() - end_key_pos);
42 int begin_values_pos = remains.find_first_not_of(key_value_delimiter);
43 if (begin_values_pos == string::npos) {
44 DLOG(INFO) << "cannot parse value from line: " << line;
45 return false; // no value
46 }
47 string values_string(remains, begin_values_pos,
48 remains.size() - begin_values_pos);
49
50 // construct the values vector
51 values->push_back(values_string);
52 return true;
53 }
54
55 bool SplitStringIntoKeyValuePairs(const string& line,
56 char key_value_delimiter,
57 char key_value_pair_delimiter,
58 vector<pair<string, string> >* kv_pairs) {
59 kv_pairs->clear();
60
61 vector<string> pairs;
62 SplitString(line, key_value_pair_delimiter, &pairs);
63
64 bool success = true;
65 for (size_t i = 0; i < pairs.size(); ++i) {
66 string key;
67 vector<string> value;
68 if (!SplitStringIntoKeyValues(pairs[i],
69 key_value_delimiter,
70 &key, &value)) {
71 // Don't return here, to allow for keys without associated
72 // values; just record that our split failed.
73 success = false;
74 }
75 DCHECK_LE(value.size(), 1);
76 kv_pairs->push_back(make_pair(key, value.empty()? "" : value[0]));
77 }
78 return success;
79 }
80
81 namespace browser_sync {
82
83 static const char kGaiaV1IssueAuthTokenPath[] = "/accounts/IssueAuthToken";
84
85 static const char kGetUserInfoPath[] = "/accounts/GetUserInfo";
86
87 // Sole constructor with initializers for all fields.
88 GaiaAuthenticator::GaiaAuthenticator(const string& user_agent,
89 const string& service_id,
90 const string& gaia_url)
91 : user_agent_(user_agent),
92 service_id_(service_id),
93 gaia_url_(gaia_url),
94 request_count_(0),
95 early_auth_attempt_count_(0),
96 delay_(0),
97 next_allowed_auth_attempt_time_(0) {
98 GaiaAuthEvent done = { GaiaAuthEvent::GAIA_AUTHENTICATOR_DESTROYED, None,
99 this };
100 channel_ = new Channel(done);
101 }
102
103 GaiaAuthenticator::~GaiaAuthenticator() {
104 delete channel_;
105 }
106
107 bool GaiaAuthenticator::LaunchAuthenticate(const AuthParams& params,
108 bool synchronous) {
109 if (synchronous)
110 return AuthenticateImpl(params);
111 AuthParams* copy = new AuthParams;
112 *copy = params;
113 pthread_t thread_id;
114 int result = pthread_create(&thread_id, 0, &GaiaAuthenticator::ThreadMain,
115 copy);
116 if (result)
117 return false;
118 return true;
119 }
120
121
122 void* GaiaAuthenticator::ThreadMain(void* arg) {
123 NameCurrentThreadForDebugging("SyncEngine_GaiaAuthenticatorThread");
124 AuthParams* const params = reinterpret_cast<AuthParams*>(arg);
125 params->authenticator->AuthenticateImpl(*params);
126 delete params;
127 return 0;
128 }
129
130 // mutex_ must be entered before calling this function.
131 GaiaAuthenticator::AuthParams GaiaAuthenticator::MakeParams(
132 const string& user_name,
133 const string& password,
134 SaveCredentials should_save_credentials,
135 const string& captcha_token,
136 const string& captcha_value,
137 SignIn try_first) {
138 AuthParams params;
139 params.request_id = ++request_count_;
140 params.email = user_name;
141 params.password = password;
142 params.should_save_credentials = should_save_credentials;
143 params.captcha_token = captcha_token;
144 params.captcha_value = captcha_value;
145 params.authenticator = this;
146 params.try_first = try_first;
147 return params;
148 }
149
150 bool GaiaAuthenticator::Authenticate(const string& user_name,
151 const string& password,
152 SaveCredentials should_save_credentials,
153 bool synchronous,
154 const string& captcha_token,
155 const string& captcha_value,
156 SignIn try_first) {
157 mutex_.Lock();
158 AuthParams const params =
159 MakeParams(user_name, password, should_save_credentials, captcha_token,
160 captcha_value, try_first);
161 mutex_.Unlock();
162 return LaunchAuthenticate(params, synchronous);
163 }
164
165 bool GaiaAuthenticator::AuthenticateImpl(const AuthParams& params) {
166 AuthResults results;
167 const bool succeeded = AuthenticateImpl(params, &results);
168 mutex_.Lock();
169 if (params.request_id == request_count_) {
170 auth_results_ = results;
171 GaiaAuthEvent event = { succeeded ? GaiaAuthEvent::GAIA_AUTH_SUCCEEDED
172 : GaiaAuthEvent::GAIA_AUTH_FAILED,
173 results.auth_error, this };
174 mutex_.Unlock();
175 channel_->NotifyListeners(event);
176 } else {
177 mutex_.Unlock();
178 }
179 return succeeded;
180 }
181
182 // This method makes an HTTP request to the Gaia server, and calls other
183 // methods to help parse the response. If authentication succeeded, then
184 // Gaia-issued cookies are available in the respective variables; if
185 // authentication failed, then the exact error is available as an enum. If the
186 // client wishes to save the credentials, the last parameter must be true.
187 // If a subsequent request is made with fresh credentials, the saved credentials
188 // are wiped out; any subsequent request to the zero-parameter overload of this
189 // method preserves the saved credentials.
190 bool GaiaAuthenticator::AuthenticateImpl(const AuthParams& params,
191 AuthResults* results) {
192 results->credentials_saved = params.should_save_credentials;
193 results->auth_error = ConnectionUnavailable;
194 // Save credentials if so requested.
195 if (params.should_save_credentials != DONT_SAVE_CREDENTIALS) {
196 results->email = params.email.data();
197 results->password = params.password;
198 } else { // Explicitly clear previously-saved credentials.
199 results->email = "";
200 results->password = "";
201 }
202
203 // The aim of this code is to start failing requests if due to a logic error
204 // in the program we're hammering GAIA.
205 time_t now = time(0);
206 if (now > next_allowed_auth_attempt_time_) {
207 next_allowed_auth_attempt_time_ = now + 1;
208 // If we're more than 2 minutes past the allowed time we reset the early
209 // attempt count.
210 if (now - next_allowed_auth_attempt_time_ > 2 * 60) {
211 delay_ = 1;
212 early_auth_attempt_count_ = 0;
213 }
214 } else {
215 ++early_auth_attempt_count_;
216 // Allow 3 attempts, but then limit.
217 if (early_auth_attempt_count_ > 3) {
218 delay_ = AllStatus::GetRecommendedDelaySeconds(delay_);
219 next_allowed_auth_attempt_time_ = now + delay_;
220 return false;
221 }
222 }
223
224 return PerformGaiaRequest(params, results);
225 }
226
227 bool GaiaAuthenticator::PerformGaiaRequest(const AuthParams& params,
228 AuthResults* results) {
229 GURL gaia_auth_url(gaia_url_);
230
231 string post_body;
232 post_body += "Email=" + CgiEscapeString(params.email);
233 post_body += "&Passwd=" + CgiEscapeString(params.password);
234 post_body += "&source=" + CgiEscapeString(user_agent_);
235 post_body += "&service=" + service_id_;
236 if (!params.captcha_token.empty() && !params.captcha_value.empty()) {
237 post_body += "&logintoken=" + CgiEscapeString(params.captcha_token);
238 post_body += "&logincaptcha=" + CgiEscapeString(params.captcha_value);
239 }
240 post_body += "&PersistentCookie=true";
241 // We set it to GOOGLE (and not HOSTED or HOSTED_OR_GOOGLE) because we only
242 // allow consumer logins.
243 post_body += "&accountType=GOOGLE";
244
245 string message_text;
246 unsigned long server_response_code;
247 if (!Post(gaia_auth_url, post_body, &server_response_code,
248 &message_text)) {
249 results->auth_error = ConnectionUnavailable;
250 return false;
251 }
252
253 // Parse reply in two different ways, depending on if request failed or
254 // succeeded.
255 if (RC_FORBIDDEN == server_response_code) {
256 ExtractAuthErrorFrom(message_text, results);
257 return false;
258 } else if (RC_REQUEST_OK == server_response_code) {
259 ExtractTokensFrom(message_text, results);
260 const bool old_gaia =
261 results->auth_token.empty() && !results->lsid.empty();
262 const bool long_lived_token =
263 params.should_save_credentials == PERSIST_TO_DISK;
264 if ((old_gaia || long_lived_token) &&
265 !IssueAuthToken(results, service_id_, long_lived_token))
266 return false;
267
268 return LookupEmail(results);
269 } else {
270 results->auth_error = Unknown;
271 return false;
272 }
273 }
274
275 bool GaiaAuthenticator::LookupEmail(AuthResults* results) {
276 // Use the provided Gaia server, but change the path to what V1 expects.
277 GURL url(gaia_url_); // Gaia server
278 GURL::Replacements repl;
279 // Needs to stay in scope till GURL is out of scope
280 string path(kGetUserInfoPath);
281 repl.SetPathStr(path);
282 url = url.ReplaceComponents(repl);
283
284 string post_body;
285 post_body += "LSID=";
286 post_body += CgiEscapeString(results->lsid);
287
288 unsigned long server_response_code;
289 string message_text;
290 if (!Post(url, post_body, &server_response_code, &message_text)) {
291 return false;
292 }
293
294 // Check if we received a valid AuthToken; if not, ignore it.
295 if (RC_FORBIDDEN == server_response_code) {
296 // Server says we're not authenticated.
297 ExtractAuthErrorFrom(message_text, results);
298 return false;
299 } else if (RC_REQUEST_OK == server_response_code) {
300 typedef vector<pair<string, string> > Tokens;
301 Tokens tokens;
302 SplitStringIntoKeyValuePairs(message_text, '=', '\n', &tokens);
303 for (Tokens::iterator i = tokens.begin(); i != tokens.end(); ++i) {
304 if ("accountType" == i->first) {
305 // We never authenticate an email as a hosted account.
306 DCHECK_EQ("GOOGLE", i->second);
307 results->signin = GMAIL_SIGNIN;
308 } else if ("email" == i->first) {
309 results->primary_email = i->second;
310 }
311 }
312 return true;
313 }
314 return false;
315 }
316
317 // We need to call this explicitly when we need to obtain a long-lived session
318 // token.
319 bool GaiaAuthenticator::IssueAuthToken(AuthResults* results,
320 const string& service_id,
321 bool long_lived) {
322 // Use the provided Gaia server, but change the path to what V1 expects.
323 GURL url(gaia_url_); // Gaia server
324 GURL::Replacements repl;
325 // Needs to stay in scope till GURL is out of scope
326 string path(kGaiaV1IssueAuthTokenPath);
327 repl.SetPathStr(path);
328 url = url.ReplaceComponents(repl);
329
330 string post_body;
331 post_body += "LSID=";
332 post_body += CgiEscapeString(results->lsid);
333 post_body += "&service=" + service_id;
334 if (long_lived) {
335 post_body += "&Session=true";
336 }
337
338 unsigned long server_response_code;
339 string message_text;
340 if (!Post(url, post_body,
341 &server_response_code, &message_text)) {
342 return false;
343 }
344
345 // Check if we received a valid AuthToken; if not, ignore it.
346 if (RC_FORBIDDEN == server_response_code) {
347 // Server says we're not authenticated.
348 ExtractAuthErrorFrom(message_text, results);
349 return false;
350 } else if (RC_REQUEST_OK == server_response_code) {
351 // Note that the format of message_text is different from what is returned
352 // in the first request, or to the sole request that is made to Gaia V2.
353 // Specifically, the entire string is the AuthToken, and looks like:
354 // "<token>" rather than "AuthToken=<token>". Thus, we need not use
355 // ExtractTokensFrom(...), but simply assign the token.
356 int last_index = message_text.length() - 1;
357 if ('\n' == message_text[last_index])
358 message_text.erase(last_index);
359 results->auth_token = message_text;
360 return true;
361 }
362 return false;
363 }
364
365 // TOOD(sync): This passing around of AuthResults makes it really unclear who
366 // actually owns the authentication state and when it is valid, but this is
367 // endemic to this implementation. We should fix this.
368 bool GaiaAuthenticator::AuthenticateService(const string& service_id,
369 const string& sid,
370 const string& lsid,
371 string* other_service_cookie) {
372 // Copy the AuthResults structure and overload the auth_token field
373 // in the copy, local_results, to mean the auth_token for service_id.
374 AuthResults local_results;
375 local_results.sid = sid;
376 local_results.lsid = lsid;
377
378 if (!IssueAuthToken(&local_results, service_id, true)) {
379 LOG(ERROR) << "[AUTH] Failed to obtain cookie for " << service_id;
380 return false;
381 }
382
383 swap(*other_service_cookie, local_results.auth_token);
384 return true;
385 }
386
387 // Helper method that extracts tokens from a successful reply, and saves them
388 // in the right fields.
389 void GaiaAuthenticator::ExtractTokensFrom(const string& response,
390 AuthResults* results) {
391 vector<pair<string, string> > tokens;
392 SplitStringIntoKeyValuePairs(response, '=', '\n', &tokens);
393 for (vector<pair<string, string> >::iterator i = tokens.begin();
394 i != tokens.end(); ++i) {
395 if (i->first == "SID") {
396 results->sid = i->second;
397 } else if (i->first == "LSID") {
398 results->lsid = i->second;
399 } else if (i->first == "Auth") {
400 results->auth_token = i->second;
401 }
402 }
403 }
404
405 // Helper method that extracts tokens from a failure response, and saves them
406 // in the right fields.
407 void GaiaAuthenticator::ExtractAuthErrorFrom(const string& response,
408 AuthResults* results) {
409 vector<pair<string, string> > tokens;
410 SplitStringIntoKeyValuePairs(response, '=', '\n', &tokens);
411 for (vector<pair<string, string> >::iterator i = tokens.begin();
412 i != tokens.end(); ++i) {
413 if (i->first == "Error") {
414 results->error_msg = i->second;
415 } else if (i->first == "Url") {
416 results->auth_error_url = i->second;
417 } else if (i->first == "CaptchaToken") {
418 results->captcha_token = i->second;
419 } else if (i->first == "CaptchaUrl") {
420 results->captcha_url = i->second;
421 }
422 }
423
424 // Convert string error messages to enum values. Each case has two different
425 // strings; the first one is the most current and the second one is
426 // deprecated, but available.
427 const string& error_msg = results->error_msg;
428 if (error_msg == "BadAuthentication" || error_msg == "badauth") {
429 results->auth_error = BadAuthentication;
430 } else if (error_msg == "NotVerified" || error_msg == "nv") {
431 results->auth_error = NotVerified;
432 } else if (error_msg == "TermsNotAgreed" || error_msg == "tna") {
433 results->auth_error = TermsNotAgreed;
434 } else if (error_msg == "Unknown" || error_msg == "unknown") {
435 results->auth_error = Unknown;
436 } else if (error_msg == "AccountDeleted" || error_msg == "adel") {
437 results->auth_error = AccountDeleted;
438 } else if (error_msg == "AccountDisabled" || error_msg == "adis") {
439 results->auth_error = AccountDisabled;
440 } else if (error_msg == "CaptchaRequired" || error_msg == "cr") {
441 results->auth_error = CaptchaRequired;
442 } else if (error_msg == "ServiceUnavailable" || error_msg == "ire") {
443 results->auth_error = ServiceUnavailable;
444 }
445 }
446
447 // Reset all stored credentials, perhaps in preparation for letting a different
448 // user sign in.
449 void GaiaAuthenticator::ResetCredentials() {
450 PThreadScopedLock<PThreadMutex> enter(&mutex_);
451 AuthResults blank;
452 auth_results_ = blank;
453 }
454
455 void GaiaAuthenticator::SetUsernamePassword(const string& username,
456 const string& password) {
457 PThreadScopedLock<PThreadMutex> enter(&mutex_);
458 auth_results_.password = password;
459 auth_results_.email = username;
460 }
461
462 void GaiaAuthenticator::SetUsername(const string& username) {
463 PThreadScopedLock<PThreadMutex> enter(&mutex_);
464 auth_results_.email = username;
465 }
466
467 void GaiaAuthenticator::SetAuthToken(const string& auth_token,
468 SaveCredentials save) {
469 PThreadScopedLock<PThreadMutex> enter(&mutex_);
470 auth_results_.auth_token = auth_token;
471 auth_results_.credentials_saved = save;
472 }
473
474 bool GaiaAuthenticator::Authenticate(const string& user_name,
475 const string& password,
476 SaveCredentials should_save_credentials,
477 bool synchronous, SignIn try_first) {
478 const string empty;
479 return Authenticate(user_name, password, should_save_credentials, synchronous,
480 empty, empty, try_first);
481 }
482
483 } // namespace browser_sync
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698