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.

Files changed (87) hide show
  1. synapse/axon.py +19 -16
  2. synapse/cortex.py +203 -15
  3. synapse/exc.py +0 -2
  4. synapse/lib/ast.py +42 -23
  5. synapse/lib/autodoc.py +2 -2
  6. synapse/lib/cache.py +16 -1
  7. synapse/lib/cell.py +5 -5
  8. synapse/lib/httpapi.py +198 -2
  9. synapse/lib/layer.py +5 -2
  10. synapse/lib/modelrev.py +36 -3
  11. synapse/lib/node.py +2 -5
  12. synapse/lib/parser.py +1 -1
  13. synapse/lib/schemas.py +51 -0
  14. synapse/lib/snap.py +10 -0
  15. synapse/lib/storm.lark +24 -4
  16. synapse/lib/storm.py +98 -19
  17. synapse/lib/storm_format.py +1 -1
  18. synapse/lib/stormhttp.py +11 -4
  19. synapse/lib/stormlib/auth.py +16 -2
  20. synapse/lib/stormlib/backup.py +1 -0
  21. synapse/lib/stormlib/basex.py +2 -0
  22. synapse/lib/stormlib/cell.py +7 -0
  23. synapse/lib/stormlib/compression.py +3 -0
  24. synapse/lib/stormlib/cortex.py +1168 -0
  25. synapse/lib/stormlib/ethereum.py +1 -0
  26. synapse/lib/stormlib/graph.py +2 -0
  27. synapse/lib/stormlib/hashes.py +5 -0
  28. synapse/lib/stormlib/hex.py +6 -0
  29. synapse/lib/stormlib/infosec.py +6 -1
  30. synapse/lib/stormlib/ipv6.py +1 -0
  31. synapse/lib/stormlib/iters.py +58 -1
  32. synapse/lib/stormlib/json.py +5 -0
  33. synapse/lib/stormlib/mime.py +1 -0
  34. synapse/lib/stormlib/model.py +19 -3
  35. synapse/lib/stormlib/modelext.py +1 -0
  36. synapse/lib/stormlib/notifications.py +2 -0
  37. synapse/lib/stormlib/pack.py +2 -0
  38. synapse/lib/stormlib/random.py +1 -0
  39. synapse/lib/stormlib/smtp.py +0 -7
  40. synapse/lib/stormlib/stats.py +223 -0
  41. synapse/lib/stormlib/stix.py +8 -0
  42. synapse/lib/stormlib/storm.py +1 -0
  43. synapse/lib/stormlib/version.py +3 -0
  44. synapse/lib/stormlib/xml.py +3 -0
  45. synapse/lib/stormlib/yaml.py +2 -0
  46. synapse/lib/stormtypes.py +250 -170
  47. synapse/lib/trigger.py +180 -4
  48. synapse/lib/types.py +1 -1
  49. synapse/lib/version.py +2 -2
  50. synapse/lib/view.py +55 -6
  51. synapse/models/inet.py +21 -6
  52. synapse/models/orgs.py +48 -2
  53. synapse/models/risk.py +126 -2
  54. synapse/models/syn.py +6 -0
  55. synapse/tests/files/stormpkg/badapidef.yaml +13 -0
  56. synapse/tests/files/stormpkg/storm/modules/apimod +10 -0
  57. synapse/tests/files/stormpkg/testpkg.yaml +23 -0
  58. synapse/tests/test_axon.py +7 -2
  59. synapse/tests/test_cortex.py +231 -35
  60. synapse/tests/test_lib_ast.py +138 -43
  61. synapse/tests/test_lib_autodoc.py +1 -1
  62. synapse/tests/test_lib_modelrev.py +9 -0
  63. synapse/tests/test_lib_node.py +55 -0
  64. synapse/tests/test_lib_storm.py +14 -1
  65. synapse/tests/test_lib_stormhttp.py +65 -6
  66. synapse/tests/test_lib_stormlib_auth.py +12 -3
  67. synapse/tests/test_lib_stormlib_cortex.py +1327 -0
  68. synapse/tests/test_lib_stormlib_iters.py +116 -0
  69. synapse/tests/test_lib_stormlib_stats.py +187 -0
  70. synapse/tests/test_lib_stormlib_storm.py +8 -0
  71. synapse/tests/test_lib_stormsvc.py +24 -1
  72. synapse/tests/test_lib_stormtypes.py +124 -69
  73. synapse/tests/test_lib_trigger.py +315 -0
  74. synapse/tests/test_lib_view.py +1 -2
  75. synapse/tests/test_model_base.py +26 -0
  76. synapse/tests/test_model_inet.py +22 -0
  77. synapse/tests/test_model_orgs.py +28 -0
  78. synapse/tests/test_model_risk.py +73 -0
  79. synapse/tests/test_tools_autodoc.py +25 -0
  80. synapse/tests/test_tools_genpkg.py +9 -3
  81. synapse/tests/utils.py +39 -0
  82. synapse/tools/autodoc.py +42 -2
  83. {synapse-2.152.0.dist-info → synapse-2.154.0.dist-info}/METADATA +2 -2
  84. {synapse-2.152.0.dist-info → synapse-2.154.0.dist-info}/RECORD +87 -79
  85. {synapse-2.152.0.dist-info → synapse-2.154.0.dist-info}/WHEEL +1 -1
  86. {synapse-2.152.0.dist-info → synapse-2.154.0.dist-info}/LICENSE +0 -0
  87. {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='ignore') as fd:
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.hashes as s_stormlib_hashes # NOQA
80
- import synapse.lib.stormlib.random as s_stormlib_random # NOQA
81
- import synapse.lib.stormlib.scrape as s_stormlib_scrape # NOQA
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, topoffs, newlayer=newlayer)
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
@@ -319,5 +319,3 @@ class FatalErr(SynErr):
319
319
  pass
320
320
 
321
321
  class LmdbLock(SynErr): pass
322
-
323
- proxy_admin_mesg = 'Specifying a proxy to the HTTP library requires admin privileges.'
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
- genr = self.run(runt, genr)
218
+ async with contextlib.aclosing(self.run(runt, genr)) as agen:
219
+ async for node, path in agen:
219
220
 
220
- async for node, path in genr:
221
-
222
- runt.tick()
221
+ runt.tick()
223
222
 
224
- yield node, path
223
+ yield node, path
225
224
 
226
- count += 1
225
+ count += 1
227
226
 
228
- limit = runt.getOpt('limit')
229
- if limit is not None and count >= limit:
230
- break
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
- async with runt.getSubRuntime(self.kids[0]) as runt:
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
- return await valu.deref(name)
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 (len(self.kids) == 1 and isinstance(self.kids[0], Const))
3190
- self.constval = self.kids[0].value() if self.isconst and self.kids else None
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 for item in s_base.schedGenr(feedfunc()):
3597
- yield item
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 for item in await subr.emitter():
4387
- yield item
4403
+ async with contextlib.aclosing(await subr.emitter()) as agen:
4404
+ async for item in agen:
4405
+ yield item
4388
4406
  else:
4389
- async for node, path in subr.execute():
4390
- yield node, path
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 one one of the following: {tdata}.'
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')