OLD | NEW |
(Empty) | |
| 1 // Copyright 2014 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. |
| 4 |
| 5 'use strict'; |
| 6 |
| 7 /** @suppress {duplicate} */ |
| 8 var remoting = remoting || {}; |
| 9 |
| 10 /** |
| 11 * XmppLoginHandler handles authentication handshake for XmppConnection. It |
| 12 * receives incoming data using onDataReceived() calls |sendMessageCallback| |
| 13 * to send outgoing messages and |
| 14 * |
| 15 * @param {string} username Username. |
| 16 * @param {string} authToken OAuth2 token. |
| 17 * @param {function(string):void} sendMessageCallback Callback call to send |
| 18 * a message. |
| 19 * @param {function():void} startTlsCallback Callback to call to start TLS on |
| 20 * the underlying socket. |
| 21 * @param {function(string):void} onHandshakeDoneCallback Callback to call |
| 22 * after authentication is completed successfully |
| 23 * @param {function(Element):void} onStanzaCallback Callback to call for each |
| 24 * incoming stanza (when authenticated). |
| 25 * @param {function(remoting.Error, string):void} onErrorCallback Callback to |
| 26 * call on error. Can be called at any point during lifetime of connection. |
| 27 * @constructor |
| 28 */ |
| 29 remoting.XmppLoginHandler = function(username, |
| 30 authToken, |
| 31 sendMessageCallback, |
| 32 startTlsCallback, |
| 33 onHandshakeDoneCallback, |
| 34 onStanzaCallback, |
| 35 onErrorCallback) { |
| 36 this.username_ = username; |
| 37 this.authToken_ = authToken; |
| 38 this.sendMessageCallback_ = sendMessageCallback; |
| 39 this.startTlsCallback_ = startTlsCallback; |
| 40 this.onHandshakeDoneCallback_ = onHandshakeDoneCallback; |
| 41 this.onStanzaCallback_ = onStanzaCallback; |
| 42 this.onErrorCallback_ = onErrorCallback; |
| 43 |
| 44 this.state_ = remoting.XmppLoginHandler.State.INIT; |
| 45 this.jid_ = ''; |
| 46 |
| 47 /** @type {remoting.XmppStreamParser} */ |
| 48 this.streamParser_ = null; |
| 49 } |
| 50 |
| 51 /** |
| 52 * @enum {number} |
| 53 */ |
| 54 remoting.XmppLoginHandler.State = { |
| 55 INIT: 0, |
| 56 START_SENT: 1, |
| 57 STARTTLS_SENT: 2, |
| 58 STARTING_TLS: 3, |
| 59 START_SENT_AFTER_TLS: 4, |
| 60 AUTH_SENT: 5, |
| 61 AUTH_ACCEPTED: 6, |
| 62 BIND_SENT: 7, |
| 63 SESSION_IQ_SENT: 8, |
| 64 SESSION_ACTIVE: 9, |
| 65 ERROR: 10 |
| 66 }; |
| 67 |
| 68 remoting.XmppLoginHandler.prototype.start = function() { |
| 69 this.state_ = remoting.XmppLoginHandler.State.START_SENT; |
| 70 this.startStream_(); |
| 71 } |
| 72 |
| 73 /** @param {ArrayBuffer} data */ |
| 74 remoting.XmppLoginHandler.prototype.onDataReceived = function(data) { |
| 75 switch (this.state_) { |
| 76 case remoting.XmppLoginHandler.State.INIT: |
| 77 this.onError_(remoting.Error.UNEXPECTED, |
| 78 'Data was received before the stream was started.'); |
| 79 break; |
| 80 |
| 81 case remoting.XmppLoginHandler.State.ERROR: |
| 82 // Ignore data received in the error state. |
| 83 break; |
| 84 |
| 85 default: |
| 86 this.streamParser_.appendData(data); |
| 87 break; |
| 88 } |
| 89 } |
| 90 |
| 91 /** @param {Element} stanza */ |
| 92 remoting.XmppLoginHandler.prototype.onStanza_ = function(stanza) { |
| 93 switch (this.state_) { |
| 94 case remoting.XmppLoginHandler.State.START_SENT: |
| 95 if (stanza.querySelector('features>starttls')) { |
| 96 this.sendMessageCallback_( |
| 97 '<starttls xmlns="urn:ietf:params:xml:ns:xmpp-tls"/>'); |
| 98 this.state_ = remoting.XmppLoginHandler.State.STARTTLS_SENT; |
| 99 } else { |
| 100 this.onError_(remoting.Error.UNEXPECTED, "Server doesn't support TLS."); |
| 101 } |
| 102 break; |
| 103 |
| 104 case remoting.XmppLoginHandler.State.STARTTLS_SENT: |
| 105 if (stanza.localName == "proceed") { |
| 106 this.state_ = remoting.XmppLoginHandler.State.STARTING_TLS; |
| 107 this.startTlsCallback_(); |
| 108 } else { |
| 109 this.onError_(remoting.Error.UNEXPECTED, |
| 110 "Failed to start TLS: " + |
| 111 (new XMLSerializer().serializeToString(stanza))); |
| 112 } |
| 113 break; |
| 114 |
| 115 case remoting.XmppLoginHandler.State.START_SENT_AFTER_TLS: |
| 116 var mechanisms = Array.prototype.map.call( |
| 117 stanza.querySelectorAll('features>mechanisms>mechanism'), |
| 118 /** @param {Element} m */ |
| 119 function(m) { return m.textContent; }); |
| 120 if (mechanisms.indexOf("X-OAUTH2")) { |
| 121 this.onError_(remoting.Error.UNEXPECTED, |
| 122 "OAuth2 is not supported by the server."); |
| 123 return; |
| 124 } |
| 125 |
| 126 var cookie = window.btoa("\0" + this.username_ + "\0" + this.authToken_); |
| 127 |
| 128 this.state_ = remoting.XmppLoginHandler.State.AUTH_SENT; |
| 129 this.sendMessageCallback_( |
| 130 '<auth xmlns="urn:ietf:params:xml:ns:xmpp-sasl" ' + |
| 131 'mechanism="X-OAUTH2" auth:service="oauth2" ' + |
| 132 'auth:allow-generated-jid="true" ' + |
| 133 'auth:client-uses-full-bind-result="true" ' + |
| 134 'auth:allow-non-google-login="true" ' + |
| 135 'xmlns:auth="http://www.google.com/talk/protocol/auth">' + |
| 136 cookie + '</auth>'); |
| 137 break; |
| 138 |
| 139 case remoting.XmppLoginHandler.State.AUTH_SENT: |
| 140 if (stanza.localName == 'success') { |
| 141 this.state_ = remoting.XmppLoginHandler.State.AUTH_ACCEPTED; |
| 142 this.startStream_(); |
| 143 } else { |
| 144 this.onError_(remoting.Error.AUTHENTICATION_FAILED, |
| 145 'Failed to authenticate: ' + |
| 146 (new XMLSerializer().serializeToString(stanza))); |
| 147 } |
| 148 break; |
| 149 |
| 150 case remoting.XmppLoginHandler.State.AUTH_ACCEPTED: |
| 151 if (stanza.querySelector('features>bind')) { |
| 152 this.sendMessageCallback_( |
| 153 '<iq type="set" id="0">' + |
| 154 '<bind xmlns="urn:ietf:params:xml:ns:xmpp-bind">' + |
| 155 '<resource>chromoting</resource></bind></iq>'); |
| 156 this.state_ = remoting.XmppLoginHandler.State.BIND_SENT; |
| 157 } else { |
| 158 this.onError_(remoting.Error.UNEXPECTED, |
| 159 'Server doesn\'t support bind after authentication.'); |
| 160 } |
| 161 break; |
| 162 |
| 163 case remoting.XmppLoginHandler.State.BIND_SENT: |
| 164 var jidElement = stanza.querySelector('iq>bind>jid'); |
| 165 if (!jidElement) |
| 166 if (stanza.getAttribute('id') != '0' || |
| 167 stanza.getAttribute('type') != 'result' || !jidElement) { |
| 168 this.onError_(remoting.Error.UNEXPECTED, |
| 169 'Received unexpected response to bind: ' + |
| 170 (new XMLSerializer().serializeToString(stanza))); |
| 171 return; |
| 172 } |
| 173 this.jid_ = jidElement.textContent; |
| 174 this.sendMessageCallback_( |
| 175 '<iq type="set" id="1">' + |
| 176 '<session xmlns="urn:ietf:params:xml:ns:xmpp-session"/></iq>'); |
| 177 this.state_ = remoting.XmppLoginHandler.State.SESSION_IQ_SENT; |
| 178 break; |
| 179 |
| 180 case remoting.XmppLoginHandler.State.SESSION_IQ_SENT: |
| 181 if (stanza.getAttribute('id') != '1' || |
| 182 stanza.getAttribute('type') != 'result') { |
| 183 this.onError_(remoting.Error.UNEXPECTED, |
| 184 'Failed to start session: ' + |
| 185 (new XMLSerializer().serializeToString(stanza))); |
| 186 return; |
| 187 } |
| 188 this.state_ = remoting.XmppLoginHandler.State.SESSION_ACTIVE; |
| 189 this.onHandshakeDoneCallback_(this.jid_); |
| 190 break; |
| 191 |
| 192 case remoting.XmppLoginHandler.State.SESSION_ACTIVE: |
| 193 this.onStanzaCallback_(stanza); |
| 194 break; |
| 195 } |
| 196 } |
| 197 |
| 198 /** @param {string} text */ |
| 199 remoting.XmppLoginHandler.prototype.onParserError_ = function(text) { |
| 200 this.onError_(remoting.Error.UNEXPECTED, text); |
| 201 } |
| 202 |
| 203 remoting.XmppLoginHandler.prototype.onTlsStarted = function() { |
| 204 base.debug.assert(this.state_ == |
| 205 remoting.XmppLoginHandler.State.STARTING_TLS); |
| 206 this.state_ = remoting.XmppLoginHandler.State.START_SENT_AFTER_TLS; |
| 207 this.startStream_(); |
| 208 }; |
| 209 |
| 210 remoting.XmppLoginHandler.prototype.startStream_ = function() { |
| 211 var server = this.username_.split('@', 2)[1]; |
| 212 this.sendMessageCallback_('<stream:stream to="' + server + |
| 213 '" version="1.0" xmlns="jabber:client" ' + |
| 214 'xmlns:stream="http://etherx.jabber.org/streams">'); |
| 215 this.streamParser_ = |
| 216 new remoting.XmppStreamParser(this.onStanza_.bind(this), |
| 217 this.onParserError_.bind(this)); |
| 218 } |
| 219 |
| 220 /** |
| 221 * @param {remoting.Error} error |
| 222 * @param {string} text |
| 223 */ |
| 224 remoting.XmppLoginHandler.prototype.onError_ = function(error, text) { |
| 225 if (this.state_ != remoting.XmppLoginHandler.State.ERROR) { |
| 226 this.onErrorCallback_(error, text); |
| 227 this.state_ = remoting.XmppLoginHandler.State.ERROR; |
| 228 } else { |
| 229 console.error(text); |
| 230 } |
| 231 } |
OLD | NEW |