OLD | NEW |
(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 <string> |
| 6 |
| 7 #include "chrome/browser/sync/notifier/gaia_auth/gaiaauth.h" |
| 8 #include "talk/base/asynchttprequest.h" |
| 9 #include "talk/base/firewallsocketserver.h" |
| 10 #include "talk/base/httpclient.h" |
| 11 #include "talk/base/logging.h" |
| 12 #include "talk/base/physicalsocketserver.h" |
| 13 #include "talk/base/signalthread.h" |
| 14 #include "talk/base/socketadapters.h" |
| 15 #include "talk/base/socketpool.h" |
| 16 #include "talk/base/stringutils.h" |
| 17 #include "talk/base/urlencode.h" |
| 18 #include "talk/xmpp/saslcookiemechanism.h" |
| 19 #include "talk/xmpp/saslplainmechanism.h" |
| 20 |
| 21 namespace buzz { |
| 22 |
| 23 static const int kGaiaAuthTimeoutMs = 30 * 1000; // 30 sec |
| 24 |
| 25 // Warning, this is externed. |
| 26 GaiaServer buzz::g_gaia_server; |
| 27 |
| 28 /////////////////////////////////////////////////////////////////////////////// |
| 29 // GaiaAuth::WorkerThread |
| 30 /////////////////////////////////////////////////////////////////////////////// |
| 31 |
| 32 // GaiaAuth is NOT invoked during SASL authenticatioin, but |
| 33 // it is invoked even before XMPP login begins. As a PreXmppAuth |
| 34 // object, it is driven by XmppClient before the XMPP socket is |
| 35 // opened. The job of GaiaAuth is to goes out using HTTPS |
| 36 // POST to grab cookies from GAIA. |
| 37 |
| 38 // It is used by XmppClient. |
| 39 // It grabs a SaslAuthenticator which knows how to play the cookie login. |
| 40 |
| 41 class GaiaAuth::WorkerThread : public talk_base::SignalThread { |
| 42 public: |
| 43 WorkerThread(const std::string& username, |
| 44 const talk_base::CryptString& pass, |
| 45 const std::string& token, |
| 46 const std::string& service, |
| 47 const std::string& user_agent, |
| 48 const std::string& signature, |
| 49 bool obtain_auth, |
| 50 const std::string& token_service) : |
| 51 username_(username), |
| 52 pass_(pass), |
| 53 service_(service), |
| 54 firewall_(0), |
| 55 done_(false), |
| 56 success_(false), |
| 57 error_(true), |
| 58 error_code_(0), |
| 59 proxy_auth_required_(false), |
| 60 certificate_expired_(false), |
| 61 auth_token_(token), |
| 62 fresh_auth_token_(false), |
| 63 obtain_auth_(obtain_auth), |
| 64 agent_(user_agent), |
| 65 signature_(signature), |
| 66 token_service_(token_service) {} |
| 67 |
| 68 void set_proxy(const talk_base::ProxyInfo& proxy) { proxy_ = proxy; } |
| 69 void set_firewall(talk_base::FirewallManager * firewall) { |
| 70 firewall_ = firewall; |
| 71 } |
| 72 void set_captcha_answer(const CaptchaAnswer& captcha_answer) { |
| 73 captcha_answer_ = captcha_answer; |
| 74 } |
| 75 |
| 76 virtual void DoWork() { |
| 77 LOG(INFO) << "GaiaAuth Begin"; |
| 78 // Maybe we already have an auth token, then there is nothing to do. |
| 79 if (!auth_token_.empty()) { |
| 80 LOG(INFO) << "Reusing auth token:" << auth_token_; |
| 81 success_ = true; |
| 82 error_ = false; |
| 83 } else { |
| 84 talk_base::PhysicalSocketServer physical; |
| 85 talk_base::SocketServer * ss = &physical; |
| 86 if (firewall_) { |
| 87 ss = new talk_base::FirewallSocketServer(ss, firewall_); |
| 88 } |
| 89 |
| 90 talk_base::SslSocketFactory factory(ss, agent_); |
| 91 factory.SetProxy(proxy_); |
| 92 if (g_gaia_server.use_ssl()) { |
| 93 factory.SetIgnoreBadCert(true); |
| 94 factory.UseSSL(g_gaia_server.hostname().c_str()); |
| 95 } |
| 96 factory.SetLogging(talk_base::LS_VERBOSE, "GaiaAuth"); |
| 97 |
| 98 talk_base::ReuseSocketPool pool(&factory); |
| 99 talk_base::HttpClient http(agent_, &pool); |
| 100 |
| 101 talk_base::HttpMonitor monitor(ss); |
| 102 monitor.Connect(&http); |
| 103 |
| 104 // If we do not already have a SID, let's get one using our password. |
| 105 if (sid_.empty() || (auth_.empty() && obtain_auth_)) { |
| 106 GaiaRequestSid(&http, username_, pass_, signature_, |
| 107 obtain_auth_ ? service_ : "", captcha_answer_, |
| 108 g_gaia_server); |
| 109 ss->Wait(kGaiaAuthTimeoutMs, true); |
| 110 |
| 111 error_code_ = monitor.error(); // save off the error code |
| 112 |
| 113 if (!monitor.done()) { |
| 114 LOG(INFO) << "GaiaAuth request timed out"; |
| 115 goto Cleanup; |
| 116 } else if (monitor.error()) { |
| 117 LOG(INFO) << "GaiaAuth request error: " << monitor.error(); |
| 118 if (monitor.error() == talk_base::HE_AUTH) { |
| 119 success_ = false; |
| 120 proxy_auth_required_ = true; |
| 121 } else if (monitor.error() == talk_base::HE_CERTIFICATE_EXPIRED) { |
| 122 success_ = false; |
| 123 certificate_expired_ = true; |
| 124 } |
| 125 goto Cleanup; |
| 126 } else { |
| 127 std::string captcha_token, captcha_url; |
| 128 switch (GaiaParseSidResponse(http, g_gaia_server, |
| 129 &captcha_token, &captcha_url, |
| 130 &sid_, &lsid_, &auth_)) { |
| 131 case GR_ERROR: |
| 132 goto Cleanup; |
| 133 |
| 134 case GR_UNAUTHORIZED: |
| 135 if (!captcha_url.empty()) { |
| 136 captcha_challenge_ = buzz::CaptchaChallenge(captcha_token, |
| 137 captcha_url); |
| 138 } |
| 139 // We had no "error" - we were just unauthorized |
| 140 error_ = false; |
| 141 error_code_ = 0; |
| 142 goto Cleanup; |
| 143 |
| 144 case GR_SUCCESS: |
| 145 break; |
| 146 } |
| 147 } |
| 148 } |
| 149 |
| 150 // If all we need is a SID, then we are done now. |
| 151 if (service_.empty() || obtain_auth_) { |
| 152 success_ = true; |
| 153 error_ = false; |
| 154 error_code_ = 0; |
| 155 goto Cleanup; |
| 156 } |
| 157 |
| 158 monitor.reset(); |
| 159 GaiaRequestAuthToken(&http, sid_, lsid_, service_, g_gaia_server); |
| 160 ss->Wait(kGaiaAuthTimeoutMs, true); |
| 161 |
| 162 error_code_ = monitor.error(); // save off the error code |
| 163 |
| 164 if (!monitor.done()) { |
| 165 LOG(INFO) << "GaiaAuth request timed out"; |
| 166 } else if (monitor.error()) { |
| 167 LOG(INFO) << "GaiaAuth request error: " << monitor.error(); |
| 168 if (monitor.error() == talk_base::HE_AUTH) { |
| 169 success_ = false; |
| 170 proxy_auth_required_ = true; |
| 171 } else if (monitor.error() == talk_base::HE_CERTIFICATE_EXPIRED) { |
| 172 success_ = false; |
| 173 certificate_expired_ = true; |
| 174 } |
| 175 } else { |
| 176 if (GR_SUCCESS == GaiaParseAuthTokenResponse(http, &auth_token_)) { |
| 177 fresh_auth_token_ = true; |
| 178 success_ = true; |
| 179 error_ = false; |
| 180 error_code_ = 0; |
| 181 } |
| 182 } |
| 183 } |
| 184 |
| 185 // done authenticating |
| 186 |
| 187 Cleanup: |
| 188 done_ = true; |
| 189 } |
| 190 |
| 191 bool IsDone() const { return done_; } |
| 192 bool Succeeded() const { return success_; } |
| 193 bool HadError() const { return error_; } |
| 194 int GetError() const { return error_code_; } |
| 195 bool ProxyAuthRequired() const { return proxy_auth_required_; } |
| 196 bool CertificateExpired() const { return certificate_expired_; } |
| 197 const buzz::CaptchaChallenge& GetCaptchaChallenge() { |
| 198 return captcha_challenge_; |
| 199 } |
| 200 bool fresh_auth_token() const { return fresh_auth_token_; } |
| 201 |
| 202 talk_base::CryptString GetPassword() const { return pass_; } |
| 203 std::string GetSID() const { return sid_; } |
| 204 std::string GetAuth() const { return auth_; } |
| 205 std::string GetToken() const { return auth_token_; } |
| 206 std::string GetUsername() const { return username_; } |
| 207 std::string GetTokenService() const { return token_service_; } |
| 208 |
| 209 private: |
| 210 std::string username_; |
| 211 talk_base::CryptString pass_; |
| 212 std::string service_; |
| 213 talk_base::ProxyInfo proxy_; |
| 214 talk_base::FirewallManager * firewall_; |
| 215 bool done_; |
| 216 bool success_; |
| 217 bool error_; |
| 218 int error_code_; |
| 219 bool proxy_auth_required_; |
| 220 bool certificate_expired_; |
| 221 std::string sid_; |
| 222 std::string lsid_; |
| 223 std::string auth_; |
| 224 std::string auth_token_; |
| 225 buzz::CaptchaChallenge captcha_challenge_; |
| 226 CaptchaAnswer captcha_answer_; |
| 227 bool fresh_auth_token_; |
| 228 bool obtain_auth_; |
| 229 std::string agent_; |
| 230 std::string signature_; |
| 231 std::string token_service_; |
| 232 }; |
| 233 |
| 234 /////////////////////////////////////////////////////////////////////////////// |
| 235 // GaiaAuth |
| 236 /////////////////////////////////////////////////////////////////////////////// |
| 237 |
| 238 GaiaAuth::GaiaAuth(const std::string &user_agent, const std::string &sig) |
| 239 : agent_(user_agent), signature_(sig), |
| 240 firewall_(0), worker_(NULL), done_(false) { |
| 241 } |
| 242 |
| 243 GaiaAuth::~GaiaAuth() { |
| 244 if (worker_) { |
| 245 worker_->Release(); |
| 246 worker_ = NULL; |
| 247 } |
| 248 } |
| 249 |
| 250 void GaiaAuth::StartPreXmppAuth(const buzz::Jid& jid, |
| 251 const talk_base::SocketAddress& server, |
| 252 const talk_base::CryptString& pass, |
| 253 const std::string & auth_cookie) { |
| 254 InternalStartGaiaAuth(jid, server, pass, auth_cookie, "mail", false); |
| 255 } |
| 256 |
| 257 void GaiaAuth::StartTokenAuth(const buzz::Jid& jid, |
| 258 const talk_base::CryptString& pass, |
| 259 const std::string& service) { |
| 260 InternalStartGaiaAuth(jid, talk_base::SocketAddress(), |
| 261 pass, "", service, false); |
| 262 } |
| 263 |
| 264 void GaiaAuth::StartAuth(const buzz::Jid& jid, |
| 265 const talk_base::CryptString& pass, |
| 266 const std::string & service) { |
| 267 InternalStartGaiaAuth(jid, talk_base::SocketAddress(), |
| 268 pass, "", service, true); |
| 269 } |
| 270 |
| 271 void GaiaAuth::StartAuthFromSid(const buzz::Jid& jid, |
| 272 const std::string& sid, |
| 273 const std::string& service) { |
| 274 InternalStartGaiaAuth(jid, talk_base::SocketAddress(), |
| 275 talk_base::CryptString(), sid, service, false); |
| 276 } |
| 277 |
| 278 void GaiaAuth::InternalStartGaiaAuth(const buzz::Jid& jid, |
| 279 const talk_base::SocketAddress& server, |
| 280 const talk_base::CryptString& pass, |
| 281 const std::string& token, |
| 282 const std::string& service, |
| 283 bool obtain_auth) { |
| 284 worker_ = new WorkerThread(jid.Str(), pass, token, |
| 285 service, agent_, signature_, |
| 286 obtain_auth, token_service_); |
| 287 worker_->set_proxy(proxy_); |
| 288 worker_->set_firewall(firewall_); |
| 289 worker_->set_captcha_answer(captcha_answer_); |
| 290 worker_->SignalWorkDone.connect(this, &GaiaAuth::OnAuthDone); |
| 291 worker_->Start(); |
| 292 } |
| 293 |
| 294 void GaiaAuth::OnAuthDone(talk_base::SignalThread* worker) { |
| 295 if (!worker_->IsDone()) |
| 296 return; |
| 297 done_ = true; |
| 298 |
| 299 if (worker_->fresh_auth_token()) { |
| 300 SignalFreshAuthCookie(worker_->GetToken()); |
| 301 } |
| 302 if (worker_->ProxyAuthRequired()) { |
| 303 SignalAuthenticationError(); |
| 304 } |
| 305 if (worker_->CertificateExpired()) { |
| 306 SignalCertificateExpired(); |
| 307 } |
| 308 SignalAuthDone(); |
| 309 } |
| 310 |
| 311 std::string GaiaAuth::ChooseBestSaslMechanism( |
| 312 const std::vector<std::string> & mechanisms, bool encrypted) { |
| 313 if (!done_) |
| 314 return ""; |
| 315 |
| 316 std::vector<std::string>::const_iterator it; |
| 317 |
| 318 // a token is the weakest auth - 15s, service-limited, so prefer it. |
| 319 it = std::find(mechanisms.begin(), mechanisms.end(), "X-GOOGLE-TOKEN"); |
| 320 if (it != mechanisms.end()) |
| 321 return "X-GOOGLE-TOKEN"; |
| 322 |
| 323 // a cookie is the next weakest - 14 days |
| 324 it = std::find(mechanisms.begin(), mechanisms.end(), "X-GOOGLE-COOKIE"); |
| 325 if (it != mechanisms.end()) |
| 326 return "X-GOOGLE-COOKIE"; |
| 327 |
| 328 // never pass @google.com passwords without encryption!! |
| 329 if (!encrypted && |
| 330 buzz::Jid(worker_->GetUsername()).domain() == "google.com") { |
| 331 return ""; |
| 332 } |
| 333 |
| 334 // as a last resort, use plain authentication |
| 335 if (buzz::Jid(worker_->GetUsername()).domain() != "google.com") { |
| 336 it = std::find(mechanisms.begin(), mechanisms.end(), "PLAIN"); |
| 337 if (it != mechanisms.end()) |
| 338 return "PLAIN"; |
| 339 } |
| 340 |
| 341 // No good mechanism found |
| 342 return ""; |
| 343 } |
| 344 |
| 345 buzz::SaslMechanism* GaiaAuth::CreateSaslMechanism( |
| 346 const std::string& mechanism) { |
| 347 |
| 348 if (!done_) { |
| 349 return NULL; |
| 350 } |
| 351 |
| 352 if (mechanism == "X-GOOGLE-TOKEN") { |
| 353 return new buzz::SaslCookieMechanism( |
| 354 mechanism, |
| 355 worker_->GetUsername(), |
| 356 worker_->GetToken(), |
| 357 worker_->GetTokenService()); |
| 358 } |
| 359 |
| 360 if (mechanism == "X-GOOGLE-COOKIE") { |
| 361 return new buzz::SaslCookieMechanism( |
| 362 "X-GOOGLE-COOKIE", |
| 363 worker_->GetUsername(), |
| 364 worker_->GetSID(), |
| 365 worker_->GetTokenService()); |
| 366 } |
| 367 |
| 368 if (mechanism == "PLAIN") { |
| 369 return new buzz::SaslPlainMechanism(buzz::Jid(worker_->GetUsername()), |
| 370 worker_->GetPassword()); |
| 371 } |
| 372 |
| 373 // oh well - none of the above |
| 374 return NULL; |
| 375 } |
| 376 |
| 377 std::string GaiaAuth::CreateAuthenticatedUrl( |
| 378 const std::string & continue_url, const std::string & service) { |
| 379 if (!done_ || worker_->GetToken().empty()) |
| 380 return ""; |
| 381 |
| 382 std::string url; |
| 383 // Note that http_prefix always ends with a "/" |
| 384 url += g_gaia_server.http_prefix() |
| 385 + "accounts/TokenAuth?auth=" |
| 386 + worker_->GetToken(); // Do not URL encode - GAIA doesn't like that |
| 387 url += "&service=" + service; |
| 388 url += "&continue=" + UrlEncodeString(continue_url); |
| 389 url += "&source=" + signature_; |
| 390 return url; |
| 391 } |
| 392 |
| 393 std::string GaiaAuth::GetAuthCookie() { |
| 394 assert(IsAuthDone() && IsAuthorized()); |
| 395 if (!done_ || !worker_->Succeeded()) { |
| 396 return ""; |
| 397 } |
| 398 return worker_->GetToken(); |
| 399 } |
| 400 |
| 401 std::string GaiaAuth::GetAuth() { |
| 402 assert(IsAuthDone() && IsAuthorized()); |
| 403 if (!done_ || !worker_->Succeeded()) { |
| 404 return ""; |
| 405 } |
| 406 return worker_->GetAuth(); |
| 407 } |
| 408 |
| 409 std::string GaiaAuth::GetSID() { |
| 410 assert(IsAuthDone() && IsAuthorized()); |
| 411 if (!done_ || !worker_->Succeeded()) { |
| 412 return ""; |
| 413 } |
| 414 return worker_->GetSID(); |
| 415 } |
| 416 |
| 417 bool GaiaAuth::IsAuthDone() { |
| 418 return done_; |
| 419 } |
| 420 |
| 421 bool GaiaAuth::IsAuthorized() { |
| 422 return done_ && worker_ != NULL && worker_->Succeeded(); |
| 423 } |
| 424 |
| 425 bool GaiaAuth::HadError() { |
| 426 return done_ && worker_ != NULL && worker_->HadError(); |
| 427 } |
| 428 |
| 429 int GaiaAuth::GetError() { |
| 430 if (done_ && worker_ != NULL) { |
| 431 return worker_->GetError(); |
| 432 } |
| 433 return 0; |
| 434 } |
| 435 |
| 436 buzz::CaptchaChallenge GaiaAuth::GetCaptchaChallenge() { |
| 437 if (!done_ || worker_->Succeeded()) { |
| 438 return buzz::CaptchaChallenge(); |
| 439 } |
| 440 return worker_->GetCaptchaChallenge(); |
| 441 } |
| 442 } // namespace buzz |
OLD | NEW |