synapse 2.176.0__py311-none-any.whl → 2.178.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 (95) hide show
  1. synapse/axon.py +24 -9
  2. synapse/cortex.py +337 -172
  3. synapse/cryotank.py +46 -37
  4. synapse/datamodel.py +17 -4
  5. synapse/exc.py +19 -0
  6. synapse/lib/agenda.py +7 -13
  7. synapse/lib/aha.py +361 -88
  8. synapse/lib/auth.py +1520 -0
  9. synapse/lib/base.py +27 -9
  10. synapse/lib/cell.py +422 -163
  11. synapse/lib/config.py +15 -11
  12. synapse/lib/coro.py +13 -0
  13. synapse/lib/grammar.py +5 -0
  14. synapse/lib/hive.py +24 -3
  15. synapse/lib/hiveauth.py +6 -32
  16. synapse/lib/layer.py +7 -9
  17. synapse/lib/link.py +22 -18
  18. synapse/lib/lmdbslab.py +152 -3
  19. synapse/lib/modelrev.py +1 -1
  20. synapse/lib/nexus.py +24 -12
  21. synapse/lib/schemas.py +136 -0
  22. synapse/lib/storm.py +61 -29
  23. synapse/lib/stormlib/aha.py +1 -1
  24. synapse/lib/stormlib/auth.py +185 -10
  25. synapse/lib/stormlib/cortex.py +16 -5
  26. synapse/lib/stormlib/gen.py +80 -0
  27. synapse/lib/stormlib/imap.py +6 -2
  28. synapse/lib/stormlib/model.py +55 -0
  29. synapse/lib/stormlib/modelext.py +60 -0
  30. synapse/lib/stormlib/smtp.py +12 -2
  31. synapse/lib/stormlib/tabular.py +212 -0
  32. synapse/lib/stormtypes.py +14 -1
  33. synapse/lib/trigger.py +1 -1
  34. synapse/lib/version.py +2 -2
  35. synapse/lib/view.py +55 -28
  36. synapse/models/base.py +7 -0
  37. synapse/models/biz.py +4 -0
  38. synapse/models/files.py +8 -1
  39. synapse/models/inet.py +8 -0
  40. synapse/telepath.py +32 -17
  41. synapse/tests/files/aha/certs/cas/synapse.crt +28 -0
  42. synapse/tests/files/aha/certs/cas/synapse.key +51 -0
  43. synapse/tests/files/aha/certs/hosts/00.aha.loop.vertex.link.crt +30 -0
  44. synapse/tests/files/aha/certs/hosts/00.aha.loop.vertex.link.key +51 -0
  45. synapse/tests/files/aha/certs/users/root@synapse.crt +29 -0
  46. synapse/tests/files/aha/certs/users/root@synapse.key +51 -0
  47. synapse/tests/files/changelog/model_2.176.0_16ee721a6b7221344eaf946c3ab4602dda546b1a.yaml.gz +0 -0
  48. synapse/tests/files/changelog/model_2.176.0_2a25c58bbd344716cd7cbc3f4304d8925b0f4ef2.yaml.gz +0 -0
  49. synapse/tests/files/rstorm/testsvc.py +1 -1
  50. synapse/tests/test_axon.py +8 -5
  51. synapse/tests/test_cortex.py +149 -141
  52. synapse/tests/test_cryotank.py +4 -4
  53. synapse/tests/test_datamodel.py +7 -0
  54. synapse/tests/test_lib_agenda.py +10 -3
  55. synapse/tests/test_lib_aha.py +336 -490
  56. synapse/tests/{test_lib_hiveauth.py → test_lib_auth.py} +314 -11
  57. synapse/tests/test_lib_base.py +20 -0
  58. synapse/tests/test_lib_cell.py +210 -30
  59. synapse/tests/test_lib_config.py +4 -3
  60. synapse/tests/test_lib_httpapi.py +18 -14
  61. synapse/tests/test_lib_layer.py +33 -33
  62. synapse/tests/test_lib_link.py +42 -1
  63. synapse/tests/test_lib_lmdbslab.py +68 -0
  64. synapse/tests/test_lib_nexus.py +12 -4
  65. synapse/tests/test_lib_node.py +0 -7
  66. synapse/tests/test_lib_storm.py +45 -0
  67. synapse/tests/test_lib_stormlib_aha.py +35 -36
  68. synapse/tests/test_lib_stormlib_auth.py +21 -0
  69. synapse/tests/test_lib_stormlib_cell.py +4 -15
  70. synapse/tests/test_lib_stormlib_cortex.py +12 -12
  71. synapse/tests/test_lib_stormlib_gen.py +99 -0
  72. synapse/tests/test_lib_stormlib_imap.py +14 -3
  73. synapse/tests/test_lib_stormlib_model.py +108 -0
  74. synapse/tests/test_lib_stormlib_modelext.py +64 -0
  75. synapse/tests/test_lib_stormlib_smtp.py +51 -0
  76. synapse/tests/test_lib_stormlib_tabular.py +226 -0
  77. synapse/tests/test_lib_stormsvc.py +4 -1
  78. synapse/tests/test_lib_stormtypes.py +10 -0
  79. synapse/tests/test_model_base.py +3 -0
  80. synapse/tests/test_model_biz.py +3 -0
  81. synapse/tests/test_model_files.py +12 -2
  82. synapse/tests/test_model_inet.py +24 -0
  83. synapse/tests/test_tools_aha.py +78 -101
  84. synapse/tests/test_tools_changelog.py +196 -0
  85. synapse/tests/test_tools_healthcheck.py +4 -3
  86. synapse/tests/utils.py +87 -121
  87. synapse/tools/aha/clone.py +50 -0
  88. synapse/tools/aha/enroll.py +2 -1
  89. synapse/tools/backup.py +2 -2
  90. synapse/tools/changelog.py +776 -15
  91. {synapse-2.176.0.dist-info → synapse-2.178.0.dist-info}/METADATA +48 -48
  92. {synapse-2.176.0.dist-info → synapse-2.178.0.dist-info}/RECORD +95 -82
  93. {synapse-2.176.0.dist-info → synapse-2.178.0.dist-info}/WHEEL +1 -1
  94. {synapse-2.176.0.dist-info → synapse-2.178.0.dist-info}/LICENSE +0 -0
  95. {synapse-2.176.0.dist-info → synapse-2.178.0.dist-info}/top_level.txt +0 -0
@@ -633,7 +633,7 @@ class UserProfile(s_stormtypes.Prim):
633
633
  async def deref(self, name):
634
634
  name = await s_stormtypes.tostr(name)
635
635
  self.runt.confirm(('auth', 'user', 'get', 'profile', name))
636
- return copy.deepcopy(await self.runt.snap.core.getUserProfInfo(self.valu, name))
636
+ return await self.runt.snap.core.getUserProfInfo(self.valu, name)
637
637
 
638
638
  async def setitem(self, name, valu):
639
639
  name = await s_stormtypes.tostr(name)
@@ -654,7 +654,7 @@ class UserProfile(s_stormtypes.Prim):
654
654
 
655
655
  async def value(self):
656
656
  self.runt.confirm(('auth', 'user', 'get', 'profile'))
657
- return copy.deepcopy(await self.runt.snap.core.getUserProfile(self.valu))
657
+ return await self.runt.snap.core.getUserProfile(self.valu)
658
658
 
659
659
  @s_stormtypes.registry.registerType
660
660
  class UserJson(s_stormtypes.Prim):
@@ -807,7 +807,7 @@ class UserVars(s_stormtypes.Prim):
807
807
 
808
808
  async def deref(self, name):
809
809
  name = await s_stormtypes.tostr(name)
810
- return copy.deepcopy(await self.runt.snap.core.getUserVarValu(self.valu, name))
810
+ return await self.runt.snap.core.getUserVarValu(self.valu, name)
811
811
 
812
812
  async def setitem(self, name, valu):
813
813
  name = await s_stormtypes.tostr(name)
@@ -821,7 +821,7 @@ class UserVars(s_stormtypes.Prim):
821
821
 
822
822
  async def iter(self):
823
823
  async for name, valu in self.runt.snap.core.iterUserVars(self.valu):
824
- yield name, copy.deepcopy(valu)
824
+ yield name, valu
825
825
  await asyncio.sleep(0)
826
826
 
827
827
  @s_stormtypes.registry.registerType
@@ -1596,6 +1596,181 @@ class LibAuth(s_stormtypes.Lib):
1596
1596
  perm = await s_stormtypes.toprim(perm)
1597
1597
  return self.runt.snap.core.getPermDef(perm)
1598
1598
 
1599
+ @s_stormtypes.registry.registerType
1600
+ class StormUserVarsDict(s_stormtypes.Prim):
1601
+ '''
1602
+ A Storm Primitive that maps the HiveDict interface to a user vars dictionary.
1603
+ '''
1604
+ _storm_locals = (
1605
+ {'name': 'get', 'desc': 'Get the value for a user var.',
1606
+ 'type': {'type': 'function', '_funcname': '_get',
1607
+ 'args': (
1608
+ {'name': 'name', 'type': 'str', 'desc': 'The name of the var.', },
1609
+ {'name': 'default', 'type': 'prim', 'default': None,
1610
+ 'desc': 'The default value to return if not set.', },
1611
+ ),
1612
+ 'returns': {'type': 'prim', 'desc': 'The requested value.', }}},
1613
+ {'name': 'pop', 'desc': 'Remove a user var value.',
1614
+ 'type': {'type': 'function', '_funcname': '_pop',
1615
+ 'args': (
1616
+ {'name': 'name', 'type': 'str', 'desc': 'The name of the var.', },
1617
+ {'name': 'default', 'type': 'prim', 'default': None,
1618
+ 'desc': 'The default value to return if not set.', },
1619
+ ),
1620
+ 'returns': {'type': 'prim', 'desc': 'The requested value.', }}},
1621
+ {'name': 'set', 'desc': 'Set a user var value.',
1622
+ 'type': {'type': 'function', '_funcname': '_set',
1623
+ 'args': (
1624
+ {'name': 'name', 'type': 'str', 'desc': 'The name of the var to set.', },
1625
+ {'name': 'valu', 'type': 'prim', 'desc': 'The value to store.', },
1626
+ ),
1627
+ 'returns': {'type': ['null', 'prim'],
1628
+ 'desc': 'Old value of the var if it was previously set, or none.', }}},
1629
+ {'name': 'list', 'desc': 'List the vars and their values.',
1630
+ 'type': {'type': 'function', '_funcname': '_list',
1631
+ 'returns': {'type': 'list', 'desc': 'A list of tuples containing var, value pairs.', }}},
1632
+ )
1633
+ _storm_typename = 'user:vars:dict'
1634
+ _ismutable = True
1635
+
1636
+ def __init__(self, runt, valu, path=None):
1637
+ s_stormtypes.Prim.__init__(self, valu, path=path)
1638
+ self.runt = runt
1639
+ self.locls.update(self.getObjLocals())
1640
+
1641
+ def getObjLocals(self):
1642
+ return {
1643
+ 'get': self._get,
1644
+ 'pop': self._pop,
1645
+ 'set': self._set,
1646
+ 'list': self._list,
1647
+ }
1648
+
1649
+ @s_stormtypes.stormfunc(readonly=True)
1650
+ async def _get(self, name, default=None):
1651
+ name = await s_stormtypes.tostr(name)
1652
+ return await self.runt.snap.core.getUserVarValu(self.valu, name, default=default)
1653
+
1654
+ async def _pop(self, name, default=None):
1655
+ name = await s_stormtypes.tostr(name)
1656
+ return await self.runt.snap.core.popUserVarValu(self.valu, name, default=default)
1657
+
1658
+ async def _set(self, name, valu):
1659
+ if not isinstance(name, str):
1660
+ mesg = 'The name of a variable must be a string.'
1661
+ raise s_exc.StormRuntimeError(mesg=mesg, name=name)
1662
+
1663
+ name = await s_stormtypes.tostr(name)
1664
+ oldv = await self.runt.snap.core.getUserVarValu(self.valu, name)
1665
+
1666
+ valu = await s_stormtypes.toprim(valu)
1667
+
1668
+ await self.runt.snap.core.setUserVarValu(self.valu, name, valu)
1669
+ return oldv
1670
+
1671
+ @s_stormtypes.stormfunc(readonly=True)
1672
+ async def _list(self):
1673
+ valu = await self.value()
1674
+ return list(valu.items())
1675
+
1676
+ async def iter(self):
1677
+ async for name, valu in self.runt.snap.core.iterUserVars(self.valu):
1678
+ yield name, valu
1679
+ await asyncio.sleep(0)
1680
+
1681
+ async def value(self):
1682
+ varz = {}
1683
+ async for key, valu in self.runt.snap.core.iterUserVars(self.valu):
1684
+ varz[key] = valu
1685
+ await asyncio.sleep(0)
1686
+
1687
+ return varz
1688
+
1689
+ @s_stormtypes.registry.registerType
1690
+ class StormUserProfileDict(s_stormtypes.Prim):
1691
+ '''
1692
+ A Storm Primitive that maps the HiveDict interface to a user profile dictionary.
1693
+ '''
1694
+ _storm_locals = (
1695
+ {'name': 'get', 'desc': 'Get a user profile value.',
1696
+ 'type': {'type': 'function', '_funcname': '_get',
1697
+ 'args': (
1698
+ {'name': 'name', 'type': 'str', 'desc': 'The name of the user profile value.', },
1699
+ {'name': 'default', 'type': 'prim', 'default': None,
1700
+ 'desc': 'The default value to return if not set.', },
1701
+ ),
1702
+ 'returns': {'type': 'prim', 'desc': 'The requested value.', }}},
1703
+ {'name': 'pop', 'desc': 'Remove a user profile value.',
1704
+ 'type': {'type': 'function', '_funcname': '_pop',
1705
+ 'args': (
1706
+ {'name': 'name', 'type': 'str', 'desc': 'The name of the user profile value.', },
1707
+ {'name': 'default', 'type': 'prim', 'default': None,
1708
+ 'desc': 'The default value to return if not set.', },
1709
+ ),
1710
+ 'returns': {'type': 'prim', 'desc': 'The requested value.', }}},
1711
+ {'name': 'set', 'desc': 'Set a user profile value.',
1712
+ 'type': {'type': 'function', '_funcname': '_set',
1713
+ 'args': (
1714
+ {'name': 'name', 'type': 'str', 'desc': 'The name of the user profile value to set.', },
1715
+ {'name': 'valu', 'type': 'prim', 'desc': 'The value to store.', },
1716
+ ),
1717
+ 'returns': {'type': ['null', 'prim'],
1718
+ 'desc': 'Old value if it was previously set, or none.', }}},
1719
+ {'name': 'list', 'desc': 'List the user profile vars and their values.',
1720
+ 'type': {'type': 'function', '_funcname': '_list',
1721
+ 'returns': {'type': 'list', 'desc': 'A list of tuples containing var, value pairs.', }}},
1722
+ )
1723
+ _storm_typename = 'user:profile:dict'
1724
+ _ismutable = True
1725
+
1726
+ def __init__(self, runt, valu, path=None):
1727
+ s_stormtypes.Prim.__init__(self, valu, path=path)
1728
+ self.runt = runt
1729
+ self.locls.update(self.getObjLocals())
1730
+
1731
+ def getObjLocals(self):
1732
+ return {
1733
+ 'get': self._get,
1734
+ 'pop': self._pop,
1735
+ 'set': self._set,
1736
+ 'list': self._list,
1737
+ }
1738
+
1739
+ @s_stormtypes.stormfunc(readonly=True)
1740
+ async def _get(self, name, default=None):
1741
+ name = await s_stormtypes.tostr(name)
1742
+ return await self.runt.snap.core.getUserProfInfo(self.valu, name, default=default)
1743
+
1744
+ async def _pop(self, name, default=None):
1745
+ name = await s_stormtypes.tostr(name)
1746
+ return await self.runt.snap.core.popUserProfInfo(self.valu, name, default=default)
1747
+
1748
+ async def _set(self, name, valu):
1749
+ if not isinstance(name, str):
1750
+ mesg = 'The name of a variable must be a string.'
1751
+ raise s_exc.StormRuntimeError(mesg=mesg, name=name)
1752
+
1753
+ name = await s_stormtypes.tostr(name)
1754
+ oldv = await self.runt.snap.core.getUserProfInfo(self.valu, name)
1755
+
1756
+ valu = await s_stormtypes.toprim(valu)
1757
+
1758
+ await self.runt.snap.core.setUserProfInfo(self.valu, name, valu)
1759
+ return oldv
1760
+
1761
+ @s_stormtypes.stormfunc(readonly=True)
1762
+ async def _list(self):
1763
+ valu = await self.value()
1764
+ return list(valu.items())
1765
+
1766
+ async def iter(self):
1767
+ async for name, valu in self.runt.snap.core.iterUserProfInfo(self.valu):
1768
+ yield name, valu
1769
+ await asyncio.sleep(0)
1770
+
1771
+ async def value(self):
1772
+ return await self.runt.snap.core.getUserProfile(self.valu)
1773
+
1599
1774
  @s_stormtypes.registry.registerLib
1600
1775
  class LibUser(s_stormtypes.Lib):
1601
1776
  '''
@@ -1614,10 +1789,10 @@ class LibUser(s_stormtypes.Lib):
1614
1789
  ),
1615
1790
  'returns': {'type': 'boolean',
1616
1791
  'desc': 'True if the user has the requested permission, false otherwise.', }}},
1617
- {'name': 'vars', 'desc': "Get a Hive dictionary representing the current user's persistent variables.",
1618
- 'type': 'hive:dict', },
1619
- {'name': 'profile', 'desc': "Get a Hive dictionary representing the current user's profile information.",
1620
- 'type': 'hive:dict', },
1792
+ {'name': 'vars', 'desc': "Get a dictionary representing the current user's persistent variables.",
1793
+ 'type': 'auth:user:vars', },
1794
+ {'name': 'profile', 'desc': "Get a dictionary representing the current user's profile information.",
1795
+ 'type': 'auth:user:profile', },
1621
1796
  {'name': 'iden', 'desc': 'The user GUID for the current storm user.', 'type': 'str'},
1622
1797
  )
1623
1798
  _storm_lib_path = ('user', )
@@ -1632,9 +1807,9 @@ class LibUser(s_stormtypes.Lib):
1632
1807
  def addLibFuncs(self):
1633
1808
  super().addLibFuncs()
1634
1809
  self.locls.update({
1635
- 'vars': s_stormtypes.StormHiveDict(self.runt, self.runt.user.vars),
1810
+ 'vars': StormUserVarsDict(self.runt, self.runt.user.iden),
1636
1811
  'json': UserJson(self.runt, self.runt.user.iden),
1637
- 'profile': s_stormtypes.StormHiveDict(self.runt, self.runt.user.profile),
1812
+ 'profile': StormUserProfileDict(self.runt, self.runt.user.iden),
1638
1813
  })
1639
1814
 
1640
1815
  @s_stormtypes.stormfunc(readonly=True)
@@ -20,8 +20,21 @@ stormcmds = [
20
20
  'storm': '''
21
21
  $apis = $lib.cortex.httpapi.list()
22
22
  if $apis {
23
- $header = 'order iden owner auth runas path'
24
- $lib.print($header)
23
+ $config = (({
24
+ "separators": {
25
+ 'data:row': ''
26
+ },
27
+ "columns": [
28
+ {'name': 'order', 'width': 5},
29
+ {'name': 'iden', 'width': 32},
30
+ {'name': 'owner', 'width': 20},
31
+ {'name': 'auth', 'width': 5},
32
+ {'name': 'runas', 'width': 6},
33
+ {'name': 'path'},
34
+ ]
35
+ }))
36
+ $printer = $lib.tabular.printer($config)
37
+ $lib.print($printer.header())
25
38
  for ($n, $api) in $lib.iters.enum($apis) {
26
39
  try {
27
40
  $user = $api.owner.name
@@ -29,9 +42,7 @@ stormcmds = [
29
42
  $user = `No user found ({$err.info.user})`
30
43
  }
31
44
  $auth = `{$api.authenticated}`
32
- $order = `{$n}`
33
- $mesg=`{$order.ljust(5)} {$api.iden} {$user.ljust(20)} {$auth.ljust(5)} {$api.runas.ljust(6)} {$api.path}`
34
- $lib.print($mesg)
45
+ $lib.print($printer.row(($n, $api.iden, $user, $auth, $api.runas, $api.path)))
35
46
  }
36
47
  } else {
37
48
  $lib.print('No Extended HTTP API endpoints are registered.')
@@ -136,6 +136,34 @@ class LibGen(s_stormtypes.Lib):
136
136
  {'name': 'name', 'type': 'str', 'desc': 'The name of the place.'},
137
137
  ),
138
138
  'returns': {'type': 'node', 'desc': 'A geo:place node with the given name.'}}},
139
+ {'name': 'fileBytesBySha256',
140
+ 'desc': 'Returns a file:bytes node by SHA256, adding the node if it does not exist.',
141
+ 'type': {'type': 'function', '_funcname': '_storm_query',
142
+ 'args': (
143
+ {'name': 'sha256', 'type': ['str', 'hash:sha256'], 'desc': 'The SHA256 fingerprint for the file:bytes node.'},
144
+ {'name': 'try', 'type': 'boolean', 'default': False,
145
+ 'desc': 'Type normalization will fail silently instead of raising an exception.'},
146
+ ),
147
+ 'returns': {'type': 'node', 'desc': 'A file:bytes node with the given SHA256.'}}},
148
+ {'name': 'cryptoX509CertBySha256',
149
+ 'desc': 'Returns a crypto:x509:cert node by SHA256, adding the node if it does not exist.',
150
+ 'type': {'type': 'function', '_funcname': '_storm_query',
151
+ 'args': (
152
+ {'name': 'sha256', 'type': ['str', 'hash:sha256'], 'desc': 'The SHA256 fingerprint for the certificate.'},
153
+ {'name': 'try', 'type': 'boolean', 'default': False,
154
+ 'desc': 'Type normalization will fail silently instead of raising an exception.'},
155
+ ),
156
+ 'returns': {'type': 'node', 'desc': 'A crypto:x509:cert node with the given SHA256.'}}},
157
+ {'name': 'inetTlsServerCertByServerAndSha256',
158
+ 'desc': 'Returns an inet:tls:servercert node by server and SHA256, adding the node if it does not exist.',
159
+ 'type': {'type': 'function', '_funcname': '_storm_query',
160
+ 'args': (
161
+ {'name': 'server', 'type': ['str', 'inet:server'], 'desc': 'The server associated with the x509 certificate.'},
162
+ {'name': 'sha256', 'type': ['str', 'hash:sha256'], 'desc': 'The SHA256 fingerprint for the certificate.'},
163
+ {'name': 'try', 'type': 'boolean', 'default': False,
164
+ 'desc': 'Type normalization will fail silently instead of raising an exception.'},
165
+ ),
166
+ 'returns': {'type': 'node', 'desc': 'An inet:tls:servercert node with the given server and SHA256.'}}},
139
167
  )
140
168
  _storm_lib_path = ('gen',)
141
169
 
@@ -425,6 +453,58 @@ class LibGen(s_stormtypes.Lib):
425
453
  [ geo:place=(gen, name, $geoname) :name=$geoname ]
426
454
  return($node)
427
455
  }
456
+
457
+ function fileBytesBySha256(sha256, try=$lib.false) {
458
+ ($ok, $sha256) = $__maybeCast($try, hash:sha256, $sha256)
459
+ if (not $ok) { return() }
460
+
461
+ file:bytes=$sha256
462
+ return($node)
463
+
464
+ file:bytes:sha256=$sha256
465
+ return($node)
466
+
467
+ [ file:bytes=$sha256 ]
468
+ return($node)
469
+ }
470
+
471
+ function cryptoX509CertBySha256(sha256, try=$lib.false) {
472
+ ($ok, $sha256) = $__maybeCast($try, hash:sha256, $sha256)
473
+ if (not $ok) { return() }
474
+
475
+ $guid = $lib.guid(valu=$sha256)
476
+
477
+ // Try to lift crypto:x509:cert by guid
478
+ crypto:x509:cert=$guid
479
+ return($node)
480
+
481
+ // Try to lift crypto:x509:cert by sha256
482
+ crypto:x509:cert:sha256=$sha256
483
+ return($node)
484
+
485
+ // Try to lift crypto:x509:cert by file
486
+ file:bytes:sha256=$sha256 -> crypto:x509:cert:file
487
+ { -:sha256 [ :sha256 = $sha256 ] }
488
+ return($node)
489
+
490
+ // Create a new crypto:x509:cert with file and sha256
491
+ [ crypto:x509:cert=$guid
492
+ :file = $fileBytesBySha256($sha256)
493
+ :sha256 = $sha256
494
+ ]
495
+ return($node)
496
+ }
497
+
498
+ function inetTlsServerCertByServerAndSha256(server, sha256, try=$lib.false) {
499
+ ($ok, $server) = $__maybeCast($try, inet:server, $server)
500
+ if (not $ok) { return() }
501
+
502
+ $crypto = $cryptoX509CertBySha256($sha256, try=$try)
503
+ if (not $crypto) { return() }
504
+
505
+ [ inet:tls:servercert=($server, $crypto) ]
506
+ return($node)
507
+ }
428
508
  '''
429
509
 
430
510
  stormcmds = (
@@ -50,6 +50,8 @@ class ImapLib(s_stormtypes.Lib):
50
50
  'desc': 'The time to wait for all commands on the server to execute.'},
51
51
  {'type': 'bool', 'name': 'ssl', 'default': True,
52
52
  'desc': 'Use SSL to connect to the IMAP server.'},
53
+ {'type': 'bool', 'name': 'ssl_verify', 'default': True,
54
+ 'desc': 'Perform SSL/TLS verification.'},
53
55
  ),
54
56
  'returns': {
55
57
  'type': 'inet:imap:server',
@@ -69,17 +71,19 @@ class ImapLib(s_stormtypes.Lib):
69
71
  'connect': self.connect,
70
72
  }
71
73
 
72
- async def connect(self, host, port=993, timeout=30, ssl=True):
74
+ async def connect(self, host, port=993, timeout=30, ssl=True, ssl_verify=True):
73
75
 
74
76
  self.runt.confirm(('storm', 'inet', 'imap', 'connect'))
75
77
 
76
78
  ssl = await s_stormtypes.tobool(ssl)
77
79
  host = await s_stormtypes.tostr(host)
78
80
  port = await s_stormtypes.toint(port)
81
+ ssl_verify = await s_stormtypes.tobool(ssl_verify)
79
82
  timeout = await s_stormtypes.toint(timeout, noneok=True)
80
83
 
81
84
  if ssl:
82
- imap_cli = aioimaplib.IMAP4_SSL(host=host, port=port, timeout=timeout)
85
+ ctx = self.runt.snap.core.getCachedSslCtx(opts=None, verify=ssl_verify)
86
+ imap_cli = aioimaplib.IMAP4_SSL(host=host, port=port, timeout=timeout, ssl_context=ctx)
83
87
  else:
84
88
  imap_cli = aioimaplib.IMAP4(host=host, port=port, timeout=timeout)
85
89
 
@@ -919,8 +919,63 @@ class LibModelMigrations(s_stormtypes.Lib, MigrationEditorMixin):
919
919
  'desc': 'Do not copy nodedata to the risk:vulnerable node.'},
920
920
  ),
921
921
  'returns': {'type': 'list', 'desc': 'A list of idens for the risk:vulnerable nodes.'}}},
922
+ {'name': 'inetSslCertToTlsServerCert', 'desc': '''
923
+ Create a inet:tls:servercert node from the provided inet:ssl:cert node.
924
+
925
+ Edits will be made to the inet:tls:servercert node in the current write layer.
926
+
927
+ Tags, tag properties, edges, and node data will be copied
928
+ to the inet:tls:servercert node. However, existing tag properties and
929
+ node data will not be overwritten.
930
+ ''',
931
+ 'type': {'type': 'function', '_funcname': '_storm_query',
932
+ 'args': (
933
+ {'name': 'n', 'type': 'node', 'desc': 'The inet:ssl:cert node to migrate.'},
934
+ {'name': 'nodata', 'type': 'bool', 'default': False,
935
+ 'desc': 'Do not copy nodedata to the inet:tls:servercert node.'},
936
+ ),
937
+ 'returns': {'type': 'node', 'desc': 'The newly created inet:tls:servercert node.'}}},
938
+
922
939
  )
923
940
  _storm_lib_path = ('model', 'migration', 's')
941
+ _storm_query = '''
942
+ function inetSslCertToTlsServerCert(n, nodata=$lib.false) {
943
+ $form = $n.form()
944
+ if ($form != 'inet:ssl:cert') {
945
+ $mesg = `$lib.model.migration.s.inetSslCertToTlsServerCert() only accepts inet:ssl:cert nodes, not {$form}`
946
+ $lib.raise(BadArg, $mesg)
947
+ }
948
+
949
+ $server = $n.props.server
950
+ $sha256 = { yield $n -> file:bytes -> hash:sha256 }
951
+
952
+ if $sha256 {
953
+
954
+ yield $lib.gen.inetTlsServerCertByServerAndSha256($server, $sha256)
955
+
956
+ } else {
957
+
958
+ // File doesn't have a :sha256, try to lift/create a crypto:x509:node based on the file link
959
+ $crypto = { yield $n -> file:bytes -> crypto:x509:cert:file }
960
+ if (not $crypto) {
961
+ $crypto = {[ crypto:x509:cert=($n.props.file,) :file=$n.props.file ]}
962
+ }
963
+
964
+ [ inet:tls:servercert=($server, $crypto) ]
965
+
966
+ }
967
+
968
+ [ .seen ?= $n.props.".seen" ]
969
+
970
+ $lib.model.migration.copyTags($n, $node, overwrite=$lib.false)
971
+ $lib.model.migration.copyEdges($n, $node)
972
+ if (not $nodata) {
973
+ $lib.model.migration.copyData($n, $node, overwrite=$lib.false)
974
+ }
975
+
976
+ return($node)
977
+ }
978
+ '''
924
979
 
925
980
  def getObjLocals(self):
926
981
  return {
@@ -77,6 +77,27 @@ class LibModelExt(s_stormtypes.Lib):
77
77
  {'name': 'model', 'type': 'dict', 'desc': 'A model dictionary from getExtModel().', },
78
78
  ),
79
79
  'returns': {'type': 'boolean'}}},
80
+ {'name': 'addEdge', 'desc': 'Add an extended edge definition to the data model.',
81
+ 'type': {'type': 'function', '_funcname': 'addEdge',
82
+ 'args': (
83
+ {'name': 'n1form', 'type': 'str',
84
+ 'desc': 'The form of the n1 node. May be "*" or null to specify "any".'},
85
+ {'name': 'verb', 'type': 'str', 'desc': 'The edge verb, which must begin with "_".'},
86
+ {'name': 'n2form', 'type': 'str',
87
+ 'desc': 'The form of the n2 node. May be "*" or null to specify "any".'},
88
+ {'name': 'edgeinfo', 'type': 'dict', 'desc': 'A Synapse edge info dictionary.'},
89
+ ),
90
+ 'returns': {'type': 'null'}}},
91
+ {'name': 'delEdge', 'desc': 'Remove an extended edge definition from the data model.',
92
+ 'type': {'type': 'function', '_funcname': 'delEdge',
93
+ 'args': (
94
+ {'name': 'n1form', 'type': 'str',
95
+ 'desc': 'The form of the n1 node. May be "*" or null to specify "any".'},
96
+ {'name': 'verb', 'type': 'str', 'desc': 'The edge verb, which must begin with "_".'},
97
+ {'name': 'n2form', 'type': 'str',
98
+ 'desc': 'The form of the n2 node. May be "*" or null to specify "any".'},
99
+ ),
100
+ 'returns': {'type': 'null'}}},
80
101
  )
81
102
  _storm_lib_path = ('model', 'ext')
82
103
 
@@ -92,6 +113,8 @@ class LibModelExt(s_stormtypes.Lib):
92
113
  'delTagProp': self.delTagProp,
93
114
  'getExtModel': self.getExtModel,
94
115
  'addExtModel': self.addExtModel,
116
+ 'addEdge': self.addEdge,
117
+ 'delEdge': self.delEdge,
95
118
  }
96
119
 
97
120
  # TODO type docs in the new convention
@@ -163,3 +186,40 @@ class LibModelExt(s_stormtypes.Lib):
163
186
  async def addExtModel(self, model):
164
187
  model = await s_stormtypes.toprim(model)
165
188
  return await self.runt.snap.core.addExtModel(model)
189
+
190
+ async def addEdge(self, n1form, verb, n2form, edgeinfo):
191
+ verb = await s_stormtypes.tostr(verb)
192
+ n1form = await s_stormtypes.tostr(n1form, noneok=True)
193
+ n2form = await s_stormtypes.tostr(n2form, noneok=True)
194
+ edgeinfo = await s_stormtypes.toprim(edgeinfo)
195
+
196
+ if not (s_grammar.isEdgeVerb(verb) and verb.islower()):
197
+ mesg = f'Invalid edge verb {verb}'
198
+ raise s_exc.BadEdgeDef(mesg=mesg, n1form=n1form, verb=verb, n2form=n2form)
199
+
200
+ if n1form == '*':
201
+ n1form = None
202
+
203
+ if n2form == '*':
204
+ n2form = None
205
+
206
+ s_stormtypes.confirm(('model', 'edge', 'add'))
207
+ await self.runt.snap.core.addEdge((n1form, verb, n2form), edgeinfo)
208
+
209
+ async def delEdge(self, n1form, verb, n2form):
210
+ verb = await s_stormtypes.tostr(verb)
211
+ n1form = await s_stormtypes.tostr(n1form, noneok=True)
212
+ n2form = await s_stormtypes.tostr(n2form, noneok=True)
213
+
214
+ if not (s_grammar.isEdgeVerb(verb) and verb.islower()):
215
+ mesg = f'Invalid edge verb {verb}'
216
+ raise s_exc.BadEdgeDef(mesg=mesg, n1form=n1form, verb=verb, n2form=n2form)
217
+
218
+ if n1form == '*':
219
+ n1form = None
220
+
221
+ if n2form == '*':
222
+ n2form = None
223
+
224
+ s_stormtypes.confirm(('model', 'edge', 'del'))
225
+ await self.runt.snap.core.delEdge((n1form, verb, n2form))
@@ -96,6 +96,8 @@ class SmtpMessage(s_stormtypes.StormType):
96
96
  'desc': 'Use the STARTTLS directive with the SMTP server.'},
97
97
  {'name': 'timeout', 'type': 'int', 'default': 60,
98
98
  'desc': 'The timeout (in seconds) to wait for message delivery.'},
99
+ {'type': 'bool', 'name': 'ssl_verify', 'default': True,
100
+ 'desc': 'Perform SSL/TLS verification.'},
99
101
  ),
100
102
  'returns': {'type': 'list', 'desc': 'An ($ok, $valu) tuple.'}}},
101
103
 
@@ -148,7 +150,8 @@ class SmtpMessage(s_stormtypes.StormType):
148
150
  async def _getEmailHtml(self):
149
151
  return self.bodyhtml
150
152
 
151
- async def send(self, host, port=25, user=None, passwd=None, usetls=False, starttls=False, timeout=60):
153
+ async def send(self, host, port=25, user=None, passwd=None, usetls=False, starttls=False, timeout=60,
154
+ ssl_verify=True):
152
155
 
153
156
  self.runt.confirm(('storm', 'inet', 'smtp', 'send'))
154
157
 
@@ -161,6 +164,7 @@ class SmtpMessage(s_stormtypes.StormType):
161
164
  port = await s_stormtypes.toint(port)
162
165
  usetls = await s_stormtypes.tobool(usetls)
163
166
  starttls = await s_stormtypes.tobool(starttls)
167
+ ssl_verify = await s_stormtypes.tobool(ssl_verify)
164
168
 
165
169
  if usetls and starttls:
166
170
  raise s_exc.BadArg(mesg='usetls and starttls are mutually exclusive arguments.')
@@ -183,6 +187,10 @@ class SmtpMessage(s_stormtypes.StormType):
183
187
 
184
188
  recipients = [await s_stormtypes.tostr(e) for e in self.recipients]
185
189
 
190
+ ctx = None
191
+ if usetls or starttls:
192
+ ctx = self.runt.snap.core.getCachedSslCtx(opts=None, verify=ssl_verify)
193
+
186
194
  futu = aiosmtplib.send(message,
187
195
  port=port,
188
196
  hostname=host,
@@ -191,7 +199,9 @@ class SmtpMessage(s_stormtypes.StormType):
191
199
  use_tls=usetls,
192
200
  start_tls=starttls,
193
201
  username=user,
194
- password=passwd)
202
+ password=passwd,
203
+ tls_context=ctx,
204
+ )
195
205
 
196
206
  await s_common.wait_for(futu, timeout=timeout)
197
207