synapse 2.191.0__py311-none-any.whl → 2.193.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/axon.py +54 -23
  2. synapse/common.py +15 -0
  3. synapse/cortex.py +18 -20
  4. synapse/exc.py +6 -1
  5. synapse/lib/agenda.py +0 -2
  6. synapse/lib/ast.py +30 -12
  7. synapse/lib/cell.py +79 -85
  8. synapse/lib/cli.py +20 -11
  9. synapse/lib/nexus.py +2 -1
  10. synapse/lib/parser.py +1 -1
  11. synapse/lib/snap.py +4 -4
  12. synapse/lib/storm.py +34 -17
  13. synapse/lib/stormhttp.py +32 -35
  14. synapse/lib/stormlib/json.py +5 -2
  15. synapse/lib/stormtypes.py +121 -20
  16. synapse/lib/version.py +2 -2
  17. synapse/models/inet.py +17 -1
  18. synapse/models/infotech.py +14 -4
  19. synapse/models/risk.py +16 -2
  20. synapse/tests/test_axon.py +10 -0
  21. synapse/tests/test_cortex.py +55 -3
  22. synapse/tests/test_exc.py +3 -0
  23. synapse/tests/test_lib_agenda.py +157 -1
  24. synapse/tests/test_lib_ast.py +49 -1
  25. synapse/tests/test_lib_cell.py +106 -1
  26. synapse/tests/test_lib_httpapi.py +9 -2
  27. synapse/tests/test_lib_storm.py +72 -30
  28. synapse/tests/test_lib_stormhttp.py +57 -12
  29. synapse/tests/test_lib_stormlib_json.py +20 -0
  30. synapse/tests/test_lib_stormlib_scrape.py +2 -2
  31. synapse/tests/test_model_inet.py +40 -5
  32. synapse/tests/test_model_risk.py +2 -0
  33. synapse/tests/test_servers_univ.py +0 -12
  34. synapse/tests/test_tools_apikey.py +227 -0
  35. synapse/tests/test_tools_storm.py +95 -0
  36. synapse/tests/test_utils_getrefs.py +1 -1
  37. synapse/tools/apikey.py +93 -0
  38. synapse/utils/getrefs.py +14 -3
  39. synapse/vendor/cpython/lib/http/__init__.py +0 -0
  40. synapse/vendor/cpython/lib/http/cookies.py +59 -0
  41. synapse/vendor/cpython/lib/test/test_http_cookies.py +49 -0
  42. {synapse-2.191.0.dist-info → synapse-2.193.0.dist-info}/METADATA +2 -2
  43. {synapse-2.191.0.dist-info → synapse-2.193.0.dist-info}/RECORD +46 -41
  44. {synapse-2.191.0.dist-info → synapse-2.193.0.dist-info}/WHEEL +1 -1
  45. {synapse-2.191.0.dist-info → synapse-2.193.0.dist-info}/LICENSE +0 -0
  46. {synapse-2.191.0.dist-info → synapse-2.193.0.dist-info}/top_level.txt +0 -0
@@ -9,7 +9,7 @@ import synapse.common as s_common
9
9
  import synapse.lib.certdir as s_certdir
10
10
  import synapse.lib.httpapi as s_httpapi
11
11
  import synapse.lib.stormctrl as s_stormctrl
12
- import synapse.lib.stormhttp as s_stormhttp
12
+ import synapse.lib.stormtypes as s_stormtypes
13
13
 
14
14
  import synapse.tests.utils as s_test
15
15
 
@@ -606,11 +606,39 @@ class StormHttpTest(s_test.SynTest):
606
606
  self.false(resp.get('ok'))
607
607
  self.ne(-1, resp['mesg'].find('connect to proxy 127.0.0.1:1'))
608
608
 
609
+ msgs = await core.stormlist('$resp=$lib.axon.wget("http://vertex.link", proxy=(null)) $lib.print($resp.mesg)')
610
+ self.stormIsInWarn('HTTP proxy argument to $lib.null is deprecated', msgs)
611
+ self.stormIsInPrint('connect to proxy 127.0.0.1:1', msgs)
612
+
613
+ await self.asyncraises(s_exc.BadArg, core.nodes('$lib.axon.wget("http://vertex.link", proxy=(1.1))'))
614
+
615
+ # todo: setting the synapse version can be removed once proxy=true support is released
616
+ try:
617
+ oldv = core.axoninfo['synapse']['version']
618
+ core.axoninfo['synapse']['version'] = (oldv[0], oldv[1] + 1, oldv[2])
619
+ resp = await core.callStorm('return($lib.axon.wget("http://vertex.link", proxy=(null)))')
620
+ self.false(resp.get('ok'))
621
+ self.ne(-1, resp['mesg'].find('connect to proxy 127.0.0.1:1'))
622
+ finally:
623
+ core.axoninfo['synapse']['version'] = oldv
624
+
625
+ size, sha256 = await core.axon.put(b'asdf')
626
+ opts = {'vars': {'sha256': s_common.ehex(sha256)}}
627
+ resp = await core.callStorm(f'return($lib.axon.wput($sha256, http://vertex.link))', opts=opts)
628
+ self.false(resp.get('ok'))
629
+ self.isin('connect to proxy 127.0.0.1:1', resp['mesg'])
630
+
609
631
  q = '$resp=$lib.inet.http.get("http://vertex.link") return(($resp.code, $resp.err))'
610
632
  code, (errname, _) = await core.callStorm(q)
611
633
  self.eq(code, -1)
612
634
  self.eq('ProxyConnectionError', errname)
613
635
 
636
+ msgs = await core.stormlist('$resp=$lib.inet.http.get("http://vertex.link", proxy=(null)) $lib.print($resp.err)')
637
+ self.stormIsInWarn('HTTP proxy argument to $lib.null is deprecated', msgs)
638
+ self.stormIsInPrint('connect to proxy 127.0.0.1:1', msgs)
639
+
640
+ await self.asyncraises(s_exc.BadArg, core.nodes('$lib.inet.http.get("http://vertex.link", proxy=(1.1))'))
641
+
614
642
  async with self.getTestCore() as core:
615
643
 
616
644
  visi = await core.auth.addUser('visi')
@@ -654,6 +682,24 @@ class StormHttpTest(s_test.SynTest):
654
682
  self.false(resp.get('ok'))
655
683
  self.isin('connect to proxy 127.0.0.1:1', resp['mesg'])
656
684
 
685
+ host, port = await core.addHttpsPort(0)
686
+ opts = {
687
+ 'vars': {
688
+ 'url': f'https://loop.vertex.link:{port}',
689
+ 'proxy': 'socks5://user:pass@127.0.0.1:1',
690
+ }
691
+ }
692
+ try:
693
+ oldv = core.axoninfo['synapse']['version']
694
+ minver = s_stormtypes.AXON_MINVERS_PROXY
695
+ core.axoninfo['synapse']['version'] = minver[2], minver[1] - 1, minver[0]
696
+ q = '$resp=$lib.axon.wget($url, ssl=(false), proxy=$proxy) $lib.print(`code={$resp.code}`)'
697
+ mesgs = await core.stormlist(q, opts=opts)
698
+ self.stormIsInPrint('code=404', mesgs)
699
+ self.stormIsInWarn('Axon version does not support proxy argument', mesgs)
700
+ finally:
701
+ core.axoninfo['synapse']['version'] = oldv
702
+
657
703
  async with self.getTestCore(conf=conf) as core:
658
704
  # Proxy permission tests in this section
659
705
 
@@ -762,10 +808,15 @@ class StormHttpTest(s_test.SynTest):
762
808
  ($ok, $valu) = $sock.tx(lololol)
763
809
  return($sock.rx())
764
810
  '''
765
- opts = {'vars': {'port': port, 'proxy': None}}
811
+ opts = {'vars': {'port': port, 'proxy': True}}
766
812
  self.eq((True, ('echo', 'lololol')),
767
813
  await core.callStorm(query, opts=opts))
768
814
 
815
+ opts = {'vars': {'port': port, 'proxy': None}}
816
+ mesgs = await core.stormlist(query, opts=opts)
817
+ self.stormIsInWarn('proxy argument to $lib.null is deprecated', mesgs)
818
+ self.true(mesgs[-2][0] == 'err' and mesgs[-2][1][1]['mesg'] == "(True, ['echo', 'lololol'])")
819
+
769
820
  visi = await core.auth.addUser('visi')
770
821
 
771
822
  opts = {'user': visi.iden, 'vars': {'port': port, 'proxy': False}}
@@ -903,16 +954,10 @@ class StormHttpTest(s_test.SynTest):
903
954
  core.axoninfo['synapse']['version'] = oldv
904
955
 
905
956
  ## version check succeeds
906
- # todo: setting the synapse version can be removed once ssl_opts is released
907
- try:
908
- oldv = core.axoninfo['synapse']['version']
909
- core.axoninfo['synapse']['version'] = (oldv[0], oldv[1] + 1, oldv[2])
910
- self.eq(200, await core.callStorm(axon_queries['postfile'], opts=opts))
911
- self.eq(200, await core.callStorm(axon_queries['wget'], opts=opts))
912
- self.eq(200, await core.callStorm(axon_queries['wput'], opts=opts))
913
- self.len(1, await core.nodes(axon_queries['urlfile'], opts=opts))
914
- finally:
915
- core.axoninfo['synapse']['version'] = oldv
957
+ self.eq(200, await core.callStorm(axon_queries['postfile'], opts=opts))
958
+ self.eq(200, await core.callStorm(axon_queries['wget'], opts=opts))
959
+ self.eq(200, await core.callStorm(axon_queries['wput'], opts=opts))
960
+ self.len(1, await core.nodes(axon_queries['urlfile'], opts=opts))
916
961
 
917
962
  # verify arg precedence
918
963
 
@@ -12,6 +12,26 @@ class JsonTest(s_test.SynTest):
12
12
 
13
13
  self.eq(((1, 2, 3)), await core.callStorm('return($lib.json.load("[1, 2, 3]"))'))
14
14
  self.eq(('["foo", "bar", "baz"]'), await core.callStorm('return($lib.json.save((foo, bar, baz)))'))
15
+ self.eq(('{"foo": 1, "bar": {"baz": "hello"}}'), await core.callStorm('return($lib.json.save(({"foo": 1, "bar": {"baz": "hello"}})))'))
16
+ self.eq(('{"foo": 1, "bar": {"baz": "hello"}}'), await core.callStorm('return($lib.json.save(({"foo": 1, "bar": {"baz": "hello"}}), (null)))'))
17
+ self.eq((
18
+ '''{
19
+ "foo": 1,
20
+ "bar": {
21
+ "baz": "hello"
22
+ }
23
+ }'''), await core.callStorm('return($lib.json.save(({"foo": 1, "bar": {"baz": "hello"}}), indent=(4)))'))
24
+
25
+ self.eq((
26
+ '''{
27
+ "foo": 1,
28
+ "bar": {
29
+ "baz": "hello"
30
+ }
31
+ }'''), await core.callStorm('return($lib.json.save(({"foo": 1, "bar": {"baz": "hello"}}), indent=2))'))
32
+
33
+ with self.raises(s_exc.BadCast):
34
+ await core.callStorm('return($lib.json.save(({"foo": 1, "bar": {"baz": "hello"}}), indent=x))')
15
35
 
16
36
  with self.raises(s_exc.BadJsonText):
17
37
  await core.callStorm('return($lib.json.load(foo))')
@@ -92,7 +92,7 @@ class StormScrapeTest(s_test.SynTest):
92
92
  self.len(0, mods)
93
93
  self.len(0, core.modsbyiface.get('scrape'))
94
94
 
95
- await core.loadStormPkg(pkgdef)
95
+ core.loadStormPkg(pkgdef)
96
96
 
97
97
  mods = await core.getStormIfaces('scrape')
98
98
  self.len(2, mods)
@@ -131,7 +131,7 @@ class StormScrapeTest(s_test.SynTest):
131
131
  conf = {'storm:interface:scrape': False, }
132
132
  async with self.getTestCore(conf=conf) as core:
133
133
 
134
- await core.loadStormPkg(pkgdef)
134
+ core.loadStormPkg(pkgdef)
135
135
 
136
136
  mods = await core.getStormIfaces('scrape')
137
137
  self.len(2, mods)
@@ -10,17 +10,40 @@ class InetModelTest(s_t_utils.SynTest):
10
10
 
11
11
  async def test_model_inet_basics(self):
12
12
  async with self.getTestCore() as core:
13
+ self.len(1, await core.nodes('[ inet:web:hashtag="#🫠" ]'))
14
+ self.len(1, await core.nodes('[ inet:web:hashtag="#🫠🫠" ]'))
15
+ self.len(1, await core.nodes('[ inet:web:hashtag="#·bar"]'))
16
+ self.len(1, await core.nodes('[ inet:web:hashtag="#foo·"]'))
17
+ self.len(1, await core.nodes('[ inet:web:hashtag="#foo〜"]'))
13
18
  self.len(1, await core.nodes('[ inet:web:hashtag="#hehe" ]'))
14
19
  self.len(1, await core.nodes('[ inet:web:hashtag="#foo·bar"]')) # note the interpunct
20
+ self.len(1, await core.nodes('[ inet:web:hashtag="#foo〜bar"]')) # note the wave dash
15
21
  self.len(1, await core.nodes('[ inet:web:hashtag="#fo·o·······b·ar"]'))
16
22
  with self.raises(s_exc.BadTypeValu):
17
23
  await core.nodes('[ inet:web:hashtag="foo" ]')
24
+
18
25
  with self.raises(s_exc.BadTypeValu):
19
- await core.nodes('[ inet:web:hashtag="#foo bar" ]')
20
- with self.raises(s_exc.BadTypeValu):
21
- self.len(1, await core.nodes('[ inet:web:hashtag="#·bar"]'))
22
- with self.raises(s_exc.BadTypeValu):
23
- self.len(1, await core.nodes('[ inet:web:hashtag="#foo·"]'))
26
+ await core.nodes('[ inet:web:hashtag="#foo#bar" ]')
27
+
28
+ # All unicode whitespace from:
29
+ # https://www.compart.com/en/unicode/category/Zl
30
+ # https://www.compart.com/en/unicode/category/Zp
31
+ # https://www.compart.com/en/unicode/category/Zs
32
+ whitespace = [
33
+ '\u0020', '\u00a0', '\u1680', '\u2000', '\u2001', '\u2002', '\u2003', '\u2004',
34
+ '\u2005', '\u2006', '\u2007', '\u2008', '\u2009', '\u200a', '\u202f', '\u205f',
35
+ '\u3000', '\u2028', '\u2029',
36
+ ]
37
+ for char in whitespace:
38
+ with self.raises(s_exc.BadTypeValu):
39
+ await core.callStorm(f'[ inet:web:hashtag="#foo{char}bar" ]')
40
+
41
+ with self.raises(s_exc.BadTypeValu):
42
+ await core.callStorm(f'[ inet:web:hashtag="#{char}bar" ]')
43
+
44
+ # These are allowed because strip=True
45
+ await core.callStorm(f'[ inet:web:hashtag="#foo{char}" ]')
46
+ await core.callStorm(f'[ inet:web:hashtag=" #foo{char}" ]')
24
47
 
25
48
  nodes = await core.nodes('''
26
49
  [ inet:web:instance=(foo,)
@@ -457,6 +480,7 @@ class InetModelTest(s_t_utils.SynTest):
457
480
  :raw=((10), (20))
458
481
  :src:txfiles={[ file:attachment=* :name=foo.exe ]}
459
482
  :dst:txfiles={[ file:attachment=* :name=bar.exe ]}
483
+ :capture:host=*
460
484
  )]'''
461
485
  nodes = await core.nodes(q, opts={'vars': {'valu': valu, 'p': props}})
462
486
  self.len(1, nodes)
@@ -500,11 +524,13 @@ class InetModelTest(s_t_utils.SynTest):
500
524
  self.eq(node.get('src:rdp:hostname'), 'syncoder')
501
525
  self.eq(node.get('src:rdp:keyboard:layout'), 'azerty')
502
526
  self.eq(node.get('raw'), (10, 20))
527
+ self.nn(node.get('capture:host'))
503
528
  self.len(2, await core.nodes('inet:flow -> crypto:x509:cert'))
504
529
  self.len(1, await core.nodes('inet:flow :src:ssh:key -> crypto:key'))
505
530
  self.len(1, await core.nodes('inet:flow :dst:ssh:key -> crypto:key'))
506
531
  self.len(1, await core.nodes('inet:flow :src:txfiles -> file:attachment +:name=foo.exe'))
507
532
  self.len(1, await core.nodes('inet:flow :dst:txfiles -> file:attachment +:name=bar.exe'))
533
+ self.len(1, await core.nodes('inet:flow :capture:host -> it:host'))
508
534
 
509
535
  async def test_fqdn(self):
510
536
  formname = 'inet:fqdn'
@@ -2746,6 +2772,7 @@ class InetModelTest(s_t_utils.SynTest):
2746
2772
  q = '''
2747
2773
  [
2748
2774
  inet:email:message="*"
2775
+ :id="Woot-12345 "
2749
2776
  :to=woot@woot.com
2750
2777
  :from=visi@vertex.link
2751
2778
  :replyto=root@root.com
@@ -2767,6 +2794,7 @@ class InetModelTest(s_t_utils.SynTest):
2767
2794
  nodes = await core.nodes(q, opts={'vars': {'flow': flow}})
2768
2795
  self.len(1, nodes)
2769
2796
 
2797
+ self.eq(nodes[0].get('id'), 'Woot-12345')
2770
2798
  self.eq(nodes[0].get('cc'), ('baz@faz.org', 'foo@bar.com'))
2771
2799
  self.eq(nodes[0].get('received:from:ipv6'), '::1')
2772
2800
  self.eq(nodes[0].get('received:from:ipv4'), 0x01020304)
@@ -2847,6 +2875,7 @@ class InetModelTest(s_t_utils.SynTest):
2847
2875
  nodes = await core.nodes('''
2848
2876
  [ inet:egress=*
2849
2877
  :host = *
2878
+ :host:iface = *
2850
2879
  :client=1.2.3.4
2851
2880
  :client:ipv6="::1"
2852
2881
  ]
@@ -2854,10 +2883,14 @@ class InetModelTest(s_t_utils.SynTest):
2854
2883
 
2855
2884
  self.len(1, nodes)
2856
2885
  self.nn(nodes[0].get('host'))
2886
+ self.nn(nodes[0].get('host:iface'))
2857
2887
  self.eq(nodes[0].get('client'), 'tcp://1.2.3.4')
2858
2888
  self.eq(nodes[0].get('client:ipv4'), 0x01020304)
2859
2889
  self.eq(nodes[0].get('client:ipv6'), '::1')
2860
2890
 
2891
+ self.len(1, await core.nodes('inet:egress -> it:host'))
2892
+ self.len(1, await core.nodes('inet:egress -> inet:iface'))
2893
+
2861
2894
  async def test_model_inet_tls_handshake(self):
2862
2895
 
2863
2896
  async with self.getTestCore() as core:
@@ -2976,6 +3009,7 @@ class InetModelTest(s_t_utils.SynTest):
2976
3009
  (inet:service:account=(blackout, account, vertex, slack)
2977
3010
  :id=U7RN51U1J
2978
3011
  :user=blackout
3012
+ :url=https://vertex.link/users/blackout
2979
3013
  :email=blackout@vertex.link
2980
3014
  :profile={ gen.ps.contact.email vertex.employee blackout@vertex.link }
2981
3015
  :tenant={[ inet:service:tenant=({"id": "VS-31337"}) ]}
@@ -3003,6 +3037,7 @@ class InetModelTest(s_t_utils.SynTest):
3003
3037
  self.eq(accounts[0].ndef, ('inet:service:account', s_common.guid(('blackout', 'account', 'vertex', 'slack'))))
3004
3038
  self.eq(accounts[0].get('id'), 'U7RN51U1J')
3005
3039
  self.eq(accounts[0].get('user'), 'blackout')
3040
+ self.eq(accounts[0].get('url'), 'https://vertex.link/users/blackout')
3006
3041
  self.eq(accounts[0].get('email'), 'blackout@vertex.link')
3007
3042
  self.eq(accounts[0].get('profile'), blckprof.ndef[1])
3008
3043
 
@@ -430,6 +430,7 @@ class RiskModelTest(s_t_utils.SynTest):
430
430
  :disclosed=20231102
431
431
  :owner={ gen.ou.org.hq acme }
432
432
  :leaker={ gen.ou.org.hq wikileaks }
433
+ :recipient={ gen.ou.org.hq everyone }
433
434
  :type=public
434
435
  :goal={[ ou:goal=* :name=publicity ]}
435
436
  :compromise={[ risk:compromise=* :target={ gen.ou.org.hq acme } ]}
@@ -458,6 +459,7 @@ class RiskModelTest(s_t_utils.SynTest):
458
459
  self.len(1, await core.nodes('risk:leak -> risk:leak:type:taxonomy'))
459
460
  self.len(1, await core.nodes('risk:leak :owner -> ps:contact +:orgname=acme'))
460
461
  self.len(1, await core.nodes('risk:leak :leaker -> ps:contact +:orgname=wikileaks'))
462
+ self.len(1, await core.nodes('risk:leak :recipient -> ps:contact +:orgname=everyone'))
461
463
  self.len(1, await core.nodes('risk:leak -> ou:goal +:name=publicity'))
462
464
  self.len(1, await core.nodes('risk:leak -> risk:compromise :target -> ps:contact +:orgname=acme'))
463
465
  self.len(1, await core.nodes('risk:leak :reporter -> ou:org +:name=vertex'))
@@ -56,18 +56,6 @@ class UnivServerTest(s_t_utils.SynTest):
56
56
  async with await s_telepath.openurl(f'cell://{dirn}') as proxy:
57
57
  self.eq('cell', await proxy.getCellType())
58
58
 
59
- argv = [
60
- 'synapse.tests.test_lib_cell.EchoAuth',
61
- '--telepath', 'tcp://127.0.0.1:0/',
62
- '--https', '0',
63
- '--name', 'univtest',
64
- dirn,
65
- ]
66
- # Or start the Cortex off a a EchoAuth (don't do this in practice...)
67
- async with await s_s_univ.main(argv) as cell:
68
- async with await s_telepath.openurl(f'cell://{dirn}') as proxy:
69
- self.eq('echoauth', await proxy.getCellType())
70
-
71
59
  argv = ['synapse.lib.newp.Newp']
72
60
  with self.raises(s_exc.NoSuchCtor):
73
61
  async with await s_s_univ.main(argv) as core:
@@ -0,0 +1,227 @@
1
+ import datetime
2
+
3
+ import synapse.exc as s_exc
4
+ import synapse.common as s_common
5
+
6
+ import synapse.lib.time as s_time
7
+ import synapse.lib.output as s_output
8
+
9
+ import synapse.tests.utils as s_test
10
+ import synapse.tools.apikey as s_t_apikey
11
+
12
+ async def getApiKeyByName(core, name):
13
+ keys = {k.get('name'): k async for k in core.getApiKeys()}
14
+ return keys.get(name)
15
+
16
+ class ApiKeyTest(s_test.SynTest):
17
+
18
+ async def test_tools_apikey(self):
19
+ async with self.getTestCore() as core:
20
+
21
+ await core.auth.addUser('blackout')
22
+
23
+ rooturl = core.getLocalUrl()
24
+ blckurl = core.getLocalUrl(user='blackout')
25
+
26
+ # Add API keys
27
+ argv = (
28
+ '--svcurl', rooturl,
29
+ 'add',
30
+ 'rootkey00',
31
+ '-d', '120',
32
+ )
33
+ outp = s_output.OutPutStr()
34
+ self.eq(0, await s_t_apikey.main(argv, outp=outp))
35
+
36
+ self.isin('Successfully added API key with name=rootkey00.', str(outp))
37
+ rootkey00 = await getApiKeyByName(core, 'rootkey00')
38
+
39
+ self.isin(f'Iden: {rootkey00.get("iden")}', str(outp))
40
+ self.isin(' API Key: ', str(outp))
41
+ self.isin(' Name: rootkey00', str(outp))
42
+ self.isin(f' Created: {s_time.repr(rootkey00.get("created"))}', str(outp))
43
+ self.isin(f' Updated: {s_time.repr(rootkey00.get("updated"))}', str(outp))
44
+ self.isin(f' Expires: {s_time.repr(rootkey00.get("expires"))}', str(outp))
45
+ self.eq(rootkey00.get('expires'), rootkey00.get('created') + 120000)
46
+
47
+ argv = (
48
+ '--svcurl', rooturl,
49
+ 'add',
50
+ '-u', 'blackout',
51
+ 'blckkey00',
52
+ )
53
+ outp = s_output.OutPutStr()
54
+ self.eq(0, await s_t_apikey.main(argv, outp=outp))
55
+
56
+ self.isin('Successfully added API key with name=blckkey00.', str(outp))
57
+ blckkey00 = await getApiKeyByName(core, 'blckkey00')
58
+
59
+ self.isin(f'Iden: {blckkey00.get("iden")}', str(outp))
60
+ self.isin(' API Key: ', str(outp))
61
+ self.isin(' Name: blckkey00', str(outp))
62
+ self.isin(f' Created: {s_time.repr(blckkey00.get("created"))}', str(outp))
63
+ self.isin(f' Updated: {s_time.repr(blckkey00.get("updated"))}', str(outp))
64
+ self.notin(' Expires: ', str(outp))
65
+
66
+ argv = (
67
+ '--svcurl', blckurl,
68
+ 'add',
69
+ 'blckkey01',
70
+ )
71
+ outp = s_output.OutPutStr()
72
+ self.eq(0, await s_t_apikey.main(argv, outp=outp))
73
+
74
+ self.isin('Successfully added API key with name=blckkey01.', str(outp))
75
+ blckkey01 = await getApiKeyByName(core, 'blckkey01')
76
+
77
+ self.isin(f'Iden: {blckkey01.get("iden")}', str(outp))
78
+ self.isin(' API Key: ', str(outp))
79
+ self.isin(' Name: blckkey01', str(outp))
80
+ self.isin(f' Created: {s_time.repr(blckkey01.get("created"))}', str(outp))
81
+ self.isin(f' Updated: {s_time.repr(blckkey01.get("updated"))}', str(outp))
82
+ self.notin(' Expires: ', str(outp))
83
+
84
+ # List API keys
85
+ argv = (
86
+ '--svcurl', rooturl,
87
+ 'list',
88
+ )
89
+ outp = s_output.OutPutStr()
90
+ self.eq(0, await s_t_apikey.main(argv, outp=outp))
91
+
92
+ self.isin(f'Iden: {rootkey00.get("iden")}', str(outp))
93
+ self.notin(' API Key: ', str(outp))
94
+ self.isin(' Name: rootkey00', str(outp))
95
+ self.isin(f' Created: {s_time.repr(rootkey00.get("created"))}', str(outp))
96
+ self.isin(f' Updated: {s_time.repr(rootkey00.get("updated"))}', str(outp))
97
+ self.isin(f' Expires: {s_time.repr(rootkey00.get("expires"))}', str(outp))
98
+ self.eq(rootkey00.get('expires'), rootkey00.get('created') + 120000)
99
+
100
+ argv = (
101
+ '--svcurl', rooturl,
102
+ 'list',
103
+ '-u', 'blackout',
104
+ )
105
+ outp = s_output.OutPutStr()
106
+ self.eq(0, await s_t_apikey.main(argv, outp=outp))
107
+
108
+ self.isin(f'Iden: {blckkey00.get("iden")}', str(outp))
109
+ self.notin(' API Key: ', str(outp))
110
+ self.isin(' Name: blckkey00', str(outp))
111
+ self.isin(f' Created: {s_time.repr(blckkey00.get("created"))}', str(outp))
112
+ self.isin(f' Updated: {s_time.repr(blckkey00.get("updated"))}', str(outp))
113
+ self.notin(' Expires: ', str(outp))
114
+
115
+ self.isin(f'Iden: {blckkey01.get("iden")}', str(outp))
116
+ self.notin(' API Key: ', str(outp))
117
+ self.isin(' Name: blckkey01', str(outp))
118
+ self.isin(f' Created: {s_time.repr(blckkey01.get("created"))}', str(outp))
119
+ self.isin(f' Updated: {s_time.repr(blckkey01.get("updated"))}', str(outp))
120
+ self.notin(' Expires: ', str(outp))
121
+
122
+ argv = (
123
+ '--svcurl', blckurl,
124
+ 'list',
125
+ )
126
+ outp = s_output.OutPutStr()
127
+ self.eq(0, await s_t_apikey.main(argv, outp=outp))
128
+
129
+ self.isin(f'Iden: {blckkey00.get("iden")}', str(outp))
130
+ self.notin(' API Key: ', str(outp))
131
+ self.isin(' Name: blckkey00', str(outp))
132
+ self.isin(f' Created: {s_time.repr(blckkey00.get("created"))}', str(outp))
133
+ self.isin(f' Updated: {s_time.repr(blckkey00.get("updated"))}', str(outp))
134
+ self.notin(' Expires: ', str(outp))
135
+
136
+ self.isin(f'Iden: {blckkey01.get("iden")}', str(outp))
137
+ self.notin(' API Key: ', str(outp))
138
+ self.isin(' Name: blckkey01', str(outp))
139
+ self.isin(f' Created: {s_time.repr(blckkey01.get("created"))}', str(outp))
140
+ self.isin(f' Updated: {s_time.repr(blckkey01.get("updated"))}', str(outp))
141
+ self.notin(' Expires: ', str(outp))
142
+
143
+ # Delete API keys
144
+ rootiden00 = rootkey00.get('iden')
145
+ argv = (
146
+ '--svcurl', rooturl,
147
+ 'del',
148
+ rootiden00,
149
+ )
150
+ outp = s_output.OutPutStr()
151
+ self.eq(0, await s_t_apikey.main(argv, outp=outp))
152
+ self.isin(f'Successfully deleted API key with iden={rootiden00}.', str(outp))
153
+
154
+ blckiden00 = blckkey00.get('iden')
155
+ argv = (
156
+ '--svcurl', rooturl,
157
+ 'del',
158
+ blckiden00,
159
+ )
160
+ outp = s_output.OutPutStr()
161
+ self.eq(0, await s_t_apikey.main(argv, outp=outp))
162
+ self.isin(f'Successfully deleted API key with iden={blckiden00}.', str(outp))
163
+
164
+ blckiden01 = blckkey01.get('iden')
165
+ argv = (
166
+ '--svcurl', blckurl,
167
+ 'del',
168
+ blckiden01,
169
+ )
170
+ outp = s_output.OutPutStr()
171
+ self.eq(0, await s_t_apikey.main(argv, outp=outp))
172
+ self.isin(f'Successfully deleted API key with iden={blckiden01}.', str(outp))
173
+
174
+ # List API keys again
175
+ argv = (
176
+ '--svcurl', rooturl,
177
+ 'list',
178
+ )
179
+ outp = s_output.OutPutStr()
180
+ self.eq(0, await s_t_apikey.main(argv, outp=outp))
181
+ self.isin('No API keys found.', str(outp))
182
+
183
+ argv = (
184
+ '--svcurl', rooturl,
185
+ 'list',
186
+ '-u', 'blackout',
187
+ )
188
+ outp = s_output.OutPutStr()
189
+ self.eq(0, await s_t_apikey.main(argv, outp=outp))
190
+ self.isin('No API keys found.', str(outp))
191
+
192
+ argv = (
193
+ '--svcurl', blckurl,
194
+ 'list',
195
+ )
196
+ outp = s_output.OutPutStr()
197
+ self.eq(0, await s_t_apikey.main(argv, outp=outp))
198
+ self.isin('No API keys found.', str(outp))
199
+
200
+ # Check errors
201
+ argv = (
202
+ '--svcurl', rooturl,
203
+ 'list',
204
+ '-u', 'newp',
205
+ )
206
+ outp = s_output.OutPutStr()
207
+ self.eq(1, await s_t_apikey.main(argv, outp=outp))
208
+ self.isin('ERROR: NoSuchUser: No user named newp.', str(outp))
209
+
210
+ argv = (
211
+ '--svcurl', blckurl,
212
+ 'list',
213
+ '-u', 'root',
214
+ )
215
+ outp = s_output.OutPutStr()
216
+ self.eq(1, await s_t_apikey.main(argv, outp=outp))
217
+ self.isin('ERROR: AuthDeny: getUserInfo denied for non-admin and non-self', str(outp))
218
+
219
+ newpiden = s_common.guid()
220
+ argv = (
221
+ '--svcurl', rooturl,
222
+ 'del',
223
+ newpiden,
224
+ )
225
+ outp = s_output.OutPutStr()
226
+ self.eq(1, await s_t_apikey.main(argv, outp=outp))
227
+ self.isin(f"ERROR: NoSuchIden: User API key with iden='{newpiden}' does not exist.", str(outp))
@@ -1,4 +1,9 @@
1
1
  import os
2
+ import sys
3
+ import signal
4
+ import asyncio
5
+ import multiprocessing
6
+
2
7
  import synapse.tests.utils as s_test
3
8
 
4
9
  from prompt_toolkit.document import Document
@@ -6,10 +11,49 @@ from prompt_toolkit.completion import Completion, CompleteEvent
6
11
 
7
12
  import synapse.exc as s_exc
8
13
  import synapse.common as s_common
14
+ import synapse.telepath as s_telepath
15
+
16
+ import synapse.lib.coro as s_coro
9
17
  import synapse.lib.output as s_output
10
18
  import synapse.lib.msgpack as s_msgpack
11
19
  import synapse.tools.storm as s_t_storm
12
20
 
21
+ def run_cli_till_print(url, evt1):
22
+ '''
23
+ Run the stormCLI until we get a print mesg then set the event.
24
+
25
+ This is a Process target.
26
+ '''
27
+ async def main():
28
+ outp = s_output.OutPutStr() # Capture output instead of sending it to stdout
29
+ async with await s_telepath.openurl(url) as proxy:
30
+ async with await s_t_storm.StormCli.anit(proxy, outp=outp) as scli:
31
+ cmdqueue = asyncio.Queue()
32
+ await cmdqueue.put('while (true) { $lib.print(go) $lib.time.sleep(1) }')
33
+ await cmdqueue.put('!quit')
34
+
35
+ async def fake_prompt():
36
+ return await cmdqueue.get()
37
+
38
+ scli.prompt = fake_prompt
39
+
40
+ d = {'evt1': False}
41
+ async def onmesg(event):
42
+ if d.get('evt1'):
43
+ return
44
+ mesg = event[1].get('mesg')
45
+ if mesg[0] != 'print':
46
+ return
47
+ evt1.set()
48
+ d['evt1'] = True
49
+
50
+ with scli.onWith('storm:mesg', onmesg):
51
+ await scli.addSignalHandlers()
52
+ await scli.runCmdLoop()
53
+
54
+ asyncio.run(main())
55
+ sys.exit(137)
56
+
13
57
  class StormCliTest(s_test.SynTest):
14
58
 
15
59
  async def test_tools_storm(self):
@@ -378,3 +422,54 @@ class StormCliTest(s_test.SynTest):
378
422
  ),
379
423
  vals
380
424
  )
425
+
426
+ async def test_storm_cmdloop_interrupt(self):
427
+ '''
428
+ Test interrupting a long-running query in the command loop
429
+ '''
430
+ async with self.getTestCore() as core:
431
+
432
+ async with core.getLocalProxy() as proxy:
433
+
434
+ outp = s_test.TstOutPut()
435
+ async with await s_t_storm.StormCli.anit(proxy, outp=outp) as scli:
436
+
437
+ cmdqueue = asyncio.Queue()
438
+ await cmdqueue.put('while (true) { $lib.time.sleep(1) }')
439
+ await cmdqueue.put('!quit')
440
+
441
+ async def fake_prompt():
442
+ return await cmdqueue.get()
443
+ scli.prompt = fake_prompt
444
+
445
+ cmdloop_task = asyncio.create_task(scli.runCmdLoop())
446
+ await asyncio.sleep(0.1)
447
+
448
+ if scli.cmdtask is not None:
449
+ scli.cmdtask.cancel()
450
+
451
+ await cmdloop_task
452
+
453
+ outp.expect('<ctrl-c>')
454
+ outp.expect('o/')
455
+ self.true(scli.isfini)
456
+
457
+ async def test_storm_cmdloop_sigint(self):
458
+ '''
459
+ Test interrupting a long-running query in the command loop with a process target and SIGINT.
460
+ '''
461
+
462
+ async with self.getTestCore() as core:
463
+ url = core.getLocalUrl()
464
+
465
+ ctx = multiprocessing.get_context('spawn')
466
+
467
+ evt1 = ctx.Event()
468
+
469
+ proc = ctx.Process(target=run_cli_till_print, args=(url, evt1,))
470
+ proc.start()
471
+
472
+ self.true(await s_coro.executor(evt1.wait, timeout=30))
473
+ os.kill(proc.pid, signal.SIGINT)
474
+ proc.join(timeout=30)
475
+ self.eq(proc.exitcode, 137)
@@ -24,7 +24,7 @@ class TestUtilsGetrefs(s_utils.SynTest):
24
24
  cm = myvcr.use_cassette(fp)
25
25
  return cm
26
26
 
27
- async def test_basics(self):
27
+ def test_basics(self):
28
28
 
29
29
  args = s_getrefs.parse_args([
30
30
  s_data.path('attack-flow', 'attack-flow-schema-2.0.0.json')