synapse 2.152.0__py311-none-any.whl → 2.154.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/axon.py +19 -16
- synapse/cortex.py +203 -15
- synapse/exc.py +0 -2
- synapse/lib/ast.py +42 -23
- synapse/lib/autodoc.py +2 -2
- synapse/lib/cache.py +16 -1
- synapse/lib/cell.py +5 -5
- synapse/lib/httpapi.py +198 -2
- synapse/lib/layer.py +5 -2
- synapse/lib/modelrev.py +36 -3
- synapse/lib/node.py +2 -5
- synapse/lib/parser.py +1 -1
- synapse/lib/schemas.py +51 -0
- synapse/lib/snap.py +10 -0
- synapse/lib/storm.lark +24 -4
- synapse/lib/storm.py +98 -19
- synapse/lib/storm_format.py +1 -1
- synapse/lib/stormhttp.py +11 -4
- synapse/lib/stormlib/auth.py +16 -2
- synapse/lib/stormlib/backup.py +1 -0
- synapse/lib/stormlib/basex.py +2 -0
- synapse/lib/stormlib/cell.py +7 -0
- synapse/lib/stormlib/compression.py +3 -0
- synapse/lib/stormlib/cortex.py +1168 -0
- synapse/lib/stormlib/ethereum.py +1 -0
- synapse/lib/stormlib/graph.py +2 -0
- synapse/lib/stormlib/hashes.py +5 -0
- synapse/lib/stormlib/hex.py +6 -0
- synapse/lib/stormlib/infosec.py +6 -1
- synapse/lib/stormlib/ipv6.py +1 -0
- synapse/lib/stormlib/iters.py +58 -1
- synapse/lib/stormlib/json.py +5 -0
- synapse/lib/stormlib/mime.py +1 -0
- synapse/lib/stormlib/model.py +19 -3
- synapse/lib/stormlib/modelext.py +1 -0
- synapse/lib/stormlib/notifications.py +2 -0
- synapse/lib/stormlib/pack.py +2 -0
- synapse/lib/stormlib/random.py +1 -0
- synapse/lib/stormlib/smtp.py +0 -7
- synapse/lib/stormlib/stats.py +223 -0
- synapse/lib/stormlib/stix.py +8 -0
- synapse/lib/stormlib/storm.py +1 -0
- synapse/lib/stormlib/version.py +3 -0
- synapse/lib/stormlib/xml.py +3 -0
- synapse/lib/stormlib/yaml.py +2 -0
- synapse/lib/stormtypes.py +250 -170
- synapse/lib/trigger.py +180 -4
- synapse/lib/types.py +1 -1
- synapse/lib/version.py +2 -2
- synapse/lib/view.py +55 -6
- synapse/models/inet.py +21 -6
- synapse/models/orgs.py +48 -2
- synapse/models/risk.py +126 -2
- synapse/models/syn.py +6 -0
- synapse/tests/files/stormpkg/badapidef.yaml +13 -0
- synapse/tests/files/stormpkg/storm/modules/apimod +10 -0
- synapse/tests/files/stormpkg/testpkg.yaml +23 -0
- synapse/tests/test_axon.py +7 -2
- synapse/tests/test_cortex.py +231 -35
- synapse/tests/test_lib_ast.py +138 -43
- synapse/tests/test_lib_autodoc.py +1 -1
- synapse/tests/test_lib_modelrev.py +9 -0
- synapse/tests/test_lib_node.py +55 -0
- synapse/tests/test_lib_storm.py +14 -1
- synapse/tests/test_lib_stormhttp.py +65 -6
- synapse/tests/test_lib_stormlib_auth.py +12 -3
- synapse/tests/test_lib_stormlib_cortex.py +1327 -0
- synapse/tests/test_lib_stormlib_iters.py +116 -0
- synapse/tests/test_lib_stormlib_stats.py +187 -0
- synapse/tests/test_lib_stormlib_storm.py +8 -0
- synapse/tests/test_lib_stormsvc.py +24 -1
- synapse/tests/test_lib_stormtypes.py +124 -69
- synapse/tests/test_lib_trigger.py +315 -0
- synapse/tests/test_lib_view.py +1 -2
- synapse/tests/test_model_base.py +26 -0
- synapse/tests/test_model_inet.py +22 -0
- synapse/tests/test_model_orgs.py +28 -0
- synapse/tests/test_model_risk.py +73 -0
- synapse/tests/test_tools_autodoc.py +25 -0
- synapse/tests/test_tools_genpkg.py +9 -3
- synapse/tests/utils.py +39 -0
- synapse/tools/autodoc.py +42 -2
- {synapse-2.152.0.dist-info → synapse-2.154.0.dist-info}/METADATA +2 -2
- {synapse-2.152.0.dist-info → synapse-2.154.0.dist-info}/RECORD +87 -79
- {synapse-2.152.0.dist-info → synapse-2.154.0.dist-info}/WHEEL +1 -1
- {synapse-2.152.0.dist-info → synapse-2.154.0.dist-info}/LICENSE +0 -0
- {synapse-2.152.0.dist-info → synapse-2.154.0.dist-info}/top_level.txt +0 -0
synapse/axon.py
CHANGED
|
@@ -677,27 +677,29 @@ class AxonApi(s_cell.CellApi, s_share.Share): # type: ignore
|
|
|
677
677
|
async for item in self.cell.iterMpkFile(sha256):
|
|
678
678
|
yield item
|
|
679
679
|
|
|
680
|
-
async def readlines(self, sha256):
|
|
680
|
+
async def readlines(self, sha256, errors='ignore'):
|
|
681
681
|
'''
|
|
682
682
|
Yield lines from a multi-line text file in the axon.
|
|
683
683
|
|
|
684
684
|
Args:
|
|
685
685
|
sha256 (bytes): The sha256 hash of the file.
|
|
686
|
+
errors (str): Specify how encoding errors should handled.
|
|
686
687
|
|
|
687
688
|
Yields:
|
|
688
689
|
str: Lines of text
|
|
689
690
|
'''
|
|
690
691
|
await self._reqUserAllowed(('axon', 'get'))
|
|
691
|
-
async for item in self.cell.readlines(sha256):
|
|
692
|
+
async for item in self.cell.readlines(sha256, errors=errors):
|
|
692
693
|
yield item
|
|
693
694
|
|
|
694
|
-
async def csvrows(self, sha256, dialect='excel', **fmtparams):
|
|
695
|
+
async def csvrows(self, sha256, dialect='excel', errors='ignore', **fmtparams):
|
|
695
696
|
'''
|
|
696
697
|
Yield CSV rows from a CSV file.
|
|
697
698
|
|
|
698
699
|
Args:
|
|
699
700
|
sha256 (bytes): The sha256 hash of the file.
|
|
700
701
|
dialect (str): The CSV dialect to use.
|
|
702
|
+
errors (str): Specify how encoding errors should handled.
|
|
701
703
|
**fmtparams: The CSV dialect format parameters.
|
|
702
704
|
|
|
703
705
|
Notes:
|
|
@@ -719,21 +721,22 @@ class AxonApi(s_cell.CellApi, s_share.Share): # type: ignore
|
|
|
719
721
|
list: Decoded CSV rows.
|
|
720
722
|
'''
|
|
721
723
|
await self._reqUserAllowed(('axon', 'get'))
|
|
722
|
-
async for item in self.cell.csvrows(sha256, dialect, **fmtparams):
|
|
724
|
+
async for item in self.cell.csvrows(sha256, dialect, errors=errors, **fmtparams):
|
|
723
725
|
yield item
|
|
724
726
|
|
|
725
|
-
async def jsonlines(self, sha256):
|
|
727
|
+
async def jsonlines(self, sha256, errors='ignore'):
|
|
726
728
|
'''
|
|
727
729
|
Yield JSON objects from JSONL (JSON lines) file.
|
|
728
730
|
|
|
729
731
|
Args:
|
|
730
732
|
sha256 (bytes): The sha256 hash of the file.
|
|
733
|
+
errors (str): Specify how encoding errors should handled.
|
|
731
734
|
|
|
732
735
|
Yields:
|
|
733
736
|
object: Decoded JSON objects.
|
|
734
737
|
'''
|
|
735
738
|
await self._reqUserAllowed(('axon', 'get'))
|
|
736
|
-
async for item in self.cell.jsonlines(sha256):
|
|
739
|
+
async for item in self.cell.jsonlines(sha256, errors=errors):
|
|
737
740
|
yield item
|
|
738
741
|
|
|
739
742
|
|
|
@@ -1350,7 +1353,7 @@ class Axon(s_cell.Cell):
|
|
|
1350
1353
|
finally:
|
|
1351
1354
|
link.txfini()
|
|
1352
1355
|
|
|
1353
|
-
async def readlines(self, sha256):
|
|
1356
|
+
async def readlines(self, sha256, errors='ignore'):
|
|
1354
1357
|
|
|
1355
1358
|
sha256 = s_common.uhex(sha256)
|
|
1356
1359
|
await self._reqHas(sha256)
|
|
@@ -1360,7 +1363,7 @@ class Axon(s_cell.Cell):
|
|
|
1360
1363
|
feedtask = None
|
|
1361
1364
|
|
|
1362
1365
|
try:
|
|
1363
|
-
todo = s_common.todo(_spawn_readlines, sock00)
|
|
1366
|
+
todo = s_common.todo(_spawn_readlines, sock00, errors=errors)
|
|
1364
1367
|
async with await s_base.Base.anit() as scope:
|
|
1365
1368
|
|
|
1366
1369
|
scope.schedCoro(s_coro.spawn(todo, log_conf=await self._getSpawnLogConf()))
|
|
@@ -1384,7 +1387,7 @@ class Axon(s_cell.Cell):
|
|
|
1384
1387
|
if feedtask is not None:
|
|
1385
1388
|
await feedtask
|
|
1386
1389
|
|
|
1387
|
-
async def csvrows(self, sha256, dialect='excel', **fmtparams):
|
|
1390
|
+
async def csvrows(self, sha256, dialect='excel', errors='ignore', **fmtparams):
|
|
1388
1391
|
await self._reqHas(sha256)
|
|
1389
1392
|
if dialect not in csv.list_dialects():
|
|
1390
1393
|
raise s_exc.BadArg(mesg=f'Invalid CSV dialect, use one of {csv.list_dialects()}')
|
|
@@ -1394,7 +1397,7 @@ class Axon(s_cell.Cell):
|
|
|
1394
1397
|
feedtask = None
|
|
1395
1398
|
|
|
1396
1399
|
try:
|
|
1397
|
-
todo = s_common.todo(_spawn_readrows, sock00, dialect, fmtparams)
|
|
1400
|
+
todo = s_common.todo(_spawn_readrows, sock00, dialect, fmtparams, errors=errors)
|
|
1398
1401
|
async with await s_base.Base.anit() as scope:
|
|
1399
1402
|
|
|
1400
1403
|
scope.schedCoro(s_coro.spawn(todo, log_conf=await self._getSpawnLogConf()))
|
|
@@ -1418,8 +1421,8 @@ class Axon(s_cell.Cell):
|
|
|
1418
1421
|
if feedtask is not None:
|
|
1419
1422
|
await feedtask
|
|
1420
1423
|
|
|
1421
|
-
async def jsonlines(self, sha256):
|
|
1422
|
-
async for line in self.readlines(sha256):
|
|
1424
|
+
async def jsonlines(self, sha256, errors='ignore'):
|
|
1425
|
+
async for line in self.readlines(sha256, errors=errors):
|
|
1423
1426
|
line = line.strip()
|
|
1424
1427
|
if not line:
|
|
1425
1428
|
continue
|
|
@@ -1745,9 +1748,9 @@ class Axon(s_cell.Cell):
|
|
|
1745
1748
|
'err': err,
|
|
1746
1749
|
}
|
|
1747
1750
|
|
|
1748
|
-
def _spawn_readlines(sock): # pragma: no cover
|
|
1751
|
+
def _spawn_readlines(sock, errors='ignore'): # pragma: no cover
|
|
1749
1752
|
try:
|
|
1750
|
-
with sock.makefile('r') as fd:
|
|
1753
|
+
with sock.makefile('r', errors=errors) as fd:
|
|
1751
1754
|
|
|
1752
1755
|
try:
|
|
1753
1756
|
|
|
@@ -1763,11 +1766,11 @@ def _spawn_readlines(sock): # pragma: no cover
|
|
|
1763
1766
|
mesg = s_common.retnexc(e)
|
|
1764
1767
|
sock.sendall(s_msgpack.en(mesg))
|
|
1765
1768
|
|
|
1766
|
-
def _spawn_readrows(sock, dialect, fmtparams): # pragma: no cover
|
|
1769
|
+
def _spawn_readrows(sock, dialect, fmtparams, errors='ignore'): # pragma: no cover
|
|
1767
1770
|
try:
|
|
1768
1771
|
|
|
1769
1772
|
# Assume utf8 encoding and ignore errors.
|
|
1770
|
-
with sock.makefile('r', errors=
|
|
1773
|
+
with sock.makefile('r', errors=errors) as fd:
|
|
1771
1774
|
|
|
1772
1775
|
try:
|
|
1773
1776
|
|
synapse/cortex.py
CHANGED
|
@@ -36,6 +36,7 @@ import synapse.lib.grammar as s_grammar
|
|
|
36
36
|
import synapse.lib.httpapi as s_httpapi
|
|
37
37
|
import synapse.lib.msgpack as s_msgpack
|
|
38
38
|
import synapse.lib.modules as s_modules
|
|
39
|
+
import synapse.lib.schemas as s_schemas
|
|
39
40
|
import synapse.lib.spooled as s_spooled
|
|
40
41
|
import synapse.lib.version as s_version
|
|
41
42
|
import synapse.lib.urlhelp as s_urlhelp
|
|
@@ -74,11 +75,13 @@ import synapse.lib.stormlib.iters as s_stormlib_iters # NOQA
|
|
|
74
75
|
import synapse.lib.stormlib.macro as s_stormlib_macro
|
|
75
76
|
import synapse.lib.stormlib.model as s_stormlib_model
|
|
76
77
|
import synapse.lib.stormlib.oauth as s_stormlib_oauth # NOQA
|
|
78
|
+
import synapse.lib.stormlib.stats as s_stormlib_stats # NOQA
|
|
77
79
|
import synapse.lib.stormlib.storm as s_stormlib_storm # NOQA
|
|
78
80
|
import synapse.lib.stormlib.backup as s_stormlib_backup # NOQA
|
|
79
|
-
import synapse.lib.stormlib.
|
|
80
|
-
import synapse.lib.stormlib.
|
|
81
|
-
import synapse.lib.stormlib.
|
|
81
|
+
import synapse.lib.stormlib.cortex as s_stormlib_cortex # NOQA
|
|
82
|
+
import synapse.lib.stormlib.hashes as s_stormlib_hashes # NOQA
|
|
83
|
+
import synapse.lib.stormlib.random as s_stormlib_random # NOQA
|
|
84
|
+
import synapse.lib.stormlib.scrape as s_stormlib_scrape # NOQA
|
|
82
85
|
import synapse.lib.stormlib.infosec as s_stormlib_infosec # NOQA
|
|
83
86
|
import synapse.lib.stormlib.project as s_stormlib_project # NOQA
|
|
84
87
|
import synapse.lib.stormlib.version as s_stormlib_version # NOQA
|
|
@@ -574,7 +577,7 @@ class CoreApi(s_cell.CellApi):
|
|
|
574
577
|
|
|
575
578
|
view = self.cell.getView(viewiden, user=self.user)
|
|
576
579
|
if view is None:
|
|
577
|
-
raise s_exc.NoSuchView(iden=viewiden)
|
|
580
|
+
raise s_exc.NoSuchView(mesg=f'No such view iden={viewiden}', iden=viewiden)
|
|
578
581
|
|
|
579
582
|
wlyr = view.layers[0]
|
|
580
583
|
parts = name.split('.')
|
|
@@ -1045,6 +1048,10 @@ class CoreApi(s_cell.CellApi):
|
|
|
1045
1048
|
async for item in self.cell.watchAllUserNotifs(offs=offs):
|
|
1046
1049
|
yield item
|
|
1047
1050
|
|
|
1051
|
+
@s_cell.adminapi()
|
|
1052
|
+
async def getHttpExtApiByPath(self, path):
|
|
1053
|
+
return await self.cell.getHttpExtApiByPath(path)
|
|
1054
|
+
|
|
1048
1055
|
class Cortex(s_oauth.OAuthMixin, s_cell.Cell): # type: ignore
|
|
1049
1056
|
'''
|
|
1050
1057
|
A Cortex implements the synapse hypergraph.
|
|
@@ -1163,6 +1170,7 @@ class Cortex(s_oauth.OAuthMixin, s_cell.Cell): # type: ignore
|
|
|
1163
1170
|
|
|
1164
1171
|
# NOTE: we may not make *any* nexus actions in this method
|
|
1165
1172
|
self.macrodb = self.slab.initdb('storm:macros')
|
|
1173
|
+
self.httpextapidb = self.slab.initdb('http:ext:apis')
|
|
1166
1174
|
|
|
1167
1175
|
if self.inaugural:
|
|
1168
1176
|
await self.cellinfo.set('cortex:version', s_version.version)
|
|
@@ -1239,6 +1247,10 @@ class Cortex(s_oauth.OAuthMixin, s_cell.Cell): # type: ignore
|
|
|
1239
1247
|
self.stormiface_scrape = self.conf.get('storm:interface:scrape')
|
|
1240
1248
|
|
|
1241
1249
|
self._initCortexHttpApi()
|
|
1250
|
+
self._exthttpapis = {} # iden -> adef; relies on cpython ordered dictionary behavior.
|
|
1251
|
+
self._exthttpapiorder = b'exthttpapiorder'
|
|
1252
|
+
self._exthttpapicache = s_cache.FixedCache(self._getHttpExtApiByPath, size=1000)
|
|
1253
|
+
self._initCortexExtHttpApi()
|
|
1242
1254
|
|
|
1243
1255
|
self.model = s_datamodel.Model()
|
|
1244
1256
|
|
|
@@ -1259,10 +1271,14 @@ class Cortex(s_oauth.OAuthMixin, s_cell.Cell): # type: ignore
|
|
|
1259
1271
|
|
|
1260
1272
|
await self._initOAuthManager()
|
|
1261
1273
|
|
|
1274
|
+
stormdmonhive = await self.hive.open(('cortex', 'storm', 'dmons'))
|
|
1275
|
+
self.stormdmonhive = await stormdmonhive.dict()
|
|
1262
1276
|
self.stormdmons = await s_storm.DmonManager.anit(self)
|
|
1263
1277
|
self.onfini(self.stormdmons)
|
|
1278
|
+
|
|
1264
1279
|
self.agenda = await s_agenda.Agenda.anit(self)
|
|
1265
1280
|
self.onfini(self.agenda)
|
|
1281
|
+
|
|
1266
1282
|
await self._initStormGraphs()
|
|
1267
1283
|
|
|
1268
1284
|
self.trigson = self.conf.get('trigger:enable')
|
|
@@ -2221,10 +2237,6 @@ class Cortex(s_oauth.OAuthMixin, s_cell.Cell): # type: ignore
|
|
|
2221
2237
|
|
|
2222
2238
|
async def _initStormDmons(self):
|
|
2223
2239
|
|
|
2224
|
-
node = await self.hive.open(('cortex', 'storm', 'dmons'))
|
|
2225
|
-
|
|
2226
|
-
self.stormdmonhive = await node.dict()
|
|
2227
|
-
|
|
2228
2240
|
for iden, ddef in self.stormdmonhive.items():
|
|
2229
2241
|
try:
|
|
2230
2242
|
await self.runStormDmon(iden, ddef)
|
|
@@ -3291,7 +3303,7 @@ class Cortex(s_oauth.OAuthMixin, s_cell.Cell): # type: ignore
|
|
|
3291
3303
|
|
|
3292
3304
|
view = self.views.get(iden)
|
|
3293
3305
|
if view is None:
|
|
3294
|
-
raise s_exc.NoSuchView(iden=iden)
|
|
3306
|
+
raise s_exc.NoSuchView(mesg=f'No such view {iden=}', iden=iden)
|
|
3295
3307
|
|
|
3296
3308
|
async with await s_queue.Window.anit(maxsize=10000) as wind:
|
|
3297
3309
|
|
|
@@ -3848,11 +3860,11 @@ class Cortex(s_oauth.OAuthMixin, s_cell.Cell): # type: ignore
|
|
|
3848
3860
|
|
|
3849
3861
|
async with await s_base.Base.anit() as base:
|
|
3850
3862
|
|
|
3851
|
-
def addlayr(layr, newlayer=False):
|
|
3863
|
+
def addlayr(layr, newlayer=False, startoffs=topoffs):
|
|
3852
3864
|
'''
|
|
3853
3865
|
A new layer joins the live stream
|
|
3854
3866
|
'''
|
|
3855
|
-
genr = genrfunc(layr,
|
|
3867
|
+
genr = genrfunc(layr, startoffs, newlayer=newlayer)
|
|
3856
3868
|
layrgenrs[layr.iden] = genr
|
|
3857
3869
|
task = base.schedCoro(genr.__anext__())
|
|
3858
3870
|
task.iden = layr.iden
|
|
@@ -3873,6 +3885,8 @@ class Cortex(s_oauth.OAuthMixin, s_cell.Cell): # type: ignore
|
|
|
3873
3885
|
|
|
3874
3886
|
# First, catch up to what was the current offset when we started, guaranteeing order
|
|
3875
3887
|
|
|
3888
|
+
logger.debug(f'_syncNodeEdits() running catch-up sync to offs={topoffs}')
|
|
3889
|
+
|
|
3876
3890
|
genrs = [genrfunc(layr, offsdict.get(layr.iden, 0), endoff=topoffs) for layr in self.layers.values()]
|
|
3877
3891
|
async for item in s_common.merggenr(genrs, lambda x, y: x[0] < y[0]):
|
|
3878
3892
|
yield item
|
|
@@ -3884,6 +3898,10 @@ class Cortex(s_oauth.OAuthMixin, s_cell.Cell): # type: ignore
|
|
|
3884
3898
|
|
|
3885
3899
|
# After we've caught up, read on genrs from all the layers simultaneously
|
|
3886
3900
|
|
|
3901
|
+
logger.debug('_syncNodeEdits() entering into live sync')
|
|
3902
|
+
|
|
3903
|
+
lastoffs = {}
|
|
3904
|
+
|
|
3887
3905
|
todo.clear()
|
|
3888
3906
|
|
|
3889
3907
|
for layr in self.layers.values():
|
|
@@ -3922,6 +3940,8 @@ class Cortex(s_oauth.OAuthMixin, s_cell.Cell): # type: ignore
|
|
|
3922
3940
|
|
|
3923
3941
|
yield result
|
|
3924
3942
|
|
|
3943
|
+
lastoffs[layriden] = result[0]
|
|
3944
|
+
|
|
3925
3945
|
# Re-add a task to wait on the next iteration of the generator
|
|
3926
3946
|
genr = layrgenrs[layriden]
|
|
3927
3947
|
task = base.schedCoro(genr.__anext__())
|
|
@@ -3929,9 +3949,21 @@ class Cortex(s_oauth.OAuthMixin, s_cell.Cell): # type: ignore
|
|
|
3929
3949
|
todo.add(task)
|
|
3930
3950
|
|
|
3931
3951
|
except StopAsyncIteration:
|
|
3952
|
+
|
|
3932
3953
|
# Help out the garbage collector
|
|
3933
3954
|
del layrgenrs[layriden]
|
|
3934
3955
|
|
|
3956
|
+
layr = self.getLayer(iden=layriden)
|
|
3957
|
+
if layr is None or not layr.logedits:
|
|
3958
|
+
logger.debug(f'_syncNodeEdits() removed {layriden=} from sync')
|
|
3959
|
+
continue
|
|
3960
|
+
|
|
3961
|
+
startoffs = lastoffs[layriden] + 1 if layriden in lastoffs else topoffs
|
|
3962
|
+
logger.debug(f'_syncNodeEdits() restarting {layriden=} live sync from offs={startoffs}')
|
|
3963
|
+
addlayr(layr, startoffs=startoffs)
|
|
3964
|
+
|
|
3965
|
+
await self.waitfini(1)
|
|
3966
|
+
|
|
3935
3967
|
async def spliceHistory(self, user):
|
|
3936
3968
|
'''
|
|
3937
3969
|
Yield splices backwards from the end of the nodeedit log.
|
|
@@ -4152,6 +4184,7 @@ class Cortex(s_oauth.OAuthMixin, s_cell.Cell): # type: ignore
|
|
|
4152
4184
|
self.addStormCmd(s_storm.SpliceListCmd)
|
|
4153
4185
|
self.addStormCmd(s_storm.SpliceUndoCmd)
|
|
4154
4186
|
self.addStormCmd(s_stormlib_macro.MacroExecCmd)
|
|
4187
|
+
self.addStormCmd(s_stormlib_stats.StatsCountByCmd)
|
|
4155
4188
|
|
|
4156
4189
|
for cdef in s_stormsvc.stormcmds:
|
|
4157
4190
|
await self._trySetStormCmd(cdef.get('name'), cdef)
|
|
@@ -4171,6 +4204,9 @@ class Cortex(s_oauth.OAuthMixin, s_cell.Cell): # type: ignore
|
|
|
4171
4204
|
for cdef in s_stormlib_model.stormcmds:
|
|
4172
4205
|
await self._trySetStormCmd(cdef.get('name'), cdef)
|
|
4173
4206
|
|
|
4207
|
+
for cdef in s_stormlib_cortex.stormcmds:
|
|
4208
|
+
await self._trySetStormCmd(cdef.get('name'), cdef)
|
|
4209
|
+
|
|
4174
4210
|
async def _initPureStormCmds(self):
|
|
4175
4211
|
oldcmds = []
|
|
4176
4212
|
for name, cdef in self.cmdhive.items():
|
|
@@ -4257,6 +4293,158 @@ class Cortex(s_oauth.OAuthMixin, s_cell.Cell): # type: ignore
|
|
|
4257
4293
|
self.addHttpApi('/api/v1/axon/files/by/sha256/([0-9a-fA-F]{64}$)', CortexAxonHttpBySha256V1, {'cell': self})
|
|
4258
4294
|
self.addHttpApi('/api/v1/axon/files/by/sha256/(.*)', CortexAxonHttpBySha256InvalidV1, {'cell': self})
|
|
4259
4295
|
|
|
4296
|
+
self.addHttpApi('/api/ext/(.*)', s_httpapi.ExtApiHandler, {'cell': self})
|
|
4297
|
+
|
|
4298
|
+
def _initCortexExtHttpApi(self):
|
|
4299
|
+
self._exthttpapis.clear()
|
|
4300
|
+
self._exthttpapicache.clear()
|
|
4301
|
+
|
|
4302
|
+
byts = self.slab.get(self._exthttpapiorder, self.httpextapidb)
|
|
4303
|
+
if byts is None:
|
|
4304
|
+
return
|
|
4305
|
+
|
|
4306
|
+
order = s_msgpack.un(byts)
|
|
4307
|
+
|
|
4308
|
+
for iden in order:
|
|
4309
|
+
byts = self.slab.get(s_common.uhex(iden), self.httpextapidb)
|
|
4310
|
+
if byts is None: # pragma: no cover
|
|
4311
|
+
logger.error(f'Missing HTTP API definition for iden={iden}')
|
|
4312
|
+
continue
|
|
4313
|
+
adef = s_msgpack.un(byts)
|
|
4314
|
+
self._exthttpapis[adef.get('iden')] = adef
|
|
4315
|
+
|
|
4316
|
+
async def addHttpExtApi(self, adef):
|
|
4317
|
+
path = adef.get('path')
|
|
4318
|
+
try:
|
|
4319
|
+
_ = regex.compile(path)
|
|
4320
|
+
except Exception as e:
|
|
4321
|
+
mesg = f'Invalid path for Extended HTTP API - cannot compile regular expression for [{path}] : {e}'
|
|
4322
|
+
raise s_exc.BadArg(mesg=mesg) from None
|
|
4323
|
+
adef['iden'] = s_common.guid()
|
|
4324
|
+
adef['created'] = s_common.now()
|
|
4325
|
+
adef['updated'] = adef['created']
|
|
4326
|
+
adef = s_schemas.reqValidHttpExtAPIConf(adef)
|
|
4327
|
+
return await self._push('http:api:add', adef)
|
|
4328
|
+
|
|
4329
|
+
@s_nexus.Pusher.onPush('http:api:add')
|
|
4330
|
+
async def _addHttpExtApi(self, adef):
|
|
4331
|
+
iden = adef.get('iden')
|
|
4332
|
+
self.slab.put(s_common.uhex(iden), s_msgpack.en(adef), db=self.httpextapidb)
|
|
4333
|
+
|
|
4334
|
+
order = self.slab.get(self._exthttpapiorder, db=self.httpextapidb)
|
|
4335
|
+
if order is None:
|
|
4336
|
+
self.slab.put(self._exthttpapiorder, s_msgpack.en([iden]), db=self.httpextapidb)
|
|
4337
|
+
else:
|
|
4338
|
+
order = s_msgpack.un(order) # type: tuple
|
|
4339
|
+
if iden not in order:
|
|
4340
|
+
# Replay safety
|
|
4341
|
+
order = order + (iden, ) # New handlers go to the end of the list of handlers
|
|
4342
|
+
self.slab.put(self._exthttpapiorder, s_msgpack.en(order), db=self.httpextapidb)
|
|
4343
|
+
|
|
4344
|
+
# Re-initialize the HTTP API list from the index order
|
|
4345
|
+
self._initCortexExtHttpApi()
|
|
4346
|
+
return adef
|
|
4347
|
+
|
|
4348
|
+
@s_nexus.Pusher.onPushAuto('http:api:del')
|
|
4349
|
+
async def delHttpExtApi(self, iden):
|
|
4350
|
+
if s_common.isguid(iden) is False:
|
|
4351
|
+
raise s_exc.BadArg(mesg=f'Must provide an iden. Got {iden}')
|
|
4352
|
+
|
|
4353
|
+
self.slab.pop(s_common.uhex(iden), db=self.httpextapidb)
|
|
4354
|
+
|
|
4355
|
+
byts = self.slab.get(self._exthttpapiorder, self.httpextapidb)
|
|
4356
|
+
order = list(s_msgpack.un(byts))
|
|
4357
|
+
if iden in order:
|
|
4358
|
+
order.remove(iden)
|
|
4359
|
+
self.slab.put(self._exthttpapiorder, s_msgpack.en(order), db=self.httpextapidb)
|
|
4360
|
+
|
|
4361
|
+
self._initCortexExtHttpApi()
|
|
4362
|
+
|
|
4363
|
+
return
|
|
4364
|
+
|
|
4365
|
+
@s_nexus.Pusher.onPushAuto('http:api:mod')
|
|
4366
|
+
async def modHttpExtApi(self, iden, name, valu):
|
|
4367
|
+
# Created, Creator, Updated are not mutable
|
|
4368
|
+
if name in ('name', 'desc', 'runas', 'methods', 'authenticated', 'perms', 'readonly', 'vars'):
|
|
4369
|
+
# Schema takes care of these values
|
|
4370
|
+
pass
|
|
4371
|
+
elif name == 'owner':
|
|
4372
|
+
_obj = await self.getUserDef(valu, packroles=False)
|
|
4373
|
+
if _obj is None:
|
|
4374
|
+
raise s_exc.NoSuchUser(mesg=f'Cannot set owner={valu} on extended httpapi, it does not exist.')
|
|
4375
|
+
elif name == 'view':
|
|
4376
|
+
_obj = self.getView(valu)
|
|
4377
|
+
if _obj is None:
|
|
4378
|
+
raise s_exc.NoSuchView(mesg=f'Cannot set view={valu} on extended httpapi, it does not exist.')
|
|
4379
|
+
elif name == 'path':
|
|
4380
|
+
try:
|
|
4381
|
+
_ = regex.compile(valu)
|
|
4382
|
+
except Exception as e:
|
|
4383
|
+
mesg = f'Invalid path for Extended HTTP API - cannot compile regular expression for [{valu}] : {e}'
|
|
4384
|
+
raise s_exc.BadArg(mesg=mesg) from None
|
|
4385
|
+
else:
|
|
4386
|
+
raise s_exc.BadArg(mesg=f'Cannot set {name=} on extended httpapi')
|
|
4387
|
+
|
|
4388
|
+
byts = self.slab.get(s_common.uhex(iden), db=self.httpextapidb)
|
|
4389
|
+
if byts is None:
|
|
4390
|
+
raise s_exc.NoSuchIden(mesg=f'No http api for {iden=}', iden=iden)
|
|
4391
|
+
|
|
4392
|
+
adef = s_msgpack.un(byts)
|
|
4393
|
+
adef[name] = valu
|
|
4394
|
+
adef['updated'] = s_common.now()
|
|
4395
|
+
adef = s_schemas.reqValidHttpExtAPIConf(adef)
|
|
4396
|
+
self.slab.put(s_common.uhex(iden), s_msgpack.en(adef), db=self.httpextapidb)
|
|
4397
|
+
|
|
4398
|
+
self._initCortexExtHttpApi()
|
|
4399
|
+
|
|
4400
|
+
return adef
|
|
4401
|
+
|
|
4402
|
+
@s_nexus.Pusher.onPushAuto('http:api:indx')
|
|
4403
|
+
async def setHttpApiIndx(self, iden, indx):
|
|
4404
|
+
if indx < 0:
|
|
4405
|
+
raise s_exc.BadArg(mesg=f'indx must be greater than or equal to 0; got {indx}')
|
|
4406
|
+
byts = self.slab.get(self._exthttpapiorder, db=self.httpextapidb)
|
|
4407
|
+
if byts is None:
|
|
4408
|
+
raise s_exc.SynErr(mesg='No Extended HTTP handlers registered. Cannot set order.')
|
|
4409
|
+
|
|
4410
|
+
order = list(s_msgpack.un(byts))
|
|
4411
|
+
|
|
4412
|
+
if iden not in order:
|
|
4413
|
+
raise s_exc.NoSuchIden(mesg=f'Extended HTTP API is not set: {iden}')
|
|
4414
|
+
|
|
4415
|
+
if order.index(iden) == indx:
|
|
4416
|
+
return indx
|
|
4417
|
+
order.remove(iden)
|
|
4418
|
+
# indx values > length of the list end up at the end of the list.
|
|
4419
|
+
order.insert(indx, iden)
|
|
4420
|
+
self.slab.put(self._exthttpapiorder, s_msgpack.en(order), db=self.httpextapidb)
|
|
4421
|
+
self._initCortexExtHttpApi()
|
|
4422
|
+
return order.index(iden)
|
|
4423
|
+
|
|
4424
|
+
async def getHttpExtApis(self):
|
|
4425
|
+
return copy.deepcopy(list(self._exthttpapis.values()))
|
|
4426
|
+
|
|
4427
|
+
async def getHttpExtApi(self, iden):
|
|
4428
|
+
adef = self._exthttpapis.get(iden)
|
|
4429
|
+
if adef is None:
|
|
4430
|
+
raise s_exc.NoSuchIden(mesg=f'No extended http api for {iden=}', iden=iden)
|
|
4431
|
+
return copy.deepcopy(adef)
|
|
4432
|
+
|
|
4433
|
+
async def getHttpExtApiByPath(self, path):
|
|
4434
|
+
iden, args = self._exthttpapicache.get(path)
|
|
4435
|
+
adef = copy.deepcopy(self._exthttpapis.get(iden))
|
|
4436
|
+
return adef, args
|
|
4437
|
+
|
|
4438
|
+
def _getHttpExtApiByPath(self, key):
|
|
4439
|
+
# Cache callback.
|
|
4440
|
+
# Returns (iden, args) or (None, ()) for caching.
|
|
4441
|
+
for iden, adef in self._exthttpapis.items():
|
|
4442
|
+
match = regex.fullmatch(adef.get('path'), key)
|
|
4443
|
+
if match is None:
|
|
4444
|
+
continue
|
|
4445
|
+
return iden, match.groups()
|
|
4446
|
+
return None, ()
|
|
4447
|
+
|
|
4260
4448
|
async def getCellApi(self, link, user, path):
|
|
4261
4449
|
|
|
4262
4450
|
if not path:
|
|
@@ -4560,7 +4748,7 @@ class Cortex(s_oauth.OAuthMixin, s_cell.Cell): # type: ignore
|
|
|
4560
4748
|
async def delView(self, iden):
|
|
4561
4749
|
view = self.views.get(iden)
|
|
4562
4750
|
if view is None:
|
|
4563
|
-
raise s_exc.NoSuchView(iden=iden)
|
|
4751
|
+
raise s_exc.NoSuchView(mesg=f'No such view {iden=}', iden=iden)
|
|
4564
4752
|
|
|
4565
4753
|
return await self._push('view:del', iden)
|
|
4566
4754
|
|
|
@@ -4633,7 +4821,7 @@ class Cortex(s_oauth.OAuthMixin, s_cell.Cell): # type: ignore
|
|
|
4633
4821
|
'''
|
|
4634
4822
|
view = self.getView(iden)
|
|
4635
4823
|
if view is None:
|
|
4636
|
-
raise s_exc.NoSuchView(iden=iden)
|
|
4824
|
+
raise s_exc.NoSuchView(mesg=f'No such view {iden=}', iden=iden)
|
|
4637
4825
|
|
|
4638
4826
|
await view.setLayers(layers)
|
|
4639
4827
|
self._calcViewsByLayer()
|
|
@@ -5432,7 +5620,7 @@ class Cortex(s_oauth.OAuthMixin, s_cell.Cell): # type: ignore
|
|
|
5432
5620
|
|
|
5433
5621
|
view = self.views.get(viewiden)
|
|
5434
5622
|
if view is None:
|
|
5435
|
-
raise s_exc.NoSuchView(iden=viewiden)
|
|
5623
|
+
raise s_exc.NoSuchView(mesg=f'No such view iden={viewiden}', iden=viewiden)
|
|
5436
5624
|
|
|
5437
5625
|
user.confirm(('view', 'read'), gateiden=viewiden)
|
|
5438
5626
|
|
|
@@ -5750,7 +5938,7 @@ class Cortex(s_oauth.OAuthMixin, s_cell.Cell): # type: ignore
|
|
|
5750
5938
|
|
|
5751
5939
|
view = self.getView(viewiden)
|
|
5752
5940
|
if view is None:
|
|
5753
|
-
raise s_exc.NoSuchView(iden=viewiden)
|
|
5941
|
+
raise s_exc.NoSuchView(mesg=f'No such view iden={viewiden}', iden=viewiden)
|
|
5754
5942
|
|
|
5755
5943
|
async with await self.snap(view=view) as snap:
|
|
5756
5944
|
snap.strict = False
|
synapse/exc.py
CHANGED
synapse/lib/ast.py
CHANGED
|
@@ -215,19 +215,18 @@ class Query(AstNode):
|
|
|
215
215
|
if genr is None:
|
|
216
216
|
genr = runt.getInput()
|
|
217
217
|
|
|
218
|
-
|
|
218
|
+
async with contextlib.aclosing(self.run(runt, genr)) as agen:
|
|
219
|
+
async for node, path in agen:
|
|
219
220
|
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
runt.tick()
|
|
221
|
+
runt.tick()
|
|
223
222
|
|
|
224
|
-
|
|
223
|
+
yield node, path
|
|
225
224
|
|
|
226
|
-
|
|
225
|
+
count += 1
|
|
227
226
|
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
227
|
+
limit = runt.getOpt('limit')
|
|
228
|
+
if limit is not None and count >= limit:
|
|
229
|
+
break
|
|
231
230
|
|
|
232
231
|
class Lookup(Query):
|
|
233
232
|
'''
|
|
@@ -594,10 +593,15 @@ class SubQuery(Oper):
|
|
|
594
593
|
async for item in self.kids[0].run(runt, genr):
|
|
595
594
|
yield item
|
|
596
595
|
|
|
597
|
-
async def _compute(self, runt, limit):
|
|
596
|
+
async def _compute(self, runt, path, limit):
|
|
597
|
+
|
|
598
598
|
retn = []
|
|
599
599
|
|
|
600
|
-
|
|
600
|
+
opts = {}
|
|
601
|
+
if path is not None:
|
|
602
|
+
opts['vars'] = path.vars.copy()
|
|
603
|
+
|
|
604
|
+
async with runt.getSubRuntime(self.kids[0], opts=opts) as runt:
|
|
601
605
|
async for valunode, valupath in runt.execute():
|
|
602
606
|
|
|
603
607
|
retn.append(valunode.ndef[1])
|
|
@@ -616,7 +620,7 @@ class SubQuery(Oper):
|
|
|
616
620
|
Its value is the primary property of the node yielded, or the returned value.
|
|
617
621
|
'''
|
|
618
622
|
try:
|
|
619
|
-
retn = await self._compute(runt, 1)
|
|
623
|
+
retn = await self._compute(runt, path, 1)
|
|
620
624
|
|
|
621
625
|
except s_stormctrl.StormReturn as e:
|
|
622
626
|
# a subquery assignment with a return; just use the returned value
|
|
@@ -632,7 +636,7 @@ class SubQuery(Oper):
|
|
|
632
636
|
Use subquery as an array.
|
|
633
637
|
'''
|
|
634
638
|
try:
|
|
635
|
-
return await self._compute(runt, 128)
|
|
639
|
+
return await self._compute(runt, path, 128)
|
|
636
640
|
except s_stormctrl.StormReturn as e:
|
|
637
641
|
# a subquery assignment with a return; just use the returned value
|
|
638
642
|
return e.item
|
|
@@ -2710,6 +2714,10 @@ class TagValuCond(Cond):
|
|
|
2710
2714
|
if isinstance(lnode, VarValue) or not lnode.isconst:
|
|
2711
2715
|
async def cond(node, path):
|
|
2712
2716
|
name = await lnode.compute(runt, path)
|
|
2717
|
+
if '*' in name:
|
|
2718
|
+
mesg = f'Wildcard tag names may not be used in conjunction with tag value comparison: {name}'
|
|
2719
|
+
raise self.addExcInfo(s_exc.StormRuntimeError(mesg=mesg, name=name))
|
|
2720
|
+
|
|
2713
2721
|
valu = await rnode.compute(runt, path)
|
|
2714
2722
|
return cmprctor(valu)(node.tags.get(name))
|
|
2715
2723
|
|
|
@@ -2796,6 +2804,10 @@ class TagPropCond(Cond):
|
|
|
2796
2804
|
tag = await self.kids[0].compute(runt, path)
|
|
2797
2805
|
name = await self.kids[1].compute(runt, path)
|
|
2798
2806
|
|
|
2807
|
+
if '*' in tag:
|
|
2808
|
+
mesg = f'Wildcard tag names may not be used in conjunction with tagprop value comparison: {tag}'
|
|
2809
|
+
raise self.addExcInfo(s_exc.StormRuntimeError(mesg=mesg, name=tag))
|
|
2810
|
+
|
|
2799
2811
|
prop = runt.model.getTagProp(name)
|
|
2800
2812
|
if prop is None:
|
|
2801
2813
|
mesg = f'No such tag property: {name}'
|
|
@@ -3019,7 +3031,8 @@ class VarDeref(Value):
|
|
|
3019
3031
|
name = await self.kids[1].compute(runt, path)
|
|
3020
3032
|
|
|
3021
3033
|
valu = s_stormtypes.fromprim(base, path=path)
|
|
3022
|
-
|
|
3034
|
+
with s_scope.enter({'runt': runt}):
|
|
3035
|
+
return await valu.deref(name)
|
|
3023
3036
|
|
|
3024
3037
|
class FuncCall(Value):
|
|
3025
3038
|
|
|
@@ -3097,7 +3110,7 @@ async def expr_prefix(x, y):
|
|
|
3097
3110
|
return x.startswith(y)
|
|
3098
3111
|
|
|
3099
3112
|
async def expr_re(x, y):
|
|
3100
|
-
if regex.search(await tostr(y), await tostr(x)):
|
|
3113
|
+
if regex.search(await tostr(y), await tostr(x), flags=regex.I):
|
|
3101
3114
|
return True
|
|
3102
3115
|
return False
|
|
3103
3116
|
|
|
@@ -3186,8 +3199,11 @@ class ExprAndNode(Value):
|
|
|
3186
3199
|
class TagName(Value):
|
|
3187
3200
|
|
|
3188
3201
|
def prepare(self):
|
|
3189
|
-
self.isconst = not self.kids or (
|
|
3190
|
-
|
|
3202
|
+
self.isconst = not self.kids or all(isinstance(k, Const) for k in self.kids)
|
|
3203
|
+
if self.isconst and self.kids:
|
|
3204
|
+
self.constval = '.'.join([k.value() for k in self.kids])
|
|
3205
|
+
else:
|
|
3206
|
+
self.constval = None
|
|
3191
3207
|
|
|
3192
3208
|
async def compute(self, runt, path):
|
|
3193
3209
|
|
|
@@ -3593,8 +3609,9 @@ class EditNodeAdd(Edit):
|
|
|
3593
3609
|
async for node, path in genr:
|
|
3594
3610
|
yield node, path
|
|
3595
3611
|
|
|
3596
|
-
async
|
|
3597
|
-
|
|
3612
|
+
async with contextlib.aclosing(s_base.schedGenr(feedfunc())) as agen:
|
|
3613
|
+
async for item in agen:
|
|
3614
|
+
yield item
|
|
3598
3615
|
|
|
3599
3616
|
class EditPropSet(Edit):
|
|
3600
3617
|
|
|
@@ -4383,11 +4400,13 @@ class Function(AstNode):
|
|
|
4383
4400
|
subr.funcscope = True
|
|
4384
4401
|
try:
|
|
4385
4402
|
if self.hasemit:
|
|
4386
|
-
async
|
|
4387
|
-
|
|
4403
|
+
async with contextlib.aclosing(await subr.emitter()) as agen:
|
|
4404
|
+
async for item in agen:
|
|
4405
|
+
yield item
|
|
4388
4406
|
else:
|
|
4389
|
-
async
|
|
4390
|
-
|
|
4407
|
+
async with contextlib.aclosing(subr.execute()) as agen:
|
|
4408
|
+
async for node, path in agen:
|
|
4409
|
+
yield node, path
|
|
4391
4410
|
except s_stormctrl.StormStop:
|
|
4392
4411
|
return
|
|
4393
4412
|
|
synapse/lib/autodoc.py
CHANGED
|
@@ -173,7 +173,7 @@ def getArgLines(rtype):
|
|
|
173
173
|
for obj in atyp:
|
|
174
174
|
assert isinstance(obj, str)
|
|
175
175
|
tdata = ', '.join([f'``{obj}``' for obj in atyp])
|
|
176
|
-
rline = f'The input type may
|
|
176
|
+
rline = f'The input type may be one of the following: {tdata}.'
|
|
177
177
|
line = f' {name}: {desc} {rline}'
|
|
178
178
|
elif isinstance(atyp, dict):
|
|
179
179
|
logger.warning('Fully declarative input types are not yet supported.')
|
|
@@ -287,7 +287,7 @@ def getReturnLines(rtype, known_types=None, types_prefix=None, suffix=None, isst
|
|
|
287
287
|
elif isinstance(rtype, dict):
|
|
288
288
|
returns = rtype.get('returns')
|
|
289
289
|
assert returns is not None, f'Invalid returns for {rtype}'
|
|
290
|
-
name = returns.get('name', 'Returns')
|
|
290
|
+
name = returns.get('name', 'Returns').title()
|
|
291
291
|
|
|
292
292
|
desc = returns.get('desc')
|
|
293
293
|
rettype = returns.get('type')
|