跳转到主要内容

Dolmen CMS认证包

项目描述

dolmen.app.authentication 是负责Dolmen应用程序中用户和组管理的包。它建立在 dolmen.authenticationzope.pluggableauth 之上,提供了一套插件和基类,有助于构建复杂的用户和组系统。

初始Grok导入

>>> import grok
>>> from grokcore.component.testing import grok_component

凭证插件

>>> from zope.pluggableauth.interfaces import ICredentialsPlugin

凭证插件负责提取凭证,以便识别用户。 dolmen.app.authentication 提供了一个插件,即Cookies凭证。

Cookies凭证

Cookie凭证插件从cookie中提取凭证。此插件基于Philipp von Weitershausen的工作(wc.cookiecredentials)。它已被重新实现,以避免使用 zope.app 包,并允许在长期内具有更大的灵活性。

此插件提供以下功能

  • 通过登录表单挑战用户输入用户名和密码;

  • 将这些凭证保存到cookie中,稍后可以从中读取它们。

要检查凭证是否可以正确提取,我们可以在测试请求中伪造cookie

>>> import base64
>>> from zope.publisher.browser import TestRequest

>>> cookie = base64.encodestring('mgr:mgrpw')
>>> request = TestRequest()
>>> request._cookies = {'dolmen.authcookie': cookie}

调用插件凭证提取器将给出我们继续认证所需的确切信息

>>> from zope.interface.verify import verifyObject
>>> from dolmen.app.authentication.plugins import CookiesCredentials

>>> plugin = CookiesCredentials()
>>> verifyObject(ICredentialsPlugin, plugin)
True

>>> print plugin.extractCredentials(request)
{'login': u'mgr', 'password': u'mgrpw'}

认证插件

>>> from zope.pluggableauth.interfaces import IAuthenticatorPlugin

认证插件使用提取的凭据来检索和识别主体。 dolmen.app.authentication 提供了两个插件 - 全局注册表认证 & 主体文件夹认证

全局注册表认证

为了注册主体,zope.principalregistry 包提供了一个全局注册表,该注册表在每次启动时都不是持久的,并重新构建。全局注册表认证器旨在在该全局注册表中查找主体。

我们验证我们的实现是否符合要求

>>> from dolmen.app.authentication import plugins
>>> IAuthenticatorPlugin.implementedBy(plugins.GlobalRegistryAuth)
True

>>> plugin = plugins.GlobalRegistryAuth()
>>> verifyObject(IAuthenticatorPlugin, plugin)
True

为了测试此插件,我们在全局注册表中注册了一个用户,称为“mgr”。我们将使用“mgr”凭据进行查找测试

>>> user = plugin.authenticateCredentials(
...            {'login': u"mgr", "password": u"mgrpw"})
>>> print user
<GlobalRegistryPrincipal "zope.mgr">

错误的凭据将使认证返回 None

>>> user = plugin.authenticateCredentials(
...            {'login': u"mgr", "password": u"wrongpass"})
>>> user is None
True

>>> user = plugin.authenticateCredentials(
...            {'login': u"anon", "password": u"wrongpass"})
>>> user is None
True

无法单独获取主体信息

>>> print plugin.principalInfo('zope.mgr')
None

>>> print plugin.principalInfo('zorglub')
None

主体文件夹认证

dolmen.app.authentication 引入了一个新的认证插件,用于存储和检索持久主体。此插件是一个容器,可以存储 IPrincipal 对象并按照 IAuthenticatorPlugin 的规定检索它们。

我们验证我们的实现是否符合要求

>>> from dolmen.app.authentication import plugins
>>> IAuthenticatorPlugin.implementedBy(plugins.PrincipalFolderPlugin)
True

>>> plugin = plugins.PrincipalFolderPlugin()
>>> verifyObject(IAuthenticatorPlugin, plugin)
True

为了测试此插件,我们必须创建一个 IPrincipal 对象并将其存储在插件中。然后,我们可以进行查找测试。

为了使认证可插件化,主体认证插件依赖于 3 个接口

  • IAccountStatus:如果从 IPrincipal 对象存在到该接口的适配器,则使用它来确定主体是否可以登录。它允许禁用用户帐户并计算该禁用。

  • IPasswordChecker:此适配器用于检查凭据。如果不存在(或如果 IPrincipal 对象不提供此接口),则终止认证并返回 None。

  • IPrincipalInfo:与上一个插件不同,主体文件夹认证器不直接返回 PrincipalInfo 对象,而是使用适配器检索适当的主体信息对象。这是将特定行为插入我们的认证系统所必需的。

让我们首先实现一个基本的 IPrincipalObject。一旦存储,我们就可以开始查找和适配器实现

>>> from dolmen.app.authentication.tests import User

请参阅以下实现

from dolmen.authentication import IPrincipal
from zope.interface import implements

class User(object):
    implements(IPrincipal)

    def __init__(self, id, title=u"", desc=u""):
        self.id = id
        self.title = title or id
        self.description = desc
        self.groups = []

我们可以验证我们的实现是否符合接口

>>> from dolmen.authentication import IPrincipal
>>> stilgar = User(u"stilgar")
>>> verifyObject(IPrincipal, stilgar)
True

我们可以为我们的 User 类设置一个简单的视图

>>> from grokcore.layout import Page

>>> class UserView(Page):
...     grok.name('index')
...     grok.context(User)
...
...     def render(self):
...         return "<User %s>" % self.context.id

>>> grok_component('index', UserView)
True

实现是一致的。我们现在可以在插件容器中持久化主体

>>> plugin['stilgar'] = stilgar
>>> print [user for user in plugin.keys()]
[u'stilgar']

我们现在可以使用认证 API 尝试查找主体

>>> found = plugin.authenticateCredentials(
...            {'login': 'stilgar', 'password': 'boo'})
>>> found is None
True

主体未找到:我们没有可用的 IPasswordChecker 到 IPasswordChecker 的适配器,因此已终止认证过程。

提供适配器将使我们能够成功检索主体

>>> from dolmen.authentication import IPasswordChecker

>>> class GrantingAccessOnBoo(grok.Adapter):
...     grok.context(IPrincipal)
...     grok.provides(IPasswordChecker)
...
...     def checkPassword(self, pwd):
...         if pwd == 'boo':
...             return True

>>> grok_component('booing', GrantingAccessOnBoo)
True

>>> found = plugin.authenticateCredentials(
...            {'login': 'stilgar', 'password': 'boo'})
>>> found is not None
True

当然,提供错误的密码将返回 None

>>> found = plugin.authenticateCredentials(
...            {'login': 'stilgar', 'password': 'not boo'})
>>> found is None
True

我们还可以通过其 ID 查找主体

>>> print plugin.principalInfo('stilgar')
<dolmen.authentication.principal.LocatablePrincipalInfo ...>

>>> print plugin.principalInfo("unknown")
None

如前所述,可以通过 IAccountStatus 接口打开和关闭特定用户的登录能力

>>> from dolmen.authentication import IAccountStatus

>>> class AllowLogin(grok.Adapter):
...     grok.context(IPrincipal)
...     grok.provides(IAccountStatus)
...
...     @property
...     def status(self):
...         return "No status information available"
...
...     def check(self):
...         if self.context.id != "stilgar":
...             return True
...         return False

>>> grok_component('allow', AllowLogin)
True

在此示例中,我们明确禁止使用标识符“stilgar”的用户通过登录检索

>>> found = plugin.authenticateCredentials(
...            {'login': 'stilgar', 'password': 'boo'})
>>> found is None
True

设置站点

为了测试包的高级功能,我们将设置一个熟悉的环境。这样做,我们可以在真实 Dolmen 站点上下文中测试我们的包的行为

>>> from dolmen.app.site import Dolmen
>>> root = getRootFolder()

>>> site = Dolmen()
>>> grok.notify(grok.ObjectCreatedEvent(site))
>>> root['site'] =  site

>>> from zope.authentication.interfaces import IAuthentication
>>> from zope.pluggableauth import PluggableAuthentication
>>> from dolmen.app.authentication import initialize_pau

>>> PAU = PluggableAuthentication()
>>> len(PAU.authenticatorPlugins)
0
>>> len(PAU.credentialsPlugins)
0

>>> initialize_pau(PAU)
>>> print PAU.authenticatorPlugins
('globalregistry',)
>>> print PAU.credentialsPlugins
('cookies', 'No Challenge if Authenticated')

>>> site['auth'] = PAU
>>> lsm = site.getSiteManager()
>>> lsm.registerUtility(PAU, IAuthentication)

>>> from grokcore.layout import Layout
>>> from zope.interface import Interface

>>> class Layout(Layout):
...     grok.name('')
...     grok.context(Interface)
...
...     def render(self):
...         return u''
...
...     def __call__(self, view):
...         return u"<!DOCTYPE html>\n" + unicode(view.render())

>>> grok_component('layout', Layout)
True

>>> from dolmen.forms import crud
>>> class Edit(crud.Edit):
...     grok.context(Dolmen)
...     grok.require('test.Edit')

>>> grok_component('editsite', Edit)
True

>>> class DolmenIndex(Page):
...     grok.name('index')
...     grok.context(Dolmen)
...
...     def render(self):
...         return "Homepage"

>>> grok_component('indexsite', DolmenIndex)
True

登录

未授权

想象一下,你访问了一个匿名用户无法访问的页面

>>> from zope.app.wsgi.testlayer import Browser
>>> browser = Browser()
>>> browser.handleErrors = False

>>> browser.open("http://localhost/site/@@edit")
Traceback (most recent call last):
...
Unauthorized: ...

登录页面

为了获取正确的凭据,我们可以简单地使用 dolmen.app.authentication 提供的登录表单登录

>>> browser.open('http://localhost/site/@@login')
>>> loginpage = browser.contents
>>> 'id="login"' in loginpage
True
>>> 'id="password"' in loginpage
True

>>> browser.getControl('Username').value = 'mgr'
>>> browser.getControl('Password').value = 'mgrpw'
>>> browser.getControl('Log in').click()

>>> browser.url
'http://localhost/site'

管理认证插件

dolmen.app.authentication 提供了一种启用和禁用不同认证插件的方法。

为了保持网站中的元素整洁,dolmen.app.authentication 假设认证插件持久保存在 PAU 容器中。

让我们用一个 PrincipalFolder 插件来举一个具体的例子

>>> members = plugins.PrincipalFolderPlugin()
>>> grok.notify(grok.ObjectCreatedEvent(members))
>>> PAU['members'] = members

此时,主文件夹已创建并持久化。我们注意到,文件夹是在PAU实用容器内部创建的。

此时,我们可以访问管理视图

>>> browser.open("http://localhost/site/auth/@@authenticators")
>>> print browser.contents
<!DOCTYPE html>
<form action="http://localhost/site/auth/@@authenticators" method="post"
      enctype="multipart/form-data">
  <h1>Edit the authentication sources</h1>
  ...

“成员”主文件夹尚未激活。

让我们在其中创建一个用户对象

>>> chani = User(u"chani", title=u"Sihaya")
>>> PAU['members']['chani'] = chani

现在,让我们用一个新的浏览器尝试登录

>>> new_browser = Browser()
>>> new_browser.open('http://localhost/site/@@login')
>>> new_browser.getControl('Username').value = 'chani'
>>> new_browser.getControl('Password').value = 'boo'
>>> new_browser.getControl('Log in').click()

>>> "Login failed" in new_browser.contents
True

使用管理视图机制,我们可以激活我们的主文件夹

>>> from dolmen.app.authentication.browser import management
>>> adapter = management.IActiveFolders(PAU)
>>> adapter.activeFolders
()
>>> adapter.activeFolders = (u'members',)

主文件夹现在已激活。让我们重试登录

>>> new_browser = Browser()
>>> new_browser.handleErrors = False

>>> new_browser.open('http://localhost/site/@@login')
>>> new_browser.getControl('Username').value = 'chani'
>>> new_browser.getControl('Password').value = 'boo'
>>> new_browser.getControl('Log in').click()

>>> "Login failed" in new_browser.contents
False

>>> print new_browser.url
http://localhost/site

创建角色

>>> class Editor(grok.Role):
...     grok.name('testEditor')
...     grok.title(u"Editor")
...     grok.description(u"A basic editor.")
...     grok.permissions('test.Edit')
>>> grok_component('editor', Editor)
True

管理用户

用户可以被赋予角色。这可以通过一个视图实现,用户作为上下文

>>> browser.handleErrors = False
>>> browser.open("http://localhost/site/auth/members/chani/@@grant_roles")
>>> browser.getControl(name='form.field.roles').value = ['testEditor']

>>> browser.handleErrors = False
>>> browser.getControl('Update').click()

这个视图是通过适配器实现的,可以在任何IPrincipals上使用。让我们检查我们之前的行为是否已经完成

>>> from zope.component.hooks import setSite
>>> setSite(site) # we got to the context of our site

>>> from dolmen.app.authentication.browser import roles
>>> role_controller = roles.IPrincipalRoles(chani)
>>> role_controller.roles
set([u'testEditor'])

选定的角色已经存在。我们可以非常容易地修改它们

>>> role_controller.roles = [u'testEditor']
>>> role_controller.roles
set([u'testEditor'])

角色管理在站点对象本身上应用更改。让我们验证角色是否已正确应用

>>> from zope.securitypolicy.interfaces import IPrincipalRoleManager
>>> prm = IPrincipalRoleManager(site)
>>> print prm.getRolesForPrincipal(chani.id)
[(u'testEditor', PermissionSetting: Allow)]

注销

我们还可以通过调用注销页面手动销毁cookie

>>> 'dolmen.authcookie' in browser.cookies.keys()
True

>>> browser.open("http://localhost/site/@@logoutaction")
>>> 'dolmen.authcookie' in browser.cookies.keys()
False

>>> browser.url
'http://localhost/site/logout.html'

更改

1.1.1 (2013-11-11)

  • 修复了cookie编码错误。

1.1 (2013-11-06)

  • 移除了所有dolmen.app依赖项,以便从无用的绑定中解放包。这是通过删除菜单条目和基本模式的集成来实现的。

1.0b4(2013-10-31)

  • PrincipalFolderPlugin需要标题。

  • 修正了cookie值的解码。

1.0b3(2010-11-18)

  • 在管理视图中修正了词汇表的声明方式(角色授权和用户文件夹选择。存在可插拔性,但如果找不到组件,则不会返回基本词汇表。

1.0b2(2010-11-16)

  • IChangePassword的变化。密码现在可以是普通字符串或unicode值(通过IProtected)。IChangePassword随后为表单提供模式。请参阅dolmen.authentication中的相应更改。

  • GlobalRegistry插件已被修复。它将不再触发AuthenticatedPrincipalCreated事件。这解决了重要错误:事件的处理器曾经认为GlobalRegistry中的所有主体都是认证的,即使是未认证的。

1.0b1(2010-07-28)

  • 国际化代码并添加了法语翻译。

  • 修正了角色授权UI并通过上下文标签公开。

  • 改进了测试覆盖率。

1.0a3(2010-06-25)

  • GlobalRegistry插件在principalInfo未找到时不再引发错误。相反,它返回None,如IAuthentication所指定。已添加测试以修复该行为和PrincipalFolder的行为。

1.0a2(2010-06-25)

  • 当principalInfo未找到时,PrincipalFolder不再引发错误。相反,它返回None,如IAuthentication所指定。

1.0a1(2010-06-04)

  • 删除了未使用和不合理的权限:“CanLogin”和“CanLogout”。

  • dolmen.app.authentication现在使用dolmen.menu创建菜单和注册条目。

  • 我们现在使用zeam.form表单库而不是z3c.form。

0.1.1 (2010-05-31)

  • 更新了翻译。

0.1 (2010-05-30)

  • 初始发布。

项目详情


下载文件

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

源分发

支持者