synapse 2.186.0__py311-none-any.whl → 2.188.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.
- synapse/cortex.py +133 -9
- synapse/datamodel.py +20 -4
- synapse/exc.py +14 -1
- synapse/lib/ast.py +6 -4
- synapse/lib/auth.py +9 -0
- synapse/lib/hive.py +1 -1
- synapse/lib/httpapi.py +2 -1
- synapse/lib/modelrev.py +771 -11
- synapse/lib/nexus.py +6 -0
- synapse/lib/node.py +5 -3
- synapse/lib/scrape.py +18 -104
- synapse/lib/spooled.py +26 -3
- synapse/lib/storm.py +51 -28
- synapse/lib/stormlib/model.py +320 -250
- synapse/lib/stormlib/modelext.py +31 -0
- synapse/lib/stormlib/scrape.py +1 -4
- synapse/lib/stormtypes.py +53 -11
- synapse/lib/version.py +2 -2
- synapse/lib/view.py +9 -3
- synapse/models/base.py +27 -0
- synapse/models/files.py +22 -0
- synapse/models/inet.py +49 -4
- synapse/models/infotech.py +49 -22
- synapse/models/orgs.py +64 -2
- synapse/models/proj.py +1 -6
- synapse/models/risk.py +65 -0
- synapse/tests/test_cortex.py +21 -0
- synapse/tests/test_lib_agenda.py +13 -0
- synapse/tests/test_lib_auth.py +15 -0
- synapse/tests/test_lib_cell.py +2 -1
- synapse/tests/test_lib_httpapi.py +6 -0
- synapse/tests/test_lib_modelrev.py +918 -379
- synapse/tests/test_lib_nexus.py +26 -0
- synapse/tests/test_lib_scrape.py +14 -6
- synapse/tests/test_lib_spooled.py +34 -0
- synapse/tests/test_lib_storm.py +48 -0
- synapse/tests/test_lib_stormlib_model.py +0 -270
- synapse/tests/test_lib_stormlib_modelext.py +76 -1
- synapse/tests/test_lib_stormlib_scrape.py +0 -8
- synapse/tests/test_lib_stormtypes.py +12 -1
- synapse/tests/test_lib_trigger.py +8 -0
- synapse/tests/test_lib_view.py +24 -0
- synapse/tests/test_model_base.py +11 -0
- synapse/tests/test_model_files.py +19 -0
- synapse/tests/test_model_inet.py +33 -0
- synapse/tests/test_model_infotech.py +14 -11
- synapse/tests/test_model_orgs.py +39 -0
- synapse/tests/test_model_proj.py +11 -1
- synapse/tests/test_model_risk.py +32 -0
- synapse/tools/changelog.py +11 -3
- {synapse-2.186.0.dist-info → synapse-2.188.0.dist-info}/METADATA +1 -1
- {synapse-2.186.0.dist-info → synapse-2.188.0.dist-info}/RECORD +55 -58
- synapse/assets/__init__.py +0 -35
- synapse/assets/storm/migrations/model-0.2.28.storm +0 -355
- synapse/tests/test_assets.py +0 -25
- {synapse-2.186.0.dist-info → synapse-2.188.0.dist-info}/LICENSE +0 -0
- {synapse-2.186.0.dist-info → synapse-2.188.0.dist-info}/WHEEL +0 -0
- {synapse-2.186.0.dist-info → synapse-2.188.0.dist-info}/top_level.txt +0 -0
synapse/tests/test_lib_nexus.py
CHANGED
|
@@ -310,6 +310,32 @@ class NexusTest(s_t_utils.SynTest):
|
|
|
310
310
|
await cell01.sync()
|
|
311
311
|
self.isin(s_nexus.leaderversion, cell01.nexsroot.writeholds)
|
|
312
312
|
|
|
313
|
+
cell00.getCellInfo = getCellInfo
|
|
314
|
+
|
|
315
|
+
# test case where a mirror which is updated first may push events
|
|
316
|
+
# the leader does not yet have handlers for
|
|
317
|
+
async with await s_cell.Cell.anit(dirn=path) as cell01:
|
|
318
|
+
cell01.nexsiden = 'newp'
|
|
319
|
+
with self.raises(s_exc.NoSuchIden) as cm:
|
|
320
|
+
await cell01.sync()
|
|
321
|
+
self.eq(cm.exception.get('mesg'), 'No Nexus Pusher with iden newp.')
|
|
322
|
+
|
|
323
|
+
self.none(await cell00.nexsroot.nexslog.last())
|
|
324
|
+
self.none(await cell01.nexsroot.nexslog.last())
|
|
325
|
+
|
|
326
|
+
cell01.nexsiden = cell00.nexsiden
|
|
327
|
+
await cell01.sync()
|
|
328
|
+
|
|
329
|
+
self.eq(0, (await cell00.nexsroot.nexslog.last())[0])
|
|
330
|
+
self.eq(0, (await cell01.nexsroot.nexslog.last())[0])
|
|
331
|
+
|
|
332
|
+
with self.raises(s_exc.NoSuchName) as cm:
|
|
333
|
+
await cell01._push('newp')
|
|
334
|
+
self.eq(cm.exception.get('mesg'), 'No Nexus handler for event newp.')
|
|
335
|
+
|
|
336
|
+
self.eq(0, (await cell00.nexsroot.nexslog.last())[0])
|
|
337
|
+
self.eq(0, (await cell01.nexsroot.nexslog.last())[0])
|
|
338
|
+
|
|
313
339
|
async def test_mirror_nexus_loop_failure(self):
|
|
314
340
|
with self.getTestDir() as dirn:
|
|
315
341
|
|
synapse/tests/test_lib_scrape.py
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
from unittest import mock
|
|
2
2
|
|
|
3
|
+
import regex
|
|
4
|
+
|
|
3
5
|
import synapse.exc as s_exc
|
|
4
6
|
|
|
5
7
|
import synapse.lib.scrape as s_scrape
|
|
@@ -889,14 +891,20 @@ class ScrapeTest(s_t_utils.SynTest):
|
|
|
889
891
|
nodes.remove(('inet:url', 'smb://1:2:3:4:5:6:7:8/share'))
|
|
890
892
|
|
|
891
893
|
async def test_scrape_async(self):
|
|
894
|
+
text = 'log4j vuln CVE-2021-44228 is pervasive'
|
|
895
|
+
ndefs = await s_t_utils.alist(s_scrape.scrapeAsync(text))
|
|
896
|
+
self.eq(ndefs, (('it:sec:cve', 'CVE-2021-44228'),))
|
|
892
897
|
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
898
|
+
regx = regex.compile('(?P<valu>CVE-[0-9]{4}-[0-9]{4,})(?:[^a-z0-9]|$)')
|
|
899
|
+
infos = s_scrape._genMatchList(text, regx, {})
|
|
900
|
+
self.eq(infos, [{'match': 'CVE-2021-44228', 'offset': 11, 'valu': 'CVE-2021-44228'}])
|
|
896
901
|
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
902
|
+
text = 'endashs are a vulnerability CVE\u20132022\u20131138 '
|
|
903
|
+
infos = await s_t_utils.alist(s_scrape.contextScrapeAsync(text))
|
|
904
|
+
self.eq(infos, [{'match': 'CVE–2022–1138', 'offset': 29, 'valu': 'CVE-2022-1138', 'form': 'it:sec:cve'}])
|
|
905
|
+
|
|
906
|
+
infos = s_scrape._contextScrapeList(text)
|
|
907
|
+
self.eq(infos, [{'match': 'CVE–2022–1138', 'offset': 29, 'valu': 'CVE-2022-1138', 'form': 'it:sec:cve'}])
|
|
900
908
|
|
|
901
909
|
def test_scrape_sequential(self):
|
|
902
910
|
md5 = ('a' * 32, 'b' * 32,)
|
|
@@ -28,11 +28,34 @@ class SpooledTest(s_test.SynTest):
|
|
|
28
28
|
|
|
29
29
|
await sset.add(10)
|
|
30
30
|
|
|
31
|
+
newset = await sset.copy()
|
|
32
|
+
self.len(1, newset)
|
|
33
|
+
self.true(10 in newset)
|
|
34
|
+
self.false(newset.fallback)
|
|
35
|
+
|
|
31
36
|
# Trigger fallback
|
|
32
37
|
await sset.add(20)
|
|
33
38
|
await sset.add(30)
|
|
34
39
|
await sset.add(None)
|
|
35
40
|
|
|
41
|
+
newset = await sset.copy()
|
|
42
|
+
self.true(10 in newset)
|
|
43
|
+
self.true(20 in newset)
|
|
44
|
+
self.true(30 in newset)
|
|
45
|
+
self.true(None in newset)
|
|
46
|
+
self.len(4, newset)
|
|
47
|
+
|
|
48
|
+
await newset.clear()
|
|
49
|
+
self.false(10 in newset)
|
|
50
|
+
self.false(20 in newset)
|
|
51
|
+
self.false(30 in newset)
|
|
52
|
+
self.false(None in newset)
|
|
53
|
+
self.len(0, newset)
|
|
54
|
+
|
|
55
|
+
self.true(os.path.isdir(newset.slab.path))
|
|
56
|
+
await newset.fini()
|
|
57
|
+
self.false(os.path.isdir(newset.slab.path))
|
|
58
|
+
|
|
36
59
|
self.len(4, sset)
|
|
37
60
|
|
|
38
61
|
await sset.add(20)
|
|
@@ -68,6 +91,17 @@ class SpooledTest(s_test.SynTest):
|
|
|
68
91
|
self.true(os.path.isdir(sset.slab.path))
|
|
69
92
|
self.true(os.path.abspath(sset.slab.path).startswith(dirn))
|
|
70
93
|
|
|
94
|
+
newset = await sset.copy()
|
|
95
|
+
self.true(os.path.isdir(newset.slab.path))
|
|
96
|
+
self.true(os.path.abspath(newset.slab.path).startswith(dirn))
|
|
97
|
+
|
|
98
|
+
# Slabs should get removed on fini
|
|
99
|
+
self.false(os.path.isdir(sset.slab.path))
|
|
100
|
+
|
|
101
|
+
self.true(os.path.isdir(newset.slab.path))
|
|
102
|
+
await newset.fini()
|
|
103
|
+
self.false(os.path.isdir(newset.slab.path))
|
|
104
|
+
|
|
71
105
|
async def test_spooled_dict(self):
|
|
72
106
|
|
|
73
107
|
async def runtest(x):
|
synapse/tests/test_lib_storm.py
CHANGED
|
@@ -638,6 +638,8 @@ class StormTest(s_t_utils.SynTest):
|
|
|
638
638
|
with self.raises(s_exc.NoSuchVar):
|
|
639
639
|
await core.nodes('background { $lib.print($foo) }')
|
|
640
640
|
|
|
641
|
+
await core.nodes('background ${ $foo=test $lib.print($foo) }')
|
|
642
|
+
|
|
641
643
|
await core.nodes('background { $lib.time.sleep(4) }')
|
|
642
644
|
task = await core.callStorm('for $t in $lib.ps.list() { if $t.info.background { return($t) } }')
|
|
643
645
|
self.nn(task)
|
|
@@ -667,6 +669,9 @@ class StormTest(s_t_utils.SynTest):
|
|
|
667
669
|
q = '[ inet:fqdn=www.vertex.link ] $q=:domain | parallel $q'
|
|
668
670
|
await self.asyncraises(s_exc.StormRuntimeError, core.nodes(q))
|
|
669
671
|
|
|
672
|
+
nodes = await core.nodes('ou:org | parallel ${ $foo=bar [ :name=$foo ]}')
|
|
673
|
+
self.true(all([n.get('name') == 'bar' for n in nodes]))
|
|
674
|
+
|
|
670
675
|
# test $lib.exit() and the StormExit handlers
|
|
671
676
|
msgs = [m async for m in core.view.storm('$lib.exit()')]
|
|
672
677
|
self.eq(msgs[-1][0], 'fini')
|
|
@@ -2137,6 +2142,49 @@ class StormTest(s_t_utils.SynTest):
|
|
|
2137
2142
|
self.nn(bot.get('country::flag::md5'))
|
|
2138
2143
|
self.eq(bot['country::flag::md5'][0], 'fa818a259cbed7ce8bc2a22d35a464fc')
|
|
2139
2144
|
|
|
2145
|
+
await core.nodes('''
|
|
2146
|
+
[( risk:vulnerable=*
|
|
2147
|
+
:mitigated=true
|
|
2148
|
+
:node={ [ it:prod:hardware=* :name=foohw ] return($node.ndef()) }
|
|
2149
|
+
:vuln={[ risk:vuln=* :name=barvuln ]}
|
|
2150
|
+
+#test
|
|
2151
|
+
)]
|
|
2152
|
+
[( inet:service:rule=*
|
|
2153
|
+
:object={ risk:vulnerable#test return($node.ndef()) }
|
|
2154
|
+
:grantee={ [ inet:service:account=* :id=foocon ] return($node.ndef()) }
|
|
2155
|
+
+#test
|
|
2156
|
+
)]
|
|
2157
|
+
''')
|
|
2158
|
+
|
|
2159
|
+
opts = {
|
|
2160
|
+
'embeds': {
|
|
2161
|
+
'risk:vulnerable': {
|
|
2162
|
+
'vuln': ['name'],
|
|
2163
|
+
'node': ['name'],
|
|
2164
|
+
},
|
|
2165
|
+
'inet:service:rule': {
|
|
2166
|
+
'object': ['mitigated', 'newp'],
|
|
2167
|
+
'object::node': ['name', 'newp'],
|
|
2168
|
+
'grantee': ['id', 'newp'],
|
|
2169
|
+
}
|
|
2170
|
+
}
|
|
2171
|
+
}
|
|
2172
|
+
msgs = await core.stormlist('inet:service:rule#test :object -+> risk:vulnerable', opts=opts)
|
|
2173
|
+
nodes = sorted([m[1] for m in msgs if m[0] == 'node'], key=lambda p: p[0][0])
|
|
2174
|
+
self.eq(['inet:service:rule', 'risk:vulnerable'], [n[0][0] for n in nodes])
|
|
2175
|
+
|
|
2176
|
+
embeds = nodes[0][1]['embeds']
|
|
2177
|
+
self.eq(1, embeds['object']['mitigated'])
|
|
2178
|
+
self.eq(None, embeds['object']['newp'])
|
|
2179
|
+
self.eq('foohw', embeds['object::node']['name'])
|
|
2180
|
+
self.eq(None, embeds['object::node']['newp'])
|
|
2181
|
+
self.eq('foocon', embeds['grantee']['id'])
|
|
2182
|
+
self.eq(None, embeds['grantee']['newp'])
|
|
2183
|
+
|
|
2184
|
+
embeds = nodes[1][1]['embeds']
|
|
2185
|
+
self.eq('barvuln', embeds['vuln']['name'])
|
|
2186
|
+
self.eq('foohw', embeds['node']['name'])
|
|
2187
|
+
|
|
2140
2188
|
async def test_storm_wget(self):
|
|
2141
2189
|
|
|
2142
2190
|
async def _getRespFromSha(core, mesgs):
|
|
@@ -403,24 +403,7 @@ class StormlibModelTest(s_test.SynTest):
|
|
|
403
403
|
q = 'test:str=src $n=$node -> { test:str=deny $lib.model.migration.copyTags($n, $node) }'
|
|
404
404
|
await self.asyncraises(s_exc.AuthDeny, core.nodes(q, opts=aslow))
|
|
405
405
|
|
|
406
|
-
with self.raises(s_exc.NoSuchProp) as exc:
|
|
407
|
-
await core.callStorm('$lib.model.migration.liftByPropValuNoNorm(formname, propname, valu)')
|
|
408
|
-
self.eq(exc.exception.get('mesg'), 'Could not find prop: formname:propname')
|
|
409
|
-
|
|
410
|
-
with self.raises(s_exc.AuthDeny) as exc:
|
|
411
|
-
await core.callStorm('$lib.model.migration.liftByPropValuNoNorm(it:prod:soft, cpe, valu)')
|
|
412
|
-
self.eq(exc.exception.get('mesg'), '$lib.model.migration.liftByPropValuNoNorm() is restricted to model migrations only.')
|
|
413
|
-
|
|
414
|
-
with self.raises(s_exc.BadArg) as exc:
|
|
415
|
-
await core.callStorm('$lib.model.migration.setNodePropValuNoNorm(notanode, propname, valu)')
|
|
416
|
-
self.eq(exc.exception.get('mesg'), '$lib.model.migration.setNodePropValuNoNorm() argument must be a node.')
|
|
417
|
-
|
|
418
|
-
with self.raises(s_exc.AuthDeny) as exc:
|
|
419
|
-
await core.callStorm('test:str $lib.model.migration.setNodePropValuNoNorm($node, propname, valu)')
|
|
420
|
-
self.eq(exc.exception.get('mesg'), '$lib.model.migration.setNodePropValuNoNorm() is restricted to model migrations only.')
|
|
421
|
-
|
|
422
406
|
# copy extended properties
|
|
423
|
-
|
|
424
407
|
await self.asyncraises(s_exc.BadArg, core.nodes('test:str=src $lib.model.migration.copyExtProps($node, newp)'))
|
|
425
408
|
await self.asyncraises(s_exc.BadArg, core.nodes('test:str=dst $lib.model.migration.copyExtProps(newp, $node)'))
|
|
426
409
|
|
|
@@ -442,259 +425,6 @@ class StormlibModelTest(s_test.SynTest):
|
|
|
442
425
|
self.len(1, nodes)
|
|
443
426
|
self.eq(nodes[0].get('_foo'), 'foobarbaz')
|
|
444
427
|
|
|
445
|
-
async def test_model_migration_s_itSecCpe_2_170_0(self):
|
|
446
|
-
|
|
447
|
-
async with self.getRegrCore('itSecCpe_2_170_0', maxvers=(0, 2, 27)) as core:
|
|
448
|
-
# Migrate it:sec:cpe nodes with a valid CPE2.3, valid CPE2.2
|
|
449
|
-
q = 'it:sec:cpe +#test.cpe.23valid +#test.cpe.22valid'
|
|
450
|
-
nodes = await core.nodes(q)
|
|
451
|
-
self.len(2, nodes)
|
|
452
|
-
self.eq(
|
|
453
|
-
[
|
|
454
|
-
('it:sec:cpe', 'cpe:2.3:a:abine:donottrackme_-_mobile_privacy:1.1.8:*:*:*:*:android:*:*'),
|
|
455
|
-
('it:sec:cpe', 'cpe:2.3:a:01generator:pireospay:-:*:*:*:*:prestashop:*:*')
|
|
456
|
-
],
|
|
457
|
-
[node.ndef for node in nodes]
|
|
458
|
-
)
|
|
459
|
-
|
|
460
|
-
q = '''
|
|
461
|
-
it:sec:cpe +#test.cpe.23valid +#test.cpe.22valid
|
|
462
|
-
$lib.debug=$lib.true
|
|
463
|
-
$lib.model.migration.s.itSecCpe_2_170_0($node)
|
|
464
|
-
$node.data.load(migration.s.itSecCpe_2_170_0)
|
|
465
|
-
'''
|
|
466
|
-
nodes = await core.nodes(q)
|
|
467
|
-
|
|
468
|
-
data = nodes[0].nodedata['migration.s.itSecCpe_2_170_0']
|
|
469
|
-
self.nn(data)
|
|
470
|
-
self.eq(data['status'], 'success')
|
|
471
|
-
self.none(data.get('reason'))
|
|
472
|
-
|
|
473
|
-
data = nodes[1].nodedata['migration.s.itSecCpe_2_170_0']
|
|
474
|
-
self.nn(data)
|
|
475
|
-
self.eq(data['status'], 'success')
|
|
476
|
-
self.none(data.get('reason'))
|
|
477
|
-
|
|
478
|
-
async with self.getRegrCore('itSecCpe_2_170_0', maxvers=(0, 2, 27)) as core:
|
|
479
|
-
# Migrate it:sec:cpe nodes with a valid CPE2.3, invalid CPE2.2
|
|
480
|
-
q = '''
|
|
481
|
-
it:sec:cpe +#test.cpe.23valid +#test.cpe.22invalid
|
|
482
|
-
$lib.debug=$lib.true
|
|
483
|
-
$lib.model.migration.s.itSecCpe_2_170_0($node)
|
|
484
|
-
'''
|
|
485
|
-
nodes = await core.nodes(q)
|
|
486
|
-
self.len(3, nodes)
|
|
487
|
-
self.eq(
|
|
488
|
-
[
|
|
489
|
-
('it:sec:cpe', 'cpe:2.3:a:1c:1c\\:enterprise:-:*:*:*:*:*:*:*'),
|
|
490
|
-
('it:sec:cpe', 'cpe:2.3:o:zyxel:nas542_firmware:5.21\\%28aazf.15\\%29co:*:*:*:*:*:*:*'),
|
|
491
|
-
('it:sec:cpe', 'cpe:2.3:a:abinitio:control\\>center:-:*:*:*:*:*:*:*'),
|
|
492
|
-
],
|
|
493
|
-
[node.ndef for node in nodes]
|
|
494
|
-
)
|
|
495
|
-
|
|
496
|
-
q = '''
|
|
497
|
-
it:sec:cpe +#test.cpe.23valid +#test.cpe.22invalid
|
|
498
|
-
$node.data.load(migration.s.itSecCpe_2_170_0)
|
|
499
|
-
'''
|
|
500
|
-
nodes = await core.nodes(q)
|
|
501
|
-
self.len(3, nodes)
|
|
502
|
-
|
|
503
|
-
data = nodes[0].nodedata['migration.s.itSecCpe_2_170_0']
|
|
504
|
-
self.nn(data)
|
|
505
|
-
self.eq(data['status'], 'success')
|
|
506
|
-
self.eq(data['updated'], ['v2_2', 'product'])
|
|
507
|
-
self.eq(nodes[0].get('v2_2'), 'cpe:/a:1c:1c%3aenterprise:-')
|
|
508
|
-
self.eq(nodes[0].get('product'), '1c:enterprise')
|
|
509
|
-
|
|
510
|
-
data = nodes[1].nodedata['migration.s.itSecCpe_2_170_0']
|
|
511
|
-
self.nn(data)
|
|
512
|
-
self.eq(data['status'], 'success')
|
|
513
|
-
self.none(data.get('valu'))
|
|
514
|
-
self.eq(data['updated'], ['v2_2', 'version'])
|
|
515
|
-
self.eq(nodes[1].get('v2_2'), 'cpe:/o:zyxel:nas542_firmware:5.21%2528aazf.15%2529co')
|
|
516
|
-
self.eq(nodes[1].get('version'), '5.21%28aazf.15%29co')
|
|
517
|
-
|
|
518
|
-
data = nodes[2].nodedata['migration.s.itSecCpe_2_170_0']
|
|
519
|
-
self.nn(data)
|
|
520
|
-
self.eq(data['status'], 'success')
|
|
521
|
-
self.eq(data['updated'], ['v2_2', 'product'])
|
|
522
|
-
self.eq(nodes[2].get('v2_2'), 'cpe:/a:abinitio:control%3ecenter:-')
|
|
523
|
-
self.eq(nodes[2].get('product'), 'control>center')
|
|
524
|
-
|
|
525
|
-
# The migration of this node was not correct because the CPE2.3 string (primary property) is valid but was
|
|
526
|
-
# not created correctly due to a bad CPE2.2 input value. Now we update :v2_2 to be correct, and re-run the
|
|
527
|
-
# migration. This time, we specify `prefer_v22=True` and `force=True` so the migration will use the updated
|
|
528
|
-
# :v2_2 prop for reparsing the strings. Force will cause the migration to continue past the check where both
|
|
529
|
-
# the primary property and :v2_2 are valid.
|
|
530
|
-
q = '''
|
|
531
|
-
it:sec:cpe:product=nas542_firmware [ :v2_2="cpe:/o:zyxel:nas542_firmware:5.21%28aazf.15%29co" ]
|
|
532
|
-
$lib.debug=$lib.true
|
|
533
|
-
$lib.model.migration.s.itSecCpe_2_170_0($node, prefer_v22=$lib.true, force=$lib.true)
|
|
534
|
-
'''
|
|
535
|
-
nodes = await core.nodes(q)
|
|
536
|
-
self.len(1, nodes)
|
|
537
|
-
|
|
538
|
-
# Lift the updated node and check the migration did what was expected.
|
|
539
|
-
q = '''
|
|
540
|
-
it:sec:cpe:product=nas542_firmware
|
|
541
|
-
$node.data.load(migration.s.itSecCpe_2_170_0)
|
|
542
|
-
'''
|
|
543
|
-
nodes = await core.nodes(q)
|
|
544
|
-
self.len(1, nodes)
|
|
545
|
-
|
|
546
|
-
data = nodes[0].nodedata['migration.s.itSecCpe_2_170_0']
|
|
547
|
-
self.nn(data)
|
|
548
|
-
self.eq(data['status'], 'success')
|
|
549
|
-
self.eq(data['updated'], ['version'])
|
|
550
|
-
self.eq(data['valu'], 'cpe:2.3:o:zyxel:nas542_firmware:5.21\\(aazf.15\\)co:*:*:*:*:*:*:*')
|
|
551
|
-
self.eq(nodes[0].get('v2_2'), 'cpe:/o:zyxel:nas542_firmware:5.21%28aazf.15%29co')
|
|
552
|
-
self.eq(nodes[0].get('version'), '5.21(aazf.15)co')
|
|
553
|
-
|
|
554
|
-
async with self.getRegrCore('itSecCpe_2_170_0', maxvers=(0, 2, 27)) as core:
|
|
555
|
-
# Migrate it:sec:cpe nodes with a invalid CPE2.3, valid CPE2.2
|
|
556
|
-
q = '''
|
|
557
|
-
it:sec:cpe +#test.cpe.23invalid +#test.cpe.22valid
|
|
558
|
-
$lib.debug=$lib.true
|
|
559
|
-
$lib.model.migration.s.itSecCpe_2_170_0($node)
|
|
560
|
-
'''
|
|
561
|
-
nodes = await core.nodes(q)
|
|
562
|
-
self.len(4, nodes)
|
|
563
|
-
self.eq(
|
|
564
|
-
[
|
|
565
|
-
('it:sec:cpe', 'cpe:2.3:h:d\\-link:dir\\-850l:*:*:*:*:*:*:*:*'),
|
|
566
|
-
('it:sec:cpe', 'cpe:2.3:a:acurax:under_construction_%2f_maintenance_mode:-::~~~wordpress~~:*:*:*:*:*'),
|
|
567
|
-
('it:sec:cpe', 'cpe:2.3:a:10web:social_feed_for_instagram:1.0.0::~~premium~wordpress~~:*:*:*:*:*'),
|
|
568
|
-
('it:sec:cpe', 'cpe:2.3:o:zyxel:nas326_firmware:5.21%28aazf.14%29c0:*:*:*:*:*:*:*'),
|
|
569
|
-
],
|
|
570
|
-
[node.ndef for node in nodes]
|
|
571
|
-
)
|
|
572
|
-
|
|
573
|
-
q = '''
|
|
574
|
-
it:sec:cpe +#test.cpe.23invalid +#test.cpe.22valid
|
|
575
|
-
$node.data.load(migration.s.itSecCpe_2_170_0)
|
|
576
|
-
'''
|
|
577
|
-
nodes = await core.nodes(q)
|
|
578
|
-
self.len(4, nodes)
|
|
579
|
-
|
|
580
|
-
data = nodes[0].nodedata['migration.s.itSecCpe_2_170_0']
|
|
581
|
-
self.nn(data)
|
|
582
|
-
self.eq(data['status'], 'success')
|
|
583
|
-
self.eq(data['updated'], ['vendor', 'product'])
|
|
584
|
-
self.eq(data['valu'], 'cpe:2.3:h:d-link:dir-850l:*:*:*:*:*:*:*:*')
|
|
585
|
-
self.eq(nodes[0].get('vendor'), 'd-link')
|
|
586
|
-
self.eq(nodes[0].get('product'), 'dir-850l')
|
|
587
|
-
|
|
588
|
-
data = nodes[1].nodedata['migration.s.itSecCpe_2_170_0']
|
|
589
|
-
self.nn(data)
|
|
590
|
-
self.eq(data['status'], 'success')
|
|
591
|
-
self.eq(data['updated'], ['product', 'update', 'edition', 'target_sw'])
|
|
592
|
-
self.eq(data['valu'], 'cpe:2.3:a:acurax:under_construction_\\/_maintenance_mode:-:*:*:*:*:wordpress:*:*')
|
|
593
|
-
self.eq(nodes[1].get('product'), 'under_construction_/_maintenance_mode')
|
|
594
|
-
self.eq(nodes[1].get('update'), '*')
|
|
595
|
-
self.eq(nodes[1].get('edition'), '*')
|
|
596
|
-
self.eq(nodes[1].get('target_sw'), 'wordpress')
|
|
597
|
-
|
|
598
|
-
data = nodes[2].nodedata['migration.s.itSecCpe_2_170_0']
|
|
599
|
-
self.nn(data)
|
|
600
|
-
self.eq(data['status'], 'success')
|
|
601
|
-
self.eq(data['updated'], ['update', 'edition', 'sw_edition', 'target_sw'])
|
|
602
|
-
self.eq(data['valu'], 'cpe:2.3:a:10web:social_feed_for_instagram:1.0.0:*:*:*:premium:wordpress:*:*')
|
|
603
|
-
self.eq(nodes[2].get('update'), '*')
|
|
604
|
-
self.eq(nodes[2].get('edition'), '*')
|
|
605
|
-
self.eq(nodes[2].get('sw_edition'), 'premium')
|
|
606
|
-
self.eq(nodes[2].get('target_sw'), 'wordpress')
|
|
607
|
-
|
|
608
|
-
data = nodes[3].nodedata['migration.s.itSecCpe_2_170_0']
|
|
609
|
-
self.nn(data)
|
|
610
|
-
self.eq(data['status'], 'success')
|
|
611
|
-
self.eq(data['updated'], ['version'])
|
|
612
|
-
self.eq(data['valu'], 'cpe:2.3:o:zyxel:nas326_firmware:5.21\\(aazf.14\\)c0:*:*:*:*:*:*:*')
|
|
613
|
-
self.eq(nodes[3].get('version'), '5.21(aazf.14)c0')
|
|
614
|
-
|
|
615
|
-
async with self.getRegrCore('itSecCpe_2_170_0', maxvers=(0, 2, 27)) as core:
|
|
616
|
-
# Migrate it:sec:cpe nodes with a invalid CPE2.3, invalid CPE2.2
|
|
617
|
-
q = '''
|
|
618
|
-
it:sec:cpe +#test.cpe.23invalid +#test.cpe.22invalid
|
|
619
|
-
$lib.debug=$lib.true
|
|
620
|
-
$lib.model.migration.s.itSecCpe_2_170_0($node)
|
|
621
|
-
'''
|
|
622
|
-
msgs = await core.stormlist(q)
|
|
623
|
-
mesg = 'itSecCpe_2_170_0(it:sec:cpe=cpe:2.3:a:openbsd:openssh:8.2p1 ubuntu-4ubuntu0.2:*:*:*:*:*:*:*): '
|
|
624
|
-
mesg += 'Unable to migrate due to invalid data. Primary property and :v2_2 are both invalid.'
|
|
625
|
-
self.stormIsInWarn(mesg, msgs)
|
|
626
|
-
|
|
627
|
-
ndefs = [m[1][0] for m in msgs if m[0] == 'node']
|
|
628
|
-
self.eq(
|
|
629
|
-
[
|
|
630
|
-
('it:sec:cpe', 'cpe:2.3:a:openbsd:openssh:7.4\r\n:*:*:*:*:*:*:*'),
|
|
631
|
-
('it:sec:cpe', 'cpe:2.3:a:openbsd:openssh:8.2p1 ubuntu-4ubuntu0.2:*:*:*:*:*:*:*')
|
|
632
|
-
],
|
|
633
|
-
ndefs
|
|
634
|
-
)
|
|
635
|
-
|
|
636
|
-
q = '''
|
|
637
|
-
it:sec:cpe +#test.cpe.23invalid +#test.cpe.22invalid
|
|
638
|
-
$node.data.load(migration.s.itSecCpe_2_170_0)
|
|
639
|
-
'''
|
|
640
|
-
nodes = await core.nodes(q)
|
|
641
|
-
self.len(2, nodes)
|
|
642
|
-
|
|
643
|
-
for node in nodes:
|
|
644
|
-
data = node.nodedata['migration.s.itSecCpe_2_170_0']
|
|
645
|
-
self.eq(data, {
|
|
646
|
-
'status': 'failed',
|
|
647
|
-
'reason': 'Unable to migrate due to invalid data. Primary property and :v2_2 are both invalid.',
|
|
648
|
-
})
|
|
649
|
-
|
|
650
|
-
# Now update the :v2_2 on one of the nodes and migrate again
|
|
651
|
-
q = '''
|
|
652
|
-
it:sec:cpe:version^=8.2p1 [ :v2_2="cpe:/a:openbsd:openssh:8.2p1_ubuntu-4ubuntu0.2" ]
|
|
653
|
-
$lib.debug=$lib.true
|
|
654
|
-
$lib.model.migration.s.itSecCpe_2_170_0($node)
|
|
655
|
-
'''
|
|
656
|
-
msgs = await core.stormlist(q)
|
|
657
|
-
self.stormHasNoWarnErr(msgs)
|
|
658
|
-
|
|
659
|
-
q = '''
|
|
660
|
-
it:sec:cpe:version^=8.2p1
|
|
661
|
-
$node.data.load(migration.s.itSecCpe_2_170_0)
|
|
662
|
-
'''
|
|
663
|
-
nodes = await core.nodes(q)
|
|
664
|
-
self.len(1, nodes)
|
|
665
|
-
|
|
666
|
-
data = nodes[0].nodedata['migration.s.itSecCpe_2_170_0']
|
|
667
|
-
self.nn(data)
|
|
668
|
-
self.eq(data['status'], 'success')
|
|
669
|
-
self.eq(data['updated'], ['version'])
|
|
670
|
-
self.eq(data['valu'], 'cpe:2.3:a:openbsd:openssh:8.2p1_ubuntu-4ubuntu0.2:*:*:*:*:*:*:*')
|
|
671
|
-
self.eq(nodes[0].get('version'), '8.2p1_ubuntu-4ubuntu0.2')
|
|
672
|
-
|
|
673
|
-
# Run the migration again to make sure we identify already migrated
|
|
674
|
-
# nodes correctly and bail early.
|
|
675
|
-
q = '''
|
|
676
|
-
it:sec:cpe:version^=8.2p1
|
|
677
|
-
$lib.debug=$lib.true
|
|
678
|
-
$lib.model.migration.s.itSecCpe_2_170_0($node)
|
|
679
|
-
'''
|
|
680
|
-
msgs = await core.stormlist(q)
|
|
681
|
-
self.stormIsInPrint('DEBUG: itSecCpe_2_170_0(it:sec:cpe=cpe:2.3:a:openbsd:openssh:8.2p1 ubuntu-4ubuntu0.2:*:*:*:*:*:*:*): Node already migrated.', msgs)
|
|
682
|
-
|
|
683
|
-
q = '''
|
|
684
|
-
it:sec:cpe:version^=8.2p1
|
|
685
|
-
$lib.debug=$lib.true
|
|
686
|
-
$lib.model.migration.s.itSecCpe_2_170_0($node, force=$lib.true)
|
|
687
|
-
'''
|
|
688
|
-
msgs = await core.stormlist(q)
|
|
689
|
-
self.stormIsInPrint('DEBUG: itSecCpe_2_170_0(it:sec:cpe=cpe:2.3:a:openbsd:openssh:8.2p1 ubuntu-4ubuntu0.2:*:*:*:*:*:*:*): No property updates required.', msgs)
|
|
690
|
-
|
|
691
|
-
async with self.getTestCore() as core:
|
|
692
|
-
with self.raises(s_exc.BadArg):
|
|
693
|
-
await core.callStorm('$lib.model.migration.s.itSecCpe_2_170_0(newp)')
|
|
694
|
-
|
|
695
|
-
with self.raises(s_exc.BadArg):
|
|
696
|
-
await core.callStorm('[ inet:fqdn=vertex.link ] $lib.model.migration.s.itSecCpe_2_170_0($node)')
|
|
697
|
-
|
|
698
428
|
async def test_stormlib_model_migrations_risk_hasvuln_vulnerable(self):
|
|
699
429
|
|
|
700
430
|
async with self.getTestCore() as core:
|
|
@@ -25,6 +25,13 @@ class StormtypesModelextTest(s_test.SynTest):
|
|
|
25
25
|
|
|
26
26
|
$edgeinfo = ({"doc": "A test edge."})
|
|
27
27
|
$lib.model.ext.addEdge(inet:user, _copies, *, $edgeinfo)
|
|
28
|
+
|
|
29
|
+
$typeopts = ({"lower": true, "onespace": true})
|
|
30
|
+
$typeinfo = ({"doc": "A test type doc."})
|
|
31
|
+
$forminfo = ({"doc": "A test type form doc."})
|
|
32
|
+
$lib.model.ext.addType(_test:type, str, $typeopts, $typeinfo)
|
|
33
|
+
$lib.model.ext.addForm(_test:typeform, _test:type, ({}), $forminfo)
|
|
34
|
+
$lib.model.ext.addForm(_test:typearry, array, ({"type": "_test:type"}), $forminfo)
|
|
28
35
|
''')
|
|
29
36
|
|
|
30
37
|
nodes = await core.nodes('[ _visi:int=10 :tick=20210101 ._woot=30 +#lol:score=99 ]')
|
|
@@ -39,6 +46,18 @@ class StormtypesModelextTest(s_test.SynTest):
|
|
|
39
46
|
self.eq(nodes[0].ndef, ('test:int', 1234))
|
|
40
47
|
self.eq(nodes[0].get('_tick'), 1609459200000)
|
|
41
48
|
|
|
49
|
+
nodes = await core.nodes('[_test:typeform=" FoO BaR "]')
|
|
50
|
+
self.len(1, nodes)
|
|
51
|
+
self.eq(nodes[0].ndef, ('_test:typeform', 'foo bar'))
|
|
52
|
+
|
|
53
|
+
with self.raises(s_exc.DupTypeName):
|
|
54
|
+
q = '$lib.model.ext.addType(_test:type, str, ({}), ({}))'
|
|
55
|
+
await core.callStorm(q)
|
|
56
|
+
|
|
57
|
+
with self.raises(s_exc.DupTypeName):
|
|
58
|
+
q = '$lib.model.ext.addForm(_test:type, str, ({}), ({}))'
|
|
59
|
+
await core.callStorm(q)
|
|
60
|
+
|
|
42
61
|
with self.raises(s_exc.DupPropName):
|
|
43
62
|
q = '''$lib.model.ext.addFormProp(_visi:int, tick, (time, ({})), ({}))'''
|
|
44
63
|
await core.callStorm(q)
|
|
@@ -69,7 +88,7 @@ class StormtypesModelextTest(s_test.SynTest):
|
|
|
69
88
|
await core._delAllTagProp('score', {})
|
|
70
89
|
self.len(0, await core.nodes('#lol:score'))
|
|
71
90
|
|
|
72
|
-
await core.callStorm('_visi:int=10 test:int=1234 | delnode')
|
|
91
|
+
await core.callStorm('_visi:int=10 test:int=1234 _test:typeform | delnode')
|
|
73
92
|
await core.callStorm('''
|
|
74
93
|
$lib.model.ext.delTagProp(score, force=(true))
|
|
75
94
|
$lib.model.ext.delUnivProp(_woot, force=(true))
|
|
@@ -79,6 +98,22 @@ class StormtypesModelextTest(s_test.SynTest):
|
|
|
79
98
|
$lib.model.ext.delEdge(inet:user, _copies, *)
|
|
80
99
|
''')
|
|
81
100
|
|
|
101
|
+
with self.raises(s_exc.CantDelType) as cm:
|
|
102
|
+
await core.callStorm('$lib.model.ext.delType(_test:type)')
|
|
103
|
+
self.isin('still in use by other types', cm.exception.get('mesg'))
|
|
104
|
+
|
|
105
|
+
await core.callStorm('$lib.model.ext.delForm(_test:typeform)')
|
|
106
|
+
|
|
107
|
+
with self.raises(s_exc.CantDelType) as cm:
|
|
108
|
+
await core.callStorm('$lib.model.ext.delType(_test:type)')
|
|
109
|
+
self.isin('still in use by array types', cm.exception.get('mesg'))
|
|
110
|
+
|
|
111
|
+
await core.callStorm('$lib.model.ext.delForm(_test:typearry)')
|
|
112
|
+
await core.callStorm('$lib.model.ext.delType(_test:type)')
|
|
113
|
+
|
|
114
|
+
self.none(core.model.type('_test:type'))
|
|
115
|
+
self.none(core.model.form('_test:typeform'))
|
|
116
|
+
self.none(core.model.form('_test:typearry'))
|
|
82
117
|
self.none(core.model.form('_visi:int'))
|
|
83
118
|
self.none(core.model.prop('._woot'))
|
|
84
119
|
self.none(core.model.prop('_visi:int:tick'))
|
|
@@ -100,6 +135,14 @@ class StormtypesModelextTest(s_test.SynTest):
|
|
|
100
135
|
q = '''$lib.model.ext.addTagProp(some:_score, (int, ({})), ({}))'''
|
|
101
136
|
self.none(await core.callStorm(q))
|
|
102
137
|
|
|
138
|
+
with self.raises(s_exc.BadTypeDef):
|
|
139
|
+
q = '$lib.model.ext.addType(test:type, str, ({}), ({}))'
|
|
140
|
+
await core.callStorm(q)
|
|
141
|
+
|
|
142
|
+
with self.raises(s_exc.BadTypeDef):
|
|
143
|
+
q = '$lib.model.ext.delType(test:type)'
|
|
144
|
+
await core.callStorm(q)
|
|
145
|
+
|
|
103
146
|
with self.raises(s_exc.BadPropDef):
|
|
104
147
|
q = '''$l =$lib.list('str', ({})) $d=({"doc": "Foo"})
|
|
105
148
|
$lib.model.ext.addFormProp('test:str', '_test:_my^prop', $l, $d)
|
|
@@ -158,6 +201,18 @@ class StormtypesModelextTest(s_test.SynTest):
|
|
|
158
201
|
$lib.model.ext.addForm(_visi:int, int, $typeinfo, $forminfo)
|
|
159
202
|
''', opts=opts)
|
|
160
203
|
|
|
204
|
+
with self.raises(s_exc.AuthDeny) as cm:
|
|
205
|
+
await core.callStorm('''
|
|
206
|
+
$lib.model.ext.addType(_test:type, str, ({}), ({}))
|
|
207
|
+
''', opts=opts)
|
|
208
|
+
self.isin('permission model.type.add._test:type', cm.exception.get('mesg'))
|
|
209
|
+
|
|
210
|
+
with self.raises(s_exc.AuthDeny) as cm:
|
|
211
|
+
await core.callStorm('''
|
|
212
|
+
$lib.model.ext.delType(_test:type)
|
|
213
|
+
''', opts=opts)
|
|
214
|
+
self.isin('permission model.type.del._test:type', cm.exception.get('mesg'))
|
|
215
|
+
|
|
161
216
|
with self.raises(s_exc.AuthDeny):
|
|
162
217
|
await core.callStorm('''
|
|
163
218
|
$propinfo = ({"doc": "A test prop doc."})
|
|
@@ -206,6 +261,10 @@ class StormtypesModelextTest(s_test.SynTest):
|
|
|
206
261
|
# Add props which conflict with what was previously dumped
|
|
207
262
|
async with self.getTestCore() as core:
|
|
208
263
|
await core.callStorm('''
|
|
264
|
+
$typeopts = ({"lower": true})
|
|
265
|
+
$typeinfo = ({"doc": "A test type doc."})
|
|
266
|
+
$lib.model.ext.addType(_test:type, str, $typeopts, $typeinfo)
|
|
267
|
+
|
|
209
268
|
$typeinfo = ({})
|
|
210
269
|
$forminfo = ({"doc": "NEWP"})
|
|
211
270
|
$lib.model.ext.addForm(_visi:int, int, $typeinfo, $forminfo)
|
|
@@ -226,6 +285,11 @@ class StormtypesModelextTest(s_test.SynTest):
|
|
|
226
285
|
$lib.model.ext.addEdge(inet:user, _copies, *, $edgeinfo)
|
|
227
286
|
''')
|
|
228
287
|
|
|
288
|
+
q = '''return ($lib.model.ext.addExtModel($model_defs))'''
|
|
289
|
+
with self.raises(s_exc.BadTypeDef) as cm:
|
|
290
|
+
opts = {'vars': {'model_defs': {'types': model_defs['types']}}}
|
|
291
|
+
await core.callStorm(q, opts)
|
|
292
|
+
|
|
229
293
|
q = '''return ($lib.model.ext.addExtModel($model_defs))'''
|
|
230
294
|
with self.raises(s_exc.BadFormDef) as cm:
|
|
231
295
|
opts = {'vars': {'model_defs': {'forms': model_defs['forms']}}}
|
|
@@ -255,6 +319,9 @@ class StormtypesModelextTest(s_test.SynTest):
|
|
|
255
319
|
async with self.getTestCore() as core:
|
|
256
320
|
opts = {'vars': {'model_defs': model_defs}}
|
|
257
321
|
q = '''
|
|
322
|
+
for ($name, $type, $opts, $info) in $model_defs.types {
|
|
323
|
+
$lib.model.ext.addType($name, $type, $opts, $info)
|
|
324
|
+
}
|
|
258
325
|
for ($name, $type, $opts, $info) in $model_defs.forms {
|
|
259
326
|
$lib.model.ext.addForm($name, $type, $opts, $info)
|
|
260
327
|
}
|
|
@@ -473,6 +540,14 @@ class StormtypesModelextTest(s_test.SynTest):
|
|
|
473
540
|
'$lib.model.ext.addForm(inet:fqdn, _foo:bar, ({}), ())',
|
|
474
541
|
'Form type info should be a dict.'
|
|
475
542
|
),
|
|
543
|
+
(
|
|
544
|
+
'$lib.model.ext.addType(_test:type, str, (guid, ()), ())',
|
|
545
|
+
'Type options should be a dict.'
|
|
546
|
+
),
|
|
547
|
+
(
|
|
548
|
+
'$lib.model.ext.addType(_test:type, str, ({}), ())',
|
|
549
|
+
'Type info should be a dict.'
|
|
550
|
+
),
|
|
476
551
|
(
|
|
477
552
|
'$lib.model.ext.addFormProp(inet:fqdn, _foo:bar, ({}), ())',
|
|
478
553
|
'Form property type definitions should be a tuple.'
|
|
@@ -105,14 +105,6 @@ class StormScrapeTest(s_test.SynTest):
|
|
|
105
105
|
self.stormIsInPrint('inet:url=https://giggles.com/mallory.html', msgs)
|
|
106
106
|
self.stormIsInPrint("'match': 'hzzps[:]\\\\giggles.com/mallory.html'", msgs)
|
|
107
107
|
|
|
108
|
-
with mock.patch('synapse.lib.scrape.SCRAPE_SPAWN_LENGTH', 0):
|
|
109
|
-
msgs = await core.stormlist(q, opts={'vars': {'text': text}})
|
|
110
|
-
self.stormIsInPrint('ps:name=alice', msgs)
|
|
111
|
-
self.stormIsInPrint('inet:fqdn=foo.bar.com', msgs)
|
|
112
|
-
self.stormIsInPrint('inet:url=https://1.2.3.4/alice.html', msgs)
|
|
113
|
-
self.stormIsInPrint('inet:url=https://giggles.com/mallory.html', msgs)
|
|
114
|
-
self.stormIsInPrint("'match': 'hzzps[:]\\\\giggles.com/mallory.html'", msgs)
|
|
115
|
-
|
|
116
108
|
cq = '''$ret=$lib.list()
|
|
117
109
|
for ($form, $valu) in $lib.scrape.ndefs($text) {
|
|
118
110
|
$ret.append(($form, $valu))
|
|
@@ -907,6 +907,17 @@ class StormTypesTest(s_test.SynTest):
|
|
|
907
907
|
'refs',
|
|
908
908
|
'20153b758f9d5eaaa38e4f4a65c36da797c3e59e549620fa7c4895e1a920991f'), edges)
|
|
909
909
|
|
|
910
|
+
data = await core.callStorm('''
|
|
911
|
+
$data = ({})
|
|
912
|
+
inet:user=visi
|
|
913
|
+
for ($name, $valu) in $lib.layer.get().getNodeData($node.iden()) { $data.$name = $valu }
|
|
914
|
+
return($data)
|
|
915
|
+
''')
|
|
916
|
+
foo = data.get('foo')
|
|
917
|
+
self.nn(foo)
|
|
918
|
+
self.nn(foo.get('asof'))
|
|
919
|
+
self.eq('bar', foo.get('data'))
|
|
920
|
+
|
|
910
921
|
msgs = await core.stormlist('$lib.print($lib.null)')
|
|
911
922
|
self.stormIsInPrint('$lib.null', msgs)
|
|
912
923
|
self.stormNotInPrint('None', msgs)
|
|
@@ -2331,7 +2342,7 @@ class StormTypesTest(s_test.SynTest):
|
|
|
2331
2342
|
path = pode[1]['path']
|
|
2332
2343
|
self.len(1, path)
|
|
2333
2344
|
key = list(path.keys())[0]
|
|
2334
|
-
self.true(key.startswith(
|
|
2345
|
+
self.true(key.startswith('vertex.link'))
|
|
2335
2346
|
self.eq(('foo', 'bar'), path[key])
|
|
2336
2347
|
|
|
2337
2348
|
q = '''
|