Chromium Code Reviews| Index: remoting/signaling/xmpp_login_handler.cc |
| diff --git a/remoting/signaling/xmpp_login_handler.cc b/remoting/signaling/xmpp_login_handler.cc |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..948e2fab46b2d0c625aa9b0cb9b9099982637dbf |
| --- /dev/null |
| +++ b/remoting/signaling/xmpp_login_handler.cc |
| @@ -0,0 +1,221 @@ |
| +// Copyright 2015 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 "remoting/signaling/xmpp_login_handler.h" |
| + |
| +#include <set> |
| + |
| +#include "base/base64.h" |
| +#include "base/bind.h" |
| +#include "base/logging.h" |
| +#include "remoting/signaling/xmpp_stream_parser.h" |
| +#include "third_party/webrtc/libjingle/xmllite/xmlelement.h" |
| + |
| +namespace remoting { |
| + |
| +buzz::StaticQName kXmppIqName = {"jabber:client", "iq"}; |
| + |
| +char kXmppBindNs[] = "urn:ietf:params:xml:ns:xmpp-bind"; |
| +buzz::StaticQName kXmppBindName = {kXmppBindNs, "bind"}; |
| +buzz::StaticQName kXmppJidName = {kXmppBindNs, "jid"}; |
| + |
| +buzz::StaticQName kJabberFeaturesName = {"http://etherx.jabber.org/streams", |
| + "features"}; |
| + |
| +char kXmppTlsNs[] = "urn:ietf:params:xml:ns:xmpp-tls"; |
| +buzz::StaticQName kStartTlsName = {kXmppTlsNs, "starttls"}; |
| +buzz::StaticQName kTlsProceedName = {kXmppTlsNs, "proceed"}; |
| + |
| +char kXmppSaslNs[] = "urn:ietf:params:xml:ns:xmpp-sasl"; |
| +buzz::StaticQName kSaslMechanismsName = {kXmppSaslNs, "mechanisms"}; |
| +buzz::StaticQName kSaslMechanismName = {kXmppSaslNs, "mechanism"}; |
| +buzz::StaticQName kSaslSuccessName = {kXmppSaslNs, "success"}; |
| + |
| +XmppLoginHandler::XmppLoginHandler(const std::string& server, |
| + const std::string& username, |
| + const std::string& auth_token, |
| + const std::string& auth_service, |
| + bool need_handshake_before_tls, |
| + Delegate* delegate) |
| + : server_(server), |
| + username_(username), |
| + auth_token_(auth_token), |
| + auth_service_(auth_service), |
| + auth_mechanism_((auth_service == "oauth2") ? "X-OAUTH2" |
|
rmsousa
2015/02/28 06:33:29
nit: if this is for client login, it can be remove
Sergey Ulanov
2015/03/02 18:11:09
Done.
|
| + : "X-GOOGLE-TOKEN"), |
| + need_handshake_before_tls_(need_handshake_before_tls), |
| + delegate_(delegate), |
| + state_(State::INIT) { |
| +} |
| + |
| +XmppLoginHandler::~XmppLoginHandler() { |
| +} |
| + |
| +void XmppLoginHandler::Start() { |
| + if (need_handshake_before_tls_) { |
| + state_ = State::WAIT_STREAM_HEADER; |
| + StartStream("<starttls xmlns=\"urn:ietf:params:xml:ns:xmpp-tls\"/>"); |
| + } else { |
| + // If <starttls> handshake is not required then start TLS right away. |
| + state_ = State::STARTING_TLS; |
| + delegate_->StartTls(); |
| + } |
| +} |
| + |
| +void XmppLoginHandler::OnDataReceived(const std::string& data) { |
| + DCHECK(state_ != State::INIT && state_ != State::DONE && |
| + state_ != State::ERROR); |
| + stream_parser_->AppendData(data); |
| +} |
| + |
| +void XmppLoginHandler::OnStanza(scoped_ptr<buzz::XmlElement> stanza) { |
| + switch (state_) { |
| + case State::WAIT_STREAM_HEADER: { |
| + if (stanza->Name() == kJabberFeaturesName && |
| + stanza->FirstNamed(kStartTlsName) != nullptr) { |
| + state_ = State::WAIT_STARTTLS_RESPONSE; |
| + } else { |
| + LOG(ERROR) << "Server doesn't support TLS."; |
| + OnError(SignalStrategy::PROTOCOL_ERROR); |
| + } |
| + break; |
| + } |
| + |
| + case State::WAIT_STARTTLS_RESPONSE: { |
| + if (stanza->Name() == kTlsProceedName) { |
| + state_ = State::STARTING_TLS; |
| + delegate_->StartTls(); |
| + } else { |
| + LOG(ERROR) << "Failed to start TLS: " << stanza->Str(); |
| + OnError(SignalStrategy::PROTOCOL_ERROR); |
| + } |
| + break; |
| + } |
| + |
| + case State::WAIT_STREAM_HEADER_AFTER_TLS: { |
| + buzz::XmlElement* mechanisms_element = |
| + stanza->FirstNamed(kSaslMechanismsName); |
| + std::set<std::string> mechanisms; |
| + if (mechanisms_element) { |
| + for (buzz::XmlElement* element = |
| + mechanisms_element->FirstNamed(kSaslMechanismName); |
| + element; element = element->NextNamed(kSaslMechanismName)) { |
| + mechanisms.insert(element->BodyText()); |
| + } |
| + } |
| + |
| + if (mechanisms.find(auth_mechanism_) == mechanisms.end()) { |
| + LOG(ERROR) << auth_mechanism_ |
| + << " auth mechanism is not supported by the server."; |
| + OnError(SignalStrategy::PROTOCOL_ERROR); |
| + return; |
| + } |
| + |
| + state_ = State::WAIT_AUTH_RESULT; |
| + break; |
| + } |
| + |
| + case State::WAIT_AUTH_RESULT: { |
| + if (stanza->Name() == kSaslSuccessName) { |
| + state_ = State::WAIT_STREAM_HEADER_AFTER_AUTH; |
| + StartStream( |
| + "<iq type=\"set\" id=\"0\">" |
| + "<bind xmlns=\"urn:ietf:params:xml:ns:xmpp-bind\">" |
| + "<resource>chromoting</resource>" |
| + "</bind>" |
| + "</iq>" |
| + "<iq type=\"set\" id=\"1\">" |
| + "<session xmlns=\"urn:ietf:params:xml:ns:xmpp-session\"/>" |
| + "</iq>"); |
| + } else { |
| + OnError(SignalStrategy::AUTHENTICATION_FAILED); |
| + } |
| + break; |
| + } |
| + |
| + case State::WAIT_STREAM_HEADER_AFTER_AUTH: |
| + if (stanza->Name() == kJabberFeaturesName && |
| + stanza->FirstNamed(kXmppBindName) != nullptr) { |
| + state_ = State::WAIT_BIND_RESULT; |
| + } else { |
| + LOG(ERROR) << "Server doesn't support bind after authentication."; |
| + OnError(SignalStrategy::PROTOCOL_ERROR); |
| + } |
| + break; |
| + |
| + case State::WAIT_BIND_RESULT: { |
| + buzz::XmlElement* bind = stanza->FirstNamed(kXmppBindName); |
| + buzz::XmlElement* jid = bind ? bind->FirstNamed(kXmppJidName) : nullptr; |
| + if (stanza->Attr(buzz::QName("", "id")) != "0" || |
| + stanza->Attr(buzz::QName("", "type")) != "result" || !jid) { |
| + LOG(ERROR) << "Received unexpected response to bind: " << stanza->Str(); |
| + OnError(SignalStrategy::PROTOCOL_ERROR); |
| + return; |
| + } |
| + jid_ = jid->BodyText(); |
| + state_ = State::WAIT_SESSION_IQ_RESULT; |
| + break; |
| + } |
| + |
| + case State::WAIT_SESSION_IQ_RESULT: |
| + if (stanza->Name() != kXmppIqName || |
| + stanza->Attr(buzz::QName("", "id")) != "1" || |
| + stanza->Attr(buzz::QName("", "type")) != "result") { |
| + LOG(ERROR) << "Failed to start session: " << stanza->Str(); |
| + OnError(SignalStrategy::PROTOCOL_ERROR); |
| + return; |
| + } |
| + state_ = State::DONE; |
| + delegate_->OnHandshakeDone(jid_, stream_parser_.Pass()); |
| + break; |
| + |
| + default: |
| + NOTREACHED(); |
| + break; |
| + } |
| +} |
| + |
| +void XmppLoginHandler::OnTlsStarted() { |
| + DCHECK(state_ == State::STARTING_TLS); |
| + state_ = State::WAIT_STREAM_HEADER_AFTER_TLS; |
| + |
| + std::string cookie; |
| + base::Base64Encode( |
| + std::string("\0", 1) + username_ + std::string("\0", 1) + auth_token_, |
| + &cookie); |
| + StartStream( |
| + "<auth xmlns=\"" + std::string(kXmppSaslNs) + "\" " |
| + "mechanism=\"" + auth_mechanism_+ "\" " |
| + "auth:service=\"" + auth_service_ + "\" " |
| + "auth:allow-generated-jid=\"true\" " |
| + "auth:client-uses-full-bind-result=\"true\" " |
| + "auth:allow-non-google-login=\"true\" " |
| + "xmlns:auth=\"http://www.google.com/talk/protocol/auth\">" + |
| + cookie + |
| + "</auth>"); |
| +}; |
| + |
| +void XmppLoginHandler::OnParserError() { |
| + OnError(SignalStrategy::PROTOCOL_ERROR); |
| +} |
| + |
| +void XmppLoginHandler::StartStream(const std::string& first_message) { |
| + delegate_->SendMessage("<stream:stream to=\"" + server_ + |
| + "\" version=\"1.0\" xmlns=\"jabber:client\" " |
| + "xmlns:stream=\"http://etherx.jabber.org/streams\">" + |
| + first_message); |
| + stream_parser_.reset(new XmppStreamParser()); |
| + stream_parser_->SetCallbacks( |
| + base::Bind(&XmppLoginHandler::OnStanza, base::Unretained(this)), |
| + base::Bind(&XmppLoginHandler::OnParserError, base::Unretained(this))); |
| +} |
| + |
| +void XmppLoginHandler::OnError(SignalStrategy::Error error) { |
| + if (state_ != State::ERROR) { |
| + state_ = State::ERROR; |
| + delegate_->OnLoginHandlerError(error); |
| + } |
| +} |
| + |
| +} // namespace remoting |