Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merged revisions 37911, 37979 from the trunk:
Grant views cleaned up
- Loading branch information
Dmitry Vasiliev
committed
Aug 17, 2005
1 parent
171b9e6
commit 723d27f
Showing
4 changed files
with
700 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,176 @@ | ||
############################################################################## | ||
# | ||
# Copyright (c) 2004 Zope Corporation and Contributors. | ||
# All Rights Reserved. | ||
# | ||
# This software is subject to the provisions of the Zope Public License, | ||
# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. | ||
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED | ||
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED | ||
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS | ||
# FOR A PARTICULAR PURPOSE. | ||
# | ||
############################################################################## | ||
"""Pluggable Authentication Utility implementation | ||
$Id$ | ||
""" | ||
import zope.interface | ||
|
||
from zope import component | ||
from zope.schema.interfaces import ISourceQueriables | ||
from zope.app.security.interfaces import IAuthentication, PrincipalLookupError | ||
from zope.app.location.interfaces import ILocation | ||
from zope.app.component import queryNextUtility | ||
from zope.app.component.site import SiteManagementFolder | ||
|
||
from zope.app.authentication import interfaces | ||
|
||
|
||
class PluggableAuthentication(SiteManagementFolder): | ||
|
||
zope.interface.implements( | ||
IAuthentication, | ||
interfaces.IPluggableAuthentication, | ||
ISourceQueriables) | ||
|
||
authenticatorPlugins = () | ||
credentialsPlugins = () | ||
|
||
def __init__(self, prefix=''): | ||
super(PluggableAuthentication, self).__init__() | ||
self.prefix = prefix | ||
|
||
def authenticate(self, request): | ||
authenticatorPlugins = [ | ||
component.queryUtility(interfaces.IAuthenticatorPlugin, | ||
name, context=self) | ||
for name in self.authenticatorPlugins] | ||
for name in self.credentialsPlugins: | ||
credplugin = component.queryUtility( | ||
interfaces.ICredentialsPlugin, name, context=self) | ||
if credplugin is None: | ||
continue | ||
credentials = credplugin.extractCredentials(request) | ||
for authplugin in authenticatorPlugins: | ||
if authplugin is None: | ||
continue | ||
info = authplugin.authenticateCredentials(credentials) | ||
if info is None: | ||
continue | ||
principal = component.getMultiAdapter((info, request), | ||
interfaces.IAuthenticatedPrincipalFactory)(self) | ||
principal.id = self.prefix + info.id | ||
return principal | ||
return None | ||
|
||
def getPrincipal(self, id): | ||
if not id.startswith(self.prefix): | ||
next = queryNextUtility(self, IAuthentication) | ||
if next is None: | ||
raise PrincipalLookupError(id) | ||
return next.getPrincipal(id) | ||
id = id[len(self.prefix):] | ||
for name in self.authenticatorPlugins: | ||
authplugin = component.queryUtility( | ||
interfaces.IAuthenticatorPlugin, name, context=self) | ||
if authplugin is None: | ||
continue | ||
info = authplugin.principalInfo(id) | ||
if info is None: | ||
continue | ||
principal = interfaces.IFoundPrincipalFactory(info)(self) | ||
principal.id = self.prefix + info.id | ||
return principal | ||
next = queryNextUtility(self, IAuthentication) | ||
if next is not None: | ||
return next.getPrincipal(self.prefix + id) | ||
raise PrincipalLookupError(id) | ||
|
||
def getQueriables(self): | ||
for name in self.authenticatorPlugins: | ||
authplugin = component.queryUtility( | ||
interfaces.IAuthenticatorPlugin, name, context=self) | ||
if authplugin is None: | ||
continue | ||
queriable = component.queryMultiAdapter((authplugin, self), | ||
interfaces.IQueriableAuthenticator) | ||
if queriable is not None: | ||
yield name, queriable | ||
|
||
def unauthenticatedPrincipal(self): | ||
return None | ||
|
||
def unauthorized(self, id, request): | ||
challengeProtocol = None | ||
|
||
for name in self.credentialsPlugins: | ||
credplugin = component.queryUtility(interfaces.ICredentialsPlugin, | ||
name) | ||
if credplugin is None: | ||
continue | ||
protocol = getattr(credplugin, 'challengeProtocol', None) | ||
if challengeProtocol is None or protocol == challengeProtocol: | ||
if credplugin.challenge(request): | ||
if protocol is None: | ||
return | ||
elif challengeProtocol is None: | ||
challengeProtocol = protocol | ||
|
||
if challengeProtocol is None: | ||
next = queryNextUtility(self, IAuthentication) | ||
if next is not None: | ||
next.unauthorized(id, request) | ||
|
||
def logout(self, request): | ||
challengeProtocol = None | ||
|
||
for name in self.credentialsPlugins: | ||
credplugin = component.queryUtility(interfaces.ICredentialsPlugin, | ||
name) | ||
if credplugin is None: | ||
continue | ||
protocol = getattr(credplugin, 'challengeProtocol', None) | ||
if challengeProtocol is None or protocol == challengeProtocol: | ||
if credplugin.logout(request): | ||
if protocol is None: | ||
return | ||
elif challengeProtocol is None: | ||
challengeProtocol = protocol | ||
|
||
if challengeProtocol is None: | ||
next = queryNextUtility(self, IAuthentication) | ||
if next is not None: | ||
next.logout(request) | ||
|
||
|
||
class QuerySchemaSearchAdapter(object): | ||
"""Performs schema-based principal searches on behalf of a PAU. | ||
Delegates the search to the adapted authenticator (which also provides | ||
IQuerySchemaSearch) and prepends the PAU prefix to the resulting principal | ||
IDs. | ||
""" | ||
component.adapts( | ||
interfaces.IQuerySchemaSearch, | ||
interfaces.IPluggableAuthentication) | ||
|
||
zope.interface.implements( | ||
interfaces.IQueriableAuthenticator, | ||
interfaces.IQuerySchemaSearch, | ||
ILocation) | ||
|
||
def __init__(self, authplugin, pau): | ||
if ILocation.providedBy(authplugin): | ||
self.__parent__ = authplugin.__parent__ | ||
self.__name__ = authplugin.__name__ | ||
else: | ||
self.__parent__ = pau | ||
self.__name__ = "" | ||
self.authplugin = authplugin | ||
self.pau = pau | ||
self.schema = authplugin.schema | ||
|
||
def search(self, query, start=None, batch_size=None): | ||
for id in self.authplugin.search(query, start, batch_size): | ||
yield self.pau.prefix + id |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,110 @@ | ||
############################################################################## | ||
# | ||
# Copyright (c) 2004 Zope Corporation and Contributors. | ||
# All Rights Reserved. | ||
# | ||
# This software is subject to the provisions of the Zope Public License, | ||
# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. | ||
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED | ||
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED | ||
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS | ||
# FOR A PARTICULAR PURPOSE. | ||
# | ||
############################################################################## | ||
"""Search interface for queriables. | ||
$Id$ | ||
""" | ||
__docformat__ = "reStructuredText" | ||
|
||
from zope.interface import implements | ||
from zope.i18n import translate | ||
from zope.schema import getFieldsInOrder | ||
from zope.app.zapi import getName, getPath | ||
from zope.app.form.utility import setUpWidgets, getWidgetsData | ||
from zope.app.form.interfaces import IInputWidget | ||
from zope.app.form.browser.interfaces import ISourceQueryView | ||
from zope.app.i18n import ZopeMessageIDFactory as _ | ||
|
||
|
||
search_label = _('search-button', 'Search') | ||
source_label = _(u"Source path") | ||
source_title = _(u"Path to the source utility") | ||
|
||
class QuerySchemaSearchView(object): | ||
implements(ISourceQueryView) | ||
|
||
def __init__(self, context, request): | ||
self.context = context | ||
self.request = request | ||
|
||
def render(self, name): | ||
schema = self.context.schema | ||
sourcename = getName(self.context) | ||
sourcepath = getPath(self.context) | ||
setUpWidgets(self, schema, IInputWidget, prefix=name+'.field') | ||
html = [] | ||
|
||
# add sub title for source search field | ||
html.append('<h4>%s</h4>' % sourcename) | ||
|
||
# start row for path display field | ||
html.append('<div class="row">') | ||
|
||
# for each source add path of source | ||
html.append(' <div class="label">') | ||
label = translate(source_label, context=self.request) | ||
title = translate(source_title, context=self.request) | ||
html.append(' <label for="%s" title="%s">' % (sourcename, title)) | ||
html.append(' %s' % label) | ||
html.append(' </label>') | ||
html.append(' </div>') | ||
html.append(' <div class="field">') | ||
html.append(' %s' % sourcepath) | ||
html.append(' </div>') | ||
html.append('</div>') | ||
|
||
# start row for search fields | ||
html.append('<div class="row">') | ||
|
||
for field_name, field in getFieldsInOrder(schema): | ||
widget = getattr(self, field_name+'_widget') | ||
|
||
# for each field add label... | ||
html.append(' <div class="label">') | ||
html.append(' <label for="%s" title="%s">' | ||
% (widget.name, widget.hint)) | ||
html.append(' %s' % widget.label) | ||
html.append(' </label>') | ||
html.append(' </div>') | ||
|
||
# ...and field widget | ||
html.append(' <div class="field">') | ||
html.append(' %s' % widget()) | ||
|
||
if widget.error(): | ||
html.append(' <div class="error">') | ||
html.append(' %s' % widget.error()) | ||
html.append(' </div>') | ||
html.append(' </div>') | ||
# end row | ||
html.append('</div>') | ||
|
||
# add search button for search fields | ||
html.append('<div class="row">') | ||
html.append(' <div class="field">') | ||
html.append(' <input type="submit" name="%s" value="%s" />' | ||
% (name+'.search', | ||
translate(search_label, context=self.request))) | ||
html.append(' </div>') | ||
html.append('</div>') | ||
|
||
return '\n'.join(html) | ||
|
||
def results(self, name): | ||
if not (name+'.search' in self.request): | ||
return None | ||
schema = self.context.schema | ||
setUpWidgets(self, schema, IInputWidget, prefix=name+'.field') | ||
data = getWidgetsData(self, schema) | ||
return self.context.search(data) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,104 @@ | ||
The Query View for Schema Search Plugins | ||
======================================== | ||
|
||
Placefull setup for making the search plugin IPhysicallyLocatable: | ||
|
||
>>> from zope.app.testing import ztapi | ||
>>> from zope.schema.interfaces import ITextLine | ||
>>> from zope.app.form.browser import TextWidget | ||
>>> from zope.app.form.interfaces import IInputWidget | ||
>>> from zope.app.testing.setup import placefulSetUp, placefulTearDown | ||
>>> site = placefulSetUp(True) | ||
>>> ztapi.browserView(ITextLine, '', TextWidget, providing=IInputWidget) | ||
|
||
If a plugin supports `IQuerySchemaSearch`: | ||
|
||
>>> from zope.interface import Interface | ||
>>> import zope.schema | ||
>>> class ISearchCriteria(Interface): | ||
... search = zope.schema.TextLine(title=u"Search String") | ||
|
||
>>> from zope.interface import implements | ||
>>> class MySearchPlugin: | ||
... __name__ = 'searchplugin' | ||
... __parent__ = site | ||
... schema = ISearchCriteria | ||
... data = ['foo', 'bar', 'blah'] | ||
... | ||
... def get(self, id): | ||
... if id in self.data: | ||
... return {} | ||
... | ||
... def search(self, query, start=None, batch_size=None): | ||
... search = query.get('search') | ||
... if search is not None: | ||
... i = 0 | ||
... n = 0 | ||
... for value in self.data: | ||
... if search in value: | ||
... if not ((start is not None and i < start) | ||
... or | ||
... (batch_size is not None and n > batch_size)): | ||
... yield value | ||
|
||
then we can get a view: | ||
|
||
>>> from zope.app.authentication.browser.schemasearch \ | ||
... import QuerySchemaSearchView | ||
>>> from zope.publisher.browser import TestRequest | ||
>>> request = TestRequest() | ||
>>> view = QuerySchemaSearchView(MySearchPlugin(), request) | ||
|
||
This allows us to render a search form. | ||
|
||
>>> print view.render('test') # doctest: +NORMALIZE_WHITESPACE | ||
<h4>searchplugin</h4> | ||
<div class="row"> | ||
<div class="label"> | ||
<label for="searchplugin" title="Path to the source utility"> | ||
Source path | ||
</label> | ||
</div> | ||
<div class="field"> | ||
/searchplugin | ||
</div> | ||
</div> | ||
<div class="row"> | ||
<div class="label"> | ||
<label for="test.field.search" title=""> | ||
Search String | ||
</label> | ||
</div> | ||
<div class="field"> | ||
<input class="textType" id="test.field.search" name="test.field.search" | ||
size="20" type="text" value="" /> | ||
</div> | ||
</div> | ||
<div class="row"> | ||
<div class="field"> | ||
<input type="submit" name="test.search" value="Search" /> | ||
</div> | ||
</div> | ||
|
||
If we ask for results: | ||
|
||
>>> view.results('test') | ||
|
||
We don't get any, since we did not provide any. But if we give input: | ||
|
||
>>> request.form['test.field.search'] = 'a' | ||
|
||
we still don't get any: | ||
|
||
>>> view.results('test') | ||
|
||
because we did not press the button. So let's press the button: | ||
|
||
>>> request.form['test.search'] = 'Search' | ||
|
||
so that we now get results (!): | ||
|
||
>>> list(view.results('test')) | ||
['bar', 'blah'] | ||
|
||
>>> placefulTearDown() |
Oops, something went wrong.