跳转到主要内容

Zope3的默认安全策略

项目描述

zope.securitypolicy

Latest release Supported Python versions https://github.com/zopefoundation/zope.securitypolicy/actions/workflows/tests.yml/badge.svg https://coveralls.io/repos/github/zopefoundation/zope.securitypolicy/badge.svg?branch=master

此包为Zope3提供了一种有用的安全策略。它是“zope3应用程序”和其他许多基于Zope的项目中使用的默认安全策略。

经典Zope安全策略

此包实现了一种基于角色的安全策略,类似于Zope 2中找到的策略。安全策略负责决定一个交互是否具有对象上的权限。此安全策略使用授予和拒绝信息来完成此操作。管理员可以为主体授予或拒绝

  • 角色,

  • 权限给主体,以及

  • 权限给角色

授予和拒绝存储为对象的注释。要存储授予和拒绝,对象必须是可注释的

>>> import zope.interface
>>> from zope.annotation.interfaces import IAttributeAnnotatable
>>> @zope.interface.implementer(IAttributeAnnotatable)
... class Ob:
...     pass
>>> ob = Ob()

我们使用对象来表示主体。这些对象实现了一个名为IPrincipal的接口,但安全策略只使用idgroups属性

>>> class Principal:
...     def __init__(self, id):
...         self.id = id
...         self.groups = []
>>> principal = Principal('bob')

角色和权限也由对象表示,但是,对于安全策略的目的,仅使用字符串ids

安全策略提供了一个用于创建交互的工厂

>>> import zope.securitypolicy.zopepolicy
>>> interaction = zope.securitypolicy.zopepolicy.ZopeSecurityPolicy()

交互代表某些主体(通常是用户)与系统之间的特定交互。通常,我们只关注一个主体与系统的交互,尽管我们可以有多个主体的交互。多主体交互通常发生在不可信用户将代码存储在系统上以供以后执行时。当不可信代码执行时,代码的作者参与交互。只有当参与交互的所有主体都可以访问对象时,交互才对对象具有权限。

交互上的checkPermission方法用于测试交互是否具有对象的权限。没有参与者的交互总是具有所有权限。

>>> interaction.checkPermission('P1', ob)
True

在这个例子中,“P1”是一个权限ID。

通常,交互都有参与者。

>>> class Participation:
...     interaction = None
>>> participation = Participation()
>>> participation.principal = principal
>>> interaction.add(participation)

如果我们有参与者,那么除非有授权,否则我们没有权限。

>>> interaction.checkPermission('P1', ob)
False

然而,请注意,我们始终具有CheckerPublic权限。

>>> from zope.security.checker import CheckerPublic
>>> interaction.checkPermission(CheckerPublic, ob)
True

我们通过适配各种授权接口来对对象进行授权和拒绝。适配后的对象是特定于对象的管理对象。

>>> from zope.securitypolicy import interfaces
>>> roleper  = interfaces.IRolePermissionManager(ob)
>>> prinrole = interfaces.IPrincipalRoleManager(ob)
>>> prinper  = interfaces.IPrincipalPermissionManager(ob)

检查权限所涉及的计算可能很大。为了减少计算成本,广泛使用缓存。我们可以在做出授权时使缓存无效,但做出授权的适配器将自动使当前交互的缓存无效。它们使用安全管理API来完成此操作。为了利用缓存失效,我们需要让安全管理系统管理我们的交互。首先,我们将我们的安全策略设置为默认值

>>> import zope.security.management
>>> oldpolicy = zope.security.management.setSecurityPolicy(
...      zope.securitypolicy.zopepolicy.ZopeSecurityPolicy)

然后我们创建一个新的交互

>>> participation = Participation()
>>> participation.principal = principal
>>> zope.security.management.newInteraction(participation)
>>> interaction = zope.security.management.getInteraction()

我们通常通过为对象授权角色来提供访问权限

>>> roleper.grantPermissionToRole('P1', 'R1')

然后为对象授权主体(本地角色)

>>> prinrole.assignRoleToPrincipal('R1', 'bob')

这些授权的组合,我们称之为基于角色的授权,提供了权限

>>> interaction.checkPermission('P1', ob)
True

我们也可以直接提供权限

>>> prinper.grantPermissionToPrincipal('P2', 'bob')
>>> interaction.checkPermission('P2', ob)
True

权限授权或拒绝会覆盖基于角色的授权或拒绝。所以如果我们拒绝P1

>>> prinper.denyPermissionToPrincipal('P1', 'bob')

即使有角色授权,交互也会缺少权限

>>> interaction.checkPermission('P1', ob)
False

同样,即使我们有基于P2的角色拒绝

>>> roleper.denyPermissionToRole('P2', 'R1')

由于基于权限的授权,我们仍然可以访问

>>> interaction.checkPermission('P2', ob)
True

基于角色的拒绝实际上并不拒绝权限;而是防止授权权限。因此,如果我们既有基于角色的授权,又有基于角色的拒绝,我们就有访问权限

>>> roleper.grantPermissionToRole('P3', 'R1')
>>> roleper.grantPermissionToRole('P3', 'R2')
>>> roleper.denyPermissionToRole('P3', 'R3')
>>> prinrole.removeRoleFromPrincipal('R2', 'bob')
>>> prinrole.assignRoleToPrincipal('R3', 'bob')
>>> interaction.checkPermission('P3', ob)
True

全局授权

对对象做出的授权被称为“本地”。我们也可以做出全局授权

>>> from zope.securitypolicy.rolepermission import \
...     rolePermissionManager as roleperG
>>> from zope.securitypolicy.principalpermission import \
...     principalPermissionManager as prinperG
>>> from zope.securitypolicy.principalrole import \
...     principalRoleManager as prinroleG

全局授权和拒绝同样适用。

>>> roleperG.grantPermissionToRole('P1G', 'R1G', False)

在这些测试中,我们不想定义任何角色、权限或主体,所以我们传递一个额外的参数,告诉授权程序不要检查值的有效性。

>>> prinroleG.assignRoleToPrincipal('R1G', 'bob', False)
>>> interaction.checkPermission('P1G', ob)
True
>>> prinperG.grantPermissionToPrincipal('P2G', 'bob', False)
>>> interaction.checkPermission('P2G', ob)
True
>>> prinperG.denyPermissionToPrincipal('P1G', 'bob', False)
>>> interaction.checkPermission('P1G', ob)
False
>>> roleperG.denyPermissionToRole('P2G', 'R1G', False)
>>> interaction.checkPermission('P2G', ob)
True
>>> roleperG.grantPermissionToRole('P3G', 'R1G', False)
>>> roleperG.grantPermissionToRole('P3G', 'R2G', False)
>>> roleperG.denyPermissionToRole('P3G', 'R3G', False)
>>> prinroleG.removeRoleFromPrincipal('R2G', 'bob', False)
>>> prinroleG.assignRoleToPrincipal('R3G', 'bob', False)
>>> interaction.checkPermission('P3G', ob)
True

本地与全局授权

我们当然默认获取全局授权

>>> interaction.checkPermission('P1G', ob)
False
>>> interaction.checkPermission('P2G', ob)
True
>>> interaction.checkPermission('P3G', ob)
True

本地基于角色的授权不会覆盖全局主体特定的拒绝

>>> roleper.grantPermissionToRole('P1G', 'R1G')
>>> prinrole.assignRoleToPrincipal('R1G', 'bob')
>>> interaction.checkPermission('P1G', ob)
False

本地基于角色的拒绝不会覆盖全局主体授权

>>> roleper.denyPermissionToRole('P2G', 'R1G')
>>> interaction.checkPermission('P2G', ob)
True

本地基于角色的拒绝可以取消全局基于角色的授权

>>> roleper.denyPermissionToRole('P3G', 'R1G')
>>> interaction.checkPermission('P3G', ob)
False

本地基于角色的授权可以覆盖全局基于角色的拒绝

>>> roleperG.denyPermissionToRole('P4G', 'R1G', False)
>>> prinroleG.assignRoleToPrincipal('R1G', "bob", False)
>>> interaction.checkPermission('P4G', ob)
False
>>> roleper.grantPermissionToRole('P4G', 'R1G')
>>> interaction.checkPermission('P4G', ob)
True
>>> prinroleG.removeRoleFromPrincipal('R1G', "bob", False)
>>> interaction.checkPermission('P4G', ob)
True

当然,本地权限授权或拒绝覆盖任何全局设置,并覆盖本地基于角色的授权或拒绝

>>> prinper.grantPermissionToPrincipal('P3G', 'bob')
>>> interaction.checkPermission('P3G', ob)
True
>>> prinper.denyPermissionToPrincipal('P2G', 'bob')
>>> interaction.checkPermission('P2G', ob)
False

子位置

我们可以有子位置。一个位置的字节是具有其__parent__属性为位置的实体

>>> ob2 = Ob()
>>> ob2.__parent__ = ob

默认情况下,子位置从更高位置获取授权

>>> interaction.checkPermission('P1', ob2)
False
>>> interaction.checkPermission('P2', ob2)
True
>>> interaction.checkPermission('P3', ob2)
True
>>> interaction.checkPermission('P1G', ob2)
False
>>> interaction.checkPermission('P2G', ob2)
False
>>> interaction.checkPermission('P3G', ob2)
True
>>> interaction.checkPermission('P4G', ob2)
True

子位置基于角色的授权不会覆盖其父主体特定的拒绝

>>> roleper2  = interfaces.IRolePermissionManager(ob2)
>>> prinrole2 = interfaces.IPrincipalRoleManager(ob2)
>>> prinper2  = interfaces.IPrincipalPermissionManager(ob2)
>>> roleper2.grantPermissionToRole('P1', 'R1')
>>> prinrole2.assignRoleToPrincipal('R1', 'bob')
>>> interaction.checkPermission('P1', ob2)
False

本地基于角色的拒绝不会覆盖其父主体授权

>>> roleper2.denyPermissionToRole('P2', 'R1')
>>> interaction.checkPermission('P2', ob2)
True

本地基于角色的拒绝可以取消父级基于角色的授权

>>> roleper2.denyPermissionToRole('P3', 'R1')
>>> interaction.checkPermission('P3', ob2)
False

本地基于角色的授权可以覆盖父级基于角色的拒绝

>>> roleper.denyPermissionToRole('P4', 'R1')
>>> prinrole.assignRoleToPrincipal('R1', 'bob')
>>> interaction.checkPermission('P4', ob2)
False
>>> roleper2.grantPermissionToRole('P4', 'R1')
>>> interaction.checkPermission('P4', ob2)
True
>>> prinrole.removeRoleFromPrincipal('R1', 'bob')
>>> interaction.checkPermission('P4', ob2)
True

当然,本地权限授权或拒绝覆盖任何全局设置,并覆盖本地基于角色的授权或拒绝

>>> prinper.grantPermissionToPrincipal('P3', 'bob')
>>> interaction.checkPermission('P3', ob2)
True
>>> prinper.denyPermissionToPrincipal('P2', 'bob')
>>> interaction.checkPermission('P2', ob2)
False

如果一个对象不是可注释的,但有一个父对象,它将从其父对象获取授权

>>> class C:
...     pass
>>> ob3 = C()
>>> ob3.__parent__ = ob
>>> interaction.checkPermission('P1', ob3)
False
>>> interaction.checkPermission('P2', ob3)
False
>>> interaction.checkPermission('P3', ob3)
True
>>> interaction.checkPermission('P1G', ob3)
False
>>> interaction.checkPermission('P2G', ob3)
False
>>> interaction.checkPermission('P3G', ob3)
True
>>> interaction.checkPermission('P4G', ob3)
True

如果有多个不可注释的对象,将得到相同的结果

>>> ob3.__parent__ = C()
>>> ob3.__parent__.__parent__ = ob
>>> interaction.checkPermission('P1', ob3)
False
>>> interaction.checkPermission('P2', ob3)
False
>>> interaction.checkPermission('P3', ob3)
True
>>> interaction.checkPermission('P1G', ob3)
False
>>> interaction.checkPermission('P2G', ob3)
False
>>> interaction.checkPermission('P3G', ob3)
True
>>> interaction.checkPermission('P4G', ob3)
True

如果对象没有父对象

>>> ob4 = C()

它将拥有全局范围内所做的任何授权

>>> interaction.checkPermission('P1', ob4)
False
>>> interaction.checkPermission('P2', ob4)
False
>>> interaction.checkPermission('P3', ob4)
False
>>> interaction.checkPermission('P1G', ob4)
False
>>> interaction.checkPermission('P2G', ob4)
True
>>> interaction.checkPermission('P3G', ob4)
False
>>> interaction.checkPermission('P4G', ob4)
False
>>> prinroleG.assignRoleToPrincipal('R1G', "bob", False)
>>> interaction.checkPermission('P3G', ob4)
True

如果我们有一个没有父对象的不可注解的父对象,我们会得到相同的结果

>>> ob3.__parent__ = C()
>>> interaction.checkPermission('P1', ob3)
False
>>> interaction.checkPermission('P2', ob3)
False
>>> interaction.checkPermission('P3', ob3)
False
>>> interaction.checkPermission('P1G', ob3)
False
>>> interaction.checkPermission('P2G', ob3)
True
>>> interaction.checkPermission('P3G', ob3)
True
>>> interaction.checkPermission('P4G', ob3)
False

匿名角色

安全策略定义了一个名为“zope.Anonymous”的特殊角色。所有主体都拥有此角色,且此角色不能被剥夺。

>>> roleperG.grantPermissionToRole('P5', 'zope.Anonymous', False)
>>> interaction.checkPermission('P5', ob2)
True

代理

对象可以被代理

>>> from zope.security.checker import ProxyFactory
>>> ob = ProxyFactory(ob)
>>> interaction.checkPermission('P1', ob)
False
>>> interaction.checkPermission('P2', ob)
False
>>> interaction.checkPermission('P3', ob)
True
>>> interaction.checkPermission('P1G', ob)
False
>>> interaction.checkPermission('P2G', ob)
False
>>> interaction.checkPermission('P3G', ob)
True
>>> interaction.checkPermission('P4G', ob)
True

它们的父对象也可以

>>> ob3 = C()
>>> ob3.__parent__ = ob
>>> interaction.checkPermission('P1', ob3)
False
>>> interaction.checkPermission('P2', ob3)
False
>>> interaction.checkPermission('P3', ob3)
True
>>> interaction.checkPermission('P1G', ob3)
False
>>> interaction.checkPermission('P2G', ob3)
False
>>> interaction.checkPermission('P3G', ob3)
True
>>> interaction.checkPermission('P4G', ob3)
True

主体可以有组。组也是主体(因此,也可以有组)。

如果主体有组,这些组作为组ID在主体的 groups 属性中可用。交互必须将这些组ID转换为组对象,以便它可以判断组是否有组。它通过在主体身份验证服务上调用 getPrincipal 方法来完成此操作,该服务负责将主体ID转换为主体。在我们的示例中,我们将创建并注册一个存根主体身份验证服务

>>> from zope.authentication.interfaces import IAuthentication
>>> @zope.interface.implementer(IAuthentication)
... class FauxPrincipals(object):
...     def __init__(self):
...         self.data = {}
...     def __setitem__(self, key, value):
...         self.data[key] = value
...     def __getitem__(self, key):
...         return self.data[key]
...     def getPrincipal(self, id):
...         return self.data[id]
>>> auth = FauxPrincipals()
>>> from zope.component import provideUtility
>>> provideUtility(auth, IAuthentication)

让我们定义一个组

>>> auth['g1'] = Principal('g1')

让我们将主体放入我们的组中。我们通过将组ID添加到新主体的组中来实现这一点

>>> principal.groups.append('g1')

当然,主体没有未授权的权限

>>> interaction.checkPermission('gP1', ob)
False

现在,如果我们授予组权限

>>> prinper.grantPermissionToPrincipal('gP1', 'g1')

我们看到我们的主体有这个权限

>>> interaction.checkPermission('gP1', ob)
True

即使组授权是全局的,这也有效

>>> interaction.checkPermission('gP1G', ob)
False
>>> prinperG.grantPermissionToPrincipal('gP1G', 'g1', True)
>>> interaction.checkPermission('gP1G', ob)
True

当然,授权是获得的

>>> interaction.checkPermission('gP1', ob2)
True
>>> interaction.checkPermission('gP1G', ob2)
True

内部授权可以覆盖外部授权

>>> prinper2.denyPermissionToPrincipal('gP1', 'g1')
>>> interaction.checkPermission('gP1', ob2)
False

但主体授权始终优先于组授权

>>> prinper2.grantPermissionToPrincipal('gP1', 'bob')
>>> interaction.checkPermission('gP1', ob2)
True

组也可以有组

>>> auth['g2'] = Principal('g2')
>>> auth['g1'].groups.append('g2')

如果我们授予新组权限

>>> prinper.grantPermissionToPrincipal('gP2', 'g2')

那么,当然,我们也有那个权限

>>> interaction.checkPermission('gP2', ob2)
True

就像主体授权覆盖组授权一样,组授权也可以覆盖其他组授权

>>> prinper.denyPermissionToPrincipal('gP2', 'g1')
>>> interaction.checkPermission('gP2', ob2)
False

主体可以属于多个组。让我们定义一个新的组

>>> auth['g3'] = Principal('g3')
>>> principal.groups.append('g3')
>>> prinper.grantPermissionToPrincipal('gP2', 'g3')

现在,主体有两个组。在一个组中,权限“gP2”被拒绝,但在另一个组中,它是允许的。在这种情况下,权限是允许的

>>> interaction.checkPermission('gP2', ob2)
True

在主体有两个或更多组的情况下,组的拒绝阻止允许从它们的父级。它们不会阻止主体从另一个主体那里获得允许。

通过多个路径从祖先组继承授权。让我们授权给g2并拒绝给g1

>>> prinper.grantPermissionToPrincipal('gP3', 'g2')
>>> prinper.denyPermissionToPrincipal('gP3', 'g1')

现在,就像以前一样,g1中的拒绝阻止了g2中的授权

>>> interaction.checkPermission('gP3', ob2)
False

让我们将g2作为g3的组

>>> auth['g3'].groups.append('g2')

现在,我们通过g3获得g2的授权,访问被允许

>>> interaction.invalidate_cache()
>>> interaction.checkPermission('gP3', ob2)
True

我们可以给组分配角色

>>> prinrole.assignRoleToPrincipal('gR1', 'g2')

并通过角色获取权限

>>> roleper.grantPermissionToRole('gP4', 'gR1')
>>> interaction.checkPermission('gP4', ob2)
True

我们可以通过子组和主体覆盖组角色分配

>>> prinrole.removeRoleFromPrincipal('gR1', 'g1')
>>> prinrole.removeRoleFromPrincipal('gR1', 'g3')
>>> interaction.checkPermission('gP4', ob2)
False

和通过主体

>>> prinrole.assignRoleToPrincipal('gR1', 'bob')
>>> interaction.checkPermission('gP4', ob2)
True

我们清理了这些示例中做出的更改

>>> zope.security.management.endInteraction()
>>> ignore = zope.security.management.setSecurityPolicy(oldpolicy)

更改

5.0 (2023-02-24)

  • 添加对Python 3.9、3.10、3.11的支持。

  • 放弃对Python 2.7、3.5、3.6的支持。

4.3.2 (2021-03-19)

  • 添加对Python 3.8的支持。

  • 放弃对Python 3.4的支持。

  • 修复一些测试导入,使用来自 zope.interface 的正确导入,而不是 zope.component

4.3.1 (2018-10-11)

  • 使用当前位置为 IRegisteredIUnregistered 接口。

4.3.0 (2018-08-27)

  • 添加对Python 3.7的支持。

  • 放弃对Python 3.3的支持。

  • 放弃使用 python setup.py test

  • 使 SecurityMapAnnotationGrantInfo 在Python 3上的真值行为正确;之前它们总是为真。

  • 使 AnnotationGrantInfo 在Python 3上一致地返回列表而不是字典视图。

  • 使 AnnotationSecurityMap(以及从它派生的对象,例如 AnnotationPrincipalPermissionManager 和角色管理器)在添加或删除单元格(在它们被持久化之前)时更有效。现在它们避免了一些不必要的对象复制。

4.2.0 (2017-08-24)

  • 添加 <zope:deny> 指令,它是 <zope:grant> 指令的镜像。

  • 支持 Python 3.6。

4.1.0 (2016-11-05)

  • 支持 Python 3.5。

  • 移除对 Python 2.6 的支持。

  • 支持通过一个 ZCML 语句授予多个权限。示例

    <grant
      role="my-role"
      permissions="zope.foo
                   zope.bar" />

4.0.0 (2014-12-24)

  • 支持 PyPy。 (PyPy3 正在等待修复:[a href="https://bitbucket.org/pypy/pypy/issue/1946" rel="nofollow">https://bitbucket.org/pypy/pypy/issue/1946

  • 支持 Python 3.4。

  • 支持在 Travis 上进行测试。

4.0.0a1 (2013-02-22)

  • 支持 Python 3.3。

  • 将废弃的 zope.interface.classProvides 使用替换为等效的 zope.interface.provider 装饰器。

  • 将废弃的 zope.interface.implements 使用替换为等效的 zope.interface.implementer 装饰器。

  • 移除对 Python 2.4 和 2.5 的支持。

3.7.0 (2010-09-25)

  • LP #131115:清理 getSetting 接口定义以及各种安全映射的实际使用中的不一致性。

  • LP #564525:修复权限从 zope.app.dublincore 命名空间移动到 zope.dublincore

  • 移除未使用的导入和 pep8 清理。

  • 使用 doctest 模块代替废弃的 zope.testing.doctest。

  • AnnotationGrantInfo 实现 IGrantInfo。

  • 添加测试额外信息以声明对 zope.component [test] 的测试依赖。

  • 添加一个名为 dublincore 的额外信息,以表达对 zope.dublincore >= 3.7 的可选依赖。

  • 添加测试 ZCML 文件以确保它们包含所需的一切。

3.6.1 (2009-07-24)

  • 当默认和 Zope 词汇注册表在清理过程中竞争时,使测试工作。

3.6.0 (2009-03-14)

  • zope.app.security 依赖项更改为新的 zope.authentication 包,移除大量未使用的依赖项。

  • 去除 zope.app.testing 和其他测试依赖项。

  • ZODB3 添加到安装依赖项中,因为我们使用了 Persistent 类。我们之前没有失败,因为它已隐式安装。

3.5.1 (2009-03-10)

  • 不要依赖于 zope.component 的 hook 额外信息,因为我们不需要显式地使用它。

  • 从上一个版本中添加的 zope.securitypolicy.settings 中导入安全设置(允许、拒绝、未设置)到 interfaces 模块,而不是旧 zope.app.security.settingszope.app.security 将适配以从 zope.securitypolicy.interfaces 中导入它们。

  • 对于 zope.securitypolicy.settings.PermissionSetting 单例实现存储实例,使用 _z_instances 而不是 __instances__,因为 __*__ 名称模式是 Python 中特殊名称的保留模式。

  • PermissionSetting 添加安全保护。

  • 改进文档格式,将其添加到包的长描述中。

  • 移除不必要的依赖项。

  • 移除旧的 zpkg 相关文件和 zcml 段落。

3.5.0 (2009-01-31)

  • 包括之前从 zope.app.security 导入的设置。

3.4.2 (2009-01-28)

  • 将邮件列表地址更改为 zope-dev at zope.org。修复包主页为 pypi 页面。

  • 修复 buildout 中的测试,该测试错误地依赖于 zope.app.securitypolicy。

  • setup.py 中移除对 zope.app.form 的显式依赖;代码中没有直接依赖于此。

3.4.1 (2008-06-02)

  • 修复 ZCML 中的废弃安全策略引用。

3.4.0 (2007-09-25)

  • 首次文档化版本

下载文件

下载适用于您平台的应用文件。如果您不确定该选择哪个,请了解更多关于安装包的信息。

源分布

zope.securitypolicy-5.0.tar.gz (38.3 kB 查看哈希值)

上传时间

构建分布

zope.securitypolicy-5.0-py3-none-any.whl (55.2 kB 查看哈希值)

上传时间 Python 3

由...