synapse 2.164.0__py311-none-any.whl → 2.166.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 (89) hide show
  1. synapse/axon.py +3 -3
  2. synapse/cmds/cortex.py +1 -6
  3. synapse/common.py +7 -1
  4. synapse/cortex.py +145 -192
  5. synapse/datamodel.py +36 -1
  6. synapse/lib/agenda.py +87 -97
  7. synapse/lib/aha.py +51 -0
  8. synapse/lib/ast.py +22 -23
  9. synapse/lib/base.py +0 -6
  10. synapse/lib/boss.py +3 -0
  11. synapse/lib/cell.py +70 -39
  12. synapse/lib/certdir.py +9 -0
  13. synapse/lib/hiveauth.py +65 -12
  14. synapse/lib/httpapi.py +1 -0
  15. synapse/lib/modelrev.py +121 -33
  16. synapse/lib/modules.py +1 -0
  17. synapse/lib/nexus.py +64 -26
  18. synapse/lib/parser.py +2 -0
  19. synapse/lib/schemas.py +14 -0
  20. synapse/lib/snap.py +50 -4
  21. synapse/lib/storm.lark +4 -3
  22. synapse/lib/storm.py +96 -22
  23. synapse/lib/storm_format.py +1 -0
  24. synapse/lib/stormlib/aha.py +7 -1
  25. synapse/lib/stormlib/auth.py +13 -5
  26. synapse/lib/stormlib/cache.py +202 -0
  27. synapse/lib/stormlib/cortex.py +147 -8
  28. synapse/lib/stormlib/gen.py +53 -6
  29. synapse/lib/stormlib/math.py +1 -1
  30. synapse/lib/stormlib/model.py +11 -1
  31. synapse/lib/stormlib/spooled.py +109 -0
  32. synapse/lib/stormlib/vault.py +1 -1
  33. synapse/lib/stormtypes.py +113 -17
  34. synapse/lib/trigger.py +36 -47
  35. synapse/lib/types.py +29 -2
  36. synapse/lib/version.py +2 -2
  37. synapse/lib/view.py +80 -53
  38. synapse/models/economic.py +174 -5
  39. synapse/models/files.py +2 -0
  40. synapse/models/inet.py +77 -2
  41. synapse/models/infotech.py +12 -12
  42. synapse/models/orgs.py +72 -21
  43. synapse/models/person.py +40 -11
  44. synapse/models/risk.py +78 -24
  45. synapse/models/science.py +102 -0
  46. synapse/telepath.py +117 -35
  47. synapse/tests/test_cortex.py +84 -158
  48. synapse/tests/test_datamodel.py +22 -0
  49. synapse/tests/test_lib_agenda.py +52 -96
  50. synapse/tests/test_lib_aha.py +126 -4
  51. synapse/tests/test_lib_ast.py +412 -6
  52. synapse/tests/test_lib_cell.py +24 -8
  53. synapse/tests/test_lib_certdir.py +32 -0
  54. synapse/tests/test_lib_grammar.py +9 -1
  55. synapse/tests/test_lib_httpapi.py +0 -1
  56. synapse/tests/test_lib_jupyter.py +0 -1
  57. synapse/tests/test_lib_modelrev.py +41 -0
  58. synapse/tests/test_lib_nexus.py +38 -0
  59. synapse/tests/test_lib_storm.py +95 -5
  60. synapse/tests/test_lib_stormlib_cache.py +272 -0
  61. synapse/tests/test_lib_stormlib_cortex.py +71 -0
  62. synapse/tests/test_lib_stormlib_gen.py +37 -2
  63. synapse/tests/test_lib_stormlib_model.py +2 -0
  64. synapse/tests/test_lib_stormlib_spooled.py +190 -0
  65. synapse/tests/test_lib_stormlib_vault.py +12 -3
  66. synapse/tests/test_lib_stormsvc.py +0 -10
  67. synapse/tests/test_lib_stormtypes.py +60 -8
  68. synapse/tests/test_lib_trigger.py +20 -2
  69. synapse/tests/test_lib_types.py +17 -1
  70. synapse/tests/test_model_economic.py +114 -0
  71. synapse/tests/test_model_files.py +2 -0
  72. synapse/tests/test_model_inet.py +73 -1
  73. synapse/tests/test_model_infotech.py +2 -2
  74. synapse/tests/test_model_orgs.py +10 -1
  75. synapse/tests/test_model_risk.py +30 -2
  76. synapse/tests/test_model_science.py +59 -0
  77. synapse/tests/test_model_syn.py +0 -1
  78. synapse/tests/test_telepath.py +30 -7
  79. synapse/tests/test_tools_modrole.py +81 -0
  80. synapse/tests/test_tools_moduser.py +105 -0
  81. synapse/tools/modrole.py +59 -7
  82. synapse/tools/moduser.py +78 -10
  83. {synapse-2.164.0.dist-info → synapse-2.166.0.dist-info}/METADATA +2 -2
  84. {synapse-2.164.0.dist-info → synapse-2.166.0.dist-info}/RECORD +87 -83
  85. {synapse-2.164.0.dist-info → synapse-2.166.0.dist-info}/WHEEL +1 -1
  86. synapse/lib/provenance.py +0 -111
  87. synapse/tests/test_lib_provenance.py +0 -37
  88. {synapse-2.164.0.dist-info → synapse-2.166.0.dist-info}/LICENSE +0 -0
  89. {synapse-2.164.0.dist-info → synapse-2.166.0.dist-info}/top_level.txt +0 -0
@@ -394,6 +394,7 @@ class RiskModelTest(s_t_utils.SynTest):
394
394
  :sophistication=high
395
395
  :merged:time = 20230111
396
396
  :merged:isnow = {[ risk:threat=* ]}
397
+ :mitre:attack:group=G0001
397
398
  ]
398
399
  ''')
399
400
  self.len(1, nodes)
@@ -414,10 +415,12 @@ class RiskModelTest(s_t_utils.SynTest):
414
415
  self.eq(1673395200000, nodes[0].get('merged:time'))
415
416
  self.eq(1643673600000, nodes[0].get('reporter:discovered'))
416
417
  self.eq(1675209600000, nodes[0].get('reporter:published'))
418
+ self.eq('G0001', nodes[0].get('mitre:attack:group'))
417
419
 
418
420
  self.len(1, nodes[0].get('goals'))
419
421
  self.len(1, nodes[0].get('techniques'))
420
422
  self.len(1, await core.nodes('risk:threat:merged:isnow -> risk:threat'))
423
+ self.len(1, await core.nodes('risk:threat -> it:mitre:attack:group'))
421
424
 
422
425
  nodes = await core.nodes('''[ risk:leak=*
423
426
  :name="WikiLeaks ACME Leak"
@@ -432,6 +435,8 @@ class RiskModelTest(s_t_utils.SynTest):
432
435
  :public:url=https://wikileaks.org/acme
433
436
  :reporter={ gen.ou.org vertex }
434
437
  :reporter:name=vertex
438
+ :size:bytes=99
439
+ :extortion=*
435
440
  ]''')
436
441
  self.len(1, nodes)
437
442
  self.eq('wikileaks acme leak', nodes[0].get('name'))
@@ -439,9 +444,11 @@ class RiskModelTest(s_t_utils.SynTest):
439
444
  self.eq(1698883200000, nodes[0].get('disclosed'))
440
445
  self.eq('public.', nodes[0].get('type'))
441
446
  self.eq(1, nodes[0].get('public'))
447
+ self.eq(99, nodes[0].get('size:bytes'))
442
448
  self.eq('https://wikileaks.org/acme', nodes[0].get('public:url'))
443
449
  self.eq('vertex', nodes[0].get('reporter:name'))
444
450
 
451
+ self.len(1, await core.nodes('risk:leak -> risk:extortion'))
445
452
  self.len(1, await core.nodes('risk:leak -> risk:leak:type:taxonomy'))
446
453
  self.len(1, await core.nodes('risk:leak :owner -> ps:contact +:orgname=acme'))
447
454
  self.len(1, await core.nodes('risk:leak :leaker -> ps:contact +:orgname=wikileaks'))
@@ -451,6 +458,7 @@ class RiskModelTest(s_t_utils.SynTest):
451
458
 
452
459
  nodes = await core.nodes('''[ risk:extortion=*
453
460
  :demanded=20231102
461
+ :deadline=20240329
454
462
  :name="APT99 Extorted ACME"
455
463
  :desc="APT99 extorted ACME for a zillion vertex coins."
456
464
  :type=fingain
@@ -471,6 +479,7 @@ class RiskModelTest(s_t_utils.SynTest):
471
479
  self.eq('apt99 extorted acme', nodes[0].get('name'))
472
480
  self.eq('APT99 extorted ACME for a zillion vertex coins.', nodes[0].get('desc'))
473
481
  self.eq(1698883200000, nodes[0].get('demanded'))
482
+ self.eq(1711670400000, nodes[0].get('deadline'))
474
483
  self.eq('fingain.', nodes[0].get('type'))
475
484
  self.eq(1, nodes[0].get('public'))
476
485
  self.eq(1, nodes[0].get('success'))
@@ -501,25 +510,41 @@ class RiskModelTest(s_t_utils.SynTest):
501
510
  self.len(1, await core.nodes('risk:technique:masquerade :node -> * +inet:fqdn=microsoft-verify.com'))
502
511
  self.len(1, await core.nodes('risk:technique:masquerade :target -> * +inet:fqdn=microsoft.com'))
503
512
 
513
+ nodes = await core.nodes('''
514
+ [ risk:vulnerable=*
515
+ :period=(2022, ?)
516
+ :node=(inet:fqdn, vertex.link)
517
+ :vuln={[ risk:vuln=* :name=redtree ]}
518
+ ]
519
+ ''')
520
+ self.len(1, nodes)
521
+ self.nn(nodes[0].get('vuln'))
522
+ self.eq((1640995200000, 9223372036854775807), nodes[0].get('period'))
523
+ self.eq(('inet:fqdn', 'vertex.link'), nodes[0].get('node'))
524
+ self.len(1, await core.nodes('risk:vulnerable -> risk:vuln'))
525
+ self.len(1, await core.nodes('risk:vuln:name=redtree -> risk:vulnerable :node -> *'))
526
+
504
527
  async def test_model_risk_mitigation(self):
505
528
  async with self.getTestCore() as core:
506
529
  nodes = await core.nodes('''[
507
530
  risk:mitigation=*
508
531
  :vuln=*
509
- :name=FooBar
532
+ :name=" FooBar "
510
533
  :desc=BazFaz
511
534
  :hardware=*
512
535
  :software=*
513
536
  :reporter:name=vertex
514
537
  :reporter = { gen.ou.org vertex }
538
+ :mitre:attack:mitigation=M1036
515
539
  ]''')
516
- self.eq('FooBar', nodes[0].props['name'])
540
+ self.eq('foobar', nodes[0].props['name'])
517
541
  self.eq('BazFaz', nodes[0].props['desc'])
518
542
  self.eq('vertex', nodes[0].get('reporter:name'))
519
543
  self.nn(nodes[0].get('reporter'))
520
544
  self.len(1, await core.nodes('risk:mitigation -> risk:vuln'))
521
545
  self.len(1, await core.nodes('risk:mitigation -> it:prod:softver'))
522
546
  self.len(1, await core.nodes('risk:mitigation -> it:prod:hardware'))
547
+ self.len(1, await core.nodes('risk:mitigation -> it:mitre:attack:mitigation'))
523
548
 
524
549
  async def test_model_risk_tool_software(self):
525
550
 
@@ -536,6 +561,7 @@ class RiskModelTest(s_t_utils.SynTest):
536
561
  :reporter:published=202302
537
562
  :techniques=(*,)
538
563
  :tag=cno.mal.cobaltstrike
564
+ :mitre:attack:software=S0001
539
565
 
540
566
  :sophistication=high
541
567
  :availability=public
@@ -551,6 +577,7 @@ class RiskModelTest(s_t_utils.SynTest):
551
577
  self.eq((1325376000000, 9223372036854775807), nodes[0].get('used'))
552
578
  self.eq(1643673600000, nodes[0].get('reporter:discovered'))
553
579
  self.eq(1675209600000, nodes[0].get('reporter:published'))
580
+ self.eq('S0001', nodes[0].get('mitre:attack:software'))
554
581
 
555
582
  self.eq('cobaltstrike', nodes[0].get('soft:name'))
556
583
  self.eq(('beacon',), nodes[0].get('soft:names'))
@@ -560,6 +587,7 @@ class RiskModelTest(s_t_utils.SynTest):
560
587
  self.len(1, await core.nodes('risk:tool:software -> it:prod:soft'))
561
588
  self.len(1, await core.nodes('risk:tool:software -> ou:technique'))
562
589
  self.len(1, await core.nodes('risk:tool:software -> syn:tag'))
590
+ self.len(1, await core.nodes('risk:tool:software -> it:mitre:attack:software'))
563
591
 
564
592
  nodes = await core.nodes('''
565
593
  [ risk:vuln:soft:range=*
@@ -0,0 +1,59 @@
1
+ import synapse.exc as s_exc
2
+ import synapse.common as s_common
3
+ import synapse.tests.utils as s_t_utils
4
+
5
+ class SciModelTest(s_t_utils.SynTest):
6
+
7
+ async def test_model_sci(self):
8
+
9
+ async with self.getTestCore() as core:
10
+
11
+ nodes = await core.nodes('''
12
+ [ sci:hypothesis=*
13
+ :name="Light travels as a WAVE"
14
+ :type=physics.quantum
15
+ :summary="Light travels as a wave not a particle."
16
+ ]
17
+ ''')
18
+ self.len(1, nodes)
19
+ self.eq('physics.quantum.', nodes[0].get('type'))
20
+ self.eq('light travels as a wave', nodes[0].get('name'))
21
+ self.eq('Light travels as a wave not a particle.', nodes[0].get('summary'))
22
+
23
+ nodes = await core.nodes('''
24
+ [ sci:experiment=*
25
+ :name=double-slit
26
+ :time=2024-03-19
27
+ :type=lab.light
28
+ :summary="Foo bar baz."
29
+ ]
30
+ ''')
31
+ self.len(1, nodes)
32
+ self.eq(1710806400000, nodes[0].get('time'))
33
+ self.eq('lab.light.', nodes[0].get('type'))
34
+ self.eq('double-slit', nodes[0].get('name'))
35
+ self.eq('Foo bar baz.', nodes[0].get('summary'))
36
+
37
+ nodes = await core.nodes('''
38
+ [ sci:evidence=*
39
+ :observation={[ sci:observation=*
40
+ :time=2024-03-19
41
+ :experiment={sci:experiment:name=double-slit}
42
+ :summary="Shadows cast on the wall in a diffusion pattern."
43
+ ]}
44
+ :hypothesis={ sci:hypothesis:name="light travels as a wave" }
45
+ :summary="Shadows in wave diffusion pattern support the hypothesis."
46
+ :refutes=(false)
47
+ ]
48
+ ''')
49
+ self.len(1, nodes)
50
+ self.nn(nodes[0].get('hypothesis'))
51
+ self.nn(nodes[0].get('observation'))
52
+ self.eq(False, nodes[0].get('refutes'))
53
+ self.eq("Shadows in wave diffusion pattern support the hypothesis.", nodes[0].get('summary'))
54
+
55
+ nodes = await core.nodes('sci:observation')
56
+ self.len(1, nodes)
57
+ self.nn(nodes[0].get('experiment'))
58
+ self.eq(1710806400000, nodes[0].get('time'))
59
+ self.eq("Shadows cast on the wall in a diffusion pattern.", nodes[0].get('summary'))
@@ -11,7 +11,6 @@ class TestService(s_stormsvc.StormSvc):
11
11
  {
12
12
  'name': 'foo',
13
13
  'version': (0, 0, 1),
14
- 'synapse_minversion': [2, 144, 0],
15
14
  'synapse_version': '>=2.8.0,<3.0.0',
16
15
  'commands': (
17
16
  {
@@ -831,15 +831,17 @@ class TeleTest(s_t_utils.SynTest):
831
831
 
832
832
  await targ.waitready()
833
833
 
834
- self.eq(110, await targ.dostuff(100))
834
+ prox00 = await targ.proxy(timeout=12)
835
+ self.eq(110, await prox00.dostuff(100))
836
+
835
837
  self.eq(1, fail0.count)
836
838
  self.eq(0, fail1.count)
837
839
 
838
- _prox = await targ.proxy()
839
840
  await dmon0.fini()
840
- self.true(await _prox.waitfini(10))
841
+ self.true(await prox00.waitfini(10))
841
842
 
842
- self.eq(110, await targ.dostuff(100))
843
+ prox01 = await targ.proxy(timeout=12)
844
+ self.eq(110, await prox01.dostuff(100))
843
845
  self.eq(1, fail0.count)
844
846
  self.eq(1, fail1.count)
845
847
 
@@ -855,15 +857,36 @@ class TeleTest(s_t_utils.SynTest):
855
857
  mesgs = stream.read()
856
858
  self.notin('password', mesgs)
857
859
 
858
- self.eq(110, await targ.dostuff(100))
860
+ prox00 = await targ.proxy(timeout=12)
861
+ self.eq(110, await prox00.dostuff(100))
859
862
 
860
863
  self.eq(1, fail0.count)
861
864
  self.eq(2, fail1.count)
862
865
 
863
866
  async with await s_telepath.open(url1) as targ:
864
- await targ.waitready()
865
- self.eq(110, await targ.dostuff(100))
867
+ await targ.waitready(timeout=12)
868
+ prox00 = await targ.proxy(timeout=12)
869
+ self.eq(110, await prox00.dostuff(100))
870
+
871
+ async def onlink(proxy, urlinfo):
872
+ self.eq(110, await proxy.dostuff(100))
873
+ _url = s_telepath.zipurl(urlinfo)
874
+ logger.info(f'Connected to url={_url}')
875
+
876
+ with self.getAsyncLoggerStream('synapse.tests.test_telepath',
877
+ f'Connected to url=tcp://127.0.0.1:{addr1[1]}/foo') as stream:
878
+ async with await s_telepath.open(url1, onlink=onlink) as targ:
879
+ self.true(await stream.wait(timeout=12))
880
+
881
+ # Coverage
882
+ async def badonlink(proxy, urlinfo):
883
+ raise ValueError('oopsie')
884
+
885
+ with self.getAsyncLoggerStream('synapse.telepath', 'onlink: ') as stream:
886
+ async with await s_telepath.open(url1, onlink=badonlink) as targ:
887
+ self.true(await stream.wait(timeout=12))
866
888
 
889
+ await dmon0.fini()
867
890
  await dmon1.fini()
868
891
 
869
892
  async def test_telepath_poolsize(self):
@@ -2,6 +2,26 @@ import synapse.lib.output as s_output
2
2
  import synapse.tests.utils as s_test
3
3
  import synapse.tools.modrole as s_t_modrole
4
4
 
5
+ rolelist = s_test.deguidify('''
6
+ Roles:
7
+ ce6928dfe88cace918c405bbef51e72f - all
8
+ 0f316b3f3e6ec970fcdb9a085fcd5b77 - visi
9
+ '''.strip())
10
+
11
+ roleinfo = s_test.deguidify('''
12
+ Role: visi (145c3321a0cd0cd06de19174415a7aeb)
13
+
14
+ Rules:
15
+ [0 ] - !foo.bar.baz
16
+ [1 ] - foo.bar
17
+
18
+ Gates:
19
+ 3ad191c63a201df3246c6d6ff81763ad
20
+ Admin: False
21
+ [0 ] - !bar.baz.faz
22
+ [1 ] - bar.baz
23
+ '''.strip())
24
+
5
25
  class ModRoleTest(s_test.SynTest):
6
26
 
7
27
  async def test_tools_modrole(self):
@@ -40,6 +60,60 @@ class ModRoleTest(s_test.SynTest):
40
60
  self.true(bool(visi.allowed('foo.bar.gaz'.split('.'))))
41
61
  self.false(bool(visi.allowed('foo.bar.baz'.split('.'))))
42
62
 
63
+ gateiden = core.getLayer().iden
64
+ argv = (
65
+ '--svcurl', svcurl,
66
+ 'visi',
67
+ '--allow', 'bar.baz',
68
+ '--deny', 'bar.baz.faz',
69
+ '--gate', gateiden,
70
+ )
71
+ outp = s_output.OutPutStr()
72
+ self.eq(0, await s_t_modrole.main(argv, outp=outp))
73
+ self.isin(f'...adding allow rule: bar.baz on gate {gateiden}', str(outp))
74
+ self.isin(f'...adding deny rule: bar.baz.faz on gate {gateiden}', str(outp))
75
+
76
+ gate = await core.getAuthGate(gateiden)
77
+ for role in gate['roles']:
78
+ if role['iden'] == visi.iden:
79
+ self.isin((True, ('bar', 'baz')), role['rules'])
80
+ self.isin((False, ('bar', 'baz', 'faz')), role['rules'])
81
+
82
+ argv = (
83
+ '--svcurl', svcurl,
84
+ '--list',
85
+ )
86
+ outp = s_output.OutPutStr()
87
+ self.eq(0, await s_t_modrole.main(argv, outp=outp))
88
+ self.isin(rolelist, s_test.deguidify(str(outp)))
89
+
90
+ argv = (
91
+ '--svcurl', svcurl,
92
+ '--list',
93
+ 'visi',
94
+ )
95
+ outp = s_output.OutPutStr()
96
+ self.eq(0, await s_t_modrole.main(argv, outp=outp))
97
+ self.isin(roleinfo, s_test.deguidify(str(outp)))
98
+
99
+ argv = (
100
+ '--svcurl', svcurl,
101
+ '--list',
102
+ 'newprole',
103
+ )
104
+ outp = s_output.OutPutStr()
105
+ self.eq(1, await s_t_modrole.main(argv, outp=outp))
106
+ self.isin('ERROR: Role not found: newprole', str(outp))
107
+
108
+ argv = (
109
+ '--svcurl', svcurl,
110
+ 'visi',
111
+ '--gate', 'newp',
112
+ )
113
+ outp = s_output.OutPutStr()
114
+ self.eq(1, await s_t_modrole.main(argv, outp=outp))
115
+ self.isin('ERROR: No auth gate found with iden: newp', str(outp))
116
+
43
117
  argv = (
44
118
  '--svcurl', svcurl,
45
119
  '--add',
@@ -59,3 +133,10 @@ class ModRoleTest(s_test.SynTest):
59
133
  self.eq(0, await s_t_modrole.main(argv, outp=outp))
60
134
  self.isin('...deleting role: visi', str(outp))
61
135
  self.none(await core.auth.getRoleByName('visi'))
136
+
137
+ argv = (
138
+ '--svcurl', svcurl,
139
+ )
140
+ outp = s_output.OutPutStr()
141
+ self.eq(1, await s_t_modrole.main(argv, outp=outp))
142
+ self.isin('ERROR: A rolename argument is required when --list is not specified.', str(outp))
@@ -2,6 +2,32 @@ import synapse.lib.output as s_output
2
2
  import synapse.tests.utils as s_test
3
3
  import synapse.tools.moduser as s_t_moduser
4
4
 
5
+ userlist = '''
6
+ Users:
7
+ root
8
+ visi
9
+ '''.strip()
10
+
11
+ userinfo = s_test.deguidify('''
12
+ User: visi (04dddd4ff39e4ce00b36c7d526b9eac7)
13
+
14
+ Locked: False
15
+ Admin: False
16
+ Email: visi@test.com
17
+ Rules:
18
+ [0 ] - !foo.bar.baz
19
+ [1 ] - foo.bar
20
+
21
+ Roles:
22
+ 576a948f9944c58d3953f0d36bc2da81 - all
23
+
24
+ Gates:
25
+ c7b276154c0c799430668cb3c4cd259d
26
+ Admin: False
27
+ [0 ] - !bar.baz.faz
28
+ [1 ] - bar.baz
29
+ '''.strip())
30
+
5
31
  class ModUserTest(s_test.SynTest):
6
32
 
7
33
  async def test_tools_moduser(self):
@@ -124,6 +150,78 @@ class ModUserTest(s_test.SynTest):
124
150
  self.true(bool(visi.allowed('foo.bar.gaz'.split('.'))))
125
151
  self.false(bool(visi.allowed('foo.bar.baz'.split('.'))))
126
152
 
153
+ gateiden = core.getLayer().iden
154
+ argv = (
155
+ '--svcurl', svcurl,
156
+ 'visi',
157
+ '--admin', 'true',
158
+ '--gate', gateiden,
159
+ )
160
+ outp = s_output.OutPutStr()
161
+ self.eq(0, await s_t_moduser.main(argv, outp=outp))
162
+ self.isin(f'...setting admin: true on gate {gateiden}', str(outp))
163
+
164
+ gate = await core.getAuthGate(gateiden)
165
+ for user in gate['users']:
166
+ if user['iden'] == visi.iden:
167
+ self.true(user['admin'])
168
+
169
+ gateiden = core.getLayer().iden
170
+ argv = (
171
+ '--svcurl', svcurl,
172
+ 'visi',
173
+ '--admin', 'false',
174
+ '--allow', 'bar.baz',
175
+ '--deny', 'bar.baz.faz',
176
+ '--gate', gateiden,
177
+ )
178
+ outp = s_output.OutPutStr()
179
+ self.eq(0, await s_t_moduser.main(argv, outp=outp))
180
+ self.isin(f'...setting admin: false on gate {gateiden}', str(outp))
181
+ self.isin(f'...adding allow rule: bar.baz on gate {gateiden}', str(outp))
182
+ self.isin(f'...adding deny rule: bar.baz.faz on gate {gateiden}', str(outp))
183
+
184
+ gate = await core.getAuthGate(gateiden)
185
+ for user in gate['users']:
186
+ if user['iden'] == visi.iden:
187
+ self.isin((True, ('bar', 'baz')), user['rules'])
188
+ self.isin((False, ('bar', 'baz', 'faz')), user['rules'])
189
+
190
+ argv = (
191
+ '--svcurl', svcurl,
192
+ '--list',
193
+ )
194
+ outp = s_output.OutPutStr()
195
+ self.eq(0, await s_t_moduser.main(argv, outp=outp))
196
+ self.isin(userlist, str(outp))
197
+
198
+ argv = (
199
+ '--svcurl', svcurl,
200
+ '--list',
201
+ 'visi',
202
+ )
203
+ outp = s_output.OutPutStr()
204
+ self.eq(0, await s_t_moduser.main(argv, outp=outp))
205
+ self.isin(userinfo, s_test.deguidify(str(outp)))
206
+
207
+ argv = (
208
+ '--svcurl', svcurl,
209
+ '--list',
210
+ 'newpuser',
211
+ )
212
+ outp = s_output.OutPutStr()
213
+ self.eq(1, await s_t_moduser.main(argv, outp=outp))
214
+ self.isin('ERROR: User not found: newpuser', str(outp))
215
+
216
+ argv = (
217
+ '--svcurl', svcurl,
218
+ 'visi',
219
+ '--gate', 'newp',
220
+ )
221
+ outp = s_output.OutPutStr()
222
+ self.eq(1, await s_t_moduser.main(argv, outp=outp))
223
+ self.isin('ERROR: No auth gate found with iden: newp', str(outp))
224
+
127
225
  argv = (
128
226
  '--svcurl', svcurl,
129
227
  'visi',
@@ -151,3 +249,10 @@ class ModUserTest(s_test.SynTest):
151
249
  outp = s_output.OutPutStr()
152
250
  self.eq(1, await s_t_moduser.main(argv, outp=outp))
153
251
  self.isin('ERROR: User not found (need --add?): visi', str(outp))
252
+
253
+ argv = (
254
+ '--svcurl', svcurl,
255
+ )
256
+ outp = s_output.OutPutStr()
257
+ self.eq(1, await s_t_moduser.main(argv, outp=outp))
258
+ self.isin('ERROR: A username argument is required when --list is not specified.', str(outp))
synapse/tools/modrole.py CHANGED
@@ -11,15 +11,34 @@ descr = '''
11
11
  Add or modify a role in a Synapse service.
12
12
  '''
13
13
 
14
+ def printrole(role, outp):
15
+
16
+ outp.printf(f'Role: {role.get("name")} ({role.get("iden")})')
17
+ outp.printf('')
18
+ outp.printf(' Rules:')
19
+ for indx, rule in enumerate(role.get('rules')):
20
+ outp.printf(f' [{str(indx).ljust(3)}] - {s_common.reprauthrule(rule)}')
21
+
22
+ outp.printf('')
23
+ outp.printf(' Gates:')
24
+ for gateiden, gateinfo in role.get('authgates', {}).items():
25
+ outp.printf(f' {gateiden}')
26
+ outp.printf(f' Admin: {gateinfo.get("admin") == True}')
27
+ for indx, rule in enumerate(gateinfo.get('rules', ())):
28
+ outp.printf(f' [{str(indx).ljust(3)}] - {s_common.reprauthrule(rule)}')
29
+
14
30
  async def main(argv, outp=s_output.stdout):
15
31
 
16
32
  pars = argparse.ArgumentParser(prog='modrole', description=descr)
17
33
  pars.add_argument('--svcurl', default='cell:///vertex/storage', help='The telepath URL of the Synapse service.')
18
34
  pars.add_argument('--add', default=False, action='store_true', help='Add the role if they do not already exist.')
19
35
  pars.add_argument('--del', dest='delete', default=False, action='store_true', help='Delete the role if it exists.')
36
+ pars.add_argument('--list', default=False, action='store_true',
37
+ help='List existing roles, or rules of a specific role.')
20
38
  pars.add_argument('--allow', default=[], action='append', help='A permission string to allow for the role.')
21
39
  pars.add_argument('--deny', default=[], action='append', help='A permission string to deny for the role.')
22
- pars.add_argument('rolename', help='The rolename to add/edit.')
40
+ pars.add_argument('--gate', default=None, help='The iden of an auth gate to add/del rules on.')
41
+ pars.add_argument('rolename', nargs='?', help='The rolename to add/edit.')
23
42
 
24
43
  opts = pars.parse_args(argv)
25
44
 
@@ -31,6 +50,31 @@ async def main(argv, outp=s_output.stdout):
31
50
 
32
51
  async with await s_telepath.openurl(opts.svcurl) as cell:
33
52
 
53
+ if opts.list:
54
+ if opts.rolename:
55
+ role = await cell.getRoleDefByName(opts.rolename)
56
+ if role is None:
57
+ outp.printf(f'ERROR: Role not found: {opts.rolename}')
58
+ return 1
59
+
60
+ printrole(role, outp)
61
+
62
+ else:
63
+ outp.printf('Roles:')
64
+ for role in await cell.getRoleDefs():
65
+ outp.printf(f' {role.get("iden")} - {role.get("name")}')
66
+
67
+ return 0
68
+ elif opts.rolename is None:
69
+ outp.printf(f'ERROR: A rolename argument is required when --list is not specified.')
70
+ return 1
71
+
72
+ if opts.gate:
73
+ gate = await cell.getAuthGate(opts.gate)
74
+ if gate is None:
75
+ outp.printf(f'ERROR: No auth gate found with iden: {opts.gate}')
76
+ return 1
77
+
34
78
  role = await cell.getRoleDefByName(opts.rolename)
35
79
  if role is not None:
36
80
  outp.printf(f'Modifying role: {opts.rolename}')
@@ -52,15 +96,23 @@ async def main(argv, outp=s_output.stdout):
52
96
 
53
97
  for allow in opts.allow:
54
98
  perm = allow.lower().split('.')
55
- outp.printf(f'...adding allow rule: {allow}')
56
- if not await cell.isRoleAllowed(roleiden, perm):
57
- await cell.addRoleRule(roleiden, (True, perm), indx=0)
99
+ mesg = f'...adding allow rule: {allow}'
100
+ if opts.gate:
101
+ mesg += f' on gate {opts.gate}'
102
+
103
+ outp.printf(mesg)
104
+ if not await cell.isRoleAllowed(roleiden, perm, gateiden=opts.gate):
105
+ await cell.addRoleRule(roleiden, (True, perm), indx=0, gateiden=opts.gate)
58
106
 
59
107
  for deny in opts.deny:
60
108
  perm = deny.lower().split('.')
61
- outp.printf(f'...adding deny rule: {deny}')
62
- if await cell.isRoleAllowed(roleiden, perm):
63
- await cell.addRoleRule(roleiden, (False, perm), indx=0)
109
+ mesg = f'...adding deny rule: {deny}'
110
+ if opts.gate:
111
+ mesg += f' on gate {opts.gate}'
112
+
113
+ outp.printf(mesg)
114
+ if await cell.isRoleAllowed(roleiden, perm, gateiden=opts.gate):
115
+ await cell.addRoleRule(roleiden, (False, perm), indx=0, gateiden=opts.gate)
64
116
  return 0
65
117
 
66
118
  if __name__ == '__main__': # pragma: no cover