[Checkins]
SVN: zope.app.authentication/trunk/src/zope/app/authentication/
Fixed a bug with authenticator plugins providing ILocation
without being actually located anywhere.
Christian Zagrodnick
cz at gocept.com
Mon Jun 25 10:32:13 EDT 2007
Log message for revision 77044:
Fixed a bug with authenticator plugins providing ILocation without being actually located anywhere.
Changed:
U zope.app.authentication/trunk/src/zope/app/authentication/README.txt
U zope.app.authentication/trunk/src/zope/app/authentication/authentication.py
-=-
Modified: zope.app.authentication/trunk/src/zope/app/authentication/README.txt
===================================================================
--- zope.app.authentication/trunk/src/zope/app/authentication/README.txt 2007-06-25 14:22:46 UTC (rev 77043)
+++ zope.app.authentication/trunk/src/zope/app/authentication/README.txt 2007-06-25 14:32:12 UTC (rev 77044)
@@ -45,7 +45,7 @@
Simple Credentials Plugin
-------------------------
-To illustrate, we'll create a simple credentials plugin:
+To illustrate, we'll create a simple credentials plugin::
>>> from zope import interface
>>> from zope.app.authentication import interfaces
@@ -63,7 +63,7 @@
... def logout(self, request):
... pass # logout is a no-op for this plugin
-As a plugin, MyCredentialsPlugin needs to be registered as a named utility:
+As a plugin, MyCredentialsPlugin needs to be registered as a named utility::
>>> myCredentialsPlugin = MyCredentialsPlugin()
>>> provideUtility(myCredentialsPlugin, name='My Credentials Plugin')
@@ -72,7 +72,7 @@
---------------------------
Next we'll create a simple authenticator plugin. For our plugin, we'll need
-an implementation of IPrincipalInfo:
+an implementation of IPrincipalInfo::
>>> class PrincipalInfo(object):
...
@@ -86,7 +86,7 @@
... def __repr__(self):
... return 'PrincipalInfo(%r)' % self.id
-Our authenticator uses this type when it creates a principal info:
+Our authenticator uses this type when it creates a principal info::
>>> class MyAuthenticatorPlugin(object):
...
@@ -100,7 +100,7 @@
... pass # plugin not currently supporting search
As with the credentials plugin, the authenticator plugin must be registered
-as a named utility:
+as a named utility::
>>> myAuthenticatorPlugin = MyAuthenticatorPlugin()
>>> provideUtility(myAuthenticatorPlugin, name='My Authenticator Plugin')
@@ -109,7 +109,7 @@
-------------------
While authenticator plugins provide principal info, they are not responsible
for creating principals. This function is performed by factory adapters. For
-these tests we'll borrow some factories from the principal folder:
+these tests we'll borrow some factories from the principal folder::
>>> from zope.app.authentication import principalfolder
>>> provideAdapter(principalfolder.AuthenticatedPrincipalFactory)
@@ -120,12 +120,12 @@
Configuring a PAU
-----------------
-Finally, we'll create the PAU itself:
+Finally, we'll create the PAU itself::
>>> from zope.app import authentication
>>> pau = authentication.PluggableAuthentication('xyz_')
-and configure it with the two plugins:
+and configure it with the two plugins::
>>> pau.credentialsPlugins = ('My Credentials Plugin', )
>>> pau.authenticatorPlugins = ('My Authenticator Plugin', )
@@ -133,19 +133,19 @@
Using the PAU to Authenticate
-----------------------------
-We can now use the PAU to authenticate a sample request:
+We can now use the PAU to authenticate a sample request::
>>> from zope.publisher.browser import TestRequest
>>> print pau.authenticate(TestRequest())
None
In this case, we cannot authenticate an empty request. In the same way, we
-will not be able to authenticate a request with the wrong credentials:
+will not be able to authenticate a request with the wrong credentials::
>>> print pau.authenticate(TestRequest(credentials='let me in!'))
None
-However, if we provide the proper credentials:
+However, if we provide the proper credentials::
>>> request = TestRequest(credentials='secretcode')
>>> principal = pau.authenticate(request)
@@ -157,7 +157,7 @@
Authenticated Principal Creates Events
--------------------------------------
-We can verify that the appropriate event was published:
+We can verify that the appropriate event was published::
>>> [event] = getEvents(interfaces.IAuthenticatedPrincipalCreated)
>>> event.principal is principal
@@ -169,7 +169,7 @@
The info object has the id, title, and description of the principal. The info
object is also generated by the authenticator plugin, so the plugin may
-itself have provided additional information on the info object.
+itself have provided additional information on the info object::
>>> event.info.title
'Bob'
@@ -183,7 +183,7 @@
authenticate this principal. These attributes can be useful for subscribers
that want to react to the plugins used. For instance, subscribers can
determine that a given credential plugin does or does not support logout, and
-provide information usable to show or hide logout user interface.
+provide information usable to show or hide logout user interface::
>>> event.info.credentialsPlugin is myCredentialsPlugin
True
@@ -192,13 +192,13 @@
Normally, we provide subscribers to these events that add additional
information to the principal. For example, we'll add one that sets
-the title:
+the title::
>>> def add_info(event):
... event.principal.title = event.info.title
>>> subscribe([interfaces.IAuthenticatedPrincipalCreated], None, add_info)
-Now, if we authenticate a principal, its title is set:
+Now, if we authenticate a principal, its title is set::
>>> principal = pau.authenticate(request)
>>> principal.title
@@ -211,7 +211,7 @@
order specified in the PAU's authenticatorPlugins attribute, to authenticate
a set of credentials.
-To illustrate, we'll create another authenticator:
+To illustrate, we'll create another authenticator::
>>> class MyAuthenticatorPlugin2(MyAuthenticatorPlugin):
...
@@ -223,34 +223,34 @@
>>> provideUtility(MyAuthenticatorPlugin2(), name='My Authenticator Plugin 2')
-If we put it before the original authenticator:
+If we put it before the original authenticator::
>>> pau.authenticatorPlugins = (
... 'My Authenticator Plugin 2',
... 'My Authenticator Plugin')
-Then it will be given the first opportunity to authenticate a request:
+Then it will be given the first opportunity to authenticate a request::
>>> pau.authenticate(TestRequest(credentials='secretcode'))
Principal('xyz_black')
-If neither plugins can authenticate, pau returns None:
+If neither plugins can authenticate, pau returns None::
>>> print pau.authenticate(TestRequest(credentials='let me in!!'))
None
-When we change the order of the authenticator plugins:
+When we change the order of the authenticator plugins::
>>> pau.authenticatorPlugins = (
... 'My Authenticator Plugin',
... 'My Authenticator Plugin 2')
-we see that our original plugin is now acting first:
+we see that our original plugin is now acting first::
>>> pau.authenticate(TestRequest(credentials='secretcode'))
Principal('xyz_bob')
-The second plugin, however, gets a chance to authenticate if first does not:
+The second plugin, however, gets a chance to authenticate if first does not::
>>> pau.authenticate(TestRequest(credentials='hiddenkey'))
Principal('xyz_white')
@@ -260,7 +260,7 @@
As with with authenticators, we can specify multiple credentials plugins. To
illustrate, we'll create a credentials plugin that extracts credentials from
-a request form:
+a request form::
>>> class FormCredentialsPlugin:
...
@@ -278,14 +278,14 @@
>>> provideUtility(FormCredentialsPlugin(),
... name='Form Credentials Plugin')
-and insert the new credentials plugin before the existing plugin:
+and insert the new credentials plugin before the existing plugin::
>>> pau.credentialsPlugins = (
... 'Form Credentials Plugin',
... 'My Credentials Plugin')
The PAU will use each plugin in order to try and obtain credentials from a
-request.
+request::
>>> pau.authenticate(TestRequest(credentials='secretcode',
... form={'my_credentials': 'hiddenkey'}))
@@ -305,12 +305,12 @@
- Succeeded in authenticating with 'My Authenticator Plugin 2'
-Let's try a different scenario:
+Let's try a different scenario::
>>> pau.authenticate(TestRequest(credentials='secretcode'))
Principal('xyz_bob')
-In this case, the PAU went through these steps:
+In this case, the PAU went through these steps::
- Get credentials using 'Form Credentials Plugin'
@@ -322,7 +322,7 @@
- Succeeded in authenticating with 'My Authenticator Plugin'
-Let's try a slightly more complex scenario:
+Let's try a slightly more complex scenario::
>>> pau.authenticate(TestRequest(credentials='hiddenkey',
... form={'my_credentials': 'bogusvalue'}))
@@ -360,7 +360,7 @@
As a component that provides IAuthentication, a PAU lets you lookup a
principal with a principal ID. The PAU looks up a principal by delegating to
its authenticators. In our example, none of the authenticators implement this
-search capability, so when we look for a principal:
+search capability, so when we look for a principal::
>>> print pau.getPrincipal('xyz_bob')
Traceback (most recent call last):
@@ -376,7 +376,7 @@
For a PAU to support search, it needs to be configured with one or more
authenticator plugins that support search. To illustrate, we'll create a new
-authenticator:
+authenticator::
>>> class SearchableAuthenticatorPlugin:
...
@@ -403,26 +403,26 @@
where an authenticator may opt to not perform one of these two functions, they
are less typical.
-As with any plugin, we need to register it as a utility:
+As with any plugin, we need to register it as a utility::
>>> searchable = SearchableAuthenticatorPlugin()
>>> provideUtility(searchable, name='Searchable Authentication Plugin')
-We'll now configure the PAU to use only the searchable authenticator:
+We'll now configure the PAU to use only the searchable authenticator::
>>> pau.authenticatorPlugins = ('Searchable Authentication Plugin',)
-and add some principals to the authenticator:
+and add some principals to the authenticator::
>>> searchable.add('bob', 'Bob', 'A nice guy', 'b0b')
>>> searchable.add('white', 'White Spy', 'Sneaky', 'deathtoblack')
-Now when we ask the PAU to find a principal:
+Now when we ask the PAU to find a principal::
>>> pau.getPrincipal('xyz_bob')
Principal('xyz_bob')
-but only those it knows about:
+but only those it knows about::
>>> print pau.getPrincipal('black')
Traceback (most recent call last):
@@ -433,7 +433,7 @@
As evident in the authenticator's 'createFoundPrincipal' method (see above),
a FoundPrincipalCreatedEvent is published when the authenticator finds a
-principal on behalf of PAU's 'getPrincipal':
+principal on behalf of PAU's 'getPrincipal'::
>>> clearEvents()
>>> principal = pau.getPrincipal('xyz_white')
@@ -447,7 +447,7 @@
PrincipalInfo('white')
The info has an authenticatorPlugin, but no credentialsPlugin, since none was
-used.
+used::
>>> event.info.credentialsPlugin is None
True
@@ -456,7 +456,7 @@
As we have seen with authenticated principals, it is common to subscribe to
principal created events to add information to the newly created principal.
-In this case, we need to subscribe to IFoundPrincipalCreated events:
+In this case, we need to subscribe to IFoundPrincipalCreated events::
>>> subscribe([interfaces.IFoundPrincipalCreated], None, add_info)
@@ -470,23 +470,23 @@
find a principal. If the first authenticator plugin can't find the requested
principal, the next plugin is used, and so on.
-To illustrate, we'll create and register a second searchable authenticator:
+To illustrate, we'll create and register a second searchable authenticator::
>>> searchable2 = SearchableAuthenticatorPlugin()
>>> provideUtility(searchable2, name='Searchable Authentication Plugin 2')
-and add a principal to it:
+and add a principal to it::
>>> searchable.add('black', 'Black Spy', 'Also sneaky', 'deathtowhite')
When we configure the PAU to use both searchable authenticators (note the
-order):
+order)::
>>> pau.authenticatorPlugins = (
... 'Searchable Authentication Plugin 2',
... 'Searchable Authentication Plugin')
-we see how the PAU uses both plugins:
+we see how the PAU uses both plugins::
>>> pau.getPrincipal('xyz_white')
Principal('xyz_white')
@@ -496,19 +496,19 @@
If more than one plugin know about the same principal ID, the first plugin is
used and the remaining are not delegated to. To illustrate, we'll add
-another principal with the same ID as an existing principal:
+another principal with the same ID as an existing principal::
>>> searchable2.add('white', 'White Rider', '', 'r1der')
>>> pau.getPrincipal('xyz_white').title
'White Rider'
-If we change the order of the plugins:
+If we change the order of the plugins::
>>> pau.authenticatorPlugins = (
... 'Searchable Authentication Plugin',
... 'Searchable Authentication Plugin 2')
-we get a different principal for ID 'white':
+we get a different principal for ID 'white'::
>>> pau.getPrincipal('xyz_white').title
'White Spy'
@@ -537,7 +537,7 @@
perform any action when asked to challenge (see above the 'challenge' methods).
To illustrate challenges, we'll subclass an existing credentials plugin and
-do something in its 'challenge':
+do something in its 'challenge'::
>>> class LoginFormCredentialsPlugin(FormCredentialsPlugin):
...
@@ -551,7 +551,7 @@
This plugin handles a challenge by redirecting the response to a login form.
It returns True to signal to the PAU that it handled the challenge.
-We will now create and register a couple of these plugins:
+We will now create and register a couple of these plugins::
>>> provideUtility(LoginFormCredentialsPlugin('simplelogin.html'),
... name='Simple Login Form Plugin')
@@ -559,36 +559,36 @@
>>> provideUtility(LoginFormCredentialsPlugin('advancedlogin.html'),
... name='Advanced Login Form Plugin')
-and configure the PAU to use them:
+and configure the PAU to use them::
>>> pau.credentialsPlugins = (
... 'Simple Login Form Plugin',
... 'Advanced Login Form Plugin')
-Now when we call 'unauthorized' on the PAU:
+Now when we call 'unauthorized' on the PAU::
>>> request = TestRequest()
>>> pau.unauthorized(id=None, request=request)
-we see that the user is redirected to the simple login form:
+we see that the user is redirected to the simple login form::
>>> request.response.getStatus()
302
>>> request.response.getHeader('location')
'simplelogin.html'
-We can change the challenge policy by reordering the plugins:
+We can change the challenge policy by reordering the plugins::
>>> pau.credentialsPlugins = (
... 'Advanced Login Form Plugin',
... 'Simple Login Form Plugin')
-Now when we call 'unauthorized':
+Now when we call 'unauthorized'::
>>> request = TestRequest()
>>> pau.unauthorized(id=None, request=request)
-the advanced plugin is used because it's first:
+the advanced plugin is used because it's first::
>>> request.response.getStatus()
302
@@ -747,7 +747,7 @@
>>> list(pau.getQueriables()) # doctest: +ELLIPSIS
[('Queriable', ...QuerySchemaSearchAdapter object...)]
-We can use this queriable to search for our principal:
+We can use this queriable to search for our principal::
>>> queriable = list(pau.getQueriables())[0][1]
>>> list(queriable.search('not-used'))
@@ -761,3 +761,61 @@
The result does not include the PAU prefix. The prepending of the prefix is
handled by the PluggableAuthenticationQueriable.
+
+
+Queryiable plugins can provide the ILocation interface. In this case the
+QuerySchemaSearchAdapter's __parent__ is the same as the __parent__ of the
+plugin::
+
+ >>> import zope.location.interfaces
+ >>> class LocatedQueriableAuthenticatorPlugin(QueriableAuthenticatorPlugin):
+ ...
+ ... interface.implements(zope.location.interfaces.ILocation)
+ ...
+ ... __parent__ = __name__ = None
+ ...
+ >>> import zope.app.component.hooks
+ >>> site = zope.app.component.hooks.getSite()
+ >>> plugin = LocatedQueriableAuthenticatorPlugin()
+ >>> plugin.__parent__ = site
+ >>> plugin.__name__ = 'localname'
+ >>> provideUtility(plugin,
+ ... provides=interfaces.IAuthenticatorPlugin,
+ ... name='location-queriable')
+ >>> pau.authenticatorPlugins = ('location-queriable',)
+
+We have one queriable again::
+
+ >>> queriables = list(pau.getQueriables())
+ >>> queriables # doctest: +ELLIPSIS
+ [('location-queriable', ...QuerySchemaSearchAdapter object...)]
+
+The queriable's __parent__ is the site as set above::
+
+ >>> queriable = queriables[0][1]
+ >>> queriable.__parent__ is site
+ True
+
+If the queriable provides ILocation but is not actually locatable (i.e. the
+parent is None) the pau itself becomes the parent::
+
+
+ >>> plugin = LocatedQueriableAuthenticatorPlugin()
+ >>> provideUtility(plugin,
+ ... provides=interfaces.IAuthenticatorPlugin,
+ ... name='location-queriable-wo-parent')
+ >>> pau.authenticatorPlugins = ('location-queriable-wo-parent',)
+
+We have one queriable again::
+
+ >>> queriables = list(pau.getQueriables())
+ >>> queriables # doctest: +ELLIPSIS
+ [('location-queriable-wo-parent', ...QuerySchemaSearchAdapter object...)]
+
+And the parent is the pau::
+
+ >>> queriable = queriables[0][1]
+ >>> queriable.__parent__ # doctest: +ELLIPSIS
+ <zope.app.authentication.authentication.PluggableAuthentication object at 0x...>
+ >>> queriable.__parent__ is pau
+ True
Modified: zope.app.authentication/trunk/src/zope/app/authentication/authentication.py
===================================================================
--- zope.app.authentication/trunk/src/zope/app/authentication/authentication.py 2007-06-25 14:22:46 UTC (rev 77043)
+++ zope.app.authentication/trunk/src/zope/app/authentication/authentication.py 2007-06-25 14:32:12 UTC (rev 77044)
@@ -167,7 +167,11 @@
ILocation)
def __init__(self, authplugin, pau):
- if ILocation.providedBy(authplugin):
+ if (ILocation.providedBy(authplugin) and
+ authplugin.__parent__ is not None):
+ # Checking explicitly for the parent, because providing ILocation
+ # basically means that the object *could* be located. It doesn't
+ # say the object must be located.
self.__parent__ = authplugin.__parent__
self.__name__ = authplugin.__name__
else:
More information about the Checkins
mailing list