diff options
Diffstat (limited to 'chrome/browser/sync/notifier/gaia_auth/gaiaauth.cc')
-rw-r--r-- | chrome/browser/sync/notifier/gaia_auth/gaiaauth.cc | 435 |
1 files changed, 435 insertions, 0 deletions
diff --git a/chrome/browser/sync/notifier/gaia_auth/gaiaauth.cc b/chrome/browser/sync/notifier/gaia_auth/gaiaauth.cc new file mode 100644 index 0000000..1558b06 --- /dev/null +++ b/chrome/browser/sync/notifier/gaia_auth/gaiaauth.cc @@ -0,0 +1,435 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include <string> + +#include "base/logging.h" +#include "base/message_loop.h" +#include "base/task.h" +#include "base/thread.h" +#include "chrome/browser/sync/notifier/base/ssl_adapter.h" +#include "chrome/browser/sync/notifier/gaia_auth/gaiaauth.h" +#include "talk/base/asynchttprequest.h" +#include "talk/base/firewallsocketserver.h" +#include "talk/base/httpclient.h" +#include "talk/base/physicalsocketserver.h" +#include "talk/base/socketadapters.h" +#include "talk/base/socketpool.h" +#include "talk/base/stringutils.h" +#include "talk/base/urlencode.h" +#include "talk/xmpp/saslcookiemechanism.h" +#include "talk/xmpp/saslplainmechanism.h" + +namespace buzz { + +static const int kGaiaAuthTimeoutMs = 30 * 1000; // 30 sec + +// Warning, this is externed. +GaiaServer g_gaia_server; + +/////////////////////////////////////////////////////////////////////////////// +// GaiaAuth::WorkerTask +/////////////////////////////////////////////////////////////////////////////// + +// GaiaAuth is NOT invoked during SASL authentication, but it is invoked even +// before XMPP login begins. As a PreXmppAuth object, it is driven by +// XmppClient before the XMPP socket is opened. The job of GaiaAuth is to goes +// out using HTTPS POST to grab cookies from GAIA. +// +// It is used by XmppClient. It grabs a SaslAuthenticator which knows how to +// play the cookie login. + +class GaiaAuth::WorkerTask { + public: + WorkerTask(const std::string& username, + const talk_base::CryptString& pass, + const std::string& service, + const talk_base::ProxyInfo& proxy, + talk_base::FirewallManager* firewall, + const std::string& token, + const CaptchaAnswer& captcha_answer, + bool obtain_auth, + const std::string& user_agent, + const std::string& signature, + const std::string& token_service) : + username_(username), + pass_(pass), + service_(service), + proxy_(proxy), + firewall_(firewall), + success_(false), + error_(true), + error_code_(0), + proxy_auth_required_(false), + certificate_expired_(false), + auth_token_(token), + captcha_answer_(captcha_answer), + fresh_auth_token_(false), + obtain_auth_(obtain_auth), + agent_(user_agent), + signature_(signature), + token_service_(token_service) {} + + ~WorkerTask() {} + + void DoWork(MessageLoop* parent_message_loop, Task* on_work_done_task) { + DCHECK(parent_message_loop); + DCHECK(on_work_done_task); + LOG(INFO) << "GaiaAuth Begin"; + // Maybe we already have an auth token, then there is nothing to do. + if (!auth_token_.empty()) { + LOG(INFO) << "Reusing auth token:" << auth_token_; + success_ = true; + error_ = false; + } else { + // SocketFactory::CreateSSLAdapter() is called on the following + // object somewhere in the depths of libjingle so we wrap it + // with SSLAdapterSocketFactory to make sure it returns the + // right SSLAdapter (see http://crbug.com/30721 ). + notifier::SSLAdapterSocketFactory<talk_base::PhysicalSocketServer> + physical; + talk_base::SocketServer* ss = &physical; + if (firewall_) { + ss = new talk_base::FirewallSocketServer(ss, firewall_); + } + + talk_base::SslSocketFactory factory(ss, agent_); + factory.SetProxy(proxy_); + if (g_gaia_server.use_ssl()) { + factory.SetIgnoreBadCert(true); + factory.UseSSL(g_gaia_server.hostname().c_str()); + } + factory.SetLogging(talk_base::LS_VERBOSE, "GaiaAuth"); + +#if defined(OS_WIN) + talk_base::ReuseSocketPool pool(&factory); +#else + // On non-Windows platforms our SSL socket wrapper + // implementation does not support restartable SSL sockets, so + // we turn it off and use a NewSocketPool instead. This means + // that we create separate connections for each HTTP request but + // this is okay since we do only two (in GaiaRequestSid() and + // GaiaRequestAuthToken()). + factory.SetUseRestartableSSLSockets(false); + talk_base::NewSocketPool pool(&factory); +#endif + talk_base::HttpClient http(agent_, &pool); + + talk_base::HttpMonitor monitor(ss); + monitor.Connect(&http); + + // If we do not already have a SID, let's get one using our password. + if (sid_.empty() || (auth_.empty() && obtain_auth_)) { + GaiaRequestSid(&http, username_, pass_, signature_, + obtain_auth_ ? service_ : "", captcha_answer_, + g_gaia_server); + // TODO(akalin): handle timeouts better; this can cause jank, + // e.g. when we're waiting on this and the user stops syncing, + // which causes the GaiaAuth object to be destroyed, which + // waits on this. (bug: http://crbug.com/31981 ) + ss->Wait(kGaiaAuthTimeoutMs, true); + + error_code_ = monitor.error(); // Save off the error code. + + if (!monitor.done()) { + LOG(INFO) << "GaiaAuth request timed out"; + goto Cleanup; + } else if (monitor.error()) { + LOG(INFO) << "GaiaAuth request error: " << monitor.error(); + if (monitor.error() == talk_base::HE_AUTH) { + success_ = false; + proxy_auth_required_ = true; + } else if (monitor.error() == talk_base::HE_CERTIFICATE_EXPIRED) { + success_ = false; + certificate_expired_ = true; + } + goto Cleanup; + } else { + std::string captcha_token, captcha_url; + switch (GaiaParseSidResponse(http, g_gaia_server, + &captcha_token, &captcha_url, + &sid_, &lsid_, &auth_)) { + case GR_ERROR: + goto Cleanup; + + case GR_UNAUTHORIZED: + if (!captcha_url.empty()) { + captcha_challenge_ = buzz::CaptchaChallenge(captcha_token, + captcha_url); + } + // We had no "error" - we were just unauthorized. + error_ = false; + error_code_ = 0; + goto Cleanup; + + case GR_SUCCESS: + break; + } + } + } + + // If all we need is a SID, then we are done now. + if (service_.empty() || obtain_auth_) { + success_ = true; + error_ = false; + error_code_ = 0; + goto Cleanup; + } + + monitor.reset(); + GaiaRequestAuthToken(&http, sid_, lsid_, service_, g_gaia_server); + ss->Wait(kGaiaAuthTimeoutMs, true); + + error_code_ = monitor.error(); // Save off the error code. + + if (!monitor.done()) { + LOG(INFO) << "GaiaAuth request timed out"; + } else if (monitor.error()) { + LOG(INFO) << "GaiaAuth request error: " << monitor.error(); + if (monitor.error() == talk_base::HE_AUTH) { + success_ = false; + proxy_auth_required_ = true; + } else if (monitor.error() == talk_base::HE_CERTIFICATE_EXPIRED) { + success_ = false; + certificate_expired_ = true; + } + } else { + if (GR_SUCCESS == GaiaParseAuthTokenResponse(http, &auth_token_)) { + fresh_auth_token_ = true; + success_ = true; + error_ = false; + error_code_ = 0; + } + } + } + + // Done authenticating. + + Cleanup: + LOG(INFO) << "GaiaAuth done"; + parent_message_loop->PostTask(FROM_HERE, on_work_done_task); + } + + bool Succeeded() const { return success_; } + bool HadError() const { return error_; } + int GetError() const { return error_code_; } + bool ProxyAuthRequired() const { return proxy_auth_required_; } + bool CertificateExpired() const { return certificate_expired_; } + const buzz::CaptchaChallenge& GetCaptchaChallenge() { + return captcha_challenge_; + } + bool fresh_auth_token() const { return fresh_auth_token_; } + + talk_base::CryptString GetPassword() const { return pass_; } + std::string GetSID() const { return sid_; } + std::string GetAuth() const { return auth_; } + std::string GetToken() const { return auth_token_; } + std::string GetUsername() const { return username_; } + std::string GetTokenService() const { return token_service_; } + + private: + std::string username_; + talk_base::CryptString pass_; + std::string service_; + talk_base::ProxyInfo proxy_; + talk_base::FirewallManager * firewall_; + bool done_; + bool success_; + bool error_; + int error_code_; + bool proxy_auth_required_; + bool certificate_expired_; + std::string sid_; + std::string lsid_; + std::string auth_; + std::string auth_token_; + buzz::CaptchaChallenge captcha_challenge_; + CaptchaAnswer captcha_answer_; + bool fresh_auth_token_; + bool obtain_auth_; + std::string agent_; + std::string signature_; + std::string token_service_; + + DISALLOW_COPY_AND_ASSIGN(WorkerTask); +}; + +} // namespace buzz + +// We outlive any runnable methods we create, so stub out +// RunnableMethodTraits for GaiaAuth and WorkerTask. + +template <> +struct RunnableMethodTraits<buzz::GaiaAuth> { + void RetainCallee(buzz::GaiaAuth* gaia_auth) {} + void ReleaseCallee(buzz::GaiaAuth* gaia_auth) {} +}; + +template <> +struct RunnableMethodTraits<buzz::GaiaAuth::WorkerTask> { + void RetainCallee(buzz::GaiaAuth::WorkerTask* gaia_auth) {} + void ReleaseCallee(buzz::GaiaAuth::WorkerTask* gaia_auth) {} +}; + +namespace buzz { + +/////////////////////////////////////////////////////////////////////////////// +// GaiaAuth +/////////////////////////////////////////////////////////////////////////////// + +GaiaAuth::GaiaAuth(const std::string &user_agent, const std::string &sig) + : agent_(user_agent), signature_(sig), firewall_(0), + worker_task_(NULL), on_work_done_task_(NULL), + worker_thread_("GaiaAuth worker thread"), done_(false), + current_message_loop_(MessageLoop::current()) { + DCHECK(current_message_loop_); +} + +GaiaAuth::~GaiaAuth() { + DCHECK_EQ(MessageLoop::current(), current_message_loop_); + // Wait until worker thread stops running. + worker_thread_.Stop(); + // Then, if the worker thread posted an OnAuthDone() task that is + // still pending, cancel it. + if (on_work_done_task_) { + on_work_done_task_->Cancel(); + } +} + +void GaiaAuth::StartPreXmppAuth(const buzz::Jid& jid, + const talk_base::SocketAddress& server, + const talk_base::CryptString& pass, + const std::string & auth_cookie) { + InternalStartGaiaAuth(jid, server, pass, auth_cookie, "mail", false); +} + +void GaiaAuth::StartTokenAuth(const buzz::Jid& jid, + const talk_base::CryptString& pass, + const std::string& service) { + InternalStartGaiaAuth(jid, talk_base::SocketAddress(), pass, "", service, + false); +} + +void GaiaAuth::StartAuth(const buzz::Jid& jid, + const talk_base::CryptString& pass, + const std::string & service) { + InternalStartGaiaAuth(jid, talk_base::SocketAddress(), pass, "", service, + true); +} + +void GaiaAuth::StartAuthFromSid(const buzz::Jid& jid, + const std::string& sid, + const std::string& service) { + InternalStartGaiaAuth(jid, talk_base::SocketAddress(), + talk_base::CryptString(), sid, service, false); +} + +void GaiaAuth::InternalStartGaiaAuth(const buzz::Jid& jid, + const talk_base::SocketAddress& server, + const talk_base::CryptString& pass, + const std::string& token, + const std::string& service, + bool obtain_auth) { + DCHECK_EQ(MessageLoop::current(), current_message_loop_); + worker_task_.reset( + new WorkerTask(jid.Str(), pass, service, proxy_, firewall_, + token, captcha_answer_, obtain_auth, agent_, + signature_, token_service_)); + on_work_done_task_ = NewRunnableMethod(this, &GaiaAuth::OnAuthDone); + LOG(INFO) << "GaiaAuth begin (async)"; + worker_thread_.Start(); + worker_thread_.message_loop()->PostTask( + FROM_HERE, NewRunnableMethod(worker_task_.get(), + &WorkerTask::DoWork, + current_message_loop_, + on_work_done_task_)); +} + +void GaiaAuth::OnAuthDone() { + DCHECK_EQ(MessageLoop::current(), current_message_loop_); + LOG(INFO) << "GaiaAuth done (async)"; + worker_thread_.Stop(); + on_work_done_task_ = NULL; + done_ = true; + + if (worker_task_->fresh_auth_token()) { + SignalFreshAuthCookie(worker_task_->GetToken()); + } + if (worker_task_->ProxyAuthRequired()) { + SignalAuthenticationError(); + } + if (worker_task_->CertificateExpired()) { + SignalCertificateExpired(); + } + SignalAuthDone(); +} + +std::string GaiaAuth::CreateAuthenticatedUrl( + const std::string & continue_url, const std::string & service) { + if (!done_ || worker_task_->GetToken().empty()) + return ""; + + std::string url; + // Note that http_prefix always ends with a "/". + url += g_gaia_server.http_prefix() + + "accounts/TokenAuth?auth=" + // Do not URL encode - GAIA doesn't like that. + + worker_task_->GetToken(); + url += "&service=" + service; + url += "&continue=" + UrlEncodeString(continue_url); + url += "&source=" + signature_; + return url; +} + +std::string GaiaAuth::GetAuthCookie() { + assert(IsAuthDone() && IsAuthorized()); + if (!done_ || !worker_task_->Succeeded()) { + return ""; + } + return worker_task_->GetToken(); +} + +std::string GaiaAuth::GetAuth() { + assert(IsAuthDone() && IsAuthorized()); + if (!done_ || !worker_task_->Succeeded()) { + return ""; + } + return worker_task_->GetAuth(); +} + +std::string GaiaAuth::GetSID() { + assert(IsAuthDone() && IsAuthorized()); + if (!done_ || !worker_task_->Succeeded()) { + return ""; + } + return worker_task_->GetSID(); +} + +bool GaiaAuth::IsAuthDone() { + return done_; +} + +bool GaiaAuth::IsAuthorized() { + return done_ && worker_task_ != NULL && worker_task_->Succeeded(); +} + +bool GaiaAuth::HadError() { + return done_ && worker_task_ != NULL && worker_task_->HadError(); +} + +int GaiaAuth::GetError() { + if (done_ && worker_task_ != NULL) { + return worker_task_->GetError(); + } + return 0; +} + +buzz::CaptchaChallenge GaiaAuth::GetCaptchaChallenge() { + if (!done_ || worker_task_->Succeeded()) { + return buzz::CaptchaChallenge(); + } + return worker_task_->GetCaptchaChallenge(); +} + +} // namespace buzz |