synapse 2.192.0__py311-none-any.whl → 2.194.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 (77) hide show
  1. synapse/common.py +15 -0
  2. synapse/cortex.py +19 -25
  3. synapse/datamodel.py +6 -3
  4. synapse/exc.py +6 -1
  5. synapse/lib/agenda.py +17 -6
  6. synapse/lib/ast.py +242 -97
  7. synapse/lib/auth.py +1 -0
  8. synapse/lib/cell.py +31 -85
  9. synapse/lib/cli.py +20 -11
  10. synapse/lib/parser.py +5 -1
  11. synapse/lib/snap.py +44 -15
  12. synapse/lib/storm.lark +16 -1
  13. synapse/lib/storm.py +40 -21
  14. synapse/lib/storm_format.py +1 -0
  15. synapse/lib/stormctrl.py +88 -6
  16. synapse/lib/stormlib/cache.py +6 -2
  17. synapse/lib/stormlib/json.py +5 -2
  18. synapse/lib/stormlib/scrape.py +1 -1
  19. synapse/lib/stormlib/stix.py +8 -8
  20. synapse/lib/stormtypes.py +32 -5
  21. synapse/lib/version.py +2 -2
  22. synapse/lib/view.py +20 -3
  23. synapse/models/geopol.py +1 -0
  24. synapse/models/geospace.py +1 -0
  25. synapse/models/inet.py +20 -1
  26. synapse/models/infotech.py +24 -6
  27. synapse/models/orgs.py +7 -2
  28. synapse/models/person.py +15 -4
  29. synapse/models/risk.py +19 -2
  30. synapse/models/telco.py +10 -3
  31. synapse/tests/test_axon.py +6 -6
  32. synapse/tests/test_cortex.py +133 -14
  33. synapse/tests/test_exc.py +4 -0
  34. synapse/tests/test_lib_agenda.py +282 -2
  35. synapse/tests/test_lib_aha.py +13 -6
  36. synapse/tests/test_lib_ast.py +301 -10
  37. synapse/tests/test_lib_auth.py +6 -7
  38. synapse/tests/test_lib_cell.py +71 -1
  39. synapse/tests/test_lib_grammar.py +14 -0
  40. synapse/tests/test_lib_layer.py +1 -1
  41. synapse/tests/test_lib_lmdbslab.py +3 -3
  42. synapse/tests/test_lib_storm.py +273 -55
  43. synapse/tests/test_lib_stormctrl.py +65 -0
  44. synapse/tests/test_lib_stormhttp.py +5 -5
  45. synapse/tests/test_lib_stormlib_auth.py +5 -5
  46. synapse/tests/test_lib_stormlib_cache.py +38 -6
  47. synapse/tests/test_lib_stormlib_json.py +20 -0
  48. synapse/tests/test_lib_stormlib_modelext.py +3 -3
  49. synapse/tests/test_lib_stormlib_scrape.py +6 -6
  50. synapse/tests/test_lib_stormlib_spooled.py +1 -1
  51. synapse/tests/test_lib_stormlib_xml.py +5 -5
  52. synapse/tests/test_lib_stormtypes.py +54 -57
  53. synapse/tests/test_lib_view.py +1 -1
  54. synapse/tests/test_model_base.py +1 -2
  55. synapse/tests/test_model_geopol.py +4 -0
  56. synapse/tests/test_model_geospace.py +6 -0
  57. synapse/tests/test_model_inet.py +43 -5
  58. synapse/tests/test_model_infotech.py +10 -1
  59. synapse/tests/test_model_orgs.py +17 -2
  60. synapse/tests/test_model_person.py +23 -1
  61. synapse/tests/test_model_risk.py +13 -0
  62. synapse/tests/test_tools_healthcheck.py +4 -4
  63. synapse/tests/test_tools_storm.py +95 -0
  64. synapse/tests/test_utils.py +17 -18
  65. synapse/tests/test_utils_getrefs.py +1 -1
  66. synapse/tests/utils.py +0 -35
  67. synapse/tools/changelog.py +6 -4
  68. synapse/tools/storm.py +1 -1
  69. synapse/utils/getrefs.py +14 -3
  70. synapse/vendor/cpython/lib/http/__init__.py +0 -0
  71. synapse/vendor/cpython/lib/http/cookies.py +59 -0
  72. synapse/vendor/cpython/lib/test/test_http_cookies.py +49 -0
  73. {synapse-2.192.0.dist-info → synapse-2.194.0.dist-info}/METADATA +6 -6
  74. {synapse-2.192.0.dist-info → synapse-2.194.0.dist-info}/RECORD +77 -73
  75. {synapse-2.192.0.dist-info → synapse-2.194.0.dist-info}/WHEEL +1 -1
  76. {synapse-2.192.0.dist-info → synapse-2.194.0.dist-info}/LICENSE +0 -0
  77. {synapse-2.192.0.dist-info → synapse-2.194.0.dist-info}/top_level.txt +0 -0
@@ -18,6 +18,7 @@ class GeoPolModelTest(s_t_utils.SynTest):
18
18
  ]
19
19
  ''')
20
20
  self.len(1, nodes)
21
+ node = nodes[0]
21
22
  self.eq('visiland', nodes[0].get('name'))
22
23
  self.eq(('visitopia',), nodes[0].get('names'))
23
24
  self.eq(1640995200000, nodes[0].get('founded'))
@@ -29,6 +30,9 @@ class GeoPolModelTest(s_t_utils.SynTest):
29
30
  self.len(2, await core.nodes('pol:country -> geo:name'))
30
31
  self.len(3, await core.nodes('pol:country -> econ:currency'))
31
32
 
33
+ self.len(1, nodes := await core.nodes('[ pol:country=({"name": "visitopia"}) ]'))
34
+ self.eq(node.ndef, nodes[0].ndef)
35
+
32
36
  nodes = await core.nodes('''
33
37
  [ pol:vitals=*
34
38
  :country={pol:country:name=visiland}
@@ -281,6 +281,12 @@ class GeoTest(s_t_utils.SynTest):
281
281
  nodes = await core.nodes('[ geo:place=(hehe, haha) :names=("Foo Bar ", baz) ] -> geo:name')
282
282
  self.eq(('baz', 'foo bar'), [n.ndef[1] for n in nodes])
283
283
 
284
+ nodes = await core.nodes('geo:place=(hehe, haha)')
285
+ node = nodes[0]
286
+
287
+ self.len(1, nodes := await core.nodes('[ geo:place=({"name": "baz"}) ]'))
288
+ self.eq(node.ndef, nodes[0].ndef)
289
+
284
290
  async def test_eq(self):
285
291
 
286
292
  async with self.getTestCore() as core:
@@ -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
 
@@ -3207,6 +3242,7 @@ class InetModelTest(s_t_utils.SynTest):
3207
3242
  :type=chat.group
3208
3243
  :group=$devsiden
3209
3244
  :public=$lib.false
3245
+ :repost=*
3210
3246
  )
3211
3247
 
3212
3248
  (inet:service:message=(blackout, visi, 1715856900000, vertex, slack)
@@ -3256,6 +3292,7 @@ class InetModelTest(s_t_utils.SynTest):
3256
3292
  self.nn(node.get('place'))
3257
3293
  self.eq(node.get('place:name'), 'nyc')
3258
3294
 
3295
+ self.nn(nodes[0].get('repost'))
3259
3296
  self.eq(nodes[0].get('group'), devsgrp.ndef[1])
3260
3297
  self.false(nodes[0].get('public'))
3261
3298
  self.eq(nodes[0].get('type'), 'chat.group.')
@@ -3285,6 +3322,7 @@ class InetModelTest(s_t_utils.SynTest):
3285
3322
  nodes = await core.nodes('inet:service:message:type:taxonomy=chat.channel -> inet:service:message')
3286
3323
  self.len(1, nodes)
3287
3324
  self.eq(nodes[0].ndef, ('inet:service:message', 'c0d64c559e2f42d57b37b558458c068b'))
3325
+ self.len(1, await core.nodes('inet:service:message:repost :repost -> inet:service:message'))
3288
3326
 
3289
3327
  q = '''
3290
3328
  [ inet:service:resource=(web, api, vertex, slack)
@@ -761,7 +761,7 @@ class InfotechModelTest(s_t_utils.SynTest):
761
761
  'techniques': teqs,
762
762
  'url': url0,
763
763
  }
764
- q = '''[(it:prod:soft=$valu :name=$p.name :type=$p.type :names=$p.names
764
+ q = '''[(it:prod:soft=$valu :id="Foo " :name=$p.name :type=$p.type :names=$p.names
765
765
  :desc=$p.desc :desc:short=$p."desc:short" :author:org=$p."author:org" :author:email=$p."author:email"
766
766
  :author:acct=$p."author:acct" :author:person=$p."author:person"
767
767
  :techniques=$p.techniques :url=$p.url )]'''
@@ -769,6 +769,7 @@ class InfotechModelTest(s_t_utils.SynTest):
769
769
  self.len(1, nodes)
770
770
  node = nodes[0]
771
771
  self.eq(node.ndef, ('it:prod:soft', prod0))
772
+ self.eq(node.get('id'), 'Foo')
772
773
  self.eq(node.get('name'), 'balloon maker')
773
774
  self.eq(node.get('desc'), "Pennywise's patented balloon blower upper")
774
775
  self.eq(node.get('desc:short'), 'balloon blower')
@@ -786,6 +787,10 @@ class InfotechModelTest(s_t_utils.SynTest):
786
787
  self.eq(node.get('url'), url0)
787
788
  self.len(1, await core.nodes('it:prod:soft:name="balloon maker" -> it:prod:soft:taxonomy'))
788
789
  self.len(2, await core.nodes('it:prod:softname="balloon maker" -> it:prod:soft -> it:prod:softname'))
790
+
791
+ self.len(1, nodes := await core.nodes('[ it:prod:soft=({"name": "clowns inc"}) ]'))
792
+ self.eq(node.ndef, nodes[0].ndef)
793
+
789
794
  # it:prod:softver - this does test a bunch of property related callbacks
790
795
  ver0 = s_common.guid()
791
796
  url1 = 'https://vertex.link/products/balloonmaker/release_101-beta.exe'
@@ -819,6 +824,10 @@ class InfotechModelTest(s_t_utils.SynTest):
819
824
  self.eq(node.get('url'), url1)
820
825
  self.eq(node.get('name'), 'balloonmaker')
821
826
  self.eq(node.get('desc'), 'makes balloons')
827
+
828
+ self.len(1, nodes := await core.nodes('[ it:prod:softver=({"name": "clowns inc"}) ]'))
829
+ self.eq(node.ndef, nodes[0].ndef)
830
+
822
831
  # callback node creation checks
823
832
  self.len(1, await core.nodes('it:dev:str=V1.0.1-beta+exp.sha.5114f85'))
824
833
  self.len(1, await core.nodes('it:dev:str=amd64'))
@@ -60,6 +60,9 @@ class OuModelTest(s_t_utils.SynTest):
60
60
  self.eq(node.get('desc'), 'MyDesc')
61
61
  self.eq(node.get('prev'), goal)
62
62
 
63
+ self.len(1, nodes := await core.nodes('[ ou:goal=({"name": "foo goal"}) ]'))
64
+ self.eq(node.ndef, nodes[0].ndef)
65
+
63
66
  nodes = await core.nodes('[(ou:hasgoal=$valu :stated=$lib.true :window="2019,2020")]',
64
67
  opts={'vars': {'valu': (org0, goal)}})
65
68
  self.len(1, nodes)
@@ -69,12 +72,13 @@ class OuModelTest(s_t_utils.SynTest):
69
72
  self.eq(node.get('stated'), True)
70
73
  self.eq(node.get('window'), (1546300800000, 1577836800000))
71
74
 
75
+ altgoal = s_common.guid()
72
76
  timeline = s_common.guid()
73
77
 
74
78
  props = {
75
79
  'org': org0,
76
80
  'goal': goal,
77
- 'goals': (goal,),
81
+ 'goals': (goal, altgoal),
78
82
  'actors': (acto,),
79
83
  'camptype': 'get.pizza',
80
84
  'name': 'MyName',
@@ -103,7 +107,7 @@ class OuModelTest(s_t_utils.SynTest):
103
107
  self.eq(node.get('tag'), 'cno.camp.31337')
104
108
  self.eq(node.get('org'), org0)
105
109
  self.eq(node.get('goal'), goal)
106
- self.eq(node.get('goals'), (goal,))
110
+ self.eq(node.get('goals'), sorted((goal, altgoal)))
107
111
  self.eq(node.get('actors'), (acto,))
108
112
  self.eq(node.get('name'), 'myname')
109
113
  self.eq(node.get('names'), ('bar', 'foo'))
@@ -120,6 +124,10 @@ class OuModelTest(s_t_utils.SynTest):
120
124
  self.eq(node.get('mitre:attack:campaign'), 'C0011')
121
125
  self.eq(node.get('slogan'), 'for the people')
122
126
 
127
+ opts = {'vars': {'altgoal': altgoal}}
128
+ self.len(1, nodes := await core.nodes('[ ou:campaign=({"name": "foo", "goal": $altgoal}) ]', opts=opts))
129
+ self.eq(node.ndef, nodes[0].ndef)
130
+
123
131
  self.len(1, await core.nodes(f'ou:campaign={camp} :slogan -> lang:phrase'))
124
132
  nodes = await core.nodes(f'ou:campaign={camp} -> it:mitre:attack:campaign')
125
133
  self.len(1, nodes)
@@ -405,6 +413,9 @@ class OuModelTest(s_t_utils.SynTest):
405
413
  self.eq(node.get('place'), place0)
406
414
  self.eq(node.get('url'), 'http://arrowcon.org/2018')
407
415
 
416
+ self.len(1, nodes := await core.nodes('[ ou:conference=({"name": "arrcon18"}) ]'))
417
+ self.eq(node.ndef, nodes[0].ndef)
418
+
408
419
  props = {
409
420
  'arrived': '201803010800',
410
421
  'departed': '201803021500',
@@ -870,6 +881,7 @@ class OuModelTest(s_t_utils.SynTest):
870
881
  ] '''
871
882
  nodes = await core.nodes(q)
872
883
  self.len(1, nodes)
884
+ node = nodes[0]
873
885
  self.nn(nodes[0].get('reporter'))
874
886
  self.eq('foo bar', nodes[0].get('name'))
875
887
  self.eq('vertex', nodes[0].get('reporter:name'))
@@ -884,6 +896,9 @@ class OuModelTest(s_t_utils.SynTest):
884
896
  self.len(3, nodes)
885
897
  self.len(3, await core.nodes('ou:industryname=baz -> ou:industry -> ou:industryname'))
886
898
 
899
+ self.len(1, nodes := await core.nodes('[ ou:industry=({"name": "faz"}) ]'))
900
+ self.eq(node.ndef, nodes[0].ndef)
901
+
887
902
  async def test_ou_opening(self):
888
903
 
889
904
  async with self.getTestCore() as core:
@@ -60,6 +60,9 @@ class PsModelTest(s_t_utils.SynTest):
60
60
  self.eq(node.get('names'), ['billy bob'])
61
61
  self.eq(node.get('photo'), file0)
62
62
 
63
+ self.len(1, nodes := await core.nodes('[ ps:person=({"name": "billy bob"}) ]'))
64
+ self.eq(node.ndef, nodes[0].ndef)
65
+
63
66
  props = {
64
67
  'dob': '2000',
65
68
  'img': file0,
@@ -147,9 +150,11 @@ class PsModelTest(s_t_utils.SynTest):
147
150
  'id:numbers': (('*', 'asdf'), ('*', 'qwer')),
148
151
  'users': ('visi', 'invisigoth'),
149
152
  'crypto:address': 'btc/1BvBMSEYstWetqTFn5Au4m4GFg7xJaNVN2',
153
+ 'langs': (lang00 := s_common.guid(),),
150
154
  }
151
155
  opts = {'vars': {'valu': con0, 'p': props}}
152
156
  q = '''[(ps:contact=$valu
157
+ :bio="I am ironman."
153
158
  :org=$p.org :asof=$p.asof :person=$p.person
154
159
  :place=$p.place :place:name=$p."place:name" :name=$p.name
155
160
  :title=$p.title :orgname=$p.orgname :user=$p.user
@@ -165,7 +170,7 @@ class PsModelTest(s_t_utils.SynTest):
165
170
  :birth:place:name=$p."birth:place:name"
166
171
  :death:place=$p."death:place" :death:place:loc=$p."death:place:loc"
167
172
  :death:place:name=$p."death:place:name"
168
- :service:accounts=(*, *)
173
+ :service:accounts=(*, *) :langs=$p.langs
169
174
  )]'''
170
175
  nodes = await core.nodes(q, opts=opts)
171
176
  self.len(1, nodes)
@@ -178,6 +183,7 @@ class PsModelTest(s_t_utils.SynTest):
178
183
  self.eq(node.get('place'), place)
179
184
  self.eq(node.get('place:name'), 'the shire')
180
185
  self.eq(node.get('name'), 'tony stark')
186
+ self.eq(node.get('bio'), 'I am ironman.')
181
187
  self.eq(node.get('title'), 'ceo')
182
188
  self.eq(node.get('titles'), ('haha', 'hehe'))
183
189
  self.eq(node.get('orgname'), 'stark industries, inc')
@@ -211,6 +217,22 @@ class PsModelTest(s_t_utils.SynTest):
211
217
  self.len(1, await core.nodes('ps:contact :death:place -> geo:place'))
212
218
  self.len(2, await core.nodes('ps:contact :service:accounts -> inet:service:account'))
213
219
 
220
+ opts = {
221
+ 'vars': {
222
+ 'ctor': {
223
+ 'email': 'v@vtx.lk',
224
+ 'id:number': node.get('id:numbers')[0],
225
+ 'lang': lang00,
226
+ 'name': 'vi',
227
+ 'orgname': 'vertex',
228
+ 'title': 'haha',
229
+ 'user': 'invisigoth',
230
+ },
231
+ },
232
+ }
233
+ self.len(1, nodes := await core.nodes('[ ps:contact=$ctor ]', opts=opts))
234
+ self.eq(node.ndef, nodes[0].ndef)
235
+
214
236
  nodes = await core.nodes('''[
215
237
  ps:achievement=*
216
238
  :award=*
@@ -253,6 +253,9 @@ class RiskModelTest(s_t_utils.SynTest):
253
253
  self.len(1, await core.nodes('risk:attack :target -> ps:contact'))
254
254
  self.len(1, await core.nodes('risk:attack :attacker -> ps:contact'))
255
255
 
256
+ self.len(1, nodes := await core.nodes('[ risk:vuln=({"name": "hehe"}) ]'))
257
+ self.eq(node.ndef, nodes[0].ndef)
258
+
256
259
  node = await addNode(f'''[
257
260
  risk:hasvuln={hasv}
258
261
  :vuln={vuln}
@@ -399,6 +402,7 @@ class RiskModelTest(s_t_utils.SynTest):
399
402
  ]
400
403
  ''')
401
404
  self.len(1, nodes)
405
+ node = nodes[0]
402
406
  self.eq('vtx-apt1', nodes[0].get('name'))
403
407
  self.eq('VTX-APT1', nodes[0].get('desc'))
404
408
  self.eq(40, nodes[0].get('activity'))
@@ -424,12 +428,16 @@ class RiskModelTest(s_t_utils.SynTest):
424
428
  self.len(1, await core.nodes('risk:threat:merged:isnow -> risk:threat'))
425
429
  self.len(1, await core.nodes('risk:threat -> it:mitre:attack:group'))
426
430
 
431
+ self.len(1, nodes := await core.nodes('[ risk:threat=({"org:name": "comment crew"}) ]'))
432
+ self.eq(node.ndef, nodes[0].ndef)
433
+
427
434
  nodes = await core.nodes('''[ risk:leak=*
428
435
  :name="WikiLeaks ACME Leak"
429
436
  :desc="WikiLeaks leaked ACME stuff."
430
437
  :disclosed=20231102
431
438
  :owner={ gen.ou.org.hq acme }
432
439
  :leaker={ gen.ou.org.hq wikileaks }
440
+ :recipient={ gen.ou.org.hq everyone }
433
441
  :type=public
434
442
  :goal={[ ou:goal=* :name=publicity ]}
435
443
  :compromise={[ risk:compromise=* :target={ gen.ou.org.hq acme } ]}
@@ -458,6 +466,7 @@ class RiskModelTest(s_t_utils.SynTest):
458
466
  self.len(1, await core.nodes('risk:leak -> risk:leak:type:taxonomy'))
459
467
  self.len(1, await core.nodes('risk:leak :owner -> ps:contact +:orgname=acme'))
460
468
  self.len(1, await core.nodes('risk:leak :leaker -> ps:contact +:orgname=wikileaks'))
469
+ self.len(1, await core.nodes('risk:leak :recipient -> ps:contact +:orgname=everyone'))
461
470
  self.len(1, await core.nodes('risk:leak -> ou:goal +:name=publicity'))
462
471
  self.len(1, await core.nodes('risk:leak -> risk:compromise :target -> ps:contact +:orgname=acme'))
463
472
  self.len(1, await core.nodes('risk:leak :reporter -> ou:org +:name=vertex'))
@@ -616,6 +625,7 @@ class RiskModelTest(s_t_utils.SynTest):
616
625
  ]
617
626
  ''')
618
627
  self.len(1, nodes)
628
+ node = nodes[0]
619
629
  self.nn(nodes[0].get('soft'))
620
630
 
621
631
  self.nn(nodes[0].get('reporter'))
@@ -638,6 +648,9 @@ class RiskModelTest(s_t_utils.SynTest):
638
648
  self.len(1, await core.nodes('risk:tool:software -> syn:tag'))
639
649
  self.len(1, await core.nodes('risk:tool:software -> it:mitre:attack:software'))
640
650
 
651
+ self.len(1, nodes := await core.nodes('[ risk:tool:software=({"soft:name": "beacon"}) ]'))
652
+ self.eq(node.ndef, nodes[0].ndef)
653
+
641
654
  nodes = await core.nodes('''
642
655
  [ risk:vuln:soft:range=*
643
656
  :vuln={[ risk:vuln=* :name=woot ]}
@@ -45,7 +45,7 @@ class HealthcheckTest(s_t_utils.SynTest):
45
45
  await asyncio.sleep(0.6)
46
46
  core.addHealthFunc(sleep)
47
47
  outp.clear()
48
- retn = await s_t_healthcheck.main(['-c', curl, '-t', '0.2'], outp)
48
+ retn = await s_t_healthcheck.main(['-c', curl, '-t', '0.4'], outp)
49
49
  self.eq(retn, 1)
50
50
  resp = json.loads(str(outp))
51
51
  self.eq(resp.get('components')[0].get('name'), 'error')
@@ -58,7 +58,7 @@ class HealthcheckTest(s_t_utils.SynTest):
58
58
  _, port = await core.dmon.listen('tcp://127.0.0.1:0')
59
59
  root = await core.auth.getUserByName('root')
60
60
  await root.setPasswd('secret')
61
- retn = await s_t_healthcheck.main(['-c', f'tcp://root:newp@127.0.0.1:{port}/cortex', '-t', '0.2'], outp)
61
+ retn = await s_t_healthcheck.main(['-c', f'tcp://root:newp@127.0.0.1:{port}/cortex', '-t', '0.4'], outp)
62
62
  self.eq(retn, 1)
63
63
  resp = json.loads(str(outp))
64
64
  self.eq(resp.get('components')[0].get('name'), 'error')
@@ -70,7 +70,7 @@ class HealthcheckTest(s_t_utils.SynTest):
70
70
 
71
71
  logger.info('Checking without perms')
72
72
  outp.clear()
73
- retn = await s_t_healthcheck.main(['-c', f'tcp://visi:secret@127.0.0.1:{port}/cortex', '-t', '0.2'], outp)
73
+ retn = await s_t_healthcheck.main(['-c', f'tcp://visi:secret@127.0.0.1:{port}/cortex', '-t', '0.4'], outp)
74
74
  self.eq(retn, 1)
75
75
  resp = json.loads(str(outp))
76
76
  self.eq(resp.get('components')[0].get('name'), 'error')
@@ -83,7 +83,7 @@ class HealthcheckTest(s_t_utils.SynTest):
83
83
  await core.fini()
84
84
  await asyncio.sleep(0)
85
85
  outp.clear()
86
- retn = await s_t_healthcheck.main(['-c', curl, '-t', '0.2'], outp)
86
+ retn = await s_t_healthcheck.main(['-c', curl, '-t', '0.4'], outp)
87
87
  self.eq(retn, 1)
88
88
  resp = json.loads(str(outp))
89
89
  self.eq(resp.get('components')[0].get('name'), 'error')
@@ -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)
@@ -244,24 +244,6 @@ class TestUtils(s_t_utils.SynTest):
244
244
  with self.raises(AssertionError):
245
245
  self.stormHasNoWarnErr(msgs)
246
246
 
247
- async def test_stable_uids(self):
248
- with self.withStableUids():
249
- guid = s_common.guid()
250
- self.eq('000000', guid[:6])
251
- guid2 = s_common.guid()
252
- self.ne(guid, guid2)
253
-
254
- guid = s_common.guid(42)
255
- self.ne('000000', guid[:6])
256
-
257
- buid = s_common.buid()
258
- self.eq(b'\00\00\00\00\00\00', buid[:6])
259
- buid2 = s_common.buid()
260
- self.ne(buid, buid2)
261
-
262
- buid = s_common.buid(42)
263
- self.ne(b'\00\00\00\00\00\00', buid[:6])
264
-
265
247
  def test_utils_certdir(self):
266
248
  oldcertdirn = s_certdir.getCertDirn()
267
249
  oldcertdir = s_certdir.getCertDir()
@@ -297,3 +279,20 @@ class TestUtils(s_t_utils.SynTest):
297
279
  # Patch is removed and singleton behavior is restored
298
280
  self.true(oldcertdir is s_certdir.getCertDir())
299
281
  self.eq(oldcertdirn, s_certdir.getCertDirn())
282
+
283
+ async def test_checknode(self):
284
+ async with self.getTestCore() as core:
285
+ nodes = await core.nodes('[test:comp=(1, test)]')
286
+ self.len(1, nodes)
287
+ self.checkNode(nodes[0], (('test:comp', (1, 'test')), {'hehe': 1, 'haha': 'test'}))
288
+ with self.raises(AssertionError):
289
+ self.checkNode(nodes[0], (('test:comp', (1, 'newp')), {'hehe': 1, 'haha': 'test'}))
290
+ with self.raises(AssertionError):
291
+ self.checkNode(nodes[0], (('test:comp', (1, 'test')), {'hehe': 1, 'haha': 'newp'}))
292
+ with self.getAsyncLoggerStream('synapse.tests.utils', 'untested properties') as stream:
293
+ self.checkNode(nodes[0], (('test:comp', (1, 'test')), {'hehe': 1}))
294
+ self.true(await stream.wait(timeout=12))
295
+
296
+ await self.checkNodes(core, [('test:comp', (1, 'test')),])
297
+ with self.raises(AssertionError):
298
+ await self.checkNodes(core, [('test:comp', (1, 'newp')),])
@@ -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')
synapse/tests/utils.py CHANGED
@@ -1005,8 +1005,6 @@ class SynTest(unittest.TestCase):
1005
1005
  '''
1006
1006
  def __init__(self, *args, **kwargs):
1007
1007
  unittest.TestCase.__init__(self, *args, **kwargs)
1008
- self._NextBuid = 0
1009
- self._NextGuid = 0
1010
1008
 
1011
1009
  for s in dir(self):
1012
1010
  attr = getattr(self, s, None)
@@ -2377,39 +2375,6 @@ class SynTest(unittest.TestCase):
2377
2375
 
2378
2376
  yield hive
2379
2377
 
2380
- def stablebuid(self, valu=None):
2381
- '''
2382
- A stable buid generation for testing purposes
2383
- '''
2384
- if valu is None:
2385
- retn = self._NextBuid.to_bytes(32, 'big')
2386
- self._NextBuid += 1
2387
- return retn
2388
-
2389
- byts = s_msgpack.en(valu)
2390
- return hashlib.sha256(byts).digest()
2391
-
2392
- def stableguid(self, valu=None):
2393
- '''
2394
- A stable guid generation for testing purposes
2395
- '''
2396
- if valu is None:
2397
- retn = s_common.ehex(self._NextGuid.to_bytes(16, 'big'))
2398
- self._NextGuid += 1
2399
- return retn
2400
-
2401
- byts = s_msgpack.en(valu)
2402
- return hashlib.md5(byts, usedforsecurity=False).hexdigest()
2403
-
2404
- @contextlib.contextmanager
2405
- def withStableUids(self):
2406
- '''
2407
- A context manager that generates guids and buids in sequence so that successive test runs use the same
2408
- data
2409
- '''
2410
- with mock.patch('synapse.common.guid', self.stableguid), mock.patch('synapse.common.buid', self.stablebuid):
2411
- yield
2412
-
2413
2378
  async def runCoreNodes(self, core, query, opts=None):
2414
2379
  '''
2415
2380
  Run a storm query through a Cortex as a SchedCoro and return the results.