synapse 2.187.0__py311-none-any.whl → 2.188.1__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 (46) hide show
  1. synapse/cortex.py +131 -7
  2. synapse/datamodel.py +20 -4
  3. synapse/exc.py +14 -1
  4. synapse/lib/ast.py +6 -4
  5. synapse/lib/auth.py +9 -0
  6. synapse/lib/drive.py +1 -1
  7. synapse/lib/httpapi.py +2 -1
  8. synapse/lib/nexus.py +6 -0
  9. synapse/lib/node.py +5 -3
  10. synapse/lib/scrape.py +18 -104
  11. synapse/lib/storm.py +44 -28
  12. synapse/lib/stormlib/modelext.py +31 -0
  13. synapse/lib/stormlib/scrape.py +1 -4
  14. synapse/lib/stormtypes.py +17 -1
  15. synapse/lib/version.py +2 -2
  16. synapse/lib/view.py +9 -3
  17. synapse/models/base.py +27 -0
  18. synapse/models/files.py +22 -0
  19. synapse/models/inet.py +49 -4
  20. synapse/models/orgs.py +64 -2
  21. synapse/models/proj.py +1 -6
  22. synapse/models/risk.py +65 -0
  23. synapse/tests/test_cortex.py +21 -0
  24. synapse/tests/test_lib_agenda.py +13 -0
  25. synapse/tests/test_lib_auth.py +15 -0
  26. synapse/tests/test_lib_cell.py +170 -159
  27. synapse/tests/test_lib_httpapi.py +6 -0
  28. synapse/tests/test_lib_nexus.py +26 -0
  29. synapse/tests/test_lib_scrape.py +14 -6
  30. synapse/tests/test_lib_storm.py +48 -0
  31. synapse/tests/test_lib_stormlib_modelext.py +76 -1
  32. synapse/tests/test_lib_stormlib_scrape.py +0 -8
  33. synapse/tests/test_lib_stormtypes.py +1 -1
  34. synapse/tests/test_lib_trigger.py +8 -0
  35. synapse/tests/test_lib_view.py +24 -0
  36. synapse/tests/test_model_base.py +11 -0
  37. synapse/tests/test_model_files.py +19 -0
  38. synapse/tests/test_model_inet.py +33 -0
  39. synapse/tests/test_model_orgs.py +39 -0
  40. synapse/tests/test_model_proj.py +11 -1
  41. synapse/tests/test_model_risk.py +32 -0
  42. {synapse-2.187.0.dist-info → synapse-2.188.1.dist-info}/METADATA +1 -1
  43. {synapse-2.187.0.dist-info → synapse-2.188.1.dist-info}/RECORD +46 -46
  44. {synapse-2.187.0.dist-info → synapse-2.188.1.dist-info}/WHEEL +1 -1
  45. {synapse-2.187.0.dist-info → synapse-2.188.1.dist-info}/LICENSE +0 -0
  46. {synapse-2.187.0.dist-info → synapse-2.188.1.dist-info}/top_level.txt +0 -0
@@ -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))
@@ -2342,7 +2342,7 @@ class StormTypesTest(s_test.SynTest):
2342
2342
  path = pode[1]['path']
2343
2343
  self.len(1, path)
2344
2344
  key = list(path.keys())[0]
2345
- self.true(key.startswith("Node{(('inet:fqdn', 'vertex.link'), {'iden':"))
2345
+ self.true(key.startswith('vertex.link'))
2346
2346
  self.eq(('foo', 'bar'), path[key])
2347
2347
 
2348
2348
  q = '''
@@ -264,6 +264,14 @@ class TrigTest(s_t_utils.SynTest):
264
264
  await view.addTrigger(tdef)
265
265
  self.eq(pdef0.get('storm'), (await view.getTrigger(iden)).tdef.get('storm'))
266
266
 
267
+ with self.raises(s_exc.DupIden):
268
+ tdef = {'cond': 'node:add', 'storm': '[ +#dupiden ]', 'form': 'test:int', 'iden': view.iden}
269
+ await view.addTrigger(tdef)
270
+
271
+ with self.raises(s_exc.NoSuchIden):
272
+ await view.delTrigger(view.iden)
273
+ self.nn(core.auth.getAuthGate(view.iden))
274
+
267
275
  # Bad trigger parms
268
276
  with self.raises(s_exc.SchemaViolation):
269
277
  await view.addTrigger({'cond': 'nocond', 'storm': 'test:int=4', 'form': 'test:str'})
@@ -905,3 +905,27 @@ class ViewTest(s_t_utils.SynTest):
905
905
 
906
906
  with self.raises(s_exc.BadState):
907
907
  await core.callStorm('return($lib.view.get().insertParentFork().iden)')
908
+
909
+ async def test_view_children(self):
910
+
911
+ async with self.getTestCore() as core:
912
+
913
+ view00 = core.getView()
914
+ view01 = core.getView((await view00.fork())['iden'])
915
+ view02 = core.getView((await view01.fork())['iden'])
916
+ view03 = core.getView((await view01.fork())['iden'])
917
+
918
+ q = '''
919
+ $kids = ([])
920
+ for $child in $lib.view.get($iden).children() { $kids.append($child.iden) }
921
+ return($kids)
922
+ '''
923
+
924
+ opts = {'vars': {'iden': view00.iden}}
925
+ self.eq([view01.iden], await core.callStorm(q, opts=opts))
926
+
927
+ opts['vars']['iden'] = view01.iden
928
+ self.eq([view02.iden, view03.iden], await core.callStorm(q, opts=opts))
929
+
930
+ opts['vars']['iden'] = view02.iden
931
+ self.eq([], await core.callStorm(q, opts=opts))
@@ -391,3 +391,14 @@ class BaseTest(s_t_utils.SynTest):
391
391
  for node in nodes:
392
392
  form = core.model.form(node.ndef[1])
393
393
  self.true(form.deprecated, msg=form)
394
+
395
+ async def test_model_aggregate(self):
396
+
397
+ async with self.getTestCore() as core:
398
+
399
+ nodes = await core.nodes('[ meta:aggregate=* :count=99 :type=bottles :time=20240202 ]')
400
+ self.len(1, nodes)
401
+ self.eq(99, nodes[0].get('count'))
402
+ self.eq('bottles.', nodes[0].get('type'))
403
+ self.eq(1706832000000, nodes[0].get('time'))
404
+ self.len(1, await core.nodes('meta:aggregate -> meta:aggregate:type:taxonomy'))
@@ -638,3 +638,22 @@ class FileTest(s_t_utils.SynTest):
638
638
  self.eq(node.get('iconindex'), 1)
639
639
 
640
640
  self.len(1, await core.nodes('file:mime:lnk -> it:hostname'))
641
+
642
+ async def test_model_file_attachment(self):
643
+
644
+ async with self.getTestCore() as core:
645
+
646
+ nodes = await core.nodes('''
647
+ [ file:attachment=*
648
+ :name=Foo/Bar.exe
649
+ :text="foo bar"
650
+ :file=*
651
+ ]
652
+ ''')
653
+ self.len(1, nodes)
654
+ self.nn(nodes[0].get('file'))
655
+ self.eq('foo bar', nodes[0].get('text'))
656
+ self.eq('foo/bar.exe', nodes[0].get('name'))
657
+
658
+ self.len(1, await core.nodes('file:attachment -> file:bytes'))
659
+ self.len(1, await core.nodes('file:attachment -> file:path'))
@@ -455,6 +455,8 @@ class InetModelTest(s_t_utils.SynTest):
455
455
  :src:rdp:hostname=SYNCODER
456
456
  :src:rdp:keyboard:layout=AZERTY
457
457
  :raw=((10), (20))
458
+ :src:txfiles={[ file:attachment=* :name=foo.exe ]}
459
+ :dst:txfiles={[ file:attachment=* :name=bar.exe ]}
458
460
  )]'''
459
461
  nodes = await core.nodes(q, opts={'vars': {'valu': valu, 'p': props}})
460
462
  self.len(1, nodes)
@@ -501,6 +503,8 @@ class InetModelTest(s_t_utils.SynTest):
501
503
  self.len(2, await core.nodes('inet:flow -> crypto:x509:cert'))
502
504
  self.len(1, await core.nodes('inet:flow :src:ssh:key -> crypto:key'))
503
505
  self.len(1, await core.nodes('inet:flow :dst:ssh:key -> crypto:key'))
506
+ self.len(1, await core.nodes('inet:flow :src:txfiles -> file:attachment +:name=foo.exe'))
507
+ self.len(1, await core.nodes('inet:flow :dst:txfiles -> file:attachment +:name=bar.exe'))
504
508
 
505
509
  async def test_fqdn(self):
506
510
  formname = 'inet:fqdn'
@@ -3374,3 +3378,32 @@ class InetModelTest(s_t_utils.SynTest):
3374
3378
  :channel -> inet:service:channel
3375
3379
  +:name="/r/synapse"
3376
3380
  '''))
3381
+
3382
+ nodes = await core.nodes('''
3383
+ [ inet:service:relationship=*
3384
+ :source={ inet:service:account:user=visi }
3385
+ :target={ inet:service:account:user=visi }
3386
+ :type=follows
3387
+ ]
3388
+ ''')
3389
+ self.nn(nodes[0].get('source'))
3390
+ self.nn(nodes[0].get('target'))
3391
+ self.eq('follows.', nodes[0].get('type'))
3392
+ self.len(1, await core.nodes('inet:service:relationship :source -> inet:service:account +:user=visi'))
3393
+ self.len(1, await core.nodes('inet:service:relationship :target -> inet:service:account +:user=visi'))
3394
+
3395
+ nodes = await core.nodes('''
3396
+ [ inet:service:emote=*
3397
+ :creator={ inet:service:account:user=visi }
3398
+ :about={[ it:dev:repo=* :name=vertex ]}
3399
+ :text=":gothparrot:"
3400
+ ]
3401
+ ''')
3402
+ self.nn(nodes[0].get('about'))
3403
+ self.nn(nodes[0].get('creator'))
3404
+ self.eq(':gothparrot:', nodes[0].get('text'))
3405
+ self.len(1, await core.nodes('inet:service:emote :about -> it:dev:repo +:name=vertex'))
3406
+ self.len(1, await core.nodes('inet:service:emote :creator -> inet:service:account +:user=visi'))
3407
+
3408
+ with self.raises(s_exc.BadTypeValu):
3409
+ await core.nodes('[ inet:service:relationship=* :source={[it:dev:str=foo]} ]')
@@ -742,6 +742,32 @@ class OuModelTest(s_t_utils.SynTest):
742
742
  self.len(1, await core.nodes('ou:enacted :ext:creator -> ps:contact +:name=root'))
743
743
  self.len(1, await core.nodes('ou:enacted :ext:assignee -> ps:contact +:name=visi'))
744
744
 
745
+ nodes = await core.nodes('''
746
+ [ ou:candidate=*
747
+ :org={ ou:org:name=vertex | limit 1 }
748
+ :contact={ ps:contact:name=visi | limit 1 }
749
+ :intro=" Hi there!"
750
+ :submitted=20241104
751
+ :method=referral.employee
752
+ :resume=*
753
+ :opening=*
754
+ :agent={[ ps:contact=* :name=agent ]}
755
+ :recruiter={[ ps:contact=* :name=recruiter ]}
756
+ :attachments={[ file:attachment=* :name=questions.pdf ]}
757
+ ]
758
+ ''')
759
+ self.len(1, nodes)
760
+ self.eq('Hi there!', nodes[0].get('intro'))
761
+ self.eq(1730678400000, nodes[0].get('submitted'))
762
+ self.eq('referral.employee.', nodes[0].get('method'))
763
+ self.len(1, await core.nodes('ou:candidate :org -> ou:org +:name=vertex'))
764
+ self.len(1, await core.nodes('ou:candidate :agent -> ps:contact +:name=agent'))
765
+ self.len(1, await core.nodes('ou:candidate :contact -> ps:contact +:name=visi'))
766
+ self.len(1, await core.nodes('ou:candidate :recruiter -> ps:contact +:name=recruiter'))
767
+
768
+ self.len(1, await core.nodes('ou:candidate :method -> ou:candidate:method:taxonomy'))
769
+ self.len(1, await core.nodes('ou:candidate :attachments -> file:attachment'))
770
+
745
771
  async def test_ou_code_prefixes(self):
746
772
  guid0 = s_common.guid()
747
773
  guid1 = s_common.guid()
@@ -839,16 +865,21 @@ class OuModelTest(s_t_utils.SynTest):
839
865
  :sic="1234,5678"
840
866
  :isic=C1393
841
867
  :desc="Moldy cheese"
868
+ :reporter={[ ou:org=* :name=vertex ]}
869
+ :reporter:name=vertex
842
870
  ] '''
843
871
  nodes = await core.nodes(q)
844
872
  self.len(1, nodes)
873
+ self.nn(nodes[0].get('reporter'))
845
874
  self.eq('foo bar', nodes[0].get('name'))
875
+ self.eq('vertex', nodes[0].get('reporter:name'))
846
876
  self.sorteq(('1234', '5678'), nodes[0].get('sic'))
847
877
  self.sorteq(('11111', '22222'), nodes[0].get('naics'))
848
878
  self.sorteq(('C1393', ), nodes[0].get('isic'))
849
879
  self.len(2, nodes[0].get('subs'))
850
880
  self.eq('Moldy cheese', nodes[0].get('desc'))
851
881
 
882
+ self.len(1, await core.nodes('ou:industry :reporter -> ou:org'))
852
883
  nodes = await core.nodes('ou:industry:name="foo bar" | tree { :subs -> ou:industry } | uniq')
853
884
  self.len(3, nodes)
854
885
  self.len(3, await core.nodes('ou:industryname=baz -> ou:industry -> ou:industryname'))
@@ -989,3 +1020,11 @@ class OuModelTest(s_t_utils.SynTest):
989
1020
  self.len(1, await core.nodes('ou:contribution -> econ:acct:payment'))
990
1021
  self.len(1, await core.nodes('ou:contribution -> mat:spec'))
991
1022
  self.len(1, await core.nodes('ou:contribution -> ou:jobtitle +ou:jobtitle=analysts'))
1023
+
1024
+ async def test_ou_technique(self):
1025
+
1026
+ async with self.getTestCore() as core:
1027
+ nodes = await core.nodes('''
1028
+ [ ou:technique=* :name=foo +(uses)> { [ risk:vuln=* :name=bar ] } ]
1029
+ ''')
1030
+ self.len(1, await core.nodes('ou:technique:name=foo -(uses)> risk:vuln:name=bar'))
@@ -334,7 +334,7 @@ class ProjModelTest(s_test.SynTest):
334
334
  self.true(await core.callStorm('return($lib.projects.del($proj))', opts=opts))
335
335
  self.false(await core.callStorm('return($lib.projects.del(newp))', opts=opts))
336
336
 
337
- self.none(core.auth.getAuthGate(proj))
337
+ self.nn(core.auth.getAuthGate(proj))
338
338
 
339
339
  self.len(1, await core.nodes('yield $lib.projects.add(proj)'))
340
340
  self.len(1, await core.nodes('yield $lib.projects.get(proj).epics.add(epic)'))
@@ -345,6 +345,16 @@ class ProjModelTest(s_test.SynTest):
345
345
  name = await core.callStorm('$p=$lib.projects.get(proj) $p.name=newproj return ( $p.name )')
346
346
  self.eq(name, 'newproj')
347
347
 
348
+ viewiden = core.getView().iden
349
+ self.len(1, await core.nodes(f'[ proj:project={viewiden}]'))
350
+ gate = core.auth.getAuthGate(viewiden)
351
+ self.eq(gate.type, 'view')
352
+
353
+ await core.nodes(f'proj:project={viewiden} | delnode')
354
+ gate = core.auth.getAuthGate(viewiden)
355
+ self.nn(gate)
356
+ self.eq(gate.type, 'view')
357
+
348
358
  async def test_model_proj_attachment(self):
349
359
 
350
360
  async with self.getTestCore() as core:
@@ -536,6 +536,31 @@ class RiskModelTest(s_t_utils.SynTest):
536
536
  self.len(1, await core.nodes('risk:vuln:name=redtree -> risk:vulnerable :node -> *'))
537
537
  self.len(1, await core.nodes('risk:vulnerable -> risk:mitigation'))
538
538
 
539
+ nodes = await core.nodes('''
540
+ [ risk:outage=*
541
+ :name="The Big One"
542
+ :period=(2023, 2024)
543
+ :type=service.power
544
+ :cause=nature.earthquake
545
+ :provider={[ ou:org=* :name="desert power" ]}
546
+ :provider:name="desert power"
547
+ :reporter={ ou:org:name=vertex }
548
+ :reporter:name=vertex
549
+ ]
550
+ ''')
551
+ self.len(1, nodes)
552
+ self.nn(nodes[0].get('reporter'))
553
+ self.eq('the big one', nodes[0].get('name'))
554
+ self.eq('vertex', nodes[0].get('reporter:name'))
555
+ self.eq('desert power', nodes[0].get('provider:name'))
556
+ self.eq('service.power.', nodes[0].get('type'))
557
+ self.eq('nature.earthquake.', nodes[0].get('cause'))
558
+ self.eq((1672531200000, 1704067200000), nodes[0].get('period'))
559
+
560
+ self.len(1, await core.nodes('risk:outage -> risk:outage:cause:taxonomy'))
561
+ self.len(1, await core.nodes('risk:outage :reporter -> ou:org +:name=vertex'))
562
+ self.len(1, await core.nodes('risk:outage :provider -> ou:org +:name="desert power"'))
563
+
539
564
  async def test_model_risk_mitigation(self):
540
565
  async with self.getTestCore() as core:
541
566
  nodes = await core.nodes('''[
@@ -618,3 +643,10 @@ class RiskModelTest(s_t_utils.SynTest):
618
643
  self.nn(nodes[0].get('version:min'))
619
644
  self.nn(nodes[0].get('version:max'))
620
645
  self.len(2, await core.nodes('risk:vuln:name=woot -> risk:vuln:soft:range -> it:prod:softver'))
646
+
647
+ async def test_model_risk_vuln_technique(self):
648
+ async with self.getTestCore() as core:
649
+ nodes = await core.nodes('''
650
+ [ risk:vuln=* :name=foo <(uses)+ { [ ou:technique=* :name=bar ] } ]
651
+ ''')
652
+ self.len(1, await core.nodes('risk:vuln:name=foo <(uses)- ou:technique:name=bar'))
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: synapse
3
- Version: 2.187.0
3
+ Version: 2.188.1
4
4
  Summary: Synapse Intelligence Analysis Framework
5
5
  Author-email: The Vertex Project LLC <root@vertex.link>
6
6
  License: Apache License 2.0