summaryrefslogtreecommitdiffstats
path: root/chrome/browser/sync/notifier/gaia_auth/gaiaauth.cc
diff options
context:
space:
mode:
Diffstat (limited to 'chrome/browser/sync/notifier/gaia_auth/gaiaauth.cc')
-rw-r--r--chrome/browser/sync/notifier/gaia_auth/gaiaauth.cc435
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