synapse 2.183.0__py311-none-any.whl → 2.185.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 (46) hide show
  1. synapse/cortex.py +1 -1
  2. synapse/datamodel.py +82 -9
  3. synapse/lib/ast.py +85 -23
  4. synapse/lib/auth.py +13 -0
  5. synapse/lib/autodoc.py +9 -2
  6. synapse/lib/cell.py +14 -1
  7. synapse/lib/modules.py +1 -0
  8. synapse/lib/parser.py +1 -0
  9. synapse/lib/snap.py +1 -0
  10. synapse/lib/storm.lark +12 -6
  11. synapse/lib/storm.py +45 -9
  12. synapse/lib/storm_format.py +1 -0
  13. synapse/lib/stormlib/graph.py +17 -0
  14. synapse/lib/stormlib/stix.py +14 -5
  15. synapse/lib/stormtypes.py +65 -37
  16. synapse/lib/stormwhois.py +3 -0
  17. synapse/lib/version.py +2 -2
  18. synapse/models/doc.py +93 -0
  19. synapse/models/infotech.py +5 -1
  20. synapse/models/media.py +0 -1
  21. synapse/models/orgs.py +102 -5
  22. synapse/models/proj.py +56 -36
  23. synapse/models/risk.py +22 -0
  24. synapse/models/syn.py +64 -6
  25. synapse/tests/test_cortex.py +54 -45
  26. synapse/tests/test_lib_ast.py +58 -0
  27. synapse/tests/test_lib_autodoc.py +54 -0
  28. synapse/tests/test_lib_cell.py +44 -1
  29. synapse/tests/test_lib_grammar.py +2 -0
  30. synapse/tests/test_lib_storm.py +68 -1
  31. synapse/tests/test_lib_stormlib_modelext.py +52 -0
  32. synapse/tests/test_lib_stormlib_stix.py +3 -2
  33. synapse/tests/test_lib_stormwhois.py +4 -4
  34. synapse/tests/test_model_doc.py +51 -0
  35. synapse/tests/test_model_infotech.py +5 -1
  36. synapse/tests/test_model_orgs.py +78 -0
  37. synapse/tests/test_model_risk.py +3 -0
  38. synapse/tests/test_model_syn.py +43 -0
  39. synapse/tests/test_tools_promote.py +67 -0
  40. synapse/tests/utils.py +26 -0
  41. synapse/tools/promote.py +14 -1
  42. {synapse-2.183.0.dist-info → synapse-2.185.0.dist-info}/METADATA +5 -10
  43. {synapse-2.183.0.dist-info → synapse-2.185.0.dist-info}/RECORD +46 -43
  44. {synapse-2.183.0.dist-info → synapse-2.185.0.dist-info}/WHEEL +1 -1
  45. {synapse-2.183.0.dist-info → synapse-2.185.0.dist-info}/LICENSE +0 -0
  46. {synapse-2.183.0.dist-info → synapse-2.185.0.dist-info}/top_level.txt +0 -0
@@ -147,6 +147,20 @@ class StormTest(s_t_utils.SynTest):
147
147
  retn = await core.callStorm('return(({"foo": "bar", "baz": 10 , }))')
148
148
  self.eq(retn, {'foo': 'bar', 'baz': 10})
149
149
 
150
+ q = '''
151
+ $foo = ({"bar": ${[inet:fqdn=foo.com]}})
152
+ for $n in $foo.bar { return($n.repr()) }
153
+ '''
154
+ retn = await core.callStorm(q)
155
+ self.eq(retn, 'foo.com')
156
+
157
+ q = '''
158
+ $foo = ([${[inet:fqdn=foo.com]}])
159
+ for $n in $foo.0 { return($n.repr()) }
160
+ '''
161
+ retn = await core.callStorm(q)
162
+ self.eq(retn, 'foo.com')
163
+
150
164
  with self.raises(s_exc.BadSyntax):
151
165
  await core.callStorm('return((["foo" "foo"]))')
152
166
 
@@ -612,6 +626,12 @@ class StormTest(s_t_utils.SynTest):
612
626
  ''')
613
627
  self.eq((0, 'haha'), await core.callStorm('return($lib.queue.get(bar).get())'))
614
628
 
629
+ await core.nodes('$foo = (foo,) background ${ $foo.append(bar) $lib.queue.get(bar).put($foo) }')
630
+ self.eq((1, ['foo', 'bar']), await core.callStorm('return($lib.queue.get(bar).get(1))'))
631
+
632
+ await core.nodes('$foo = ([["foo"]]) background ${ $foo.0.append(bar) $lib.queue.get(bar).put($foo) }')
633
+ self.eq((2, [['foo', 'bar']]), await core.callStorm('return($lib.queue.get(bar).get(2))'))
634
+
615
635
  with self.raises(s_exc.StormRuntimeError):
616
636
  await core.nodes('[ ou:org=*] $text = $node.repr() | background $text')
617
637
 
@@ -3586,7 +3606,7 @@ class StormTest(s_t_utils.SynTest):
3586
3606
  'quickly became his weapon of choice.'])
3587
3607
  pars.help()
3588
3608
  helptext = '\n'.join(pars.mesgs)
3589
- self.isin('default: \n[', helptext)
3609
+ self.isin('default:\n [', helptext)
3590
3610
 
3591
3611
  pars = s_storm.Parser()
3592
3612
  pars.add_argument('--ques', nargs='?')
@@ -3771,6 +3791,27 @@ class StormTest(s_t_utils.SynTest):
3771
3791
  opts = pars.parse_args(['faz', '--cat', 'cam', 'cool'])
3772
3792
  self.nn(opts)
3773
3793
 
3794
+ pars = s_storm.Parser()
3795
+ pars.add_argument('--baz', nargs=3, help='''
3796
+ This is the top line, nothing special.
3797
+ This is my second line with sublines that should have some leading spaces:
3798
+ subline 1: this is a line which has three spaces.
3799
+ subline 2: this is another line with five leading spaces.
3800
+ subline 3: yet another line with only two leading spaces.
3801
+ subline 4: this line has one space and is long which should wrap around because it exceeds the default display width.
3802
+ This is the final line with no leading spaces.''')
3803
+ pars.add_argument('--taz', type='bool', default=True, help='Taz option')
3804
+ pars.help()
3805
+ self.eq(' --baz <baz> : This is the top line, nothing special.', pars.mesgs[5])
3806
+ self.eq(' This is my second line with sublines that should have some leading spaces:', pars.mesgs[6])
3807
+ self.eq(' subline 1: this is a line which has three spaces.', pars.mesgs[7])
3808
+ self.eq(' subline 2: this is another line with five leading spaces.', pars.mesgs[8])
3809
+ self.eq(' subline 3: yet another line with only two leading spaces.', pars.mesgs[9])
3810
+ self.eq(' subline 4: this line has one space and is long which should wrap around because it', pars.mesgs[10])
3811
+ self.eq(' exceeds the default display width.', pars.mesgs[11])
3812
+ self.eq(' This is the final line with no leading spaces.', pars.mesgs[12])
3813
+ self.eq(' --taz <taz> : Taz option (default: True)', pars.mesgs[13])
3814
+
3774
3815
  async def test_storm_cmd_help(self):
3775
3816
 
3776
3817
  async with self.getTestCore() as core:
@@ -3917,6 +3958,11 @@ class StormTest(s_t_utils.SynTest):
3917
3958
  self.stormIsInPrint('Warning', msgs)
3918
3959
  self.stormIsInPrint('``$lib.infosec.cvss.saveVectToNode`` has been deprecated and will be removed in version v3.0.0.', msgs)
3919
3960
 
3961
+ msgs = await core.stormlist('help --verbose $lib.inet.whois.guid')
3962
+ self.stormIsInPrint('Warning', msgs)
3963
+ self.stormIsInPrint('``$lib.inet.whois.guid`` has been deprecated and will be removed in version v3.0.0.', msgs)
3964
+ self.stormIsInPrint('Please use the GUID constructor syntax.', msgs)
3965
+
3920
3966
  msgs = await core.stormlist('help $lib.inet')
3921
3967
  self.stormIsInPrint('The following libraries are available:\n\n'
3922
3968
  '$lib.inet.http : A Storm Library exposing an HTTP client API.\n'
@@ -3947,6 +3993,15 @@ class StormTest(s_t_utils.SynTest):
3947
3993
  msgs = await core.stormlist('$mod=$lib.import(foosmod) help $mod.f')
3948
3994
  self.stormIsInErr('help does not currently support runtime defined functions.', msgs)
3949
3995
 
3996
+ msgs = await core.stormlist('help --verbose $lib.bytes')
3997
+ self.stormIsInPrint('Warning', msgs)
3998
+ self.stormIsInPrint('$lib.bytes.put`` has been deprecated and will be removed in version v3.0.0', msgs)
3999
+ self.stormIsInPrint('$lib.bytes.has`` has been deprecated and will be removed in version v3.0.0', msgs)
4000
+ self.stormIsInPrint('$lib.bytes.size`` has been deprecated and will be removed in version v3.0.0', msgs)
4001
+ self.stormIsInPrint('$lib.bytes.upload`` has been deprecated and will be removed in version v3.0.0', msgs)
4002
+ self.stormIsInPrint('$lib.bytes.hashset`` has been deprecated and will be removed in version v3.0.0', msgs)
4003
+ self.stormIsInPrint('Use the corresponding ``$lib.axon`` function.', msgs)
4004
+
3950
4005
  async def test_liftby_edge(self):
3951
4006
  async with self.getTestCore() as core:
3952
4007
 
@@ -5071,3 +5126,15 @@ class StormTest(s_t_utils.SynTest):
5071
5126
  ''')
5072
5127
 
5073
5128
  self.none(await core.callStorm('return($lib.queue.gen(haha).get().1)'))
5129
+
5130
+ await core.nodes('''
5131
+ $foo = (foo,)
5132
+ $query = ${
5133
+ $foo.append(bar)
5134
+ $lib.queue.gen(hoho).put($foo)
5135
+ $lib.dmon.del($auto.iden)
5136
+ }
5137
+ $lib.dmon.add($query)
5138
+ ''')
5139
+
5140
+ self.eq(['foo', 'bar'], await core.callStorm('return($lib.queue.gen(hoho).get().1)'))
@@ -510,3 +510,55 @@ class StormtypesModelextTest(s_test.SynTest):
510
510
  with self.raises(s_exc.BadArg) as exc:
511
511
  await core.callStorm(query)
512
512
  self.eq(err, exc.exception.get('mesg'))
513
+
514
+ async def test_lib_stormlib_modelext_interfaces(self):
515
+ async with self.getTestCore() as core:
516
+
517
+ await core.callStorm('''
518
+ $forminfo = ({"interfaces": ["test:interface"]})
519
+ $lib.model.ext.addForm(_test:iface, str, ({}), $forminfo)
520
+ $lib.model.ext.addFormProp(_test:iface, tick, (time, ({})), ({}))
521
+ ''')
522
+
523
+ self.nn(core.model.form('_test:iface'))
524
+ self.nn(core.model.prop('_test:iface:flow'))
525
+ self.nn(core.model.prop('_test:iface:proc'))
526
+ self.nn(core.model.prop('_test:iface:tick'))
527
+ self.isin('_test:iface', core.model.formsbyiface['test:interface'])
528
+ self.isin('_test:iface', core.model.formsbyiface['inet:proto:request'])
529
+ self.isin('_test:iface', core.model.formsbyiface['it:host:activity'])
530
+ self.isin('_test:iface:flow', core.model.ifaceprops['inet:proto:request:flow'])
531
+ self.isin('_test:iface:proc', core.model.ifaceprops['test:interface:proc'])
532
+ self.isin('_test:iface:proc', core.model.ifaceprops['inet:proto:request:proc'])
533
+ self.isin('_test:iface:proc', core.model.ifaceprops['it:host:activity:proc'])
534
+
535
+ q = '$lib.model.ext.delForm(_test:iface)'
536
+ with self.raises(s_exc.CantDelForm) as exc:
537
+ await core.callStorm(q)
538
+ self.eq('Form has extended properties: tick', exc.exception.get('mesg'))
539
+
540
+ await core.callStorm('''
541
+ $lib.model.ext.delFormProp(_test:iface, tick)
542
+ $lib.model.ext.delForm(_test:iface)
543
+ ''')
544
+
545
+ self.none(core.model.form('_test:iface'))
546
+ self.none(core.model.prop('_test:iface:flow'))
547
+ self.none(core.model.prop('_test:iface:proc'))
548
+ self.none(core.model.prop('_test:iface:tick'))
549
+ self.notin('_test:iface', core.model.formsbyiface['test:interface'])
550
+ self.notin('_test:iface', core.model.formsbyiface['inet:proto:request'])
551
+ self.notin('_test:iface', core.model.formsbyiface['it:host:activity'])
552
+ self.notin('_test:iface:flow', core.model.ifaceprops['inet:proto:request:flow'])
553
+ self.notin('_test:iface:proc', core.model.ifaceprops['test:interface:proc'])
554
+ self.notin('_test:iface:proc', core.model.ifaceprops['inet:proto:request:proc'])
555
+ self.notin('_test:iface:proc', core.model.ifaceprops['it:host:activity:proc'])
556
+
557
+ await core.stormlist('''
558
+ $forminfo = ({"interfaces": ["newp"]})
559
+ $lib.model.ext.addForm(_test:iface, str, ({}), $forminfo)
560
+ ''')
561
+ self.nn(core.model.form('_test:iface'))
562
+
563
+ await core.callStorm('$lib.model.ext.delForm(_test:iface)')
564
+ self.none(core.model.form('_test:iface'))
@@ -55,6 +55,7 @@ class StormLibStixTest(s_test.SynTest):
55
55
  async def test_stormlib_libstix(self, conf=None):
56
56
 
57
57
  async with self.getTestCore(conf=conf) as core:
58
+ visi = await core.auth.addUser('visi')
58
59
  opts = {'vars': {
59
60
  'ind': '6ba7d8500964902bf2e03126ed0f6cb1',
60
61
  'news': '840b9b003a765020705ea8d203a7659c',
@@ -228,9 +229,9 @@ class StormLibStixTest(s_test.SynTest):
228
229
  opts = {'vars': {'config': config}}
229
230
  await core.callStorm('$lib.stix.export.bundle(config=$config)', opts=opts)
230
231
 
231
- with self.raises(s_exc.BadConfValu):
232
+ with self.raises(s_exc.AuthDeny):
232
233
  config = {'maxsize': 10000000}
233
- opts = {'vars': {'config': config}}
234
+ opts = {'user': visi.iden, 'vars': {'config': config}}
234
235
  await core.callStorm('$lib.stix.export.bundle(config=$config)', opts=opts)
235
236
 
236
237
  with self.raises(s_exc.NoSuchForm):
@@ -84,8 +84,8 @@ class StormWhoisTest(s_test.SynTest):
84
84
  '''
85
85
  opts = {'vars': {'props': props}}
86
86
  mesgs = await core.stormlist(stormcmd, opts=opts)
87
- warn = [m[1]['mesg'] for m in mesgs if m[0] == 'warn']
88
- self.isin('Insufficient guid vals identified, using random guid:', warn[0])
87
+ self.stormIsInWarn('$lib.inet.whois.guid() is deprecated', mesgs)
88
+ self.stormIsInWarn('Insufficient guid vals identified, using random guid:', mesgs)
89
89
  self.len(1, await core.nodes(f'inet:whois:ipquery:fqdn={props["fqdn"]}'))
90
90
 
91
91
  props = {
@@ -97,8 +97,8 @@ class StormWhoisTest(s_test.SynTest):
97
97
  '''
98
98
  opts = {'vars': {'props': props}}
99
99
  mesgs = await core.stormlist(stormcmd, opts=opts)
100
- warn = [m[1]['mesg'] for m in mesgs if m[0] == 'warn']
101
- self.isin('Insufficient guid vals identified, using random guid:', warn[0])
100
+ self.stormIsInWarn('$lib.inet.whois.guid() is deprecated', mesgs)
101
+ self.stormIsInWarn('Insufficient guid vals identified, using random guid:', mesgs)
102
102
  self.len(1, await core.nodes(f'inet:whois:ipcontact:asn={props["asn"]}'))
103
103
 
104
104
  # Failure cases
@@ -0,0 +1,51 @@
1
+ import synapse.tests.utils as s_tests
2
+
3
+ class DocModelTest(s_tests.SynTest):
4
+
5
+ async def test_model_doc(self):
6
+
7
+ async with self.getTestCore() as core:
8
+
9
+ nodes = await core.nodes('''
10
+ [ doc:policy=*
11
+ :id=V-41
12
+ :name="Rule 41"
13
+ :text="If you can AAAAAAAA..."
14
+ :file=*
15
+ :created=20241018
16
+ :updated=20241018
17
+ :author={[ ps:contact=* :name=visi ]}
18
+ :contributors={[ ps:contact=* :name=shuka ]}
19
+ :version=1.2.3
20
+ :supersedes={[ doc:policy=* doc:policy=* ]}
21
+ ]
22
+ ''')
23
+ self.len(1, nodes)
24
+ self.eq('V-41', nodes[0].get('id'))
25
+ self.eq('rule 41', nodes[0].get('name'))
26
+ self.eq('If you can AAAAAAAA...', nodes[0].get('text'))
27
+ self.eq(1729209600000, nodes[0].get('created'))
28
+ self.eq(1729209600000, nodes[0].get('updated'))
29
+ self.eq(1099513724931, nodes[0].get('version'))
30
+
31
+ self.nn(nodes[0].get('file'))
32
+ self.nn(nodes[0].get('author'))
33
+
34
+ self.len(2, nodes[0].get('supersedes'))
35
+ self.len(1, nodes[0].get('contributors'))
36
+
37
+ self.len(1, await core.nodes('doc:policy:id=V-41 :file -> file:bytes'))
38
+ self.len(2, await core.nodes('doc:policy:id=V-41 :supersedes -> doc:policy'))
39
+ self.len(1, await core.nodes('doc:policy:id=V-41 :author -> ps:contact +:name=visi'))
40
+ self.len(1, await core.nodes('doc:policy:id=V-41 :contributors -> ps:contact +:name=shuka'))
41
+
42
+ nodes = await core.nodes('''
43
+ [ doc:standard=*
44
+ :id=V-99
45
+ :policy={ doc:policy:id=V-41 }
46
+ ]
47
+ ''')
48
+ self.len(1, nodes)
49
+ self.eq('V-99', nodes[0].get('id'))
50
+ self.nn(nodes[0].get('policy'))
51
+ self.len(1, await core.nodes('doc:standard -> doc:policy'))
@@ -1675,8 +1675,12 @@ class InfotechModelTest(s_t_utils.SynTest):
1675
1675
  self.eq(1640995200000, nodes[0].get('updated'))
1676
1676
  self.nn(nodes[0].get('author'))
1677
1677
 
1678
- nodes = await core.nodes('[ it:app:snort:hit=$hit :rule=$rule :flow=$flow :src="tcp://[::ffff:0102:0304]:0" :dst="tcp://[::ffff:0505:0505]:80" :time=2015 :sensor=$host :version=1.2.3 ]', opts=opts)
1678
+ nodes = await core.nodes('''[ it:app:snort:hit=$hit
1679
+ :rule=$rule :flow=$flow :src="tcp://[::ffff:0102:0304]:0"
1680
+ :dst="tcp://[::ffff:0505:0505]:80" :time=2015 :sensor=$host
1681
+ :version=1.2.3 :dropped=true ]''', opts=opts)
1679
1682
  self.len(1, nodes)
1683
+ self.true(nodes[0].get('dropped'))
1680
1684
  self.eq(rule, nodes[0].get('rule'))
1681
1685
  self.eq(flow, nodes[0].get('flow'))
1682
1686
  self.eq(host, nodes[0].get('sensor'))
@@ -640,6 +640,7 @@ class OuModelTest(s_t_utils.SynTest):
640
640
 
641
641
  nodes = await core.nodes('''[ ou:requirement=50b757fafe4a839ec499023ebcffe7c0
642
642
  :name="acquire pizza toppings"
643
+ :type=foo.bar
643
644
  :text="The team must acquire ANSI standard pizza toppings."
644
645
  :goal={[ ou:goal=* :name=pizza ]}
645
646
  :issuer={[ ps:contact=* :name=visi ]}
@@ -657,6 +658,7 @@ class OuModelTest(s_t_utils.SynTest):
657
658
  self.eq('The team must acquire ANSI standard pizza toppings.', nodes[0].get('text'))
658
659
  self.eq(1, nodes[0].get('deps:min'))
659
660
  self.eq(50, nodes[0].get('priority'))
661
+ self.eq('foo.bar.', nodes[0].get('type'))
660
662
  self.eq(True, nodes[0].get('optional'))
661
663
  self.eq(1328140800000, nodes[0].get('issued'))
662
664
  self.eq((1672531200000, 9223372036854775807), nodes[0].get('period'))
@@ -665,6 +667,80 @@ class OuModelTest(s_t_utils.SynTest):
665
667
  self.len(1, await core.nodes('ou:requirement=50b757fafe4a839ec499023ebcffe7c0 -> ou:goal +:name=pizza'))
666
668
  self.len(1, await core.nodes('ou:requirement=50b757fafe4a839ec499023ebcffe7c0 :issuer -> ps:contact +:name=visi'))
667
669
  self.len(1, await core.nodes('ou:requirement=50b757fafe4a839ec499023ebcffe7c0 :assignee -> ps:contact +:orgname=ledos'))
670
+ self.len(1, await core.nodes('ou:requirement=50b757fafe4a839ec499023ebcffe7c0 -> ou:requirement:type:taxonomy'))
671
+
672
+ nodes = await core.nodes('''
673
+ [ ou:asset=*
674
+ :id=V-31337
675
+ :name="visi laptop"
676
+ :type=host.laptop
677
+ :priority=highest
678
+ :priority:confidentiality=highest
679
+ :priority:integrity=highest
680
+ :priority:availability=highest
681
+ :node = (it:host, *)
682
+ :period=(2016, ?)
683
+ :status=deployed
684
+ :org={[ ou:org=* :name=vertex ]}
685
+ :owner={[ ps:contact=* :name=foo ]}
686
+ :operator={[ ps:contact=* :name=bar ]}
687
+ ]''')
688
+ self.len(1, nodes)
689
+ self.eq((1451606400000, 9223372036854775807), nodes[0].get('period'))
690
+ self.eq('visi laptop', nodes[0].get('name'))
691
+ self.eq('host.laptop.', nodes[0].get('type'))
692
+ self.eq('deployed.', nodes[0].get('status'))
693
+ self.eq(50, nodes[0].get('priority'))
694
+ self.eq(50, nodes[0].get('priority:confidentiality'))
695
+ self.eq(50, nodes[0].get('priority:integrity'))
696
+ self.eq(50, nodes[0].get('priority:availability'))
697
+
698
+ self.len(1, await core.nodes('ou:asset -> ou:asset:type:taxonomy'))
699
+ self.len(1, await core.nodes('ou:asset :node -> it:host'))
700
+ self.len(1, await core.nodes('ou:asset :org -> ou:org +:name=vertex'))
701
+ self.len(1, await core.nodes('ou:asset :owner -> ps:contact +:name=foo '))
702
+ self.len(1, await core.nodes('ou:asset :operator -> ps:contact +:name=bar '))
703
+
704
+ visi = await core.auth.addUser('visi')
705
+
706
+ nodes = await core.nodes('''
707
+ [ ou:enacted=*
708
+ :id=V-99
709
+ :project={[ proj:project=* ]}
710
+ :status=10
711
+ :priority=highest
712
+ :created=20241018
713
+ :updated=20241018
714
+ :due=20241018
715
+ :completed=20241018
716
+ :creator=root
717
+ :assignee=visi
718
+ :scope=(ou:team, *)
719
+ :ext:creator={[ ps:contact=* :name=root ]}
720
+ :ext:assignee={[ ps:contact=* :name=visi ]}
721
+ ]
722
+ ''')
723
+ self.len(1, nodes)
724
+ self.eq('V-99', nodes[0].get('id'))
725
+ self.eq(10, nodes[0].get('status'))
726
+ self.eq(50, nodes[0].get('priority'))
727
+
728
+ self.eq(1729209600000, nodes[0].get('due'))
729
+ self.eq(1729209600000, nodes[0].get('created'))
730
+ self.eq(1729209600000, nodes[0].get('updated'))
731
+ self.eq(1729209600000, nodes[0].get('completed'))
732
+
733
+ self.eq(visi.iden, nodes[0].get('assignee'))
734
+ self.eq(core.auth.rootuser.iden, nodes[0].get('creator'))
735
+
736
+ self.nn(nodes[0].get('scope'))
737
+ self.nn(nodes[0].get('ext:creator'))
738
+ self.nn(nodes[0].get('ext:assignee'))
739
+
740
+ self.len(1, await core.nodes('ou:enacted -> proj:project'))
741
+ self.len(1, await core.nodes('ou:enacted :scope -> ou:team'))
742
+ self.len(1, await core.nodes('ou:enacted :ext:creator -> ps:contact +:name=root'))
743
+ self.len(1, await core.nodes('ou:enacted :ext:assignee -> ps:contact +:name=visi'))
668
744
 
669
745
  async def test_ou_code_prefixes(self):
670
746
  guid0 = s_common.guid()
@@ -832,6 +908,7 @@ class OuModelTest(s_t_utils.SynTest):
832
908
  :orgfqdn = wootwoot.com
833
909
  :currency = USD
834
910
  :costs = 200
911
+ :budget = 300
835
912
  :revenue = 500
836
913
  :profit = 300
837
914
  :valuation = 1000000000
@@ -850,6 +927,7 @@ class OuModelTest(s_t_utils.SynTest):
850
927
  self.eq(nodes[0].get('orgfqdn'), 'wootwoot.com')
851
928
  self.eq(nodes[0].get('currency'), 'usd')
852
929
  self.eq(nodes[0].get('costs'), '200')
930
+ self.eq(nodes[0].get('budget'), '300')
853
931
  self.eq(nodes[0].get('revenue'), '500')
854
932
  self.eq(nodes[0].get('profit'), '300')
855
933
  self.eq(nodes[0].get('valuation'), '1000000000')
@@ -542,6 +542,7 @@ class RiskModelTest(s_t_utils.SynTest):
542
542
  risk:mitigation=*
543
543
  :vuln=*
544
544
  :name=" FooBar "
545
+ :type=foo.bar
545
546
  :desc=BazFaz
546
547
  :hardware=*
547
548
  :software=*
@@ -552,11 +553,13 @@ class RiskModelTest(s_t_utils.SynTest):
552
553
  self.eq('foobar', nodes[0].props['name'])
553
554
  self.eq('BazFaz', nodes[0].props['desc'])
554
555
  self.eq('vertex', nodes[0].get('reporter:name'))
556
+ self.eq('foo.bar.', nodes[0].get('type'))
555
557
  self.nn(nodes[0].get('reporter'))
556
558
  self.len(1, await core.nodes('risk:mitigation -> risk:vuln'))
557
559
  self.len(1, await core.nodes('risk:mitigation -> it:prod:softver'))
558
560
  self.len(1, await core.nodes('risk:mitigation -> it:prod:hardware'))
559
561
  self.len(1, await core.nodes('risk:mitigation -> it:mitre:attack:mitigation'))
562
+ self.len(1, await core.nodes('risk:mitigation -> risk:mitigation:type:taxonomy'))
560
563
 
561
564
  async def test_model_risk_tool_software(self):
562
565
 
@@ -1,5 +1,7 @@
1
1
  import synapse.exc as s_exc
2
+ import synapse.common as s_common
2
3
  import synapse.cortex as s_cortex
4
+ import synapse.datamodel as s_datamodel
3
5
 
4
6
  import synapse.lib.stormsvc as s_stormsvc
5
7
 
@@ -46,6 +48,47 @@ class TestService(s_stormsvc.StormSvc):
46
48
 
47
49
  class SynModelTest(s_t_utils.SynTest):
48
50
 
51
+ async def test_syn_userrole(self):
52
+
53
+ async with self.getTestCore() as core:
54
+
55
+ (ok, iden) = await core.callStorm('return($lib.trycast(syn:user, root))')
56
+ self.true(ok)
57
+ self.eq(iden, core.auth.rootuser.iden)
58
+
59
+ # coverage for iden taking precedence
60
+ (ok, iden) = await core.callStorm(f'return($lib.trycast(syn:user, {iden}))')
61
+ self.true(ok)
62
+ self.eq(iden, core.auth.rootuser.iden)
63
+
64
+ self.eq('root', await core.callStorm(f'return($lib.repr(syn:user, {iden}))'))
65
+
66
+ (ok, iden) = await core.callStorm('return($lib.trycast(syn:role, all))')
67
+ self.true(ok)
68
+ self.eq(iden, core.auth.allrole.iden)
69
+
70
+ # coverage for iden taking precedence
71
+ (ok, iden) = await core.callStorm(f'return($lib.trycast(syn:role, {iden}))')
72
+ self.true(ok)
73
+ self.eq(iden, core.auth.allrole.iden)
74
+
75
+ self.eq('all', await core.callStorm(f'return($lib.repr(syn:role, {iden}))'))
76
+
77
+ # coverage for DataModel without a cortex reference
78
+ iden = s_common.guid()
79
+
80
+ model = core.model
81
+ model.core = None
82
+
83
+ synuser = model.type('syn:user')
84
+ synrole = model.type('syn:user')
85
+
86
+ self.eq(iden, synuser.repr(iden))
87
+ self.eq(iden, synrole.repr(iden))
88
+
89
+ self.eq(iden, synuser.norm(iden)[0])
90
+ self.eq(iden, synrole.norm(iden)[0])
91
+
49
92
  async def test_syn_tag(self):
50
93
 
51
94
  async with self.getTestCore() as core:
@@ -0,0 +1,67 @@
1
+ import synapse.common as s_common
2
+
3
+ import synapse.lib.base as s_base
4
+ import synapse.lib.cell as s_cell
5
+
6
+ import synapse.tools.promote as s_tools_promote
7
+
8
+ import synapse.tests.utils as s_t_utils
9
+
10
+
11
+ class PromoteToolTest(s_t_utils.SynTest):
12
+
13
+ async def test_tool_promote_simple(self):
14
+ async with self.getTestAha() as aha:
15
+ async with await s_base.Base.anit() as base:
16
+ with self.getTestDir() as dirn:
17
+ dirn00 = s_common.genpath(dirn, '00.cell')
18
+ dirn01 = s_common.genpath(dirn, '01.cell')
19
+
20
+ cell00 = await base.enter_context(self.addSvcToAha(aha, '00.cell', s_cell.Cell, dirn=dirn00))
21
+ cell01 = await base.enter_context(self.addSvcToAha(aha, '01.cell', s_cell.Cell, dirn=dirn01,
22
+ provinfo={'mirror': 'cell'}))
23
+ self.true(cell00.isactive)
24
+ self.false(cell01.isactive)
25
+ await cell01.sync()
26
+
27
+ outp = self.getTestOutp()
28
+ argv = ['--svcurl', cell00.getLocalUrl()]
29
+ ret = await s_tools_promote.main(argv, outp=outp)
30
+ self.eq(1, ret)
31
+ outp.expect('Failed to promote service')
32
+ outp.expect('promote() called on non-mirror')
33
+
34
+ outp.clear()
35
+ argv = ['--svcurl', cell01.getLocalUrl()]
36
+ ret = await s_tools_promote.main(argv, outp=outp)
37
+ self.eq(0, ret)
38
+ self.false(cell00.isactive)
39
+ self.true(cell01.isactive)
40
+ await cell00.sync()
41
+
42
+ async def test_tool_promote_schism(self):
43
+ # Create a mirror of mirrors and try promoting the end mirror.
44
+ async with self.getTestAha() as aha:
45
+ async with await s_base.Base.anit() as base:
46
+ with self.getTestDir() as dirn:
47
+ dirn00 = s_common.genpath(dirn, '00.cell')
48
+ dirn01 = s_common.genpath(dirn, '01.cell')
49
+ dirn02 = s_common.genpath(dirn, '02.cell')
50
+
51
+ cell00 = await base.enter_context(self.addSvcToAha(aha, '00.cell', s_cell.Cell, dirn=dirn00))
52
+ cell01 = await base.enter_context(self.addSvcToAha(aha, '01.cell', s_cell.Cell, dirn=dirn01,
53
+ provinfo={'mirror': '00.cell'}))
54
+ cell02 = await base.enter_context(self.addSvcToAha(aha, '02.cell', s_cell.Cell, dirn=dirn02,
55
+ provinfo={'mirror': '01.cell'}))
56
+ self.true(cell00.isactive)
57
+ self.false(cell01.isactive)
58
+ self.false(cell02.isactive)
59
+ await cell02.sync()
60
+
61
+ outp = self.getTestOutp()
62
+ argv = ['--svcurl', cell02.getLocalUrl()]
63
+ ret = await s_tools_promote.main(argv, outp=outp)
64
+ self.eq(1, ret)
65
+ outp.expect('Failed to promote service')
66
+ # Note: The following message may change when SYN-7659 is addressed
67
+ outp.expect('ahaname=01.cell is not the current leader and cannot handoff leadership to aha://02.cell.synapse')
synapse/tests/utils.py CHANGED
@@ -201,6 +201,32 @@ class LibTst(s_stormtypes.Lib):
201
201
  ret = f'A {valu} beep which {bar} the {faz}!'
202
202
  return ret
203
203
 
204
+ class LibDepr(s_stormtypes.Lib):
205
+ '''
206
+ Deprecate me!
207
+ '''
208
+ _storm_locals = (
209
+ {'name': 'boop',
210
+ 'desc': '''
211
+ An example storm function that's not deprecated on its own, but the entire library is.
212
+ ''',
213
+ 'type': {'type': 'function', '_funcname': 'boop',
214
+ 'args': (
215
+ {'name': 'valu', 'type': 'str', 'desc': 'What to boop.', },
216
+ ),
217
+ 'returns': {'type': 'str', 'desc': 'The booped.', }}},
218
+ )
219
+ _storm_lib_path = ('depr',)
220
+ _storm_lib_deprecation = {'eolvers': 'v3.0.0'}
221
+
222
+ def addLibFuncs(self): # pragma: no cover
223
+ self.locls.update({
224
+ 'boop': self.boop,
225
+ })
226
+
227
+ async def boop(self, valu): # pragma: no cover
228
+ return f'You have been booped, {valu}!'
229
+
204
230
  class TestType(s_types.Type):
205
231
 
206
232
  stortype = s_layer.STOR_TYPE_UTF8
synapse/tools/promote.py CHANGED
@@ -2,9 +2,12 @@ import sys
2
2
  import asyncio
3
3
  import argparse
4
4
 
5
+ import synapse.exc as s_exc
6
+
5
7
  import synapse.telepath as s_telepath
6
8
 
7
9
  import synapse.lib.output as s_output
10
+ import synapse.lib.urlhelp as s_urlhelp
8
11
 
9
12
  descr = '''
10
13
  Promote a mirror to the leader.
@@ -29,7 +32,17 @@ async def main(argv, outp=s_output.stdout):
29
32
  graceful = not opts.failure
30
33
 
31
34
  outp.printf(f'Promoting to leader: {opts.svcurl}')
32
- await cell.promote(graceful=graceful)
35
+ try:
36
+ await cell.promote(graceful=graceful)
37
+ except s_exc.BadState as e:
38
+ mesg = f'Failed to promote service to being a leader; {e.get("mesg")}'
39
+ outp.printf(mesg)
40
+ return 1
41
+ except s_exc.SynErr as e:
42
+ outp.printf(f'Failed to promote service {s_urlhelp.sanitizeUrl(opts.svcurl)}: {e}')
43
+ return 1
44
+
45
+ return 0
33
46
 
34
47
  if __name__ == '__main__': # pragma: no cover
35
48
  sys.exit(asyncio.run(main(sys.argv[1:])))
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: synapse
3
- Version: 2.183.0
3
+ Version: 2.185.0
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
@@ -59,15 +59,10 @@ Requires-Dist: bump2version<1.1.0,>=1.0.1; extra == "dev"
59
59
  Requires-Dist: pytest-xdist<4.0.0,>=3.0.2; extra == "dev"
60
60
  Requires-Dist: coverage<8.0.0,>=7.0.0; extra == "dev"
61
61
  Provides-Extra: docs
62
- Requires-Dist: nbconvert<8.0.0,>=7.3.1; extra == "docs"
63
- Requires-Dist: jupyter-client<=8.2.0; extra == "docs"
64
- Requires-Dist: jupyter<2.0.0,>=1.0.0; extra == "docs"
65
- Requires-Dist: hide-code<0.8.0,>=0.7.0; extra == "docs"
66
- Requires-Dist: nbstripout<1.0.0,>=0.3.3; extra == "docs"
67
- Requires-Dist: sphinx<7.0.0,>=6.2.0; extra == "docs"
68
- Requires-Dist: sphinx-rtd-theme<2.0.0,>=1.0.0; extra == "docs"
69
- Requires-Dist: sphinx-notfound-page==0.8.3; extra == "docs"
70
- Requires-Dist: jinja2<3.1.0; extra == "docs"
62
+ Requires-Dist: sphinx<9.0.0,>=8.0.0; extra == "docs"
63
+ Requires-Dist: sphinx-rtd-theme<4.0.0,>=3.0.0; extra == "docs"
64
+ Requires-Dist: sphinx-notfound-page<2.0.0,>=1.0.4; extra == "docs"
65
+ Requires-Dist: jinja2<4.0.0,>=3.1.4; extra == "docs"
71
66
 
72
67
  Synapse
73
68
  =======