synapse 2.152.0__py311-none-any.whl → 2.154.0__py311-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of synapse might be problematic. Click here for more details.

Files changed (87) hide show
  1. synapse/axon.py +19 -16
  2. synapse/cortex.py +203 -15
  3. synapse/exc.py +0 -2
  4. synapse/lib/ast.py +42 -23
  5. synapse/lib/autodoc.py +2 -2
  6. synapse/lib/cache.py +16 -1
  7. synapse/lib/cell.py +5 -5
  8. synapse/lib/httpapi.py +198 -2
  9. synapse/lib/layer.py +5 -2
  10. synapse/lib/modelrev.py +36 -3
  11. synapse/lib/node.py +2 -5
  12. synapse/lib/parser.py +1 -1
  13. synapse/lib/schemas.py +51 -0
  14. synapse/lib/snap.py +10 -0
  15. synapse/lib/storm.lark +24 -4
  16. synapse/lib/storm.py +98 -19
  17. synapse/lib/storm_format.py +1 -1
  18. synapse/lib/stormhttp.py +11 -4
  19. synapse/lib/stormlib/auth.py +16 -2
  20. synapse/lib/stormlib/backup.py +1 -0
  21. synapse/lib/stormlib/basex.py +2 -0
  22. synapse/lib/stormlib/cell.py +7 -0
  23. synapse/lib/stormlib/compression.py +3 -0
  24. synapse/lib/stormlib/cortex.py +1168 -0
  25. synapse/lib/stormlib/ethereum.py +1 -0
  26. synapse/lib/stormlib/graph.py +2 -0
  27. synapse/lib/stormlib/hashes.py +5 -0
  28. synapse/lib/stormlib/hex.py +6 -0
  29. synapse/lib/stormlib/infosec.py +6 -1
  30. synapse/lib/stormlib/ipv6.py +1 -0
  31. synapse/lib/stormlib/iters.py +58 -1
  32. synapse/lib/stormlib/json.py +5 -0
  33. synapse/lib/stormlib/mime.py +1 -0
  34. synapse/lib/stormlib/model.py +19 -3
  35. synapse/lib/stormlib/modelext.py +1 -0
  36. synapse/lib/stormlib/notifications.py +2 -0
  37. synapse/lib/stormlib/pack.py +2 -0
  38. synapse/lib/stormlib/random.py +1 -0
  39. synapse/lib/stormlib/smtp.py +0 -7
  40. synapse/lib/stormlib/stats.py +223 -0
  41. synapse/lib/stormlib/stix.py +8 -0
  42. synapse/lib/stormlib/storm.py +1 -0
  43. synapse/lib/stormlib/version.py +3 -0
  44. synapse/lib/stormlib/xml.py +3 -0
  45. synapse/lib/stormlib/yaml.py +2 -0
  46. synapse/lib/stormtypes.py +250 -170
  47. synapse/lib/trigger.py +180 -4
  48. synapse/lib/types.py +1 -1
  49. synapse/lib/version.py +2 -2
  50. synapse/lib/view.py +55 -6
  51. synapse/models/inet.py +21 -6
  52. synapse/models/orgs.py +48 -2
  53. synapse/models/risk.py +126 -2
  54. synapse/models/syn.py +6 -0
  55. synapse/tests/files/stormpkg/badapidef.yaml +13 -0
  56. synapse/tests/files/stormpkg/storm/modules/apimod +10 -0
  57. synapse/tests/files/stormpkg/testpkg.yaml +23 -0
  58. synapse/tests/test_axon.py +7 -2
  59. synapse/tests/test_cortex.py +231 -35
  60. synapse/tests/test_lib_ast.py +138 -43
  61. synapse/tests/test_lib_autodoc.py +1 -1
  62. synapse/tests/test_lib_modelrev.py +9 -0
  63. synapse/tests/test_lib_node.py +55 -0
  64. synapse/tests/test_lib_storm.py +14 -1
  65. synapse/tests/test_lib_stormhttp.py +65 -6
  66. synapse/tests/test_lib_stormlib_auth.py +12 -3
  67. synapse/tests/test_lib_stormlib_cortex.py +1327 -0
  68. synapse/tests/test_lib_stormlib_iters.py +116 -0
  69. synapse/tests/test_lib_stormlib_stats.py +187 -0
  70. synapse/tests/test_lib_stormlib_storm.py +8 -0
  71. synapse/tests/test_lib_stormsvc.py +24 -1
  72. synapse/tests/test_lib_stormtypes.py +124 -69
  73. synapse/tests/test_lib_trigger.py +315 -0
  74. synapse/tests/test_lib_view.py +1 -2
  75. synapse/tests/test_model_base.py +26 -0
  76. synapse/tests/test_model_inet.py +22 -0
  77. synapse/tests/test_model_orgs.py +28 -0
  78. synapse/tests/test_model_risk.py +73 -0
  79. synapse/tests/test_tools_autodoc.py +25 -0
  80. synapse/tests/test_tools_genpkg.py +9 -3
  81. synapse/tests/utils.py +39 -0
  82. synapse/tools/autodoc.py +42 -2
  83. {synapse-2.152.0.dist-info → synapse-2.154.0.dist-info}/METADATA +2 -2
  84. {synapse-2.152.0.dist-info → synapse-2.154.0.dist-info}/RECORD +87 -79
  85. {synapse-2.152.0.dist-info → synapse-2.154.0.dist-info}/WHEEL +1 -1
  86. {synapse-2.152.0.dist-info → synapse-2.154.0.dist-info}/LICENSE +0 -0
  87. {synapse-2.152.0.dist-info → synapse-2.154.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,1327 @@
1
+ import json
2
+
3
+ import aiohttp
4
+
5
+ import synapse.common as s_common
6
+ import synapse.exc as s_exc
7
+
8
+ import synapse.tests.utils as s_test
9
+
10
+ from pprint import pprint
11
+
12
+ class CortexLibTest(s_test.SynTest):
13
+
14
+ async def test_libcortex_httpapi_methods(self):
15
+
16
+ async with self.getTestCore() as core:
17
+ udef = await core.addUser('lowuser')
18
+ lowuser = udef.get('iden')
19
+ await core.setUserPasswd(core.auth.rootuser.iden, 'root')
20
+ await core.setUserPasswd(lowuser, 'secret')
21
+ addr, hport = await core.addHttpsPort(0)
22
+
23
+ # Define a handler. Add storm for each METHOD we support
24
+ q = '''
25
+ $api = $lib.cortex.httpapi.add(testpath00)
26
+ $api.methods.get = ${
27
+ $data = ({'method': 'get'})
28
+ $headers = ({'Secret-Header': 'OhBoy!'})
29
+ $request.reply(200, headers=$headers, body=$data)
30
+ }
31
+ $api.methods.post = ${
32
+ $data = ({'method': 'post'})
33
+ $headers = ({'Secret-Header': 'OhBoy!'})
34
+ $request.reply(201, headers=$headers, body=$data)
35
+ }
36
+ $api.methods.put = ${
37
+ $data = ({'method': 'put'})
38
+ $headers = ({'Secret-Header': 'OhBoy!'})
39
+ $request.reply(202, headers=$headers, body=$data)
40
+ }
41
+ $api.methods.patch = ${
42
+ $data = ({'method': 'patch'})
43
+ $headers = ({'Secret-Header': 'OhBoy!'})
44
+ $request.reply(203, headers=$headers, body=$data)
45
+ }
46
+ $api.methods.options = ${
47
+ $headers = ({'Secret-Header': 'Options', 'Content-Type': 'application/json'})
48
+ $request.reply(204, headers=$headers)
49
+ }
50
+ $api.methods.delete = ${
51
+ $data = ({'method': 'delete'})
52
+ $headers = ({'Secret-Header': 'OhBoy!'})
53
+ $request.reply(205, headers=$headers, body=$data)
54
+ }
55
+ $api.methods.head = ${
56
+ $headers = ({'Secret-Header': 'Head'})
57
+ $request.reply(206, headers=$headers, body=({"no":"body"}))
58
+ }
59
+
60
+ return ( $api.iden )
61
+ '''
62
+ testpath00 = await core.callStorm(q)
63
+
64
+ q = '''
65
+ $api = $lib.cortex.httpapi.add(nomeths)
66
+ return ( $api.iden )
67
+ '''
68
+ nomeths = await core.callStorm(q)
69
+
70
+ info = await core.callStorm('return( $lib.cortex.httpapi.get($iden).pack() )',
71
+ opts={'vars': {'iden': testpath00}})
72
+ self.eq(info.get('iden'), testpath00)
73
+
74
+ with self.raises(s_exc.BadSyntax):
75
+ q = '$api = $lib.cortex.httpapi.get($iden) $api.methods.get = "| | | "'
76
+ await core.callStorm(q, opts={'vars': {'iden': testpath00}})
77
+
78
+ with self.raises(s_exc.NoSuchName):
79
+ q = '$api = $lib.cortex.httpapi.get($iden) $api.methods.trace = ${}'
80
+ await core.callStorm(q, opts={'vars': {'iden': testpath00}})
81
+
82
+ with self.raises(s_exc.NoSuchName):
83
+ q = '$api = $lib.cortex.httpapi.get($iden) $api.methods.connect = ${}'
84
+ await core.callStorm(q, opts={'vars': {'iden': testpath00}})
85
+
86
+ with self.raises(s_exc.NoSuchName):
87
+ q = '$api = $lib.cortex.httpapi.get($iden) $api.methods.wildycustomverb = ${}'
88
+ await core.callStorm(q, opts={'vars': {'iden': testpath00}})
89
+
90
+ async with self.getHttpSess(auth=('root', 'root'), port=hport) as sess: # type: aiohttp.ClientSession
91
+ resp = await sess.get(f'https://localhost:{hport}/api/ext/testpath00')
92
+ self.eq(resp.status, 200)
93
+ data = await resp.json()
94
+ self.eq(data.get('method'), 'get')
95
+
96
+ resp = await sess.post(f'https://localhost:{hport}/api/ext/testpath00')
97
+ self.eq(resp.status, 201)
98
+ data = await resp.json()
99
+ self.eq(data.get('method'), 'post')
100
+
101
+ resp = await sess.put(f'https://localhost:{hport}/api/ext/testpath00')
102
+ self.eq(resp.status, 202)
103
+ data = await resp.json()
104
+ self.eq(data.get('method'), 'put')
105
+
106
+ resp = await sess.patch(f'https://localhost:{hport}/api/ext/testpath00')
107
+ self.eq(resp.status, 203)
108
+ data = await resp.json()
109
+ self.eq(data.get('method'), 'patch')
110
+
111
+ resp = await sess.options(f'https://localhost:{hport}/api/ext/testpath00')
112
+ self.eq(resp.status, 204)
113
+ self.eq(resp.headers.get('Secret-Header'), 'Options')
114
+ # HTTP 204 code has no response content per rfc9110
115
+ self.eq(await resp.read(), b'')
116
+
117
+ resp = await sess.delete(f'https://localhost:{hport}/api/ext/testpath00')
118
+ self.eq(resp.status, 205)
119
+ data = await resp.json()
120
+ self.eq(data.get('method'), 'delete')
121
+
122
+ resp = await sess.head(f'https://localhost:{hport}/api/ext/testpath00')
123
+ self.eq(resp.status, 206)
124
+ self.eq(resp.headers.get('Secret-Header'), 'Head')
125
+ self.eq(resp.headers.get('Content-Length'), '14')
126
+ # HEAD had no body in its response
127
+ self.eq(await resp.read(), b'')
128
+
129
+ # exercise _gtors
130
+ q = '''$api = $lib.cortex.httpapi.get($iden)
131
+ $ret = ({
132
+ "get": $api.methods.get,
133
+ "put": $api.methods.put,
134
+ "head": $api.methods.head,
135
+ "post": $api.methods.post,
136
+ "patch": $api.methods.patch,
137
+ "delete": $api.methods.delete,
138
+ "options": $api.methods.options,
139
+ })
140
+ return ($ret)
141
+ '''
142
+ queries = await core.callStorm(q, opts={'vars': {'iden': testpath00}})
143
+ self.len(7, queries)
144
+
145
+ # Stat the api to enumerate all its method _gtors
146
+ msgs = await core.stormlist('cortex.httpapi.stat $iden', opts={'vars': {'iden': testpath00}})
147
+ self.stormIsInPrint(f'Iden: {testpath00}', msgs)
148
+ self.stormIsInPrint('Method: GET', msgs)
149
+ self.stormIsInPrint('Method: PUT', msgs)
150
+ self.stormIsInPrint('Method: POST', msgs)
151
+ self.stormIsInPrint('Method: PATCH', msgs)
152
+ self.stormIsInPrint('Method: OPTIONS', msgs)
153
+ self.stormIsInPrint('Method: DELETE', msgs)
154
+ self.stormIsInPrint('Method: HEAD', msgs)
155
+
156
+ # Unset a method and try to use it
157
+ q = '$api = $lib.cortex.httpapi.get($iden) $api.methods.post = $lib.undef'
158
+ await core.callStorm(q, opts={'vars': {'iden': testpath00}})
159
+ resp = await sess.post(f'https://localhost:{hport}/api/ext/testpath00')
160
+ self.eq(resp.status, 405)
161
+ self.eq(resp.headers.get('Allowed'), 'GET, PUT, PATCH, OPTIONS, DELETE, HEAD')
162
+ data = await resp.json()
163
+ self.eq(data.get('mesg'), f'Extended HTTP API {testpath00} has no method for POST. Supports GET, PUT, PATCH, OPTIONS, DELETE, HEAD.')
164
+
165
+ # Unsetting a HEAD method and calling it yields a 500
166
+ # but still has no body in the response.
167
+ q = '$api = $lib.cortex.httpapi.get($iden) $api.methods.head = $lib.undef'
168
+ await core.callStorm(q, opts={'vars': {'iden': testpath00}})
169
+ resp = await sess.head(f'https://localhost:{hport}/api/ext/testpath00')
170
+ self.eq(resp.status, 405)
171
+ self.eq(resp.headers.get('Allowed'), 'GET, PUT, PATCH, OPTIONS, DELETE')
172
+ self.eq(await resp.read(), b'')
173
+
174
+ # No methods returns a 405 and nothing allowed
175
+ resp = await sess.get(f'https://localhost:{hport}/api/ext/nomeths')
176
+ self.eq(resp.status, 405)
177
+ self.eq(resp.headers.get('Allowed'), '')
178
+ data = await resp.json()
179
+ self.eq(data.get('mesg'), f'Extended HTTP API {nomeths} has no method for GET.')
180
+
181
+ msgs = await core.stormlist('cortex.httpapi.stat $iden', opts={'vars': {'iden': testpath00}})
182
+ self.stormNotInPrint('Method: POST', msgs)
183
+ self.stormNotInPrint('Method: HEAD', msgs)
184
+ self.stormIsInPrint('Method: GET', msgs)
185
+ self.stormIsInPrint('Method: PUT', msgs)
186
+ self.stormIsInPrint('Method: PATCH', msgs)
187
+ self.stormIsInPrint('Method: OPTIONS', msgs)
188
+ self.stormIsInPrint('Method: DELETE', msgs)
189
+
190
+ q = '''$api = $lib.cortex.httpapi.add(testpath01)
191
+ $api.methods.get = ${ $request.reply(200, body=({"hehe": "haha"})) }
192
+ return ( $api.iden )'''
193
+ testpath01 = await core.callStorm(q)
194
+ resp = await sess.get(f'https://localhost:{hport}/api/ext/testpath01')
195
+ self.eq(resp.status, 200)
196
+ data = await resp.json()
197
+ self.eq(data.get('hehe'), 'haha')
198
+
199
+ q = '$lib.cortex.httpapi.del($iden)'
200
+ msgs = await core.stormlist(q, opts={'vars': {'iden': testpath01}})
201
+ self.stormHasNoWarnErr(msgs)
202
+
203
+ resp = await sess.get(f'https://localhost:{hport}/api/ext/testpath01')
204
+ self.eq(resp.status, 404)
205
+ data = await resp.json()
206
+ self.eq(data.get('code'), 'NoSuchPath')
207
+ self.eq(data.get('mesg'), 'No Extended HTTP API endpoint matches testpath01')
208
+
209
+ # Test method reply types
210
+ q = '''$api = $lib.cortex.httpapi.add(testreply)
211
+ $api.methods.post = ${ $request.reply(200, body=$request.json.data) }
212
+ return ( $api.iden )'''
213
+ testreply = await core.callStorm(q)
214
+ edata = ('hello',
215
+ 31337,
216
+ ['a', 2],
217
+ {'key': 'valu', 'hehe': [1, '2']},
218
+ None,
219
+ )
220
+ for valu in edata:
221
+ resp = await sess.post(f'https://localhost:{hport}/api/ext/testreply',
222
+ json={'data': valu}
223
+ )
224
+ self.eq(resp.status, 200)
225
+ data = await resp.json()
226
+ self.eq(data, valu)
227
+
228
+ # Echo handler returns the request data.
229
+ # This also shows some flattening of request data we do.
230
+
231
+ q = '''$api = $lib.cortex.httpapi.add('echo/(.*)/([a-zA-Z0-9]*)?')
232
+ $api.methods.get = ${
233
+ $data = ({
234
+ "echo": $lib.true,
235
+ "method": $request.method,
236
+ "headers": $request.headers,
237
+ "params": $request.params,
238
+ "uri": $request.uri,
239
+ "path": $request.path,
240
+ "client": $request.client,
241
+ "args": $request.args,
242
+ })
243
+ try {
244
+ $data.json = $request.json
245
+ } catch StormRuntimeError as err {
246
+ $data.json = "err"
247
+ }
248
+ $headers = ({'Echo': 'hehe!'})
249
+ $request.reply(200, headers=$headers, body=$data)
250
+ }
251
+ return ( $api.iden )
252
+ '''
253
+ echoiden = await core.callStorm(q)
254
+
255
+ url = f'https://lowuser:secret@localhost:{hport}/api/ext/echo/sup/?echo=test&giggle=haha&echo=eggs'
256
+ resp = await sess.get(url, headers=(('hehe', 'haha'), ('apikey', 'secret'), ('hehe', 'badjoke')),
257
+ json={'look': 'at this!'},
258
+ )
259
+ self.eq(resp.status, 200)
260
+ data = await resp.json()
261
+ self.eq(data.get('args'), ['sup', ''])
262
+ self.eq(data.get('echo'), True)
263
+ self.eq(data.get('headers').get('hehe'), 'haha')
264
+ self.eq(data.get('headers').get('apikey'), 'secret')
265
+ self.eq(data.get('json'), {'look': 'at this!'})
266
+ self.eq(data.get('method'), 'GET')
267
+ self.eq(data.get('params'), {'echo': 'test', 'giggle': 'haha'})
268
+ self.eq(data.get('path'), 'echo/sup/')
269
+ self.isin('client', data)
270
+ self.eq(data.get('uri'), '/api/ext/echo/sup/?echo=test&giggle=haha&echo=eggs')
271
+
272
+ url = f'https://lowuser:secret@localhost:{hport}/api/ext/echo/words/wOw'
273
+ resp = await sess.get(url, headers=(('hehe', 'haha'), ('apikey', 'secret'), ('hehe', 'badjoke')),
274
+ data=b'hehe',
275
+ )
276
+ self.eq(resp.status, 200)
277
+ data = await resp.json()
278
+ self.eq(data.get('args'), ['words', 'wOw'])
279
+ self.eq(data.get('json'), 'err')
280
+ self.eq(data.get('path'), 'echo/words/wOw')
281
+
282
+ # Sad paths on the $request methods
283
+ q = '''$api = $lib.cortex.httpapi.add(testpath02)
284
+ $api.methods.get = ${ $request.sendcode(200) $request.sendheaders('beep beep') }
285
+ return ( $api.iden )'''
286
+ testpath02 = await core.callStorm(q)
287
+
288
+ resp = await sess.get(f'https://localhost:{hport}/api/ext/testpath02')
289
+ self.eq(resp.status, 500)
290
+ data = await resp.json()
291
+ self.eq(data.get('code'), 'BadArg')
292
+ self.eq(data.get('mesg'), 'HTTP Response headers must be a dictionary, got str.')
293
+
294
+ q = '''$api = $lib.cortex.httpapi.add(testpath03)
295
+ $api.methods.get = ${ $request.sendcode(200) $request.sendbody('beep beep') }
296
+ return ( $api.iden )'''
297
+ testpath03 = await core.callStorm(q)
298
+
299
+ resp = await sess.get(f'https://localhost:{hport}/api/ext/testpath03')
300
+ self.eq(resp.status, 500)
301
+ data = await resp.json()
302
+ self.eq(data.get('code'), 'BadArg')
303
+ self.eq(data.get('mesg'), 'HTTP Response body must be bytes, got str.')
304
+
305
+ q = '''$api = $lib.cortex.httpapi.add(testpath04)
306
+ $api.methods.get = ${ $request.reply(200, body=({"hehe": "yes!"})) $request.reply(201, body=({"hehe":" newp"})) }
307
+ return ( $api.iden )'''
308
+ test04 = await core.callStorm(q)
309
+
310
+ emsg = f'Error executing custom HTTP API {test04}: BadArg Response.reply() has already been called.'
311
+ with self.getAsyncLoggerStream('synapse.lib.httpapi', emsg) as stream:
312
+ resp = await sess.get(f'https://localhost:{hport}/api/ext/testpath04')
313
+ self.eq(resp.status, 200)
314
+ self.eq(await resp.json(), {'hehe': 'yes!'})
315
+
316
+ async def test_libcortex_httpapi_runas_owner(self):
317
+ async with self.getTestCore() as core:
318
+ udef = await core.addUser('lowuser')
319
+ lowuser = udef.get('iden')
320
+ await core.setUserPasswd(core.auth.rootuser.iden, 'root')
321
+ await core.setUserPasswd(lowuser, 'secret')
322
+ addr, hport = await core.addHttpsPort(0)
323
+
324
+ q = '''$api = $lib.cortex.httpapi.add(testpath00)
325
+ $api.methods.get = ${
326
+ $view = $lib.view.get()
327
+ $request.reply(200, body=({'view': $view.iden, "username": $lib.user.name()}) )
328
+ }
329
+ return ( ($api.iden, $api.owner.name) )
330
+ '''
331
+ iden, uname = await core.callStorm(q)
332
+ self.eq(uname, 'root')
333
+
334
+ async with self.getHttpSess(auth=('root', 'root'), port=hport) as sess: # type: aiohttp.ClientSession
335
+ resp = await sess.get(f'https://localhost:{hport}/api/ext/testpath00')
336
+ self.eq(resp.status, 200)
337
+ data = await resp.json()
338
+ self.eq(data.get('username'), 'root')
339
+
340
+ q = '''$api=$lib.cortex.httpapi.get($http_iden)
341
+ $user=$lib.auth.users.byname(lowuser) $api.owner = $user
342
+ return ($api.owner.name)'''
343
+ name = await core.callStorm(q, opts={'vars': {'http_iden': iden}})
344
+ self.eq(name, 'lowuser')
345
+
346
+ resp = await sess.get(f'https://localhost:{hport}/api/ext/testpath00')
347
+ self.eq(resp.status, 200)
348
+ data = await resp.json()
349
+ self.eq(data.get('username'), 'lowuser')
350
+
351
+ q = '''
352
+ $api=$lib.cortex.httpapi.get($http_iden)
353
+ $api.runas = user
354
+ return ($api.runas)'''
355
+ name = await core.callStorm(q, opts={'vars': {'http_iden': iden}})
356
+ self.eq(name, 'user')
357
+
358
+ resp = await sess.get(f'https://localhost:{hport}/api/ext/testpath00')
359
+ self.eq(resp.status, 200)
360
+ data = await resp.json()
361
+ self.eq(data.get('username'), 'root')
362
+
363
+ async with self.getHttpSess(auth=('lowuser', 'secret'), port=hport) as sess: # type: aiohttp.ClientSession
364
+ resp = await sess.get(f'https://localhost:{hport}/api/ext/testpath00')
365
+ self.eq(resp.status, 200)
366
+ data = await resp.json()
367
+ self.eq(data.get('username'), 'lowuser')
368
+
369
+ # Set the user by iden
370
+ q = '''$api=$lib.cortex.httpapi.get($http_iden)
371
+ $user=$lib.auth.users.byname(root) $api.owner = $user.iden
372
+ return ($api.owner.name)'''
373
+ name = await core.callStorm(q, opts={'vars': {'http_iden': iden}})
374
+ self.eq(name, 'root')
375
+
376
+ async def test_libcortex_httpapi_order_stat(self):
377
+
378
+ async with self.getTestCore() as core:
379
+ udef = await core.addUser('lowuser')
380
+ lowuser = udef.get('iden')
381
+ await core.setUserPasswd(core.auth.rootuser.iden, 'root')
382
+ await core.setUserPasswd(lowuser, 'secret')
383
+ addr, hport = await core.addHttpsPort(0)
384
+
385
+ # Define a few handlers
386
+ q = '''
387
+ $api = $lib.cortex.httpapi.add('hehe/([a-z0-9]*)')
388
+ $api.methods.get = ${ $request.reply(200, headers=({"yup": "wildcard"}), body=({"path": $request.path})) }
389
+ $api.methods.head = ${ $request.reply(200, headers=({"yup": "wildcard"})) }
390
+ $api.name = 'the hehe wildcard handler'
391
+ $api.desc = 'wildcard words'
392
+ $api.runas = owner
393
+ return ( $api.iden )
394
+ '''
395
+ iden0 = await core.callStorm(q)
396
+
397
+ q = '''
398
+ $api = $lib.cortex.httpapi.add('hehe/haha')
399
+ $api.methods.get = ${ $request.reply(200, headers=({"yup": "hehe/haha"}), body=({"path": $request.path})) }
400
+ $api.methods.head = ${ $request.reply(200, headers=({"yup": "hehe/haha"}) ) }
401
+ $api.name = 'the hehe/haha handler'
402
+ $api.desc = 'beep boop zoop robot captain'
403
+ $api.runas = user
404
+ $api.perms = (
405
+ ({"perm": ["hehe", "haha"]}),
406
+ ({"perm": ["some", "thing"], "default": $lib.true}),
407
+ )
408
+ return ( $api.iden )
409
+ '''
410
+ iden1 = await core.callStorm(q)
411
+
412
+ q = '''
413
+ $api = $lib.cortex.httpapi.add('hehe')
414
+ $api.methods.get = ${ $request.reply(200, headers=({"yup": "hehe"}), body=({"path": $request.path})) }
415
+ $api.methods.head = ${ $request.reply(200, headers=({"yup": "hehe"})) }
416
+ $api.authenticated = $lib.false
417
+ $api.owner = $lowuser
418
+ return ( $api.iden )
419
+ '''
420
+ iden2 = await core.callStorm(q, opts={'vars': {'lowuser': lowuser}})
421
+
422
+ q = '''
423
+ $api = $lib.cortex.httpapi.add('wow')
424
+ $api.methods.get = ${ $request.reply(200, body=({"path": $request.path})) }
425
+ $api.authenticated = $lib.false
426
+ $api.vars.hehe = wow
427
+ $api.vars.items = (1, 2, (3) )
428
+ return ( $api.iden )
429
+ '''
430
+ iden3 = await core.callStorm(q)
431
+
432
+ msgs = await core.stormlist('cortex.httpapi.list')
433
+ self.stormIsInPrint(f'0 {iden0}', msgs)
434
+ self.stormIsInPrint(f'1 {iden1}', msgs)
435
+ self.stormIsInPrint(f'2 {iden2}', msgs)
436
+ self.stormIsInPrint(f'3 {iden3}', msgs)
437
+
438
+ # Order matters. The hehe/haha path occurs after the wildcard.
439
+ async with self.getHttpSess(auth=('root', 'root'), port=hport) as sess:
440
+ resp = await sess.get(f'https://localhost:{hport}/api/ext/hehe/haha')
441
+ data = await resp.json()
442
+ self.eq(data.get('path'), 'hehe/haha')
443
+ self.eq(resp.headers.get('yup'), 'wildcard')
444
+
445
+ resp = await sess.get(f'https://localhost:{hport}/api/ext/hehe/ohmy1234')
446
+ data = await resp.json()
447
+ self.eq(data.get('path'), 'hehe/ohmy1234')
448
+ self.eq(resp.headers.get('yup'), 'wildcard')
449
+
450
+ resp = await sess.get(f'https://localhost:{hport}/api/ext/hehe')
451
+ data = await resp.json()
452
+ self.eq(data.get('path'), 'hehe')
453
+ self.eq(resp.headers.get('yup'), 'hehe')
454
+
455
+ # Move the wildcard handler after the more specific handler
456
+ msgs = await core.stormlist('cortex.httpapi.index $iden 1', opts={'vars': {'iden': iden0}})
457
+ self.stormIsInPrint(f'Set HTTP API {iden0} to index 1', msgs)
458
+
459
+ msgs = await core.stormlist('cortex.httpapi.list')
460
+ self.stormIsInPrint(f'0 {iden1}', msgs)
461
+ self.stormIsInPrint(f'1 {iden0}', msgs)
462
+ self.stormIsInPrint(f'2 {iden2}', msgs)
463
+ self.stormIsInPrint(f'3 {iden3}', msgs)
464
+
465
+ # The wildcard handler does not match the more specific request as a result of the new order
466
+ async with self.getHttpSess(auth=('root', 'root'), port=hport) as sess:
467
+ resp = await sess.get(f'https://localhost:{hport}/api/ext/hehe/haha')
468
+ data = await resp.json()
469
+ self.eq(data.get('path'), 'hehe/haha')
470
+ self.eq(resp.headers.get('yup'), 'hehe/haha')
471
+
472
+ resp = await sess.head(f'https://localhost:{hport}/api/ext/hehe/haha')
473
+ data = await resp.read()
474
+ self.eq(data, b'')
475
+ self.eq(resp.headers.get('yup'), 'hehe/haha')
476
+ self.none(resp.headers.get('Etag')) # No etag is computed for ext api
477
+
478
+ resp = await sess.get(f'https://localhost:{hport}/api/ext/hehe/ohmy1234')
479
+ data = await resp.json()
480
+ self.eq(data.get('path'), 'hehe/ohmy1234')
481
+ self.eq(resp.headers.get('yup'), 'wildcard')
482
+
483
+ # The paths are matched in case sensitive manner. The current regex fails to match.
484
+ resp = await sess.get(f'https://localhost:{hport}/api/ext/hehe/OhMy1234')
485
+ self.eq(resp.status, 404)
486
+
487
+ q = "$api=$lib.cortex.httpapi.get($iden) $api.path='hehe/([A-Za-z0-9]*)' "
488
+ await core.callStorm(q, opts={'vars': {'iden': iden0}})
489
+
490
+ resp = await sess.get(f'https://localhost:{hport}/api/ext/hehe/OhMy1234')
491
+ data = await resp.json()
492
+ self.eq(data.get('path'), 'hehe/OhMy1234')
493
+ self.eq(resp.headers.get('yup'), 'wildcard')
494
+
495
+ # We can move the endpoint to the end of the list too. An arbitrary high
496
+ # index will place it at the end.
497
+ msgs = await core.stormlist('cortex.httpapi.index $iden 100', opts={'vars': {'iden': iden0}})
498
+ self.stormIsInPrint(f'Set HTTP API {iden0} to index 3', msgs)
499
+
500
+ msgs = await core.stormlist('cortex.httpapi.list')
501
+ self.stormIsInPrint(f'0 {iden1}', msgs)
502
+ self.stormIsInPrint(f'1 {iden2}', msgs)
503
+ self.stormIsInPrint(f'2 {iden3}', msgs)
504
+ self.stormIsInPrint(f'3 {iden0}', msgs)
505
+
506
+ # iden Prefix + name matching
507
+ msgs = await core.stormlist('cortex.httpapi.index $iden 0', opts={'vars': {'iden': iden1[:6]}})
508
+ self.stormIsInPrint(f'Set HTTP API {iden1} to index 0', msgs)
509
+
510
+ msgs = await core.stormlist('cortex.httpapi.index $iden 0', opts={'vars': {'iden': 'the hehe/haha'}})
511
+ self.stormIsInPrint(f'Set HTTP API {iden1} to index 0', msgs)
512
+
513
+ # No match
514
+ msgs = await core.stormlist('cortex.httpapi.index $iden 0', opts={'vars': {'iden': 'newp'}})
515
+ self.stormIsInErr('Failed to match Extended HTTP API by iden or name!', msgs)
516
+
517
+ # too many matches
518
+ msgs = await core.stormlist('cortex.httpapi.index $iden 0', opts={'vars': {'iden': 'the'}})
519
+ self.stormIsInErr('Already matched one Extended HTTP API!', msgs)
520
+
521
+ # Show detailed information for a given api
522
+ msgs = await core.stormlist('cortex.httpapi.stat $iden', opts={'vars': {'iden': iden0}})
523
+ self.stormIsInPrint(f'Iden: {iden0}', msgs)
524
+ self.stormIsInPrint('Owner: root', msgs)
525
+ self.stormIsInPrint('Runas: owner', msgs)
526
+ self.stormIsInPrint('Readonly: false', msgs)
527
+ self.stormIsInPrint('Authenticated: true', msgs)
528
+ self.stormIsInPrint('Name: the hehe wildcard handler', msgs)
529
+ self.stormIsInPrint('Description: wildcard words', msgs)
530
+ self.stormIsInPrint('No user permissions are required to run this HTTP API endpoint.', msgs)
531
+ self.stormIsInPrint('Method: GET', msgs)
532
+ self.stormIsInPrint('$request.reply(200, headers=({"yup": "wildcard"}), body=({"path": $request.path}))',
533
+ msgs)
534
+ self.stormIsInPrint(f'Method: HEAD', msgs)
535
+ self.stormIsInPrint('$request.reply(200, headers=({"yup": "wildcard"}))', msgs)
536
+ self.stormIsInPrint('No vars are set for the handler.', msgs)
537
+
538
+ # iden Prefix + name matching
539
+ msgs = await core.stormlist('cortex.httpapi.stat $iden', opts={'vars': {'iden': iden0[:6]}})
540
+ self.stormIsInPrint(f'Iden: {iden0}', msgs)
541
+
542
+ msgs = await core.stormlist('cortex.httpapi.stat $iden', opts={'vars': {'iden': 'the hehe wildcard handler'}})
543
+ self.stormIsInPrint(f'Iden: {iden0}', msgs)
544
+
545
+ # No match
546
+ msgs = await core.stormlist('cortex.httpapi.stat $iden', opts={'vars': {'iden': 'newp'}})
547
+ self.stormIsInErr('Failed to match Extended HTTP API by iden or name!', msgs)
548
+
549
+ # too many matches
550
+ msgs = await core.stormlist('cortex.httpapi.stat $iden', opts={'vars': {'iden': 'the'}})
551
+ self.stormIsInErr('Already matched one Extended HTTP API!', msgs)
552
+
553
+ msgs = await core.stormlist('cortex.httpapi.stat $iden', opts={'vars': {'iden': iden1}})
554
+ self.stormIsInPrint(f'Iden: {iden1}', msgs)
555
+ self.stormIsInPrint('Creator: root', msgs)
556
+ self.stormIsInPrint('Created: ', msgs)
557
+ self.stormIsInPrint('Updated: ', msgs)
558
+ self.stormIsInPrint('Owner: root', msgs)
559
+ self.stormIsInPrint('Runas: user', msgs)
560
+ self.stormIsInPrint('Readonly: false', msgs)
561
+ self.stormIsInPrint('Authenticated: true', msgs)
562
+ self.stormIsInPrint('Name: the hehe/haha handler', msgs)
563
+ self.stormIsInPrint('Description: beep boop zoop robot captain', msgs)
564
+ self.stormIsInPrint('The following user permissions are required to run this HTTP API endpoint:',
565
+ msgs)
566
+ self.stormIsInPrint('hehe.haha\n default: false', msgs)
567
+ self.stormIsInPrint('some.thing\n default: true', msgs)
568
+
569
+ msgs = await core.stormlist('cortex.httpapi.stat $iden', opts={'vars': {'iden': iden3}})
570
+ self.stormIsInPrint(f'Iden: {iden3}', msgs)
571
+ self.stormIsInPrint('The handler has the following runtime variables set:', msgs)
572
+ self.stormIsInPrint('hehe => wow', msgs)
573
+ self.stormIsInPrint("items => ('1', '2', 3)", msgs)
574
+
575
+ # Remove a user + view and stat the handler
576
+ badview = await core.callStorm('''$view=$lib.view.get().fork() $lib.auth.users.del($user)
577
+ $api=$lib.cortex.httpapi.get($iden)
578
+ $api.view=$view.iden
579
+ $lib.view.del($view.iden)
580
+ return ( $view.iden )
581
+ ''',
582
+ opts={'vars': {'user': lowuser, 'iden': iden2}})
583
+
584
+ msgs = await core.stormlist('cortex.httpapi.stat $iden', opts={'vars': {'iden': iden2}})
585
+ self.stormIsInPrint(f'Iden: {iden2}', msgs)
586
+ self.stormIsInPrint(f'!Owner: No user found ({lowuser})', msgs)
587
+ self.stormIsInPrint(f'!View: No view found ({badview})', msgs)
588
+
589
+ # Paths must be valid regular expressions when created or modified
590
+ q = '''$api=$lib.cortex.httpapi.add("hehehe/hahaha)") return ($api.iden)'''
591
+ with self.raises(s_exc.BadArg) as cm:
592
+ await core.callStorm(q)
593
+
594
+ q = '''$api=$lib.cortex.httpapi.get($iden) $api.path="newp/(stuff" '''
595
+ with self.raises(s_exc.BadArg) as cm:
596
+ ret = await core.callStorm(q, opts={'vars': {'iden': iden0}})
597
+
598
+ # Creator / Created / Updated may not be set
599
+ q = '''$api=$lib.cortex.httpapi.get($iden) $api.creator=$valu'''
600
+ with self.raises(s_exc.NoSuchName) as cm:
601
+ await core.callStorm(q, opts={'vars': {'iden': iden0, 'valu': lowuser}})
602
+
603
+ valu = 0x01020304
604
+ q = '''$api=$lib.cortex.httpapi.get($iden) $api.created=$valu'''
605
+ with self.raises(s_exc.NoSuchName) as cm:
606
+ await core.callStorm(q, opts={'vars': {'iden': iden0, 'valu': valu}})
607
+
608
+ q = '''$api=$lib.cortex.httpapi.get($iden) $api.updated=$valu'''
609
+ with self.raises(s_exc.NoSuchName) as cm:
610
+ await core.callStorm(q, opts={'vars': {'iden': iden0, 'valu': valu}})
611
+
612
+ async def test_libcortex_httpapi_auth(self):
613
+ async with self.getTestCore() as core:
614
+ udef = await core.addUser('lowuser')
615
+ lowuser = udef.get('iden')
616
+ root = core.auth.rootuser.iden
617
+ await core.setUserPasswd(root, 'root')
618
+ await core.setUserPasswd(lowuser, 'secret')
619
+ addr, hport = await core.addHttpsPort(0)
620
+
621
+ q = '''$api = $lib.cortex.httpapi.add('auth')
622
+ $api.methods.get = ${
623
+ $request.reply(200, body=({"username": $lib.user.name(), "user": $request.user}) )
624
+ }
625
+ return ( $api.iden )
626
+ '''
627
+ iden = await core.callStorm(q)
628
+
629
+ async with self.getHttpSess(auth=('root', 'root'), port=hport) as sess:
630
+ resp = await sess.get(f'https://localhost:{hport}/api/ext/auth')
631
+ self.eq(resp.status, 200)
632
+ data = await resp.json()
633
+ self.eq(data.get('username'), 'root')
634
+ self.eq(data.get('user'), root)
635
+
636
+ async with self.getHttpSess() as sess:
637
+ resp = await sess.get(f'https://localhost:{hport}/api/ext/auth')
638
+ self.eq(resp.status, 401)
639
+ data = await resp.json()
640
+ self.eq(data.get('code'), 'NotAuthenticated')
641
+
642
+ q = '$api = $lib.cortex.httpapi.get($iden) $api.authenticated=$lib.false'
643
+ msgs = await core.stormlist(q, opts={'vars': {'iden': iden}})
644
+ self.stormHasNoWarnErr(msgs)
645
+
646
+ resp = await sess.get(f'https://localhost:{hport}/api/ext/auth')
647
+ self.eq(resp.status, 200)
648
+ data = await resp.json()
649
+ self.eq(data.get('username'), 'root')
650
+ self.eq(data.get('user'), '')
651
+
652
+ # authenticated = false + runas = user -> runs as owner
653
+ q = '$api = $lib.cortex.httpapi.get($iden) $api.runas=user'
654
+ msgs = await core.stormlist(q, opts={'vars': {'iden': iden}})
655
+ self.stormHasNoWarnErr(msgs)
656
+
657
+ resp = await sess.get(f'https://localhost:{hport}/api/ext/auth')
658
+ self.eq(resp.status, 200)
659
+ data = await resp.json()
660
+ self.eq(data.get('username'), 'root')
661
+ self.eq(data.get('user'), '')
662
+
663
+ # The user value is populated for authenticated requests which
664
+ # indicates who the requester's user iden is.
665
+ async with self.getHttpSess(auth=('lowuser', 'secret'), port=hport) as sess:
666
+ resp = await sess.get(f'https://localhost:{hport}/api/ext/auth')
667
+ self.eq(resp.status, 200)
668
+ data = await resp.json()
669
+ self.eq(data.get('username'), 'root')
670
+ self.eq(data.get('user'), '')
671
+
672
+ q = '$api = $lib.cortex.httpapi.get($iden) $api.authenticated=$lib.true'
673
+ msgs = await core.stormlist(q, opts={'vars': {'iden': iden}})
674
+ self.stormHasNoWarnErr(msgs)
675
+
676
+ resp = await sess.get(f'https://localhost:{hport}/api/ext/auth')
677
+ self.eq(resp.status, 200)
678
+ data = await resp.json()
679
+ self.eq(data.get('username'), 'lowuser')
680
+ self.eq(data.get('user'), lowuser)
681
+
682
+ q = '$api = $lib.cortex.httpapi.get($iden) $api.runas=owner'
683
+ msgs = await core.stormlist(q, opts={'vars': {'iden': iden}})
684
+ self.stormHasNoWarnErr(msgs)
685
+
686
+ resp = await sess.get(f'https://localhost:{hport}/api/ext/auth')
687
+ self.eq(resp.status, 200)
688
+ data = await resp.json()
689
+ self.eq(data.get('username'), 'root')
690
+ self.eq(data.get('user'), lowuser)
691
+
692
+ async def test_libcortex_httpapi_raw(self):
693
+ async with self.getTestCore() as core:
694
+ udef = await core.addUser('lowuser')
695
+ lowuser = udef.get('iden')
696
+ await core.setUserPasswd(core.auth.rootuser.iden, 'root')
697
+ await core.setUserPasswd(lowuser, 'secret')
698
+ addr, hport = await core.addHttpsPort(0)
699
+
700
+ # Define a handler that makes its own response headers, bytes, body.
701
+ q = '''
702
+ $api = $lib.cortex.httpapi.add('raw')
703
+ $api.methods.get = ${
704
+ $data = ({'oh': 'my'})
705
+ $body = $lib.json.save($data).encode()
706
+ $headers = ({'Secret-Header': 'OhBoy!'})
707
+ $headers."Content-Type" = "application/json"
708
+ $headers."Content-Length" = $lib.len($body)
709
+ $request.reply(200, headers=$headers, body=$body)
710
+ }
711
+ return ( $api.iden )
712
+ '''
713
+ await core.callStorm(q)
714
+
715
+ async with self.getHttpSess(auth=('root', 'root'), port=hport) as sess:
716
+ resp = await sess.get(f'https://localhost:{hport}/api/ext/raw')
717
+ self.eq(resp.status, 200)
718
+ self.eq(resp.headers.get('Content-Type'), 'application/json')
719
+ self.eq(resp.headers.get('Content-Length'), '12')
720
+ self.eq(resp.headers.get('Secret-Header'), 'OhBoy!')
721
+
722
+ buf = await resp.read()
723
+ self.len(12, buf)
724
+ self.eq(json.loads(buf), {'oh': 'my'})
725
+
726
+ async def test_libcortex_httpapi_jsonlines(self):
727
+ async with self.getTestCore() as core:
728
+ await core.setUserPasswd(core.auth.rootuser.iden, 'root')
729
+ addr, hport = await core.addHttpsPort(0)
730
+
731
+ # Example which uses the sendcode / sendheaders / sendbody
732
+ # methods in order to implement a streaming API endpoint
733
+
734
+ q = '''$api = $lib.cortex.httpapi.add('jsonlines')
735
+ $api.methods.get = ${
736
+ $request.sendcode(200)
737
+ $request.sendheaders(({"Secret-Header": "OhBoy!", "Content-Type": "text/plain; charset=utf8"}))
738
+ $values = ((1), (2), (3))
739
+ for $i in $values {
740
+ $body=`{$lib.json.save(({'oh': $i}))}\\n`
741
+ $request.sendbody($body.encode())
742
+ }
743
+ }
744
+ return ( $api.iden )
745
+ '''
746
+ iden00 = await core.callStorm(q)
747
+
748
+ async with self.getHttpSess(auth=('root', 'root'), port=hport) as sess:
749
+ resp = await sess.get(f'https://localhost:{hport}/api/ext/jsonlines')
750
+ self.eq(resp.status, 200)
751
+ self.eq(resp.headers.get('Content-Type'), 'text/plain; charset=utf8')
752
+
753
+ msgs = []
754
+ bufr = b''
755
+ async for byts, x in resp.content.iter_chunks():
756
+ if not byts:
757
+ break
758
+
759
+ bufr += byts
760
+ for jstr in bufr.split(b'\n'):
761
+ if not jstr:
762
+ bufr = b''
763
+ break
764
+
765
+ try:
766
+ mesg = json.loads(byts)
767
+ except json.JSONDecodeError:
768
+ bufr = jstr
769
+ break
770
+ else:
771
+ msgs.append(mesg)
772
+
773
+ self.eq(msgs, ({'oh': 1}, {'oh': 2}, {'oh': 3}))
774
+
775
+ async def test_libcortex_httpapi_perms(self):
776
+
777
+ async with self.getTestCore() as core:
778
+ udef = await core.addUser('lowuser')
779
+ lowuser = udef.get('iden')
780
+ await core.setUserPasswd(core.auth.rootuser.iden, 'root')
781
+ await core.setUserPasswd(lowuser, 'secret')
782
+ addr, hport = await core.addHttpsPort(0)
783
+
784
+ # Set a handler which requires a single permission
785
+
786
+ q = '''
787
+ $api = $lib.cortex.httpapi.add('hehe/haha')
788
+ $api.methods.get = ${
789
+ $data = ({'path': $request.path})
790
+ $request.reply(200, body=$data)
791
+ }
792
+ $api.methods.head = ${
793
+ $request.reply(200, ({"yup": "it exists"}) )
794
+ }
795
+ $api.name = 'the hehe/haha handler'
796
+ $api.desc = 'beep boop zoop robot captain'
797
+ $api.runas = user
798
+ $api.perms = (foocorp.http.user, )
799
+ return ( $api.iden )
800
+ '''
801
+ iden0 = await core.callStorm(q)
802
+
803
+ # Set a handler which has a few permissions, using perm
804
+ # defs to require there is a default=true permission
805
+ q = '''$api = $lib.cortex.httpapi.add('weee')
806
+ $api.methods.get = ${
807
+ $data = ({'path': $request.path})
808
+ $request.reply(200, body=$data)
809
+ }
810
+ $api.perms = ( ({'perm': ['foocorp', 'http', 'user']}), ({'perm': ['apiuser'], 'default': $lib.true}) )
811
+ $api.runas = user
812
+ return ( $api.iden )
813
+ '''
814
+ iden1 = await core.callStorm(q)
815
+
816
+ async with self.getHttpSess(auth=('lowuser', 'secret'), port=hport) as sess:
817
+ resp = await sess.get(f'https://localhost:{hport}/api/ext/hehe/haha')
818
+ self.eq(resp.status, 403)
819
+ data = await resp.json()
820
+ self.eq(data.get('mesg'), 'User (lowuser) must have permission foocorp.http.user')
821
+
822
+ await core.stormlist('auth.user.addrule lowuser foocorp.http.user')
823
+
824
+ resp = await sess.get(f'https://localhost:{hport}/api/ext/hehe/haha')
825
+ self.eq(resp.status, 200)
826
+ data = await resp.json()
827
+ self.eq(data.get('path'), 'hehe/haha')
828
+
829
+ resp = await sess.get(f'https://localhost:{hport}/api/ext/weee')
830
+ self.eq(resp.status, 200)
831
+ data = await resp.json()
832
+ self.eq(data.get('path'), 'weee')
833
+
834
+ # Add a deny rule for this user
835
+ await core.stormlist('auth.user.addrule lowuser "!apiuser"')
836
+
837
+ resp = await sess.get(f'https://localhost:{hport}/api/ext/weee')
838
+ self.eq(resp.status, 403)
839
+ data = await resp.json()
840
+ self.eq(data.get('mesg'), 'User (lowuser) is denied the permission apiuser')
841
+
842
+ # Defining perms as !foo.bar.baz does not work
843
+ with self.raises(s_exc.BadArg):
844
+ q = "$api=$lib.cortex.httpapi.get($iden) $api.perms=('!foo.bar.baz',)"
845
+ await core.callStorm(q, opts={'vars': {'iden': iden0}})
846
+
847
+ # Perms are list-like objects that we can mutate
848
+ q = '''$api = $lib.cortex.httpapi.add('perm/mutatorr')
849
+ $api.perms = (hehe.haha, wow.it.works)
850
+ return (($api.iden, $api.perms))
851
+ '''
852
+ iden2, perms = await core.callStorm(q)
853
+
854
+ # Perform a series of modifications of permissions that should have a
855
+ # known end state. These exercise the HttpPermsList object
856
+ q = '''$api=$lib.cortex.httpapi.get($iden)
857
+ $perms = $api.perms
858
+ $perms.append(another.permission)
859
+
860
+ // We can reverse the permissions...
861
+ $perms.reverse()
862
+
863
+ // No sort()
864
+ try { $perms.sort() } catch StormRuntimeError as err { }
865
+
866
+ // Clear the perms down for extend()
867
+ $api.perms = (hehe.haha,)
868
+ $api.perms.extend( (["woah.dude", {"perm": ["giggle", "clown"], "default": $lib.true}, "a.b.c" ]) )
869
+
870
+ // Pop some values
871
+ $pdef = $perms.pop()
872
+ $pdef2 = $perms.pop()
873
+
874
+ // has
875
+ if $perms.has(hehe.haha) {} else { $lib.exit(mesg="test fail") }
876
+ if $perms.has(haha.newp) { $lib.exit(mesg="test fail") } else { }
877
+ if $perms.has($pdef) { $lib.exit(mesg="test fail") } else { }
878
+
879
+ // Add pdef back
880
+ $perms.append($pdef)
881
+
882
+ // setitem
883
+ $perms.0=$lib.undef
884
+ $old_perm = $perms.0
885
+ $perms.0 = $pdef2
886
+
887
+ $perms.append(c.d.e) // Add an additional perm
888
+ $perms.2 = something.else // Replace that perm with a perm string
889
+
890
+ // No-op
891
+ $perms.1000 = $lib.undef
892
+
893
+ $lib.fire(perms, ref=$api.perms, obj=$perms)
894
+ '''
895
+ msgs = await core.stormlist(q, opts={'vars': {'iden': iden2}})
896
+ self.stormHasNoWarnErr(msgs)
897
+
898
+ mesg = [m[1] for m in msgs if m[0] == 'storm:fire'][0]
899
+ adef = await core.getHttpExtApi(iden2)
900
+ aprm = adef.get('perms')
901
+ self.eq(aprm, ({'perm': ('giggle', 'clown'), 'default': True},
902
+ {'perm': ('a', 'b', 'c'), 'default': False},
903
+ {'perm': ('something', 'else'), 'default': False}),
904
+
905
+ )
906
+ self.eq(aprm, mesg.get('data').get('obj'))
907
+ self.eq(aprm, mesg.get('data').get('ref'))
908
+
909
+ with self.raises(s_exc.StormRuntimeError) as cm:
910
+ q = '$api=$lib.cortex.httpapi.get($iden) while $lib.true { $api.perms.pop() } '
911
+ await core.callStorm(q, opts={'vars': {'iden': iden2}})
912
+ self.eq(cm.exception.get('mesg'), 'The permissions list is empty. Nothing to pop.')
913
+
914
+ async def test_libcortex_httpapi_view(self):
915
+ async with self.getTestCore() as core:
916
+ udef = await core.addUser('lowuser')
917
+ lowuser = udef.get('iden')
918
+ await core.setUserPasswd(core.auth.rootuser.iden, 'root')
919
+ await core.setUserPasswd(lowuser, 'secret')
920
+ addr, hport = await core.addHttpsPort(0)
921
+
922
+ def_view = await core.callStorm('return ( $lib.view.get().iden )')
923
+
924
+ q = '$lyr=$lib.layer.add() $view=$lib.view.add(($lyr.iden,), name="iso view") return ( $view.iden )'
925
+ view = await core.callStorm(q)
926
+
927
+ q = '''$api = $lib.cortex.httpapi.add(testpath)
928
+ $api.methods.get = ${
929
+ $view = $lib.view.get()
930
+ $request.reply(200, body=({'view': $view.iden}) )
931
+ }
932
+ return ( $api.iden )
933
+ '''
934
+ iden = await core.callStorm(q)
935
+
936
+ async with self.getHttpSess(auth=('lowuser', 'secret'), port=hport) as sess:
937
+ resp = await sess.get(f'https://localhost:{hport}/api/ext/testpath')
938
+ data = await resp.json()
939
+ self.eq(data.get('view'), def_view)
940
+
941
+ # Change the view the endpoint uses
942
+ q = '$api=$lib.cortex.httpapi.get($http_iden) $api.view = $lib.view.get($view_iden)'
943
+ msgs = await core.stormlist(q, opts={'vars': {'http_iden': iden, 'view_iden': view}})
944
+ self.stormHasNoWarnErr(msgs)
945
+
946
+ resp = await sess.get(f'https://localhost:{hport}/api/ext/testpath')
947
+ data = await resp.json()
948
+ self.eq(data.get('view'), view)
949
+
950
+ # Our gtor gives a heavy view object
951
+ name = await core.callStorm('$api=$lib.cortex.httpapi.get($http_iden) return ($api.view.get(name))',
952
+ opts={'vars': {'http_iden': iden}})
953
+ self.eq(name, 'iso view')
954
+
955
+ # If the view is deleted out from under the object, it has no knowledge of that
956
+ msgs = await core.stormlist('$lib.view.del($iden)', opts={'vars': {'iden': view}})
957
+ self.stormHasNoWarnErr(msgs)
958
+
959
+ # The gtor throws
960
+ with self.raises(s_exc.NoSuchView):
961
+ await core.callStorm('$api=$lib.cortex.httpapi.get($http_iden) return ($api.view.get(name))',
962
+ opts={'vars': {'http_iden': iden}})
963
+
964
+ resp = await sess.get(f'https://localhost:{hport}/api/ext/testpath')
965
+ self.eq(resp.status, 500)
966
+ data = await resp.json()
967
+ self.eq(data.get('code'), 'NoSuchView')
968
+
969
+ # Change the view back to the original view by iden
970
+ q = '$api=$lib.cortex.httpapi.get($http_iden) $api.view = $view_iden'
971
+ msgs = await core.stormlist(q, opts={'vars': {'http_iden': iden, 'view_iden': def_view}})
972
+ self.stormHasNoWarnErr(msgs)
973
+
974
+ resp = await sess.get(f'https://localhost:{hport}/api/ext/testpath')
975
+ data = await resp.json()
976
+ self.eq(data.get('view'), def_view)
977
+
978
+ async def test_libcortex_httpapi_headers_params(self):
979
+
980
+ # Test around case sensitivity of request headers and parameters
981
+
982
+ async with self.getTestCore() as core:
983
+ udef = await core.addUser('lowuser')
984
+ lowuser = udef.get('iden')
985
+ await core.setUserPasswd(core.auth.rootuser.iden, 'root')
986
+ await core.setUserPasswd(lowuser, 'secret')
987
+ addr, hport = await core.addHttpsPort(0)
988
+
989
+ q = '''$api = $lib.cortex.httpapi.add(testpath)
990
+ $api.methods.get = ${
991
+ $data = ({
992
+ "Secret-Key": $request.headers."Secret-Key",
993
+ "secret-key": $request.headers."secret-key",
994
+ "aaa": $request.headers.AAA,
995
+ "hehe": $request.params.hehe,
996
+ "HeHe": $request.params.HeHe,
997
+ })
998
+ $request.reply(200, body=$data )
999
+ }
1000
+ // Cannot modify request headers
1001
+ $api.methods.post = ${
1002
+ $request.headers.newp = haha
1003
+ }
1004
+ return ( ($api.iden, $api.owner.name) )
1005
+ '''
1006
+ iden, uname = await core.callStorm(q)
1007
+ self.eq(uname, 'root')
1008
+
1009
+ async with self.getHttpSess(auth=('root', 'root'), port=hport) as sess:
1010
+ resp = await sess.get(f'https://localhost:{hport}/api/ext/testpath',
1011
+ headers=(('secret-KEY', 'myluggagecombination'), ('aaa', 'zzz'), ('aaa', 'wtaf')),
1012
+ params=(('hehe', 'haha'), ('wow', 'words'), ('hehe', 'badjoke'), ('HeHe', ':)'))
1013
+ )
1014
+ self.eq(resp.status, 200)
1015
+ data = await resp.json()
1016
+
1017
+ # Params are flattened and case-sensitive upon access
1018
+ self.eq(data.get('hehe'), 'haha')
1019
+ self.eq(data.get('HeHe'), ':)')
1020
+
1021
+ # Headers are flattened and NOT case-sensitive upon access
1022
+ self.eq(data.get('aaa'), 'zzz')
1023
+ self.eq(data.get('Secret-Key'), 'myluggagecombination')
1024
+ self.eq(data.get('secret-key'), 'myluggagecombination')
1025
+
1026
+ resp = await sess.post(f'https://localhost:{hport}/api/ext/testpath')
1027
+ self.eq(resp.status, 500)
1028
+ data = await resp.json()
1029
+ self.eq(data.get('code'), 'StormRuntimeError')
1030
+ self.eq(data.get('mesg'), 'http:api:request:headers may not be modified by the runtime.')
1031
+
1032
+ async def test_libcortex_httpapi_vars(self):
1033
+
1034
+ async with self.getTestCore() as core:
1035
+ udef = await core.addUser('lowuser')
1036
+ lowuser = udef.get('iden')
1037
+ await core.setUserPasswd(core.auth.rootuser.iden, 'root')
1038
+ await core.setUserPasswd(lowuser, 'secret')
1039
+ addr, hport = await core.addHttpsPort(0)
1040
+
1041
+ q = '''$api = $lib.cortex.httpapi.add(testpath)
1042
+ $api.methods.get = ${ $data = ({"hehe": $hehe }) $request.reply(200, body=$data) }
1043
+ $api.methods.post = ${ $data = ({"sup": $sup }) $request.reply(200, body=$data) }
1044
+ $api.vars.hehe = haha
1045
+ $api.vars.sup = dawg
1046
+ return ( ($api.iden) )
1047
+ '''
1048
+ iden = await core.callStorm(q)
1049
+
1050
+ async with self.getHttpSess(auth=('root', 'root'), port=hport) as sess:
1051
+ resp = await sess.get(f'https://localhost:{hport}/api/ext/testpath',)
1052
+ data = await resp.json()
1053
+ self.eq(data.get('hehe'), 'haha')
1054
+
1055
+ resp = await sess.post(f'https://localhost:{hport}/api/ext/testpath', )
1056
+ data = await resp.json()
1057
+ self.eq(data.get('sup'), 'dawg')
1058
+
1059
+ q = '''$api=$lib.cortex.httpapi.get($http_iden)
1060
+ $api.vars = ({ "hehe": "yup", "sup": "word"})
1061
+ '''
1062
+ msgs = await core.stormlist(q, opts={'vars': {'http_iden': iden}})
1063
+ self.stormHasNoWarnErr(msgs)
1064
+
1065
+ resp = await sess.get(f'https://localhost:{hport}/api/ext/testpath',)
1066
+ data = await resp.json()
1067
+ self.eq(data.get('hehe'), 'yup')
1068
+
1069
+ resp = await sess.post(f'https://localhost:{hport}/api/ext/testpath', )
1070
+ data = await resp.json()
1071
+ self.eq(data.get('sup'), 'word')
1072
+
1073
+ # Cause a NoSuchVar error due to a missing var in the handler
1074
+ q = '$api=$lib.cortex.httpapi.get($iden) $api.vars.sup = $lib.undef'
1075
+ msgs = await core.stormlist(q, opts={'vars': {'iden': iden}})
1076
+ self.stormHasNoWarnErr(msgs)
1077
+ resp = await sess.post(f'https://localhost:{hport}/api/ext/testpath', )
1078
+ self.eq(resp.status, 500)
1079
+ data = await resp.json()
1080
+ self.eq(data.get('code'), 'NoSuchVar')
1081
+ self.eq(data.get('mesg'), 'Missing variable: sup')
1082
+
1083
+ q = '''$api = $lib.cortex.httpapi.get($iden)
1084
+
1085
+ $api = $lib.cortex.httpapi.get($iden)
1086
+
1087
+ $vars = $api.vars // _ctor to make a thin object
1088
+ $vars.hehe = haha // set and persist hehe=haha
1089
+
1090
+ $lib.print('pre _stor')
1091
+ for ($k, $v) in $vars {
1092
+ $lib.print(`{$k} -> {$v}`)
1093
+ }
1094
+
1095
+ // Use a _stor to set new vars in place
1096
+ $api.vars = ({"hehe": "i am silly", "why": "why not"})
1097
+
1098
+ $lib.print('post _stor')
1099
+ for ($k, $v) in $vars {
1100
+ $lib.print(`{$k} -> {$v}`)
1101
+ }
1102
+ '''
1103
+ msgs = await core.stormlist(q, opts={'vars': {'iden': iden}})
1104
+ self.stormHasNoWarnErr(msgs)
1105
+ self.stormIsInPrint('hehe -> haha', msgs)
1106
+ self.stormIsInPrint('hehe -> i am silly', msgs)
1107
+ self.stormIsInPrint('why -> why not', msgs)
1108
+
1109
+ async def test_libcortex_httpapi_readonly(self):
1110
+ async with self.getTestCore() as core:
1111
+ udef = await core.addUser('lowuser')
1112
+ lowuser = udef.get('iden')
1113
+ await core.setUserPasswd(core.auth.rootuser.iden, 'root')
1114
+ await core.setUserPasswd(lowuser, 'secret')
1115
+ addr, hport = await core.addHttpsPort(0)
1116
+
1117
+ # nothing
1118
+ q = '''$api = $lib.cortex.httpapi.add(testpath)
1119
+ $api.methods.get = ${ [inet:asn=$request.params.asn ] $request.reply(200, body=$node.value())}
1120
+ return ( ($api.iden) )'''
1121
+ iden = await core.callStorm(q)
1122
+
1123
+ async with self.getHttpSess(auth=('root', 'root'), port=hport) as sess:
1124
+ resp = await sess.get(f'https://localhost:{hport}/api/ext/testpath?asn=0')
1125
+ self.eq(resp.status, 200)
1126
+ data = await resp.json()
1127
+ self.eq(data, 0)
1128
+
1129
+ msgs = await core.stormlist('$api=$lib.cortex.httpapi.get($iden) $api.readonly = $lib.true',
1130
+ opts={'vars': {'iden': iden}})
1131
+ self.stormHasNoWarnErr(msgs)
1132
+
1133
+ resp = await sess.get(f'https://localhost:{hport}/api/ext/testpath?asn=1')
1134
+ self.eq(resp.status, 500)
1135
+ data = await resp.json()
1136
+ self.eq(data.get('code'), 'IsReadOnly')
1137
+
1138
+ q = '''$api=$lib.cortex.httpapi.get($iden)
1139
+ $api.methods.get = ${ inet:asn=$request.params.asn $request.reply(200, body=$node.value()) }'''
1140
+ msgs = await core.stormlist(q, opts={'vars': {'iden': iden}})
1141
+ self.stormHasNoWarnErr(msgs)
1142
+
1143
+ resp = await sess.get(f'https://localhost:{hport}/api/ext/testpath?asn=0')
1144
+ self.eq(resp.status, 200)
1145
+ data = await resp.json()
1146
+ self.eq(data, 0)
1147
+
1148
+ async def test_libcortex_httpapi_fsm_sadpath(self):
1149
+
1150
+ # Test to exercise sad paths of the state machine for the ExtHttpApi handler
1151
+ async with self.getTestCore() as core:
1152
+ udef = await core.addUser('lowuser')
1153
+ lowuser = udef.get('iden')
1154
+ await core.setUserPasswd(core.auth.rootuser.iden, 'root')
1155
+ await core.setUserPasswd(lowuser, 'secret')
1156
+ addr, hport = await core.addHttpsPort(0)
1157
+
1158
+ # nothing
1159
+ q = '''$api = $lib.cortex.httpapi.add(bad00)
1160
+ $api.methods.get = ${ }
1161
+ return ( ($api.iden) )'''
1162
+ iden00 = await core.callStorm(q)
1163
+
1164
+ # no status code
1165
+ q = '''$api = $lib.cortex.httpapi.add(bad01)
1166
+ $api.methods.get = ${ $request.sendheaders( ({"oh":"my"}) ) }
1167
+ return ( ($api.iden) )'''
1168
+ iden01 = await core.callStorm(q)
1169
+
1170
+ # no status code and a body being sent
1171
+ q = '''$api = $lib.cortex.httpapi.add(bad02)
1172
+ $api.methods.get = ${ $request.sendheaders(({"oh":"my"})) $data='text' $request.sendbody($data.encode()) }
1173
+ return ( ($api.iden) )'''
1174
+ iden02 = await core.callStorm(q)
1175
+
1176
+ # code after body has been sent
1177
+ q = '''$api = $lib.cortex.httpapi.add(bad03)
1178
+ $api.methods.get = ${ $data='text' $request.reply(201, body=$data.encode()) $request.sendcode(403) }
1179
+ return ( ($api.iden) )'''
1180
+ iden03 = await core.callStorm(q)
1181
+
1182
+ # headers after body has been sent
1183
+ q = '''$api = $lib.cortex.httpapi.add(bad04)
1184
+ $api.methods.get = ${$d='text' $request.reply(200, body=$d.encode()) $request.sendheaders(({"oh": "hi"}))}
1185
+ return ( ($api.iden) )'''
1186
+ iden04 = await core.callStorm(q)
1187
+
1188
+ # storm error
1189
+ q = '''$api = $lib.cortex.httpapi.add(bad05)
1190
+ $api.methods.get = ${ [test:int = notAnInt ] }
1191
+ return ( ($api.iden) )'''
1192
+ iden05 = await core.callStorm(q)
1193
+
1194
+ # storm error AFTER body has been sent
1195
+ q = '''$api = $lib.cortex.httpapi.add(bad06)
1196
+ $api.methods.get = ${ $request.reply(201, body=({})) [test:int = notAnInt ] }
1197
+ return ( ($api.iden) )'''
1198
+ iden06 = await core.callStorm(q)
1199
+
1200
+ async with self.getHttpSess(auth=('root', 'root'), port=hport) as sess:
1201
+ resp = await sess.get(f'https://localhost:{hport}/api/ext/bad00')
1202
+ self.eq(resp.status, 500)
1203
+ data = await resp.json()
1204
+ self.eq(data.get('code'), 'StormRuntimeError')
1205
+ self.eq(data.get('mesg'), f'Extended HTTP API {iden00} never set status code.')
1206
+
1207
+ resp = await sess.get(f'https://localhost:{hport}/api/ext/bad01')
1208
+ self.eq(resp.status, 500)
1209
+ data = await resp.json()
1210
+ self.notin('oh', resp.headers)
1211
+ self.eq(data.get('code'), 'StormRuntimeError')
1212
+ self.eq(data.get('mesg'), f'Extended HTTP API {iden01} never set status code.')
1213
+
1214
+ resp = await sess.get(f'https://localhost:{hport}/api/ext/bad02')
1215
+ self.eq(resp.status, 500)
1216
+ data = await resp.json()
1217
+ self.notin('oh', resp.headers)
1218
+ self.eq(data.get('code'), 'StormRuntimeError')
1219
+ self.eq(data.get('mesg'), f'Extended HTTP API {iden02} must set status code before sending body.')
1220
+
1221
+ with self.getAsyncLoggerStream('synapse.lib.httpapi',
1222
+ f'Extended HTTP API {iden03} tried to set code after sending body.') as stream:
1223
+
1224
+ resp = await sess.get(f'https://localhost:{hport}/api/ext/bad03')
1225
+ self.true(await stream.wait(timeout=6))
1226
+ self.eq(resp.status, 201)
1227
+ self.eq(await resp.read(), b'text')
1228
+
1229
+ with self.getAsyncLoggerStream('synapse.lib.httpapi',
1230
+ f'Extended HTTP API {iden04} tried to set headers after sending body.') as stream:
1231
+ resp = await sess.get(f'https://localhost:{hport}/api/ext/bad04')
1232
+ self.true(await stream.wait(timeout=6))
1233
+ self.eq(resp.status, 200)
1234
+ self.eq(await resp.read(), b'text')
1235
+
1236
+ resp = await sess.get(f'https://localhost:{hport}/api/ext/bad05')
1237
+ self.eq(resp.status, 500)
1238
+ data = await resp.json()
1239
+ self.eq(data.get('code'), 'BadTypeValu')
1240
+ self.eq(data.get('mesg'), "invalid literal for int() with base 0: 'notAnInt'")
1241
+
1242
+ with self.getAsyncLoggerStream('synapse.lib.httpapi',
1243
+ f'Error executing Extended HTTP API {iden06}: BadTypeValu') as stream:
1244
+ resp = await sess.get(f'https://localhost:{hport}/api/ext/bad06')
1245
+ self.true(await stream.wait(timeout=6))
1246
+ self.eq(resp.status, 201)
1247
+ self.eq(await resp.json(), {})
1248
+
1249
+ async def test_cortex_httpapi_dynamic(self):
1250
+
1251
+ # API endpoints can be dynamic. Use at your own risk.
1252
+
1253
+ async with self.getTestCore() as core:
1254
+ udef = await core.addUser('lowuser')
1255
+ lowuser = udef.get('iden')
1256
+ await core.setUserPasswd(core.auth.rootuser.iden, 'root')
1257
+ await core.setUserPasswd(lowuser, 'secret')
1258
+ addr, hport = await core.addHttpsPort(0)
1259
+
1260
+ q = '''$api = $lib.cortex.httpapi.add(dyn00)
1261
+ $api.methods.get = ${
1262
+ $api = $request.api
1263
+ if $n {
1264
+ $part = `redir0{$n}`
1265
+ $redir = `/api/ext/{$part}`
1266
+ $headers = ({"Location": $redir})
1267
+ $api.vars.n = ($n - (1) )
1268
+ $api.path = $part
1269
+ $request.reply(301, headers=$headers)
1270
+ } else {
1271
+ $api.vars.n = (3)
1272
+ $api.vars.melt = $lib.true
1273
+ $api.path = dyn00
1274
+ $request.reply(200, body=({"end": "youMadeIt", "melt": $melt}) )
1275
+ if $melt {
1276
+ $lib.cortex.httpapi.del($api.iden)
1277
+ }
1278
+ }
1279
+ }
1280
+ $api.vars.n = (3)
1281
+ $api.vars.melt = $lib.false
1282
+ return ( ($api.iden) )'''
1283
+ iden00 = await core.callStorm(q)
1284
+
1285
+ async with self.getHttpSess(auth=('root', 'root'), port=hport) as sess:
1286
+ resp = await sess.get(f'https://localhost:{hport}/api/ext/dyn00') # type: aiohttp.ClientResponse
1287
+ self.eq(resp.status, 200)
1288
+ self.eq(resp.url.path, '/api/ext/redir01')
1289
+ self.len(3, resp.history)
1290
+ data = await resp.json()
1291
+ self.eq(data, {'end': 'youMadeIt', 'melt': False})
1292
+
1293
+ resp = await sess.get(f'https://localhost:{hport}/api/ext/dyn00') # type: aiohttp.ClientResponse
1294
+ self.eq(resp.status, 200)
1295
+ self.len(3, resp.history)
1296
+ data = await resp.json()
1297
+ self.eq(data, {'end': 'youMadeIt', 'melt': True})
1298
+
1299
+ resp = await sess.get(f'https://localhost:{hport}/api/ext/dyn00') # type: aiohttp.ClientResponse
1300
+ self.eq(resp.status, 404)
1301
+ data = await resp.json()
1302
+ self.eq(data.get('code'), 'NoSuchPath')
1303
+
1304
+ async def test_cortex_httpapi_cell_headers(self):
1305
+ async with self.getTestCore(conf={'https:headers': {'Key1': 'Valu1'}}) as core:
1306
+ udef = await core.addUser('lowuser')
1307
+ lowuser = udef.get('iden')
1308
+ await core.setUserPasswd(core.auth.rootuser.iden, 'root')
1309
+ await core.setUserPasswd(lowuser, 'secret')
1310
+ addr, hport = await core.addHttpsPort(0)
1311
+
1312
+ q = '''$api = $lib.cortex.httpapi.add(stuff)
1313
+ $api.methods.get = ${
1314
+ $request.reply(200, headers=({"Weee": "valu"}) )
1315
+ }
1316
+ return ( ($api.iden) )'''
1317
+ iden00 = await core.callStorm(q)
1318
+
1319
+ async with self.getHttpSess(auth=('root', 'root'), port=hport) as sess:
1320
+ resp = await sess.get(f'https://localhost:{hport}/api/ext/stuff') # type: aiohttp.ClientResponse
1321
+ self.eq(resp.status, 200)
1322
+ self.eq(resp.headers.get('Weee'), 'valu')
1323
+ self.eq(resp.headers.get('Key1'), 'Valu1')
1324
+ # general default synapse headers are not present
1325
+ self.none(resp.headers.get('X-Content-Type-Options'))
1326
+ # Server is still omitted though
1327
+ self.none(resp.headers.get('Server'))