synapse 2.170.0__py311-none-any.whl → 2.172.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.
- synapse/common.py +20 -0
- synapse/cortex.py +98 -6
- synapse/lib/agenda.py +13 -7
- synapse/lib/ast.py +9 -8
- synapse/lib/cache.py +2 -2
- synapse/lib/cell.py +7 -3
- synapse/lib/coro.py +12 -0
- synapse/lib/layer.py +124 -84
- synapse/lib/lmdbslab.py +34 -10
- synapse/lib/node.py +1 -1
- synapse/lib/slabseqn.py +11 -5
- synapse/lib/storm.py +7 -71
- synapse/lib/stormhttp.py +1 -1
- synapse/lib/stormlib/auth.py +19 -0
- synapse/lib/stormlib/cell.py +42 -4
- synapse/lib/stormlib/compression.py +6 -6
- synapse/lib/stormlib/env.py +50 -0
- synapse/lib/stormlib/gen.py +1 -1
- synapse/lib/stormlib/model.py +1 -1
- synapse/lib/stormtypes.py +35 -11
- synapse/lib/types.py +7 -7
- synapse/lib/version.py +2 -2
- synapse/lib/view.py +13 -13
- synapse/models/base.py +13 -0
- synapse/models/biz.py +14 -0
- synapse/models/economic.py +3 -0
- synapse/models/inet.py +474 -4
- synapse/models/infotech.py +163 -22
- synapse/models/orgs.py +17 -0
- synapse/models/risk.py +15 -1
- synapse/models/transport.py +1 -1
- synapse/tests/test_common.py +15 -0
- synapse/tests/test_cortex.py +9 -0
- synapse/tests/test_lib_ast.py +2 -1
- synapse/tests/test_lib_cell.py +1 -1
- synapse/tests/test_lib_layer.py +168 -59
- synapse/tests/test_lib_lmdbslab.py +49 -0
- synapse/tests/test_lib_stormlib_auth.py +22 -0
- synapse/tests/test_lib_stormlib_cell.py +47 -0
- synapse/tests/test_lib_stormlib_env.py +25 -0
- synapse/tests/test_lib_stormtypes.py +12 -1
- synapse/tests/test_lib_types.py +1 -0
- synapse/tests/test_lib_view.py +9 -9
- synapse/tests/test_model_base.py +5 -3
- synapse/tests/test_model_economic.py +4 -0
- synapse/tests/test_model_files.py +1 -0
- synapse/tests/test_model_inet.py +405 -1
- synapse/tests/test_model_infotech.py +135 -3
- synapse/tests/test_model_orgs.py +6 -0
- synapse/tests/test_model_risk.py +8 -0
- synapse/tests/test_tools_storm.py +46 -8
- synapse/tests/utils.py +30 -9
- synapse/tools/storm.py +14 -6
- {synapse-2.170.0.dist-info → synapse-2.172.0.dist-info}/METADATA +1 -1
- {synapse-2.170.0.dist-info → synapse-2.172.0.dist-info}/RECORD +58 -56
- {synapse-2.170.0.dist-info → synapse-2.172.0.dist-info}/WHEEL +1 -1
- {synapse-2.170.0.dist-info → synapse-2.172.0.dist-info}/LICENSE +0 -0
- {synapse-2.170.0.dist-info → synapse-2.172.0.dist-info}/top_level.txt +0 -0
synapse/common.py
CHANGED
|
@@ -1183,6 +1183,26 @@ def httpcodereason(code):
|
|
|
1183
1183
|
except ValueError:
|
|
1184
1184
|
return f'Unknown HTTP status code {code}'
|
|
1185
1185
|
|
|
1186
|
+
def trimText(text: str, n: int = 256, placeholder: str = '...') -> str:
|
|
1187
|
+
'''
|
|
1188
|
+
Trim a text string larger than n characters and add a placeholder at the end.
|
|
1189
|
+
|
|
1190
|
+
Args:
|
|
1191
|
+
text: String to trim.
|
|
1192
|
+
n: Number of characters to allow.
|
|
1193
|
+
placeholder: Placeholder text.
|
|
1194
|
+
|
|
1195
|
+
Returns:
|
|
1196
|
+
The original string or the trimmed string.
|
|
1197
|
+
'''
|
|
1198
|
+
if len(text) <= n:
|
|
1199
|
+
return text
|
|
1200
|
+
plen = len(placeholder)
|
|
1201
|
+
mlen = n - plen
|
|
1202
|
+
assert plen > 0
|
|
1203
|
+
assert n > plen
|
|
1204
|
+
return f'{text[:mlen]}{placeholder}'
|
|
1205
|
+
|
|
1186
1206
|
# TODO: Switch back to using asyncio.wait_for when we are using py 3.12+
|
|
1187
1207
|
# This is a workaround for a race where asyncio.wait_for can end up
|
|
1188
1208
|
# ignoring cancellation https://github.com/python/cpython/issues/86296
|
synapse/cortex.py
CHANGED
|
@@ -56,6 +56,7 @@ import synapse.lib.stormwhois as s_stormwhois # NOQA
|
|
|
56
56
|
import synapse.lib.stormtypes as s_stormtypes
|
|
57
57
|
|
|
58
58
|
import synapse.lib.stormlib.aha as s_stormlib_aha # NOQA
|
|
59
|
+
import synapse.lib.stormlib.env as s_stormlib_env # NOQA
|
|
59
60
|
import synapse.lib.stormlib.gen as s_stormlib_gen # NOQA
|
|
60
61
|
import synapse.lib.stormlib.gis as s_stormlib_gis # NOQA
|
|
61
62
|
import synapse.lib.stormlib.hex as s_stormlib_hex # NOQA
|
|
@@ -4548,7 +4549,11 @@ class Cortex(s_oauth.OAuthMixin, s_cell.Cell): # type: ignore
|
|
|
4548
4549
|
continue
|
|
4549
4550
|
|
|
4550
4551
|
view.layers = [lyr for lyr in view.layers if lyr.iden != layriden]
|
|
4551
|
-
|
|
4552
|
+
layridens = [lyr.iden for lyr in view.layers]
|
|
4553
|
+
await view.info.set('layers', layridens)
|
|
4554
|
+
|
|
4555
|
+
mesg = {'iden': view.iden, 'layers': layridens}
|
|
4556
|
+
await self.feedBeholder('view:setlayers', mesg, gates=[view.iden, layridens[0]])
|
|
4552
4557
|
|
|
4553
4558
|
if view.parent.iden == viewiden:
|
|
4554
4559
|
if newparent is None:
|
|
@@ -4558,6 +4563,9 @@ class Cortex(s_oauth.OAuthMixin, s_cell.Cell): # type: ignore
|
|
|
4558
4563
|
view.parent = newview
|
|
4559
4564
|
await view.info.set('parent', newparent)
|
|
4560
4565
|
|
|
4566
|
+
mesg = {'iden': view.iden, 'name': 'parent', 'valu': newparent}
|
|
4567
|
+
await self.feedBeholder('view:set', mesg, gates=[view.iden, layridens[0]])
|
|
4568
|
+
|
|
4561
4569
|
if not layrinuse and (layr := self.layers.get(layriden)) is not None:
|
|
4562
4570
|
del self.layers[layriden]
|
|
4563
4571
|
|
|
@@ -4682,6 +4690,13 @@ class Cortex(s_oauth.OAuthMixin, s_cell.Cell): # type: ignore
|
|
|
4682
4690
|
|
|
4683
4691
|
return self.layers.get(iden)
|
|
4684
4692
|
|
|
4693
|
+
def reqLayer(self, iden=None):
|
|
4694
|
+
layr = self.getLayer(iden=iden)
|
|
4695
|
+
if layr is None:
|
|
4696
|
+
mesg = f'No layer found with iden: {iden}'
|
|
4697
|
+
raise s_exc.NoSuchLayer(mesg=mesg, iden=iden)
|
|
4698
|
+
return layr
|
|
4699
|
+
|
|
4685
4700
|
def listLayers(self):
|
|
4686
4701
|
return self.layers.values()
|
|
4687
4702
|
|
|
@@ -5038,10 +5053,7 @@ class Cortex(s_oauth.OAuthMixin, s_cell.Cell): # type: ignore
|
|
|
5038
5053
|
await self.setNexsIndx(maxindx)
|
|
5039
5054
|
|
|
5040
5055
|
async def saveLayerNodeEdits(self, layriden, edits, meta):
|
|
5041
|
-
layr = self.
|
|
5042
|
-
if layr is None:
|
|
5043
|
-
mesg = f'No layer found with iden: {layriden}'
|
|
5044
|
-
raise s_exc.NoSuchLayer(mesg=mesg, iden=layriden)
|
|
5056
|
+
layr = self.reqLayer(layriden)
|
|
5045
5057
|
return await layr.saveNodeEdits(edits, meta)
|
|
5046
5058
|
|
|
5047
5059
|
async def cloneLayer(self, iden, ldef=None):
|
|
@@ -6905,6 +6917,83 @@ class Cortex(s_oauth.OAuthMixin, s_cell.Cell): # type: ignore
|
|
|
6905
6917
|
self.slab.delete(name.encode(), db=self.vaultsbynamedb)
|
|
6906
6918
|
self.slab.delete(bidn, db=self.vaultsdb)
|
|
6907
6919
|
|
|
6920
|
+
def _propAllowedReason(self, user, perms, gateiden=None, default=None):
|
|
6921
|
+
'''
|
|
6922
|
+
Similar to allowed, but always prefer the default value specified by the caller.
|
|
6923
|
+
Default values are still pulled from permdefs if there is a match there; but still prefer caller default.
|
|
6924
|
+
This results in a ternary response that can be used to know if a rule had a positive/negative or no match.
|
|
6925
|
+
The matching reason metadata is also returned.
|
|
6926
|
+
'''
|
|
6927
|
+
if default is None:
|
|
6928
|
+
permdef = self.getPermDef(perms)
|
|
6929
|
+
if permdef:
|
|
6930
|
+
default = permdef.get('default', default)
|
|
6931
|
+
|
|
6932
|
+
return user.getAllowedReason(perms, gateiden=gateiden, default=default)
|
|
6933
|
+
|
|
6934
|
+
def confirmPropSet(self, user, prop, layriden):
|
|
6935
|
+
meta0 = self._propAllowedReason(user, prop.setperms[0], gateiden=layriden)
|
|
6936
|
+
|
|
6937
|
+
if meta0.isadmin:
|
|
6938
|
+
return
|
|
6939
|
+
|
|
6940
|
+
allowed0 = meta0.value
|
|
6941
|
+
|
|
6942
|
+
meta1 = self._propAllowedReason(user, prop.setperms[1], gateiden=layriden)
|
|
6943
|
+
allowed1 = meta1.value
|
|
6944
|
+
|
|
6945
|
+
if allowed0:
|
|
6946
|
+
if allowed1:
|
|
6947
|
+
return
|
|
6948
|
+
elif allowed1 is False:
|
|
6949
|
+
# This is a allow-with-precedence case.
|
|
6950
|
+
# Inspect meta to determine if the rule a0 is more specific than rule a1
|
|
6951
|
+
if len(meta0.rule) >= len(meta1.rule):
|
|
6952
|
+
return
|
|
6953
|
+
user.raisePermDeny(prop.setperms[0], gateiden=layriden)
|
|
6954
|
+
return
|
|
6955
|
+
|
|
6956
|
+
if allowed1:
|
|
6957
|
+
if allowed0 is None:
|
|
6958
|
+
return
|
|
6959
|
+
# allowed0 here is False. This is a deny-with-precedence case.
|
|
6960
|
+
# Inspect meta to determine if the rule a1 is more specific than rule a0
|
|
6961
|
+
if len(meta1.rule) > len(meta0.rule):
|
|
6962
|
+
return
|
|
6963
|
+
|
|
6964
|
+
user.raisePermDeny(prop.setperms[0], gateiden=layriden)
|
|
6965
|
+
|
|
6966
|
+
def confirmPropDel(self, user, prop, layriden):
|
|
6967
|
+
meta0 = self._propAllowedReason(user, prop.delperms[0], gateiden=layriden)
|
|
6968
|
+
|
|
6969
|
+
if meta0.isadmin:
|
|
6970
|
+
return
|
|
6971
|
+
|
|
6972
|
+
allowed0 = meta0.value
|
|
6973
|
+
meta1 = self._propAllowedReason(user, prop.delperms[1], gateiden=layriden)
|
|
6974
|
+
allowed1 = meta1.value
|
|
6975
|
+
|
|
6976
|
+
if allowed0:
|
|
6977
|
+
if allowed1:
|
|
6978
|
+
return
|
|
6979
|
+
elif allowed1 is False:
|
|
6980
|
+
# This is a allow-with-precedence case.
|
|
6981
|
+
# Inspect meta to determine if the rule a0 is more specific than rule a1
|
|
6982
|
+
if len(meta0.rule) >= len(meta1.rule):
|
|
6983
|
+
return
|
|
6984
|
+
user.raisePermDeny(prop.delperms[0], gateiden=layriden)
|
|
6985
|
+
return
|
|
6986
|
+
|
|
6987
|
+
if allowed1:
|
|
6988
|
+
if allowed0 is None:
|
|
6989
|
+
return
|
|
6990
|
+
# allowed0 here is False. This is a deny-with-precedence case.
|
|
6991
|
+
# Inspect meta to determine if the rule a1 is more specific than rule a0
|
|
6992
|
+
if len(meta1.rule) > len(meta0.rule):
|
|
6993
|
+
return
|
|
6994
|
+
|
|
6995
|
+
user.raisePermDeny(prop.delperms[0], gateiden=layriden)
|
|
6996
|
+
|
|
6908
6997
|
@contextlib.asynccontextmanager
|
|
6909
6998
|
async def getTempCortex(mods=None):
|
|
6910
6999
|
'''
|
|
@@ -6922,7 +7011,10 @@ async def getTempCortex(mods=None):
|
|
|
6922
7011
|
'''
|
|
6923
7012
|
with s_common.getTempDir() as dirn:
|
|
6924
7013
|
logger.debug(f'Creating temporary cortex as {dirn}')
|
|
6925
|
-
|
|
7014
|
+
conf = {
|
|
7015
|
+
'health:sysctl:checks': False,
|
|
7016
|
+
}
|
|
7017
|
+
async with await Cortex.anit(dirn, conf=conf) as core:
|
|
6926
7018
|
if mods:
|
|
6927
7019
|
for mod in mods:
|
|
6928
7020
|
await core.loadCoreModule(mod)
|
synapse/lib/agenda.py
CHANGED
|
@@ -572,7 +572,8 @@ class Agenda(s_base.Base):
|
|
|
572
572
|
self._next_indx += 1
|
|
573
573
|
|
|
574
574
|
if iden in self.appts:
|
|
575
|
-
|
|
575
|
+
mesg = f'Cron job already exists with iden: {iden}'
|
|
576
|
+
raise s_exc.DupIden(iden=iden, mesg=mesg)
|
|
576
577
|
|
|
577
578
|
if not query:
|
|
578
579
|
raise ValueError('"query" key of cdef parameter is not present or empty')
|
|
@@ -622,20 +623,22 @@ class Agenda(s_base.Base):
|
|
|
622
623
|
if appt is not None:
|
|
623
624
|
return appt
|
|
624
625
|
|
|
625
|
-
mesg = f'No cron job with
|
|
626
|
+
mesg = f'No cron job with iden {iden}'
|
|
626
627
|
raise s_exc.NoSuchIden(iden=iden, mesg=mesg)
|
|
627
628
|
|
|
628
629
|
async def enable(self, iden):
|
|
629
630
|
appt = self.appts.get(iden)
|
|
630
631
|
if appt is None:
|
|
631
|
-
|
|
632
|
+
mesg = f'No cron job with iden: {iden}'
|
|
633
|
+
raise s_exc.NoSuchIden(iden=iden, mesg=mesg)
|
|
632
634
|
|
|
633
635
|
await self.mod(iden, appt.query)
|
|
634
636
|
|
|
635
637
|
async def disable(self, iden):
|
|
636
638
|
appt = self.appts.get(iden)
|
|
637
639
|
if appt is None:
|
|
638
|
-
|
|
640
|
+
mesg = f'No cron job with iden: {iden}'
|
|
641
|
+
raise s_exc.NoSuchIden(iden=iden, mesg=mesg)
|
|
639
642
|
|
|
640
643
|
appt.enabled = False
|
|
641
644
|
await appt.save()
|
|
@@ -646,7 +649,8 @@ class Agenda(s_base.Base):
|
|
|
646
649
|
'''
|
|
647
650
|
appt = self.appts.get(iden)
|
|
648
651
|
if appt is None:
|
|
649
|
-
|
|
652
|
+
mesg = f'No cron job with iden: {iden}'
|
|
653
|
+
raise s_exc.NoSuchIden(iden=iden, mesg=mesg)
|
|
650
654
|
|
|
651
655
|
if not query:
|
|
652
656
|
raise ValueError('empty query')
|
|
@@ -664,7 +668,8 @@ class Agenda(s_base.Base):
|
|
|
664
668
|
'''
|
|
665
669
|
appt = self.appts.get(croniden)
|
|
666
670
|
if appt is None:
|
|
667
|
-
|
|
671
|
+
mesg = f'No cron job with iden: {croniden}'
|
|
672
|
+
raise s_exc.NoSuchIden(iden=croniden, mesg=mesg)
|
|
668
673
|
|
|
669
674
|
appt.view = viewiden
|
|
670
675
|
|
|
@@ -676,7 +681,8 @@ class Agenda(s_base.Base):
|
|
|
676
681
|
'''
|
|
677
682
|
appt = self.appts.get(iden)
|
|
678
683
|
if appt is None:
|
|
679
|
-
|
|
684
|
+
mesg = f'No cron job with iden: {iden}'
|
|
685
|
+
raise s_exc.NoSuchIden(iden=iden, mesg=mesg)
|
|
680
686
|
|
|
681
687
|
try:
|
|
682
688
|
heappos = self.apptheap.index(appt)
|
synapse/lib/ast.py
CHANGED
|
@@ -733,8 +733,9 @@ class SubQuery(Oper):
|
|
|
733
733
|
retn.append(valunode.ndef[1])
|
|
734
734
|
|
|
735
735
|
if len(retn) > limit:
|
|
736
|
-
|
|
737
|
-
|
|
736
|
+
query = self.kids[0].text
|
|
737
|
+
mesg = f'Subquery used as a value yielded too many (>{limit}) nodes. {s_common.trimText(query)}'
|
|
738
|
+
raise self.addExcInfo(s_exc.BadTypeValu(mesg=mesg, text=query))
|
|
738
739
|
|
|
739
740
|
return retn
|
|
740
741
|
|
|
@@ -987,12 +988,12 @@ class ForLoop(Oper):
|
|
|
987
988
|
try:
|
|
988
989
|
numitems = len(item)
|
|
989
990
|
except TypeError:
|
|
990
|
-
mesg = f'Number of items to unpack does not match the number of variables: {repr(item)
|
|
991
|
+
mesg = f'Number of items to unpack does not match the number of variables: {s_common.trimText(repr(item))}'
|
|
991
992
|
exc = s_exc.StormVarListError(mesg=mesg, names=name)
|
|
992
993
|
raise self.kids[1].addExcInfo(exc)
|
|
993
994
|
|
|
994
995
|
if len(name) != numitems:
|
|
995
|
-
mesg = f'Number of items to unpack does not match the number of variables: {repr(item)
|
|
996
|
+
mesg = f'Number of items to unpack does not match the number of variables: {s_common.trimText(repr(item))}'
|
|
996
997
|
exc = s_exc.StormVarListError(mesg=mesg, names=name, numitems=numitems)
|
|
997
998
|
raise self.kids[1].addExcInfo(exc)
|
|
998
999
|
|
|
@@ -1053,12 +1054,12 @@ class ForLoop(Oper):
|
|
|
1053
1054
|
try:
|
|
1054
1055
|
numitems = len(item)
|
|
1055
1056
|
except TypeError:
|
|
1056
|
-
mesg = f'Number of items to unpack does not match the number of variables: {repr(item)
|
|
1057
|
+
mesg = f'Number of items to unpack does not match the number of variables: {s_common.trimText(repr(item))}'
|
|
1057
1058
|
exc = s_exc.StormVarListError(mesg=mesg, names=name)
|
|
1058
1059
|
raise self.kids[1].addExcInfo(exc)
|
|
1059
1060
|
|
|
1060
1061
|
if len(name) != numitems:
|
|
1061
|
-
mesg = f'Number of items to unpack does not match the number of variables: {repr(item)
|
|
1062
|
+
mesg = f'Number of items to unpack does not match the number of variables: {s_common.trimText(repr(item))}'
|
|
1062
1063
|
exc = s_exc.StormVarListError(mesg=mesg, names=name, numitems=numitems)
|
|
1063
1064
|
raise self.kids[1].addExcInfo(exc)
|
|
1064
1065
|
|
|
@@ -1306,7 +1307,7 @@ class VarListSetOper(Oper):
|
|
|
1306
1307
|
item = [i async for i in s_stormtypes.toiter(item)]
|
|
1307
1308
|
|
|
1308
1309
|
if len(item) < len(names):
|
|
1309
|
-
mesg = f'Attempting to assign more items than we have variables to assign to: {repr(item)
|
|
1310
|
+
mesg = f'Attempting to assign more items than we have variables to assign to: {s_common.trimText(repr(item))}'
|
|
1310
1311
|
exc = s_exc.StormVarListError(mesg=mesg, names=names, numitems=len(item))
|
|
1311
1312
|
raise self.kids[0].addExcInfo(exc)
|
|
1312
1313
|
|
|
@@ -1322,7 +1323,7 @@ class VarListSetOper(Oper):
|
|
|
1322
1323
|
item = [i async for i in s_stormtypes.toiter(item)]
|
|
1323
1324
|
|
|
1324
1325
|
if len(item) < len(names):
|
|
1325
|
-
mesg = f'Attempting to assign more items than we have variables to assign to: {repr(item)
|
|
1326
|
+
mesg = f'Attempting to assign more items than we have variables to assign to: {s_common.trimText(repr(item))}'
|
|
1326
1327
|
exc = s_exc.StormVarListError(mesg=mesg, names=names, numitems=len(item))
|
|
1327
1328
|
raise self.kids[0].addExcInfo(exc)
|
|
1328
1329
|
|
synapse/lib/cache.py
CHANGED
|
@@ -66,7 +66,7 @@ class FixedCache:
|
|
|
66
66
|
|
|
67
67
|
def get(self, key):
|
|
68
68
|
if self.iscorocall:
|
|
69
|
-
raise s_exc.BadArg('cache was initialized with coroutine. Must use aget')
|
|
69
|
+
raise s_exc.BadArg(mesg='cache was initialized with coroutine. Must use aget')
|
|
70
70
|
|
|
71
71
|
valu = self.cache.get(key, s_common.novalu)
|
|
72
72
|
if valu is not s_common.novalu:
|
|
@@ -81,7 +81,7 @@ class FixedCache:
|
|
|
81
81
|
|
|
82
82
|
async def aget(self, key):
|
|
83
83
|
if not self.iscorocall:
|
|
84
|
-
raise s_exc.BadOperArg('cache was initialized with non coroutine. Must use get')
|
|
84
|
+
raise s_exc.BadOperArg(mesg='cache was initialized with non coroutine. Must use get')
|
|
85
85
|
|
|
86
86
|
valu = self.cache.get(key, s_common.novalu)
|
|
87
87
|
if valu is not s_common.novalu:
|
synapse/lib/cell.py
CHANGED
|
@@ -55,7 +55,6 @@ import synapse.lib.lmdbslab as s_lmdbslab
|
|
|
55
55
|
import synapse.lib.thisplat as s_thisplat
|
|
56
56
|
|
|
57
57
|
import synapse.lib.crypto.passwd as s_passwd
|
|
58
|
-
import synapse.lib.platforms.linux as s_linux
|
|
59
58
|
|
|
60
59
|
import synapse.tools.backup as s_t_backup
|
|
61
60
|
|
|
@@ -1465,7 +1464,7 @@ class Cell(s_nexus.Pusher, s_telepath.Aware):
|
|
|
1465
1464
|
async def _runSysctlLoop(self):
|
|
1466
1465
|
while not self.isfini:
|
|
1467
1466
|
fixvals = []
|
|
1468
|
-
sysctls =
|
|
1467
|
+
sysctls = s_thisplat.getSysctls()
|
|
1469
1468
|
|
|
1470
1469
|
for name, valu in self.SYSCTL_VALS.items():
|
|
1471
1470
|
if (sysval := sysctls.get(name)) != valu:
|
|
@@ -1484,7 +1483,7 @@ class Cell(s_nexus.Pusher, s_telepath.Aware):
|
|
|
1484
1483
|
extra = await self.getLogExtra(sysctls=fixvals)
|
|
1485
1484
|
logger.warning(mesg, extra=extra)
|
|
1486
1485
|
|
|
1487
|
-
await
|
|
1486
|
+
await self.waitfini(self.SYSCTL_CHECK_FREQ)
|
|
1488
1487
|
|
|
1489
1488
|
def _getAhaAdmin(self):
|
|
1490
1489
|
name = self.conf.get('aha:admin')
|
|
@@ -4143,6 +4142,11 @@ class Cell(s_nexus.Pusher, s_telepath.Aware):
|
|
|
4143
4142
|
async with await s_spooled.Set.anit(dirn=self.dirn, cell=self) as sset:
|
|
4144
4143
|
yield sset
|
|
4145
4144
|
|
|
4145
|
+
@contextlib.asynccontextmanager
|
|
4146
|
+
async def getSpooledDict(self):
|
|
4147
|
+
async with await s_spooled.Dict.anit(dirn=self.dirn, cell=self) as sdict:
|
|
4148
|
+
yield sdict
|
|
4149
|
+
|
|
4146
4150
|
async def addSignalHandlers(self):
|
|
4147
4151
|
await s_base.Base.addSignalHandlers(self)
|
|
4148
4152
|
|
synapse/lib/coro.py
CHANGED
|
@@ -39,6 +39,18 @@ async def agen(item):
|
|
|
39
39
|
for x in item:
|
|
40
40
|
yield x
|
|
41
41
|
|
|
42
|
+
async def pause(genr, iterations=10):
|
|
43
|
+
idx = 0
|
|
44
|
+
|
|
45
|
+
async for out in agen(genr):
|
|
46
|
+
yield out
|
|
47
|
+
idx += 1
|
|
48
|
+
|
|
49
|
+
if idx % iterations == 0:
|
|
50
|
+
await asyncio.sleep(0)
|
|
51
|
+
|
|
52
|
+
return
|
|
53
|
+
|
|
42
54
|
def executor(func, *args, **kwargs):
|
|
43
55
|
'''
|
|
44
56
|
Execute a non-coroutine function in the ioloop executor pool.
|
synapse/lib/layer.py
CHANGED
|
@@ -75,6 +75,7 @@ import synapse.telepath as s_telepath
|
|
|
75
75
|
|
|
76
76
|
import synapse.lib.gis as s_gis
|
|
77
77
|
import synapse.lib.cell as s_cell
|
|
78
|
+
import synapse.lib.coro as s_coro
|
|
78
79
|
import synapse.lib.cache as s_cache
|
|
79
80
|
import synapse.lib.nexus as s_nexus
|
|
80
81
|
import synapse.lib.queue as s_queue
|
|
@@ -157,14 +158,14 @@ class LayerApi(s_cell.CellApi):
|
|
|
157
158
|
|
|
158
159
|
await self.layr.storNodeEditsNoLift(nodeedits, meta)
|
|
159
160
|
|
|
160
|
-
async def syncNodeEdits(self, offs, wait=True):
|
|
161
|
+
async def syncNodeEdits(self, offs, wait=True, reverse=False):
|
|
161
162
|
'''
|
|
162
163
|
Yield (offs, nodeedits) tuples from the nodeedit log starting from the given offset.
|
|
163
164
|
|
|
164
165
|
Once caught up with storage, yield them in realtime.
|
|
165
166
|
'''
|
|
166
167
|
await self._reqUserAllowed(self.liftperm)
|
|
167
|
-
async for item in self.layr.syncNodeEdits(offs, wait=wait):
|
|
168
|
+
async for item in self.layr.syncNodeEdits(offs, wait=wait, reverse=reverse):
|
|
168
169
|
yield item
|
|
169
170
|
await asyncio.sleep(0)
|
|
170
171
|
|
|
@@ -3816,7 +3817,7 @@ class Layer(s_nexus.Pusher):
|
|
|
3816
3817
|
|
|
3817
3818
|
async def getEdgeVerbs(self):
|
|
3818
3819
|
|
|
3819
|
-
for lkey in self.layrslab.scanKeys(db=self.byverb):
|
|
3820
|
+
for lkey in self.layrslab.scanKeys(db=self.byverb, nodup=True):
|
|
3820
3821
|
yield lkey.decode()
|
|
3821
3822
|
|
|
3822
3823
|
async def getEdges(self, verb=None):
|
|
@@ -4088,11 +4089,126 @@ class Layer(s_nexus.Pusher):
|
|
|
4088
4089
|
'''
|
|
4089
4090
|
Return a generator of all a buid's node data keys
|
|
4090
4091
|
'''
|
|
4091
|
-
for lkey in self.dataslab.scanKeysByPref(buid, db=self.nodedata):
|
|
4092
|
+
for lkey in self.dataslab.scanKeysByPref(buid, db=self.nodedata, nodup=True):
|
|
4092
4093
|
abrv = lkey[32:]
|
|
4093
4094
|
prop = self.getAbrvProp(abrv)
|
|
4094
4095
|
yield prop[0]
|
|
4095
4096
|
|
|
4097
|
+
async def confirmLayerEditPerms(self, user, gateiden, delete=False):
|
|
4098
|
+
if user.allowed(('node',), gateiden=gateiden):
|
|
4099
|
+
return
|
|
4100
|
+
|
|
4101
|
+
if delete:
|
|
4102
|
+
perm_forms = ('node', 'del')
|
|
4103
|
+
perm_props = ('node', 'prop', 'del')
|
|
4104
|
+
perm_tags = ('node', 'tag', 'del')
|
|
4105
|
+
perm_ndata = ('node', 'data', 'pop')
|
|
4106
|
+
perm_edges = ('node', 'edge', 'del')
|
|
4107
|
+
else:
|
|
4108
|
+
perm_forms = ('node', 'add')
|
|
4109
|
+
perm_props = ('node', 'prop', 'set')
|
|
4110
|
+
perm_tags = ('node', 'tag', 'add')
|
|
4111
|
+
perm_ndata = ('node', 'data', 'set')
|
|
4112
|
+
perm_edges = ('node', 'edge', 'add')
|
|
4113
|
+
|
|
4114
|
+
allow_forms = user.allowed(perm_forms, gateiden=gateiden)
|
|
4115
|
+
allow_props = user.allowed(perm_props, gateiden=gateiden)
|
|
4116
|
+
allow_tags = user.allowed(perm_tags, gateiden=gateiden)
|
|
4117
|
+
allow_ndata = user.allowed(perm_ndata, gateiden=gateiden)
|
|
4118
|
+
allow_edges = user.allowed(perm_edges, gateiden=gateiden)
|
|
4119
|
+
|
|
4120
|
+
if all((allow_forms, allow_props, allow_tags, allow_ndata, allow_edges)):
|
|
4121
|
+
return
|
|
4122
|
+
|
|
4123
|
+
# nodes & props
|
|
4124
|
+
if not allow_forms or not allow_props:
|
|
4125
|
+
async for byts, abrv in s_coro.pause(self.propabrv.slab.scanByFull(db=self.propabrv.name2abrv)):
|
|
4126
|
+
form, prop = s_msgpack.un(byts)
|
|
4127
|
+
if form is None: # pragma: no cover
|
|
4128
|
+
continue
|
|
4129
|
+
|
|
4130
|
+
if self.layrslab.prefexists(abrv, db=self.byprop):
|
|
4131
|
+
if prop and not allow_props:
|
|
4132
|
+
realform = self.core.model.form(form)
|
|
4133
|
+
if not realform: # pragma: no cover
|
|
4134
|
+
mesg = f'Invalid form: {form}'
|
|
4135
|
+
raise s_exc.NoSuchForm(mesg=mesg, form=form)
|
|
4136
|
+
|
|
4137
|
+
realprop = realform.prop(prop)
|
|
4138
|
+
if not realprop: # pragma: no cover
|
|
4139
|
+
mesg = f'Invalid prop: {form}:{prop}'
|
|
4140
|
+
raise s_exc.NoSuchProp(mesg=mesg, form=form, prop=prop)
|
|
4141
|
+
|
|
4142
|
+
if delete:
|
|
4143
|
+
self.core.confirmPropDel(user, realprop, gateiden)
|
|
4144
|
+
else:
|
|
4145
|
+
self.core.confirmPropSet(user, realprop, gateiden)
|
|
4146
|
+
|
|
4147
|
+
elif not prop and not allow_forms:
|
|
4148
|
+
user.confirm(perm_forms + (form,), gateiden=gateiden)
|
|
4149
|
+
|
|
4150
|
+
# tagprops
|
|
4151
|
+
if not allow_tags:
|
|
4152
|
+
async for byts, abrv in s_coro.pause(self.tagpropabrv.slab.scanByFull(db=self.tagpropabrv.name2abrv)):
|
|
4153
|
+
info = s_msgpack.un(byts)
|
|
4154
|
+
if None in info or len(info) != 3:
|
|
4155
|
+
continue
|
|
4156
|
+
|
|
4157
|
+
if self.layrslab.prefexists(abrv, db=self.bytagprop):
|
|
4158
|
+
perm = perm_tags + tuple(info[1].split('.'))
|
|
4159
|
+
user.confirm(perm, gateiden=gateiden)
|
|
4160
|
+
|
|
4161
|
+
# nodedata
|
|
4162
|
+
if not allow_ndata:
|
|
4163
|
+
async for abrv in s_coro.pause(self.dataslab.scanKeys(db=self.dataname, nodup=True)):
|
|
4164
|
+
name, _ = self.getAbrvProp(abrv)
|
|
4165
|
+
perm = perm_ndata + (name,)
|
|
4166
|
+
user.confirm(perm, gateiden=gateiden)
|
|
4167
|
+
|
|
4168
|
+
# edges
|
|
4169
|
+
if not allow_edges:
|
|
4170
|
+
async for verb in s_coro.pause(self.layrslab.scanKeys(db=self.byverb, nodup=True)):
|
|
4171
|
+
perm = perm_edges + (verb.decode(),)
|
|
4172
|
+
user.confirm(perm, gateiden=gateiden)
|
|
4173
|
+
|
|
4174
|
+
# tags
|
|
4175
|
+
# NB: tag perms should be yielded for every leaf on every node in the layer
|
|
4176
|
+
if not allow_tags:
|
|
4177
|
+
async with self.core.getSpooledDict() as tags:
|
|
4178
|
+
|
|
4179
|
+
# Collect all tag abrvs for all nodes in the layer
|
|
4180
|
+
async for lkey, buid in s_coro.pause(self.layrslab.scanByFull(db=self.bytag)):
|
|
4181
|
+
abrv = lkey[:8]
|
|
4182
|
+
abrvs = list(tags.get(buid, []))
|
|
4183
|
+
abrvs.append(abrv)
|
|
4184
|
+
await tags.set(buid, abrvs)
|
|
4185
|
+
|
|
4186
|
+
# Iterate over each node and it's tags
|
|
4187
|
+
async for buid, abrvs in s_coro.pause(tags.items()):
|
|
4188
|
+
seen = {}
|
|
4189
|
+
|
|
4190
|
+
if len(abrvs) == 1:
|
|
4191
|
+
# Easy optimization: If there's only one tag abrv, then it's a
|
|
4192
|
+
# leaf by default
|
|
4193
|
+
name = self.tagabrv.abrvToName(abrv)
|
|
4194
|
+
key = tuple(name.split('.'))
|
|
4195
|
+
perm = perm_tags + key
|
|
4196
|
+
user.confirm(perm, gateiden=gateiden)
|
|
4197
|
+
|
|
4198
|
+
else:
|
|
4199
|
+
for abrv in abrvs:
|
|
4200
|
+
name = self.tagabrv.abrvToName(abrv)
|
|
4201
|
+
parts = tuple(name.split('.'))
|
|
4202
|
+
for idx in range(1, len(parts) + 1):
|
|
4203
|
+
key = tuple(parts[:idx])
|
|
4204
|
+
seen.setdefault(key, 0)
|
|
4205
|
+
seen[key] += 1
|
|
4206
|
+
|
|
4207
|
+
for key, count in seen.items():
|
|
4208
|
+
if count == 1:
|
|
4209
|
+
perm = perm_tags + key
|
|
4210
|
+
user.confirm(perm, gateiden=gateiden)
|
|
4211
|
+
|
|
4096
4212
|
async def iterLayerNodeEdits(self):
|
|
4097
4213
|
'''
|
|
4098
4214
|
Scan the full layer and yield artificial sets of nodeedits.
|
|
@@ -4291,7 +4407,7 @@ class Layer(s_nexus.Pusher):
|
|
|
4291
4407
|
for offs, (edits, meta) in self.nodeeditlog.iterBack(offs):
|
|
4292
4408
|
yield (offs, edits, meta)
|
|
4293
4409
|
|
|
4294
|
-
async def syncNodeEdits2(self, offs, wait=True):
|
|
4410
|
+
async def syncNodeEdits2(self, offs, wait=True, reverse=False):
|
|
4295
4411
|
'''
|
|
4296
4412
|
Once caught up with storage, yield them in realtime.
|
|
4297
4413
|
|
|
@@ -4301,7 +4417,7 @@ class Layer(s_nexus.Pusher):
|
|
|
4301
4417
|
if not self.logedits:
|
|
4302
4418
|
return
|
|
4303
4419
|
|
|
4304
|
-
for offi, (nodeedits, meta) in self.nodeeditlog.iter(offs):
|
|
4420
|
+
for offi, (nodeedits, meta) in self.nodeeditlog.iter(offs, reverse=reverse):
|
|
4305
4421
|
yield (offi, nodeedits, meta)
|
|
4306
4422
|
|
|
4307
4423
|
if wait:
|
|
@@ -4309,11 +4425,11 @@ class Layer(s_nexus.Pusher):
|
|
|
4309
4425
|
async for item in wind:
|
|
4310
4426
|
yield item
|
|
4311
4427
|
|
|
4312
|
-
async def syncNodeEdits(self, offs, wait=True):
|
|
4428
|
+
async def syncNodeEdits(self, offs, wait=True, reverse=False):
|
|
4313
4429
|
'''
|
|
4314
4430
|
Identical to syncNodeEdits2, but doesn't yield meta
|
|
4315
4431
|
'''
|
|
4316
|
-
async for offi, nodeedits, _meta in self.syncNodeEdits2(offs, wait=wait):
|
|
4432
|
+
async for offi, nodeedits, _meta in self.syncNodeEdits2(offs, wait=wait, reverse=reverse):
|
|
4317
4433
|
yield (offi, nodeedits)
|
|
4318
4434
|
|
|
4319
4435
|
async def syncIndexEvents(self, offs, matchdef, wait=True):
|
|
@@ -4450,79 +4566,3 @@ def getFlatEdits(nodeedits):
|
|
|
4450
4566
|
addedits(buid, form, edits)
|
|
4451
4567
|
|
|
4452
4568
|
return [(k[0], k[1], v) for (k, v) in editsbynode.items()]
|
|
4453
|
-
|
|
4454
|
-
def getNodeEditPerms(nodeedits):
|
|
4455
|
-
'''
|
|
4456
|
-
Yields (offs, perm) tuples that can be used in user.allowed()
|
|
4457
|
-
'''
|
|
4458
|
-
tags = []
|
|
4459
|
-
tagadds = []
|
|
4460
|
-
|
|
4461
|
-
for nodeoffs, (buid, form, edits) in enumerate(nodeedits):
|
|
4462
|
-
|
|
4463
|
-
tags.clear()
|
|
4464
|
-
tagadds.clear()
|
|
4465
|
-
|
|
4466
|
-
for editoffs, (edit, info, _) in enumerate(edits):
|
|
4467
|
-
|
|
4468
|
-
permoffs = (nodeoffs, editoffs)
|
|
4469
|
-
|
|
4470
|
-
if edit == EDIT_NODE_ADD:
|
|
4471
|
-
yield (permoffs, ('node', 'add', form))
|
|
4472
|
-
continue
|
|
4473
|
-
|
|
4474
|
-
if edit == EDIT_NODE_DEL:
|
|
4475
|
-
yield (permoffs, ('node', 'del', form))
|
|
4476
|
-
continue
|
|
4477
|
-
|
|
4478
|
-
if edit == EDIT_PROP_SET:
|
|
4479
|
-
yield (permoffs, ('node', 'prop', 'set', f'{form}:{info[0]}'))
|
|
4480
|
-
continue
|
|
4481
|
-
|
|
4482
|
-
if edit == EDIT_PROP_DEL:
|
|
4483
|
-
yield (permoffs, ('node', 'prop', 'del', f'{form}:{info[0]}'))
|
|
4484
|
-
continue
|
|
4485
|
-
|
|
4486
|
-
if edit == EDIT_TAG_SET:
|
|
4487
|
-
if info[1] != (None, None):
|
|
4488
|
-
tagadds.append(info[0])
|
|
4489
|
-
yield (permoffs, ('node', 'tag', 'add', *info[0].split('.')))
|
|
4490
|
-
else:
|
|
4491
|
-
tags.append((len(info[0]), editoffs, info[0]))
|
|
4492
|
-
continue
|
|
4493
|
-
|
|
4494
|
-
if edit == EDIT_TAG_DEL:
|
|
4495
|
-
yield (permoffs, ('node', 'tag', 'del', *info[0].split('.')))
|
|
4496
|
-
continue
|
|
4497
|
-
|
|
4498
|
-
if edit == EDIT_TAGPROP_SET:
|
|
4499
|
-
yield (permoffs, ('node', 'tag', 'add', *info[0].split('.')))
|
|
4500
|
-
continue
|
|
4501
|
-
|
|
4502
|
-
if edit == EDIT_TAGPROP_DEL:
|
|
4503
|
-
yield (permoffs, ('node', 'tag', 'del', *info[0].split('.')))
|
|
4504
|
-
continue
|
|
4505
|
-
|
|
4506
|
-
if edit == EDIT_NODEDATA_SET:
|
|
4507
|
-
yield (permoffs, ('node', 'data', 'set', info[0]))
|
|
4508
|
-
continue
|
|
4509
|
-
|
|
4510
|
-
if edit == EDIT_NODEDATA_DEL:
|
|
4511
|
-
yield (permoffs, ('node', 'data', 'pop', info[0]))
|
|
4512
|
-
continue
|
|
4513
|
-
|
|
4514
|
-
if edit == EDIT_EDGE_ADD:
|
|
4515
|
-
yield (permoffs, ('node', 'edge', 'add', info[0]))
|
|
4516
|
-
continue
|
|
4517
|
-
|
|
4518
|
-
if edit == EDIT_EDGE_DEL:
|
|
4519
|
-
yield (permoffs, ('node', 'edge', 'del', info[0]))
|
|
4520
|
-
continue
|
|
4521
|
-
|
|
4522
|
-
for _, editoffs, tag in sorted(tags, reverse=True):
|
|
4523
|
-
look = tag + '.'
|
|
4524
|
-
if any([tagadd.startswith(look) for tagadd in tagadds]):
|
|
4525
|
-
continue
|
|
4526
|
-
|
|
4527
|
-
yield ((nodeoffs, editoffs), ('node', 'tag', 'add', *tag.split('.')))
|
|
4528
|
-
tagadds.append(tag)
|