synapse 2.197.0__py311-none-any.whl → 2.198.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 (44) hide show
  1. synapse/axon.py +3 -0
  2. synapse/common.py +3 -0
  3. synapse/cortex.py +1 -3
  4. synapse/lib/aha.py +3 -0
  5. synapse/lib/ast.py +277 -165
  6. synapse/lib/auth.py +39 -11
  7. synapse/lib/cell.py +22 -4
  8. synapse/lib/hive.py +2 -1
  9. synapse/lib/hiveauth.py +10 -1
  10. synapse/lib/jsonstor.py +6 -5
  11. synapse/lib/layer.py +6 -5
  12. synapse/lib/node.py +10 -4
  13. synapse/lib/parser.py +46 -21
  14. synapse/lib/schemas.py +13 -0
  15. synapse/lib/snap.py +68 -26
  16. synapse/lib/storm.lark +13 -11
  17. synapse/lib/storm.py +1 -1
  18. synapse/lib/storm_format.py +3 -2
  19. synapse/lib/stormtypes.py +13 -4
  20. synapse/lib/version.py +2 -2
  21. synapse/models/infotech.py +18 -0
  22. synapse/models/risk.py +9 -0
  23. synapse/models/syn.py +18 -2
  24. synapse/tests/files/stormpkg/badendpoints.yaml +7 -0
  25. synapse/tests/files/stormpkg/testpkg.yaml +8 -0
  26. synapse/tests/test_cortex.py +108 -0
  27. synapse/tests/test_lib_aha.py +12 -2
  28. synapse/tests/test_lib_ast.py +57 -0
  29. synapse/tests/test_lib_auth.py +143 -2
  30. synapse/tests/test_lib_grammar.py +54 -2
  31. synapse/tests/test_lib_lmdbslab.py +24 -0
  32. synapse/tests/test_lib_storm.py +20 -0
  33. synapse/tests/test_lib_stormlib_macro.py +3 -3
  34. synapse/tests/test_lib_stormtypes.py +14 -2
  35. synapse/tests/test_model_infotech.py +13 -0
  36. synapse/tests/test_model_risk.py +6 -0
  37. synapse/tests/test_model_syn.py +58 -0
  38. synapse/tests/test_tools_genpkg.py +10 -0
  39. synapse/tools/hive/load.py +1 -0
  40. {synapse-2.197.0.dist-info → synapse-2.198.0.dist-info}/METADATA +1 -1
  41. {synapse-2.197.0.dist-info → synapse-2.198.0.dist-info}/RECORD +44 -43
  42. {synapse-2.197.0.dist-info → synapse-2.198.0.dist-info}/LICENSE +0 -0
  43. {synapse-2.197.0.dist-info → synapse-2.198.0.dist-info}/WHEEL +0 -0
  44. {synapse-2.197.0.dist-info → synapse-2.198.0.dist-info}/top_level.txt +0 -0
synapse/lib/storm.lark CHANGED
@@ -40,7 +40,7 @@ _editblock: "[" _editoper* "]"
40
40
  // A single edit operation
41
41
  _editoper: editnodeadd
42
42
  | editpropset | editunivset | edittagpropset | edittagadd | editcondpropset
43
- | editpropdel | editunivdel | edittagpropdel | edittagdel
43
+ | editpropsetmulti | editpropdel | editunivdel | edittagpropdel | edittagdel
44
44
  | editparens | edgeaddn1 | edgedeln1 | edgeaddn2 | edgedeln2
45
45
 
46
46
  // Parenthesis in an edit block don't have incoming nodes
@@ -48,18 +48,20 @@ editparens: "(" editnodeadd _editoper* ")"
48
48
  edittagadd: "+" [SETTAGOPER] tagname [(EQSPACE | EQNOSPACE) _valu]
49
49
  editunivdel: EXPRMINUS univprop
50
50
  edittagdel: EXPRMINUS tagname
51
- editpropset: relprop (EQSPACE | EQNOSPACE | MODSET | TRYSET | TRYSETPLUS | TRYSETMINUS) _valu
51
+ editpropset: relprop (EQSPACE | EQNOSPACE | MODSET | TRYSET | TRYMODSET) _valu
52
52
  editcondpropset: relprop condsetoper _valu
53
+ editpropsetmulti: relprop (MODSETMULTI | TRYMODSETMULTI) _valu
53
54
  editpropdel: EXPRMINUS relprop
54
- editunivset: univprop (EQSPACE | EQNOSPACE | MODSET | TRYSET | TRYSETPLUS | TRYSETMINUS) _valu
55
- editnodeadd: formname (EQSPACE | EQNOSPACE | MODSET | TRYSET | TRYSETPLUS | TRYSETMINUS) _valu
56
- edittagpropset: "+" tagprop (EQSPACE | EQNOSPACE | MODSET | TRYSET | TRYSETPLUS | TRYSETMINUS) _valu
55
+ editunivset: univprop (EQSPACE | EQNOSPACE | MODSET | TRYSET | TRYMODSET) _valu
56
+ editnodeadd: formname (EQSPACE | EQNOSPACE | MODSET | TRYSET | TRYMODSET) _valu
57
+ edittagpropset: "+" tagprop (EQSPACE | EQNOSPACE | MODSET | TRYSET | TRYMODSET) _valu
57
58
  edittagpropdel: EXPRMINUS tagprop
58
59
 
59
60
  EQSPACE: /((?<=\s)=|=(?=\s))/
60
61
  MODSET.4: "+=" | "-="
61
- TRYSETPLUS.1: "?+="
62
- TRYSETMINUS.1: "?-="
62
+ TRYMODSET.1: "?+=" | "?-="
63
+ MODSETMULTI.4: "++=" | "--="
64
+ TRYMODSETMULTI.1: "?++=" | "?--="
63
65
  TRYSET.1: "?="
64
66
  SETTAGOPER: "?"
65
67
 
@@ -226,10 +228,10 @@ _EDGEN2JOININIT: "<+("
226
228
  // comparisons to an expression like '<(2)'
227
229
  _EDGEN2INIT: /\<\((?=(?>(?<EDGEN2INITRECUR>\(((?>[^()"'`]+|(?&EDGEN2INITRECUR)|`(?:[^`\\]|\\.)*`|"(?:[^"\\]|\\.)*"|'''.*?'''|'[^']*'(?!'))*)\))|'''.*?'''|`(?:[^`\\]|\\.)*`|"(?:[^"\\]|\\.)*"|'[^']*'(?!')|[^)])*\)[\-\+])/
228
230
 
229
- edgeaddn1: _EDGEADDN1INIT _valu _EDGEN1FINI baresubquery
230
- edgedeln1: _EDGEN1INIT _valu _EDGEN1FINI baresubquery
231
- edgeaddn2: _EDGEN2INIT _valu _EDGEADDN2FINI baresubquery
232
- edgedeln2: _EDGEN2INIT _valu _EDGEN2FINI baresubquery
231
+ edgeaddn1: _EDGEADDN1INIT _valu _EDGEN1FINI (baresubquery | _varvalu)
232
+ edgedeln1: _EDGEN1INIT _valu _EDGEN1FINI (baresubquery | _varvalu)
233
+ edgeaddn2: _EDGEN2INIT _valu _EDGEADDN2FINI (baresubquery | _varvalu)
234
+ edgedeln2: _EDGEN2INIT _valu _EDGEN2FINI (baresubquery | _varvalu)
233
235
 
234
236
  _REVERSE: /reverse(?=[\s\(])/
235
237
  liftreverse: _REVERSE "(" (liftformtag | liftpropby | liftprop | liftbyarray | lifttagtag | liftbytag | liftbytagprop | liftbyformtagprop) ")"
synapse/lib/storm.py CHANGED
@@ -1299,7 +1299,7 @@ stormcmds = (
1299
1299
 
1300
1300
  @s_cache.memoize(size=1024)
1301
1301
  def queryhash(text):
1302
- return hashlib.md5(text.encode(errors='surrogatepass'), usedforsecurity=False).hexdigest()
1302
+ return s_common.queryhash(text)
1303
1303
 
1304
1304
  class DmonManager(s_base.Base):
1305
1305
  '''
@@ -55,6 +55,7 @@ TerminalPygMap = {
55
55
  'LSQB': p_t.Punctuation,
56
56
  'MCASEBARE': p_t.Literal.String,
57
57
  'MODSET': p_t.Operator,
58
+ 'MODSETMULTI': p_t.Operator,
58
59
  'NONQUOTEWORD': p_t.Literal,
59
60
  'NOT': p_t.Keyword,
60
61
  'NULL': p_t.Keyword,
@@ -74,8 +75,8 @@ TerminalPygMap = {
74
75
  'TAGSEGNOVAR': p_t.Name,
75
76
  'TRIPLEQUOTEDSTRING': p_t.Literal.String,
76
77
  'TRYSET': p_t.Operator,
77
- 'TRYSETMINUS': p_t.Operator,
78
- 'TRYSETPLUS': p_t.Operator,
78
+ 'TRYMODSET': p_t.Operator,
79
+ 'TRYMODSETMULTI': p_t.Operator,
79
80
  'UNIVNAME': p_t.Name,
80
81
  'UNSET': p_t.Operator,
81
82
  'EXPRUNIVNAME': p_t.Name,
synapse/lib/stormtypes.py CHANGED
@@ -3490,7 +3490,11 @@ class LibRegx(Lib):
3490
3490
  lkey = (pattern, flags)
3491
3491
  regx = self.compiled.get(lkey)
3492
3492
  if regx is None:
3493
- regx = self.compiled[lkey] = regex.compile(pattern, flags=flags)
3493
+ try:
3494
+ regx = self.compiled[lkey] = regex.compile(pattern, flags=flags)
3495
+ except (regex.error, ValueError) as e:
3496
+ mesg = f'Error compiling regex pattern: {e}: pattern="{s_common.trimText(pattern)}"'
3497
+ raise s_exc.BadArg(mesg=mesg) from None
3494
3498
  return regx
3495
3499
 
3496
3500
  @stormfunc(readonly=True)
@@ -3500,7 +3504,12 @@ class LibRegx(Lib):
3500
3504
  pattern = await tostr(pattern)
3501
3505
  replace = await tostr(replace)
3502
3506
  regx = await self._getRegx(pattern, flags)
3503
- return regx.sub(replace, text)
3507
+
3508
+ try:
3509
+ return regx.sub(replace, text)
3510
+ except (regex.error, IndexError) as e:
3511
+ mesg = f'$lib.regex.replace() error: {e}'
3512
+ raise s_exc.BadArg(mesg=mesg) from None
3504
3513
 
3505
3514
  @stormfunc(readonly=True)
3506
3515
  async def matches(self, pattern, text, flags=0):
@@ -9868,7 +9877,7 @@ async def tostor(valu, isndef=False):
9868
9877
  retn = []
9869
9878
  for v in valu:
9870
9879
  try:
9871
- retn.append(await tostor(v))
9880
+ retn.append(await tostor(v, isndef=isndef))
9872
9881
  except s_exc.NoSuchType:
9873
9882
  pass
9874
9883
  return tuple(retn)
@@ -9877,7 +9886,7 @@ async def tostor(valu, isndef=False):
9877
9886
  retn = {}
9878
9887
  for k, v in valu.items():
9879
9888
  try:
9880
- retn[k] = await tostor(v)
9889
+ retn[k] = await tostor(v, isndef=isndef)
9881
9890
  except s_exc.NoSuchType:
9882
9891
  pass
9883
9892
  return retn
synapse/lib/version.py CHANGED
@@ -223,6 +223,6 @@ def reqVersion(valu, reqver,
223
223
  ##############################################################################
224
224
  # The following are touched during the release process by bumpversion.
225
225
  # Do not modify these directly.
226
- version = (2, 197, 0)
226
+ version = (2, 198, 0)
227
227
  verstring = '.'.join([str(x) for x in version])
228
- commit = 'c8c758131cae47ef21e23ac8242012459ebfce9d'
228
+ commit = '047e5e996d0bcaad4ada4ea390a726aca1abba9b'
@@ -1252,6 +1252,15 @@ class ItModule(s_module.CoreModule):
1252
1252
  ('product', ('it:prod:softver', {}), {
1253
1253
  'doc': 'The software which produced the log entry.'}),
1254
1254
 
1255
+ ('service:platform', ('inet:service:platform', {}), {
1256
+ 'doc': 'The service platform which generated the log event.'}),
1257
+
1258
+ ('service:instance', ('inet:service:instance', {}), {
1259
+ 'doc': 'The service instance which generated the log event.'}),
1260
+
1261
+ ('service:account', ('inet:service:account', {}), {
1262
+ 'doc': 'The service account which generated the log event.'}),
1263
+
1255
1264
  )),
1256
1265
  ('it:domain', {}, (
1257
1266
  ('name', ('str', {'lower': True, 'onespace': True}), {
@@ -2587,6 +2596,15 @@ class ItModule(s_module.CoreModule):
2587
2596
 
2588
2597
  ('synuser', ('syn:user', {}), {
2589
2598
  'doc': 'The synapse user who executed the query.'}),
2599
+
2600
+ ('service:platform', ('inet:service:platform', {}), {
2601
+ 'doc': 'The service platform which was queried.'}),
2602
+
2603
+ ('service:instance', ('inet:service:instance', {}), {
2604
+ 'doc': 'The service instance which was queried.'}),
2605
+
2606
+ ('service:account', ('inet:service:account', {}), {
2607
+ 'doc': 'The service account which ran the query.'}),
2590
2608
  )),
2591
2609
  ('it:exec:thread', {}, (
2592
2610
  ('proc', ('it:exec:proc', {}), {
synapse/models/risk.py CHANGED
@@ -810,6 +810,15 @@ class RiskModule(s_module.CoreModule):
810
810
 
811
811
  ('host', ('it:host', {}), {
812
812
  'doc': 'The host which generated the alert.'}),
813
+
814
+ ('service:platform', ('inet:service:platform', {}), {
815
+ 'doc': 'The service platform which generated the alert.'}),
816
+
817
+ ('service:instance', ('inet:service:instance', {}), {
818
+ 'doc': 'The service instance which generated the alert.'}),
819
+
820
+ ('service:account', ('inet:service:account', {}), {
821
+ 'doc': 'The service account which generated the alert.'}),
813
822
  )),
814
823
  ('risk:compromisetype', {}, ()),
815
824
  ('risk:compromise', {}, (
synapse/models/syn.py CHANGED
@@ -23,7 +23,15 @@ class SynUser(s_types.Guid):
23
23
  if user is not None:
24
24
  return user.iden, {}
25
25
 
26
- return s_types.Guid._normPyStr(self, text)
26
+ if text == '*':
27
+ mesg = f'{self.name} values must be a valid username or a guid.'
28
+ raise s_exc.BadTypeValu(mesg=mesg, name=self.name, valu=text)
29
+
30
+ try:
31
+ return s_types.Guid._normPyStr(self, text)
32
+ except s_exc.BadTypeValu:
33
+ mesg = f'No user named {text} and value is not a guid.'
34
+ raise s_exc.BadTypeValu(mesg=mesg, name=self.name, valu=text) from None
27
35
 
28
36
  def repr(self, iden):
29
37
 
@@ -51,7 +59,15 @@ class SynRole(s_types.Guid):
51
59
  if role is not None:
52
60
  return role.iden, {}
53
61
 
54
- return s_types.Guid._normPyStr(self, text)
62
+ if text == '*':
63
+ mesg = f'{self.name} values must be a valid rolename or a guid.'
64
+ raise s_exc.BadTypeValu(mesg=mesg, name=self.name, valu=text)
65
+
66
+ try:
67
+ return s_types.Guid._normPyStr(self, text)
68
+ except s_exc.BadTypeValu:
69
+ mesg = f'No role named {text} and value is not a guid.'
70
+ raise s_exc.BadTypeValu(mesg=mesg, name=self.name, valu=text) from None
55
71
 
56
72
  def repr(self, iden):
57
73
 
@@ -0,0 +1,7 @@
1
+ name: test
2
+ version: 0.0.1
3
+ commands:
4
+ - name: 'foo.bar'
5
+ storm: (null)
6
+ endpoints:
7
+ - host: vertex.link
@@ -113,6 +113,14 @@ commands:
113
113
  - help: Help on foo opt
114
114
  - - --bar
115
115
  - help: Help on bar opt
116
+ storm: |
117
+ test:str
118
+ endpoints:
119
+ - path: /v1/test/one
120
+ - path: /v1/test/two
121
+ host: vertex.link
122
+ - path: /v1/test/three
123
+ desc: endpoint three
116
124
 
117
125
  - name: testpkg.baz
118
126
  descr: |
@@ -1001,6 +1001,114 @@ class CortexTest(s_t_utils.SynTest):
1001
1001
  self.eq(cm.exception.get('mesg'),
1002
1002
  'walk operation expected a string or list. got: 0.')
1003
1003
 
1004
+ await core.nodes('[media:news=*]')
1005
+
1006
+ nodes = await core.nodes('$n = {[it:dev:str=foo]} media:news [ +(refs)> $n ]')
1007
+ self.len(1, nodes)
1008
+ self.eq(nodes[0].ndef[0], 'media:news')
1009
+
1010
+ nodes = await core.nodes('media:news -(refs)> it:dev:str')
1011
+ self.len(1, nodes)
1012
+
1013
+ q = '''
1014
+ function foo() {
1015
+ for $x in $lib.range(5) {
1016
+ [ it:dev:int=$x ]
1017
+ emit $node
1018
+ }
1019
+ }
1020
+ media:news [ +(refs)> $foo() ]
1021
+ '''
1022
+ nodes = await core.nodes(q)
1023
+ self.len(1, nodes)
1024
+ self.eq(nodes[0].ndef[0], 'media:news')
1025
+
1026
+ nodes = await core.nodes('media:news -(refs)> it:dev:int')
1027
+ self.len(5, nodes)
1028
+
1029
+ nodes = await core.nodes('$n = {[it:dev:str=foo]} media:news [ -(refs)> $n ]')
1030
+ self.len(1, nodes)
1031
+ self.eq(nodes[0].ndef[0], 'media:news')
1032
+
1033
+ nodes = await core.nodes('media:news -(refs)> it:dev:str')
1034
+ self.len(0, nodes)
1035
+
1036
+ q = '''
1037
+ function foo() {
1038
+ for $x in $lib.range(5) {
1039
+ [ it:dev:int=$x ]
1040
+ emit $node
1041
+ }
1042
+ }
1043
+ media:news [ -(refs)> $foo() ]
1044
+ '''
1045
+ nodes = await core.nodes(q)
1046
+ self.len(1, nodes)
1047
+ self.eq(nodes[0].ndef[0], 'media:news')
1048
+
1049
+ nodes = await core.nodes('media:news -(refs)> it:dev:int')
1050
+ self.len(0, nodes)
1051
+
1052
+ nodes = await core.nodes('$n = {[it:dev:str=foo]} media:news [ <(refs)+ $n ]')
1053
+ self.len(1, nodes)
1054
+ self.eq(nodes[0].ndef[0], 'media:news')
1055
+
1056
+ nodes = await core.nodes('media:news <(refs)- it:dev:str')
1057
+ self.len(1, nodes)
1058
+
1059
+ q = '''
1060
+ function foo() {
1061
+ for $x in $lib.range(5) {
1062
+ [ it:dev:int=$x ]
1063
+ emit $node
1064
+ }
1065
+ }
1066
+ media:news [ <(refs)+ $foo() ]
1067
+ '''
1068
+ nodes = await core.nodes(q)
1069
+ self.len(1, nodes)
1070
+ self.eq(nodes[0].ndef[0], 'media:news')
1071
+
1072
+ nodes = await core.nodes('media:news <(refs)- it:dev:int')
1073
+ self.len(5, nodes)
1074
+
1075
+ nodes = await core.nodes('$n = {[it:dev:str=foo]} media:news [ <(refs)- $n ]')
1076
+ self.len(1, nodes)
1077
+ self.eq(nodes[0].ndef[0], 'media:news')
1078
+
1079
+ nodes = await core.nodes('media:news <(refs)- it:dev:str')
1080
+ self.len(0, nodes)
1081
+
1082
+ q = '''
1083
+ function foo() {
1084
+ for $x in $lib.range(5) {
1085
+ [ it:dev:int=$x ]
1086
+ emit $node
1087
+ }
1088
+ }
1089
+ media:news [ <(refs)- $foo() ]
1090
+ '''
1091
+ nodes = await core.nodes(q)
1092
+ self.len(1, nodes)
1093
+ self.eq(nodes[0].ndef[0], 'media:news')
1094
+
1095
+ nodes = await core.nodes('media:news <(refs)- it:dev:int')
1096
+ self.len(0, nodes)
1097
+
1098
+ await core.nodes('[media:news=*]')
1099
+
1100
+ nodes = await core.nodes('$n = {[it:dev:str=foo]} $edge=refs media:news [ +($edge)> $n ]')
1101
+ self.len(2, nodes)
1102
+
1103
+ nodes = await core.nodes('media:news -(refs)> it:dev:str')
1104
+ self.len(2, nodes)
1105
+
1106
+ nodes = await core.nodes('$n = {[it:dev:str=foo]} $edge=refs media:news [ -($edge)> $n ]')
1107
+ self.len(2, nodes)
1108
+
1109
+ nodes = await core.nodes('media:news -(refs)> it:dev:str')
1110
+ self.len(0, nodes)
1111
+
1004
1112
  async def test_cortex_callstorm(self):
1005
1113
 
1006
1114
  async with self.getTestCore(conf={'auth:passwd': 'root'}) as core:
@@ -133,9 +133,14 @@ class AhaTest(s_test.SynTest):
133
133
  with self.getTestDir() as dirn:
134
134
  cryo0_dirn = s_common.gendir(dirn, 'cryo0')
135
135
  async with self.getTestAha(dirn=dirn) as aha:
136
+
137
+ replaymult = 1
138
+ if s_common.envbool('SYNDEV_NEXUS_REPLAY'):
139
+ replaymult = 2
140
+
136
141
  purl = await aha.addAhaSvcProv('0.cryo')
137
142
 
138
- wait00 = aha.waiter(1, 'aha:svcadd')
143
+ wait00 = aha.waiter(1 * replaymult, 'aha:svcadd')
139
144
 
140
145
  conf = {'aha:provision': purl}
141
146
  async with self.getTestCryo(dirn=cryo0_dirn, conf=conf) as cryo:
@@ -444,8 +449,13 @@ class AhaTest(s_test.SynTest):
444
449
 
445
450
  async with self.getTestAha() as aha:
446
451
 
452
+ replaymult = 1
453
+ if s_common.envbool('SYNDEV_NEXUS_REPLAY'):
454
+ replaymult = 2
455
+
447
456
  aha.testerr = True
448
457
  wait00 = aha.waiter(1, 'aha:svcadd')
458
+
449
459
  conf = {'aha:provision': await aha.addAhaSvcProv('0.cryo')}
450
460
  async with self.getTestCryo(conf=conf) as cryo:
451
461
 
@@ -454,7 +464,7 @@ class AhaTest(s_test.SynTest):
454
464
  svc = await aha.getAhaSvc('0.cryo...')
455
465
  self.none(svc)
456
466
 
457
- wait01 = aha.waiter(1, 'aha:svcadd')
467
+ wait01 = aha.waiter(1 * replaymult, 'aha:svcadd')
458
468
  aha.testerr = False
459
469
 
460
470
  self.nn(await wait01.wait(timeout=2))
@@ -492,6 +492,63 @@ class AstTest(s_test.SynTest):
492
492
  q = '$foo=newp [test:str=foo :hehe*$foo=heval]'
493
493
  nodes = await core.nodes(q)
494
494
 
495
+ async def test_ast_setmultioper(self):
496
+ async with self.getTestCore() as core:
497
+
498
+ nodes = await core.nodes('[ test:arrayprop="*" :ints=(1,) ]')
499
+ self.eq(nodes[0].get('ints'), (1,))
500
+
501
+ nodes = await core.nodes('test:arrayprop [ :ints++=([3, 4]) ]')
502
+ self.eq(nodes[0].get('ints'), (1, 3, 4))
503
+
504
+ nodes = await core.nodes('test:arrayprop [ :ints++=(null) ]')
505
+ self.eq(nodes[0].get('ints'), (1, 3, 4))
506
+
507
+ nodes = await core.nodes('test:arrayprop [ :ints--=(null) ]')
508
+ self.eq(nodes[0].get('ints'), (1, 3, 4))
509
+
510
+ nodes = await core.nodes('test:arrayprop [ :strs++=(foo, bar, baz) ]')
511
+ self.eq(nodes[0].get('strs'), ('foo', 'bar', 'baz'))
512
+
513
+ with self.raises(s_exc.BadTypeValu):
514
+ await core.nodes('test:arrayprop [ :ints++=(["newp", 5, 6]) ]')
515
+
516
+ nodes = await core.nodes('test:arrayprop [ :ints?++=(["newp", 5, 6]) ]')
517
+ self.eq(nodes[0].get('ints'), (1, 3, 4, 5, 6))
518
+
519
+ with self.raises(s_exc.BadTypeValu):
520
+ await core.nodes('test:arrayprop [ :ints--=(["newp", 5, 6]) ]')
521
+
522
+ nodes = await core.nodes('test:arrayprop [ :ints?--=(["newp", 5, 6, 7]) ]')
523
+ self.eq(nodes[0].get('ints'), (1, 3, 4))
524
+
525
+ nodes = await core.nodes('[ test:str=foo :ndefs++={[ test:str=bar ]} ]')
526
+ self.eq(nodes[0].get('ndefs'), (('test:str', 'bar'),))
527
+
528
+ nodes = await core.nodes('test:str=foo [ :ndefs++={[ test:str=baz test:str=faz ]} ]')
529
+ self.eq(nodes[0].get('ndefs'), (('test:str', 'bar'), ('test:str', 'baz'), ('test:str', 'faz')))
530
+
531
+ nodes = await core.nodes('test:str=foo [ :ndefs--={ test:str=baz test:str=faz } ]')
532
+ self.eq(nodes[0].get('ndefs'), (('test:str', 'bar'),))
533
+
534
+ with self.raises(s_exc.NoSuchProp):
535
+ await core.nodes('test:arrayprop [ :newp++=(["newp", 5, 6]) ]')
536
+
537
+ badq = [
538
+ 'test:str [ :hehe++=([3, 4]) ]',
539
+ 'test:str [ :hehe?++=([3, 4]) ]',
540
+ 'test:str [ :hehe--=([3, 4]) ]',
541
+ 'test:str [ :hehe?--=([3, 4]) ]',
542
+ 'test:arrayprop [ :ints++=(3) ]',
543
+ 'test:arrayprop [ :ints?++=(3) ]',
544
+ 'test:arrayprop [ :ints--=(3) ]',
545
+ 'test:arrayprop [ :ints?--=(3) ]',
546
+ ]
547
+
548
+ for q in badq:
549
+ with self.raises(s_exc.StormRuntimeError):
550
+ await core.nodes(q)
551
+
495
552
  async def test_ast_editparens(self):
496
553
 
497
554
  async with self.getTestCore() as core:
@@ -1,9 +1,15 @@
1
1
  import string
2
2
  import pathlib
3
3
 
4
+ from unittest import mock
5
+
4
6
  import synapse.exc as s_exc
7
+ import synapse.common as s_common
5
8
  import synapse.telepath as s_telepath
6
- import synapse.lib.hiveauth as s_hiveauth
9
+
10
+ import synapse.lib.auth as s_auth
11
+ import synapse.lib.cell as s_cell
12
+ import synapse.lib.lmdbslab as s_lmdbslab
7
13
 
8
14
  import synapse.tests.utils as s_test
9
15
 
@@ -426,6 +432,126 @@ class AuthTest(s_test.SynTest):
426
432
  with self.raises(s_exc.SchemaViolation):
427
433
  await core.auth.allrole.setRules([(True, )])
428
434
 
435
+ async def test_auth_archived_locked_interaction(self):
436
+
437
+ # Check that we can't unlock an archived user
438
+ async with self.getTestCore() as core:
439
+ lowuser = await core.addUser('lowuser')
440
+ useriden = lowuser.get('iden')
441
+
442
+ await core.setUserArchived(useriden, True)
443
+
444
+ udef = await core.getUserDef(useriden)
445
+ self.true(udef.get('archived'))
446
+ self.true(udef.get('locked'))
447
+
448
+ # Unlocking an archived user is invalid
449
+ with self.raises(s_exc.BadArg) as exc:
450
+ await core.setUserLocked(useriden, False)
451
+ self.eq(exc.exception.get('mesg'), 'Cannot unlock archived user.')
452
+ self.eq(exc.exception.get('user'), useriden)
453
+ self.eq(exc.exception.get('username'), 'lowuser')
454
+
455
+ # Check our cell migration that locks archived users
456
+ async with self.getRegrCore('unlocked-archived-users') as core:
457
+ for ii in range(10):
458
+ user = await core.getUserDefByName(f'lowuser{ii:02d}')
459
+ self.nn(user)
460
+ self.true(user.get('archived'))
461
+ self.true(user.get('locked'))
462
+
463
+ # Check behavior of upgraded mirrors and non-upgraded leader
464
+ async with self.getTestAha() as aha:
465
+
466
+ with self.getTestDir() as dirn:
467
+ path00 = s_common.gendir(dirn, 'cell00')
468
+ path01 = s_common.gendir(dirn, 'cell01')
469
+
470
+ with mock.patch('synapse.lib.cell.NEXUS_VERSION', (2, 177)):
471
+ async with self.addSvcToAha(aha, '00.cell', s_cell.Cell, dirn=path00) as cell00:
472
+ lowuser = await cell00.addUser('lowuser')
473
+ useriden = lowuser.get('iden')
474
+ await cell00.setUserArchived(useriden, True)
475
+
476
+ with mock.patch('synapse.lib.cell.NEXUS_VERSION', (2, 198)):
477
+ async with self.addSvcToAha(aha, '01.cell', s_cell.Cell, dirn=path01, provinfo={'mirror': 'cell'}) as cell01:
478
+ await cell01.sync()
479
+ udef = await cell01.getUserDef(useriden)
480
+ self.true(udef.get('locked'))
481
+ self.true(udef.get('archived'))
482
+
483
+ # Simulate a call to cell00.setUserLocked(useriden, False) to bypass
484
+ # the check in cell00.auth.setUserInfo()
485
+ await cell00.auth._push('user:info', useriden, 'locked', False)
486
+ await cell01.sync()
487
+
488
+ udef00 = await cell00.getUserDef(useriden)
489
+ self.true(udef00.get('archived'))
490
+ self.false(udef00.get('locked'))
491
+
492
+ udef01 = await cell01.getUserDef(useriden)
493
+ self.true(udef01.get('archived'))
494
+ self.false(udef01.get('locked'))
495
+
496
+ # Check that we don't blowup/schism if an upgraded mirror is behind a leader with a pending
497
+ # user:info event that unlocks an archived user
498
+ async with self.getTestAha() as aha:
499
+
500
+ with self.getTestDir() as dirn:
501
+ path00 = s_common.gendir(dirn, 'cell00')
502
+ path01 = s_common.gendir(dirn, 'cell01')
503
+
504
+ async with self.addSvcToAha(aha, '00.cell', s_cell.Cell, dirn=path00) as cell00:
505
+ lowuser = await cell00.addUser('lowuser')
506
+ useriden = lowuser.get('iden')
507
+ await cell00.setUserLocked(useriden, True)
508
+
509
+ async with self.addSvcToAha(aha, '01.cell', s_cell.Cell, dirn=path01, provinfo={'mirror': 'cell'}) as cell01:
510
+ await cell01.sync()
511
+ udef = await cell01.getUserDef(useriden)
512
+ self.true(udef.get('locked'))
513
+ self.false(udef.get('archived'))
514
+
515
+ # Set user locked while cell01 is offline so it will get the edit when it comes
516
+ # back
517
+ await cell00.setUserLocked(useriden, False)
518
+ await cell00.sync()
519
+
520
+ # Edit the slabs on both cells directly to archive the user
521
+ lmdb00 = s_common.genpath(path00, 'slabs', 'cell.lmdb')
522
+ lmdb01 = s_common.genpath(path01, 'slabs', 'cell.lmdb')
523
+
524
+ slab00 = await s_lmdbslab.Slab.anit(lmdb00, map_size=s_cell.SLAB_MAP_SIZE)
525
+ slab01 = await s_lmdbslab.Slab.anit(lmdb01, map_size=s_cell.SLAB_MAP_SIZE)
526
+
527
+ # Simulate the cell migration which locks archived users
528
+ for slab in (slab00, slab01):
529
+ authkv = slab.getSafeKeyVal('auth')
530
+ userkv = authkv.getSubKeyVal('user:info:')
531
+
532
+ info = userkv.get(useriden)
533
+ info['archived'] = True
534
+ info['locked'] = True
535
+ userkv.set(useriden, info)
536
+
537
+ await slab00.fini()
538
+ await slab01.fini()
539
+
540
+ # Spin the cells back up and wait for the edit to sync to cell01
541
+ async with self.addSvcToAha(aha, '00.cell', s_cell.Cell, dirn=path00) as cell00:
542
+ udef = await cell00.getUserDef(useriden)
543
+ self.true(udef.get('archived'))
544
+ self.true(udef.get('locked'))
545
+
546
+ async with self.addSvcToAha(aha, '01.cell', s_cell.Cell, dirn=path01, provinfo={'mirror': 'cell'}) as cell01:
547
+ await cell01.sync()
548
+ udef = await cell01.getUserDef(useriden)
549
+ self.true(udef.get('archived'))
550
+ self.true(udef.get('locked'))
551
+
552
+ self.ge(cell00.nexsvers, (2, 198))
553
+ self.ge(cell01.nexsvers, (2, 198))
554
+
429
555
  async def test_auth_password_policy(self):
430
556
  policy = {
431
557
  'complexity': {
@@ -446,6 +572,21 @@ class AuthTest(s_test.SynTest):
446
572
  pass3 = 'ZXN-pyv7ber-kzq2kgh'
447
573
 
448
574
  conf = {'auth:passwd:policy': policy}
575
+ async with self.getTestCore(conf=conf) as core:
576
+
577
+ user = await core.auth.addUser('blackout@vertex.link')
578
+
579
+ self.none(user.info.get('policy:previous'))
580
+ await user.setPasswd(pass1, nexs=False)
581
+ await user.setPasswd(pass2, nexs=False)
582
+ await user.setPasswd(pass3, nexs=False)
583
+ self.len(2, user.info.get('policy:previous'))
584
+
585
+ await user.tryPasswd('newp')
586
+ self.eq(1, user.info.get('policy:attempts'))
587
+ await user.setLocked(False, logged=False)
588
+ self.eq(0, user.info.get('policy:attempts'))
589
+
449
590
  async with self.getTestCore(conf=conf) as core:
450
591
  auth = core.auth
451
592
  self.nn(auth.policy)
@@ -708,7 +849,7 @@ class AuthTest(s_test.SynTest):
708
849
  await core.callStorm('auth.role.addrule ninjas --gate $gate another.rule',
709
850
  opts={'vars': {'gate': fork}})
710
851
 
711
- user = await core.auth.getUserByName('lowuser') # type: s_hiveauth.HiveUser
852
+ user = await core.auth.getUserByName('lowuser') # type: s_auth.User
712
853
  self.false(user.allowed(('hehe',)))
713
854
  self.false(user.allowed(('hehe',), deepdeny=True))
714
855
  self.true(user.allowed(('hehe', 'haha')))