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/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 =
|
|
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=
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
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
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:
|
|
245
|
-
|
|
246
|
-
|
|
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:
|
|
272
|
+
BYNAME.2: /\w+[@?!<>^~=]*[<>=]/
|
|
253
273
|
|
|
254
274
|
_safe_cmpr: BYNAME | CMPR | CMPROTHER | EQSPACE | EQNOSPACE | TRYSET | SETTAGOPER
|
|
255
275
|
|