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.cc442
1 files changed, 442 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..3bc6550
--- /dev/null
+++ b/chrome/browser/sync/notifier/gaia_auth/gaiaauth.cc
@@ -0,0 +1,442 @@
+// 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 "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/logging.h"
+#include "talk/base/physicalsocketserver.h"
+#include "talk/base/signalthread.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 buzz::g_gaia_server;
+
+///////////////////////////////////////////////////////////////////////////////
+// GaiaAuth::WorkerThread
+///////////////////////////////////////////////////////////////////////////////
+
+// GaiaAuth is NOT invoked during SASL authenticatioin, 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::WorkerThread : public talk_base::SignalThread {
+ public:
+ WorkerThread(const std::string& username,
+ const talk_base::CryptString& pass,
+ const std::string& token,
+ const std::string& service,
+ const std::string& user_agent,
+ const std::string& signature,
+ bool obtain_auth,
+ const std::string& token_service) :
+ username_(username),
+ pass_(pass),
+ service_(service),
+ firewall_(0),
+ done_(false),
+ success_(false),
+ error_(true),
+ error_code_(0),
+ proxy_auth_required_(false),
+ certificate_expired_(false),
+ auth_token_(token),
+ fresh_auth_token_(false),
+ obtain_auth_(obtain_auth),
+ agent_(user_agent),
+ signature_(signature),
+ token_service_(token_service) {}
+
+ void set_proxy(const talk_base::ProxyInfo& proxy) { proxy_ = proxy; }
+ void set_firewall(talk_base::FirewallManager * firewall) {
+ firewall_ = firewall;
+ }
+ void set_captcha_answer(const CaptchaAnswer& captcha_answer) {
+ captcha_answer_ = captcha_answer;
+ }
+
+ virtual void DoWork() {
+ 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 {
+ 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");
+
+ talk_base::ReuseSocketPool pool(&factory);
+ 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);
+ 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:
+ done_ = true;
+ }
+
+ bool IsDone() const { return done_; }
+ 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_;
+};
+
+///////////////////////////////////////////////////////////////////////////////
+// GaiaAuth
+///////////////////////////////////////////////////////////////////////////////
+
+GaiaAuth::GaiaAuth(const std::string &user_agent, const std::string &sig)
+ : agent_(user_agent), signature_(sig),
+ firewall_(0), worker_(NULL), done_(false) {
+}
+
+GaiaAuth::~GaiaAuth() {
+ if (worker_) {
+ worker_->Release();
+ worker_ = NULL;
+ }
+}
+
+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) {
+ worker_ = new WorkerThread(jid.Str(), pass, token,
+ service, agent_, signature_,
+ obtain_auth, token_service_);
+ worker_->set_proxy(proxy_);
+ worker_->set_firewall(firewall_);
+ worker_->set_captcha_answer(captcha_answer_);
+ worker_->SignalWorkDone.connect(this, &GaiaAuth::OnAuthDone);
+ worker_->Start();
+}
+
+void GaiaAuth::OnAuthDone(talk_base::SignalThread* worker) {
+ if (!worker_->IsDone())
+ return;
+ done_ = true;
+
+ if (worker_->fresh_auth_token()) {
+ SignalFreshAuthCookie(worker_->GetToken());
+ }
+ if (worker_->ProxyAuthRequired()) {
+ SignalAuthenticationError();
+ }
+ if (worker_->CertificateExpired()) {
+ SignalCertificateExpired();
+ }
+ SignalAuthDone();
+}
+
+std::string GaiaAuth::ChooseBestSaslMechanism(
+ const std::vector<std::string> & mechanisms, bool encrypted) {
+ if (!done_)
+ return "";
+
+ std::vector<std::string>::const_iterator it;
+
+ // a token is the weakest auth - 15s, service-limited, so prefer it.
+ it = std::find(mechanisms.begin(), mechanisms.end(), "X-GOOGLE-TOKEN");
+ if (it != mechanisms.end())
+ return "X-GOOGLE-TOKEN";
+
+ // a cookie is the next weakest - 14 days
+ it = std::find(mechanisms.begin(), mechanisms.end(), "X-GOOGLE-COOKIE");
+ if (it != mechanisms.end())
+ return "X-GOOGLE-COOKIE";
+
+ // never pass @google.com passwords without encryption!!
+ if (!encrypted &&
+ buzz::Jid(worker_->GetUsername()).domain() == "google.com") {
+ return "";
+ }
+
+ // as a last resort, use plain authentication
+ if (buzz::Jid(worker_->GetUsername()).domain() != "google.com") {
+ it = std::find(mechanisms.begin(), mechanisms.end(), "PLAIN");
+ if (it != mechanisms.end())
+ return "PLAIN";
+ }
+
+ // No good mechanism found
+ return "";
+}
+
+buzz::SaslMechanism* GaiaAuth::CreateSaslMechanism(
+ const std::string& mechanism) {
+
+ if (!done_) {
+ return NULL;
+ }
+
+ if (mechanism == "X-GOOGLE-TOKEN") {
+ return new buzz::SaslCookieMechanism(
+ mechanism,
+ worker_->GetUsername(),
+ worker_->GetToken(),
+ worker_->GetTokenService());
+ }
+
+ if (mechanism == "X-GOOGLE-COOKIE") {
+ return new buzz::SaslCookieMechanism(
+ "X-GOOGLE-COOKIE",
+ worker_->GetUsername(),
+ worker_->GetSID(),
+ worker_->GetTokenService());
+ }
+
+ if (mechanism == "PLAIN") {
+ return new buzz::SaslPlainMechanism(buzz::Jid(worker_->GetUsername()),
+ worker_->GetPassword());
+ }
+
+ // oh well - none of the above
+ return NULL;
+}
+
+std::string GaiaAuth::CreateAuthenticatedUrl(
+ const std::string & continue_url, const std::string & service) {
+ if (!done_ || worker_->GetToken().empty())
+ return "";
+
+ std::string url;
+ // Note that http_prefix always ends with a "/"
+ url += g_gaia_server.http_prefix()
+ + "accounts/TokenAuth?auth="
+ + worker_->GetToken(); // Do not URL encode - GAIA doesn't like that
+ url += "&service=" + service;
+ url += "&continue=" + UrlEncodeString(continue_url);
+ url += "&source=" + signature_;
+ return url;
+}
+
+std::string GaiaAuth::GetAuthCookie() {
+ assert(IsAuthDone() && IsAuthorized());
+ if (!done_ || !worker_->Succeeded()) {
+ return "";
+ }
+ return worker_->GetToken();
+}
+
+std::string GaiaAuth::GetAuth() {
+ assert(IsAuthDone() && IsAuthorized());
+ if (!done_ || !worker_->Succeeded()) {
+ return "";
+ }
+ return worker_->GetAuth();
+}
+
+std::string GaiaAuth::GetSID() {
+ assert(IsAuthDone() && IsAuthorized());
+ if (!done_ || !worker_->Succeeded()) {
+ return "";
+ }
+ return worker_->GetSID();
+}
+
+bool GaiaAuth::IsAuthDone() {
+ return done_;
+}
+
+bool GaiaAuth::IsAuthorized() {
+ return done_ && worker_ != NULL && worker_->Succeeded();
+}
+
+bool GaiaAuth::HadError() {
+ return done_ && worker_ != NULL && worker_->HadError();
+}
+
+int GaiaAuth::GetError() {
+ if (done_ && worker_ != NULL) {
+ return worker_->GetError();
+ }
+ return 0;
+}
+
+buzz::CaptchaChallenge GaiaAuth::GetCaptchaChallenge() {
+ if (!done_ || worker_->Succeeded()) {
+ return buzz::CaptchaChallenge();
+ }
+ return worker_->GetCaptchaChallenge();
+}
+} // namespace buzz