Roll ANGLE e754fb8..6ffeb74
[chromium-blink-merge.git] / remoting / signaling / xmpp_login_handler.cc
blob448ecbef6fae382fbed139f589fe54bfee6469aa
1 // Copyright 2015 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "remoting/signaling/xmpp_login_handler.h"
7 #include "base/base64.h"
8 #include "base/bind.h"
9 #include "base/logging.h"
10 #include "remoting/signaling/xmpp_stream_parser.h"
11 #include "third_party/webrtc/libjingle/xmllite/xmlelement.h"
13 // Undefine SendMessage and ERROR defined in Windows headers.
14 #ifdef SendMessage
15 #undef SendMessage
16 #endif
18 #ifdef ERROR
19 #undef ERROR
20 #endif
22 namespace remoting {
24 const char kOAuthMechanism[] = "X-OAUTH2";
26 buzz::StaticQName kXmppIqName = {"jabber:client", "iq"};
28 char kXmppBindNs[] = "urn:ietf:params:xml:ns:xmpp-bind";
29 buzz::StaticQName kXmppBindName = {kXmppBindNs, "bind"};
30 buzz::StaticQName kXmppJidName = {kXmppBindNs, "jid"};
32 buzz::StaticQName kJabberFeaturesName = {"http://etherx.jabber.org/streams",
33 "features"};
35 char kXmppTlsNs[] = "urn:ietf:params:xml:ns:xmpp-tls";
36 buzz::StaticQName kStartTlsName = {kXmppTlsNs, "starttls"};
37 buzz::StaticQName kTlsProceedName = {kXmppTlsNs, "proceed"};
39 char kXmppSaslNs[] = "urn:ietf:params:xml:ns:xmpp-sasl";
40 buzz::StaticQName kSaslMechanismsName = {kXmppSaslNs, "mechanisms"};
41 buzz::StaticQName kSaslMechanismName = {kXmppSaslNs, "mechanism"};
42 buzz::StaticQName kSaslSuccessName = {kXmppSaslNs, "success"};
44 XmppLoginHandler::XmppLoginHandler(const std::string& server,
45 const std::string& username,
46 const std::string& auth_token,
47 TlsMode tls_mode,
48 Delegate* delegate)
49 : server_(server),
50 username_(username),
51 auth_token_(auth_token),
52 tls_mode_(tls_mode),
53 delegate_(delegate),
54 state_(State::INIT) {
57 XmppLoginHandler::~XmppLoginHandler() {
60 void XmppLoginHandler::Start() {
61 switch (tls_mode_) {
62 case TlsMode::NO_TLS:
63 state_ = State::WAIT_STREAM_HEADER_AFTER_TLS;
64 StartAuthHandshake();
65 break;
66 case TlsMode::WITH_HANDSHAKE:
67 state_ = State::WAIT_STREAM_HEADER;
68 StartStream("<starttls xmlns=\"urn:ietf:params:xml:ns:xmpp-tls\"/>");
69 break;
70 case TlsMode::WITHOUT_HANDSHAKE:
71 // If <starttls> handshake is not required then start TLS right away.
72 state_ = State::STARTING_TLS;
73 delegate_->StartTls();
74 break;
78 void XmppLoginHandler::OnDataReceived(const std::string& data) {
79 DCHECK(state_ != State::INIT && state_ != State::DONE &&
80 state_ != State::ERROR);
81 stream_parser_->AppendData(data);
84 void XmppLoginHandler::OnStanza(scoped_ptr<buzz::XmlElement> stanza) {
85 switch (state_) {
86 case State::WAIT_STREAM_HEADER: {
87 if (stanza->Name() == kJabberFeaturesName &&
88 stanza->FirstNamed(kStartTlsName) != nullptr) {
89 state_ = State::WAIT_STARTTLS_RESPONSE;
90 } else {
91 LOG(ERROR) << "Server doesn't support TLS.";
92 OnError(SignalStrategy::PROTOCOL_ERROR);
94 break;
97 case State::WAIT_STARTTLS_RESPONSE: {
98 if (stanza->Name() == kTlsProceedName) {
99 state_ = State::STARTING_TLS;
100 delegate_->StartTls();
101 } else {
102 LOG(ERROR) << "Failed to start TLS: " << stanza->Str();
103 OnError(SignalStrategy::PROTOCOL_ERROR);
105 break;
108 case State::WAIT_STREAM_HEADER_AFTER_TLS: {
109 buzz::XmlElement* mechanisms_element =
110 stanza->FirstNamed(kSaslMechanismsName);
111 bool oauth_supported = false;
112 if (mechanisms_element) {
113 for (buzz::XmlElement* element =
114 mechanisms_element->FirstNamed(kSaslMechanismName);
115 element; element = element->NextNamed(kSaslMechanismName)) {
116 if (element->BodyText() == kOAuthMechanism) {
117 oauth_supported = true;
118 break;
123 if (!oauth_supported) {
124 LOG(ERROR) << kOAuthMechanism
125 << " auth mechanism is not supported by the server.";
126 OnError(SignalStrategy::PROTOCOL_ERROR);
127 return;
130 state_ = State::WAIT_AUTH_RESULT;
131 break;
134 case State::WAIT_AUTH_RESULT: {
135 if (stanza->Name() == kSaslSuccessName) {
136 state_ = State::WAIT_STREAM_HEADER_AFTER_AUTH;
137 StartStream(
138 "<iq type=\"set\" id=\"0\">"
139 "<bind xmlns=\"urn:ietf:params:xml:ns:xmpp-bind\">"
140 "<resource>chromoting</resource>"
141 "</bind>"
142 "</iq>"
143 "<iq type=\"set\" id=\"1\">"
144 "<session xmlns=\"urn:ietf:params:xml:ns:xmpp-session\"/>"
145 "</iq>");
146 } else {
147 OnError(SignalStrategy::AUTHENTICATION_FAILED);
149 break;
152 case State::WAIT_STREAM_HEADER_AFTER_AUTH:
153 if (stanza->Name() == kJabberFeaturesName &&
154 stanza->FirstNamed(kXmppBindName) != nullptr) {
155 state_ = State::WAIT_BIND_RESULT;
156 } else {
157 LOG(ERROR) << "Server doesn't support bind after authentication.";
158 OnError(SignalStrategy::PROTOCOL_ERROR);
160 break;
162 case State::WAIT_BIND_RESULT: {
163 buzz::XmlElement* bind = stanza->FirstNamed(kXmppBindName);
164 buzz::XmlElement* jid = bind ? bind->FirstNamed(kXmppJidName) : nullptr;
165 if (stanza->Attr(buzz::QName("", "id")) != "0" ||
166 stanza->Attr(buzz::QName("", "type")) != "result" || !jid) {
167 LOG(ERROR) << "Received unexpected response to bind: " << stanza->Str();
168 OnError(SignalStrategy::PROTOCOL_ERROR);
169 return;
171 jid_ = jid->BodyText();
172 state_ = State::WAIT_SESSION_IQ_RESULT;
173 break;
176 case State::WAIT_SESSION_IQ_RESULT:
177 if (stanza->Name() != kXmppIqName ||
178 stanza->Attr(buzz::QName("", "id")) != "1" ||
179 stanza->Attr(buzz::QName("", "type")) != "result") {
180 LOG(ERROR) << "Failed to start session: " << stanza->Str();
181 OnError(SignalStrategy::PROTOCOL_ERROR);
182 return;
184 state_ = State::DONE;
185 delegate_->OnHandshakeDone(jid_, stream_parser_.Pass());
186 break;
188 default:
189 NOTREACHED();
190 break;
194 void XmppLoginHandler::OnTlsStarted() {
195 DCHECK(state_ == State::STARTING_TLS);
196 state_ = State::WAIT_STREAM_HEADER_AFTER_TLS;
197 StartAuthHandshake();
200 void XmppLoginHandler::StartAuthHandshake() {
201 DCHECK(state_ == State::WAIT_STREAM_HEADER_AFTER_TLS);
203 std::string cookie;
204 base::Base64Encode(
205 std::string("\0", 1) + username_ + std::string("\0", 1) + auth_token_,
206 &cookie);
207 StartStream(
208 "<auth xmlns=\"" + std::string(kXmppSaslNs) + "\" "
209 "mechanism=\"" + "X-OAUTH2" + "\" "
210 "auth:service=\"oauth2\" "
211 "auth:allow-generated-jid=\"true\" "
212 "auth:client-uses-full-bind-result=\"true\" "
213 "auth:allow-non-google-login=\"true\" "
214 "xmlns:auth=\"http://www.google.com/talk/protocol/auth\">" +
215 cookie +
216 "</auth>");
219 void XmppLoginHandler::OnParserError() {
220 OnError(SignalStrategy::PROTOCOL_ERROR);
223 void XmppLoginHandler::StartStream(const std::string& first_message) {
224 stream_parser_.reset(new XmppStreamParser());
225 stream_parser_->SetCallbacks(
226 base::Bind(&XmppLoginHandler::OnStanza, base::Unretained(this)),
227 base::Bind(&XmppLoginHandler::OnParserError, base::Unretained(this)));
228 delegate_->SendMessage("<stream:stream to=\"" + server_ +
229 "\" version=\"1.0\" xmlns=\"jabber:client\" "
230 "xmlns:stream=\"http://etherx.jabber.org/streams\">" +
231 first_message);
234 void XmppLoginHandler::OnError(SignalStrategy::Error error) {
235 if (state_ != State::ERROR) {
236 state_ = State::ERROR;
237 delegate_->OnLoginHandlerError(error);
241 } // namespace remoting