synapse 2.161.0__py311-none-any.whl → 2.162.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 (48) hide show
  1. synapse/daemon.py +7 -2
  2. synapse/lib/cell.py +7 -3
  3. synapse/lib/layer.py +20 -1
  4. synapse/lib/rstorm.py +16 -0
  5. synapse/lib/storm.py +17 -1
  6. synapse/lib/stormlib/stix.py +6 -3
  7. synapse/lib/stormtypes.py +148 -19
  8. synapse/lib/version.py +2 -2
  9. synapse/lib/view.py +15 -2
  10. synapse/models/inet.py +9 -0
  11. synapse/models/infotech.py +28 -26
  12. synapse/models/orgs.py +3 -0
  13. synapse/models/proj.py +9 -2
  14. synapse/models/risk.py +32 -0
  15. synapse/telepath.py +2 -0
  16. synapse/tests/files/rstorm/testsvc.py +8 -1
  17. synapse/tests/files/stormpkg/testpkg.yaml +4 -0
  18. synapse/tests/test_axon.py +4 -4
  19. synapse/tests/test_cortex.py +6 -6
  20. synapse/tests/test_daemon.py +19 -0
  21. synapse/tests/test_lib_ast.py +16 -16
  22. synapse/tests/test_lib_grammar.py +4 -4
  23. synapse/tests/test_lib_rstorm.py +38 -2
  24. synapse/tests/test_lib_storm.py +14 -14
  25. synapse/tests/test_lib_stormhttp.py +18 -18
  26. synapse/tests/test_lib_stormlib_auth.py +3 -3
  27. synapse/tests/test_lib_stormlib_cell.py +1 -1
  28. synapse/tests/test_lib_stormlib_cortex.py +50 -2
  29. synapse/tests/test_lib_stormlib_json.py +2 -2
  30. synapse/tests/test_lib_stormlib_macro.py +1 -1
  31. synapse/tests/test_lib_stormlib_modelext.py +37 -37
  32. synapse/tests/test_lib_stormlib_oauth.py +20 -20
  33. synapse/tests/test_lib_stormlib_stix.py +3 -1
  34. synapse/tests/test_lib_stormtypes.py +127 -45
  35. synapse/tests/test_lib_stormwhois.py +1 -1
  36. synapse/tests/test_lib_trigger.py +11 -11
  37. synapse/tests/test_lib_view.py +23 -1
  38. synapse/tests/test_model_crypto.py +1 -1
  39. synapse/tests/test_model_inet.py +6 -0
  40. synapse/tests/test_model_orgs.py +2 -1
  41. synapse/tests/test_model_proj.py +6 -0
  42. synapse/tests/test_model_risk.py +10 -0
  43. synapse/tests/test_tools_storm.py +1 -1
  44. {synapse-2.161.0.dist-info → synapse-2.162.0.dist-info}/METADATA +3 -1
  45. {synapse-2.161.0.dist-info → synapse-2.162.0.dist-info}/RECORD +48 -48
  46. {synapse-2.161.0.dist-info → synapse-2.162.0.dist-info}/LICENSE +0 -0
  47. {synapse-2.161.0.dist-info → synapse-2.162.0.dist-info}/WHEEL +0 -0
  48. {synapse-2.161.0.dist-info → synapse-2.162.0.dist-info}/top_level.txt +0 -0
synapse/daemon.py CHANGED
@@ -218,7 +218,7 @@ async def t2call(link, meth, args, kwargs):
218
218
 
219
219
  class Daemon(s_base.Base):
220
220
 
221
- async def __anit__(self, certdir=None):
221
+ async def __anit__(self, certdir=None, ahainfo=None):
222
222
 
223
223
  await s_base.Base.__anit__(self)
224
224
 
@@ -227,6 +227,8 @@ class Daemon(s_base.Base):
227
227
  if certdir is None:
228
228
  certdir = s_certdir.getCertDir()
229
229
 
230
+ self.ahainfo = ahainfo
231
+
230
232
  self.certdir = certdir
231
233
  self.televers = s_telepath.televers
232
234
 
@@ -414,13 +416,16 @@ class Daemon(s_base.Base):
414
416
  async def _getSharedItem(self, name):
415
417
  return self.shared.get(name)
416
418
 
417
- async def _onTeleSyn(self, link, mesg):
419
+ async def _onTeleSyn(self, link: s_link.Link, mesg):
418
420
 
419
421
  reply = ('tele:syn', {
420
422
  'vers': self.televers,
421
423
  'retn': (True, None),
422
424
  })
423
425
 
426
+ if self.ahainfo is not None:
427
+ reply[1]['ahainfo'] = self.ahainfo
428
+
424
429
  try:
425
430
 
426
431
  vers = mesg[1].get('vers')
synapse/lib/cell.py CHANGED
@@ -2647,8 +2647,8 @@ class Cell(s_nexus.Pusher, s_telepath.Aware):
2647
2647
  for name, valu in vals.items():
2648
2648
  self.sessstor.set(iden, name, valu)
2649
2649
  sess = self.sessions.get(iden)
2650
- for name, valu in vals.items():
2651
- sess.info[name] = valu
2650
+ if sess is not None:
2651
+ sess.info.update(vals)
2652
2652
 
2653
2653
  @contextlib.contextmanager
2654
2654
  def getTempDir(self):
@@ -2840,7 +2840,11 @@ class Cell(s_nexus.Pusher, s_telepath.Aware):
2840
2840
 
2841
2841
  async def _initCellDmon(self):
2842
2842
 
2843
- self.dmon = await s_daemon.Daemon.anit()
2843
+ ahainfo = {
2844
+ 'name': self.ahasvcname
2845
+ }
2846
+
2847
+ self.dmon = await s_daemon.Daemon.anit(ahainfo=ahainfo)
2844
2848
  self.dmon.share('*', self)
2845
2849
 
2846
2850
  self.onfini(self.dmon.fini)
synapse/lib/layer.py CHANGED
@@ -1529,12 +1529,14 @@ class Layer(s_nexus.Pusher):
1529
1529
 
1530
1530
  mirror = self.layrinfo.get('mirror')
1531
1531
  if mirror is not None:
1532
+ s_common.deprecated('mirror layer configuration option', curv='2.162.0')
1532
1533
  conf = {'retrysleep': 2}
1533
1534
  self.leader = await s_telepath.Client.anit(mirror, conf=conf)
1534
1535
  self.leadtask = self.schedCoro(self._runMirrorLoop())
1535
1536
 
1536
1537
  uplayr = self.layrinfo.get('upstream')
1537
1538
  if uplayr is not None:
1539
+ s_common.deprecated('upstream layer configuration option', curv='2.162.0')
1538
1540
  if isinstance(uplayr, (tuple, list)):
1539
1541
  for layr in uplayr:
1540
1542
  await self.initUpstreamSync(layr)
@@ -4382,9 +4384,14 @@ def getNodeEditPerms(nodeedits):
4382
4384
  '''
4383
4385
  Yields (offs, perm) tuples that can be used in user.allowed()
4384
4386
  '''
4387
+ tags = []
4388
+ tagadds = []
4385
4389
 
4386
4390
  for nodeoffs, (buid, form, edits) in enumerate(nodeedits):
4387
4391
 
4392
+ tags.clear()
4393
+ tagadds.clear()
4394
+
4388
4395
  for editoffs, (edit, info, _) in enumerate(edits):
4389
4396
 
4390
4397
  permoffs = (nodeoffs, editoffs)
@@ -4406,7 +4413,11 @@ def getNodeEditPerms(nodeedits):
4406
4413
  continue
4407
4414
 
4408
4415
  if edit == EDIT_TAG_SET:
4409
- yield (permoffs, ('node', 'tag', 'add', *info[0].split('.')))
4416
+ if info[1] != (None, None):
4417
+ tagadds.append(info[0])
4418
+ yield (permoffs, ('node', 'tag', 'add', *info[0].split('.')))
4419
+ else:
4420
+ tags.append((len(info[0]), editoffs, info[0]))
4410
4421
  continue
4411
4422
 
4412
4423
  if edit == EDIT_TAG_DEL:
@@ -4436,3 +4447,11 @@ def getNodeEditPerms(nodeedits):
4436
4447
  if edit == EDIT_EDGE_DEL:
4437
4448
  yield (permoffs, ('node', 'edge', 'del', info[0]))
4438
4449
  continue
4450
+
4451
+ for _, editoffs, tag in sorted(tags, reverse=True):
4452
+ look = tag + '.'
4453
+ if any([tagadd.startswith(look) for tagadd in tagadds]):
4454
+ continue
4455
+
4456
+ yield ((nodeoffs, editoffs), ('node', 'tag', 'add', *tag.split('.')))
4457
+ tagadds.append(tag)
synapse/lib/rstorm.py CHANGED
@@ -29,6 +29,7 @@ re_directive = regex.compile(r'^\.\.\s(storm.*|[^:])::(?:\s(.*)$|$)')
29
29
 
30
30
  logger = logging.getLogger(__name__)
31
31
 
32
+ ONLOAD_TIMEOUT = int(os.getenv('SYNDEV_PKG_LOAD_TIMEOUT', 30)) # seconds
32
33
 
33
34
  class OutPutRst(s_output.OutPutStr):
34
35
  '''
@@ -413,8 +414,17 @@ class StormRst(s_base.Base):
413
414
  core = self._reqCore()
414
415
 
415
416
  pkg = s_genpkg.loadPkgProto(text)
417
+
418
+ if pkg.get('onload') is not None:
419
+ waiter = core.waiter(1, 'core:pkg:onload:complete')
420
+ else:
421
+ waiter = None
422
+
416
423
  await core.addStormPkg(pkg)
417
424
 
425
+ if waiter is not None and not await waiter.wait(timeout=ONLOAD_TIMEOUT):
426
+ raise s_exc.SynErr(mesg=f'Package onload failed to run for {pkg.get("name")}')
427
+
418
428
  async def _handleStormPre(self, text):
419
429
  '''
420
430
  Run a Storm query to prepare the Cortex without output.
@@ -453,6 +463,9 @@ class StormRst(s_base.Base):
453
463
 
454
464
  svc = await self._getCell(ctor, conf=svcconf)
455
465
 
466
+ onloadcnt = len([p for p in svc.cellapi._storm_svc_pkgs if p.get('onload') is not None])
467
+ waiter = core.waiter(onloadcnt, 'core:pkg:onload:complete') if onloadcnt else None
468
+
456
469
  svc.dmon.share('svc', svc)
457
470
  root = await svc.auth.getUserByName('root')
458
471
  await root.setPasswd('root')
@@ -463,6 +476,9 @@ class StormRst(s_base.Base):
463
476
  await core.nodes(f'service.add {svcname} {surl}')
464
477
  await core.nodes(f'$lib.service.wait({svcname})')
465
478
 
479
+ if waiter is not None and not await waiter.wait(timeout=ONLOAD_TIMEOUT):
480
+ raise s_exc.SynErr(mesg=f'Package onload failed to run for service {svcname}')
481
+
466
482
  async def _handleStormFail(self, text):
467
483
  valu = json.loads(text)
468
484
  assert valu in (True, False), f'storm-fail must be a boolean: {text}'
synapse/lib/storm.py CHANGED
@@ -181,7 +181,7 @@ wgetdescr = '''Retrieve bytes from a URL and store them in the axon. Yields inet
181
181
  Examples:
182
182
 
183
183
  # Specify custom headers and parameters
184
- inet:url=https://vertex.link/foo.bar.txt | wget --headers $lib.dict("User-Agent"="Foo/Bar") --params $lib.dict("clientid"="42")
184
+ inet:url=https://vertex.link/foo.bar.txt | wget --headers ({"User-Agent": "Foo/Bar"}) --params ({"clientid": "42"})
185
185
 
186
186
  # Download multiple URL targets without inbound nodes
187
187
  wget https://vertex.link https://vtx.lk
@@ -3787,7 +3787,23 @@ class MergeCmd(Cmd):
3787
3787
  runt.confirmPropDel(prop, layriden=layr0)
3788
3788
  runt.confirmPropSet(prop, layriden=layr1)
3789
3789
 
3790
+ tags = []
3791
+ tagadds = []
3790
3792
  for tag, valu in sode.get('tags', {}).items():
3793
+ if valu != (None, None):
3794
+ tagadds.append(tag)
3795
+ tagperm = tuple(tag.split('.'))
3796
+ runt.confirm(('node', 'tag', 'del') + tagperm, gateiden=layr0)
3797
+ runt.confirm(('node', 'tag', 'add') + tagperm, gateiden=layr1)
3798
+ else:
3799
+ tags.append((len(tag), tag))
3800
+
3801
+ for _, tag in sorted(tags, reverse=True):
3802
+ look = tag + '.'
3803
+ if any([tagadd.startswith(look) for tagadd in tagadds]):
3804
+ continue
3805
+
3806
+ tagadds.append(tag)
3791
3807
  tagperm = tuple(tag.split('.'))
3792
3808
  runt.confirm(('node', 'tag', 'del') + tagperm, gateiden=layr0)
3793
3809
  runt.confirm(('node', 'tag', 'add') + tagperm, gateiden=layr1)
@@ -247,7 +247,7 @@ _DefaultConfig = {
247
247
  'name': '{+:name return(:name)} return($node.repr())',
248
248
  'size': '+:size return(:size)',
249
249
  'hashes': '''
250
- init { $dict = $lib.dict() }
250
+ init { $dict = ({}) }
251
251
  { +:md5 $dict.MD5 = :md5 }
252
252
  { +:sha1 $dict."SHA-1" = :sha1 }
253
253
  { +:sha256 $dict."SHA-256" = :sha256 }
@@ -392,7 +392,7 @@ _DefaultConfig = {
392
392
  'description': 'if (:desc) { return (:desc) }',
393
393
  'created': 'return($lib.stix.export.timestamp(.created))',
394
394
  'modified': 'return($lib.stix.export.timestamp(.created))',
395
- 'external_references': 'if :cve { $cve=:cve $cve=$cve.upper() $list=$lib.list($lib.dict(source_name=cve, external_id=$cve)) return($list) }'
395
+ 'external_references': 'if :cve { $cve=:cve $cve=$cve.upper() $list=$lib.list(({"source_name": "cve", "external_id": $cve})) return($list) }'
396
396
  },
397
397
  'rels': (
398
398
 
@@ -1405,7 +1405,10 @@ class StixBundle(s_stormtypes.Prim):
1405
1405
 
1406
1406
  async def _callStorm(self, text, node):
1407
1407
 
1408
- opts = {'vars': {'bundle': self}}
1408
+ varz = self.runt.getScopeVars()
1409
+ varz['bundle'] = self
1410
+
1411
+ opts = {'vars': varz}
1409
1412
  query = await self.runt.snap.core.getStormQuery(text)
1410
1413
  async with self.runt.getCmdRuntime(query, opts=opts) as runt:
1411
1414
 
synapse/lib/stormtypes.py CHANGED
@@ -40,6 +40,7 @@ import synapse.lib.provenance as s_provenance
40
40
  logger = logging.getLogger(__name__)
41
41
 
42
42
  class Undef:
43
+ _storm_typename = 'undef'
43
44
  async def stormrepr(self):
44
45
  return '$lib.undef'
45
46
 
@@ -1085,13 +1086,6 @@ class LibBase(Lib):
1085
1086
  {'name': '*vals', 'type': 'any', 'desc': 'Initial values to place in the set.', },
1086
1087
  ),
1087
1088
  'returns': {'type': 'set', 'desc': 'The new set.', }}},
1088
- {'name': 'dict', 'desc': 'Get a Storm Dict object.',
1089
- 'type': {'type': 'function', '_funcname': '_dict',
1090
- 'args': (
1091
- {'name': '**kwargs', 'type': 'any',
1092
- 'desc': 'Initial set of keyword argumetns to place into the dict.', },
1093
- ),
1094
- 'returns': {'type': 'dict', 'desc': 'A dictionary object.', }}},
1095
1089
  {'name': 'exit', 'desc': 'Cause a Storm Runtime to stop running.',
1096
1090
  'type': {'type': 'function', '_funcname': '_exit',
1097
1091
  'args': (
@@ -1147,7 +1141,7 @@ class LibBase(Lib):
1147
1141
  Examples:
1148
1142
  Create a dictionary object with a key whose value is null, and call ``$lib.fire()`` with it::
1149
1143
 
1150
- cli> storm $d=$lib.dict(key=$lib.null) $lib.fire('demo', d=$d)
1144
+ cli> storm $d=({"key": $lib.null}) $lib.fire('demo', d=$d)
1151
1145
  ('storm:fire', {'type': 'demo', 'data': {'d': {'key': None}}})
1152
1146
  ''',
1153
1147
  'type': 'null', },
@@ -1227,7 +1221,7 @@ class LibBase(Lib):
1227
1221
 
1228
1222
  Format and print string based on variables::
1229
1223
 
1230
- cli> storm $d=$lib.dict(key1=(1), key2="two")
1224
+ cli> storm $d=({"key1": (1), "key2": "two"})
1231
1225
  for ($key, $value) in $d { $lib.print('{k} => {v}', k=$key, v=$value) }
1232
1226
  key1 => 1
1233
1227
  key2 => two
@@ -1377,7 +1371,6 @@ class LibBase(Lib):
1377
1371
  'max': self._max,
1378
1372
  'set': self._set,
1379
1373
  'copy': self._copy,
1380
- 'dict': self._dict,
1381
1374
  'exit': self._exit,
1382
1375
  'guid': self._guid,
1383
1376
  'fire': self._fire,
@@ -1677,16 +1670,125 @@ class LibBase(Lib):
1677
1670
  mesg = await self._get_mesg(mesg, **kwargs)
1678
1671
  await self.runt.warn(mesg, log=False)
1679
1672
 
1680
- @stormfunc(readonly=True)
1681
- async def _dict(self, **kwargs):
1682
- return Dict(kwargs)
1683
-
1684
1673
  @stormfunc(readonly=True)
1685
1674
  async def _fire(self, name, **info):
1686
1675
  info = await toprim(info)
1687
1676
  s_common.reqjsonsafe(info)
1688
1677
  await self.runt.snap.fire('storm:fire', type=name, data=info)
1689
1678
 
1679
+ @registry.registerLib
1680
+ class LibDict(Lib):
1681
+ '''
1682
+ A Storm Library for interacting with dictionaries.
1683
+ '''
1684
+ _storm_locals = (
1685
+ {'name': 'keys', 'desc': 'Retrieve a list of keys in the specified dictionary.',
1686
+ 'type': {'type': 'function', '_funcname': '_keys',
1687
+ 'args': (
1688
+ {'name': 'valu', 'type': 'dict', 'desc': 'The dictionary to operate on.'},
1689
+ ),
1690
+ 'returns': {'type': 'list', 'desc': 'List of keys in the specified dictionary.', }}},
1691
+ {'name': 'pop', 'desc': 'Remove specified key and return the corresponding value.',
1692
+ 'type': {'type': 'function', '_funcname': '_pop',
1693
+ 'args': (
1694
+ {'name': 'valu', 'type': 'dict', 'desc': 'The dictionary to operate on.'},
1695
+ {'name': 'key', 'type': 'str', 'desc': 'The key name of the value to pop.'},
1696
+ {'name': 'default', 'type': 'any', 'default': '$lib.undef',
1697
+ 'desc': 'Optional default value to return if the key does not exist in the dictionary.'},
1698
+ ),
1699
+ 'returns': {'type': 'any', 'desc': 'The popped value.', }}},
1700
+ {'name': 'update', 'desc': 'Update the specified dictionary with keys/values from another dictionary.',
1701
+ 'type': {'type': 'function', '_funcname': '_update',
1702
+ 'args': (
1703
+ {'name': 'valu', 'type': 'dict', 'desc': 'The target dictionary (update to).'},
1704
+ {'name': 'other', 'type': 'dict', 'desc': 'The source dictionary (update from).'},
1705
+ ),
1706
+ 'returns': {'type': 'null'}}},
1707
+ {'name': 'values', 'desc': 'Retrieve a list of values in the specified dictionary.',
1708
+ 'type': {'type': 'function', '_funcname': '_values',
1709
+ 'args': (
1710
+ {'name': 'valu', 'type': 'dict', 'desc': 'The dictionary to operate on.'},
1711
+ ),
1712
+ 'returns': {'type': 'list', 'desc': 'List of values in the specified dictionary.', }}},
1713
+ )
1714
+ _storm_lib_path = ('dict',)
1715
+
1716
+ def getObjLocals(self):
1717
+ return {
1718
+ 'has': self._has,
1719
+ 'keys': self._keys,
1720
+ 'pop': self._pop,
1721
+ 'update': self._update,
1722
+ 'values': self._values,
1723
+ }
1724
+
1725
+ async def _check_type(self, valu, name='valu'):
1726
+ if isinstance(valu, (dict, Dict)):
1727
+ return
1728
+
1729
+ typ = getattr(valu, '_storm_typename', None)
1730
+ if typ is None:
1731
+ prim = await toprim(valu)
1732
+ typ = type(prim).__name__
1733
+
1734
+ mesg = f'{name} argument must be a dict, not {typ}.'
1735
+ raise s_exc.BadArg(mesg=mesg)
1736
+
1737
+ @stormfunc(readonly=True)
1738
+ async def _has(self, valu, name):
1739
+ await self._check_type(valu)
1740
+ valu = await toprim(valu)
1741
+ return name in valu
1742
+
1743
+ @stormfunc(readonly=True)
1744
+ async def _keys(self, valu):
1745
+ await self._check_type(valu)
1746
+ valu = await toprim(valu)
1747
+ return list(valu.keys())
1748
+
1749
+ @stormfunc(readonly=True)
1750
+ async def _pop(self, valu, key, default=undef):
1751
+ await self._check_type(valu)
1752
+
1753
+ real = await toprim(valu)
1754
+ key = await tostr(key)
1755
+
1756
+ if key not in real:
1757
+ if default == undef:
1758
+ mesg = f'Key {key} does not exist in dictionary.'
1759
+ raise s_exc.BadArg(mesg=mesg)
1760
+ return await toprim(default)
1761
+
1762
+ # Make sure we have a storm Dict
1763
+ valu = fromprim(valu)
1764
+
1765
+ ret = await valu.deref(key)
1766
+ await valu.setitem(key, undef)
1767
+ return ret
1768
+
1769
+ @stormfunc(readonly=True)
1770
+ async def _update(self, valu, other):
1771
+ await self._check_type(valu)
1772
+ await self._check_type(other, name='other')
1773
+
1774
+ valu = fromprim(valu)
1775
+ other = await toprim(other)
1776
+
1777
+ for k, v in other.items():
1778
+ await valu.setitem(k, v)
1779
+
1780
+ @stormfunc(readonly=True)
1781
+ async def _values(self, valu):
1782
+ await self._check_type(valu)
1783
+
1784
+ valu = await toprim(valu)
1785
+ return list(valu.values())
1786
+
1787
+ async def __call__(self, **kwargs):
1788
+ s_common.deprecated('$lib.dict()', curv='2.161.0')
1789
+ await self.runt.snap.warnonce('$lib.dict() is deprecated. Use ({}) instead.')
1790
+ return Dict(kwargs)
1791
+
1690
1792
  @registry.registerLib
1691
1793
  class LibPs(Lib):
1692
1794
  '''
@@ -1700,7 +1802,7 @@ class LibPs(Lib):
1700
1802
  'desc': 'The prefix of the task to stop. '
1701
1803
  'Tasks will only be stopped if there is a single prefix match.'},
1702
1804
  ),
1703
- 'returns': {'type': 'boolean', 'desc': ' True if the task was cancelled, False otherwise.', }}},
1805
+ 'returns': {'type': 'boolean', 'desc': 'True if the task was cancelled, False otherwise.', }}},
1704
1806
  {'name': 'list', 'desc': 'List tasks the current user can access.',
1705
1807
  'type': {'type': 'function', '_funcname': '_list',
1706
1808
  'returns': {'type': 'list', 'desc': 'A list of task definitions.', }}},
@@ -1826,7 +1928,7 @@ class LibAxon(Lib):
1826
1928
  Example:
1827
1929
  Get the Vertex Project website::
1828
1930
 
1829
- $headers = $lib.dict()
1931
+ $headers = ({})
1830
1932
  $headers."User-Agent" = Foo/Bar
1831
1933
 
1832
1934
  $resp = $lib.axon.wget("http://vertex.link", method=GET, headers=$headers)
@@ -1872,7 +1974,7 @@ class LibAxon(Lib):
1872
1974
  ),
1873
1975
  'returns': {'type': 'dict', 'desc': 'A status dictionary of metadata.'}}},
1874
1976
  {'name': 'urlfile', 'desc': '''
1875
- Retrive the target URL using the wget() function and construct an inet:urlfile node from the response.
1977
+ Retrieve the target URL using the wget() function and construct an inet:urlfile node from the response.
1876
1978
 
1877
1979
  Notes:
1878
1980
  This accepts the same arguments as ``$lib.axon.wget()``.
@@ -2365,6 +2467,9 @@ class LibBytes(Lib):
2365
2467
  }
2366
2468
 
2367
2469
  async def _libBytesUpload(self, genr):
2470
+
2471
+ self.runt.confirm(('axon', 'upload'), default=True)
2472
+
2368
2473
  await self.runt.snap.core.getAxon()
2369
2474
  async with await self.runt.snap.core.axon.upload() as upload:
2370
2475
  async for byts in s_coro.agen(genr):
@@ -2378,6 +2483,8 @@ class LibBytes(Lib):
2378
2483
  if sha256 is None:
2379
2484
  return None
2380
2485
 
2486
+ self.runt.confirm(('axon', 'has'), default=True)
2487
+
2381
2488
  await self.runt.snap.core.getAxon()
2382
2489
  todo = s_common.todo('has', s_common.uhex(sha256))
2383
2490
  ret = await self.dyncall('axon', todo)
@@ -2386,6 +2493,9 @@ class LibBytes(Lib):
2386
2493
  @stormfunc(readonly=True)
2387
2494
  async def _libBytesSize(self, sha256):
2388
2495
  sha256 = await tostr(sha256)
2496
+
2497
+ self.runt.confirm(('axon', 'has'), default=True)
2498
+
2389
2499
  await self.runt.snap.core.getAxon()
2390
2500
  todo = s_common.todo('size', s_common.uhex(sha256))
2391
2501
  ret = await self.dyncall('axon', todo)
@@ -2396,6 +2506,8 @@ class LibBytes(Lib):
2396
2506
  mesg = '$lib.bytes.put() requires a bytes argument'
2397
2507
  raise s_exc.BadArg(mesg=mesg)
2398
2508
 
2509
+ self.runt.confirm(('axon', 'upload'), default=True)
2510
+
2399
2511
  await self.runt.snap.core.getAxon()
2400
2512
  todo = s_common.todo('put', byts)
2401
2513
  size, sha2 = await self.dyncall('axon', todo)
@@ -2405,6 +2517,9 @@ class LibBytes(Lib):
2405
2517
  @stormfunc(readonly=True)
2406
2518
  async def _libBytesHashset(self, sha256):
2407
2519
  sha256 = await tostr(sha256)
2520
+
2521
+ self.runt.confirm(('axon', 'has'), default=True)
2522
+
2408
2523
  await self.runt.snap.core.getAxon()
2409
2524
  todo = s_common.todo('hashset', s_common.uhex(sha256))
2410
2525
  ret = await self.dyncall('axon', todo)
@@ -3207,7 +3322,7 @@ class Pipe(StormType):
3207
3322
  {'name': 'put', 'desc': 'Add a single item to the Pipe.',
3208
3323
  'type': {'type': 'function', '_funcname': '_methPipePut',
3209
3324
  'args': (
3210
- {'name': 'item', 'type': 'any', 'desc': ' An object to add to the Pipe.', },
3325
+ {'name': 'item', 'type': 'any', 'desc': 'An object to add to the Pipe.', },
3211
3326
  ),
3212
3327
  'returns': {'type': 'null', }}},
3213
3328
  {'name': 'puts', 'desc': 'Add a list of items to the Pipe.',
@@ -4056,6 +4171,9 @@ class Str(Prim):
4056
4171
  'desc': 'Keyword values which are substituted into the string.', },
4057
4172
  ),
4058
4173
  'returns': {'type': 'str', 'desc': 'The new string.', }}},
4174
+ {'name': 'json', 'desc': 'Parse a JSON string and return the deserialized data.',
4175
+ 'type': {'type': 'function', '_funcname': '_methStrJson', 'args': (),
4176
+ 'returns': {'type': 'prim', 'desc': 'The JSON deserialized object.', }}},
4059
4177
  )
4060
4178
  _storm_typename = 'str'
4061
4179
  _ismutable = False
@@ -4085,6 +4203,7 @@ class Str(Prim):
4085
4203
  'slice': self._methStrSlice,
4086
4204
  'reverse': self._methStrReverse,
4087
4205
  'format': self._methStrFormat,
4206
+ 'json': self._methStrJson,
4088
4207
  }
4089
4208
 
4090
4209
  def __int__(self):
@@ -4200,6 +4319,14 @@ class Str(Prim):
4200
4319
  async def _methStrReverse(self):
4201
4320
  return self.valu[::-1]
4202
4321
 
4322
+ @stormfunc(readonly=True)
4323
+ async def _methStrJson(self):
4324
+ try:
4325
+ return json.loads(self.valu, strict=True)
4326
+ except Exception as e:
4327
+ mesg = f'Text is not valid JSON: {self.valu}'
4328
+ raise s_exc.BadJsonText(mesg=mesg)
4329
+
4203
4330
  @registry.registerType
4204
4331
  class Bytes(Prim):
4205
4332
  '''
@@ -5536,7 +5663,7 @@ class NodeData(Prim):
5536
5663
  {'name': 'name', 'type': 'str', 'desc': 'Name of the data to get.', },
5537
5664
  ),
5538
5665
  'returns': {'type': 'prim', 'desc': 'The stored node data.', }}},
5539
- {'name': 'pop', 'desc': ' Pop (remove) a the Node data from the Node.',
5666
+ {'name': 'pop', 'desc': 'Pop (remove) a the Node data from the Node.',
5540
5667
  'type': {'type': 'function', '_funcname': '_popNodeData',
5541
5668
  'args': (
5542
5669
  {'name': 'name', 'type': 'str', 'desc': 'The name of the data to remove from the node.', },
@@ -7591,6 +7718,8 @@ class View(Prim):
7591
7718
  mesg = 'You are not a member of a role with voting privileges for this merge request.'
7592
7719
  raise s_exc.AuthDeny(mesg=mesg)
7593
7720
 
7721
+ view.reqValidVoter(self.runt.user.iden)
7722
+
7594
7723
  vote = {'user': self.runt.user.iden, 'approved': await tobool(approved)}
7595
7724
 
7596
7725
  if comment is not None:
synapse/lib/version.py CHANGED
@@ -223,6 +223,6 @@ def reqVersion(valu, reqver,
223
223
  ##############################################################################
224
224
  # The following are touched during the release process by bumpversion.
225
225
  # Do not modify these directly.
226
- version = (2, 161, 0)
226
+ version = (2, 162, 0)
227
227
  verstring = '.'.join([str(x) for x in version])
228
- commit = '75088cc330913fd191ab36204058097fbfb5c38c'
228
+ commit = '4f4fa04685adceb99beb700b2d51b0f99afe801b'
synapse/lib/view.py CHANGED
@@ -268,15 +268,28 @@ class View(s_nexus.Pusher): # type: ignore
268
268
  vote['offset'] = await self.layers[0].getEditIndx()
269
269
  return await self._push('merge:vote:set', vote)
270
270
 
271
+ def reqValidVoter(self, useriden):
272
+
273
+ merge = self.getMergeRequest()
274
+ if merge is None:
275
+ raise s_exc.BadState(mesg=f'View ({self.iden}) does not have a merge request.')
276
+
277
+ if merge.get('creator') == useriden:
278
+ raise s_exc.AuthDeny(mesg='A user may not vote for their own merge request.')
279
+
271
280
  @s_nexus.Pusher.onPush('merge:vote:set')
272
281
  async def _setMergeVote(self, vote):
273
282
 
274
283
  self.reqParentQuorum()
275
284
  s_schemas.reqValidVote(vote)
276
285
 
277
- uidn = s_common.uhex(vote.get('user'))
286
+ useriden = vote.get('user')
287
+
288
+ self.reqValidVoter(useriden)
289
+
290
+ bidn = s_common.uhex(useriden)
278
291
 
279
- self.core.slab.put(self.bidn + b'merge:vote' + uidn, s_msgpack.en(vote), db='view:meta')
292
+ self.core.slab.put(self.bidn + b'merge:vote' + bidn, s_msgpack.en(vote), db='view:meta')
280
293
 
281
294
  await self.core.feedBeholder('view:merge:vote:set', {'view': self.iden, 'vote': vote})
282
295
 
synapse/models/inet.py CHANGED
@@ -1560,6 +1560,15 @@ class InetModule(s_module.CoreModule):
1560
1560
  ('headers', ('array', {'type': 'inet:email:header'}), {
1561
1561
  'doc': 'An array of email headers from the message.'}),
1562
1562
 
1563
+ ('received:from:ipv4', ('inet:ipv4', {}), {
1564
+ 'doc': 'The sending SMTP server IPv4, potentially from the Received: header.'}),
1565
+
1566
+ ('received:from:ipv6', ('inet:ipv6', {}), {
1567
+ 'doc': 'The sending SMTP server IPv6, potentially from the Received: header.'}),
1568
+
1569
+ ('received:from:fqdn', ('inet:fqdn', {}), {
1570
+ 'doc': 'The sending server FQDN, potentially from the Received: header.'}),
1571
+
1563
1572
  )),
1564
1573
 
1565
1574
  ('inet:email:header', {}, (