Remove getCached* methods from remoting.Identity.
[chromium-blink-merge.git] / remoting / webapp / crd / js / xmpp_login_handler.js
blobf91ef55632098ee4568b34cad5f50956671ba98d
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.
5 'use strict';
7 /** @suppress {duplicate} */
8 var remoting = remoting || {};
10 /**
11  * XmppLoginHandler handles authentication handshake for XmppConnection. It
12  * receives incoming data using onDataReceived(), calls |sendMessageCallback|
13  * to send outgoing messages and calls |onHandshakeDoneCallback| after
14  * authentication is finished successfully or |onErrorCallback| on error.
15  *
16  * See RFC3920 for description of XMPP and authentication handshake.
17  *
18  * @param {string} server Domain name of the server we are connecting to.
19  * @param {string} username Username.
20  * @param {string} authToken OAuth2 token.
21  * @param {boolean} needHandshakeBeforeTls Set to true when <starttls> handshake
22  *     is required before starting TLS. Otherwise TLS can be started right away.
23  * @param {function(string):void} sendMessageCallback Callback to call to send
24  *     a message.
25  * @param {function():void} startTlsCallback Callback to call to start TLS on
26  *     the underlying socket.
27  * @param {function(string, remoting.XmppStreamParser):void}
28  *     onHandshakeDoneCallback Callback to call after authentication is
29  *     completed successfully
30  * @param {function(remoting.Error, string):void} onErrorCallback Callback to
31  *     call on error. Can be called at any point during lifetime of connection.
32  * @constructor
33  */
34 remoting.XmppLoginHandler = function(server,
35                                      username,
36                                      authToken,
37                                      needHandshakeBeforeTls,
38                                      sendMessageCallback,
39                                      startTlsCallback,
40                                      onHandshakeDoneCallback,
41                                      onErrorCallback) {
42   /** @private */
43   this.server_ = server;
44   /** @private */
45   this.username_ = username;
46   /** @private */
47   this.authToken_ = authToken;
48   /** @private */
49   this.needHandshakeBeforeTls_ = needHandshakeBeforeTls;
50   /** @private */
51   this.sendMessageCallback_ = sendMessageCallback;
52   /** @private */
53   this.startTlsCallback_ = startTlsCallback;
54   /** @private */
55   this.onHandshakeDoneCallback_ = onHandshakeDoneCallback;
56   /** @private */
57   this.onErrorCallback_ = onErrorCallback;
59   /** @private */
60   this.state_ = remoting.XmppLoginHandler.State.INIT;
61   /** @private */
62   this.jid_ = '';
64   /** @type {remoting.XmppStreamParser} @private */
65   this.streamParser_ = null;
68 /** @return {function(string, remoting.XmppStreamParser):void} */
69 remoting.XmppLoginHandler.prototype.getHandshakeDoneCallbackForTesting =
70     function() {
71   return this.onHandshakeDoneCallback_;
74 /**
75  * States the handshake goes through. States are iterated from INIT to DONE
76  * sequentially, except for ERROR state which may be accepted at any point.
77  *
78  * Following messages are sent/received in each state:
79  *    INIT
80  *      client -> server: Stream header
81  *      client -> server: <starttls>
82  *    WAIT_STREAM_HEADER
83  *      client <- server: Stream header with list of supported features which
84  *          should include starttls.
85  *    WAIT_STARTTLS_RESPONSE
86  *      client <- server: <proceed>
87  *    STARTING_TLS
88  *      TLS handshake
89  *      client -> server: Stream header
90  *      client -> server: <auth> message with the OAuth2 token.
91  *    WAIT_STREAM_HEADER_AFTER_TLS
92  *      client <- server: Stream header with list of supported authentication
93  *          methods which is expected to include X-OAUTH2
94  *    WAIT_AUTH_RESULT
95  *      client <- server: <success> or <failure>
96  *      client -> server: Stream header
97  *      client -> server: <bind>
98  *      client -> server: <iq><session/></iq> to start the session
99  *    WAIT_STREAM_HEADER_AFTER_AUTH
100  *      client <- server: Stream header with list of features that should
101  *         include <bind>.
102  *    WAIT_BIND_RESULT
103  *      client <- server: <bind> result with JID.
104  *    WAIT_SESSION_IQ_RESULT
105  *      client <- server: result for <iq><session/></iq>
106  *    DONE
108  * @enum {number}
109  */
110 remoting.XmppLoginHandler.State = {
111   INIT: 0,
112   WAIT_STREAM_HEADER: 1,
113   WAIT_STARTTLS_RESPONSE: 2,
114   STARTING_TLS: 3,
115   WAIT_STREAM_HEADER_AFTER_TLS: 4,
116   WAIT_AUTH_RESULT: 5,
117   WAIT_STREAM_HEADER_AFTER_AUTH: 6,
118   WAIT_BIND_RESULT: 7,
119   WAIT_SESSION_IQ_RESULT: 8,
120   DONE: 9,
121   ERROR: 10
124 remoting.XmppLoginHandler.prototype.start = function() {
125   if (this.needHandshakeBeforeTls_) {
126     this.state_ = remoting.XmppLoginHandler.State.WAIT_STREAM_HEADER;
127     this.startStream_('<starttls xmlns="urn:ietf:params:xml:ns:xmpp-tls"/>');
128   } else {
129     // If <starttls> handshake is not required then start TLS right away.
130     this.state_ = remoting.XmppLoginHandler.State.STARTING_TLS;
131     this.startTlsCallback_();
132   }
135 /** @param {ArrayBuffer} data */
136 remoting.XmppLoginHandler.prototype.onDataReceived = function(data) {
137   base.debug.assert(this.state_ != remoting.XmppLoginHandler.State.INIT &&
138                     this.state_ != remoting.XmppLoginHandler.State.DONE &&
139                     this.state_ != remoting.XmppLoginHandler.State.ERROR);
141   this.streamParser_.appendData(data);
145  * @param {Element} stanza
146  * @private
147  */
148 remoting.XmppLoginHandler.prototype.onStanza_ = function(stanza) {
149   switch (this.state_) {
150     case remoting.XmppLoginHandler.State.WAIT_STREAM_HEADER:
151       if (stanza.querySelector('features>starttls')) {
152         this.state_ = remoting.XmppLoginHandler.State.WAIT_STARTTLS_RESPONSE;
153       } else {
154         this.onError_(remoting.Error.UNEXPECTED, "Server doesn't support TLS.");
155       }
156       break;
158     case remoting.XmppLoginHandler.State.WAIT_STARTTLS_RESPONSE:
159       if (stanza.localName == "proceed") {
160         this.state_ = remoting.XmppLoginHandler.State.STARTING_TLS;
161         this.startTlsCallback_();
162       } else {
163         this.onError_(remoting.Error.UNEXPECTED,
164                       "Failed to start TLS: " +
165                           (new XMLSerializer().serializeToString(stanza)));
166       }
167       break;
169     case remoting.XmppLoginHandler.State.WAIT_STREAM_HEADER_AFTER_TLS:
170       var mechanisms = Array.prototype.map.call(
171           stanza.querySelectorAll('features>mechanisms>mechanism'),
172           /** @param {Element} m */
173           function(m) { return m.textContent; });
174       if (mechanisms.indexOf("X-OAUTH2")) {
175         this.onError_(remoting.Error.UNEXPECTED,
176                       "OAuth2 is not supported by the server.");
177         return;
178       }
180       this.state_ = remoting.XmppLoginHandler.State.WAIT_AUTH_RESULT;
182       break;
184     case remoting.XmppLoginHandler.State.WAIT_AUTH_RESULT:
185       if (stanza.localName == 'success') {
186         this.state_ =
187             remoting.XmppLoginHandler.State.WAIT_STREAM_HEADER_AFTER_AUTH;
188         this.startStream_(
189             '<iq type="set" id="0">' +
190               '<bind xmlns="urn:ietf:params:xml:ns:xmpp-bind">' +
191                 '<resource>chromoting</resource>'+
192               '</bind>' +
193             '</iq>' +
194             '<iq type="set" id="1">' +
195               '<session xmlns="urn:ietf:params:xml:ns:xmpp-session"/>' +
196             '</iq>');
197       } else {
198         this.onError_(remoting.Error.AUTHENTICATION_FAILED,
199                       'Failed to authenticate: ' +
200                           (new XMLSerializer().serializeToString(stanza)));
201       }
202       break;
204     case remoting.XmppLoginHandler.State.WAIT_STREAM_HEADER_AFTER_AUTH:
205       if (stanza.querySelector('features>bind')) {
206         this.state_ = remoting.XmppLoginHandler.State.WAIT_BIND_RESULT;
207       } else {
208         this.onError_(remoting.Error.UNEXPECTED,
209                       "Server doesn't support bind after authentication.");
210       }
211       break;
213     case remoting.XmppLoginHandler.State.WAIT_BIND_RESULT:
214       var jidElement = stanza.querySelector('iq>bind>jid');
215       if (stanza.getAttribute('id') != '0' ||
216           stanza.getAttribute('type') != 'result' || !jidElement) {
217         this.onError_(remoting.Error.UNEXPECTED,
218                       'Received unexpected response to bind: ' +
219                           (new XMLSerializer().serializeToString(stanza)));
220         return;
221       }
222       this.jid_ = jidElement.textContent;
223       this.state_ = remoting.XmppLoginHandler.State.WAIT_SESSION_IQ_RESULT;
224       break;
226     case remoting.XmppLoginHandler.State.WAIT_SESSION_IQ_RESULT:
227       if (stanza.getAttribute('id') != '1' ||
228           stanza.getAttribute('type') != 'result') {
229         this.onError_(remoting.Error.UNEXPECTED,
230                       'Failed to start session: ' +
231                           (new XMLSerializer().serializeToString(stanza)));
232         return;
233       }
234       this.state_ = remoting.XmppLoginHandler.State.DONE;
235       this.onHandshakeDoneCallback_(this.jid_, this.streamParser_);
236       break;
238     default:
239       base.debug.assert(false);
240       break;
241   }
244 remoting.XmppLoginHandler.prototype.onTlsStarted = function() {
245   base.debug.assert(this.state_ ==
246                     remoting.XmppLoginHandler.State.STARTING_TLS);
247   this.state_ = remoting.XmppLoginHandler.State.WAIT_STREAM_HEADER_AFTER_TLS;
248   var cookie = window.btoa("\0" + this.username_ + "\0" + this.authToken_);
250   this.startStream_(
251       '<auth xmlns="urn:ietf:params:xml:ns:xmpp-sasl" ' +
252              'mechanism="X-OAUTH2" auth:service="oauth2" ' +
253              'auth:allow-generated-jid="true" ' +
254              'auth:client-uses-full-bind-result="true" ' +
255              'auth:allow-non-google-login="true" ' +
256              'xmlns:auth="http://www.google.com/talk/protocol/auth">' +
257         cookie +
258       '</auth>');
262  * @param {string} text
263  * @private
264  */
265 remoting.XmppLoginHandler.prototype.onParserError_ = function(text) {
266   this.onError_(remoting.Error.UNEXPECTED, text);
270  * @param {string} firstMessage Message to send after stream header.
271  * @private
272  */
273 remoting.XmppLoginHandler.prototype.startStream_ = function(firstMessage) {
274   this.sendMessageCallback_('<stream:stream to="' + this.server_ +
275                             '" version="1.0" xmlns="jabber:client" ' +
276                             'xmlns:stream="http://etherx.jabber.org/streams">' +
277                             firstMessage);
278   this.streamParser_ = new remoting.XmppStreamParser();
279   this.streamParser_.setCallbacks(this.onStanza_.bind(this),
280                                   this.onParserError_.bind(this));
284  * @param {remoting.Error} error
285  * @param {string} text
286  * @private
287  */
288 remoting.XmppLoginHandler.prototype.onError_ = function(error, text) {
289   if (this.state_ != remoting.XmppLoginHandler.State.ERROR) {
290     this.onErrorCallback_(error, text);
291     this.state_ = remoting.XmppLoginHandler.State.ERROR;
292   } else {
293     console.error(text);
294   }