| Index: chrome/browser/sync/notifier/gaia_auth/gaiaauth.cc
|
| ===================================================================
|
| --- chrome/browser/sync/notifier/gaia_auth/gaiaauth.cc (revision 0)
|
| +++ chrome/browser/sync/notifier/gaia_auth/gaiaauth.cc (revision 0)
|
| @@ -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
|
|
|
| Property changes on: chrome\browser\sync\notifier\gaia_auth\gaiaauth.cc
|
| ___________________________________________________________________
|
| Added: svn:eol-style
|
| + LF
|
|
|
|
|