OLD | NEW |
1 // Copyright 2014 The Chromium Authors. All rights reserved. | 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 | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 'use strict'; | 5 'use strict'; |
6 | 6 |
7 /** @suppress {duplicate} */ | 7 /** @suppress {duplicate} */ |
8 var remoting = remoting || {}; | 8 var remoting = remoting || {}; |
9 | 9 |
10 /** | 10 /** |
(...skipping 48 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
59 /** @type {remoting.XmppStreamParser} @private */ | 59 /** @type {remoting.XmppStreamParser} @private */ |
60 this.streamParser_ = null; | 60 this.streamParser_ = null; |
61 } | 61 } |
62 | 62 |
63 /** | 63 /** |
64 * States the handshake goes through. States are iterated from INIT to DONE | 64 * States the handshake goes through. States are iterated from INIT to DONE |
65 * sequentially, except for ERROR state which may be accepted at any point. | 65 * sequentially, except for ERROR state which may be accepted at any point. |
66 * | 66 * |
67 * Following messages are sent/received in each state: | 67 * Following messages are sent/received in each state: |
68 * INIT | 68 * INIT |
69 * client -> server: Stream header | 69 * client -> server: Stream header |
70 * START_SENT | 70 * client -> server: <starttls> |
71 * client <- server: Stream header with list of supported features which | 71 * WAIT_STREAM_HEADER |
72 * should include starttls. | 72 * client <- server: Stream header with list of supported features which |
73 * client -> server: <starttls> | 73 * should include starttls. |
74 * STARTTLS_SENT | 74 * WAIT_STARTTLS_RESPONSE |
75 * client <- server: <proceed> | 75 * client <- server: <proceed> |
76 * STARTING_TLS | 76 * STARTING_TLS |
77 * TLS handshake | 77 * TLS handshake |
78 * client -> server: Stream header | 78 * client -> server: Stream header |
79 * START_SENT_AFTER_TLS | 79 * client -> server: <auth> message with the OAuth2 token. |
| 80 * WAIT_STREAM_HEADER_AFTER_TLS |
80 * client <- server: Stream header with list of supported authentication | 81 * client <- server: Stream header with list of supported authentication |
81 * methods which is expected to include X-OAUTH2 | 82 * methods which is expected to include X-OAUTH2 |
82 * client -> server: <auth> message with the OAuth2 token. | 83 * WAIT_AUTH_RESULT |
83 * AUTH_SENT | |
84 * client <- server: <success> or <failure> | 84 * client <- server: <success> or <failure> |
85 * client -> server: Stream header | 85 * client -> server: Stream header |
86 * AUTH_ACCEPTED | 86 * client -> server: <bind> |
| 87 * client -> server: <iq><session/></iq> to start the session |
| 88 * WAIT_STREAM_HEADER_AFTER_AUTH |
87 * client <- server: Stream header with list of features that should | 89 * client <- server: Stream header with list of features that should |
88 * include <bind>. | 90 * include <bind>. |
89 * client -> server: <bind> | 91 * WAIT_BIND_RESULT |
90 * BIND_SENT | |
91 * client <- server: <bind> result with JID. | 92 * client <- server: <bind> result with JID. |
92 * client -> server: <iq><session/></iq> to start the session | 93 * WAIT_SESSION_IQ_RESULT |
93 * SESSION_IQ_SENT | 94 * client <- server: result for <iq><session/></iq> |
94 * client <- server: iq result | |
95 * DONE | 95 * DONE |
96 * | 96 * |
97 * @enum {number} | 97 * @enum {number} |
98 */ | 98 */ |
99 remoting.XmppLoginHandler.State = { | 99 remoting.XmppLoginHandler.State = { |
100 INIT: 0, | 100 INIT: 0, |
101 START_SENT: 1, | 101 WAIT_STREAM_HEADER: 1, |
102 STARTTLS_SENT: 2, | 102 WAIT_STARTTLS_RESPONSE: 2, |
103 STARTING_TLS: 3, | 103 STARTING_TLS: 3, |
104 START_SENT_AFTER_TLS: 4, | 104 WAIT_STREAM_HEADER_AFTER_TLS: 4, |
105 AUTH_SENT: 5, | 105 WAIT_AUTH_RESULT: 5, |
106 AUTH_ACCEPTED: 6, | 106 WAIT_STREAM_HEADER_AFTER_AUTH: 6, |
107 BIND_SENT: 7, | 107 WAIT_BIND_RESULT: 7, |
108 SESSION_IQ_SENT: 8, | 108 WAIT_SESSION_IQ_RESULT: 8, |
109 DONE: 9, | 109 DONE: 9, |
110 ERROR: 10 | 110 ERROR: 10 |
111 }; | 111 }; |
112 | 112 |
113 remoting.XmppLoginHandler.prototype.start = function() { | 113 remoting.XmppLoginHandler.prototype.start = function() { |
114 this.state_ = remoting.XmppLoginHandler.State.START_SENT; | 114 this.state_ = remoting.XmppLoginHandler.State.WAIT_STREAM_HEADER; |
115 this.startStream_(); | 115 this.startStream_('<starttls xmlns="urn:ietf:params:xml:ns:xmpp-tls"/>'); |
116 } | 116 } |
117 | 117 |
118 /** @param {ArrayBuffer} data */ | 118 /** @param {ArrayBuffer} data */ |
119 remoting.XmppLoginHandler.prototype.onDataReceived = function(data) { | 119 remoting.XmppLoginHandler.prototype.onDataReceived = function(data) { |
120 base.debug.assert(this.state_ != remoting.XmppLoginHandler.State.INIT && | 120 base.debug.assert(this.state_ != remoting.XmppLoginHandler.State.INIT && |
121 this.state_ != remoting.XmppLoginHandler.State.DONE && | 121 this.state_ != remoting.XmppLoginHandler.State.DONE && |
122 this.state_ != remoting.XmppLoginHandler.State.ERROR); | 122 this.state_ != remoting.XmppLoginHandler.State.ERROR); |
123 | 123 |
124 this.streamParser_.appendData(data); | 124 this.streamParser_.appendData(data); |
125 } | 125 } |
126 | 126 |
127 /** | 127 /** |
128 * @param {Element} stanza | 128 * @param {Element} stanza |
129 * @private | 129 * @private |
130 */ | 130 */ |
131 remoting.XmppLoginHandler.prototype.onStanza_ = function(stanza) { | 131 remoting.XmppLoginHandler.prototype.onStanza_ = function(stanza) { |
132 switch (this.state_) { | 132 switch (this.state_) { |
133 case remoting.XmppLoginHandler.State.START_SENT: | 133 case remoting.XmppLoginHandler.State.WAIT_STREAM_HEADER: |
134 if (stanza.querySelector('features>starttls')) { | 134 if (stanza.querySelector('features>starttls')) { |
135 this.sendMessageCallback_( | 135 this.state_ = remoting.XmppLoginHandler.State.WAIT_STARTTLS_RESPONSE; |
136 '<starttls xmlns="urn:ietf:params:xml:ns:xmpp-tls"/>'); | |
137 this.state_ = remoting.XmppLoginHandler.State.STARTTLS_SENT; | |
138 } else { | 136 } else { |
139 this.onError_(remoting.Error.UNEXPECTED, "Server doesn't support TLS."); | 137 this.onError_(remoting.Error.UNEXPECTED, "Server doesn't support TLS."); |
140 } | 138 } |
141 break; | 139 break; |
142 | 140 |
143 case remoting.XmppLoginHandler.State.STARTTLS_SENT: | 141 case remoting.XmppLoginHandler.State.WAIT_STARTTLS_RESPONSE: |
144 if (stanza.localName == "proceed") { | 142 if (stanza.localName == "proceed") { |
145 this.state_ = remoting.XmppLoginHandler.State.STARTING_TLS; | 143 this.state_ = remoting.XmppLoginHandler.State.STARTING_TLS; |
146 this.startTlsCallback_(); | 144 this.startTlsCallback_(); |
147 } else { | 145 } else { |
148 this.onError_(remoting.Error.UNEXPECTED, | 146 this.onError_(remoting.Error.UNEXPECTED, |
149 "Failed to start TLS: " + | 147 "Failed to start TLS: " + |
150 (new XMLSerializer().serializeToString(stanza))); | 148 (new XMLSerializer().serializeToString(stanza))); |
151 } | 149 } |
152 break; | 150 break; |
153 | 151 |
154 case remoting.XmppLoginHandler.State.START_SENT_AFTER_TLS: | 152 case remoting.XmppLoginHandler.State.WAIT_STREAM_HEADER_AFTER_TLS: |
155 var mechanisms = Array.prototype.map.call( | 153 var mechanisms = Array.prototype.map.call( |
156 stanza.querySelectorAll('features>mechanisms>mechanism'), | 154 stanza.querySelectorAll('features>mechanisms>mechanism'), |
157 /** @param {Element} m */ | 155 /** @param {Element} m */ |
158 function(m) { return m.textContent; }); | 156 function(m) { return m.textContent; }); |
159 if (mechanisms.indexOf("X-OAUTH2")) { | 157 if (mechanisms.indexOf("X-OAUTH2")) { |
160 this.onError_(remoting.Error.UNEXPECTED, | 158 this.onError_(remoting.Error.UNEXPECTED, |
161 "OAuth2 is not supported by the server."); | 159 "OAuth2 is not supported by the server."); |
162 return; | 160 return; |
163 } | 161 } |
164 | 162 |
165 var cookie = window.btoa("\0" + this.username_ + "\0" + this.authToken_); | 163 this.state_ = remoting.XmppLoginHandler.State.WAIT_AUTH_RESULT; |
166 | 164 |
167 this.state_ = remoting.XmppLoginHandler.State.AUTH_SENT; | |
168 this.sendMessageCallback_( | |
169 '<auth xmlns="urn:ietf:params:xml:ns:xmpp-sasl" ' + | |
170 'mechanism="X-OAUTH2" auth:service="oauth2" ' + | |
171 'auth:allow-generated-jid="true" ' + | |
172 'auth:client-uses-full-bind-result="true" ' + | |
173 'auth:allow-non-google-login="true" ' + | |
174 'xmlns:auth="http://www.google.com/talk/protocol/auth">' + | |
175 cookie + | |
176 '</auth>'); | |
177 break; | 165 break; |
178 | 166 |
179 case remoting.XmppLoginHandler.State.AUTH_SENT: | 167 case remoting.XmppLoginHandler.State.WAIT_AUTH_RESULT: |
180 if (stanza.localName == 'success') { | 168 if (stanza.localName == 'success') { |
181 this.state_ = remoting.XmppLoginHandler.State.AUTH_ACCEPTED; | 169 this.state_ = |
182 this.startStream_(); | 170 remoting.XmppLoginHandler.State.WAIT_STREAM_HEADER_AFTER_AUTH; |
| 171 this.startStream_( |
| 172 '<iq type="set" id="0">' + |
| 173 '<bind xmlns="urn:ietf:params:xml:ns:xmpp-bind">' + |
| 174 '<resource>chromoting</resource>'+ |
| 175 '</bind>' + |
| 176 '</iq>' + |
| 177 '<iq type="set" id="1">' + |
| 178 '<session xmlns="urn:ietf:params:xml:ns:xmpp-session"/>' + |
| 179 '</iq>'); |
183 } else { | 180 } else { |
184 this.onError_(remoting.Error.AUTHENTICATION_FAILED, | 181 this.onError_(remoting.Error.AUTHENTICATION_FAILED, |
185 'Failed to authenticate: ' + | 182 'Failed to authenticate: ' + |
186 (new XMLSerializer().serializeToString(stanza))); | 183 (new XMLSerializer().serializeToString(stanza))); |
187 } | 184 } |
188 break; | 185 break; |
189 | 186 |
190 case remoting.XmppLoginHandler.State.AUTH_ACCEPTED: | 187 case remoting.XmppLoginHandler.State.WAIT_STREAM_HEADER_AFTER_AUTH: |
191 if (stanza.querySelector('features>bind')) { | 188 if (stanza.querySelector('features>bind')) { |
192 this.sendMessageCallback_( | 189 this.state_ = remoting.XmppLoginHandler.State.WAIT_BIND_RESULT; |
193 '<iq type="set" id="0">' + | |
194 '<bind xmlns="urn:ietf:params:xml:ns:xmpp-bind">' + | |
195 '<resource>chromoting</resource>'+ | |
196 '</bind>' + | |
197 '</iq>'); | |
198 this.state_ = remoting.XmppLoginHandler.State.BIND_SENT; | |
199 } else { | 190 } else { |
200 this.onError_(remoting.Error.UNEXPECTED, | 191 this.onError_(remoting.Error.UNEXPECTED, |
201 "Server doesn't support bind after authentication."); | 192 "Server doesn't support bind after authentication."); |
202 } | 193 } |
203 break; | 194 break; |
204 | 195 |
205 case remoting.XmppLoginHandler.State.BIND_SENT: | 196 case remoting.XmppLoginHandler.State.WAIT_BIND_RESULT: |
206 var jidElement = stanza.querySelector('iq>bind>jid'); | 197 var jidElement = stanza.querySelector('iq>bind>jid'); |
207 if (stanza.getAttribute('id') != '0' || | 198 if (stanza.getAttribute('id') != '0' || |
208 stanza.getAttribute('type') != 'result' || !jidElement) { | 199 stanza.getAttribute('type') != 'result' || !jidElement) { |
209 this.onError_(remoting.Error.UNEXPECTED, | 200 this.onError_(remoting.Error.UNEXPECTED, |
210 'Received unexpected response to bind: ' + | 201 'Received unexpected response to bind: ' + |
211 (new XMLSerializer().serializeToString(stanza))); | 202 (new XMLSerializer().serializeToString(stanza))); |
212 return; | 203 return; |
213 } | 204 } |
214 this.jid_ = jidElement.textContent; | 205 this.jid_ = jidElement.textContent; |
215 this.sendMessageCallback_( | 206 this.state_ = remoting.XmppLoginHandler.State.WAIT_SESSION_IQ_RESULT; |
216 '<iq type="set" id="1">' + | |
217 '<session xmlns="urn:ietf:params:xml:ns:xmpp-session"/>' + | |
218 '</iq>'); | |
219 this.state_ = remoting.XmppLoginHandler.State.SESSION_IQ_SENT; | |
220 break; | 207 break; |
221 | 208 |
222 case remoting.XmppLoginHandler.State.SESSION_IQ_SENT: | 209 case remoting.XmppLoginHandler.State.WAIT_SESSION_IQ_RESULT: |
223 if (stanza.getAttribute('id') != '1' || | 210 if (stanza.getAttribute('id') != '1' || |
224 stanza.getAttribute('type') != 'result') { | 211 stanza.getAttribute('type') != 'result') { |
225 this.onError_(remoting.Error.UNEXPECTED, | 212 this.onError_(remoting.Error.UNEXPECTED, |
226 'Failed to start session: ' + | 213 'Failed to start session: ' + |
227 (new XMLSerializer().serializeToString(stanza))); | 214 (new XMLSerializer().serializeToString(stanza))); |
228 return; | 215 return; |
229 } | 216 } |
230 this.state_ = remoting.XmppLoginHandler.State.DONE; | 217 this.state_ = remoting.XmppLoginHandler.State.DONE; |
231 this.onHandshakeDoneCallback_(this.jid_, this.streamParser_); | 218 this.onHandshakeDoneCallback_(this.jid_, this.streamParser_); |
232 break; | 219 break; |
233 | 220 |
234 default: | 221 default: |
235 base.debug.assert(false); | 222 base.debug.assert(false); |
236 break; | 223 break; |
237 } | 224 } |
238 } | 225 } |
239 | 226 |
240 remoting.XmppLoginHandler.prototype.onTlsStarted = function() { | 227 remoting.XmppLoginHandler.prototype.onTlsStarted = function() { |
241 base.debug.assert(this.state_ == | 228 base.debug.assert(this.state_ == |
242 remoting.XmppLoginHandler.State.STARTING_TLS); | 229 remoting.XmppLoginHandler.State.STARTING_TLS); |
243 this.state_ = remoting.XmppLoginHandler.State.START_SENT_AFTER_TLS; | 230 this.state_ = remoting.XmppLoginHandler.State.WAIT_STREAM_HEADER_AFTER_TLS; |
244 this.startStream_(); | 231 var cookie = window.btoa("\0" + this.username_ + "\0" + this.authToken_); |
| 232 |
| 233 this.startStream_( |
| 234 '<auth xmlns="urn:ietf:params:xml:ns:xmpp-sasl" ' + |
| 235 'mechanism="X-OAUTH2" auth:service="oauth2" ' + |
| 236 'auth:allow-generated-jid="true" ' + |
| 237 'auth:client-uses-full-bind-result="true" ' + |
| 238 'auth:allow-non-google-login="true" ' + |
| 239 'xmlns:auth="http://www.google.com/talk/protocol/auth">' + |
| 240 cookie + |
| 241 '</auth>'); |
245 }; | 242 }; |
246 | 243 |
247 /** | 244 /** |
248 * @param {string} text | 245 * @param {string} text |
249 * @private | 246 * @private |
250 */ | 247 */ |
251 remoting.XmppLoginHandler.prototype.onParserError_ = function(text) { | 248 remoting.XmppLoginHandler.prototype.onParserError_ = function(text) { |
252 this.onError_(remoting.Error.UNEXPECTED, text); | 249 this.onError_(remoting.Error.UNEXPECTED, text); |
253 } | 250 } |
254 | 251 |
255 /** | 252 /** |
| 253 * @param {string} firstMessage Message to send after stream header. |
256 * @private | 254 * @private |
257 */ | 255 */ |
258 remoting.XmppLoginHandler.prototype.startStream_ = function() { | 256 remoting.XmppLoginHandler.prototype.startStream_ = function(firstMessage) { |
259 this.sendMessageCallback_('<stream:stream to="' + this.server_ + | 257 this.sendMessageCallback_('<stream:stream to="' + this.server_ + |
260 '" version="1.0" xmlns="jabber:client" ' + | 258 '" version="1.0" xmlns="jabber:client" ' + |
261 'xmlns:stream="http://etherx.jabber.org/streams">'); | 259 'xmlns:stream="http://etherx.jabber.org/streams">' + |
| 260 firstMessage); |
262 this.streamParser_ = new remoting.XmppStreamParser(); | 261 this.streamParser_ = new remoting.XmppStreamParser(); |
263 this.streamParser_.setCallbacks(this.onStanza_.bind(this), | 262 this.streamParser_.setCallbacks(this.onStanza_.bind(this), |
264 this.onParserError_.bind(this)); | 263 this.onParserError_.bind(this)); |
265 } | 264 } |
266 | 265 |
267 /** | 266 /** |
268 * @param {remoting.Error} error | 267 * @param {remoting.Error} error |
269 * @param {string} text | 268 * @param {string} text |
270 * @private | 269 * @private |
271 */ | 270 */ |
272 remoting.XmppLoginHandler.prototype.onError_ = function(error, text) { | 271 remoting.XmppLoginHandler.prototype.onError_ = function(error, text) { |
273 if (this.state_ != remoting.XmppLoginHandler.State.ERROR) { | 272 if (this.state_ != remoting.XmppLoginHandler.State.ERROR) { |
274 this.onErrorCallback_(error, text); | 273 this.onErrorCallback_(error, text); |
275 this.state_ = remoting.XmppLoginHandler.State.ERROR; | 274 this.state_ = remoting.XmppLoginHandler.State.ERROR; |
276 } else { | 275 } else { |
277 console.error(text); | 276 console.error(text); |
278 } | 277 } |
279 } | 278 } |
OLD | NEW |