| Index: remoting/webapp/xmpp_login_handler.js
|
| diff --git a/remoting/webapp/xmpp_login_handler.js b/remoting/webapp/xmpp_login_handler.js
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..9c3d5fd6a7331e5ae3f2b104d0021d786d5183b1
|
| --- /dev/null
|
| +++ b/remoting/webapp/xmpp_login_handler.js
|
| @@ -0,0 +1,231 @@
|
| +// Copyright 2014 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.
|
| +
|
| +'use strict';
|
| +
|
| +/** @suppress {duplicate} */
|
| +var remoting = remoting || {};
|
| +
|
| +/**
|
| + * XmppLoginHandler handles authentication handshake for XmppConnection. It
|
| + * receives incoming data using onDataReceived() calls |sendMessageCallback|
|
| + * to send outgoing messages and
|
| + *
|
| + * @param {string} username Username.
|
| + * @param {string} authToken OAuth2 token.
|
| + * @param {function(string):void} sendMessageCallback Callback call to send
|
| + * a message.
|
| + * @param {function():void} startTlsCallback Callback to call to start TLS on
|
| + * the underlying socket.
|
| + * @param {function(string):void} onHandshakeDoneCallback Callback to call
|
| + * after authentication is completed successfully
|
| + * @param {function(Element):void} onStanzaCallback Callback to call for each
|
| + * incoming stanza (when authenticated).
|
| + * @param {function(remoting.Error, string):void} onErrorCallback Callback to
|
| + * call on error. Can be called at any point during lifetime of connection.
|
| + * @constructor
|
| + */
|
| +remoting.XmppLoginHandler = function(username,
|
| + authToken,
|
| + sendMessageCallback,
|
| + startTlsCallback,
|
| + onHandshakeDoneCallback,
|
| + onStanzaCallback,
|
| + onErrorCallback) {
|
| + this.username_ = username;
|
| + this.authToken_ = authToken;
|
| + this.sendMessageCallback_ = sendMessageCallback;
|
| + this.startTlsCallback_ = startTlsCallback;
|
| + this.onHandshakeDoneCallback_ = onHandshakeDoneCallback;
|
| + this.onStanzaCallback_ = onStanzaCallback;
|
| + this.onErrorCallback_ = onErrorCallback;
|
| +
|
| + this.state_ = remoting.XmppLoginHandler.State.INIT;
|
| + this.jid_ = '';
|
| +
|
| + /** @type {remoting.XmppStreamParser} */
|
| + this.streamParser_ = null;
|
| +}
|
| +
|
| +/**
|
| + * @enum {number}
|
| + */
|
| +remoting.XmppLoginHandler.State = {
|
| + INIT: 0,
|
| + START_SENT: 1,
|
| + STARTTLS_SENT: 2,
|
| + STARTING_TLS: 3,
|
| + START_SENT_AFTER_TLS: 4,
|
| + AUTH_SENT: 5,
|
| + AUTH_ACCEPTED: 6,
|
| + BIND_SENT: 7,
|
| + SESSION_IQ_SENT: 8,
|
| + SESSION_ACTIVE: 9,
|
| + ERROR: 10
|
| +};
|
| +
|
| +remoting.XmppLoginHandler.prototype.start = function() {
|
| + this.state_ = remoting.XmppLoginHandler.State.START_SENT;
|
| + this.startStream_();
|
| +}
|
| +
|
| +/** @param {ArrayBuffer} data */
|
| +remoting.XmppLoginHandler.prototype.onDataReceived = function(data) {
|
| + switch (this.state_) {
|
| + case remoting.XmppLoginHandler.State.INIT:
|
| + this.onError_(remoting.Error.UNEXPECTED,
|
| + 'Data was received before the stream was started.');
|
| + break;
|
| +
|
| + case remoting.XmppLoginHandler.State.ERROR:
|
| + // Ignore data received in the error state.
|
| + break;
|
| +
|
| + default:
|
| + this.streamParser_.appendData(data);
|
| + break;
|
| + }
|
| +}
|
| +
|
| +/** @param {Element} stanza */
|
| +remoting.XmppLoginHandler.prototype.onStanza_ = function(stanza) {
|
| + switch (this.state_) {
|
| + case remoting.XmppLoginHandler.State.START_SENT:
|
| + if (stanza.querySelector('features>starttls')) {
|
| + this.sendMessageCallback_(
|
| + '<starttls xmlns="urn:ietf:params:xml:ns:xmpp-tls"/>');
|
| + this.state_ = remoting.XmppLoginHandler.State.STARTTLS_SENT;
|
| + } else {
|
| + this.onError_(remoting.Error.UNEXPECTED, "Server doesn't support TLS.");
|
| + }
|
| + break;
|
| +
|
| + case remoting.XmppLoginHandler.State.STARTTLS_SENT:
|
| + if (stanza.localName == "proceed") {
|
| + this.state_ = remoting.XmppLoginHandler.State.STARTING_TLS;
|
| + this.startTlsCallback_();
|
| + } else {
|
| + this.onError_(remoting.Error.UNEXPECTED,
|
| + "Failed to start TLS: " +
|
| + (new XMLSerializer().serializeToString(stanza)));
|
| + }
|
| + break;
|
| +
|
| + case remoting.XmppLoginHandler.State.START_SENT_AFTER_TLS:
|
| + var mechanisms = Array.prototype.map.call(
|
| + stanza.querySelectorAll('features>mechanisms>mechanism'),
|
| + /** @param {Element} m */
|
| + function(m) { return m.textContent; });
|
| + if (mechanisms.indexOf("X-OAUTH2")) {
|
| + this.onError_(remoting.Error.UNEXPECTED,
|
| + "OAuth2 is not supported by the server.");
|
| + return;
|
| + }
|
| +
|
| + var cookie = window.btoa("\0" + this.username_ + "\0" + this.authToken_);
|
| +
|
| + this.state_ = remoting.XmppLoginHandler.State.AUTH_SENT;
|
| + this.sendMessageCallback_(
|
| + '<auth xmlns="urn:ietf:params:xml:ns:xmpp-sasl" ' +
|
| + 'mechanism="X-OAUTH2" auth:service="oauth2" ' +
|
| + '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>');
|
| + break;
|
| +
|
| + case remoting.XmppLoginHandler.State.AUTH_SENT:
|
| + if (stanza.localName == 'success') {
|
| + this.state_ = remoting.XmppLoginHandler.State.AUTH_ACCEPTED;
|
| + this.startStream_();
|
| + } else {
|
| + this.onError_(remoting.Error.AUTHENTICATION_FAILED,
|
| + 'Failed to authenticate: ' +
|
| + (new XMLSerializer().serializeToString(stanza)));
|
| + }
|
| + break;
|
| +
|
| + case remoting.XmppLoginHandler.State.AUTH_ACCEPTED:
|
| + if (stanza.querySelector('features>bind')) {
|
| + this.sendMessageCallback_(
|
| + '<iq type="set" id="0">' +
|
| + '<bind xmlns="urn:ietf:params:xml:ns:xmpp-bind">' +
|
| + '<resource>chromoting</resource></bind></iq>');
|
| + this.state_ = remoting.XmppLoginHandler.State.BIND_SENT;
|
| + } else {
|
| + this.onError_(remoting.Error.UNEXPECTED,
|
| + 'Server doesn\'t support bind after authentication.');
|
| + }
|
| + break;
|
| +
|
| + case remoting.XmppLoginHandler.State.BIND_SENT:
|
| + var jidElement = stanza.querySelector('iq>bind>jid');
|
| + if (!jidElement)
|
| + if (stanza.getAttribute('id') != '0' ||
|
| + stanza.getAttribute('type') != 'result' || !jidElement) {
|
| + this.onError_(remoting.Error.UNEXPECTED,
|
| + 'Received unexpected response to bind: ' +
|
| + (new XMLSerializer().serializeToString(stanza)));
|
| + return;
|
| + }
|
| + this.jid_ = jidElement.textContent;
|
| + this.sendMessageCallback_(
|
| + '<iq type="set" id="1">' +
|
| + '<session xmlns="urn:ietf:params:xml:ns:xmpp-session"/></iq>');
|
| + this.state_ = remoting.XmppLoginHandler.State.SESSION_IQ_SENT;
|
| + break;
|
| +
|
| + case remoting.XmppLoginHandler.State.SESSION_IQ_SENT:
|
| + if (stanza.getAttribute('id') != '1' ||
|
| + stanza.getAttribute('type') != 'result') {
|
| + this.onError_(remoting.Error.UNEXPECTED,
|
| + 'Failed to start session: ' +
|
| + (new XMLSerializer().serializeToString(stanza)));
|
| + return;
|
| + }
|
| + this.state_ = remoting.XmppLoginHandler.State.SESSION_ACTIVE;
|
| + this.onHandshakeDoneCallback_(this.jid_);
|
| + break;
|
| +
|
| + case remoting.XmppLoginHandler.State.SESSION_ACTIVE:
|
| + this.onStanzaCallback_(stanza);
|
| + break;
|
| + }
|
| +}
|
| +
|
| +/** @param {string} text */
|
| +remoting.XmppLoginHandler.prototype.onParserError_ = function(text) {
|
| + this.onError_(remoting.Error.UNEXPECTED, text);
|
| +}
|
| +
|
| +remoting.XmppLoginHandler.prototype.onTlsStarted = function() {
|
| + base.debug.assert(this.state_ ==
|
| + remoting.XmppLoginHandler.State.STARTING_TLS);
|
| + this.state_ = remoting.XmppLoginHandler.State.START_SENT_AFTER_TLS;
|
| + this.startStream_();
|
| +};
|
| +
|
| +remoting.XmppLoginHandler.prototype.startStream_ = function() {
|
| + var server = this.username_.split('@', 2)[1];
|
| + this.sendMessageCallback_('<stream:stream to="' + server +
|
| + '" version="1.0" xmlns="jabber:client" ' +
|
| + 'xmlns:stream="http://etherx.jabber.org/streams">');
|
| + this.streamParser_ =
|
| + new remoting.XmppStreamParser(this.onStanza_.bind(this),
|
| + this.onParserError_.bind(this));
|
| +}
|
| +
|
| +/**
|
| + * @param {remoting.Error} error
|
| + * @param {string} text
|
| + */
|
| +remoting.XmppLoginHandler.prototype.onError_ = function(error, text) {
|
| + if (this.state_ != remoting.XmppLoginHandler.State.ERROR) {
|
| + this.onErrorCallback_(error, text);
|
| + this.state_ = remoting.XmppLoginHandler.State.ERROR;
|
| + } else {
|
| + console.error(text);
|
| + }
|
| +}
|
|
|