跳转到主要内容

用于可插入认证工具的主组和组管理

项目描述

此软件包为Zope 3提供了一个灵活的可插入认证工具,使用 zope.pluggableauth。提供了几个常见的插件。

可插入认证工具

可插入认证工具(PAU)提供了一个用于认证主实体并将信息与之关联的框架。它使用插件和订阅者来完成其工作。

要使用可插入认证工具,它应该注册为提供 zope.authentication.interfaces.IAuthentication 接口的工具。

认证

PAU的主要工作是进行主体认证。它在工作中使用两种类型的插件

  • 凭证插件

  • 认证插件

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

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

给定一个请求对象,如果PAU能够,它将返回一个主体对象。PAU通过首先遍历其凭证插件以获取一组凭证来实现这一点。如果它获取到凭证,它将遍历其认证插件来认证它们。

如果认证插件成功认证了一组凭证,PAU将使用认证插件来创建与凭证相对应的主体。认证插件在创建已认证的主体时通知订阅者。订阅者负责向主体添加数据,特别是组。通常,如果订阅者添加数据,它也应该添加相应的接口声明。

简单凭证插件

为了说明,我们将创建一个简单的凭证插件

>>> from zope import interface
>>> from zope.app.authentication import interfaces

>>> @interface.implementer(interfaces.ICredentialsPlugin)
... class MyCredentialsPlugin(object):
...
...
...     def extractCredentials(self, request):
...         return request.get('credentials')
...
...     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需要注册为一个命名工具

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

简单认证器插件

接下来我们将创建一个简单的认证插件。对于我们的插件,我们需要实现IPrincipalInfo

>>> @interface.implementer(interfaces.IPrincipalInfo)
... class PrincipalInfo(object):
...
...     def __init__(self, id, title, description):
...         self.id = id
...         self.title = title
...         self.description = description
...
...     def __repr__(self):
...         return 'PrincipalInfo(%r)' % self.id

我们的认证插件在创建主体信息时使用此类型

>>> @interface.implementer(interfaces.IAuthenticatorPlugin)
... class MyAuthenticatorPlugin(object):
...
...     def authenticateCredentials(self, credentials):
...         if credentials == 'secretcode':
...             return PrincipalInfo('bob', 'Bob', '')
...
...     def principalInfo(self, id):
...         pass # plugin not currently supporting search

与凭证插件一样,认证插件必须注册为命名工具

>>> myAuthenticatorPlugin = MyAuthenticatorPlugin()
>>> provideUtility(myAuthenticatorPlugin, name='My Authenticator Plugin')

主实体工厂

虽然认证插件提供主体信息,但它们不负责创建主体。这个功能由工厂适配器执行。对于这些测试,我们将从主体文件夹借用一些工厂

>>> from zope.app.authentication import principalfolder
>>> provideAdapter(principalfolder.AuthenticatedPrincipalFactory)
>>> provideAdapter(principalfolder.FoundPrincipalFactory)

有关这些工厂的更多信息,请参阅它们的文档字符串。

配置PAU

最后,我们将创建PAU本身

>>> from zope.app import authentication
>>> pau = authentication.PluggableAuthentication('xyz_')

并用两个插件进行配置

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

使用PAU进行认证

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

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

在这种情况下,我们无法认证一个空请求。同样,我们也不会能够认证一个凭证错误的请求

>>> print(pau.authenticate(TestRequest(credentials='let me in!')))
None

但是,如果我们提供正确的凭证

>>> request = TestRequest(credentials='secretcode')
>>> principal = pau.authenticate(request)
>>> principal
Principal('xyz_bob')

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

认证主实体创建事件

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

>>> [event] = getEvents(interfaces.IAuthenticatedPrincipalCreated)
>>> event.principal is principal
True
>>> event.info
PrincipalInfo('bob')
>>> event.request is request
True

信息对象具有主体的id、标题和描述。信息对象也是由认证插件生成的,因此插件可以自己向信息对象提供更多信息

>>> event.info.title
'Bob'
>>> event.info.id # does not include pau prefix
'bob'
>>> event.info.description
''

它还装饰了两个其他属性,credentialsPlugin和authenticatorPlugin:这些是用于提取此主体凭证和认证该主体的插件。这些属性对于希望对使用的插件做出反应的订阅者可能很有用。例如,订阅者可以确定某个特定的凭证插件是否支持注销,并提供可用于显示或隐藏注销用户界面的信息

>>> event.info.credentialsPlugin is myCredentialsPlugin
True
>>> event.info.authenticatorPlugin is myAuthenticatorPlugin
True

通常,我们为这些事件提供订阅者,以便向主体添加更多信息。例如,我们将添加一个设置标题的订阅者

>>> def add_info(event):
...     event.principal.title = event.info.title
>>> provideHandler(add_info, [interfaces.IAuthenticatedPrincipalCreated])

现在,如果我们认证一个主体,它的标题将被设置

>>> principal = pau.authenticate(request)
>>> principal.title
'Bob'

多个认证器插件

PAU与多个认证插件一起工作。它使用PAU的authenticatorPlugins属性中指定的顺序使用每个插件来认证一组凭证。

为了说明,我们将创建另一个认证插件

>>> class MyAuthenticatorPlugin2(MyAuthenticatorPlugin):
...
...     def authenticateCredentials(self, credentials):
...         if credentials == 'secretcode':
...             return PrincipalInfo('black', 'Black Spy', '')
...         elif credentials == 'hiddenkey':
...             return PrincipalInfo('white', 'White Spy', '')

>>> provideUtility(MyAuthenticatorPlugin2(), name='My Authenticator Plugin 2')

如果我们将其放置在原始认证插件之前

>>> pau.authenticatorPlugins = (
...     'My Authenticator Plugin 2',
...     'My Authenticator Plugin')

那么它将获得第一个认证请求的机会

>>> pau.authenticate(TestRequest(credentials='secretcode'))
Principal('xyz_black')

如果两个插件都无法认证,pau返回None

>>> print(pau.authenticate(TestRequest(credentials='let me in!!')))
None

当我们更改认证插件顺序时

>>> pau.authenticatorPlugins = (
...     'My Authenticator Plugin',
...     'My Authenticator Plugin 2')

我们看到我们的原始插件现在首先执行

>>> pau.authenticate(TestRequest(credentials='secretcode'))
Principal('xyz_bob')

第二个插件,然而,如果第一个插件失败,它将有机会进行身份验证

>>> pau.authenticate(TestRequest(credentials='hiddenkey'))
Principal('xyz_white')

多个凭证插件

与身份验证器一样,我们可以指定多个凭证插件。为了说明,我们将创建一个凭证插件,该插件可以从请求表单中提取凭证

>>> @interface.implementer(interfaces.ICredentialsPlugin)
... class FormCredentialsPlugin:
...
...     def extractCredentials(self, request):
...         return request.form.get('my_credentials')
...
...     def challenge(self, request):
...         pass
...
...     def logout(request):
...         pass

>>> provideUtility(FormCredentialsPlugin(),
...                name='Form Credentials Plugin')

并在现有插件之前插入新的凭证插件

>>> pau.credentialsPlugins = (
...     'Form Credentials Plugin',
...     'My Credentials Plugin')

PAU将按顺序使用每个插件,尝试从请求中获取凭证

>>> pau.authenticate(TestRequest(credentials='secretcode',
...                              form={'my_credentials': 'hiddenkey'}))
Principal('xyz_white')

在这种情况下,第一个凭证插件成功地从表单中获取了凭证,第二个身份验证器能够验证这些凭证。具体来说,PAU遵循以下步骤

  • 使用“表单凭证插件”获取凭证

  • 使用“表单凭证插件”获取“hiddenkey”凭证,尝试使用“我的身份验证器插件”进行验证

  • 使用“我的身份验证器插件”验证“hiddenkey”失败,尝试“我的身份验证器插件2”

  • 使用“我的身份验证器插件2”成功进行了验证

让我们尝试一个不同的场景

>>> pau.authenticate(TestRequest(credentials='secretcode'))
Principal('xyz_bob')

在这种情况下,PAU遵循以下步骤

- Get credentials using 'Form Credentials Plugin'

- Failed to get credentials using 'Form Credentials Plugin', try
  'My Credentials Plugin'

- Got 'scecretcode' credentials using 'My Credentials Plugin', try to
  authenticate using 'My Authenticator Plugin'

- Succeeded in authenticating with 'My Authenticator Plugin'

让我们尝试一个稍微复杂一些的场景

>>> pau.authenticate(TestRequest(credentials='hiddenkey',
...                              form={'my_credentials': 'bogusvalue'}))
Principal('xyz_white')

这突出了PAU使用多个插件进行身份验证的能力

  • 使用“表单凭证插件”获取凭证

  • 使用“表单凭证插件”获取“bogusvalue”凭证,尝试使用“我的身份验证器插件”进行验证

  • 使用“我的身份验证器插件”验证“boguskey”失败,尝试“我的身份验证器插件2”

  • 使用“我的身份验证器插件2”验证“boguskey”失败——没有更多的身份验证器可以尝试,因此让我们尝试下一个凭证插件以获取一些新的凭证

  • 使用“我的凭证插件”获取凭证

  • 使用“我的凭证插件”获取“hiddenkey”凭证,尝试使用“我的身份验证器插件”进行验证

  • 使用“我的身份验证器插件”验证“hiddenkey”失败,尝试“我的身份验证器插件2”

  • 使用“我的身份验证器插件2”成功进行了验证(欢呼!)

主实体搜索

作为一个提供IAuthentication的组件,PAU允许您使用主体ID查找主体。PAU通过委托给其身份验证器来查找主体。在我们的例子中,没有身份验证器实现了此搜索功能,因此当我们寻找主体时

>>> print(pau.getPrincipal('xyz_bob'))
Traceback (most recent call last):
zope.authentication.interfaces.PrincipalLookupError: bob

>>> print(pau.getPrincipal('white'))
Traceback (most recent call last):
zope.authentication.interfaces.PrincipalLookupError: white

>>> print(pau.getPrincipal('black'))
Traceback (most recent call last):
zope.authentication.interfaces.PrincipalLookupError: black

为了支持搜索,PAU需要配置一个或多个支持搜索的身份验证器插件。为了说明,我们将创建一个新的身份验证器

>>> @interface.implementer(interfaces.IAuthenticatorPlugin)
... class SearchableAuthenticatorPlugin:
...
...     def __init__(self):
...         self.infos = {}
...         self.ids = {}
...
...     def principalInfo(self, id):
...         return self.infos.get(id)
...
...     def authenticateCredentials(self, credentials):
...         id = self.ids.get(credentials)
...         if id is not None:
...             return self.infos[id]
...
...     def add(self, id, title, description, credentials):
...         self.infos[id] = PrincipalInfo(id, title, description)
...         self.ids[credentials] = id

这个类是身份验证器插件的一个典型例子。它可以验证主体,也可以根据ID找到主体。虽然在某些情况下,身份验证器可以选择不执行这两个功能之一,但这种情况较少。

与任何插件一样,我们需要将其注册为实用工具

>>> searchable = SearchableAuthenticatorPlugin()
>>> provideUtility(searchable, name='Searchable Authentication Plugin')

现在我们将配置PAU仅使用可搜索的身份验证器

>>> pau.authenticatorPlugins = ('Searchable Authentication Plugin',)

并添加一些主体到身份验证器中

>>> searchable.add('bob', 'Bob', 'A nice guy', 'b0b')
>>> searchable.add('white', 'White Spy', 'Sneaky', 'deathtoblack')

现在当我们要求PAU查找主体时

>>> pau.getPrincipal('xyz_bob')
Principal('xyz_bob')

但只查找它所知道的主体

>>> print(pau.getPrincipal('black'))
Traceback (most recent call last):
zope.authentication.interfaces.PrincipalLookupError: black

找到主实体创建事件

正如在身份验证器的“createFoundPrincipal”方法(见上面)中所示,当身份验证器代表PAU的“getPrincipal”找到主体时,会发布FoundPrincipalCreatedEvent

>>> clearEvents()
>>> principal = pau.getPrincipal('xyz_white')
>>> principal
Principal('xyz_white')

>>> [event] = getEvents(interfaces.IFoundPrincipalCreated)
>>> event.principal is principal
True
>>> event.info
PrincipalInfo('white')

信息有一个身份验证器插件,但没有凭证插件,因为没有使用任何插件

>>> event.info.credentialsPlugin is None
True
>>> event.info.authenticatorPlugin is searchable
True

正如我们通过已验证的主体所看到的,订阅主体创建事件以向新创建的主体添加信息是常见的。在这种情况下,我们需要订阅IFoundPrincipalCreated事件

>>> provideHandler(add_info, [interfaces.IFoundPrincipalCreated])

现在,当一个主体作为搜索的结果被创建时,它的标题和描述将被设置(由add_info处理函数完成)。

多个认证器插件

与我们所看到的其他操作一样,PAU使用多个插件来查找主体。如果第一个身份验证器插件找不到请求的主体,则使用下一个插件,依此类推。

为了说明,我们将创建并注册第二个可搜索的身份验证器

>>> searchable2 = SearchableAuthenticatorPlugin()
>>> provideUtility(searchable2, name='Searchable Authentication Plugin 2')

并向它添加一个主体

>>> searchable.add('black', 'Black Spy', 'Also sneaky', 'deathtowhite')

当我们配置PAU使用两个可搜索的身份验证器时(注意顺序)

>>> pau.authenticatorPlugins = (
...     'Searchable Authentication Plugin 2',
...     'Searchable Authentication Plugin')

我们看到PAU如何使用这两个插件

>>> pau.getPrincipal('xyz_white')
Principal('xyz_white')

>>> pau.getPrincipal('xyz_black')
Principal('xyz_black')

如果多个插件都知道相同的主体ID,则首先使用该插件,而不会将剩余的插件委托出去。为了说明,我们将添加一个与现有主体具有相同ID的新主体

>>> searchable2.add('white', 'White Rider', '', 'r1der')
>>> pau.getPrincipal('xyz_white').title
'White Rider'

如果我们更改插件的顺序

>>> pau.authenticatorPlugins = (
...     'Searchable Authentication Plugin',
...     'Searchable Authentication Plugin 2')

对于ID 'white',我们会得到不同的主要配置

>>> pau.getPrincipal('xyz_white').title
'White Spy'

发出挑战

PAU的IAuthentication合同的一部分是在其“未授权”方法被调用时对用户进行凭证挑战。这一功能的需求是由以下用例驱动的

  • 一个用户试图执行他没有授权执行的操作。

  • 处理程序通过调用IAuthentication的“未授权”来响应未授权错误。

  • 认证组件(在我们的情况下,是PAU)向用户发起挑战,以收集新的凭证(通常是以新用户登录的形式)。

PAU通过委派给其凭证插件来处理凭证挑战。

目前,PAU配置了在要求挑战时没有任何动作的凭证插件(见上面的“挑战”方法)。

为了说明挑战,我们将对现有的凭证插件进行子类化,并在其“挑战”方法中执行一些操作

>>> class LoginFormCredentialsPlugin(FormCredentialsPlugin):
...
...     def __init__(self, loginForm):
...         self.loginForm = loginForm
...
...     def challenge(self, request):
...         request.response.redirect(self.loginForm)
...         return True

此插件通过将响应重定向到登录表单来处理挑战。它返回True以向PAU发出信号,表明它已处理挑战。

我们现在将创建并注册几个这样的插件

>>> provideUtility(LoginFormCredentialsPlugin('simplelogin.html'),
...                name='Simple Login Form Plugin')

>>> provideUtility(LoginFormCredentialsPlugin('advancedlogin.html'),
...                name='Advanced Login Form Plugin')

并将PAU配置为使用它们

>>> pau.credentialsPlugins = (
...     'Simple Login Form Plugin',
...     'Advanced Login Form Plugin')

现在当我们对PAU调用“未授权”时

>>> request = TestRequest()
>>> pau.unauthorized(id=None, request=request)

我们看到用户被重定向到简单的登录表单

>>> request.response.getStatus()
302
>>> request.response.getHeader('location')
'simplelogin.html'

我们可以通过重新排序插件来更改挑战策略

>>> pau.credentialsPlugins = (
...     'Advanced Login Form Plugin',
...     'Simple Login Form Plugin')

现在当我们调用“未授权”时

>>> request = TestRequest()
>>> pau.unauthorized(id=None, request=request)

因为它是第一个,所以使用高级插件

>>> request.response.getStatus()
302
>>> request.response.getHeader('location')
'advancedlogin.html'

挑战协议

有时,我们希望多个挑战者一起工作。例如,HTTP规范允许在响应中发出多个挑战。挑战插件可以提供一个challengeProtocol属性,该属性有效地将相关的插件组合在一起进行挑战。如果插件从其挑战返回True并提供非None的挑战协议,则具有相同挑战协议的后续插件也将被用于挑战。

如果没有挑战协议,则只有第一个在挑战中成功的插件将被使用。

让我们看一个例子。我们将定义一个新的插件,该插件指定一个‘X-Challenge’协议

>>> class XChallengeCredentialsPlugin(FormCredentialsPlugin):
...
...     challengeProtocol = 'X-Challenge'
...
...     def __init__(self, challengeValue):
...         self.challengeValue = challengeValue
...
...     def challenge(self, request):
...         value = self.challengeValue
...         existing = request.response.getHeader('X-Challenge', '')
...         if existing:
...             value += ' ' + existing
...         request.response.setHeader('X-Challenge', value)
...         return True

并将几个实例注册为工具

>>> provideUtility(XChallengeCredentialsPlugin('basic'),
...                name='Basic X-Challenge Plugin')

>>> provideUtility(XChallengeCredentialsPlugin('advanced'),
...                name='Advanced X-Challenge Plugin')

当我们将这两个插件与PAU一起使用

>>> pau.credentialsPlugins = (
...     'Basic X-Challenge Plugin',
...     'Advanced X-Challenge Plugin')

并调用“未授权”时

>>> request = TestRequest()
>>> pau.unauthorized(None, request)

我们看到这两个插件都参与了挑战,而不仅仅是第一个插件

>>> request.response.getHeader('X-Challenge')
'advanced basic'

可插入认证前缀

主要ID必须在整个系统中是唯一的。插件通常会提供提供ID前缀的选项,以便不同的插件集在PAU中提供唯一的ID。如果系统中存在多个可插拔认证实用程序,则给每个PAU一个唯一的prefix是一个好主意,这样不同的PAU的主ID就不会冲突。我们可以在创建PAU时提供前缀

>>> pau = authentication.PluggableAuthentication('mypau_')
>>> pau.credentialsPlugins = ('My Credentials Plugin', )
>>> pau.authenticatorPlugins = ('My Authenticator Plugin', )

当我们创建一个请求并尝试认证时

>>> pau.authenticate(TestRequest(credentials='secretcode'))
Principal('mypau_bob')

请注意,现在,我们主要ID有可插拔认证实用程序前缀。

只要我们提供前缀,我们仍然可以查找主要ID

>> pau.getPrincipal('mypas_42')
Principal('mypas_42', "{'domain': 42}")

>> pau.getPrincipal('mypas_41')
OddPrincipal('mypas_41', "{'int': 41}")

搜索

PAU实现了ISourceQueriables

>>> from zope.schema.interfaces import ISourceQueriables
>>> ISourceQueriables.providedBy(pau)
True

这意味着PAU可以在主要来源词汇中(Zope为来源提供复杂的搜索UI)使用。

正如我们所看到的,PAU使用其每个认证器插件来定位具有给定ID的实体。然而,插件也可以提供IQuerySchemaSearch接口,以表明它们可以在PAU的主要搜索方案中使用。

目前,我们的认证器列表

>>> pau.authenticatorPlugins
('My Authenticator Plugin',)

不包括可查询的认证器。因此,PAU不能提供任何可查询项

>>> list(pau.getQueriables())
[]

在我们说明认证器如何由PAU用于搜索实体之前,我们需要设置PAU使用的适配器

>>> import zope.app.authentication.authentication
>>> provideAdapter(
...     authentication.authentication.QuerySchemaSearchAdapter,
...     provides=interfaces.IQueriableAuthenticator)

此适配器将搜索责任委派给认证器,但将PAU前缀添加到搜索中返回的任何主要ID之前。

接下来,我们将创建一个提供搜索接口的插件

>>> @interface.implementer(interfaces.IQuerySchemaSearch)
... class QueriableAuthenticatorPlugin(MyAuthenticatorPlugin):
...
...     schema = None
...
...     def search(self, query, start=None, batch_size=None):
...         yield 'foo'
...

并将它作为插件安装

>>> plugin = QueriableAuthenticatorPlugin()
>>> provideUtility(plugin,
...                provides=interfaces.IAuthenticatorPlugin,
...                name='Queriable')
>>> pau.authenticatorPlugins += ('Queriable',)

现在,PAU提供了一个单独的可查询项

>>> list(pau.getQueriables()) # doctest: +ELLIPSIS
[('Queriable', ...QuerySchemaSearchAdapter object...)]

我们可以使用这个可查询对象来搜索我们的主要对象

>>> queriable = list(pau.getQueriables())[0][1]
>>> list(queriable.search('not-used'))
['mypau_foo']

请注意,结果中的主要对象ID包括PAU前缀。如果我们直接搜索插件

>>> list(plugin.search('not-used'))
['foo']

结果不包含PAU前缀。前缀的添加由PluggableAuthenticationQueriable处理。

可查询插件可以提供ILocation接口。在这种情况下,QuerySchemaSearchAdapter的__parent__与插件的__parent__相同

>>> import zope.location.interfaces
>>> @interface.implementer(zope.location.interfaces.ILocation)
... class LocatedQueriableAuthenticatorPlugin(QueriableAuthenticatorPlugin):
...
...     __parent__ = __name__ = None
...
>>> import zope.component.hooks
>>> site = zope.component.hooks.getSite()
>>> plugin = LocatedQueriableAuthenticatorPlugin()
>>> plugin.__parent__ = site
>>> plugin.__name__ = 'localname'
>>> provideUtility(plugin,
...                provides=interfaces.IAuthenticatorPlugin,
...                name='location-queriable')
>>> pau.authenticatorPlugins = ('location-queriable',)

我们再次有一个可查询对象

>>> queriables = list(pau.getQueriables())
>>> queriables  # doctest: +ELLIPSIS
[('location-queriable', ...QuerySchemaSearchAdapter object...)]

可查询对象的__parent__是上面设置的站点

>>> queriable = queriables[0][1]
>>> queriable.__parent__ is site
True

如果可查询对象提供ILocation但实际上不可定位(即父对象为None),则pau本身成为父对象

>>> plugin = LocatedQueriableAuthenticatorPlugin()
>>> provideUtility(plugin,
...                provides=interfaces.IAuthenticatorPlugin,
...                name='location-queriable-wo-parent')
>>> pau.authenticatorPlugins = ('location-queriable-wo-parent',)

我们再次有一个可查询对象

>>> queriables = list(pau.getQueriables())
>>> queriables  # doctest: +ELLIPSIS
[('location-queriable-wo-parent', ...QuerySchemaSearchAdapter object...)]

并且父对象是pau

>>> queriable = queriables[0][1]
>>> queriable.__parent__  # doctest: +ELLIPSIS
<zope.pluggableauth.authentication.PluggableAuthentication object ...>
>>> queriable.__parent__ is pau
True

主实体文件夹

主要文件夹包含包含主要信息的对象。我们使用InternalPrincipal类创建内部主要对象

>>> from zope.app.authentication.principalfolder import InternalPrincipal
>>> p1 = InternalPrincipal('login1', '123', "Principal 1",
...     passwordManagerName="SHA1")
>>> p2 = InternalPrincipal('login2', '456', "The Other One")

并将它们添加到主要文件夹中

>>> from zope.app.authentication.principalfolder import PrincipalFolder
>>> principals = PrincipalFolder('principal.')
>>> principals['p1'] = p1
>>> principals['p2'] = p2

认证

主要文件夹提供IAuthenticatorPlugin接口。当我们提供合适的凭据时

>>> from pprint import pprint
>>> principals.authenticateCredentials({'login': 'login1', 'password': '123'})
PrincipalInfo(u'principal.p1')

我们得到一个主要对象ID和补充信息,包括主要标题和描述。请注意,主要对象ID是主要文件夹前缀和文件夹内主要信息对象名称的连接

如果凭据无效,则返回None

>>> principals.authenticateCredentials({'login': 'login1',
...                                     'password': '1234'})
>>> principals.authenticateCredentials(42)

更改凭证

可以通过修改主要信息对象来更改凭据

>>> p1.login = 'bob'
>>> p1.password = 'eek'
>>> principals.authenticateCredentials({'login': 'bob', 'password': 'eek'})
PrincipalInfo(u'principal.p1')
>>> principals.authenticateCredentials({'login': 'login1',
...                                     'password': 'eek'})
>>> principals.authenticateCredentials({'login': 'bob',
...                                     'password': '123'})

尝试选择已被占用的登录名是错误的

>>> p1.login = 'login2'
Traceback (most recent call last):
...
ValueError: Principal Login already taken!

如果尝试这样做,数据保持不变

>>> principals.authenticateCredentials({'login': 'bob', 'password': 'eek'})
PrincipalInfo(u'principal.p1')

删除主实体

当然,如果删除主要对象,我们就不能再对其进行身份验证了

>>> del principals['p1']
>>> principals.authenticateCredentials({'login': 'bob',
...                                     'password': 'eek'})

词汇表

词汇模块为身份验证插件和凭据插件提供词汇表。

选项应包括所有为当前上下文提供适当接口(接口.ICredentialsPlugin或接口.IAuthentiatorPlugin)的所有插件的唯一名称——这预计是一个可插拔身份验证工具,以下称为PAU。

这些名称可能是PAU内部的对象(“包含插件”),也可能是注册给指定接口的实用程序(“实用插件”),这些实用程序在PAU的上下文中找到。包含插件掩盖同名的实用插件。它们也可能是不对应于当前此时刻插件的实际选择名称。

以下是词汇如何工作的简短示例。假设我们正在处理身份验证插件。我们将创建一些模拟身份验证插件,并将其中一些注册为实用程序,将其他一些放入模拟PAU中。

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

我们现在准备好创建一个我们可以使用的词汇。上下文是我们的模拟身份验证实用工具,auth

>>> from zope.app.authentication import vocabulary
>>> vocab = vocabulary.authenticatorPlugins(auth)

迭代词汇的结果是所有术语,按其名称的相对任意顺序排列。(这个词汇通常应该使用一个根据术语标题的本地化比较顺序对值进行排序的小部件。)

>>> [term.value for term in vocab] # doctest: +NORMALIZE_WHITESPACE
[u'Plugin 0', u'Plugin 1', u'Plugin 2', u'Plugin 3', u'Plugin 4',
 u'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以及插件是工具还是仅包含在auth工具中。我们将给其中一个插件一个都柏林核心标题,以展示其功能。

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

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

>>> vocab = vocabulary.authenticatorPlugins(auth)

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

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

credentialsPlugins

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

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

遍历词汇表的结果是所有术语,以它们名称的相对任意顺序。(这个词汇表通常应该使用一个小部件,根据术语标题的本地化排序顺序来排序值。)同样,我们可以使用< cite >in来测试词汇表中是否存在值。长度报告预期的值。

>>> [term.value for term in vocab] # doctest: +NORMALIZE_WHITESPACE
[u'Plugin 0', u'Plugin 1', u'Plugin 2', u'Plugin 3', u'Plugin 4',
 u'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以及插件是工具还是仅包含在auth工具中。我们将给其中一个插件一个都柏林核心标题,以展示其功能。由于它一次性计算所有数据,我们需要重新生成词汇表。然后我们将检查标题。我们需要翻译它们,看看我们期望的内容。

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

变更

5.0 (2023-02-09)

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

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

4.0.0 (2017-05-02)

  • 停止对zope.app.zcmlfiles和zope.app.testing的测试依赖。

  • 停止对ZODB3的显式依赖。

  • 添加对Python 3.4、3.5和3.6以及PyPy的支持。

3.9 (2010-10-18)

  • 将具体的IAuthenticatorPlugin实现移动到zope.pluggableauth.plugins。保留向后兼容导入。

  • 在整个zope.formlib中使用,以取消对zope.app.form的依赖。实际上,zope.app.form仍然是一个间接的测试依赖。

3.8.0 (2010-09-25)

  • 使用python的< span class="docutils literal">doctest模块而不是已废弃的< span class="docutils literal">zope.testing.doctest[unit]

  • 将以下视图从< cite >zope.app.securitypolicy移动到这里,以避免这两个包之间的反向依赖,因为< cite >zope.app.securitypolicy在ZTK 1.0中已弃用

    • @@grant.html

    • @@AllRolePermissions.html

    • @@RolePermissions.html

    • @@RolesWithPermission.html

3.7.1 (2010-02-11)

  • 使用来自zope.pluggableauth的新principalfactories.zcml文件,以避免在适配器注册中发生重复错误。

3.7.0 (2010-02-08)

  • 可插拔身份验证实用程序已被分离并作为独立包发布:< cite >zope.pluggableauth。我们现在使用这个新包,提供向后兼容导入以确保平稳过渡。

3.6.2 (2010-01-05)

  • 使用zope.login修复测试,并要求新的zope.publisher 3.12。

3.6.1 (2009-10-07)

  • 由于zope.securitypolicy更新,修复ftesting.zcml。

  • 在测试中不要使用zope.app.testing.ztapi,而要使用zope.component的测试函数。

  • 修复功能测试并停止使用端口8081。不允许在不使用受信任标志的情况下重定向到不同的端口。

3.6.0 (2009-03-14)

  • loginForm.html视图的表示模板和camefrom/重定向逻辑分离。现在逻辑包含在zope.app.authentication.browser.loginform.LoginForm类中。

  • 修复在某些情况下Python 2.6中的登录表单重定向失败。

  • 使用新的zope.authentication包而不是zope.app.security

  • “密码管理器名称”词汇和简单密码管理器注册表已移动到zope.password包。

  • 删除已弃用的代码。

3.5.0 (2009-03-06)

  • 将密码管理器功能分离到新的zope.password包中。保留向后兼容导入。

  • 使用zope.site而不是zope.app.component。(浏览器代码仍然需要zope.app.component,因为它依赖于此包中的视图类。)

3.5.0a2 (2009-02-01)

  • 使旧的编码密码真正有效。

3.5.0a1 (2009-01-31)

  • 使用zope.container而不是zope.app.container。(浏览器代码仍然需要zope.app.container,因为它依赖于此包的视图类。)

  • 编码密码现在以前缀({MD5}、{SHA1}、{SSHA})存储,表示所使用的编码方案。旧的(编码)密码仍然可以使用。

  • 添加一个与标准LDAP密码兼容的SSHA密码管理器。由于这种编码提供了更好的安全性以防止字典攻击,因此鼓励用户切换到这种新的密码方案。

  • InternalPrincipal现在默认使用SSHA密码管理器。

3.4.4 (2008-12-12)

  • 依赖zope.session而不是zope.app.session。目前第一个包含了我们所需的所有功能。

  • 修复Python 2.6上的md5sha弃用警告。

3.4.3 (2008-08-07)

  • 没有更改。重新标记以在PyPI上正确发布。

3.4.2 (2008-07-09)

  • 使其与zope.app.container 3.6.1和3.5.4的更改兼容,在GroupFolder类中将super(BTreeContainer, self).__init__()更改为super(GroupFolder, self).__init__()

3.4.1 (2007-10-24)

  • 避免弃用警告。

3.4.0 (2007-10-11)

  • 更新了包元数据。

3.4.0b1 (2007-09-27)

  • 首次发布独立于Zope。

下载文件

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

源分发

zope.app.authentication-5.0.tar.gz (77.4 kB 查看散列)

上传时间:

构建分发

zope.app.authentication-5.0-py3-none-any.whl (89.4 kB 查看散列)

上传时间: Python 3

支持者:

AWS AWS 云计算和安全赞助商 Datadog Datadog 监控 Fastly Fastly CDN Google Google 下载分析 Microsoft Microsoft PSF 赞助商 Pingdom Pingdom 监控 Sentry Sentry 错误日志 StatusPage StatusPage 状态页面