synapse 2.161.0__py311-none-any.whl → 2.163.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 (53) hide show
  1. synapse/axon.py +48 -40
  2. synapse/cortex.py +4 -0
  3. synapse/daemon.py +7 -2
  4. synapse/lib/cell.py +70 -3
  5. synapse/lib/layer.py +20 -1
  6. synapse/lib/oauth.py +1 -7
  7. synapse/lib/rstorm.py +16 -0
  8. synapse/lib/schemas.py +10 -0
  9. synapse/lib/storm.py +17 -1
  10. synapse/lib/stormhttp.py +52 -28
  11. synapse/lib/stormlib/stix.py +6 -3
  12. synapse/lib/stormtypes.py +336 -26
  13. synapse/lib/version.py +2 -2
  14. synapse/lib/view.py +15 -2
  15. synapse/models/inet.py +9 -0
  16. synapse/models/infotech.py +28 -26
  17. synapse/models/orgs.py +3 -0
  18. synapse/models/proj.py +9 -2
  19. synapse/models/risk.py +32 -0
  20. synapse/telepath.py +2 -0
  21. synapse/tests/files/rstorm/testsvc.py +8 -1
  22. synapse/tests/files/stormpkg/testpkg.yaml +4 -0
  23. synapse/tests/test_axon.py +4 -4
  24. synapse/tests/test_cortex.py +8 -8
  25. synapse/tests/test_daemon.py +19 -0
  26. synapse/tests/test_lib_ast.py +17 -17
  27. synapse/tests/test_lib_grammar.py +4 -4
  28. synapse/tests/test_lib_rstorm.py +38 -2
  29. synapse/tests/test_lib_storm.py +15 -15
  30. synapse/tests/test_lib_stormhttp.py +182 -19
  31. synapse/tests/test_lib_stormlib_auth.py +3 -3
  32. synapse/tests/test_lib_stormlib_cell.py +1 -1
  33. synapse/tests/test_lib_stormlib_cortex.py +50 -2
  34. synapse/tests/test_lib_stormlib_json.py +2 -2
  35. synapse/tests/test_lib_stormlib_macro.py +1 -1
  36. synapse/tests/test_lib_stormlib_modelext.py +37 -37
  37. synapse/tests/test_lib_stormlib_oauth.py +20 -20
  38. synapse/tests/test_lib_stormlib_stix.py +3 -1
  39. synapse/tests/test_lib_stormtypes.py +159 -52
  40. synapse/tests/test_lib_stormwhois.py +1 -1
  41. synapse/tests/test_lib_trigger.py +11 -11
  42. synapse/tests/test_lib_view.py +23 -1
  43. synapse/tests/test_model_crypto.py +1 -1
  44. synapse/tests/test_model_inet.py +6 -0
  45. synapse/tests/test_model_orgs.py +2 -1
  46. synapse/tests/test_model_proj.py +6 -0
  47. synapse/tests/test_model_risk.py +10 -0
  48. synapse/tests/test_tools_storm.py +1 -1
  49. {synapse-2.161.0.dist-info → synapse-2.163.0.dist-info}/METADATA +3 -1
  50. {synapse-2.161.0.dist-info → synapse-2.163.0.dist-info}/RECORD +53 -53
  51. {synapse-2.161.0.dist-info → synapse-2.163.0.dist-info}/LICENSE +0 -0
  52. {synapse-2.161.0.dist-info → synapse-2.163.0.dist-info}/WHEEL +0 -0
  53. {synapse-2.161.0.dist-info → synapse-2.163.0.dist-info}/top_level.txt +0 -0
synapse/lib/stormtypes.py CHANGED
@@ -34,12 +34,17 @@ import synapse.lib.scope as s_scope
34
34
  import synapse.lib.msgpack as s_msgpack
35
35
  import synapse.lib.trigger as s_trigger
36
36
  import synapse.lib.urlhelp as s_urlhelp
37
+ import synapse.lib.version as s_version
37
38
  import synapse.lib.stormctrl as s_stormctrl
38
39
  import synapse.lib.provenance as s_provenance
39
40
 
40
41
  logger = logging.getLogger(__name__)
41
42
 
43
+ AXON_MINVERS_PROXY = (2, 97, 0)
44
+ AXON_MINVERS_SSLOPTS = '>=2.162.0'
45
+
42
46
  class Undef:
47
+ _storm_typename = 'undef'
43
48
  async def stormrepr(self):
44
49
  return '$lib.undef'
45
50
 
@@ -1085,13 +1090,6 @@ class LibBase(Lib):
1085
1090
  {'name': '*vals', 'type': 'any', 'desc': 'Initial values to place in the set.', },
1086
1091
  ),
1087
1092
  '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
1093
  {'name': 'exit', 'desc': 'Cause a Storm Runtime to stop running.',
1096
1094
  'type': {'type': 'function', '_funcname': '_exit',
1097
1095
  'args': (
@@ -1103,6 +1101,8 @@ class LibBase(Lib):
1103
1101
  'type': {'type': 'function', '_funcname': '_guid',
1104
1102
  'args': (
1105
1103
  {'name': '*args', 'type': 'prim', 'desc': 'Arguments which are hashed to create a guid.', },
1104
+ {'name': 'valu', 'type': 'prim', 'default': '$lib.undef',
1105
+ 'desc': 'Create a guid from a single value (no positional arguments can be specified).', },
1106
1106
  ),
1107
1107
  'returns': {'type': 'str', 'desc': 'A guid.', }}},
1108
1108
  {'name': 'fire', 'desc': '''
@@ -1147,7 +1147,7 @@ class LibBase(Lib):
1147
1147
  Examples:
1148
1148
  Create a dictionary object with a key whose value is null, and call ``$lib.fire()`` with it::
1149
1149
 
1150
- cli> storm $d=$lib.dict(key=$lib.null) $lib.fire('demo', d=$d)
1150
+ cli> storm $d=({"key": $lib.null}) $lib.fire('demo', d=$d)
1151
1151
  ('storm:fire', {'type': 'demo', 'data': {'d': {'key': None}}})
1152
1152
  ''',
1153
1153
  'type': 'null', },
@@ -1227,7 +1227,7 @@ class LibBase(Lib):
1227
1227
 
1228
1228
  Format and print string based on variables::
1229
1229
 
1230
- cli> storm $d=$lib.dict(key1=(1), key2="two")
1230
+ cli> storm $d=({"key1": (1), "key2": "two"})
1231
1231
  for ($key, $value) in $d { $lib.print('{k} => {v}', k=$key, v=$value) }
1232
1232
  key1 => 1
1233
1233
  key2 => two
@@ -1377,7 +1377,6 @@ class LibBase(Lib):
1377
1377
  'max': self._max,
1378
1378
  'set': self._set,
1379
1379
  'copy': self._copy,
1380
- 'dict': self._dict,
1381
1380
  'exit': self._exit,
1382
1381
  'guid': self._guid,
1383
1382
  'fire': self._fire,
@@ -1550,10 +1549,17 @@ class LibBase(Lib):
1550
1549
  return Text(valu)
1551
1550
 
1552
1551
  @stormfunc(readonly=True)
1553
- async def _guid(self, *args):
1552
+ async def _guid(self, *args, valu=undef):
1554
1553
  if args:
1554
+ if valu is not undef:
1555
+ raise s_exc.BadArg(mesg='Valu cannot be specified if positional arguments are provided')
1555
1556
  args = await toprim(args)
1556
1557
  return s_common.guid(args)
1558
+
1559
+ if valu is not undef:
1560
+ valu = await toprim(valu)
1561
+ return s_common.guid(valu)
1562
+
1557
1563
  return s_common.guid()
1558
1564
 
1559
1565
  @stormfunc(readonly=True)
@@ -1677,16 +1683,125 @@ class LibBase(Lib):
1677
1683
  mesg = await self._get_mesg(mesg, **kwargs)
1678
1684
  await self.runt.warn(mesg, log=False)
1679
1685
 
1680
- @stormfunc(readonly=True)
1681
- async def _dict(self, **kwargs):
1682
- return Dict(kwargs)
1683
-
1684
1686
  @stormfunc(readonly=True)
1685
1687
  async def _fire(self, name, **info):
1686
1688
  info = await toprim(info)
1687
1689
  s_common.reqjsonsafe(info)
1688
1690
  await self.runt.snap.fire('storm:fire', type=name, data=info)
1689
1691
 
1692
+ @registry.registerLib
1693
+ class LibDict(Lib):
1694
+ '''
1695
+ A Storm Library for interacting with dictionaries.
1696
+ '''
1697
+ _storm_locals = (
1698
+ {'name': 'keys', 'desc': 'Retrieve a list of keys in the specified dictionary.',
1699
+ 'type': {'type': 'function', '_funcname': '_keys',
1700
+ 'args': (
1701
+ {'name': 'valu', 'type': 'dict', 'desc': 'The dictionary to operate on.'},
1702
+ ),
1703
+ 'returns': {'type': 'list', 'desc': 'List of keys in the specified dictionary.', }}},
1704
+ {'name': 'pop', 'desc': 'Remove specified key and return the corresponding value.',
1705
+ 'type': {'type': 'function', '_funcname': '_pop',
1706
+ 'args': (
1707
+ {'name': 'valu', 'type': 'dict', 'desc': 'The dictionary to operate on.'},
1708
+ {'name': 'key', 'type': 'str', 'desc': 'The key name of the value to pop.'},
1709
+ {'name': 'default', 'type': 'any', 'default': '$lib.undef',
1710
+ 'desc': 'Optional default value to return if the key does not exist in the dictionary.'},
1711
+ ),
1712
+ 'returns': {'type': 'any', 'desc': 'The popped value.', }}},
1713
+ {'name': 'update', 'desc': 'Update the specified dictionary with keys/values from another dictionary.',
1714
+ 'type': {'type': 'function', '_funcname': '_update',
1715
+ 'args': (
1716
+ {'name': 'valu', 'type': 'dict', 'desc': 'The target dictionary (update to).'},
1717
+ {'name': 'other', 'type': 'dict', 'desc': 'The source dictionary (update from).'},
1718
+ ),
1719
+ 'returns': {'type': 'null'}}},
1720
+ {'name': 'values', 'desc': 'Retrieve a list of values in the specified dictionary.',
1721
+ 'type': {'type': 'function', '_funcname': '_values',
1722
+ 'args': (
1723
+ {'name': 'valu', 'type': 'dict', 'desc': 'The dictionary to operate on.'},
1724
+ ),
1725
+ 'returns': {'type': 'list', 'desc': 'List of values in the specified dictionary.', }}},
1726
+ )
1727
+ _storm_lib_path = ('dict',)
1728
+
1729
+ def getObjLocals(self):
1730
+ return {
1731
+ 'has': self._has,
1732
+ 'keys': self._keys,
1733
+ 'pop': self._pop,
1734
+ 'update': self._update,
1735
+ 'values': self._values,
1736
+ }
1737
+
1738
+ async def _check_type(self, valu, name='valu'):
1739
+ if isinstance(valu, (dict, Dict)):
1740
+ return
1741
+
1742
+ typ = getattr(valu, '_storm_typename', None)
1743
+ if typ is None:
1744
+ prim = await toprim(valu)
1745
+ typ = type(prim).__name__
1746
+
1747
+ mesg = f'{name} argument must be a dict, not {typ}.'
1748
+ raise s_exc.BadArg(mesg=mesg)
1749
+
1750
+ @stormfunc(readonly=True)
1751
+ async def _has(self, valu, name):
1752
+ await self._check_type(valu)
1753
+ valu = await toprim(valu)
1754
+ return name in valu
1755
+
1756
+ @stormfunc(readonly=True)
1757
+ async def _keys(self, valu):
1758
+ await self._check_type(valu)
1759
+ valu = await toprim(valu)
1760
+ return list(valu.keys())
1761
+
1762
+ @stormfunc(readonly=True)
1763
+ async def _pop(self, valu, key, default=undef):
1764
+ await self._check_type(valu)
1765
+
1766
+ real = await toprim(valu)
1767
+ key = await tostr(key)
1768
+
1769
+ if key not in real:
1770
+ if default == undef:
1771
+ mesg = f'Key {key} does not exist in dictionary.'
1772
+ raise s_exc.BadArg(mesg=mesg)
1773
+ return await toprim(default)
1774
+
1775
+ # Make sure we have a storm Dict
1776
+ valu = fromprim(valu)
1777
+
1778
+ ret = await valu.deref(key)
1779
+ await valu.setitem(key, undef)
1780
+ return ret
1781
+
1782
+ @stormfunc(readonly=True)
1783
+ async def _update(self, valu, other):
1784
+ await self._check_type(valu)
1785
+ await self._check_type(other, name='other')
1786
+
1787
+ valu = fromprim(valu)
1788
+ other = await toprim(other)
1789
+
1790
+ for k, v in other.items():
1791
+ await valu.setitem(k, v)
1792
+
1793
+ @stormfunc(readonly=True)
1794
+ async def _values(self, valu):
1795
+ await self._check_type(valu)
1796
+
1797
+ valu = await toprim(valu)
1798
+ return list(valu.values())
1799
+
1800
+ async def __call__(self, **kwargs):
1801
+ s_common.deprecated('$lib.dict()', curv='2.161.0')
1802
+ await self.runt.snap.warnonce('$lib.dict() is deprecated. Use ({}) instead.')
1803
+ return Dict(kwargs)
1804
+
1690
1805
  @registry.registerLib
1691
1806
  class LibPs(Lib):
1692
1807
  '''
@@ -1700,7 +1815,7 @@ class LibPs(Lib):
1700
1815
  'desc': 'The prefix of the task to stop. '
1701
1816
  'Tasks will only be stopped if there is a single prefix match.'},
1702
1817
  ),
1703
- 'returns': {'type': 'boolean', 'desc': ' True if the task was cancelled, False otherwise.', }}},
1818
+ 'returns': {'type': 'boolean', 'desc': 'True if the task was cancelled, False otherwise.', }}},
1704
1819
  {'name': 'list', 'desc': 'List tasks the current user can access.',
1705
1820
  'type': {'type': 'function', '_funcname': '_list',
1706
1821
  'returns': {'type': 'list', 'desc': 'A list of task definitions.', }}},
@@ -1814,6 +1929,14 @@ class LibStr(Lib):
1814
1929
  class LibAxon(Lib):
1815
1930
  '''
1816
1931
  A Storm library for interacting with the Cortex's Axon.
1932
+
1933
+ For APIs that accept an ssl_opts argument, the dictionary may contain the following values::
1934
+
1935
+ {
1936
+ 'verify': <bool> - Perform SSL/TLS verification. Is overridden by the ssl argument.
1937
+ 'client_cert': <str> - PEM encoded full chain certificate for use in mTLS.
1938
+ 'client_key': <str> - PEM encoded key for use in mTLS. Alternatively, can be included in client_cert.
1939
+ }
1817
1940
  '''
1818
1941
  _storm_locals = (
1819
1942
  {'name': 'wget', 'desc': """
@@ -1826,7 +1949,7 @@ class LibAxon(Lib):
1826
1949
  Example:
1827
1950
  Get the Vertex Project website::
1828
1951
 
1829
- $headers = $lib.dict()
1952
+ $headers = ({})
1830
1953
  $headers."User-Agent" = Foo/Bar
1831
1954
 
1832
1955
  $resp = $lib.axon.wget("http://vertex.link", method=GET, headers=$headers)
@@ -1849,6 +1972,9 @@ class LibAxon(Lib):
1849
1972
  'default': None},
1850
1973
  {'name': 'proxy', 'type': ['bool', 'null', 'str'],
1851
1974
  'desc': 'Set to a proxy URL string or $lib.false to disable proxy use.', 'default': None},
1975
+ {'name': 'ssl_opts', 'type': 'dict',
1976
+ 'desc': 'Optional SSL/TLS options. See $lib.axon help for additional details.',
1977
+ 'default': None},
1852
1978
  ),
1853
1979
  'returns': {'type': 'dict', 'desc': 'A status dictionary of metadata.'}}},
1854
1980
  {'name': 'wput', 'desc': """
@@ -1869,10 +1995,13 @@ class LibAxon(Lib):
1869
1995
  'default': None},
1870
1996
  {'name': 'proxy', 'type': ['bool', 'null', 'str'],
1871
1997
  'desc': 'Set to a proxy URL string or $lib.false to disable proxy use.', 'default': None},
1998
+ {'name': 'ssl_opts', 'type': 'dict',
1999
+ 'desc': 'Optional SSL/TLS options. See $lib.axon help for additional details.',
2000
+ 'default': None},
1872
2001
  ),
1873
2002
  'returns': {'type': 'dict', 'desc': 'A status dictionary of metadata.'}}},
1874
2003
  {'name': 'urlfile', 'desc': '''
1875
- Retrive the target URL using the wget() function and construct an inet:urlfile node from the response.
2004
+ Retrieve the target URL using the wget() function and construct an inet:urlfile node from the response.
1876
2005
 
1877
2006
  Notes:
1878
2007
  This accepts the same arguments as ``$lib.axon.wget()``.
@@ -2019,6 +2148,82 @@ class LibAxon(Lib):
2019
2148
  ''',
2020
2149
  'type': {'type': 'function', '_funcname': 'metrics',
2021
2150
  'returns': {'type': 'dict', 'desc': 'A dictionary containing runtime data about the Axon.'}}},
2151
+ {'name': 'put', 'desc': '''
2152
+ Save the given bytes variable to the Axon the Cortex is configured to use.
2153
+
2154
+ Examples:
2155
+ Save a base64 encoded buffer to the Axon::
2156
+
2157
+ cli> storm $s='dGVzdA==' $buf=$lib.base64.decode($s) ($size, $sha256)=$lib.axon.put($buf)
2158
+ $lib.print('size={size} sha256={sha256}', size=$size, sha256=$sha256)
2159
+
2160
+ size=4 sha256=9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08''',
2161
+ 'type': {'type': 'function', '_funcname': 'put',
2162
+ 'args': (
2163
+ {'name': 'byts', 'type': 'bytes', 'desc': 'The bytes to save.', },
2164
+ ),
2165
+ 'returns': {'type': 'list', 'desc': 'A tuple of the file size and sha256 value.', }}},
2166
+ {'name': 'has', 'desc': '''
2167
+ Check if the Axon the Cortex is configured to use has a given sha256 value.
2168
+
2169
+ Examples:
2170
+ Check if the Axon has a given file::
2171
+
2172
+ # This example assumes the Axon does have the bytes
2173
+ cli> storm if $lib.axon.has(9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08) {
2174
+ $lib.print("Has bytes")
2175
+ } else {
2176
+ $lib.print("Does not have bytes")
2177
+ }
2178
+
2179
+ Has bytes
2180
+ ''',
2181
+ 'type': {'type': 'function', '_funcname': 'has',
2182
+ 'args': (
2183
+ {'name': 'sha256', 'type': 'str', 'desc': 'The sha256 value to check.', },
2184
+ ),
2185
+ 'returns': {'type': 'boolean', 'desc': 'True if the Axon has the file, false if it does not.', }}},
2186
+ {'name': 'size', 'desc': '''
2187
+ Return the size of the bytes stored in the Axon for the given sha256.
2188
+
2189
+ Examples:
2190
+ Get the size for a file given a variable named ``$sha256``::
2191
+
2192
+ $size = $lib.axon.size($sha256)
2193
+ ''',
2194
+ 'type': {'type': 'function', '_funcname': 'size',
2195
+ 'args': (
2196
+ {'name': 'sha256', 'type': 'str', 'desc': 'The sha256 value to check.', },
2197
+ ),
2198
+ 'returns': {'type': ['int', 'null'],
2199
+ 'desc': 'The size of the file or ``null`` if the file is not found.', }}},
2200
+ {'name': 'hashset', 'desc': '''
2201
+ Return additional hashes of the bytes stored in the Axon for the given sha256.
2202
+
2203
+ Examples:
2204
+ Get the md5 hash for a file given a variable named ``$sha256``::
2205
+
2206
+ $hashset = $lib.axon.hashset($sha256)
2207
+ $md5 = $hashset.md5
2208
+ ''',
2209
+ 'type': {'type': 'function', '_funcname': 'hashset',
2210
+ 'args': (
2211
+ {'name': 'sha256', 'type': 'str', 'desc': 'The sha256 value to calculate hashes for.', },
2212
+ ),
2213
+ 'returns': {'type': 'dict', 'desc': 'A dictionary of additional hashes.', }}},
2214
+ {'name': 'upload', 'desc': '''
2215
+ Upload a stream of bytes to the Axon as a file.
2216
+
2217
+ Examples:
2218
+ Upload bytes from a generator::
2219
+
2220
+ ($size, $sha256) = $lib.axon.upload($getBytesChunks())
2221
+ ''',
2222
+ 'type': {'type': 'function', '_funcname': 'upload',
2223
+ 'args': (
2224
+ {'name': 'genr', 'type': 'generator', 'desc': 'A generator which yields bytes.', },
2225
+ ),
2226
+ 'returns': {'type': 'list', 'desc': 'A tuple of the file size and sha256 value.', }}},
2022
2227
  )
2023
2228
  _storm_lib_path = ('axon',)
2024
2229
  _storm_lib_perms = (
@@ -2046,6 +2251,11 @@ class LibAxon(Lib):
2046
2251
  'jsonlines': self.jsonlines,
2047
2252
  'csvrows': self.csvrows,
2048
2253
  'metrics': self.metrics,
2254
+ 'put': self.put,
2255
+ 'has': self.has,
2256
+ 'size': self.size,
2257
+ 'upload': self.upload,
2258
+ 'hashset': self.hashset,
2049
2259
  }
2050
2260
 
2051
2261
  def strify(self, item):
@@ -2105,7 +2315,8 @@ class LibAxon(Lib):
2105
2315
  axon = self.runt.snap.core.axon
2106
2316
  return await axon.del_(sha256b)
2107
2317
 
2108
- async def wget(self, url, headers=None, params=None, method='GET', json=None, body=None, ssl=True, timeout=None, proxy=None):
2318
+ async def wget(self, url, headers=None, params=None, method='GET', json=None, body=None,
2319
+ ssl=True, timeout=None, proxy=None, ssl_opts=None):
2109
2320
 
2110
2321
  if not self.runt.allowed(('axon', 'wget')):
2111
2322
  self.runt.confirm(('storm', 'lib', 'axon', 'wget'))
@@ -2120,6 +2331,7 @@ class LibAxon(Lib):
2120
2331
  headers = await toprim(headers)
2121
2332
  timeout = await toprim(timeout)
2122
2333
  proxy = await toprim(proxy)
2334
+ ssl_opts = await toprim(ssl_opts)
2123
2335
 
2124
2336
  if proxy is not None:
2125
2337
  self.runt.confirm(('storm', 'lib', 'inet', 'http', 'proxy'))
@@ -2131,16 +2343,23 @@ class LibAxon(Lib):
2131
2343
 
2132
2344
  kwargs = {}
2133
2345
  axonvers = self.runt.snap.core.axoninfo['synapse']['version']
2134
- if axonvers >= (2, 97, 0):
2346
+ if axonvers >= AXON_MINVERS_PROXY:
2135
2347
  kwargs['proxy'] = proxy
2136
2348
 
2349
+ if ssl_opts is not None:
2350
+ mesg = f'The ssl_opts argument requires an Axon Synapse version {AXON_MINVERS_SSLOPTS}, ' \
2351
+ f'but the Axon is running {axonvers}'
2352
+ s_version.reqVersion(axonvers, AXON_MINVERS_SSLOPTS, mesg=mesg)
2353
+ kwargs['ssl_opts'] = ssl_opts
2354
+
2137
2355
  axon = self.runt.snap.core.axon
2138
2356
  resp = await axon.wget(url, headers=headers, params=params, method=method, ssl=ssl, body=body, json=json,
2139
2357
  timeout=timeout, **kwargs)
2140
2358
  resp['original_url'] = url
2141
2359
  return resp
2142
2360
 
2143
- async def wput(self, sha256, url, headers=None, params=None, method='PUT', ssl=True, timeout=None, proxy=None):
2361
+ async def wput(self, sha256, url, headers=None, params=None, method='PUT',
2362
+ ssl=True, timeout=None, proxy=None, ssl_opts=None):
2144
2363
 
2145
2364
  if not self.runt.allowed(('axon', 'wput')):
2146
2365
  self.runt.confirm(('storm', 'lib', 'axon', 'wput'))
@@ -2154,6 +2373,7 @@ class LibAxon(Lib):
2154
2373
  params = await toprim(params)
2155
2374
  headers = await toprim(headers)
2156
2375
  timeout = await toprim(timeout)
2376
+ ssl_opts = await toprim(ssl_opts)
2157
2377
 
2158
2378
  params = self.strify(params)
2159
2379
  headers = self.strify(headers)
@@ -2166,10 +2386,17 @@ class LibAxon(Lib):
2166
2386
 
2167
2387
  kwargs = {}
2168
2388
  axonvers = self.runt.snap.core.axoninfo['synapse']['version']
2169
- if axonvers >= (2, 97, 0):
2389
+ if axonvers >= AXON_MINVERS_PROXY:
2170
2390
  kwargs['proxy'] = proxy
2171
2391
 
2172
- return await axon.wput(sha256byts, url, headers=headers, params=params, method=method, ssl=ssl, timeout=timeout, **kwargs)
2392
+ if ssl_opts is not None:
2393
+ mesg = f'The ssl_opts argument requires an Axon Synapse version {AXON_MINVERS_SSLOPTS}, ' \
2394
+ f'but the Axon is running {axonvers}'
2395
+ s_version.reqVersion(axonvers, AXON_MINVERS_SSLOPTS, mesg=mesg)
2396
+ kwargs['ssl_opts'] = ssl_opts
2397
+
2398
+ return await axon.wput(sha256byts, url, headers=headers, params=params, method=method,
2399
+ ssl=ssl, timeout=timeout, **kwargs)
2173
2400
 
2174
2401
  async def urlfile(self, *args, **kwargs):
2175
2402
  gateiden = self.runt.snap.wlyr.iden
@@ -2270,10 +2497,62 @@ class LibAxon(Lib):
2270
2497
  self.runt.confirm(('storm', 'lib', 'axon', 'has'))
2271
2498
  return await self.runt.snap.core.axon.metrics()
2272
2499
 
2500
+ async def upload(self, genr):
2501
+
2502
+ self.runt.confirm(('axon', 'upload'))
2503
+
2504
+ await self.runt.snap.core.getAxon()
2505
+ async with await self.runt.snap.core.axon.upload() as upload:
2506
+ async for byts in s_coro.agen(genr):
2507
+ await upload.write(byts)
2508
+ size, sha256 = await upload.save()
2509
+ return size, s_common.ehex(sha256)
2510
+
2511
+ @stormfunc(readonly=True)
2512
+ async def has(self, sha256):
2513
+ sha256 = await tostr(sha256, noneok=True)
2514
+ if sha256 is None:
2515
+ return None
2516
+
2517
+ self.runt.confirm(('axon', 'has'))
2518
+
2519
+ await self.runt.snap.core.getAxon()
2520
+ return await self.runt.snap.core.axon.has(s_common.uhex(sha256))
2521
+
2522
+ @stormfunc(readonly=True)
2523
+ async def size(self, sha256):
2524
+ sha256 = await tostr(sha256)
2525
+
2526
+ self.runt.confirm(('axon', 'has'))
2527
+
2528
+ await self.runt.snap.core.getAxon()
2529
+ return await self.runt.snap.core.axon.size(s_common.uhex(sha256))
2530
+
2531
+ async def put(self, byts):
2532
+ if not isinstance(byts, bytes):
2533
+ mesg = '$lib.axon.put() requires a bytes argument'
2534
+ raise s_exc.BadArg(mesg=mesg)
2535
+
2536
+ self.runt.confirm(('axon', 'upload'))
2537
+
2538
+ await self.runt.snap.core.getAxon()
2539
+ size, sha256 = await self.runt.snap.core.axon.put(byts)
2540
+
2541
+ return (size, s_common.ehex(sha256))
2542
+
2543
+ @stormfunc(readonly=True)
2544
+ async def hashset(self, sha256):
2545
+ sha256 = await tostr(sha256)
2546
+
2547
+ self.runt.confirm(('axon', 'has'))
2548
+
2549
+ await self.runt.snap.core.getAxon()
2550
+ return await self.runt.snap.core.axon.hashset(s_common.uhex(sha256))
2551
+
2273
2552
  @registry.registerLib
2274
2553
  class LibBytes(Lib):
2275
2554
  '''
2276
- A Storm Library for interacting with bytes storage.
2555
+ A Storm Library for interacting with bytes storage. This Library is deprecated; use ``$lib.axon.*`` instead.
2277
2556
  '''
2278
2557
  _storm_locals = (
2279
2558
  {'name': 'put', 'desc': '''
@@ -2365,6 +2644,9 @@ class LibBytes(Lib):
2365
2644
  }
2366
2645
 
2367
2646
  async def _libBytesUpload(self, genr):
2647
+
2648
+ self.runt.confirm(('axon', 'upload'), default=True)
2649
+
2368
2650
  await self.runt.snap.core.getAxon()
2369
2651
  async with await self.runt.snap.core.axon.upload() as upload:
2370
2652
  async for byts in s_coro.agen(genr):
@@ -2374,10 +2656,13 @@ class LibBytes(Lib):
2374
2656
 
2375
2657
  @stormfunc(readonly=True)
2376
2658
  async def _libBytesHas(self, sha256):
2659
+
2377
2660
  sha256 = await tostr(sha256, noneok=True)
2378
2661
  if sha256 is None:
2379
2662
  return None
2380
2663
 
2664
+ self.runt.confirm(('axon', 'has'), default=True)
2665
+
2381
2666
  await self.runt.snap.core.getAxon()
2382
2667
  todo = s_common.todo('has', s_common.uhex(sha256))
2383
2668
  ret = await self.dyncall('axon', todo)
@@ -2385,17 +2670,24 @@ class LibBytes(Lib):
2385
2670
 
2386
2671
  @stormfunc(readonly=True)
2387
2672
  async def _libBytesSize(self, sha256):
2673
+
2388
2674
  sha256 = await tostr(sha256)
2675
+
2676
+ self.runt.confirm(('axon', 'has'), default=True)
2677
+
2389
2678
  await self.runt.snap.core.getAxon()
2390
2679
  todo = s_common.todo('size', s_common.uhex(sha256))
2391
2680
  ret = await self.dyncall('axon', todo)
2392
2681
  return ret
2393
2682
 
2394
2683
  async def _libBytesPut(self, byts):
2684
+
2395
2685
  if not isinstance(byts, bytes):
2396
2686
  mesg = '$lib.bytes.put() requires a bytes argument'
2397
2687
  raise s_exc.BadArg(mesg=mesg)
2398
2688
 
2689
+ self.runt.confirm(('axon', 'upload'), default=True)
2690
+
2399
2691
  await self.runt.snap.core.getAxon()
2400
2692
  todo = s_common.todo('put', byts)
2401
2693
  size, sha2 = await self.dyncall('axon', todo)
@@ -2404,7 +2696,11 @@ class LibBytes(Lib):
2404
2696
 
2405
2697
  @stormfunc(readonly=True)
2406
2698
  async def _libBytesHashset(self, sha256):
2699
+
2407
2700
  sha256 = await tostr(sha256)
2701
+
2702
+ self.runt.confirm(('axon', 'has'), default=True)
2703
+
2408
2704
  await self.runt.snap.core.getAxon()
2409
2705
  todo = s_common.todo('hashset', s_common.uhex(sha256))
2410
2706
  ret = await self.dyncall('axon', todo)
@@ -3207,7 +3503,7 @@ class Pipe(StormType):
3207
3503
  {'name': 'put', 'desc': 'Add a single item to the Pipe.',
3208
3504
  'type': {'type': 'function', '_funcname': '_methPipePut',
3209
3505
  'args': (
3210
- {'name': 'item', 'type': 'any', 'desc': ' An object to add to the Pipe.', },
3506
+ {'name': 'item', 'type': 'any', 'desc': 'An object to add to the Pipe.', },
3211
3507
  ),
3212
3508
  'returns': {'type': 'null', }}},
3213
3509
  {'name': 'puts', 'desc': 'Add a list of items to the Pipe.',
@@ -4056,6 +4352,9 @@ class Str(Prim):
4056
4352
  'desc': 'Keyword values which are substituted into the string.', },
4057
4353
  ),
4058
4354
  'returns': {'type': 'str', 'desc': 'The new string.', }}},
4355
+ {'name': 'json', 'desc': 'Parse a JSON string and return the deserialized data.',
4356
+ 'type': {'type': 'function', '_funcname': '_methStrJson', 'args': (),
4357
+ 'returns': {'type': 'prim', 'desc': 'The JSON deserialized object.', }}},
4059
4358
  )
4060
4359
  _storm_typename = 'str'
4061
4360
  _ismutable = False
@@ -4085,6 +4384,7 @@ class Str(Prim):
4085
4384
  'slice': self._methStrSlice,
4086
4385
  'reverse': self._methStrReverse,
4087
4386
  'format': self._methStrFormat,
4387
+ 'json': self._methStrJson,
4088
4388
  }
4089
4389
 
4090
4390
  def __int__(self):
@@ -4200,6 +4500,14 @@ class Str(Prim):
4200
4500
  async def _methStrReverse(self):
4201
4501
  return self.valu[::-1]
4202
4502
 
4503
+ @stormfunc(readonly=True)
4504
+ async def _methStrJson(self):
4505
+ try:
4506
+ return json.loads(self.valu, strict=True)
4507
+ except Exception as e:
4508
+ mesg = f'Text is not valid JSON: {self.valu}'
4509
+ raise s_exc.BadJsonText(mesg=mesg)
4510
+
4203
4511
  @registry.registerType
4204
4512
  class Bytes(Prim):
4205
4513
  '''
@@ -5536,7 +5844,7 @@ class NodeData(Prim):
5536
5844
  {'name': 'name', 'type': 'str', 'desc': 'Name of the data to get.', },
5537
5845
  ),
5538
5846
  'returns': {'type': 'prim', 'desc': 'The stored node data.', }}},
5539
- {'name': 'pop', 'desc': ' Pop (remove) a the Node data from the Node.',
5847
+ {'name': 'pop', 'desc': 'Pop (remove) a the Node data from the Node.',
5540
5848
  'type': {'type': 'function', '_funcname': '_popNodeData',
5541
5849
  'args': (
5542
5850
  {'name': 'name', 'type': 'str', 'desc': 'The name of the data to remove from the node.', },
@@ -7591,6 +7899,8 @@ class View(Prim):
7591
7899
  mesg = 'You are not a member of a role with voting privileges for this merge request.'
7592
7900
  raise s_exc.AuthDeny(mesg=mesg)
7593
7901
 
7902
+ view.reqValidVoter(self.runt.user.iden)
7903
+
7594
7904
  vote = {'user': self.runt.user.iden, 'approved': await tobool(approved)}
7595
7905
 
7596
7906
  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, 163, 0)
227
227
  verstring = '.'.join([str(x) for x in version])
228
- commit = '75088cc330913fd191ab36204058097fbfb5c38c'
228
+ commit = '72acabe1afe8661274c9e09284ab1726994fa41b'
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', {}, (