synapse 2.196.0__py311-none-any.whl → 2.198.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 (61) hide show
  1. synapse/axon.py +3 -0
  2. synapse/common.py +3 -0
  3. synapse/cortex.py +13 -11
  4. synapse/cryotank.py +2 -2
  5. synapse/lib/aha.py +3 -0
  6. synapse/lib/ast.py +277 -165
  7. synapse/lib/auth.py +39 -11
  8. synapse/lib/cell.py +24 -6
  9. synapse/lib/config.py +3 -3
  10. synapse/lib/hive.py +2 -1
  11. synapse/lib/hiveauth.py +10 -1
  12. synapse/lib/jsonstor.py +6 -5
  13. synapse/lib/layer.py +6 -5
  14. synapse/lib/multislabseqn.py +2 -2
  15. synapse/lib/node.py +10 -4
  16. synapse/lib/parser.py +46 -21
  17. synapse/lib/schemas.py +491 -1
  18. synapse/lib/snap.py +68 -26
  19. synapse/lib/storm.lark +13 -11
  20. synapse/lib/storm.py +13 -395
  21. synapse/lib/storm_format.py +3 -2
  22. synapse/lib/stormlib/graph.py +0 -61
  23. synapse/lib/stormlib/index.py +52 -0
  24. synapse/lib/stormtypes.py +16 -5
  25. synapse/lib/task.py +13 -2
  26. synapse/lib/urlhelp.py +1 -1
  27. synapse/lib/version.py +2 -2
  28. synapse/models/doc.py +62 -0
  29. synapse/models/infotech.py +18 -0
  30. synapse/models/orgs.py +6 -4
  31. synapse/models/risk.py +9 -0
  32. synapse/models/syn.py +18 -2
  33. synapse/tests/files/stormpkg/badendpoints.yaml +7 -0
  34. synapse/tests/files/stormpkg/testpkg.yaml +8 -0
  35. synapse/tests/test_cortex.py +108 -0
  36. synapse/tests/test_datamodel.py +7 -0
  37. synapse/tests/test_lib_aha.py +12 -42
  38. synapse/tests/test_lib_ast.py +57 -0
  39. synapse/tests/test_lib_auth.py +143 -2
  40. synapse/tests/test_lib_boss.py +15 -6
  41. synapse/tests/test_lib_cell.py +43 -0
  42. synapse/tests/test_lib_grammar.py +54 -2
  43. synapse/tests/test_lib_lmdbslab.py +24 -0
  44. synapse/tests/test_lib_storm.py +20 -0
  45. synapse/tests/test_lib_stormlib_index.py +39 -0
  46. synapse/tests/test_lib_stormlib_macro.py +3 -3
  47. synapse/tests/test_lib_stormtypes.py +14 -2
  48. synapse/tests/test_lib_task.py +31 -13
  49. synapse/tests/test_model_doc.py +38 -0
  50. synapse/tests/test_model_infotech.py +13 -0
  51. synapse/tests/test_model_orgs.py +7 -0
  52. synapse/tests/test_model_risk.py +6 -0
  53. synapse/tests/test_model_syn.py +58 -0
  54. synapse/tests/test_tools_genpkg.py +10 -0
  55. synapse/tools/genpkg.py +2 -2
  56. synapse/tools/hive/load.py +1 -0
  57. {synapse-2.196.0.dist-info → synapse-2.198.0.dist-info}/METADATA +1 -1
  58. {synapse-2.196.0.dist-info → synapse-2.198.0.dist-info}/RECORD +61 -58
  59. {synapse-2.196.0.dist-info → synapse-2.198.0.dist-info}/LICENSE +0 -0
  60. {synapse-2.196.0.dist-info → synapse-2.198.0.dist-info}/WHEEL +0 -0
  61. {synapse-2.196.0.dist-info → synapse-2.198.0.dist-info}/top_level.txt +0 -0
synapse/lib/ast.py CHANGED
@@ -62,7 +62,7 @@ class AstNode:
62
62
 
63
63
  def getPosInfo(self):
64
64
  return {
65
- 'hash': hashlib.md5(self.astinfo.text.encode(), usedforsecurity=False).hexdigest(),
65
+ 'hash': s_common.queryhash(self.astinfo.text),
66
66
  'lines': (self.astinfo.sline, self.astinfo.eline),
67
67
  'columns': (self.astinfo.scol, self.astinfo.ecol),
68
68
  'offsets': (self.astinfo.soff, self.astinfo.eoff),
@@ -699,7 +699,92 @@ class SubGraph:
699
699
  yield node, path
700
700
 
701
701
  class Oper(AstNode):
702
- pass
702
+
703
+ async def yieldFromValu(self, runt, valu, vkid):
704
+
705
+ viewiden = runt.snap.view.iden
706
+
707
+ # there is nothing in None... ;)
708
+ if valu is None:
709
+ return
710
+
711
+ # a little DWIM on what we get back...
712
+ # ( most common case will be stormtypes libs agenr -> iden|buid )
713
+ # buid list -> nodes
714
+ if isinstance(valu, bytes):
715
+ node = await runt.snap.getNodeByBuid(valu)
716
+ if node is not None:
717
+ yield node
718
+
719
+ return
720
+
721
+ # iden list -> nodes
722
+ if isinstance(valu, str):
723
+ try:
724
+ buid = s_common.uhex(valu)
725
+ except binascii.Error:
726
+ mesg = 'Yield string must be iden in hexdecimal. Got: %r' % (valu,)
727
+ raise vkid.addExcInfo(s_exc.BadLiftValu(mesg=mesg))
728
+
729
+ node = await runt.snap.getNodeByBuid(buid)
730
+ if node is not None:
731
+ yield node
732
+
733
+ return
734
+
735
+ if isinstance(valu, types.AsyncGeneratorType):
736
+ try:
737
+ async for item in valu:
738
+ async for node in self.yieldFromValu(runt, item, vkid):
739
+ yield node
740
+ finally:
741
+ await valu.aclose()
742
+ return
743
+
744
+ if isinstance(valu, types.GeneratorType):
745
+ try:
746
+ for item in valu:
747
+ async for node in self.yieldFromValu(runt, item, vkid):
748
+ yield node
749
+ finally:
750
+ valu.close()
751
+ return
752
+
753
+ if isinstance(valu, (list, tuple, set)):
754
+ for item in valu:
755
+ async for node in self.yieldFromValu(runt, item, vkid):
756
+ yield node
757
+ return
758
+
759
+ if isinstance(valu, s_stormtypes.Node):
760
+ valu = valu.valu
761
+ if valu.snap.view.iden != viewiden:
762
+ mesg = f'Node is not from the current view. Node {valu.iden()} is from {valu.snap.view.iden} expected {viewiden}'
763
+ raise vkid.addExcInfo(s_exc.BadLiftValu(mesg=mesg))
764
+ yield valu
765
+ return
766
+
767
+ if isinstance(valu, s_node.Node):
768
+ if valu.snap.view.iden != viewiden:
769
+ mesg = f'Node is not from the current view. Node {valu.iden()} is from {valu.snap.view.iden} expected {viewiden}'
770
+ raise vkid.addExcInfo(s_exc.BadLiftValu(mesg=mesg))
771
+ yield valu
772
+ return
773
+
774
+ if isinstance(valu, (s_stormtypes.List, s_stormtypes.Set)):
775
+ for item in valu.valu:
776
+ async for node in self.yieldFromValu(runt, item, vkid):
777
+ yield node
778
+ return
779
+
780
+ if isinstance(valu, s_stormtypes.Prim):
781
+ async with contextlib.aclosing(valu.nodes()) as genr:
782
+ async for node in genr:
783
+ if node.snap.view.iden != viewiden:
784
+ mesg = f'Node is not from the current view. Node {node.iden()} is from {node.snap.view.iden} expected {viewiden}'
785
+ raise vkid.addExcInfo(s_exc.BadLiftValu(mesg=mesg))
786
+ yield node
787
+ return
703
788
 
704
789
  class SubQuery(Oper):
705
790
 
@@ -1534,106 +1619,21 @@ class YieldValu(Oper):
1534
1619
  async def run(self, runt, genr):
1535
1620
 
1536
1621
  node = None
1622
+ vkid = self.kids[0]
1537
1623
 
1538
1624
  async for node, path in genr:
1539
- valu = await self.kids[0].compute(runt, path)
1540
- async with contextlib.aclosing(self.yieldFromValu(runt, valu)) as agen:
1625
+ valu = await vkid.compute(runt, path)
1626
+ async with contextlib.aclosing(self.yieldFromValu(runt, valu, vkid)) as agen:
1541
1627
  async for subn in agen:
1542
1628
  yield subn, runt.initPath(subn)
1543
1629
  yield node, path
1544
1630
 
1545
1631
  if node is None and self.kids[0].isRuntSafe(runt):
1546
- valu = await self.kids[0].compute(runt, None)
1547
- async with contextlib.aclosing(self.yieldFromValu(runt, valu)) as agen:
1632
+ valu = await vkid.compute(runt, None)
1633
+ async with contextlib.aclosing(self.yieldFromValu(runt, valu, vkid)) as agen:
1548
1634
  async for subn in agen:
1549
1635
  yield subn, runt.initPath(subn)
1550
1636
 
1551
- async def yieldFromValu(self, runt, valu):
1552
-
1553
- viewiden = runt.snap.view.iden
1554
-
1555
- # there is nothing in None... ;)
1556
- if valu is None:
1557
- return
1558
-
1559
- # a little DWIM on what we get back...
1560
- # ( most common case will be stormtypes libs agenr -> iden|buid )
1561
- # buid list -> nodes
1562
- if isinstance(valu, bytes):
1563
- node = await runt.snap.getNodeByBuid(valu)
1564
- if node is not None:
1565
- yield node
1566
-
1567
- return
1568
-
1569
- # iden list -> nodes
1570
- if isinstance(valu, str):
1571
- try:
1572
- buid = s_common.uhex(valu)
1573
- except binascii.Error:
1574
- mesg = 'Yield string must be iden in hexdecimal. Got: %r' % (valu,)
1575
- raise self.kids[0].addExcInfo(s_exc.BadLiftValu(mesg=mesg))
1576
-
1577
- node = await runt.snap.getNodeByBuid(buid)
1578
- if node is not None:
1579
- yield node
1580
-
1581
- return
1582
-
1583
- if isinstance(valu, types.AsyncGeneratorType):
1584
- try:
1585
- async for item in valu:
1586
- async for node in self.yieldFromValu(runt, item):
1587
- yield node
1588
- finally:
1589
- await valu.aclose()
1590
- return
1591
-
1592
- if isinstance(valu, types.GeneratorType):
1593
- try:
1594
- for item in valu:
1595
- async for node in self.yieldFromValu(runt, item):
1596
- yield node
1597
- finally:
1598
- valu.close()
1599
- return
1600
-
1601
- if isinstance(valu, (list, tuple, set)):
1602
- for item in valu:
1603
- async for node in self.yieldFromValu(runt, item):
1604
- yield node
1605
- return
1606
-
1607
- if isinstance(valu, s_stormtypes.Node):
1608
- valu = valu.valu
1609
- if valu.snap.view.iden != viewiden:
1610
- mesg = f'Node is not from the current view. Node {valu.iden()} is from {valu.snap.view.iden} expected {viewiden}'
1611
- raise s_exc.BadLiftValu(mesg=mesg)
1612
- yield valu
1613
- return
1614
-
1615
- if isinstance(valu, s_node.Node):
1616
- if valu.snap.view.iden != viewiden:
1617
- mesg = f'Node is not from the current view. Node {valu.iden()} is from {valu.snap.view.iden} expected {viewiden}'
1618
- raise s_exc.BadLiftValu(mesg=mesg)
1619
- yield valu
1620
- return
1621
-
1622
- if isinstance(valu, (s_stormtypes.List, s_stormtypes.Set)):
1623
- for item in valu.valu:
1624
- async for node in self.yieldFromValu(runt, item):
1625
- yield node
1626
- return
1627
-
1628
- if isinstance(valu, s_stormtypes.Prim):
1629
- async with contextlib.aclosing(valu.nodes()) as genr:
1630
- async for node in genr:
1631
- if node.snap.view.iden != viewiden:
1632
- mesg = f'Node is not from the current view. Node {node.iden()} is from {node.snap.view.iden} expected {viewiden}'
1633
- raise s_exc.BadLiftValu(mesg=mesg)
1634
- yield node
1635
- return
1636
-
1637
1637
  class LiftTag(LiftOper):
1638
1638
 
1639
1639
  async def lift(self, runt, path):
@@ -4370,6 +4370,83 @@ class EditPropSet(Edit):
4370
4370
 
4371
4371
  await asyncio.sleep(0)
4372
4372
 
4373
+ class EditPropSetMulti(Edit):
4374
+
4375
+ async def run(self, runt, genr):
4376
+
4377
+ self.reqNotReadOnly(runt)
4378
+
4379
+ rval = self.kids[2]
4380
+ oper = await self.kids[1].compute(runt, None)
4381
+
4382
+ isadd = '+' in oper
4383
+ excignore = (s_exc.BadTypeValu,) if '?' in oper else ()
4384
+
4385
+ async for node, path in genr:
4386
+
4387
+ propname = await self.kids[0].compute(runt, path)
4388
+ name = await tostr(propname)
4389
+
4390
+ prop = node.form.props.get(name)
4391
+ if prop is None:
4392
+ if (exc := await s_stormtypes.typeerr(propname, str)) is None:
4393
+ exc = s_exc.NoSuchProp.init(f'{node.form.name}:{name}')
4394
+
4395
+ raise self.kids[0].addExcInfo(exc)
4396
+
4397
+ runt.confirmPropSet(prop)
4398
+
4399
+ if not prop.type.isarray:
4400
+ mesg = f'Property set using ({oper}) is only valid on arrays.'
4401
+ exc = s_exc.StormRuntimeError(mesg=mesg)
4402
+ raise self.kids[0].addExcInfo(exc)
4403
+
4404
+ if isinstance(rval, SubQuery):
4405
+ valu = await rval.compute_array(runt, path)
4406
+ else:
4407
+ valu = await rval.compute(runt, path)
4408
+
4409
+ if valu is None:
4410
+ yield node, path
4411
+ await asyncio.sleep(0)
4412
+ continue
4413
+
4414
+ atyp = prop.type.arraytype
4415
+ isndef = isinstance(atyp, s_types.Ndef)
4416
+ valu = await s_stormtypes.tostor(valu, isndef=isndef)
4417
+
4418
+ if (arry := node.get(name)) is None:
4419
+ arry = ()
4420
+
4421
+ arry = list(arry)
4422
+
4423
+ try:
4424
+ for item in valu:
4425
+ await asyncio.sleep(0)
4426
+
4427
+ try:
4428
+ norm, info = atyp.norm(item)
4429
+ except excignore:
4430
+ continue
4431
+
4432
+ if isadd:
4433
+ arry.append(norm)
4434
+ else:
4435
+ try:
4436
+ arry.remove(norm)
4437
+ except ValueError:
4438
+ pass
4439
+
4440
+ except TypeError:
4441
+ styp = await s_stormtypes.totype(valu, basetypes=True)
4442
+ mesg = f"'{styp}' object is not iterable: {s_common.trimText(repr(valu))}"
4443
+ raise rval.addExcInfo(s_exc.StormRuntimeError(mesg=mesg, type=styp)) from None
4444
+
4445
+ await node.set(name, arry)
4446
+
4447
+ yield node, path
4448
+ await asyncio.sleep(0)
4449
+
4373
4450
  class EditPropDel(Edit):
4374
4451
 
4375
4452
  async def run(self, runt, genr):
@@ -4578,17 +4655,27 @@ class EditEdgeAdd(Edit):
4578
4655
 
4579
4656
  self.reqNotReadOnly(runt)
4580
4657
 
4581
- # SubQuery -> Query
4582
- query = self.kids[1].kids[0]
4658
+ constverb = False
4659
+ if self.kids[0].isconst:
4660
+ constverb = True
4661
+ verb = await tostr(await self.kids[0].compute(runt, None))
4662
+ runt.layerConfirm(('node', 'edge', 'add', verb))
4663
+ else:
4664
+ hits = set()
4665
+ def allowed(x):
4666
+ if x in hits:
4667
+ return
4583
4668
 
4584
- hits = set()
4669
+ runt.layerConfirm(('node', 'edge', 'add', x))
4670
+ hits.add(x)
4585
4671
 
4586
- def allowed(x):
4587
- if x in hits:
4588
- return
4672
+ isvar = False
4673
+ vkid = self.kids[1]
4589
4674
 
4590
- runt.layerConfirm(('node', 'edge', 'add', x))
4591
- hits.add(x)
4675
+ if not isinstance(vkid, SubQuery):
4676
+ isvar = True
4677
+ else:
4678
+ query = vkid.kids[0]
4592
4679
 
4593
4680
  async for node, path in genr:
4594
4681
 
@@ -4596,38 +4683,44 @@ class EditEdgeAdd(Edit):
4596
4683
  mesg = f'Edges cannot be used with runt nodes: {node.form.full}'
4597
4684
  raise self.addExcInfo(s_exc.IsRuntForm(mesg=mesg, form=node.form.full))
4598
4685
 
4599
- iden = node.iden()
4600
- verb = await tostr(await self.kids[0].compute(runt, path))
4601
-
4602
- allowed(verb)
4603
-
4604
- async with runt.getSubRuntime(query) as subr:
4605
-
4606
- if self.n2:
4607
- async for subn, subp in subr.execute():
4608
- if subn.form.isrunt:
4609
- mesg = f'Edges cannot be used with runt nodes: {subn.form.full}'
4610
- raise self.addExcInfo(s_exc.IsRuntForm(mesg=mesg, form=subn.form.full))
4611
-
4612
- await subn.addEdge(verb, iden)
4613
-
4614
- else:
4615
- async with node.snap.getEditor() as editor:
4616
- proto = editor.loadNode(node)
4686
+ if not constverb:
4687
+ verb = await tostr(await self.kids[0].compute(runt, path))
4688
+ allowed(verb)
4689
+
4690
+ if isvar:
4691
+ valu = await vkid.compute(runt, path)
4692
+ async with contextlib.aclosing(self.yieldFromValu(runt, valu, vkid)) as agen:
4693
+ if self.n2:
4694
+ iden = node.iden()
4695
+ async for subn in agen:
4696
+ await subn.addEdge(verb, iden, extra=self.addExcInfo)
4697
+ else:
4698
+ async with node.snap.getEditor() as editor:
4699
+ proto = editor.loadNode(node)
4700
+ async for subn in agen:
4701
+ if subn.form.isrunt:
4702
+ mesg = f'Edges cannot be used with runt nodes: {subn.form.full}'
4703
+ raise self.addExcInfo(s_exc.IsRuntForm(mesg=mesg, form=subn.form.full))
4704
+
4705
+ await proto.addEdge(verb, subn.iden())
4706
+ await asyncio.sleep(0)
4617
4707
 
4708
+ else:
4709
+ async with runt.getSubRuntime(query) as subr:
4710
+ if self.n2:
4711
+ iden = node.iden()
4618
4712
  async for subn, subp in subr.execute():
4619
- if subn.form.isrunt:
4620
- mesg = f'Edges cannot be used with runt nodes: {subn.form.full}'
4621
- raise self.addExcInfo(s_exc.IsRuntForm(mesg=mesg, form=subn.form.full))
4622
-
4623
- await proto.addEdge(verb, subn.iden())
4624
- await asyncio.sleep(0)
4625
-
4626
- if len(proto.edges) >= 1000:
4627
- nodeedits = editor.getNodeEdits()
4628
- if nodeedits:
4629
- await node.snap.applyNodeEdits(nodeedits)
4630
- proto.edges.clear()
4713
+ await subn.addEdge(verb, iden, extra=self.addExcInfo)
4714
+ else:
4715
+ async with node.snap.getEditor() as editor:
4716
+ proto = editor.loadNode(node)
4717
+ async for subn, subp in subr.execute():
4718
+ if subn.form.isrunt:
4719
+ mesg = f'Edges cannot be used with runt nodes: {subn.form.full}'
4720
+ raise self.addExcInfo(s_exc.IsRuntForm(mesg=mesg, form=subn.form.full))
4721
+
4722
+ await proto.addEdge(verb, subn.iden())
4723
+ await asyncio.sleep(0)
4631
4724
 
4632
4725
  yield node, path
4633
4726
 
@@ -4641,16 +4734,27 @@ class EditEdgeDel(Edit):
4641
4734
 
4642
4735
  self.reqNotReadOnly(runt)
4643
4736
 
4644
- query = self.kids[1].kids[0]
4737
+ isvar = False
4738
+ vkid = self.kids[1]
4645
4739
 
4646
- hits = set()
4740
+ if not isinstance(vkid, SubQuery):
4741
+ isvar = True
4742
+ else:
4743
+ query = vkid.kids[0]
4647
4744
 
4648
- def allowed(x):
4649
- if x in hits:
4650
- return
4745
+ constverb = False
4746
+ if self.kids[0].isconst:
4747
+ constverb = True
4748
+ verb = await tostr(await self.kids[0].compute(runt, None))
4749
+ runt.layerConfirm(('node', 'edge', 'del', verb))
4750
+ else:
4751
+ hits = set()
4752
+ def allowed(x):
4753
+ if x in hits:
4754
+ return
4651
4755
 
4652
- runt.layerConfirm(('node', 'edge', 'del', x))
4653
- hits.add(x)
4756
+ runt.layerConfirm(('node', 'edge', 'del', x))
4757
+ hits.add(x)
4654
4758
 
4655
4759
  async for node, path in genr:
4656
4760
 
@@ -4658,36 +4762,44 @@ class EditEdgeDel(Edit):
4658
4762
  mesg = f'Edges cannot be used with runt nodes: {node.form.full}'
4659
4763
  raise self.addExcInfo(s_exc.IsRuntForm(mesg=mesg, form=node.form.full))
4660
4764
 
4661
- iden = node.iden()
4662
- verb = await tostr(await self.kids[0].compute(runt, path))
4663
-
4664
- allowed(verb)
4665
-
4666
- async with runt.getSubRuntime(query) as subr:
4667
- if self.n2:
4668
- async for subn, subp in subr.execute():
4669
- if subn.form.isrunt:
4670
- mesg = f'Edges cannot be used with runt nodes: {subn.form.full}'
4671
- raise self.addExcInfo(s_exc.IsRuntForm(mesg=mesg, form=subn.form.full))
4672
- await subn.delEdge(verb, iden)
4673
-
4674
- else:
4675
- async with node.snap.getEditor() as editor:
4676
- proto = editor.loadNode(node)
4765
+ if not constverb:
4766
+ verb = await tostr(await self.kids[0].compute(runt, path))
4767
+ allowed(verb)
4768
+
4769
+ if isvar:
4770
+ valu = await vkid.compute(runt, path)
4771
+ async with contextlib.aclosing(self.yieldFromValu(runt, valu, vkid)) as agen:
4772
+ if self.n2:
4773
+ iden = node.iden()
4774
+ async for subn in agen:
4775
+ await subn.delEdge(verb, iden, extra=self.addExcInfo)
4776
+ else:
4777
+ async with node.snap.getEditor() as editor:
4778
+ proto = editor.loadNode(node)
4779
+ async for subn in agen:
4780
+ if subn.form.isrunt:
4781
+ mesg = f'Edges cannot be used with runt nodes: {subn.form.full}'
4782
+ raise self.addExcInfo(s_exc.IsRuntForm(mesg=mesg, form=subn.form.full))
4783
+
4784
+ await proto.delEdge(verb, subn.iden())
4785
+ await asyncio.sleep(0)
4677
4786
 
4787
+ else:
4788
+ async with runt.getSubRuntime(query) as subr:
4789
+ if self.n2:
4790
+ iden = node.iden()
4678
4791
  async for subn, subp in subr.execute():
4679
- if subn.form.isrunt:
4680
- mesg = f'Edges cannot be used with runt nodes: {subn.form.full}'
4681
- raise self.addExcInfo(s_exc.IsRuntForm(mesg=mesg, form=subn.form.full))
4682
-
4683
- await proto.delEdge(verb, subn.iden())
4684
- await asyncio.sleep(0)
4685
-
4686
- if len(proto.edgedels) >= 1000:
4687
- nodeedits = editor.getNodeEdits()
4688
- if nodeedits:
4689
- await node.snap.applyNodeEdits(nodeedits)
4690
- proto.edgedels.clear()
4792
+ await subn.delEdge(verb, iden, extra=self.addExcInfo)
4793
+ else:
4794
+ async with node.snap.getEditor() as editor:
4795
+ proto = editor.loadNode(node)
4796
+ async for subn, subp in subr.execute():
4797
+ if subn.form.isrunt:
4798
+ mesg = f'Edges cannot be used with runt nodes: {subn.form.full}'
4799
+ raise self.addExcInfo(s_exc.IsRuntForm(mesg=mesg, form=subn.form.full))
4800
+
4801
+ await proto.delEdge(verb, subn.iden())
4802
+ await asyncio.sleep(0)
4691
4803
 
4692
4804
  yield node, path
4693
4805
 
synapse/lib/auth.py CHANGED
@@ -375,11 +375,32 @@ class Auth(s_nexus.Pusher):
375
375
 
376
376
  await self.fire('cell:beholder', **behold)
377
377
 
378
- @s_nexus.Pusher.onPushAuto('user:info')
379
378
  async def setUserInfo(self, iden, name, valu, gateiden=None, logged=True, mesg=None):
380
379
 
381
380
  user = await self.reqUser(iden)
382
381
 
382
+ if name == 'locked' and not valu and user.isArchived():
383
+ raise s_exc.BadArg(mesg='Cannot unlock archived user.', user=iden, username=user.name)
384
+
385
+ if name in ('locked', 'archived') and not valu:
386
+ self.checkUserLimit()
387
+
388
+ await self._push('user:info', iden, name, valu, gateiden=gateiden, logged=logged, mesg=mesg)
389
+
390
+ @s_nexus.Pusher.onPush('user:info')
391
+ async def _setUserInfo(self, iden, name, valu, gateiden=None, logged=True, mesg=None):
392
+ user = await self.reqUser(iden)
393
+
394
+ if self.nexsroot and self.nexsroot.cell.nexsvers >= (2, 198):
395
+ # If the nexus version is less than 2.197 then the leader hasn't been upgraded yet and
396
+ # we don't want to get into a schism because we're bouncing edits and the leader is
397
+ # applying them.
398
+ if name == 'locked' and not valu and user.isArchived():
399
+ return
400
+
401
+ if name in ('locked', 'archived') and not valu:
402
+ self.checkUserLimit()
403
+
383
404
  if gateiden is not None:
384
405
  info = user.genGateInfo(gateiden)
385
406
  info[name] = s_msgpack.deepcopy(valu)
@@ -392,9 +413,6 @@ class Auth(s_nexus.Pusher):
392
413
  user.info[name] = s_msgpack.deepcopy(valu)
393
414
  self.userdefs.set(iden, user.info)
394
415
 
395
- if name in ('locked', 'archived') and not valu:
396
- self.checkUserLimit()
397
-
398
416
  if mesg is None:
399
417
  mesg = {
400
418
  'iden': iden,
@@ -720,6 +738,16 @@ class Auth(s_nexus.Pusher):
720
738
  self.roleidenbyname.delete(role.name)
721
739
  await self.feedBeholder('role:del', {'iden': iden})
722
740
 
741
+ def clearAuthCache(self):
742
+ '''
743
+ Clear all auth caches.
744
+ '''
745
+ self.userbyidencache.clear()
746
+ self.useridenbynamecache.clear()
747
+ self.rolebyidencache.clear()
748
+ self.roleidenbynamecache.clear()
749
+ self.authgates.clear()
750
+
723
751
  class AuthGate():
724
752
  '''
725
753
  The storage object for object specific rules for users/roles.
@@ -975,7 +1003,7 @@ class User(Ruler):
975
1003
  if nexs:
976
1004
  return await self.auth.setUserInfo(self.iden, name, valu, gateiden=gateiden, mesg=mesg)
977
1005
  else:
978
- return await self.auth._hndlsetUserInfo(self.iden, name, valu, gateiden=gateiden, logged=nexs, mesg=mesg)
1006
+ return await self.auth._setUserInfo(self.iden, name, valu, gateiden=gateiden, logged=nexs, mesg=mesg)
979
1007
 
980
1008
  async def setName(self, name):
981
1009
  return await self.auth.setUserName(self.iden, name)
@@ -1289,7 +1317,7 @@ class User(Ruler):
1289
1317
  if nexs:
1290
1318
  await self.auth.setUserInfo(self.iden, 'roles', roles, mesg=mesg)
1291
1319
  else:
1292
- await self.auth._hndlsetUserInfo(self.iden, 'roles', roles, logged=nexs, mesg=mesg)
1320
+ await self.auth._setUserInfo(self.iden, 'roles', roles, logged=nexs, mesg=mesg)
1293
1321
 
1294
1322
  def isLocked(self):
1295
1323
  return self.info.get('locked')
@@ -1332,7 +1360,7 @@ class User(Ruler):
1332
1360
  if logged:
1333
1361
  await self.auth.setUserInfo(self.iden, 'admin', admin, gateiden=gateiden)
1334
1362
  else:
1335
- await self.auth._hndlsetUserInfo(self.iden, 'admin', admin, gateiden=gateiden, logged=logged)
1363
+ await self.auth._setUserInfo(self.iden, 'admin', admin, gateiden=gateiden, logged=logged)
1336
1364
 
1337
1365
  async def setLocked(self, locked, logged=True):
1338
1366
  if not isinstance(locked, bool):
@@ -1352,9 +1380,9 @@ class User(Ruler):
1352
1380
  await self.auth.setUserInfo(self.iden, 'policy:attempts', 0)
1353
1381
 
1354
1382
  else:
1355
- await self.auth._hndlsetUserInfo(self.iden, 'locked', locked, logged=logged)
1383
+ await self.auth._setUserInfo(self.iden, 'locked', locked, logged=logged)
1356
1384
  if resetAttempts:
1357
- await self.auth._hndlsetUserInfo(self.iden, 'policy:attempts', 0)
1385
+ await self.auth._setUserInfo(self.iden, 'policy:attempts', 0)
1358
1386
 
1359
1387
  async def setArchived(self, archived):
1360
1388
  if not isinstance(archived, bool):
@@ -1541,7 +1569,7 @@ class User(Ruler):
1541
1569
  if nexs:
1542
1570
  await self.auth.setUserInfo(self.iden, 'policy:previous', previous[:prevvalu])
1543
1571
  else:
1544
- await self.auth._hndlsetUserInfo(self.iden, 'policy:previous', previous[:prevvalu], logged=nexs)
1572
+ await self.auth._setUserInfo(self.iden, 'policy:previous', previous[:prevvalu], logged=nexs)
1545
1573
 
1546
1574
  async def setPasswd(self, passwd, nexs=True, enforce_policy=True):
1547
1575
  # Prevent empty string or non-string values
@@ -1559,4 +1587,4 @@ class User(Ruler):
1559
1587
  if nexs:
1560
1588
  await self.auth.setUserInfo(self.iden, 'passwd', shadow)
1561
1589
  else:
1562
- await self.auth._hndlsetUserInfo(self.iden, 'passwd', shadow, logged=nexs)
1590
+ await self.auth._setUserInfo(self.iden, 'passwd', shadow, logged=nexs)