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