跳转到主要内容

Zope3的IAuthentication实现

项目描述

此包为Zope3提供IAuthentication实现。请注意,此实现独立于zope.app.authentication,并且不依赖于该包。这意味着它甚至不使用zope.app.authentication包提供的凭证或认证插件。

IAuthentication工具

认证器包提供了一种认证主体并与之关联信息的框架。它使用插件和订阅者来完成其工作。

为了使用简单的身份验证工具,它应该注册为提供 zope.authentication.interfaces.IAuthentication 接口的服务。

我们的目标是支持一个便捷的 IAuthentication 工具,它提供了一个简单的 API,用于自定义 IUser 实现,并且不依赖于默认的 zope.app.authentication 实现。

安全

认证器支持主机的唯一 ID 令牌。这意味着被删除并再次以相同 ID、登录等添加的主机不会有相同的 ID。我们通过生成由主机 ID、时间戳、随机字符串和登录属性生成的用户 ID 令牌来支持这一点。

与PluggableAuthentication的不同之处

在此实现中,我们使用与 zope.app.authentication 中的 PluggableAuthentication 不同的模式来使用 IAuthenticatorPlugins,因为可插拔认证在实现自定义主体信息时不是很方便。此实现中删除了支持 IPrincipalInfo 钩子,该钩子不支持传播 IInternalPrincipal 的密码。

在我们的实现中,我们提供 IFoundPrincipal 和 IAuthenticatedPrincipal,它们作为 IUser 的适配器实现。这些适配器不提供它们的上下文,即真实的 IUser。

认证器不使用前缀。前缀的使用仅在实际的 IGroupContainer 中实现。

由于使用了唯一的用户 ID 令牌,我们在 IUserContainer 中不使用前缀。这将确保在以后不会使用相同的主体 ID(常见标准)。有一个 add 方法可以根据登录名为您创建此 ID。不再直接使用 __setitem__ 方法添加 IUser 实例。我们严重限制了此方法的使用。有关更多信息,请参阅 __setitem__ 中的内联文档测试。

认证

认证器的主要工作是验证主体。它在其工作中使用两种类型的插件

  • 凭证插件

  • 认证器插件

凭证插件负责从请求中提取用户凭证。在某些情况下,凭证插件可能会发出“挑战”以获取凭证。例如,“会话”凭证插件从会话中读取凭证(“提取”)。如果它找不到凭证,它将重定向用户到登录表单以提供凭证(“挑战”)。

认证器插件负责验证凭证插件提取的凭证。它们通常还能为成功验证的凭证创建主体对象。

给定一个请求对象,如果认证器可以,它将返回一个主体对象。认证器通过首先迭代其凭证插件以获取一组凭证来完成此操作。如果它获取到凭证,它将迭代其认证器插件以验证它们。

如果认证器成功验证了一组凭证,认证器将使用认证器创建与凭证对应的主体。如果创建了已验证的主体,认证器将通知订阅者。订阅者负责向主体添加数据,尤其是组。通常,如果订阅者添加数据,它还应添加相应的接口声明。

常见问题解答

以下是一些有用的提示

我应该如何为主体设置权限?

您可以将角色应用于组,并将权限应用于角色。或者,您可以直接将本地权限应用于组或主体。在设置这些映射后,您可以授予角色给组。我总是推荐主体-组和权限-角色映射,这为您提供了最大的抽象,这对于在不直接调用主体和权限本身的情况下管理权限和主体非常有用。但当然,您也可以将权限授予组或最糟糕的情况,直接授予主体。将权限授予主体仅在需要为选定主体进行选择性的本地权限设置时才有用,例如类似所有权的权限设置。

我如何为所有主体设置权限?

您可以将一个组注册为IEveryone工具。这个IGroup工具将应用于所有主体。

我能将本地组应用于未经认证的主体吗?

是的,这可以工作。自从上次重构以来,我对IGroup实现进行了重构,使其与principalregistry API兼容。这意味着您现在可以注册一个本地组作为未命名的IUnauthenticatedGroup。您还可以注册一个本地组作为未命名的IAuthenticatedGroup工具,该工具将应用于每个已认证的主体或未命名的IUnauthenticatedGroup工具。

我能将一个本地组应用于每个主体吗?

是的,如果您注册了一个提供IEveryoneGroup的本地未命名工具,这是可能的。

主体

首先,我们创建一个主体

>>> from z3c.authenticator import interfaces
>>> from z3c.authenticator.user import User
>>> login = 'bob'
>>> password = 'secret'
>>> title = 'Bob'
>>> p = User(login, password, title)

这样的主体默认提供以下属性

>>> p.login
'bob'
>>> p.password.decode('utf-8')
'secret'
>>> p.title
'Bob'

和IUser

>>> interfaces.IUser.providedBy(p)
True

认证插件

首先设置一个UserContainer,该容器将存储主体

>>> from z3c.authenticator.user import UserContainer
>>> authPlugin = UserContainer()

现在我们有一个提供IUserContainer的UserContainer

>>> interfaces.IUserContainer.providedBy(authPlugin)
True

现在我们将使用容器的add方法将创建的主体添加到主体容器中

>>> uid, user = authPlugin.add(p)

此方法返回用户ID和用户对象。ID由主机IP地址、时间、随机字符串和用户登录属性生成。此令牌应该是唯一的,并保证它永远不会被生成两次。这允许我们添加、删除并再次添加相同的用户,而不会使此类用户继承现有权限。我们可以通过比较该测试中对象的__name__来测试此令牌,因为令牌在每次测试运行时都会不同。

>>> user.__name__ == uid
True

返回的用户仍然是之前添加的IUser

>>> user is p
True
>>> len(user.__name__)
32
>>> user.login
'bob'
>>> user.password.decode('utf-8')
'secret'
>>> user.title
'Bob'

让我们将UserContainer注册为命名的IAuthenticatorPlugin工具

>>> import zope.component
>>> zope.component.provideUtility(authPlugin,
...     provides=interfaces.IAuthenticatorPlugin,
...     name='My Authenticator Plugin')

凭证插件

在设置用户和用户容器之后,我们将创建一个简单的凭据插件

>>> import zope.interface
>>> import zope.component
>>> @zope.interface.implementer(interfaces.ICredentialsPlugin)
... class MyCredentialsPlugin(object):
...
...
...     def extractCredentials(self, request):
...         return {'login': request.get('login', ''),
...                 'password': request.get('password', '')}
...
...     def challenge(self, request):
...         pass # challenge is a no-op for this plugin
...
...     def logout(self, request):
...         pass # logout is a no-op for this plugin

作为一个插件,MyCredentialsPlugin需要注册为命名工具,或者它可以被存储在Authenticator的credentialsPlugins属性中。使用第一个并注册插件为工具

>>> myCredentialsPlugin = MyCredentialsPlugin()
>>> zope.component.provideUtility(myCredentialsPlugin,
...     name='My Credentials Plugin')

AuthenticatedPrincipal和FoundPrincipal

虽然认证插件提供用户,但它们不负责创建主体。这个功能由认证器执行

>>> from z3c.authenticator.principal import AuthenticatedPrincipal
>>> from z3c.authenticator.principal import FoundPrincipal
>>> zope.component.provideAdapter(AuthenticatedPrincipal,
...     provides=interfaces.IAuthenticatedPrincipal)
>>> zope.component.provideAdapter(FoundPrincipal,
...     provides=interfaces.IFoundPrincipal)

配置认证器

最后,我们将创建认证器本身

>>> from z3c.authenticator import authentication
>>> auth = authentication.Authenticator()

并使用两个插件进行配置

>>> auth.credentialsPlugins = ('My Credentials Plugin', )
>>> auth.authenticatorPlugins = ('My Authenticator Plugin', )

认证

现在我们可以使用认证器来验证一个示例请求

>>> from zope.publisher.browser import TestRequest
>>> print(auth.authenticate(TestRequest()))
None

在这种情况下,我们无法验证空请求。同样,我们无法验证凭据错误的请求

>>> request = TestRequest(form={'login': 'let me in!', 'password': 'secret'})
>>> print(auth.authenticate(request))
None

然而,如果我们提供正确的凭据

>>> request = TestRequest(form={'login': 'bob', 'password': 'secret'})
>>> bob = auth.authenticate(request)
>>> bob
<AuthenticatedPrincipal...>
>>> interfaces.IAuthenticatedPrincipal.providedBy(bob)
True

我们得到一个已认证的主体。

更改登录名

更改主体的登录(即用户名)始终是一个关键任务,因为这样的登录与密码是我们实现的关键。让我们尝试更改登录并检查是否一切正常。我们可以通过从UserContainer获取主体并在IUser实现上更改登录来实现这一点

>>> internal = authPlugin[bob.id]
>>> internal.login = 'bob2'

现在我们应该能够使用新的登录登录

>>> request = TestRequest(form={'login': 'bob2', 'password': 'secret'})
>>> bob2 = auth.authenticate(request)
>>> bob2
<AuthenticatedPrincipal ...>
>>> bob2.title
'Bob'

但不能使用旧的登录

>>> request = TestRequest(form={'login': 'bob', 'password': 'secret'})
>>> auth.authenticate(request) == None
True

用户bob仍然具有与bob2相同的ID,因为用户ID令牌不会通过更改登录而更改

>>> bob.id == bob2.id
True

事件

验证主体将创建事件。

>>> from zope.component.eventtesting import getEvents
>>> from zope.component.eventtesting import clearEvents

我们可以验证是否发布了适当的事件

>>> clearEvents()
>>> request = TestRequest(form={'login': 'bob2', 'password': 'secret'})
>>> bobAgain = auth.authenticate(request)

并且事件中的主体属性提供了已认证的主体

>>> [event] = getEvents(interfaces.IAuthenticatedPrincipalCreated)
>>> event.principal is bobAgain
True
>>> event.principal
<AuthenticatedPrincipal ...>
>>> event.request is request
True

主体具有ID、标题和描述。

>>> event.principal.title
'Bob'
>>> event.principal.id == uid
True
>>> event.principal.description
''

我们为这些事件提供订阅者,可用于执行自定义处理。请注意,主体属性提供IAuthenticatedPrincipal

>>> def addInfo(event):
...     id = event.principal.id
...     event.principal.description = 'Description for: %s' % id
>>> zope.component.provideHandler(addInfo,
...     [interfaces.IAuthenticatedPrincipalCreated])

现在,如果我们验证主体,则将其描述设置为

>>> principal = auth.authenticate(request)
>>> principal.description
'Description for: ...'

定制

让我们向您展示现有模式如何在实际用例中使用。在下一个示例中,我们希望为主体提供额外的电子邮件属性。首先,我们必须定义声明电子邮件属性的接口

>>> class IMyEmail(zope.interface.Interface):
...     email = zope.schema.TextLine(
...         title='EMail',
...         description='The EMail')
>>> class IMyUser(IMyEmail, interfaces.IUser):
...     """Custom IUser interface."""
>>> class IMyFoundPrincipal(IMyEmail, interfaces.IFoundPrincipal):
...     """Custom IIMyFoundrincipal interface."""
>>> class IMyAuthenticatedPrincipal(IMyEmail,
...     interfaces.IAuthenticatedPrincipal):
...     """Custom IAuthenticatedPrincipal interface."""

在模式定义之后,我们定义一个自定义主体实现,该实现实现此接口

>>> @zope.interface.implementer(IMyUser)
... class MyUser(User):
...     def __init__(self, login, password, title, description, email):
...         super(MyUser, self).__init__(login, password, title,
...                                           description)
...         self.email = email

现在我们需要为MyUser定义AuthenticatedPrincipal

>>> @zope.interface.implementer(IMyAuthenticatedPrincipal)
... class MyAuthenticatedPrincipal(AuthenticatedPrincipal):
...     def __init__(self, principal):
...         super(MyAuthenticatedPrincipal, self).__init__(principal)
...         self.email = principal.email

我们还需要为MyUser定义FoundPrincipal

>>> @zope.interface.implementer(IMyFoundPrincipal)
... class MyFoundPrincipal(FoundPrincipal):
...     def __init__(self, principal):
...         super(MyFoundPrincipal, self).__init__(principal)
...         self.email = principal.email

注意,如果需要,您可以提供不同属性给找到的和认证的主机。这取决于您以后如何使用这些属性。

现在我们需要注册我们的自定义认证和找到的主要适配器

>>> zope.component.provideAdapter(MyAuthenticatedPrincipal,
...     provides=interfaces.IAuthenticatedPrincipal)
>>> zope.component.provideAdapter(MyFoundPrincipal,
...     provides=interfaces.IFoundPrincipal)

现在我们可以在主要容器中无需其他事件订阅者或注册即可使用它们。让我们向这个容器添加一个主要实体

>>> p = MyUser('max', 'password', 'Max', '', 'max@foobar.com')
>>> token, max = authPlugin.add(p)
>>> len(token)
32
>>> max.__name__ == token
True
>>> max.password.decode('utf-8')
'password'
>>> max.title
'Max'
>>> max.email
'max@foobar.com'

让我们尝试进行认证…

>>> request = TestRequest(form={'login': 'max', 'password': 'password'})
>>> authenticated = auth.authenticate(request)

并检查您的认证主要实体

>>> interfaces.IAuthenticatedPrincipal.providedBy(authenticated)
True
>>> authenticated
<MyAuthenticatedPrincipal ...>
>>> authenticated.id == token
True
>>> authenticated.email
'max@foobar.com'

检查getUserByLogin

>>> max = authPlugin.getUserByLogin('max')
>>> max.__class__.__name__
'MyUser'
>>> authPlugin.getUserByLogin('max').login
'max'
>>> authPlugin.getUserByLogin('max').__name__ == token
True

对于迁移,有一个方便的功能,您可以设置自己的令牌。通常在z.a.authentication中,令牌 == 登录,我们想保持这种方式,除非您想迭代所有权限等。注意,容器中的__name__和id必须是相同的对象。

>>> login = 'migrateduser'
>>> p = User(login, 'password', 'John')

预设令牌

>>> p.__name__ = login

请注意,我们使用__setitem__而不是add(),因为add()会杀死__name__中预设的令牌

>>> authPlugin[login] = p

这里我们有了,用户已设置非生成令牌。

>>> 'migrateduser' in authPlugin.keys()
True
>>> authPlugin['migrateduser']
<z3c.authenticator.user.User object at ...>
>>> authPlugin.getUserByLogin('migrateduser')
<z3c.authenticator.user.User object at ...>

边缘情况

我们可以有带文本登录的用户,因为我们允许IUser中的TextLine。

>>> p = User('bob'+chr(233), 'password', 'title')

添加它不应失败

>>> uid, user = authPlugin.add(p)

群组

组容器提供对存储在ZODB中的组信息的支持。它们是持久的,并且必须在使用它们的IAuthentication中包含。

群组

与其他用户一样,组在需要时创建。

>>> from z3c.authenticator import interfaces
>>> from z3c.authenticator.group import Group
>>> group1 = Group('groups')
>>> group1
<Group None>
>>> interfaces.IGroup.providedBy(group1)
True
>>> group1.title
'groups'
>>> group1.description
''
>>> group1.principals
()

群组容器

组容器包含IGroup对象。IAuthentication将IFoundGroup适配到这些IGroup对象。

>>> from z3c.authenticator.group import GroupContainer
>>> groups = GroupContainer('groups.')
>>> interfaces.IGroupContainer.providedBy(groups)
True

您可以使用addGroup方法将先前创建的组添加到组容器中,该方法返回组ID和组

>>> gid, g1 = groups.addGroup('g1', group1)
>>> gid
'groups.g1'
>>> interfaces.IGroup.providedBy(g1)
True
>>> g1.__name__
'groups.g1'

注意,当组添加时,会生成GroupAdded事件

>>> from zope.component.eventtesting import getEvents
>>> getEvents(interfaces.IGroupAdded)
[<GroupAdded 'groups.g1'>]

组是根据认证服务定义的。组必须可以通过认证服务访问,并且可以包含可以通过认证服务访问的主机。为了说明组与认证服务的交互,我们将设置一个Authenticator实用工具

>>> from z3c.authenticator.authentication import Authenticator
>>> authenticator = Authenticator()

给它们一个位置并将它们注册为IAuthentication实用工具

>>> import zope.component
>>> from zope.authentication.interfaces import IAuthentication
>>> rootFolder['authenticator'] = authenticator
>>> zope.component.provideUtility(authenticator, IAuthentication)

我们将创建并注册一个新的主要实体实用工具

>>> zope.component.provideUtility(authenticator, IAuthentication)

我们还需要注册组认证插件

>>> zope.component.provideUtility(groups,
...     provides=interfaces.IAuthenticatorPlugin,
...     name='My Group Plugin')

设置组和组容器后,我们将创建一个简单的凭证插件并将其添加到认证实用工具中

>>> import zope.interface
>>> from z3c.authenticator import interfaces
>>> @zope.interface.implementer(interfaces.ICredentialsPlugin)
... class MyCredentialsPlugin(object):
...
...     def extractCredentials(self, request):
...         return {'login':request.get('login', ''),
...                 'password':request.get('password', '')}
...
...     def challenge(self, request):
...         pass # challenge is a no-op for this plugin
...
...     def logout(self, request):
...         pass # logout is a no-op for this plugin

并配置和添加凭证插件到Authenticator

>>> myCredentialsPlugin = MyCredentialsPlugin()
>>> authenticator['credentials'] = myCredentialsPlugin
>>> authenticator.credentialsPlugins = ('credentials', )

我们还需要一个主要实体和IAuthenticationPlugin

>>> from z3c.authenticator.user import User
>>> p1 = User('p1', 'password', 'Principal 1')
>>> p2 = User('p2', 'password', 'Principal 2')
>>> p3 = User('p3', 'password', 'Principal 3')
>>> p4 = User('p4', 'password', 'Principal 4')
>>> from z3c.authenticator.user import UserContainer
>>> users = UserContainer()
>>> token1, p1 = users.add(p1)
>>> token2, p2 = users.add(p2)
>>> token3, p3 = users.add(p3)
>>> token4, p4 = users.add(p4)

将GroupContainer和UserContainer添加到Authenticator并设置正确的插件名称

>>> authenticator['users'] = users
>>> authenticator['groups'] = groups
>>> authenticator.authenticatorPlugins = ('users', 'groups')

将用户添加到群组中

现在我们可以在组上设置用户,但首先我们需要注册组的IFoundPrincipal适配器。FoundGroup适配器提供此接口

>>> from z3c.authenticator.principal import FoundGroup
>>> zope.component.provideAdapter(FoundGroup,
...     provides=interfaces.IFoundPrincipal)

我们还需要为IPrincipal对象提供IFoundPrincipal和IAuthenticatedPrincipal适配器

>>> from z3c.authenticator.principal import AuthenticatedPrincipal
>>> from z3c.authenticator.principal import FoundPrincipal
>>> zope.component.provideAdapter(AuthenticatedPrincipal,
...     provides=interfaces.IAuthenticatedPrincipal)
>>> zope.component.provideAdapter(FoundPrincipal,
...     provides=interfaces.IFoundPrincipal)

我们需要setGroupsForPrincipal订阅者

>>> from z3c.authenticator.group import setGroupsForPrincipal
>>> zope.component.provideHandler(setGroupsForPrincipal,
...     [interfaces.IPrincipalCreated])
>>> g1.principals = [p1.__name__, p2.__name__]
>>> g1.principals
(..., ...)
>>> g1.principals[0] == p1.__name__
True
>>> g1.principals[1] == p2.__name__
True

添加用户会触发一个事件。

>>> getEvents(interfaces.IPrincipalsAddedToGroup)[-1]
<PrincipalsAddedToGroup [..., ...] 'groups.g1'>

我们现在可以查找用户的组

>>> groups.getGroupsForPrincipal(p1.__name__)
('groups.g1',)

请注意,组ID是组文件夹前缀和文件夹中组对象名称的串联。

如果我们删除一个组

>>> del groups['groups.g1']

那么组文件夹将失去该组用户的组信息

>>> groups.getGroupsForPrincipal('p1')
()

但组上的主要实体信息没有改变

>>> g1.principals
(..., ...)
>>> g1.principals[0] == p1.__name__
True
>>> g1.principals[1] == p2.__name__
True

它还会触发一个事件,显示用户已被从组中删除。

>>> getEvents(interfaces.IPrincipalsRemovedFromGroup)[-1]
<PrincipalsRemovedFromGroup [..., ...] 'groups.g1'>

在组名不同的情况下再次添加组将使组对主要实体可用。让我们使用不同的组名

>>> groups['groups.G1'] = g1
>>> groups.getGroupsForPrincipal(p1.__name__)
('groups.G1',)

这里我们看到新名称已反映在组信息中。

像往常一样,会触发一个事件。

>>> getEvents(interfaces.IPrincipalsAddedToGroup)[-1]
<PrincipalsAddedToGroup [..., ...] 'groups.G1'>

关于成员事件(添加和从组中删除成员),我们现在已经看到,当组对象被添加到组容器时,会触发事件;当它从组容器中删除时,也会触发事件。我们还看到,当将主体添加到已注册的组时,会触发事件。当从已注册的组中删除主体时,也会触发事件。让我们快速看看一些更多示例。

>>> g1.principals = (p1.__name__, p3.__name__, p4.__name__)
>>> g1.principals
(..., ..., ...)
>>> g1.principals[0] == p1.__name__
True
>>> g1.principals[1] == p3.__name__
True
>>> g1.principals[2] == p4.__name__
True
>>> getEvents(interfaces.IPrincipalsAddedToGroup)[-1]
<PrincipalsAddedToGroup [..., ...] 'groups.G1'>
>>> getEvents(interfaces.IPrincipalsRemovedFromGroup)[-1]
<PrincipalsRemovedFromGroup [...] 'groups.G1'>
>>> g1.principals = (p1.__name__, p2.__name__)
>>> g1.principals
(..., ...)
>>> g1.principals[0] == p1.__name__
True
>>> g1.principals[1] == p2.__name__
True
>>> getEvents(interfaces.IPrincipalsAddedToGroup)[-1]
<PrincipalsAddedToGroup [...] 'groups.G1'>
>>> getEvents(interfaces.IPrincipalsRemovedFromGroup)[-1]
<PrincipalsRemovedFromGroup [..., ...] 'groups.G1'>
>>> groups.getGroupsForPrincipal(p2.__name__)
('groups.G1',)

群组中的群组

组可以包含组

>>> g2 = Group('Group Two')
>>> groups['groups.G2'] = g2
>>> g2.principals
()
>>> g2.principals = ['groups.G1']
>>> g2.principals
('groups.G1',)
>>> groups.getGroupsForPrincipal('groups.G2')
()
>>> g1.principals
(..., ...)
>>> g1.principals[0] == p1.__name__
True
>>> g1.principals[1] == p2.__name__
True
>>> groups.getGroupsForPrincipal('groups.G1')
('groups.G2',)
>>> old = getEvents(interfaces.IPrincipalsAddedToGroup)[-1]
>>> old
<PrincipalsAddedToGroup ['groups.G1'] 'groups.G2'>

组不能包含循环

>>> g1.principals
(..., ...)
>>> g1.principals[0] == p1.__name__
True
>>> g1.principals[1] == p2.__name__
True
>>> g2.principals
('groups.G1',)
>>> g1.principals = (p1.__name__, p2.__name__, 'groups.G2')
Traceback (most recent call last):
...
z3c.authenticator.group.GroupCycle: (...)
>>> g1.principals
(..., ...)
>>> g1.principals[0] == p1.__name__
True
>>> g1.principals[1] == p2.__name__
True

尝试这样做不会触发事件。

>>> getEvents(interfaces.IPrincipalsAddedToGroup)[-1] is old
True

它们不必是层次结构

>>> ga = Group("Group A")
>>> groups['groups.GA'] = ga
>>> gb = Group("Group B")
>>> groups['groups.GB'] = gb
>>> gb.principals = ['groups.GA']
>>> gc = Group("Group C")
>>> groups['groups.GC'] = gc
>>> gc.principals = ['groups.GA']
>>> gd = Group("Group D")
>>> groups['groups.GD'] = gd
>>> gd.principals = ['groups.GA', 'groups.GB']
>>> ga.principals = [p1.__name__]

组容器提供了一个非常简单的搜索界面。它们对组标题和描述进行简单的字符串搜索。

>>> list(groups.search({'search': 'gro'}))
['groups.G1', 'groups.G2',
 'groups.GA', 'groups.GB', 'groups.GC', 'groups.GD']
>>> list(groups.search({'search': 'two'}))
['groups.G2']

它们也支持批处理

>>> list(groups.search({'search': 'gro'}, 2, 3))
['groups.GA', 'groups.GB', 'groups.GC']

如果您不提供搜索键,则不会返回任何结果

>>> list(groups.search({}))
[]

识别群组

函数 setGroupsForPrincipal 是主体创建事件的订阅者。它将任何组文件夹定义的组添加到这些组中的用户

>>> auth1 = authenticator.getPrincipal(p1.__name__)
>>> auth1.groups
['groups.G1', 'groups.GA']

当然,这也适用于组

>>> g1 = authenticator.getPrincipal('groups.G1')
>>> g1.id
'groups.G1'
>>> g1.groups
['groups.G2']

A FoundGroup 提供了 IFoundGroup,它继承自 IFoundPrincipal 和 IGroup

>>> interfaces.IFoundGroup.providedBy(g1)
True
>>> interfaces.IFoundPrincipal.providedBy(g1)
True
>>> import zope.security.interfaces
>>> zope.security.interfaces.IGroup.providedBy(g1)
True

特殊群组

两个特殊组,IAuthenticatedGroup 和 IEveryoneGroup,可能适用于 IAuthentication 工具创建的用户。有一个名为 specialGroups 的订阅者。此订阅者可以将这些特殊组设置在任何主体上,如果提供了 IAuthenticatedGroup 或 IEveryoneGroup 工具。订阅者还知道如何将本地组应用到主体上。请注意,主体是指 IAuthenticatedPrincipal、IFoundPrincipal 或 IFoundGroup。

如果我们用主体通知订阅者,则不会发生任何事情,因为组尚未定义

>>> from z3c.authenticator.principal import FoundPrincipal
>>> from z3c.authenticator.event import FoundPrincipalCreated
>>> from z3c.authenticator.group import specialGroups
>>> x = User('x', 'password', 'X')
>>> found = FoundPrincipal(x)
>>> event = FoundPrincipalCreated(authenticator, found)
>>> specialGroups(event)
>>> found.groups
[]

现在,如果我们定义了“所有人”组

>>> import zope.authentication.interfaces
>>> @zope.interface.implementer(
...        zope.authentication.interfaces.IEveryoneGroup)
... class EverybodyGroup(Group):
...     pass
>>> all = EverybodyGroup('groups.all')
>>> groups['groups.all'] = all
>>> zope.component.provideUtility(all,
...     zope.authentication.interfaces.IEveryoneGroup)

那么该组将被添加到主体

>>> specialGroups(event)
>>> found.groups
['groups.all']

同样,对于已验证的组

>>> @zope.interface.implementer(
...         zope.authentication.interfaces.IAuthenticatedGroup)
... class AuthenticatedGroup(Group):
...     pass
>>> authenticated = AuthenticatedGroup('groups.authenticated')
>>> groups['groups.authenticated'] = authenticated
>>> zope.component.provideUtility(authenticated,
...     zope.authentication.interfaces.IAuthenticatedGroup)

那么该组将被添加到主体

>>> found.groups = []
>>> specialGroups(event)
>>> found.groups.sort()
>>> found.groups
['groups.all', 'groups.authenticated']

重要的是,我们不要将组应用两次,因为未经验证的主体在安全策略中是单个实例。此问题已在版本 0.6.1 和 0.7.1 中得到修复

>>> specialGroups(event)
>>> found.groups
['groups.all', 'groups.authenticated']

所有群组

allGroups 属性是 groups 属性中组的全闭包的可读只迭代器。首先让我们定义一个新的主体

>>> p = User('p', 'password', 'Principal')
>>> token, p = users.add(p)

然后是组

>>> ga = Group("Administrators")
>>> gr = Group("Reviewers")
>>> gid, ga = groups.addGroup('Administrators', ga)
>>> gid, gr = groups.addGroup('Reviewers', gr)

如果主体是“管理员”组的直接成员

>>> ga.principals = [p.__name__]

则 getGroupsForPrincipal 会是 [‘Administrators’]

>>> groups.getGroupsForPrincipal(p.__name__)
('groups.Administrators',)

如果“管理员”组是“审阅者”组的成员

>>> gr.principals = [ga.id]

那么组也会是 [‘Administrators’]。

>>> groups.getGroupsForPrincipal(p.__name__)
('groups.Administrators',)

现在让我们使用知道如何将组应用到找到的主体上的 setGroupsForPrincipal 订阅者

>>> pFound = FoundPrincipal(p)
>>> event = FoundPrincipalCreated(authenticator, pFound)
>>> setGroupsForPrincipal(event)

如您所见,pFound.groups 是 [‘Administrators’]。

>>> sorted(pFound.groups)
['groups.Administrators']

并且 pFound.allGroups 是 [‘Administrators’,‘Reviewers’]。

>>> sorted(pFound.allGroups)
['groups.Administrators', 'groups.Reviewers']

词汇表

词汇模块为认证插件和凭据插件提供词汇表。

选项应包括所有提供当前上下文适当接口(分别为 IAuthentiatorPlugin 或 ICredentialsPlugin)的插件的唯一名称。当前上下文预计是 IAuthenticator 工具,以下称为 Authenticator。

这些名称可能是 Authenticator 内部包含的对象(“包含插件”),也可能是注册为指定接口的实用程序,在 Authenticator 的上下文中找到(“实用程序插件”)。包含插件会掩盖同名的实用程序插件。它们也可能是在 Authenticator 中当前选中但尚未实际对应插件的名字。

以下是如何工作的简短示例。假设我们正在处理认证插件。我们将创建一些伪造的认证插件,并将其中一些注册为实用程序,将其他一些放入伪造的 Authenticator 中。

>>> from z3c.authenticator import interfaces
>>> import zope.interface
>>> import zope.component
>>> @zope.interface.implementer(interfaces.IAuthenticatorPlugin)
... class DemoPlugin(object):
...     def __init__(self, name):
...         self.name = name
...
>>> utility_plugins = dict(
...     (i, DemoPlugin('Plugin %d' % i)) for i in range(4))
>>> contained_plugins = dict(
...     (i, DemoPlugin('Plugin %d' % i)) for i in range(1, 5))
>>> sorted(utility_plugins.keys())
[0, 1, 2, 3]
>>> for p in utility_plugins.values():
...     zope.component.provideUtility(p, name=p.name)
...
>>> sorted(contained_plugins.keys()) # 1 will mask utility plugin 1
[1, 2, 3, 4]
>>> @zope.interface.implementer(interfaces.IAuthenticator)
... class DemoAuth(dict):
...     def __init__(self, *args, **kwargs):
...         super(DemoAuth, self).__init__(*args, **kwargs)
...         self.authenticatorPlugins = ('Plugin 3', 'Plugin X')
...         self.credentialsPlugins = ('Plugin 4', 'Plugin X')
...
>>> auth = DemoAuth((p.name, p) for p in contained_plugins.values())
>>> @zope.component.adapter(zope.interface.Interface)
... @zope.interface.implementer(zope.component.IComponentLookup)
... def getSiteManager(context):
...     return zope.component.getGlobalSiteManager()
...
>>> zope.component.provideAdapter(getSiteManager)

authenticatorPlugins

我们现在可以创建一个可以使用的新词汇。上下文是我们的伪造认证实用程序,auth

>>> from z3c.authenticator import vocabulary
>>> vocab = vocabulary.authenticatorPlugins(auth)

遍历词汇结果会产生所有术语,按其名称的相对任意顺序排列。(此词汇应通常使用一个可以根据术语标题的本地化排序顺序对值进行排序的小部件。)

>>> [term.value for term in vocab] # doctest: +NORMALIZE_WHITESPACE
['Plugin 0', 'Plugin 1', 'Plugin 2', 'Plugin 3', 'Plugin 4',
 'Plugin X']

同样,我们可以使用 in 来测试词汇中是否存在值。

>>> ['Plugin %s' % i in vocab for i in range(-1, 6)]
[False, True, True, True, True, True, False]
>>> 'Plugin X' in vocab
True

长度报告的是预期的值。

>>> len(vocab)
6

可以使用 getTerm() 获取特定值对应的术语;其令牌反过来也应当通过 getTermByToken 返回相同有效的术语。

>>> values = ['Plugin 0', 'Plugin 1', 'Plugin 2', 'Plugin 3', 'Plugin 4',
...           'Plugin X']
>>> for val in values:
...     term = vocab.getTerm(val)
...     assert term.value == val
...     term2 = vocab.getTermByToken(term.token)
...     assert term2.token == term.token
...     assert term2.value == val
...

术语有标题,这些是消息ID,显示插件标题或ID以及插件是否为工具或仅包含在认证工具中。我们将给其中一个插件一个都柏林核心标题,以展示其功能。

>>> import zope.dublincore.interfaces
>>> class ISpecial(zope.interface.Interface):
...     pass
...
>>> zope.interface.directlyProvides(contained_plugins[1], ISpecial)
>>> @zope.interface.implementer(
...         zope.dublincore.interfaces.IDCDescriptiveProperties)
... class DemoDCAdapter(object):
...     zope.component.adapts(ISpecial)
...     def __init__(self, context):
...         pass
...     title = 'Special Title'
...
>>> zope.component.provideAdapter(DemoDCAdapter)

由于它一次计算所有数据,我们需要重新生成词汇表。

>>> vocab = vocabulary.authenticatorPlugins(auth)

现在我们将检查标题。我们需要翻译它们以了解期望的内容。

>>> from zope import i18n
>>> import pprint
>>> pprint.pprint([i18n.translate(term.title) for term in vocab])
['Plugin 0 (a utility)',
 'Special Title (in contents)',
 'Plugin 2 (in contents)',
 'Plugin 3 (in contents)',
 'Plugin 4 (in contents)',
 'Plugin X (not found; deselecting will remove)']

credentialsPlugins

为了完整性,我们还将对 credentialsPlugins 进行同样的审查。

>>> @zope.interface.implementer(interfaces.ICredentialsPlugin)
... class DemoPlugin(object):
...     def __init__(self, name):
...         self.name = name
...
>>> utility_plugins = dict(
...     (i, DemoPlugin('Plugin %d' % i)) for i in range(4))
>>> contained_plugins = dict(
...     (i, DemoPlugin('Plugin %d' % i)) for i in range(1, 5))
>>> for p in utility_plugins.values():
...     zope.component.provideUtility(p, name=p.name)
...
>>> auth = DemoAuth((p.name, p) for p in contained_plugins.values())
>>> vocab = vocabulary.credentialsPlugins(auth)

遍历词汇表结果将产生所有术语,按其名称的相对任意顺序。(此词汇表通常应使用一个小部件,根据术语标题的本地排序顺序对值进行排序。)同样,我们可以使用 in 来测试词汇表中是否存在值。长度报告了预期值。

>>> [term.value for term in vocab] # doctest: +NORMALIZE_WHITESPACE
['Plugin 0', 'Plugin 1', 'Plugin 2', 'Plugin 3', 'Plugin 4',
 'Plugin X']
>>> ['Plugin %s' % i in vocab for i in range(-1, 6)]
[False, True, True, True, True, True, False]
>>> 'Plugin X' in vocab
True
>>> len(vocab)
6

可以使用 getTerm() 获取特定值对应的术语;其令牌反过来也应当通过 getTermByToken 返回相同有效的术语。

>>> values = ['Plugin 0', 'Plugin 1', 'Plugin 2', 'Plugin 3', 'Plugin 4',
...           'Plugin X']
>>> for val in values:
...     term = vocab.getTerm(val)
...     assert term.value == val
...     term2 = vocab.getTermByToken(term.token)
...     assert term2.token == term.token
...     assert term2.value == val
...

术语有标题,这些是消息ID,显示插件标题或ID以及插件是否为工具或仅包含在认证工具中。我们将给其中一个插件一个都柏林核心标题,以展示其功能。由于它一次计算所有数据,我们需要重新生成词汇表。然后我们将检查标题。我们需要翻译它们以了解期望的内容。

>>> zope.interface.directlyProvides(contained_plugins[1], ISpecial)
>>> vocab = vocabulary.credentialsPlugins(auth)
>>> pprint.pprint([i18n.translate(term.title) for term in vocab])
['Plugin 0 (a utility)',
 'Special Title (in contents)',
 'Plugin 2 (in contents)',
 'Plugin 3 (in contents)',
 'Plugin 4 (in contents)',
 'Plugin X (not found; deselecting will remove)']

变更日志

2.0 (2023-02-09)

  • 停止支持 Python 2.6、2.7、3.4、3.5、3.6。

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

  • 删除未使用的导入。

1.0.1 (2018-05-16)

  • bugfix: 从 queryNextUtiliy 中删除 None,因为 getNextUtility 签名已更改,并且 None 被用作名称参数。

  • cleanup,删除未使用的导入

  • 消除弃用警告。将 hooks 导入从 zope.site 切换到 zope.component

  • 跳过对 IAuthenticator 容器进行测试_exception_in_subscriber_leaves_item_in_place。此 zope.container 测试方法遗留了一个 zope.container 版本 4.2.1 的事件订阅者,该订阅者在其他方法中引发错误。需要审查,可能还需要在 zope.container 测试方法中进行拆卸。

1.0.0 (2017-04-17)

  • Ping Python 支持到 2.7、3.5、3.6 和 PyPy。

1.0.0a5 (2013-03-31)

  • 未测试代码中的更多 Py3 兼容性错误。

1.0.0a4 (2013-02-28)

  • 删除了对 Zope 代数的支持。它没有使用,并且应用程序级的代数更有意义。这删除了 zope.app.generationszope.generations 依赖项。

  • 使 z3c.configurator 支持可选。

  • 稍微清理了源代码。

1.0.0a3 (2013-02-28)

  • 将清单更改为实际包含页面模板。

1.0.0a2 (2013-02-28)

  • 添加了版本 Trove 分类器。

1.0.0a1 (2013-02-28)

  • 停止支持 Python 2.4 和 2.5,添加 Python 3.3 支持。

  • 删除了对 zope.app 包的依赖。

0.8.1 (2011-01-21)

  • bugfix: 未处理 unicode IUser.login 值。

  • 修复了 DeprecationWarnings。

0.8.0 (2010-01-25)

  • 安全修复:将 camefrom url 移动到会话变量中,而不是在登录表单中公开 url。因为 camefrom url 是基于本地信息在服务器端构建的,并且将始终只使用内部遍历名称。在登录 url 中公开此 camefrom 查询只给其他人提供了一个攻击点,因为它可以被不友好的域简单地设置为一个自定义 url。这样做要好得多,因为这样的不友好第三方域 url 不会根据 zope.publisher 的 redirect 方法更改默认情况下重定向。 (默认情况下,zope.publisher 3.9.3 只重定向到同一域中的 url)

    如果您使用任何自定义表单,请从我们的自定义表单中删除所有 camefrom 小部件和查询。如果需要,您可以直接在自定义表单中设置和获取 camefrom 会话变量。

0.7.2 (2010-01-26)

  • bugfix:在具有非 ASCII 名称的页面上的挑战失败。

0.7.1 (2009-08-19)

  • bugfix:即使组已被应用,方法 specialGroups 也会在每次调用方法时应用组。如果使用全局共享的未认证主体实例,则这是一个问题,因为它将应用类似的组,直到服务器重启并使用新的主体实例。

  • 特性:在IUserContainer中添加了getUserByLogin方法

  • 添加了对用户迁移的测试(确保用户ID保持不变)

0.7.0 (2009-05-11)

  • 更新依赖项

    • 使用zope.container而不是zope.app.container

    • 使用zope.site而不是zope.app.component

    • 使用zope.authenticationzope.principalregistry而不是zope.app.security

    • 使用zope.password而不是维护密码管理器的副本。

  • 移除对z3c.i18n的依赖,并重新创建消息工厂。

0.6.2 (2010-01-26)

  • 错误修复:在具有非ASCII名称的页面上挑战失败。(从0.7.2回滚)

0.6.1 (2009-08-19)

  • 将0.7.1的错误修复回滚到0.6.1。请参阅branches/roger-0.6.0

    方法specialGroups在每次调用该方法时都会应用组,即使组已经被应用。如果在使用全局共享未认证主体实例的情况下这样做,这会成为一个问题,因为它会应用相似的组直到服务器重启并使用新的主体实例。

0.6.0 (2009-01-04)

  • 特性:添加了对本地IUnauthenticatedPrincipal的支持。如果您需要将本地角色应用到IUnauthenticatedPrincipal,这将很有用。这以前是不可能的,在zope.app.authentication中也是不可能的。

  • 特性:基于ISource小部件实现了初始授权视图。注意,如果解决了ITerm依赖,则此源小部件的实现将移至z3c.from,因为这将非常复杂。这意味着ITerm需要首先从zope.app.form中移除。

  • 特性:在认证调用中添加了对下一个实用工具查找的支持。默认情况下,现在涉及全局主体注册表中的主体。您可以通过将includeNextUtilityForAuthenticate设置为False来禁用此功能。

  • 特性:添加了PrincipalRegistryAuthenticatorPlugin,允许认证全局主体注册表中定义的主体。

  • 特性:在SessionCredentialsPlugin中实现了z3c.form前缀支持。现在有一个名为prefixes的选项,可以用来定义一组使用的z3c.form前缀。这使得支持不同的表单和调整凭证提取变得简单。

  • 将IGroupPrincipal重命名为IFoundGroup,使其更易于理解为什么需要这种适配器实现。现在,IFoundGroup适配器也用于zope.security.interfaces.IGroup主体。这使得它们在新主体注册表凭证中使用成为可能。为旧的IGroupPrincipal实现提供弃用信息。

  • 移除了对zapi的依赖,但实际上并没有真正移除,因为其他包也使用zapi。

  • 移除了未使用的InvalidPrincipalIds和InvalidGroupId异常。

  • 移除了未使用的IMemberAwareGroup支持。该接口在zope中根本未使用。

  • 为Pypi主页添加了文档。

0.5.1 (2008-04-16)

  • 清理导入项并调整依赖项

0.5.0 (2008-04-16)

  • 初始发布

项目详情


下载文件

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

源分发

z3c.authenticator-2.0.tar.gz (66.4 kB 查看散列)

上传于 源代码

构建版本

z3c.authenticator-2.0-py3-none-any.whl (68.7 kB 查看哈希值)

上传于 Python 3

支持者