-
Notifications
You must be signed in to change notification settings - Fork 49
/
Copy pathxep124.py
440 lines (346 loc) · 15.5 KB
/
xep124.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
from twisted.internet import defer, reactor, task
from twisted.words.xish import xpath
from twisted.python import log
from punjab import httpb_client
import test_basic
class XEP0124TestCase(test_basic.TestCase):
"""
Tests for Punjab compatability with http://www.xmpp.org/extensions/xep-0124.html
"""
def testCreateSession(self):
"""
Test Section 7.1 of BOSH xep : http://www.xmpp.org/extensions/xep-0124.html#session
"""
def _testSessionCreate(res):
self.failUnless(res[0].name=='body', 'Wrong element')
self.failUnless(res[0].hasAttribute('sid'), 'Not session id')
def _error(e):
# This fails on DNS
log.err(e)
BOSH_XML = """<body content='text/xml; charset=utf-8'
hold='1'
rid='1573741820'
to='localhost'
route='xmpp:127.0.0.1:%(server_port)i'
secure='true'
ver='1.6'
wait='60'
ack='1'
xml:lang='en'
xmlns='http://jabber.org/protocol/httpbind'/>
"""% { "server_port": self.server_port }
d = self.proxy.connect(BOSH_XML).addCallback(_testSessionCreate)
d.addErrback(_error)
return d
def testWhiteList(self):
"""
Basic tests for whitelisting domains.
"""
def _testSessionCreate(res):
self.failUnless(res[0].name=='body', 'Wrong element')
self.failUnless(res[0].hasAttribute('sid'), 'Not session id')
def _error(e):
# This fails on DNS
log.err(e)
self.hbs.white_list = ['.localhost']
BOSH_XML = """<body content='text/xml; charset=utf-8'
hold='1'
rid='1573741820'
to='localhost'
route='xmpp:127.0.0.1:%(server_port)i'
secure='true'
ver='1.6'
wait='60'
ack='1'
xml:lang='en'
xmlns='http://jabber.org/protocol/httpbind'/>
"""% { "server_port": self.server_port }
d = self.proxy.connect(BOSH_XML).addCallback(_testSessionCreate)
d.addErrback(_error)
return d
def testWhiteListError(self):
"""
Basic tests for whitelisting domains.
"""
def _testSessionCreate(res):
self.fail("Session should not be created")
def _error(e):
# This is the error we expect.
if isinstance(e.value, ValueError) and e.value.args == (b'400', b'Bad Request'):
return True
# Any other error, including the error raised from _testSessionCreate, should
# be propagated up to the test runner.
return e
self.hbs.white_list = ['test']
BOSH_XML = """<body content='text/xml; charset=utf-8'
hold='1'
rid='1573741820'
to='localhost'
route='xmpp:127.0.0.1:%(server_port)i'
secure='true'
ver='1.6'
wait='60'
ack='1'
xml:lang='en'
xmlns='http://jabber.org/protocol/httpbind'/>
"""% { "server_port": self.server_port }
d = self.proxy.connect(BOSH_XML).addCallback(_testSessionCreate)
d.addErrback(_error)
return d
def testSessionTimeout(self):
"""Test if we timeout correctly
"""
def testTimeout(res):
self.failUnlessEqual(res.value.args[0], b'404')
def testCBTimeout(res):
# check for terminate if we expire
terminate = res[0].getAttribute('type',False)
self.failUnlessEqual(terminate, 'terminate')
def sendTest():
sd = self.send()
sd.addCallback(testCBTimeout)
sd.addErrback(testTimeout)
return sd
def testResend(res):
self.failUnless(res[0].name=='body', 'Wrong element')
s = self.b.service.sessions[self.sid]
self.failUnless(s.inactivity==2,'Wrong inactivity value')
self.failUnless(s.wait==2, 'Wrong wait value')
return task.deferLater(reactor, s.wait+s.inactivity+1, sendTest)
def testSessionCreate(res):
self.failUnless(res[0].name=='body', 'Wrong element')
self.failUnless(res[0].hasAttribute('sid'),'Not session id')
self.sid = res[0]['sid']
# send and wait
sd = self.send()
sd.addCallback(testResend)
return sd
BOSH_XML = """<body content='text/xml; charset=utf-8'
hold='1'
rid='%(rid)i'
to='localhost'
route='xmpp:127.0.0.1:%(server_port)i'
ver='1.6'
wait='2'
ack='1'
inactivity='2'
xml:lang='en'
xmlns='http://jabber.org/protocol/httpbind'/>
"""% { "rid": self.rid, "server_port": self.server_port }
return self.proxy.connect(BOSH_XML).addCallbacks(testSessionCreate)
def testRemoteError(self):
"""
This is to test if we get errors when there are no waiting requests.
"""
def _testStreamError(res):
self.assertEqual(True, isinstance(res.value, httpb_client.HTTPBNetworkTerminated))
self.assertEqual(True, res.value.body_tag.hasAttribute('condition'), 'No attribute condition')
# This is not a stream error because we sent invalid xml
self.assertEqual(res.value.body_tag['condition'], 'remote-stream-error')
self.assertEqual(True, len(res.value.elements)>0)
# The XML should exactly match the error XML sent by triggerStreamError().
self.assertEqual(True,xpath.XPathQuery("/error").matches(res.value.elements[0]))
self.assertEqual(True,xpath.XPathQuery("/error/policy-violation").matches(res.value.elements[0]))
self.assertEqual(True,xpath.XPathQuery("/error/arbitrary-extension").matches(res.value.elements[0]))
self.assertEqual(True,xpath.XPathQuery("/error/text[text() = 'Error text']").matches(res.value.elements[0]))
def _failStreamError(res):
self.fail('A stream error needs to be returned')
def _testSessionCreate(res):
self.sid = res[0]['sid']
# this xml is valid, just for testing
# the point is to wait for a stream error
d = self.send('<fdsfd/>')
d.addCallback(_failStreamError)
d.addErrback(_testStreamError)
self.server_protocol.triggerStreamError()
return d
BOSH_XML = """<body content='text/xml; charset=utf-8'
hold='1'
rid='%(rid)i'
to='localhost'
route='xmpp:127.0.0.1:%(server_port)i'
ver='1.6'
wait='60'
ack='1'
xml:lang='en'
xmlns='http://jabber.org/protocol/httpbind'/>
"""% { "rid": self.rid, "server_port": self.server_port }
d = self.proxy.connect(BOSH_XML).addCallback(_testSessionCreate)
return d
@defer.inlineCallbacks
def testStreamFlushOnError(self):
"""
Test that messages included in a <body type='terminate'> message from the
client are sent to the server before terminating.
"""
yield self.connect(self.get_body_node(connect=True))
# Set got_testing_node to true when the XMPP server receives the <testing/> we
# send below.
got_testing_node = [False] # work around Python's 2.6 lack of nonlocal
wait = defer.Deferred()
def received_testing(a):
got_testing_node[0] = True
wait.callback(True)
self.server_protocol.addObserver("/testing", received_testing)
# Ensure that we always remove the received_testing listener.
try:
# Send <body type='terminate'><testing/></body>. This should result in a
# HTTPBNetworkTerminated exception.
try:
yield self.proxy.send(self.get_body_node(ext='<testing/>', type='terminate'))
except httpb_client.HTTPBNetworkTerminated as e:
self.failUnlessEqual(e.body_tag.getAttribute('condition', None), None)
# Wait until <testing/> is actually received by the XMPP server. The previous
# request completing only means that the proxy has received the stanza, not that
# it's been delivered to the XMPP server.
yield wait
finally:
self.server_protocol.removeObserver("/testing", received_testing)
# This should always be true, or we'd never have woken up from wait.
self.assertEqual(True,got_testing_node[0])
@defer.inlineCallbacks
def testTerminateRace(self):
"""Test that buffered messages are flushed when the connection is terminated."""
yield self.connect(self.get_body_node(connect=True))
def log_observer(event):
self.failIf(event['isError'], event)
log.addObserver(log_observer)
# Simultaneously cause a stream error (server->client closed) and send a terminate
# from the client to the server. Both sides are closing the connection at once.
# Make sure the connection closes cleanly without logging any errors ("Unhandled
# Error"), and the client receives a terminate in response.
try:
self.server_protocol.triggerStreamError()
yield self.proxy.send(self.get_body_node(type='terminate'))
except httpb_client.HTTPBNetworkTerminated as e:
self.assertEqual(e.body_tag.getAttribute('condition', None), 'remote-stream-error')
finally:
log.removeObserver(log_observer)
@defer.inlineCallbacks
def testStreamKeying1(self):
"""Test that connections succeed when stream keying is active."""
yield self.connect(self.get_body_node(connect=True, useKey=True))
yield self.proxy.send(self.get_body_node(useKey=True))
yield self.proxy.send(self.get_body_node(useKey=True))
@defer.inlineCallbacks
def testStreamKeying2(self):
"""Test that 404 is received if stream keying is active and no key is supplied."""
yield self.connect(self.get_body_node(connect=True, useKey=True))
try:
yield self.proxy.send(self.get_body_node(useKey=False))
except httpb_client.HTTPBNetworkTerminated as e:
self.failUnlessEqual(e.body_tag.getAttribute('condition', None), 'item-not-found')
else:
self.fail("Expected 404 Not Found")
@defer.inlineCallbacks
def testStreamKeying3(self):
"""Test that 404 is received if stream keying is active and an invalid key is supplied."""
yield self.connect(self.get_body_node(connect=True, useKey=True))
try:
yield self.proxy.send(self.get_body_node(useKey=True, key='0'*40))
except httpb_client.HTTPBNetworkTerminated as e:
self.failUnlessEqual(e.body_tag.getAttribute('condition', None), 'item-not-found')
else:
self.fail("Expected 404 Not Found")
@defer.inlineCallbacks
def testStreamKeying4(self):
"""Test that 404 is received if we supply a key on a connection without active keying."""
yield self.connect(self.get_body_node(connect=True, useKey=False))
try:
yield self.proxy.send(self.get_body_node(key='0'*40))
except httpb_client.HTTPBNetworkTerminated as e:
self.failUnlessEqual(e.body_tag.getAttribute('condition', None), 'item-not-found')
else:
self.fail("Expected 404 Not Found")
@defer.inlineCallbacks
def testStreamKeying5(self):
"""Test rekeying."""
yield self.connect(self.get_body_node(connect=True, useKey=True))
yield self.proxy.send(self.get_body_node(useKey=True))
# Erase all but the last key to force a rekeying.
self.keys.k = [self.keys.k[-1]]
yield self.proxy.send(self.get_body_node(useKey=True))
yield self.proxy.send(self.get_body_node(useKey=True))
def testStreamParseError(self):
"""
Test that remote-connection-failed is received when the proxy receives invalid XML
from the XMPP server.
"""
def _testStreamError(res):
self.assertEqual(True, isinstance(res.value, httpb_client.HTTPBNetworkTerminated))
self.assertEqual(res.value.body_tag.getAttribute('condition', None), 'remote-connection-failed')
def _failStreamError(res):
self.fail('Expected a remote-connection-failed error')
def _testSessionCreate(res):
self.sid = res[0]['sid']
self.server_protocol.triggerInvalidXML()
return self.send().addCallbacks(_failStreamError, _testStreamError)
return self.proxy.connect(self.get_body_node(connect=True)).addCallback(_testSessionCreate)
def testFeaturesError(self):
"""
This is to test if we get stream features and NOT twice
"""
def _testError(res):
self.assertEqual(True,res[1][0].name=='challenge','Did not get correct challenge stanza')
def _testSessionCreate(res):
self.sid = res[0]['sid']
# this xml is valid, just for testing
# the point is to wait for a stream error
self.assertEqual(True,res[1][0].name=='features','Did not get initial features')
d = self.send("<auth xmlns='urn:ietf:params:xml:ns:xmpp-sasl' mechanism='DIGEST-MD5'/>")
d.addCallback(_testError)
reactor.callLater(1.1, self.server_protocol.triggerChallenge)
return d
BOSH_XML = """<body content='text/xml; charset=utf-8'
hold='1'
rid='%(rid)i'
to='localhost'
route='xmpp:127.0.0.1:%(server_port)i'
ver='1.6'
wait='15'
ack='1'
xml:lang='en'
xmlns='http://jabber.org/protocol/httpbind'/>
"""% { "rid": self.rid, "server_port": self.server_port }
self.server_factory.protocol.delay_features = 3
d = self.proxy.connect(BOSH_XML).addCallback(_testSessionCreate)
# NOTE : to trigger this bug there needs to be 0 waiting requests.
return d
def testRidCountBug(self):
"""
This is to test if rid becomes off on resends
"""
@defer.inlineCallbacks
def _testError(res):
self.assertEqual(res[1][0].name, 'challenge','Did not get correct challenge stanza')
for r in range(5):
# send auth to bump up rid
res = yield self.send("<auth xmlns='urn:ietf:params:xml:ns:xmpp-sasl' mechanism='DIGEST-MD5'/>")
# resend auth
for r in range(5):
res = yield self.resend("<auth xmlns='urn:ietf:params:xml:ns:xmpp-sasl' mechanism='DIGEST-MD5'/>")
res = yield self.resend("<auth xmlns='urn:ietf:params:xml:ns:xmpp-sasl' mechanism='DIGEST-MD5'/>")
def _testSessionCreate(res):
self.sid = res[0]['sid']
# this xml is valid, just for testing
# the point is to wait for a stream error
self.assertEqual(res[1][0].name, 'features','Did not get initial features')
d = self.send("<auth xmlns='urn:ietf:params:xml:ns:xmpp-sasl' mechanism='DIGEST-MD5'/>")
d.addCallback(_testError)
reactor.callLater(1, self.server_protocol.triggerChallenge)
return d
BOSH_XML = """<body content='text/xml; charset=utf-8'
hold='1'
rid='%(rid)i'
to='localhost'
route='xmpp:127.0.0.1:%(server_port)i'
ver='1.6'
wait='3'
ack='1'
xml:lang='en'
xmlns='http://jabber.org/protocol/httpbind'/>
"""% { "rid": self.rid, "server_port": self.server_port }
self.server_factory.protocol.delay_features = 10
d = self.proxy.connect(BOSH_XML).addCallback(_testSessionCreate)
# NOTE : to trigger this bug there needs to be 0 waiting requests.
return d