synapse 2.176.0__py311-none-any.whl → 2.178.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 (95) hide show
  1. synapse/axon.py +24 -9
  2. synapse/cortex.py +337 -172
  3. synapse/cryotank.py +46 -37
  4. synapse/datamodel.py +17 -4
  5. synapse/exc.py +19 -0
  6. synapse/lib/agenda.py +7 -13
  7. synapse/lib/aha.py +361 -88
  8. synapse/lib/auth.py +1520 -0
  9. synapse/lib/base.py +27 -9
  10. synapse/lib/cell.py +422 -163
  11. synapse/lib/config.py +15 -11
  12. synapse/lib/coro.py +13 -0
  13. synapse/lib/grammar.py +5 -0
  14. synapse/lib/hive.py +24 -3
  15. synapse/lib/hiveauth.py +6 -32
  16. synapse/lib/layer.py +7 -9
  17. synapse/lib/link.py +22 -18
  18. synapse/lib/lmdbslab.py +152 -3
  19. synapse/lib/modelrev.py +1 -1
  20. synapse/lib/nexus.py +24 -12
  21. synapse/lib/schemas.py +136 -0
  22. synapse/lib/storm.py +61 -29
  23. synapse/lib/stormlib/aha.py +1 -1
  24. synapse/lib/stormlib/auth.py +185 -10
  25. synapse/lib/stormlib/cortex.py +16 -5
  26. synapse/lib/stormlib/gen.py +80 -0
  27. synapse/lib/stormlib/imap.py +6 -2
  28. synapse/lib/stormlib/model.py +55 -0
  29. synapse/lib/stormlib/modelext.py +60 -0
  30. synapse/lib/stormlib/smtp.py +12 -2
  31. synapse/lib/stormlib/tabular.py +212 -0
  32. synapse/lib/stormtypes.py +14 -1
  33. synapse/lib/trigger.py +1 -1
  34. synapse/lib/version.py +2 -2
  35. synapse/lib/view.py +55 -28
  36. synapse/models/base.py +7 -0
  37. synapse/models/biz.py +4 -0
  38. synapse/models/files.py +8 -1
  39. synapse/models/inet.py +8 -0
  40. synapse/telepath.py +32 -17
  41. synapse/tests/files/aha/certs/cas/synapse.crt +28 -0
  42. synapse/tests/files/aha/certs/cas/synapse.key +51 -0
  43. synapse/tests/files/aha/certs/hosts/00.aha.loop.vertex.link.crt +30 -0
  44. synapse/tests/files/aha/certs/hosts/00.aha.loop.vertex.link.key +51 -0
  45. synapse/tests/files/aha/certs/users/root@synapse.crt +29 -0
  46. synapse/tests/files/aha/certs/users/root@synapse.key +51 -0
  47. synapse/tests/files/changelog/model_2.176.0_16ee721a6b7221344eaf946c3ab4602dda546b1a.yaml.gz +0 -0
  48. synapse/tests/files/changelog/model_2.176.0_2a25c58bbd344716cd7cbc3f4304d8925b0f4ef2.yaml.gz +0 -0
  49. synapse/tests/files/rstorm/testsvc.py +1 -1
  50. synapse/tests/test_axon.py +8 -5
  51. synapse/tests/test_cortex.py +149 -141
  52. synapse/tests/test_cryotank.py +4 -4
  53. synapse/tests/test_datamodel.py +7 -0
  54. synapse/tests/test_lib_agenda.py +10 -3
  55. synapse/tests/test_lib_aha.py +336 -490
  56. synapse/tests/{test_lib_hiveauth.py → test_lib_auth.py} +314 -11
  57. synapse/tests/test_lib_base.py +20 -0
  58. synapse/tests/test_lib_cell.py +210 -30
  59. synapse/tests/test_lib_config.py +4 -3
  60. synapse/tests/test_lib_httpapi.py +18 -14
  61. synapse/tests/test_lib_layer.py +33 -33
  62. synapse/tests/test_lib_link.py +42 -1
  63. synapse/tests/test_lib_lmdbslab.py +68 -0
  64. synapse/tests/test_lib_nexus.py +12 -4
  65. synapse/tests/test_lib_node.py +0 -7
  66. synapse/tests/test_lib_storm.py +45 -0
  67. synapse/tests/test_lib_stormlib_aha.py +35 -36
  68. synapse/tests/test_lib_stormlib_auth.py +21 -0
  69. synapse/tests/test_lib_stormlib_cell.py +4 -15
  70. synapse/tests/test_lib_stormlib_cortex.py +12 -12
  71. synapse/tests/test_lib_stormlib_gen.py +99 -0
  72. synapse/tests/test_lib_stormlib_imap.py +14 -3
  73. synapse/tests/test_lib_stormlib_model.py +108 -0
  74. synapse/tests/test_lib_stormlib_modelext.py +64 -0
  75. synapse/tests/test_lib_stormlib_smtp.py +51 -0
  76. synapse/tests/test_lib_stormlib_tabular.py +226 -0
  77. synapse/tests/test_lib_stormsvc.py +4 -1
  78. synapse/tests/test_lib_stormtypes.py +10 -0
  79. synapse/tests/test_model_base.py +3 -0
  80. synapse/tests/test_model_biz.py +3 -0
  81. synapse/tests/test_model_files.py +12 -2
  82. synapse/tests/test_model_inet.py +24 -0
  83. synapse/tests/test_tools_aha.py +78 -101
  84. synapse/tests/test_tools_changelog.py +196 -0
  85. synapse/tests/test_tools_healthcheck.py +4 -3
  86. synapse/tests/utils.py +87 -121
  87. synapse/tools/aha/clone.py +50 -0
  88. synapse/tools/aha/enroll.py +2 -1
  89. synapse/tools/backup.py +2 -2
  90. synapse/tools/changelog.py +776 -15
  91. {synapse-2.176.0.dist-info → synapse-2.178.0.dist-info}/METADATA +48 -48
  92. {synapse-2.176.0.dist-info → synapse-2.178.0.dist-info}/RECORD +95 -82
  93. {synapse-2.176.0.dist-info → synapse-2.178.0.dist-info}/WHEEL +1 -1
  94. {synapse-2.176.0.dist-info → synapse-2.178.0.dist-info}/LICENSE +0 -0
  95. {synapse-2.176.0.dist-info → synapse-2.178.0.dist-info}/top_level.txt +0 -0
@@ -1,4 +1,5 @@
1
1
  import synapse.exc as s_exc
2
+ import synapse.common as s_common
2
3
 
3
4
  import synapse.tests.utils as s_test
4
5
 
@@ -245,3 +246,101 @@ class StormLibGenTest(s_test.SynTest):
245
246
  names = nodes[0].get('names')
246
247
  self.len(1, names)
247
248
  self.isin('rhodesia', names)
249
+
250
+ async def test_stormlib_gen_fileBytes(self):
251
+
252
+ async with self.getTestCore() as core:
253
+ sha256 = s_common.buid().hex()
254
+ opts = {'vars': {'sha256': sha256}}
255
+
256
+ nodes = await core.nodes('yield $lib.gen.fileBytesBySha256($sha256)', opts=opts)
257
+ self.len(1, nodes)
258
+ self.eq(nodes[0].get('sha256'), sha256)
259
+
260
+ sha256 = s_common.buid().hex()
261
+ opts = {'vars': {'sha256': sha256}}
262
+
263
+ q = '''
264
+ [ file:bytes=(file1,) :sha256=$sha256 ]
265
+ spin |
266
+ yield $lib.gen.fileBytesBySha256($sha256)
267
+ '''
268
+ nodes = await core.nodes(q, opts=opts)
269
+ self.len(1, nodes)
270
+ self.eq(nodes[0].repr(), 'guid:' + s_common.guid(('file1',)))
271
+
272
+ with self.raises(s_exc.BadTypeValu):
273
+ await core.callStorm('$lib.gen.fileBytesBySha256(newp)', opts=opts)
274
+
275
+ q = 'return($lib.gen.fileBytesBySha256(newp, try=$lib.true))'
276
+ self.none(await core.callStorm(q, opts=opts))
277
+
278
+ async def test_stormlib_gen_inetTlsServerCert(self):
279
+
280
+ async with self.getTestCore() as core:
281
+ sha256 = s_common.buid().hex()
282
+ opts = {'vars': {'sha256': sha256}}
283
+
284
+ q = '''
285
+ $server = {[ inet:server="1.2.3.4:443" ]}
286
+ yield $lib.gen.inetTlsServerCertByServerAndSha256($server, $sha256)
287
+ '''
288
+ nodes = await core.nodes(q, opts=opts)
289
+ self.len(1, nodes)
290
+ self.eq(nodes[0].get('server'), 'tcp://1.2.3.4:443')
291
+ cert = nodes[0].get('cert')
292
+ self.nn(cert)
293
+
294
+ nodes = await core.nodes('crypto:x509:cert:sha256=$sha256', opts=opts)
295
+ self.len(1, nodes)
296
+ self.eq(nodes[0].repr(), cert)
297
+
298
+ with self.raises(s_exc.BadTypeValu):
299
+ await core.callStorm('$lib.gen.inetTlsServerCertByServerAndSha256(newp, $sha256)', opts=opts)
300
+
301
+ q = 'return($lib.gen.inetTlsServerCertByServerAndSha256(newp, $sha256, try=$lib.true))'
302
+ self.none(await core.callStorm(q, opts=opts))
303
+
304
+ async def test_stormlib_gen_cryptoX509Cert(self):
305
+
306
+ async with self.getTestCore() as core:
307
+
308
+ # Check guid generation
309
+ sha256 = s_common.buid().hex()
310
+ opts = {'vars': {'sha256': sha256}}
311
+ nodes = await core.nodes('yield $lib.gen.cryptoX509CertBySha256($sha256)', opts=opts)
312
+ self.len(1, nodes)
313
+ self.eq(nodes[0].get('sha256'), sha256)
314
+ self.eq(nodes[0].repr(), s_common.guid(sha256))
315
+
316
+ # Check invalid values, no try
317
+ with self.raises(s_exc.BadTypeValu):
318
+ await core.callStorm('$lib.gen.cryptoX509CertBySha256(newp)')
319
+
320
+ # Check invalid values, with try
321
+ self.none(await core.callStorm('return($lib.gen.cryptoX509CertBySha256(newp, try=$lib.true))'))
322
+
323
+ # Check node matching with same sha256 values
324
+ sha256 = s_common.buid().hex()
325
+ opts = {'vars': {'sha256': sha256}}
326
+ nodes = await core.nodes('[crypto:x509:cert=* :sha256=$sha256]', opts=opts)
327
+ self.len(1, nodes)
328
+ self.eq(nodes[0].get('sha256'), sha256)
329
+ self.ne(nodes[0].repr(), s_common.guid(sha256))
330
+ crypto = nodes[0].repr()
331
+
332
+ nodes = await core.nodes('yield $lib.gen.cryptoX509CertBySha256($sha256)', opts=opts)
333
+ self.len(1, nodes)
334
+ self.eq(nodes[0].repr(), crypto)
335
+
336
+ # Check node matching, crypto:x509:cert -> file with matching sha256
337
+ sha256 = s_common.buid().hex()
338
+ opts = {'vars': {'sha256': sha256}}
339
+ nodes = await core.nodes('[crypto:x509:cert=* :file={[ file:bytes=$sha256 ]} ]', opts=opts)
340
+ self.len(1, nodes)
341
+ self.none(nodes[0].get('sha256'))
342
+ crypto = nodes[0].repr()
343
+
344
+ nodes = await core.nodes('yield $lib.gen.cryptoX509CertBySha256($sha256)', opts=opts)
345
+ self.len(1, nodes)
346
+ self.eq(nodes[0].repr(), crypto)
@@ -1,3 +1,4 @@
1
+ import ssl
1
2
  import asyncio
2
3
 
3
4
  from unittest import mock
@@ -124,8 +125,13 @@ class ImapTest(s_test.SynTest):
124
125
 
125
126
  async def test_storm_imap(self):
126
127
 
127
- with mock.patch('aioimaplib.IMAP4.create_client', mock_create_client), \
128
- mock.patch('aioimaplib.IMAP4_SSL.create_client', mock_create_client):
128
+ client_args = []
129
+ def client_mock(*args, **kwargs):
130
+ client_args.append((args, kwargs))
131
+ return mock_create_client(*args, **kwargs)
132
+
133
+ with mock.patch('aioimaplib.IMAP4.create_client', client_mock), \
134
+ mock.patch('aioimaplib.IMAP4_SSL.create_client', client_mock):
129
135
 
130
136
  async with self.getTestCore() as core:
131
137
 
@@ -137,16 +143,20 @@ class ImapTest(s_test.SynTest):
137
143
  '''
138
144
  retn = await core.callStorm(scmd)
139
145
  self.eq((True, ('INBOX',)), retn)
146
+ ctx = self.nn(client_args[-1][0][5]) # type: ssl.SSLContext
147
+ self.eq(ctx.verify_mode, ssl.CERT_REQUIRED)
140
148
 
141
149
  # search for UIDs
142
150
  scmd = '''
143
- $server = $lib.inet.imap.connect(hello)
151
+ $server = $lib.inet.imap.connect(hello, ssl_verify=(false))
144
152
  $server.login("vtx@email.com", "secret")
145
153
  $server.select("INBOX")
146
154
  return($server.search("FROM", "foo@mail.com"))
147
155
  '''
148
156
  retn = await core.callStorm(scmd)
149
157
  self.eq((True, ('8181', '8192', '8194')), retn)
158
+ ctx = self.nn(client_args[-1][0][5]) # type: ssl.SSLContext
159
+ self.eq(ctx.verify_mode, ssl.CERT_NONE)
150
160
 
151
161
  # search for UIDs with specific charset
152
162
  scmd = '''
@@ -186,6 +196,7 @@ class ImapTest(s_test.SynTest):
186
196
  '''
187
197
  retn = await core.callStorm(scmd)
188
198
  self.eq((True, None), retn)
199
+ self.none(client_args[-1][0][5])
189
200
 
190
201
  # delete
191
202
  scmd = '''
@@ -1,4 +1,6 @@
1
1
  import synapse.exc as s_exc
2
+ import synapse.common as s_common
3
+
2
4
  import synapse.lib.time as s_time
3
5
  import synapse.lib.layer as s_layer
4
6
 
@@ -827,3 +829,109 @@ class StormlibModelTest(s_test.SynTest):
827
829
  with self.raises(s_exc.BadArg) as ectx:
828
830
  await core.nodes('$lib.model.migration.s.riskHasVulnToVulnerable(newp)')
829
831
  self.isin('must be a node', ectx.exception.errinfo['mesg'])
832
+
833
+ async def test_stormlib_model_migration_s_inet_ssl_to_tls_servercert(self):
834
+ async with self.getRegrCore('inet_ssl_to_tls_servercert') as core:
835
+ nodes = await core.nodes('meta:source')
836
+ self.len(1, nodes)
837
+
838
+ nodes = await core.nodes('meta:source -(seen)> *')
839
+ self.len(3, nodes)
840
+ for node in nodes:
841
+ self.eq(node.ndef[0], 'inet:ssl:cert')
842
+
843
+ nodes = await core.nodes('inet:ssl:cert')
844
+ self.len(3, nodes)
845
+
846
+ nodes = await core.nodes('file:bytes')
847
+ self.len(3, nodes)
848
+
849
+ nodes = await core.nodes('crypto:x509:cert')
850
+ self.len(2, nodes)
851
+
852
+ nodes = await core.nodes('inet:tls:servercert')
853
+ self.len(0, nodes)
854
+
855
+ q = 'inet:ssl:cert | $lib.model.migration.s.inetSslCertToTlsServerCert($node)'
856
+ await core.nodes(q)
857
+
858
+ nodes = await core.nodes('file:bytes')
859
+ self.len(3, nodes)
860
+
861
+ nodes = await core.nodes('crypto:x509:cert')
862
+ self.len(3, nodes)
863
+
864
+ nodes = await core.nodes('inet:tls:servercert')
865
+ self.len(3, nodes)
866
+
867
+ nodes = await core.nodes('crypto:x509:cert=(cert1,)')
868
+ self.len(1, nodes)
869
+ cert1 = nodes[0]
870
+
871
+ nodes = await core.nodes('inet:tls:servercert:server="tcp://1.2.3.4:443"')
872
+ self.len(1, nodes)
873
+ self.eq(nodes[0].get('.seen'), (1688947200000, 1688947200001))
874
+ self.eq(nodes[0].get('server'), 'tcp://1.2.3.4:443')
875
+ self.eq(nodes[0].get('cert'), cert1.ndef[1])
876
+ self.isin('ssl.migration.one', nodes[0].tags)
877
+
878
+ nodes = await core.nodes('crypto:x509:cert=(cert2,)')
879
+ self.len(1, nodes)
880
+ cert2 = nodes[0]
881
+
882
+ nodes = await core.nodes('inet:tls:servercert:server="tcp://[fe80::1]:8080"')
883
+ self.len(1, nodes)
884
+ self.none(nodes[0].get('.seen'))
885
+ self.eq(nodes[0].get('server'), 'tcp://[fe80::1]:8080')
886
+ self.eq(nodes[0].get('cert'), cert2.ndef[1])
887
+ self.isin('ssl.migration.two', nodes[0].tags)
888
+
889
+ sha256 = 'aa0366ffb013ba2053e45cd7e4bcc8acd6a6c1bafc82eddb4e155876734c5e25'
890
+ opts = {'vars': {'sha256': sha256}}
891
+
892
+ nodes = await core.nodes('file:bytes=$sha256', opts=opts)
893
+ self.len(1, nodes)
894
+ file = nodes[0]
895
+
896
+ # This cert was created by the migration code so do a little extra
897
+ # checking
898
+ nodes = await core.nodes('crypto:x509:cert:file=$sha256', opts=opts)
899
+ self.len(1, nodes)
900
+ self.eq(nodes[0].get('file'), file.ndef[1])
901
+ self.eq(nodes[0].ndef, ('crypto:x509:cert', s_common.guid(sha256)))
902
+ cert3 = nodes[0]
903
+
904
+ nodes = await core.nodes('inet:tls:servercert:server="tcp://8.8.8.8:53" $node.data.load(foo)')
905
+ self.len(1, nodes)
906
+ self.none(nodes[0].get('.seen'))
907
+ self.eq(nodes[0].get('server'), 'tcp://8.8.8.8:53')
908
+ self.eq(nodes[0].get('cert'), cert3.ndef[1])
909
+ self.isin('ssl.migration.three', nodes[0].tags)
910
+ self.eq(nodes[0].nodedata, {'foo': 'bar'})
911
+
912
+ # Check that edges were migrated
913
+ nodes = await core.nodes('meta:source -(seen)> *')
914
+ self.len(6, nodes)
915
+ self.sorteq(
916
+ [k.ndef[0] for k in nodes],
917
+ (
918
+ 'inet:ssl:cert', 'inet:ssl:cert', 'inet:ssl:cert',
919
+ 'inet:tls:servercert', 'inet:tls:servercert', 'inet:tls:servercert',
920
+ )
921
+ )
922
+
923
+ with self.raises(s_exc.BadArg) as exc:
924
+ await core.callStorm('inet:server | $lib.model.migration.s.inetSslCertToTlsServerCert($node)')
925
+ self.isin(', not inet:server', exc.exception.get('mesg'))
926
+
927
+ async with self.getRegrCore('inet_ssl_to_tls_servercert') as core:
928
+ q = 'inet:ssl:cert | $lib.model.migration.s.inetSslCertToTlsServerCert($node, nodata=$lib.true)'
929
+ await core.nodes(q)
930
+
931
+ nodes = await core.nodes('inet:tls:servercert:server="tcp://8.8.8.8:53" $node.data.load(foo)')
932
+ self.len(1, nodes)
933
+ self.none(nodes[0].get('.seen'))
934
+ self.eq(nodes[0].get('server'), 'tcp://8.8.8.8:53')
935
+ self.eq(nodes[0].get('cert'), cert3.ndef[1])
936
+ self.isin('ssl.migration.three', nodes[0].tags)
937
+ self.eq(nodes[0].nodedata, {'foo': None})
@@ -22,6 +22,9 @@ class StormtypesModelextTest(s_test.SynTest):
22
22
 
23
23
  $pinfo = ({"doc": "Extended a core model."})
24
24
  $lib.model.ext.addFormProp(test:int, _tick, (time, ({})), $propinfo)
25
+
26
+ $edgeinfo = ({"doc": "A test edge."})
27
+ $lib.model.ext.addEdge(inet:user, _copies, *, $edgeinfo)
25
28
  ''')
26
29
 
27
30
  nodes = await core.nodes('[ _visi:int=10 :tick=20210101 ._woot=30 +#lol:score=99 ]')
@@ -44,6 +47,12 @@ class StormtypesModelextTest(s_test.SynTest):
44
47
  q = '''$lib.model.ext.addUnivProp(_woot, (time, ({})), ({}))'''
45
48
  await core.callStorm(q)
46
49
 
50
+ with self.raises(s_exc.DupEdgeType):
51
+ q = '''$lib.model.ext.addEdge(inet:user, _copies, *, ({}))'''
52
+ await core.callStorm(q)
53
+
54
+ self.nn(core.model.edge(('inet:user', '_copies', None)))
55
+
47
56
  # Grab the extended model definitions
48
57
  model_defs = await core.callStorm('return ( $lib.model.ext.getExtModel() )')
49
58
  self.isinstance(model_defs, dict)
@@ -55,6 +64,7 @@ class StormtypesModelextTest(s_test.SynTest):
55
64
  $lib.model.ext.delFormProp(_visi:int, tick)
56
65
  $lib.model.ext.delFormProp(test:int, _tick)
57
66
  $lib.model.ext.delForm(_visi:int)
67
+ $lib.model.ext.delEdge(inet:user, _copies, *)
58
68
  ''')
59
69
 
60
70
  self.none(core.model.form('_visi:int'))
@@ -62,6 +72,7 @@ class StormtypesModelextTest(s_test.SynTest):
62
72
  self.none(core.model.prop('_visi:int:tick'))
63
73
  self.none(core.model.prop('test:int:_tick'))
64
74
  self.none(core.model.tagprop('score'))
75
+ self.none(core.model.edge(('inet:user', '_copies', None)))
65
76
 
66
77
  # Underscores can exist in extended names but only at specific locations
67
78
  q = '''$l =$lib.list('str', ({})) $d=({"doc": "Foo"})
@@ -105,6 +116,26 @@ class StormtypesModelextTest(s_test.SynTest):
105
116
  q = '''$lib.model.ext.addTagProp(_someones:_score^value, (int, ({})), ({}))'''
106
117
  await core.callStorm(q)
107
118
 
119
+ with self.raises(s_exc.BadEdgeDef):
120
+ q = '''$lib.model.ext.addEdge(*, does, *, ({}))'''
121
+ await core.callStorm(q)
122
+
123
+ with self.raises(s_exc.BadEdgeDef):
124
+ q = '''$lib.model.ext.addEdge(*, _NEWP, *, ({}))'''
125
+ await core.callStorm(q)
126
+
127
+ with self.raises(s_exc.BadEdgeDef):
128
+ q = '''$lib.model.ext.addEdge(*, "_ne wp", *, ({}))'''
129
+ await core.callStorm(q)
130
+
131
+ with self.raises(s_exc.BadEdgeDef):
132
+ q = f'''$lib.model.ext.addEdge(*, "_{'a'*201}", *, ({{}}))'''
133
+ await core.callStorm(q)
134
+
135
+ with self.raises(s_exc.BadEdgeDef):
136
+ q = '''$lib.model.ext.delEdge(*, "_ne wp", *)'''
137
+ await core.callStorm(q)
138
+
108
139
  # Permission errors
109
140
  visi = await core.auth.addUser('visi')
110
141
  opts = {'user': visi.iden}
@@ -133,6 +164,10 @@ class StormtypesModelextTest(s_test.SynTest):
133
164
  $lib.model.ext.addTagProp(score, (int, ({})), $tagpropinfo)
134
165
  ''', opts=opts)
135
166
 
167
+ with self.raises(s_exc.AuthDeny):
168
+ q = '''$lib.model.ext.addEdge(*, _does, *, ({}))'''
169
+ await core.callStorm(q, opts=opts)
170
+
136
171
  # Reload the model extensions automatically
137
172
  async with self.getTestCore() as core:
138
173
  opts = {'vars': {'model_defs': model_defs}}
@@ -174,6 +209,9 @@ class StormtypesModelextTest(s_test.SynTest):
174
209
 
175
210
  $pinfo = ({"doc": "NEWP"})
176
211
  $lib.model.ext.addFormProp(test:int, _tick, (time, ({})), $propinfo)
212
+
213
+ $edgeinfo = ({"doc": "NEWP"})
214
+ $lib.model.ext.addEdge(inet:user, _copies, *, $edgeinfo)
177
215
  ''')
178
216
 
179
217
  q = '''return ($lib.model.ext.addExtModel($model_defs))'''
@@ -196,6 +234,11 @@ class StormtypesModelextTest(s_test.SynTest):
196
234
  opts = {'vars': {'model_defs': {'univs': model_defs['univs']}}}
197
235
  await core.callStorm(q, opts)
198
236
 
237
+ q = '''return ($lib.model.ext.addExtModel($model_defs))'''
238
+ with self.raises(s_exc.BadEdgeDef) as cm:
239
+ opts = {'vars': {'model_defs': {'edges': model_defs['edges']}}}
240
+ await core.callStorm(q, opts)
241
+
199
242
  # Reload the model extensions from the dump by hand
200
243
  async with self.getTestCore() as core:
201
244
  opts = {'vars': {'model_defs': model_defs}}
@@ -212,6 +255,10 @@ class StormtypesModelextTest(s_test.SynTest):
212
255
  for ($prop, $def, $info) in $model_defs.univs {
213
256
  $lib.model.ext.addUnivProp($prop, $def, $info)
214
257
  }
258
+ for ($edge, $info) in $model_defs.edges {
259
+ ($n1form, $verb, $n2form) = $edge
260
+ $lib.model.ext.addEdge($n1form, $verb, $n2form, $info)
261
+ }
215
262
  '''
216
263
  await core.nodes(q, opts)
217
264
 
@@ -227,6 +274,8 @@ class StormtypesModelextTest(s_test.SynTest):
227
274
  self.eq(nodes[0].ndef, ('test:int', 1234))
228
275
  self.eq(nodes[0].get('_tick'), 1609459200000)
229
276
 
277
+ self.nn(core.model.edge(('inet:user', '_copies', None)))
278
+
230
279
  async def test_lib_stormlib_behold_modelext(self):
231
280
  self.skipIfNexusReplay()
232
281
  async with self.getTestCore() as core:
@@ -252,6 +301,7 @@ class StormtypesModelextTest(s_test.SynTest):
252
301
  $lib.model.ext.addFormProp(_behold:score, rank, (int, ({})), ({"doc": "second string"}))
253
302
  $lib.model.ext.addUnivProp(_beep, (int, ({})), ({"doc": "third string"}))
254
303
  $lib.model.ext.addTagProp(thingy, (int, ({})), ({"doc": "fourth string"}))
304
+ $lib.model.ext.addEdge(*, _goes, geo:place, ({"doc": "fifth string"}))
255
305
  ''')
256
306
 
257
307
  formmesg = await sock.receive_json()
@@ -279,11 +329,17 @@ class StormtypesModelextTest(s_test.SynTest):
279
329
  self.eq(tagpmesg['data']['info']['name'], 'thingy')
280
330
  self.eq(tagpmesg['data']['info']['info'], {'doc': 'fourth string'})
281
331
 
332
+ edgemesg = await sock.receive_json()
333
+ self.eq(edgemesg['data']['event'], 'model:edge:add')
334
+ self.eq(edgemesg['data']['info']['edge'], (None, '_goes', 'geo:place'))
335
+ self.eq(edgemesg['data']['info']['info'], {'doc': 'fifth string'})
336
+
282
337
  await core.callStorm('''
283
338
  $lib.model.ext.delTagProp(thingy)
284
339
  $lib.model.ext.delUnivProp(_beep)
285
340
  $lib.model.ext.delFormProp(_behold:score, rank)
286
341
  $lib.model.ext.delForm(_behold:score)
342
+ $lib.model.ext.delEdge(*, _goes, geo:place)
287
343
  ''')
288
344
  deltagp = await sock.receive_json()
289
345
  self.eq(deltagp['data']['event'], 'model:tagprop:del')
@@ -302,6 +358,10 @@ class StormtypesModelextTest(s_test.SynTest):
302
358
  self.eq(delform['data']['event'], 'model:form:del')
303
359
  self.eq(delform['data']['info']['form'], '_behold:score')
304
360
 
361
+ deledge = await sock.receive_json()
362
+ self.eq(deledge['data']['event'], 'model:edge:del')
363
+ self.eq(deledge['data']['info']['edge'], (None, '_goes', 'geo:place'))
364
+
305
365
  async def test_lib_stormlib_modelext_delform(self):
306
366
  '''
307
367
  Verify extended forms can't be deleted if they have associated extended props
@@ -385,6 +445,10 @@ class StormtypesModelextTest(s_test.SynTest):
385
445
  '$lib.model.ext.addTagProp(_foo:bar, (), ())',
386
446
  'Tag property definitions should be a dict.'
387
447
  ),
448
+ (
449
+ '$lib.model.ext.addEdge(*, _foo, *, ())',
450
+ 'Edge info should be a dict.'
451
+ ),
388
452
  )
389
453
 
390
454
  async with self.getTestCore() as core:
@@ -1,3 +1,6 @@
1
+ import ssl
2
+ import email.mime.multipart as e_muiltipart
3
+
1
4
  from unittest import mock
2
5
  import synapse.tests.utils as s_test
3
6
 
@@ -5,7 +8,9 @@ class SmtpTest(s_test.SynTest):
5
8
 
6
9
  async def test_storm_smtp(self):
7
10
 
11
+ called_args = []
8
12
  async def send(*args, **kwargs):
13
+ called_args.append((args, kwargs))
9
14
  return
10
15
 
11
16
  with mock.patch('aiosmtplib.send', send):
@@ -14,6 +19,9 @@ class SmtpTest(s_test.SynTest):
14
19
 
15
20
  retn = await core.callStorm('return($lib.inet.smtp.message().send(127.0.0.1))')
16
21
  self.false(retn[0])
22
+ self.eq(retn[1].get('err'), 'StormRuntimeError')
23
+ self.isin('The inet:smtp:message has no HTML or text body.', retn[1].get('errmsg'))
24
+ self.len(0, called_args)
17
25
 
18
26
  retn = await core.callStorm('''
19
27
  $message = $lib.inet.smtp.message()
@@ -31,6 +39,49 @@ class SmtpTest(s_test.SynTest):
31
39
  return($message.send('smtp.gmail.com', port=465, usetls=true))
32
40
  ''')
33
41
  self.eq(retn, (True, {}))
42
+ mesg = called_args[-1][0][0] # type: e_muiltipart.MIMEMultipart
43
+ self.eq(mesg.get_all('subject'), ['woot'])
44
+ payload = mesg.get_payload()
45
+ self.len(2, payload)
46
+ self.eq([pl.get_content_type() for pl in payload],
47
+ ['text/plain', 'text/html'])
48
+ ctx = self.nn(called_args[-1][1].get('tls_context')) # type: ssl.SSLContext
49
+ self.eq(ctx.verify_mode, ssl.CERT_REQUIRED)
50
+ self.eq(called_args[-1][1].get('port'), 465)
51
+ self.true(called_args[-1][1].get('use_tls'))
52
+ self.false(called_args[-1][1].get('start_tls'))
53
+
54
+ retn = await core.callStorm('''
55
+ $message = $lib.inet.smtp.message()
56
+ $message.text = "HELLO WORLD"
57
+ $message.sender = visi@vertex.link
58
+ $message.headers.Subject = woot
59
+ $message.recipients.append(visi@vertex.link)
60
+ return($message.send('smtp.gmail.com', port=465, starttls=true, ssl_verify=(false)))
61
+ ''')
62
+ self.eq(retn, (True, {}))
63
+ mesg = called_args[-1][0][0] # type: e_muiltipart.MIMEMultipart
64
+ payload = mesg.get_payload()
65
+ self.len(1, payload)
66
+ self.eq([pl.get_content_type() for pl in payload], ['text/plain'])
67
+ ctx = self.nn(called_args[-1][1].get('tls_context')) # type: ssl.SSLContext
68
+ self.eq(ctx.verify_mode, ssl.CERT_NONE)
69
+ self.false(called_args[-1][1].get('use_tls'))
70
+ self.true(called_args[-1][1].get('start_tls'))
71
+
72
+ retn = await core.callStorm('''
73
+ $message = $lib.inet.smtp.message()
74
+ $message.text = "HELLO WORLD"
75
+ $message.sender = visi@vertex.link
76
+ $message.headers.Subject = woot
77
+ $message.recipients.append(visi@vertex.link)
78
+ return($message.send('smtp.gmail.com', port=25))
79
+ ''')
80
+ self.eq(retn, (True, {}))
81
+ self.none(called_args[-1][1].get('tls_context')) # type: ssl.SSLContext
82
+ self.eq(called_args[-1][1].get('port'), 25)
83
+ self.false(called_args[-1][1].get('use_tls'))
84
+ self.false(called_args[-1][1].get('start_tls'))
34
85
 
35
86
  isok, info = await core.callStorm('''
36
87
  $message = $lib.inet.smtp.message()