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/lib/cache.py CHANGED
@@ -179,6 +179,14 @@ def getTagGlobRegx(name):
179
179
  regq = regexizeTagGlob(name)
180
180
  return regex.compile(regq)
181
181
 
182
+ def regexizeEdgeGlob(edge):
183
+ return ReRegex.sub(r'(.+)', regex.escape(edge))
184
+
185
+ @memoize()
186
+ def getEdgeGlobRegx(name):
187
+ regq = regexizeEdgeGlob(name)
188
+ return regex.compile(regq)
189
+
182
190
  class TagGlobs:
183
191
  '''
184
192
  An object that manages multiple tag globs and values for caching.
@@ -191,7 +199,7 @@ class TagGlobs:
191
199
 
192
200
  self.cache.clear()
193
201
 
194
- regx = getTagGlobRegx(name)
202
+ regx = self._getGlobRegex(name)
195
203
 
196
204
  glob = (regx, (name, valu))
197
205
 
@@ -213,5 +221,12 @@ class TagGlobs:
213
221
  def get(self, name):
214
222
  return self.cache.get(name)
215
223
 
224
+ def _getGlobRegex(self, name):
225
+ return getTagGlobRegx(name)
226
+
216
227
  def _getGlobMatches(self, name):
217
228
  return [g[1] for g in self.globs if g[0].fullmatch(name) is not None]
229
+
230
+ class EdgeGlobs(TagGlobs):
231
+ def _getGlobRegex(self, name):
232
+ return getEdgeGlobRegx(name)
synapse/lib/cell.py CHANGED
@@ -633,8 +633,8 @@ class CellApi(s_base.Base):
633
633
  return await self.cell.getRoleDefs()
634
634
 
635
635
  @adminapi()
636
- async def isUserAllowed(self, iden, perm, gateiden=None):
637
- return await self.cell.isUserAllowed(iden, perm, gateiden=gateiden)
636
+ async def isUserAllowed(self, iden, perm, gateiden=None, default=False):
637
+ return await self.cell.isUserAllowed(iden, perm, gateiden=gateiden, default=default)
638
638
 
639
639
  @adminapi()
640
640
  async def isRoleAllowed(self, iden, perm, gateiden=None):
@@ -2232,12 +2232,12 @@ class Cell(s_nexus.Pusher, s_telepath.Aware):
2232
2232
  if remove:
2233
2233
  self.backupstreaming = False
2234
2234
 
2235
- async def isUserAllowed(self, iden, perm, gateiden=None):
2236
- user = self.auth.user(iden)
2235
+ async def isUserAllowed(self, iden, perm, gateiden=None, default=False):
2236
+ user = self.auth.user(iden) # type: s_hiveauth.HiveUser
2237
2237
  if user is None:
2238
2238
  return False
2239
2239
 
2240
- return user.allowed(perm, gateiden=gateiden)
2240
+ return user.allowed(perm, default=default, gateiden=gateiden)
2241
2241
 
2242
2242
  async def isRoleAllowed(self, iden, perm, gateiden=None):
2243
2243
  role = self.auth.role(iden)
synapse/lib/httpapi.py CHANGED
@@ -326,12 +326,13 @@ class HandlerBase:
326
326
  self.web_username = udef.get('name')
327
327
  return self.web_useriden
328
328
 
329
- async def allowed(self, perm, gateiden=None):
329
+ async def allowed(self, perm, default=False, gateiden=None):
330
330
  '''
331
331
  Check if the authenticated user has the given permission.
332
332
 
333
333
  Args:
334
334
  perm (tuple): The permission tuple to check.
335
+ default (boolean): The default value for the permission.
335
336
  gateiden (str): The gateiden to check the permission against.
336
337
 
337
338
  Notes:
@@ -347,11 +348,16 @@ class HandlerBase:
347
348
  self.sendAuthRequired()
348
349
  return False
349
350
 
350
- if await authcell.isUserAllowed(useriden, perm, gateiden=gateiden):
351
+ if await authcell.isUserAllowed(useriden, perm, gateiden=gateiden, default=default):
351
352
  return True
352
353
 
353
354
  self.set_status(403)
355
+
354
356
  mesg = f'User ({self.web_username}) must have permission {".".join(perm)}'
357
+ if default:
358
+ mesg = f'User ({self.web_username}) is denied the permission {".".join(perm)}'
359
+ if gateiden:
360
+ mesg = f'{mesg} on object {gateiden}'
355
361
  self.sendRestErr('AuthDeny', mesg)
356
362
  return False
357
363
 
@@ -1240,3 +1246,193 @@ class CoreInfoV1(Handler):
1240
1246
 
1241
1247
  resp = await self.cell.getCoreInfoV2()
1242
1248
  return self.sendRestRetn(resp)
1249
+
1250
+ class ExtApiHandler(StormHandler):
1251
+ '''
1252
+ /api/ext/.*
1253
+ '''
1254
+
1255
+ storm_prefix = 'init { $request = $lib.cortex.httpapi.response($_http_request_info) }'
1256
+
1257
+ # Disables the etag header from being computed and set. It is too much magic for
1258
+ # a user defined API to utilize.
1259
+ def compute_etag(self):
1260
+ return None
1261
+
1262
+ def set_default_headers(self):
1263
+ self.clear_header('Server')
1264
+
1265
+ async def get(self, path):
1266
+ return await self._runHttpExt('get', path)
1267
+
1268
+ async def head(self, path):
1269
+ return await self._runHttpExt('head', path)
1270
+
1271
+ async def post(self, path):
1272
+ return await self._runHttpExt('post', path)
1273
+
1274
+ async def put(self, path):
1275
+ return await self._runHttpExt('put', path)
1276
+
1277
+ async def delete(self, path):
1278
+ return await self._runHttpExt('delete', path)
1279
+
1280
+ async def patch(self, path):
1281
+ return await self._runHttpExt('patch', path)
1282
+
1283
+ async def options(self, path):
1284
+ return await self._runHttpExt('options', path)
1285
+
1286
+ async def _runHttpExt(self, meth, path):
1287
+ core = self.getCore()
1288
+ adef, args = await core.getHttpExtApiByPath(path)
1289
+ if adef is None:
1290
+ self.set_status(404)
1291
+ self.sendRestErr('NoSuchPath', f'No Extended HTTP API endpoint matches {path}')
1292
+ return await self.finish()
1293
+
1294
+ requester = ''
1295
+ iden = adef.get("iden")
1296
+ useriden = adef.get('owner')
1297
+
1298
+ if adef.get('authenticated'):
1299
+
1300
+ requester = await self.useriden()
1301
+
1302
+ if requester is None:
1303
+ await self.reqAuthUser()
1304
+ return
1305
+
1306
+ for pdef in adef.get('perms'):
1307
+ if not await self.allowed(pdef.get('perm'), default=pdef.get('default')):
1308
+ return
1309
+
1310
+ if adef.get('runas') == 'user':
1311
+ useriden = requester
1312
+
1313
+ storm = adef['methods'].get(meth)
1314
+ if storm is None:
1315
+ self.set_status(405)
1316
+ meths = [meth.upper() for meth in adef.get('methods')]
1317
+ self.set_header('Allowed', ', '.join(meths))
1318
+ mesg = f'Extended HTTP API {iden} has no method for {meth.upper()}.'
1319
+ if meths:
1320
+ mesg = f'{mesg} Supports {", ".join(meths)}.'
1321
+ self.sendRestErr('NeedConfValu', mesg)
1322
+ return await self.finish()
1323
+
1324
+ # We flatten the request headers and parameters into a flat key/valu map.
1325
+ # The first instance of a given key wins.
1326
+ request_headers = {}
1327
+ for key, valu in self.request.headers.get_all():
1328
+ request_headers.setdefault(key.lower(), valu)
1329
+
1330
+ params = {}
1331
+ for key, valus in self.request.query_arguments.items():
1332
+ for valu in valus:
1333
+ params.setdefault(key, valu.decode())
1334
+
1335
+ info = {
1336
+ 'uri': self.request.uri,
1337
+ 'body': self.request.body,
1338
+ 'iden': iden,
1339
+ 'path': path,
1340
+ 'user': requester,
1341
+ 'method': self.request.method,
1342
+ 'params': params,
1343
+ 'headers': request_headers,
1344
+ 'args': args,
1345
+ 'client': self.request.remote_ip,
1346
+ }
1347
+
1348
+ varz = adef.get('vars')
1349
+ varz['_http_request_info'] = info
1350
+
1351
+ opts = {
1352
+ 'readonly': adef.get('readonly'),
1353
+ 'show': (
1354
+ 'http:resp:body',
1355
+ 'http:resp:code',
1356
+ 'http:resp:headers',
1357
+ ),
1358
+ 'user': useriden,
1359
+ 'vars': varz,
1360
+ 'view': adef.get('view'),
1361
+ }
1362
+
1363
+ query = '\n'.join((self.storm_prefix, storm))
1364
+
1365
+ rcode = False
1366
+ rbody = False
1367
+
1368
+ try:
1369
+ async for mtyp, info in core.storm(query, opts=opts):
1370
+ if mtyp == 'http:resp:code':
1371
+ if rbody:
1372
+ # We've already flushed() the stream at this point, so we cannot
1373
+ # change the status code or the response headers. We just have to
1374
+ # log the error and move along.
1375
+ mesg = f'Extended HTTP API {iden} tried to set code after sending body.'
1376
+ logger.error(mesg)
1377
+ continue
1378
+
1379
+ rcode = True
1380
+ self.set_status(info['code'])
1381
+
1382
+ elif mtyp == 'http:resp:headers':
1383
+ if rbody:
1384
+ # We've already flushed() the stream at this point, so we cannot
1385
+ # change the status code or the response headers. We just have to
1386
+ # log the error and move along.
1387
+ mesg = f'Extended HTTP API {iden} tried to set headers after sending body.'
1388
+ logger.error(mesg)
1389
+ continue
1390
+ for hkey, hval in info['headers'].items():
1391
+ self.set_header(hkey, hval)
1392
+
1393
+ elif mtyp == 'http:resp:body':
1394
+ if not rcode:
1395
+ self.clear()
1396
+ self.set_status(500)
1397
+ self.sendRestErr('StormRuntimeError',
1398
+ f'Extended HTTP API {iden} must set status code before sending body.')
1399
+ return await self.finish()
1400
+ rbody = True
1401
+ body = info['body']
1402
+ self.write(body)
1403
+ await self.flush()
1404
+
1405
+ elif mtyp == 'err':
1406
+ errname, erfo = info
1407
+ mesg = f'Error executing Extended HTTP API {iden}: {errname} {erfo.get("mesg")}'
1408
+ logger.error(mesg)
1409
+ if rbody:
1410
+ # We've already flushed() the stream at this point, so we cannot
1411
+ # change the status code or the response headers. We just have to
1412
+ # log the error and move along.
1413
+ continue
1414
+
1415
+ # Since we haven't flushed the body yet, we can clear the handler
1416
+ # and send the error the user.
1417
+ self.clear()
1418
+ self.set_status(500)
1419
+ self.sendRestErr(errname, erfo.get('mesg'))
1420
+ rcode = True
1421
+ rbody = True
1422
+
1423
+ except Exception as e:
1424
+ rcode = True
1425
+ enfo = s_common.err(e)
1426
+ logger.exception(f'Extended HTTP API {iden} encountered fatal error: {enfo[1].get("mesg")}')
1427
+ if rbody is False:
1428
+ self.clear()
1429
+ self.set_status(500)
1430
+ self.sendRestErr(enfo[0],
1431
+ f'Extended HTTP API {iden} encountered fatal error: {enfo[1].get("mesg")}')
1432
+
1433
+ if rcode is False:
1434
+ self.clear()
1435
+ self.set_status(500)
1436
+ self.sendRestErr('StormRuntimeError', f'Extended HTTP API {iden} never set status code.')
1437
+
1438
+ await self.finish()
synapse/lib/layer.py CHANGED
@@ -105,6 +105,8 @@ reqValidLdef = s_config.getJsValidator({
105
105
  'required': ['iden', 'creator', 'lockmemory'],
106
106
  })
107
107
 
108
+ WINDOW_MAXSIZE = 10_000
109
+
108
110
  class LayerApi(s_cell.CellApi):
109
111
 
110
112
  async def __anit__(self, core, link, user, layr):
@@ -123,6 +125,7 @@ class LayerApi(s_cell.CellApi):
123
125
  await self._reqUserAllowed(self.liftperm)
124
126
  async for item in self.layr.iterLayerNodeEdits():
125
127
  yield item
128
+ await asyncio.sleep(0)
126
129
 
127
130
  @s_cell.adminapi()
128
131
  async def saveNodeEdits(self, edits, meta):
@@ -509,7 +512,7 @@ class StorType:
509
512
 
510
513
  async def _liftRegx(self, liftby, valu, reverse=False):
511
514
 
512
- regx = regex.compile(valu)
515
+ regx = regex.compile(valu, flags=regex.I)
513
516
 
514
517
  abrvlen = liftby.abrvlen
515
518
  isarray = isinstance(liftby, IndxByPropArray)
@@ -4425,7 +4428,7 @@ class Layer(s_nexus.Pusher):
4425
4428
  if not self.logedits:
4426
4429
  raise s_exc.BadConfValu(mesg='Layer logging must be enabled for getting nodeedits')
4427
4430
 
4428
- async with await s_queue.Window.anit(maxsize=10000) as wind:
4431
+ async with await s_queue.Window.anit(maxsize=WINDOW_MAXSIZE) as wind:
4429
4432
 
4430
4433
  async def fini():
4431
4434
  self.windows.remove(wind)
synapse/lib/modelrev.py CHANGED
@@ -8,7 +8,7 @@ import synapse.lib.layer as s_layer
8
8
 
9
9
  logger = logging.getLogger(__name__)
10
10
 
11
- maxvers = (0, 2, 21)
11
+ maxvers = (0, 2, 22)
12
12
 
13
13
  class ModelRev:
14
14
 
@@ -35,6 +35,7 @@ class ModelRev:
35
35
  ((0, 2, 19), self.revModel_0_2_19),
36
36
  ((0, 2, 20), self.revModel_0_2_20),
37
37
  ((0, 2, 21), self.revModel_0_2_21),
38
+ ((0, 2, 22), self.revModel_0_2_22),
38
39
  )
39
40
 
40
41
  async def _uniqSortArray(self, todoprops, layers):
@@ -725,6 +726,9 @@ class ModelRev:
725
726
  await self._normPropValu(layers, 'risk:vuln:name')
726
727
  await self._propToForm(layers, 'risk:vuln:name', 'risk:vulnname')
727
728
 
729
+ async def revModel_0_2_22(self, layers):
730
+ await self._normFormSubs(layers, 'inet:ipv4', cmprvalu='100.64.0.0/10')
731
+
728
732
  async def runStorm(self, text, opts=None):
729
733
  '''
730
734
  Run storm code in a schedcoro and log the output messages.
@@ -880,7 +884,7 @@ class ModelRev:
880
884
  if nodeedits:
881
885
  await save()
882
886
 
883
- async def _normFormSubs(self, layers, formname, liftprop=None):
887
+ async def _normFormSubs(self, layers, formname, liftprop=None, cmprvalu=s_common.novalu, cmpr='='):
884
888
 
885
889
  # NOTE: this API may be used to re-normalize subs but *not* to change their storage types
886
890
  # and will *not* auto-populate linked forms from subs which are form types.
@@ -898,7 +902,36 @@ class ModelRev:
898
902
  await layr.storNodeEdits(nodeedits, meta)
899
903
  nodeedits.clear()
900
904
 
901
- async for lkey, buid, sode in layr.liftByProp(form.name, liftprop):
905
+ if cmprvalu is s_common.novalu:
906
+ # This is for lifts such as:
907
+ # <formname>
908
+ # <formname>:<liftprop>
909
+ # E.g.:
910
+ # inet:ipv4
911
+ # inet:ipv4:type
912
+ genr = layr.liftByProp(form.name, liftprop)
913
+
914
+ elif liftprop is None:
915
+ # This is for lifts such as:
916
+ # <formname><cmpr><cmprvalu>
917
+ # E.g.:
918
+ # inet:ipv4=1.2.3.4
919
+
920
+ # Don't norm cmprvalu first because it may not be normable
921
+ cmprvals = form.type.getStorCmprs(cmpr, cmprvalu)
922
+ genr = layr.liftByFormValu(form.name, cmprvals)
923
+
924
+ else: # liftprop is not None # pragma: no cover
925
+ # This is for lifts such as:
926
+ # <formname>:<liftprop><cmpr><cmprvalu>
927
+ # E.g.:
928
+ # inet:ipv4:type=private
929
+
930
+ # Don't norm cmprvalu first because it may not be normable
931
+ cmprvals = form.type.getStorCmprs(cmpr, cmprvalu)
932
+ genr = layr.liftByPropValu(form.name, liftprop, cmprvals)
933
+
934
+ async for _, buid, sode in genr:
902
935
 
903
936
  sodevalu = sode.get('valu')
904
937
  if sodevalu is None: # pragma: no cover
synapse/lib/node.py CHANGED
@@ -485,10 +485,6 @@ class Node:
485
485
  raise s_exc.IsRuntForm(mesg='Cannot delete tags from runt nodes.',
486
486
  form=self.form.full, tag=tag)
487
487
 
488
- curv = self.tags.get(name, s_common.novalu)
489
- if curv is s_common.novalu:
490
- return ()
491
-
492
488
  pref = name + '.'
493
489
 
494
490
  todel = [(len(t), t) for t in self.tags.keys() if t.startswith(pref)]
@@ -529,7 +525,8 @@ class Node:
529
525
  edits.append((s_layer.EDIT_TAG_DEL, (subtag, None), ()))
530
526
 
531
527
  edits.extend(self._getTagPropDel(name))
532
- edits.append((s_layer.EDIT_TAG_DEL, (name, None), ()))
528
+ if self.getTag(name, defval=s_common.novalu) is not s_common.novalu:
529
+ edits.append((s_layer.EDIT_TAG_DEL, (name, None), ()))
533
530
 
534
531
  return edits
535
532
 
synapse/lib/parser.py CHANGED
@@ -125,7 +125,7 @@ terminalEnglishMap = {
125
125
  '_LEFTPIVOT': '<-',
126
126
  '_LPARNOSPACE': '(',
127
127
  '_MATCHHASH': '#',
128
- '_MATCHHASHSPACE': '#',
128
+ '_MATCHHASHWILD': '#',
129
129
  '_RETURN': 'return',
130
130
  '_REVERSE': 'reverse',
131
131
  '_RIGHTJOIN': '-+>',
synapse/lib/schemas.py ADDED
@@ -0,0 +1,51 @@
1
+ import synapse.lib.config as s_config
2
+
3
+
4
+ _HttpExtAPIConfSchema = {
5
+ 'type': 'object',
6
+ 'properties': {
7
+ 'iden': {'type': 'string', 'pattern': s_config.re_iden},
8
+ 'methods': {
9
+ 'type': 'object',
10
+ 'default': {},
11
+ 'properties': {
12
+ 'get': {'type': 'string', 'minLen': 1},
13
+ 'head': {'type': 'string', 'minLen': 1},
14
+ 'post': {'type': 'string', 'minLen': 1},
15
+ 'put': {'type': 'string', 'minLen': 1},
16
+ 'delete': {'type': 'string', 'minLen': 1},
17
+ 'patch': {'type': 'string', 'minLen': 1},
18
+ 'options': {'type': 'string', 'minLen': 1},
19
+ },
20
+ 'additionalProperties': False,
21
+ },
22
+ 'authenticated': {'type': 'boolean', 'default': True},
23
+ 'name': {'type': 'string', 'default': ''},
24
+ 'desc': {'type': 'string', 'default': ''},
25
+ 'path': {'type': 'string', 'minlen': 1},
26
+ 'view': {'type': 'string', 'pattern': s_config.re_iden},
27
+ 'runas': {'type': 'string', 'pattern': '^(owner|user)$'},
28
+ 'owner': {'type': 'string', 'pattern': s_config.re_iden},
29
+ 'creator': {'type': 'string', 'pattern': s_config.re_iden},
30
+ 'created': {'type': 'integer', 'minimum': 0},
31
+ 'updated': {'type': 'integer', 'minimum': 0},
32
+ 'readonly': {'type': 'boolean', 'default': False},
33
+ 'perms': {
34
+ 'type': 'array',
35
+ 'items': {
36
+ 'type': 'object',
37
+ 'properties': {
38
+ 'perm': {'type': 'array', 'items': {'type': 'string', 'minlen': 1}},
39
+ 'default': {'type': 'boolean', 'default': False},
40
+ }
41
+ },
42
+ 'default': [],
43
+ },
44
+ 'vars': {'type': 'object', 'default': {}}
45
+
46
+ },
47
+ 'additionalProperties': False
48
+ }
49
+
50
+
51
+ reqValidHttpExtAPIConf = s_config.getJsValidator(_HttpExtAPIConfSchema)
synapse/lib/snap.py CHANGED
@@ -1147,6 +1147,16 @@ class Snap(s_base.Base):
1147
1147
  node.nodedata.pop(name, None)
1148
1148
  continue
1149
1149
 
1150
+ if etyp == s_layer.EDIT_EDGE_ADD:
1151
+ verb, n2iden = parms
1152
+ n2 = await self.getNodeByBuid(s_common.uhex(n2iden))
1153
+ callbacks.append((self.view.runEdgeAdd, (node, verb, n2), {}))
1154
+
1155
+ if etyp == s_layer.EDIT_EDGE_DEL:
1156
+ verb, n2iden = parms
1157
+ n2 = await self.getNodeByBuid(s_common.uhex(n2iden))
1158
+ callbacks.append((self.view.runEdgeDel, (node, verb, n2), {}))
1159
+
1150
1160
  [await func(*args, **kwargs) for (func, args, kwargs) in callbacks]
1151
1161
 
1152
1162
  if actualedits:
synapse/lib/storm.lark CHANGED
@@ -241,15 +241,35 @@ _tagsegs: TAGSEGNOVAR ( "." (TAGSEGNOVAR | "$" varvalue))*
241
241
  TAGSEGNOVAR: /[\w]+/
242
242
 
243
243
  // A tag name with asterisks or $var as segment after the first segment
244
- tagmatch: (_MATCHHASH | _MATCHHASHSPACE) (_varvalu | _wildtagsegs)
245
- _MATCHHASH.3: /(?<!\s)#/
246
- _MATCHHASHSPACE.3: /(?<=\s)#/
244
+ tagmatch: _MATCHHASH (_varvalu | _tagsegs)
245
+ | _MATCHHASHWILD _wildtagsegs
246
+
247
+ _MATCHHASH.3: /#/
248
+
249
+ // A tagmatch with wildcards cannot be followed by a cmpr
250
+ _MATCHHASHWILD.3: /\#
251
+ (?=
252
+ (?:[\w.$]|"(?:[^"\\]|\\.)*"|'[^']*'(?!'))* # match tag parts
253
+ \* # match a *
254
+ (?! # negative lookahead for cmpr
255
+ (?:[\w*.$:]|"(?:[^"\\]|\\.)*"|'[^']*'(?!'))* # continue matching tag parts
256
+ \s* # match any whitespace before cmpr
257
+ (?! # avoid matching EDGEN2INIT as a cmpr
258
+ <\(
259
+ (?=(?>(?<N2MATCHRECUR>\(((?>[^()"'`]+|(?&N2MATCHRECUR)|`(?:[^`\\]|\\.)*`|"(?:[^"\\]|\\.)*"|'''.*?'''|'[^']*'(?!'))*)\))|'''.*?'''|`(?:[^`\\]|\\.)*`|"(?:[^"\\]|\\.)*"|'[^']*'(?!')|[^)])*\)-
260
+ )
261
+ )
262
+ [@?!<>^~=]+ # match any cmprs (regex fail)
263
+ )
264
+ )
265
+ /x
266
+
247
267
  _wildtagsegs: WILDTAGSEGNOVAR ( "." (WILDTAGSEGNOVAR | "$" varvalue))*
248
268
  WILDTAGSEGNOVAR: /[\w*]+/
249
269
 
250
270
  // A comparison operator
251
271
  _cmpr: "*" BYNAME | CMPR | CMPROTHER | EQSPACE | EQNOSPACE | TRYSET | SETTAGOPER
252
- BYNAME.2: /[^=\s]+=/
272
+ BYNAME.2: /\w+[@?!<>^~=]*[<>=]/
253
273
 
254
274
  _safe_cmpr: BYNAME | CMPR | CMPROTHER | EQSPACE | EQNOSPACE | TRYSET | SETTAGOPER
255
275