diff -Nru zope.app.authentication-3.8.0/CHANGES.txt zope.app.authentication-3.9/CHANGES.txt --- zope.app.authentication-3.8.0/CHANGES.txt 2010-09-25 09:07:17.000000000 +0000 +++ zope.app.authentication-3.9/CHANGES.txt 2010-10-18 09:51:14.000000000 +0000 @@ -2,6 +2,15 @@ Changes ======= +3.9 (2010-10-18) +---------------- + +* Move concrete IAuthenticatorPlugin implementations to + zope.pluggableauth.plugins. Leave backwards compatibility imports. + +* Use zope.formlib throughout to lift the dependency on zope.app.form. As it + turns out, zope.app.form is still a indirect test dependency though. + 3.8.0 (2010-09-25) ------------------ @@ -17,14 +26,12 @@ - ``@@RolePermissions.html`` - ``@@RolesWithPermission.html`` - 3.7.1 (2010-02-11) ------------------ * Using the new `principalfactories.zcml` file, from ``zope.pluggableauth``, to avoid duplication errors, in the adapters registration. - 3.7.0 (2010-02-08) ------------------ @@ -33,13 +40,11 @@ new package, providing backward compatibility imports to assure a smooth transition. - 3.6.2 (2010-01-05) ------------------ * Fix tests by using zope.login, and require new zope.publisher 3.12. - 3.6.1 (2009-10-07) ------------------ @@ -67,7 +72,6 @@ * Remove deprecated code. - 3.5.0 (2009-03-06) ------------------ diff -Nru zope.app.authentication-3.8.0/PKG-INFO zope.app.authentication-3.9/PKG-INFO --- zope.app.authentication-3.8.0/PKG-INFO 2010-09-25 09:07:24.000000000 +0000 +++ zope.app.authentication-3.9/PKG-INFO 2010-10-18 09:51:19.000000000 +0000 @@ -1,6 +1,6 @@ Metadata-Version: 1.0 Name: zope.app.authentication -Version: 3.8.0 +Version: 3.9 Summary: Principals and groups management for the pluggable authentication utility Home-page: http://pypi.python.org/pypi/zope.app.authentication Author: Zope Corporation and Contributors @@ -30,9 +30,9 @@ The primary job of PAU is to authenticate principals. It uses two types of plug-ins in its work: - - Credentials Plugins + - Credentials Plugins - - Authenticator Plugins + - Authenticator Plugins Credentials plugins are responsible for extracting user credentials from a request. A credentials plugin may in some cases issue a 'challenge' to obtain @@ -61,26 +61,26 @@ To illustrate, we'll create a simple credentials plugin:: - >>> from zope import interface - >>> from zope.app.authentication import interfaces + >>> from zope import interface + >>> from zope.app.authentication import interfaces - >>> class MyCredentialsPlugin(object): - ... - ... interface.implements(interfaces.ICredentialsPlugin) - ... - ... 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 + >>> class MyCredentialsPlugin(object): + ... + ... interface.implements(interfaces.ICredentialsPlugin) + ... + ... 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 As a plugin, MyCredentialsPlugin needs to be registered as a named utility:: - >>> myCredentialsPlugin = MyCredentialsPlugin() - >>> provideUtility(myCredentialsPlugin, name='My Credentials Plugin') + >>> myCredentialsPlugin = MyCredentialsPlugin() + >>> provideUtility(myCredentialsPlugin, name='My Credentials Plugin') Simple Authenticator Plugin ~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -88,36 +88,36 @@ Next we'll create a simple authenticator plugin. For our plugin, we'll need an implementation of IPrincipalInfo:: - >>> class PrincipalInfo(object): - ... - ... interface.implements(interfaces.IPrincipalInfo) - ... - ... def __init__(self, id, title, description): - ... self.id = id - ... self.title = title - ... self.description = description - ... - ... def __repr__(self): - ... return 'PrincipalInfo(%r)' % self.id + >>> class PrincipalInfo(object): + ... + ... interface.implements(interfaces.IPrincipalInfo) + ... + ... def __init__(self, id, title, description): + ... self.id = id + ... self.title = title + ... self.description = description + ... + ... def __repr__(self): + ... return 'PrincipalInfo(%r)' % self.id Our authenticator uses this type when it creates a principal info:: - >>> class MyAuthenticatorPlugin(object): - ... - ... interface.implements(interfaces.IAuthenticatorPlugin) - ... - ... def authenticateCredentials(self, credentials): - ... if credentials == 'secretcode': - ... return PrincipalInfo('bob', 'Bob', '') - ... - ... def principalInfo(self, id): - ... pass # plugin not currently supporting search + >>> class MyAuthenticatorPlugin(object): + ... + ... interface.implements(interfaces.IAuthenticatorPlugin) + ... + ... def authenticateCredentials(self, credentials): + ... if credentials == 'secretcode': + ... return PrincipalInfo('bob', 'Bob', '') + ... + ... def principalInfo(self, id): + ... pass # plugin not currently supporting search As with the credentials plugin, the authenticator plugin must be registered as a named utility:: - >>> myAuthenticatorPlugin = MyAuthenticatorPlugin() - >>> provideUtility(myAuthenticatorPlugin, name='My Authenticator Plugin') + >>> myAuthenticatorPlugin = MyAuthenticatorPlugin() + >>> provideUtility(myAuthenticatorPlugin, name='My Authenticator Plugin') Principal Factories ~~~~~~~~~~~~~~~~~~~ @@ -126,9 +126,9 @@ for creating principals. This function is performed by factory adapters. For these tests we'll borrow some factories from the principal folder:: - >>> from zope.app.authentication import principalfolder - >>> provideAdapter(principalfolder.AuthenticatedPrincipalFactory) - >>> provideAdapter(principalfolder.FoundPrincipalFactory) + >>> from zope.app.authentication import principalfolder + >>> provideAdapter(principalfolder.AuthenticatedPrincipalFactory) + >>> provideAdapter(principalfolder.FoundPrincipalFactory) For more information on these factories, see their docstrings. @@ -137,35 +137,35 @@ Finally, we'll create the PAU itself:: - >>> from zope.app import authentication - >>> pau = authentication.PluggableAuthentication('xyz_') + >>> from zope.app import authentication + >>> pau = authentication.PluggableAuthentication('xyz_') and configure it with the two plugins:: - >>> pau.credentialsPlugins = ('My Credentials Plugin', ) - >>> pau.authenticatorPlugins = ('My Authenticator Plugin', ) + >>> pau.credentialsPlugins = ('My Credentials Plugin', ) + >>> pau.authenticatorPlugins = ('My Authenticator Plugin', ) Using the PAU to Authenticate ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ We can now use the PAU to authenticate a sample request:: - >>> from zope.publisher.browser import TestRequest - >>> print pau.authenticate(TestRequest()) - None + >>> 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:: - >>> print pau.authenticate(TestRequest(credentials='let me in!')) - None + >>> print pau.authenticate(TestRequest(credentials='let me in!')) + None However, if we provide the proper credentials:: - >>> request = TestRequest(credentials='secretcode') - >>> principal = pau.authenticate(request) - >>> principal - Principal('xyz_bob') + >>> request = TestRequest(credentials='secretcode') + >>> principal = pau.authenticate(request) + >>> principal + Principal('xyz_bob') we get an authenticated principal. @@ -174,24 +174,24 @@ We can verify that the appropriate event was published:: - >>> [event] = getEvents(interfaces.IAuthenticatedPrincipalCreated) - >>> event.principal is principal - True - >>> event.info - PrincipalInfo('bob') - >>> event.request is request - True + >>> [event] = getEvents(interfaces.IAuthenticatedPrincipalCreated) + >>> event.principal is principal + True + >>> event.info + PrincipalInfo('bob') + >>> event.request is request + True 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:: - >>> event.info.title - 'Bob' - >>> event.info.id # does not include pau prefix - 'bob' - >>> event.info.description - '' + >>> event.info.title + 'Bob' + >>> event.info.id # does not include pau prefix + 'bob' + >>> event.info.description + '' It is also decorated with two other attributes, credentialsPlugin and authenticatorPlugin: these are the plugins used to extract credentials for and @@ -200,24 +200,24 @@ determine that a given credential plugin does or does not support logout, and provide information usable to show or hide logout user interface:: - >>> event.info.credentialsPlugin is myCredentialsPlugin - True - >>> event.info.authenticatorPlugin is myAuthenticatorPlugin - True + >>> event.info.credentialsPlugin is myCredentialsPlugin + True + >>> event.info.authenticatorPlugin is myAuthenticatorPlugin + True Normally, we provide subscribers to these events that add additional information to the principal. For example, we'll add one that sets the title:: - >>> def add_info(event): - ... event.principal.title = event.info.title - >>> provideHandler(add_info, [interfaces.IAuthenticatedPrincipalCreated]) + >>> def add_info(event): + ... event.principal.title = event.info.title + >>> provideHandler(add_info, [interfaces.IAuthenticatedPrincipalCreated]) Now, if we authenticate a principal, its title is set:: - >>> principal = pau.authenticate(request) - >>> principal.title - 'Bob' + >>> principal = pau.authenticate(request) + >>> principal.title + 'Bob' Multiple Authenticator Plugins ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -228,47 +228,47 @@ To illustrate, we'll create another authenticator:: - >>> class MyAuthenticatorPlugin2(MyAuthenticatorPlugin): - ... - ... def authenticateCredentials(self, credentials): - ... if credentials == 'secretcode': - ... return PrincipalInfo('black', 'Black Spy', '') - ... elif credentials == 'hiddenkey': - ... return PrincipalInfo('white', 'White Spy', '') + >>> 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') + >>> provideUtility(MyAuthenticatorPlugin2(), name='My Authenticator Plugin 2') If we put it before the original authenticator:: - >>> pau.authenticatorPlugins = ( - ... 'My Authenticator Plugin 2', - ... 'My Authenticator Plugin') + >>> pau.authenticatorPlugins = ( + ... 'My Authenticator Plugin 2', + ... 'My Authenticator Plugin') Then it will be given the first opportunity to authenticate a request:: - >>> pau.authenticate(TestRequest(credentials='secretcode')) - Principal('xyz_black') + >>> pau.authenticate(TestRequest(credentials='secretcode')) + Principal('xyz_black') If neither plugins can authenticate, pau returns None:: - >>> print pau.authenticate(TestRequest(credentials='let me in!!')) - None + >>> print pau.authenticate(TestRequest(credentials='let me in!!')) + None When we change the order of the authenticator plugins:: - >>> pau.authenticatorPlugins = ( - ... 'My Authenticator Plugin', - ... 'My Authenticator Plugin 2') + >>> pau.authenticatorPlugins = ( + ... 'My Authenticator Plugin', + ... 'My Authenticator Plugin 2') we see that our original plugin is now acting first:: - >>> pau.authenticate(TestRequest(credentials='secretcode')) - Principal('xyz_bob') + >>> pau.authenticate(TestRequest(credentials='secretcode')) + Principal('xyz_bob') The second plugin, however, gets a chance to authenticate if first does not:: - >>> pau.authenticate(TestRequest(credentials='hiddenkey')) - Principal('xyz_white') + >>> pau.authenticate(TestRequest(credentials='hiddenkey')) + Principal('xyz_white') Multiple Credentials Plugins ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -277,96 +277,96 @@ illustrate, we'll create a credentials plugin that extracts credentials from a request form:: - >>> class FormCredentialsPlugin: - ... - ... interface.implements(interfaces.ICredentialsPlugin) - ... - ... def extractCredentials(self, request): - ... return request.form.get('my_credentials') - ... - ... def challenge(self, request): - ... pass - ... - ... def logout(request): - ... pass + >>> class FormCredentialsPlugin: + ... + ... interface.implements(interfaces.ICredentialsPlugin) + ... + ... 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') + >>> provideUtility(FormCredentialsPlugin(), + ... name='Form Credentials Plugin') and insert the new credentials plugin before the existing plugin:: - >>> pau.credentialsPlugins = ( - ... 'Form Credentials Plugin', - ... 'My Credentials 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:: - >>> pau.authenticate(TestRequest(credentials='secretcode', - ... form={'my_credentials': 'hiddenkey'})) - Principal('xyz_white') + >>> pau.authenticate(TestRequest(credentials='secretcode', + ... form={'my_credentials': 'hiddenkey'})) + Principal('xyz_white') In this case, the first credentials plugin succeeded in getting credentials from the form and the second authenticator was able to authenticate the credentials. Specifically, the PAU went through these steps: - - Get credentials using 'Form Credentials Plugin' + - Get credentials using 'Form Credentials Plugin' - - Got 'hiddenkey' credentials using 'Form Credentials Plugin', try to - authenticate using 'My Authenticator Plugin' + - Got 'hiddenkey' credentials using 'Form Credentials Plugin', try to + authenticate using 'My Authenticator Plugin' - - Failed to authenticate 'hiddenkey' with 'My Authenticator Plugin', try - 'My Authenticator Plugin 2' + - Failed to authenticate 'hiddenkey' with 'My Authenticator Plugin', try + 'My Authenticator Plugin 2' - - Succeeded in authenticating with 'My Authenticator Plugin 2' + - Succeeded in authenticating with 'My Authenticator Plugin 2' Let's try a different scenario:: - >>> pau.authenticate(TestRequest(credentials='secretcode')) - Principal('xyz_bob') + >>> pau.authenticate(TestRequest(credentials='secretcode')) + Principal('xyz_bob') In this case, the PAU went through these steps:: - - Get credentials using 'Form Credentials Plugin' + - Get credentials using 'Form Credentials Plugin' - - Failed to get credentials using 'Form Credentials Plugin', try - 'My 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' + - Got 'scecretcode' credentials using 'My Credentials Plugin', try to + authenticate using 'My Authenticator Plugin' - - Succeeded in authenticating with 'My Authenticator Plugin' + - Succeeded in authenticating with 'My Authenticator Plugin' Let's try a slightly more complex scenario:: - >>> pau.authenticate(TestRequest(credentials='hiddenkey', - ... form={'my_credentials': 'bogusvalue'})) - Principal('xyz_white') + >>> pau.authenticate(TestRequest(credentials='hiddenkey', + ... form={'my_credentials': 'bogusvalue'})) + Principal('xyz_white') This highlights PAU's ability to use multiple plugins for authentication: - - Get credentials using 'Form Credentials Plugin' + - Get credentials using 'Form Credentials Plugin' - - Got 'bogusvalue' credentials using 'Form Credentials Plugin', try to - authenticate using 'My Authenticator Plugin' + - Got 'bogusvalue' credentials using 'Form Credentials Plugin', try to + authenticate using 'My Authenticator Plugin' - - Failed to authenticate 'boguskey' with 'My Authenticator Plugin', try - 'My Authenticator Plugin 2' + - Failed to authenticate 'boguskey' with 'My Authenticator Plugin', try + 'My Authenticator Plugin 2' - - Failed to authenticate 'boguskey' with 'My Authenticator Plugin 2' -- - there are no more authenticators to try, so lets try the next credentials - plugin for some new credentials + - Failed to authenticate 'boguskey' with 'My Authenticator Plugin 2' -- + there are no more authenticators to try, so lets try the next credentials + plugin for some new credentials - - Get credentials using 'My Credentials Plugin' + - Get credentials using 'My Credentials Plugin' - - Got 'hiddenkey' credentials using 'My Credentials Plugin', try to - authenticate using 'My Authenticator Plugin' + - Got 'hiddenkey' credentials using 'My Credentials Plugin', try to + authenticate using 'My Authenticator Plugin' - - Failed to authenticate 'hiddenkey' using 'My Authenticator Plugin', try - 'My Authenticator Plugin 2' + - Failed to authenticate 'hiddenkey' using 'My Authenticator Plugin', try + 'My Authenticator Plugin 2' - - Succeeded in authenticating with 'My Authenticator Plugin 2' (shouts and - cheers!) + - Succeeded in authenticating with 'My Authenticator Plugin 2' (shouts and + cheers!) Principal Searching @@ -377,41 +377,41 @@ its authenticators. In our example, none of the authenticators implement this search capability, so when we look for a principal:: - >>> print pau.getPrincipal('xyz_bob') - Traceback (most recent call last): - PrincipalLookupError: bob - - >>> print pau.getPrincipal('white') - Traceback (most recent call last): - PrincipalLookupError: white - - >>> print pau.getPrincipal('black') - Traceback (most recent call last): - PrincipalLookupError: black + >>> print pau.getPrincipal('xyz_bob') + Traceback (most recent call last): + PrincipalLookupError: bob + + >>> print pau.getPrincipal('white') + Traceback (most recent call last): + PrincipalLookupError: white + + >>> print pau.getPrincipal('black') + Traceback (most recent call last): + PrincipalLookupError: black 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:: - >>> class SearchableAuthenticatorPlugin: - ... - ... interface.implements(interfaces.IAuthenticatorPlugin) - ... - ... 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 + >>> class SearchableAuthenticatorPlugin: + ... + ... interface.implements(interfaces.IAuthenticatorPlugin) + ... + ... 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 This class is typical of an authenticator plugin. It can both authenticate principals and find principals given a ID. While there are cases @@ -420,28 +420,28 @@ As with any plugin, we need to register it as a utility:: - >>> searchable = SearchableAuthenticatorPlugin() - >>> provideUtility(searchable, name='Searchable Authentication Plugin') + >>> searchable = SearchableAuthenticatorPlugin() + >>> provideUtility(searchable, name='Searchable Authentication Plugin') We'll now configure the PAU to use only the searchable authenticator:: - >>> pau.authenticatorPlugins = ('Searchable Authentication Plugin',) + >>> pau.authenticatorPlugins = ('Searchable Authentication Plugin',) and add some principals to the authenticator:: - >>> searchable.add('bob', 'Bob', 'A nice guy', 'b0b') - >>> searchable.add('white', 'White Spy', 'Sneaky', 'deathtoblack') + >>> 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:: - >>> pau.getPrincipal('xyz_bob') - Principal('xyz_bob') + >>> pau.getPrincipal('xyz_bob') + Principal('xyz_bob') but only those it knows about:: - >>> print pau.getPrincipal('black') - Traceback (most recent call last): - PrincipalLookupError: black + >>> print pau.getPrincipal('black') + Traceback (most recent call last): + PrincipalLookupError: black Found Principal Creates Events ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -450,30 +450,30 @@ a FoundPrincipalCreatedEvent is published when the authenticator finds a principal on behalf of PAU's 'getPrincipal':: - >>> clearEvents() - >>> principal = pau.getPrincipal('xyz_white') - >>> principal - Principal('xyz_white') - - >>> [event] = getEvents(interfaces.IFoundPrincipalCreated) - >>> event.principal is principal - True - >>> event.info - PrincipalInfo('white') + >>> clearEvents() + >>> principal = pau.getPrincipal('xyz_white') + >>> principal + Principal('xyz_white') + + >>> [event] = getEvents(interfaces.IFoundPrincipalCreated) + >>> event.principal is principal + True + >>> event.info + PrincipalInfo('white') The info has an authenticatorPlugin, but no credentialsPlugin, since none was used:: - >>> event.info.credentialsPlugin is None - True - >>> event.info.authenticatorPlugin is searchable - True + >>> event.info.credentialsPlugin is None + True + >>> event.info.authenticatorPlugin is searchable + True 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:: - >>> provideHandler(add_info, [interfaces.IFoundPrincipalCreated]) + >>> provideHandler(add_info, [interfaces.IFoundPrincipalCreated]) Now when a principal is created as a result of a search, it's title and description will be set (by the add_info handler function). @@ -487,46 +487,46 @@ To illustrate, we'll create and register a second searchable authenticator:: - >>> searchable2 = SearchableAuthenticatorPlugin() - >>> provideUtility(searchable2, name='Searchable Authentication Plugin 2') + >>> searchable2 = SearchableAuthenticatorPlugin() + >>> provideUtility(searchable2, name='Searchable Authentication Plugin 2') and add a principal to it:: - >>> searchable.add('black', 'Black Spy', 'Also sneaky', 'deathtowhite') + >>> searchable.add('black', 'Black Spy', 'Also sneaky', 'deathtowhite') When we configure the PAU to use both searchable authenticators (note the order):: - >>> pau.authenticatorPlugins = ( - ... 'Searchable Authentication Plugin 2', - ... 'Searchable Authentication Plugin') + >>> pau.authenticatorPlugins = ( + ... 'Searchable Authentication Plugin 2', + ... 'Searchable Authentication Plugin') we see how the PAU uses both plugins:: - >>> pau.getPrincipal('xyz_white') - Principal('xyz_white') + >>> pau.getPrincipal('xyz_white') + Principal('xyz_white') - >>> pau.getPrincipal('xyz_black') - Principal('xyz_black') + >>> pau.getPrincipal('xyz_black') + Principal('xyz_black') 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:: - >>> searchable2.add('white', 'White Rider', '', 'r1der') - >>> pau.getPrincipal('xyz_white').title - 'White Rider' + >>> searchable2.add('white', 'White Rider', '', 'r1der') + >>> pau.getPrincipal('xyz_white').title + 'White Rider' If we change the order of the plugins:: - >>> pau.authenticatorPlugins = ( - ... 'Searchable Authentication Plugin', - ... 'Searchable Authentication Plugin 2') + >>> pau.authenticatorPlugins = ( + ... 'Searchable Authentication Plugin', + ... 'Searchable Authentication Plugin 2') we get a different principal for ID 'white':: - >>> pau.getPrincipal('xyz_white').title - 'White Spy' + >>> pau.getPrincipal('xyz_white').title + 'White Spy' Issuing a Challenge @@ -536,14 +536,14 @@ credentials when its 'unauthorized' method is called. The need for this functionality is driven by the following use case: - - A user attempts to perform an operation he is not authorized to perform. + - A user attempts to perform an operation he is not authorized to perform. - - A handler responds to the unauthorized error by calling IAuthentication - 'unauthorized'. + - A handler responds to the unauthorized error by calling IAuthentication + 'unauthorized'. - - The authentication component (in our case, a PAU) issues a challenge to - the user to collect new credentials (typically in the form of logging in - as a new user). + - The authentication component (in our case, a PAU) issues a challenge to + the user to collect new credentials (typically in the form of logging in + as a new user). The PAU handles the credentials challenge by delegating to its credentials plugins. @@ -554,61 +554,61 @@ To illustrate challenges, we'll subclass an existing credentials plugin and do something in its 'challenge':: - >>> class LoginFormCredentialsPlugin(FormCredentialsPlugin): - ... - ... def __init__(self, loginForm): - ... self.loginForm = loginForm - ... - ... def challenge(self, request): - ... request.response.redirect(self.loginForm) - ... return True + >>> class LoginFormCredentialsPlugin(FormCredentialsPlugin): + ... + ... def __init__(self, loginForm): + ... self.loginForm = loginForm + ... + ... def challenge(self, request): + ... request.response.redirect(self.loginForm) + ... return True 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:: - >>> provideUtility(LoginFormCredentialsPlugin('simplelogin.html'), - ... name='Simple Login Form Plugin') + >>> provideUtility(LoginFormCredentialsPlugin('simplelogin.html'), + ... name='Simple Login Form Plugin') - >>> provideUtility(LoginFormCredentialsPlugin('advancedlogin.html'), - ... name='Advanced Login Form Plugin') + >>> provideUtility(LoginFormCredentialsPlugin('advancedlogin.html'), + ... name='Advanced Login Form Plugin') and configure the PAU to use them:: - >>> pau.credentialsPlugins = ( - ... 'Simple Login Form Plugin', - ... 'Advanced Login Form Plugin') + >>> pau.credentialsPlugins = ( + ... 'Simple Login Form Plugin', + ... 'Advanced Login Form Plugin') Now when we call 'unauthorized' on the PAU:: - >>> request = TestRequest() - >>> pau.unauthorized(id=None, request=request) + >>> request = TestRequest() + >>> pau.unauthorized(id=None, request=request) we see that the user is redirected to the simple login form:: - >>> request.response.getStatus() - 302 - >>> request.response.getHeader('location') - 'simplelogin.html' + >>> request.response.getStatus() + 302 + >>> request.response.getHeader('location') + 'simplelogin.html' We can change the challenge policy by reordering the plugins:: - >>> pau.credentialsPlugins = ( - ... 'Advanced Login Form Plugin', - ... 'Simple Login Form Plugin') + >>> pau.credentialsPlugins = ( + ... 'Advanced Login Form Plugin', + ... 'Simple Login Form Plugin') Now when we call 'unauthorized':: - >>> request = TestRequest() - >>> pau.unauthorized(id=None, request=request) + >>> request = TestRequest() + >>> pau.unauthorized(id=None, request=request) the advanced plugin is used because it's first:: - >>> request.response.getStatus() - 302 - >>> request.response.getHeader('location') - 'advancedlogin.html' + >>> request.response.getStatus() + 302 + >>> request.response.getHeader('location') + 'advancedlogin.html' Challenge Protocols ~~~~~~~~~~~~~~~~~~~ @@ -627,45 +627,45 @@ Let's look at an example. We'll define a new plugin that specifies an 'X-Challenge' protocol:: - >>> 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 + >>> 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 and register a couple instances as utilities:: - >>> provideUtility(XChallengeCredentialsPlugin('basic'), - ... name='Basic X-Challenge Plugin') + >>> provideUtility(XChallengeCredentialsPlugin('basic'), + ... name='Basic X-Challenge Plugin') - >>> provideUtility(XChallengeCredentialsPlugin('advanced'), - ... name='Advanced X-Challenge Plugin') + >>> provideUtility(XChallengeCredentialsPlugin('advanced'), + ... name='Advanced X-Challenge Plugin') When we use both plugins with the PAU:: - >>> pau.credentialsPlugins = ( - ... 'Basic X-Challenge Plugin', - ... 'Advanced X-Challenge Plugin') + >>> pau.credentialsPlugins = ( + ... 'Basic X-Challenge Plugin', + ... 'Advanced X-Challenge Plugin') and call 'unauthorized':: - >>> request = TestRequest() - >>> pau.unauthorized(None, request) + >>> request = TestRequest() + >>> pau.unauthorized(None, request) we see that both plugins participate in the challange, rather than just the first plugin:: - >>> request.response.getHeader('X-Challenge') - 'advanced basic' + >>> request.response.getHeader('X-Challenge') + 'advanced basic' Pluggable-Authentication Prefixes @@ -678,25 +678,25 @@ that principal ids from different PAUs don't conflict. We can provide a prefix when a PAU is created:: - >>> pau = authentication.PluggableAuthentication('mypau_') - >>> pau.credentialsPlugins = ('My Credentials Plugin', ) - >>> pau.authenticatorPlugins = ('My Authenticator Plugin', ) + >>> pau = authentication.PluggableAuthentication('mypau_') + >>> pau.credentialsPlugins = ('My Credentials Plugin', ) + >>> pau.authenticatorPlugins = ('My Authenticator Plugin', ) When we create a request and try to authenticate:: - >>> pau.authenticate(TestRequest(credentials='secretcode')) - Principal('mypau_bob') + >>> pau.authenticate(TestRequest(credentials='secretcode')) + Principal('mypau_bob') Note that now, our principal's id has the pluggable-authentication utility prefix. We can still lookup a principal, as long as we supply the prefix:: - >> pau.getPrincipal('mypas_42') - Principal('mypas_42', "{'domain': 42}") + >> pau.getPrincipal('mypas_42') + Principal('mypas_42', "{'domain': 42}") - >> pau.getPrincipal('mypas_41') - OddPrincipal('mypas_41', "{'int': 41}") + >> pau.getPrincipal('mypas_41') + OddPrincipal('mypas_41', "{'int': 41}") Searching @@ -704,9 +704,9 @@ PAU implements ISourceQueriables:: - >>> from zope.schema.interfaces import ISourceQueriables - >>> ISourceQueriables.providedBy(pau) - True + >>> from zope.schema.interfaces import ISourceQueriables + >>> ISourceQueriables.providedBy(pau) + True This means a PAU can be used in a principal source vocabulary (Zope provides a sophisticated searching UI for principal sources). @@ -718,61 +718,61 @@ Currently, our list of authenticators:: - >>> pau.authenticatorPlugins - ('My Authenticator Plugin',) + >>> pau.authenticatorPlugins + ('My Authenticator Plugin',) does not include a queriable authenticator. PAU cannot therefore provide any queriables:: - >>> list(pau.getQueriables()) - [] + >>> list(pau.getQueriables()) + [] Before we illustrate how an authenticator is used by the PAU to search for principals, we need to setup an adapter used by PAU:: - >>> provideAdapter( - ... authentication.authentication.QuerySchemaSearchAdapter, - ... provides=interfaces.IQueriableAuthenticator) + >>> provideAdapter( + ... authentication.authentication.QuerySchemaSearchAdapter, + ... provides=interfaces.IQueriableAuthenticator) This adapter delegates search responsibility to an authenticator, but prepends the PAU prefix to any principal IDs returned in a search. Next, we'll create a plugin that provides a search interface:: - >>> class QueriableAuthenticatorPlugin(MyAuthenticatorPlugin): - ... - ... interface.implements(interfaces.IQuerySchemaSearch) - ... - ... schema = None - ... - ... def search(self, query, start=None, batch_size=None): - ... yield 'foo' - ... + >>> class QueriableAuthenticatorPlugin(MyAuthenticatorPlugin): + ... + ... interface.implements(interfaces.IQuerySchemaSearch) + ... + ... schema = None + ... + ... def search(self, query, start=None, batch_size=None): + ... yield 'foo' + ... and install it as a plugin:: - >>> plugin = QueriableAuthenticatorPlugin() - >>> provideUtility(plugin, - ... provides=interfaces.IAuthenticatorPlugin, - ... name='Queriable') - >>> pau.authenticatorPlugins += ('Queriable',) + >>> plugin = QueriableAuthenticatorPlugin() + >>> provideUtility(plugin, + ... provides=interfaces.IAuthenticatorPlugin, + ... name='Queriable') + >>> pau.authenticatorPlugins += ('Queriable',) Now, the PAU provides a single queriable:: - >>> list(pau.getQueriables()) # doctest: +ELLIPSIS - [('Queriable', ...QuerySchemaSearchAdapter object...)] + >>> list(pau.getQueriables()) # doctest: +ELLIPSIS + [('Queriable', ...QuerySchemaSearchAdapter object...)] We can use this queriable to search for our principal:: - >>> queriable = list(pau.getQueriables())[0][1] - >>> list(queriable.search('not-used')) - ['mypau_foo'] + >>> queriable = list(pau.getQueriables())[0][1] + >>> list(queriable.search('not-used')) + ['mypau_foo'] Note that the resulting principal ID includes the PAU prefix. Were we to search the plugin directly:: - >>> list(plugin.search('not-used')) - ['foo'] + >>> list(plugin.search('not-used')) + ['foo'] The result does not include the PAU prefix. The prepending of the prefix is handled by the PluggableAuthenticationQueriable. @@ -782,58 +782,58 @@ 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.site.hooks - >>> site = zope.site.hooks.getSite() - >>> plugin = LocatedQueriableAuthenticatorPlugin() - >>> plugin.__parent__ = site - >>> plugin.__name__ = 'localname' - >>> provideUtility(plugin, - ... provides=interfaces.IAuthenticatorPlugin, - ... name='location-queriable') - >>> pau.authenticatorPlugins = ('location-queriable',) + >>> import zope.location.interfaces + >>> class LocatedQueriableAuthenticatorPlugin(QueriableAuthenticatorPlugin): + ... + ... interface.implements(zope.location.interfaces.ILocation) + ... + ... __parent__ = __name__ = None + ... + >>> import zope.site.hooks + >>> site = zope.site.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...)] + >>> 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 + >>> 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',) + >>> 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...)] + >>> 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 - - >>> queriable.__parent__ is pau - True + >>> queriable = queriables[0][1] + >>> queriable.__parent__ # doctest: +ELLIPSIS + + >>> queriable.__parent__ is pau + True ================ @@ -844,17 +844,17 @@ information. We create an internal principal using the `InternalPrincipal` class: - >>> 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 InternalPrincipal + >>> p1 = InternalPrincipal('login1', '123', "Principal 1", + ... passwordManagerName="SHA1") + >>> p2 = InternalPrincipal('login2', '456', "The Other One") and add them to a principal folder: - >>> from zope.app.authentication.principalfolder import PrincipalFolder - >>> principals = PrincipalFolder('principal.') - >>> principals['p1'] = p1 - >>> principals['p2'] = p2 + >>> from zope.app.authentication.principalfolder import PrincipalFolder + >>> principals = PrincipalFolder('principal.') + >>> principals['p1'] = p1 + >>> principals['p2'] = p2 Authentication -------------- @@ -862,9 +862,9 @@ Principal folders provide the `IAuthenticatorPlugin` interface. When we provide suitable credentials: - >>> from pprint import pprint - >>> principals.authenticateCredentials({'login': 'login1', 'password': '123'}) - PrincipalInfo(u'principal.p1') + >>> from pprint import pprint + >>> principals.authenticateCredentials({'login': 'login1', 'password': '123'}) + PrincipalInfo(u'principal.p1') We get back a principal id and supplementary information, including the principal title and description. Note that the principal id is a concatenation @@ -873,9 +873,9 @@ None is returned if the credentials are invalid: - >>> principals.authenticateCredentials({'login': 'login1', - ... 'password': '1234'}) - >>> principals.authenticateCredentials(42) + >>> principals.authenticateCredentials({'login': 'login1', + ... 'password': '1234'}) + >>> principals.authenticateCredentials(42) Search ------ @@ -883,127 +883,127 @@ Principal folders also provide the IQuerySchemaSearch interface. This supports both finding principal information based on their ids: - >>> principals.principalInfo('principal.p1') - PrincipalInfo('principal.p1') + >>> principals.principalInfo('principal.p1') + PrincipalInfo('principal.p1') - >>> principals.principalInfo('p1') + >>> principals.principalInfo('p1') and searching for principals based on a search string: - >>> list(principals.search({'search': 'other'})) - [u'principal.p2'] + >>> list(principals.search({'search': 'other'})) + [u'principal.p2'] - >>> list(principals.search({'search': 'OTHER'})) - [u'principal.p2'] + >>> list(principals.search({'search': 'OTHER'})) + [u'principal.p2'] - >>> list(principals.search({'search': ''})) - [u'principal.p1', u'principal.p2'] + >>> list(principals.search({'search': ''})) + [u'principal.p1', u'principal.p2'] - >>> list(principals.search({'search': 'eek'})) - [] + >>> list(principals.search({'search': 'eek'})) + [] - >>> list(principals.search({})) - [] + >>> list(principals.search({})) + [] If there are a large number of matches: - >>> for i in range(20): - ... i = str(i) - ... p = InternalPrincipal('l'+i, i, "Dude "+i) - ... principals[i] = p - - >>> pprint(list(principals.search({'search': 'D'}))) - [u'principal.0', - u'principal.1', - u'principal.10', - u'principal.11', - u'principal.12', - u'principal.13', - u'principal.14', - u'principal.15', - u'principal.16', - u'principal.17', - u'principal.18', - u'principal.19', - u'principal.2', - u'principal.3', - u'principal.4', - u'principal.5', - u'principal.6', - u'principal.7', - u'principal.8', - u'principal.9'] + >>> for i in range(20): + ... i = str(i) + ... p = InternalPrincipal('l'+i, i, "Dude "+i) + ... principals[i] = p + + >>> pprint(list(principals.search({'search': 'D'}))) + [u'principal.0', + u'principal.1', + u'principal.10', + u'principal.11', + u'principal.12', + u'principal.13', + u'principal.14', + u'principal.15', + u'principal.16', + u'principal.17', + u'principal.18', + u'principal.19', + u'principal.2', + u'principal.3', + u'principal.4', + u'principal.5', + u'principal.6', + u'principal.7', + u'principal.8', + u'principal.9'] We can use batching parameters to specify a subset of results: - >>> pprint(list(principals.search({'search': 'D'}, start=17))) - [u'principal.7', u'principal.8', u'principal.9'] + >>> pprint(list(principals.search({'search': 'D'}, start=17))) + [u'principal.7', u'principal.8', u'principal.9'] - >>> pprint(list(principals.search({'search': 'D'}, batch_size=5))) - [u'principal.0', - u'principal.1', - u'principal.10', - u'principal.11', - u'principal.12'] - - >>> pprint(list(principals.search({'search': 'D'}, start=5, batch_size=5))) - [u'principal.13', - u'principal.14', - u'principal.15', - u'principal.16', - u'principal.17'] + >>> pprint(list(principals.search({'search': 'D'}, batch_size=5))) + [u'principal.0', + u'principal.1', + u'principal.10', + u'principal.11', + u'principal.12'] + + >>> pprint(list(principals.search({'search': 'D'}, start=5, batch_size=5))) + [u'principal.13', + u'principal.14', + u'principal.15', + u'principal.16', + u'principal.17'] There is an additional method that allows requesting the principal id associated with a login id. The method raises KeyError when there is no associated principal:: - >>> principals.getIdByLogin("not-there") - Traceback (most recent call last): - KeyError: 'not-there' + >>> principals.getIdByLogin("not-there") + Traceback (most recent call last): + KeyError: 'not-there' If there is a matching principal, the id is returned:: - >>> principals.getIdByLogin("login1") - u'principal.p1' + >>> principals.getIdByLogin("login1") + u'principal.p1' Changing credentials -------------------- Credentials can be changed by modifying principal-information objects: - >>> p1.login = 'bob' - >>> p1.password = 'eek' + >>> p1.login = 'bob' + >>> p1.password = 'eek' - >>> principals.authenticateCredentials({'login': 'bob', 'password': 'eek'}) - PrincipalInfo(u'principal.p1') + >>> principals.authenticateCredentials({'login': 'bob', 'password': 'eek'}) + PrincipalInfo(u'principal.p1') - >>> principals.authenticateCredentials({'login': 'login1', - ... 'password': 'eek'}) + >>> principals.authenticateCredentials({'login': 'login1', + ... 'password': 'eek'}) - >>> principals.authenticateCredentials({'login': 'bob', - ... 'password': '123'}) + >>> principals.authenticateCredentials({'login': 'bob', + ... 'password': '123'}) It is an error to try to pick a login name that is already taken: - >>> p1.login = 'login2' - Traceback (most recent call last): - ... - ValueError: Principal Login already taken! + >>> p1.login = 'login2' + Traceback (most recent call last): + ... + ValueError: Principal Login already taken! If such an attempt is made, the data are unchanged: - >>> principals.authenticateCredentials({'login': 'bob', 'password': 'eek'}) - PrincipalInfo(u'principal.p1') + >>> principals.authenticateCredentials({'login': 'bob', 'password': 'eek'}) + PrincipalInfo(u'principal.p1') Removing principals ------------------- Of course, if a principal is removed, we can no-longer authenticate it: - >>> del principals['p1'] - >>> principals.authenticateCredentials({'login': 'bob', - ... 'password': 'eek'}) + >>> del principals['p1'] + >>> principals.authenticateCredentials({'login': 'bob', + ... 'password': 'eek'}) ============ @@ -1031,138 +1031,138 @@ authentication plugins, and register some of them as utilities and put others in a faux PAU. - >>> from zope.app.authentication import interfaces - >>> from zope import interface, component - >>> class DemoPlugin(object): - ... interface.implements(interfaces.IAuthenticatorPlugin) - ... 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] - >>> class DemoAuth(dict): - ... interface.implements(interfaces.IPluggableAuthentication) - ... 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) + >>> from zope.app.authentication import interfaces + >>> from zope import interface, component + >>> class DemoPlugin(object): + ... interface.implements(interfaces.IAuthenticatorPlugin) + ... 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] + >>> class DemoAuth(dict): + ... interface.implements(interfaces.IPluggableAuthentication) + ... 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) We are now ready to create a vocabulary that we can use. The context is our faux authentication utility, `auth`. - >>> from zope.app.authentication import vocabulary - >>> vocab = vocabulary.authenticatorPlugins(auth) + >>> from zope.app.authentication import vocabulary + >>> vocab = vocabulary.authenticatorPlugins(auth) Iterating over the vocabulary results in all of the terms, in a relatively arbitrary order of their names. (This vocabulary should typically use a widget that sorts values on the basis of localized collation order of the term titles.) - >>> [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'] + >>> [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'] Similarly, we can use `in` to test for the presence of values in the vocabulary. - >>> ['Plugin %s' % i in vocab for i in range(-1, 6)] - [False, True, True, True, True, True, False] - >>> 'Plugin X' in vocab - True + >>> ['Plugin %s' % i in vocab for i in range(-1, 6)] + [False, True, True, True, True, True, False] + >>> 'Plugin X' in vocab + True The length reports the expected value. - >>> len(vocab) - 6 + >>> len(vocab) + 6 One can get a term for a given value using `getTerm()`; its token, in turn, should also return the same effective term from `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 - ... + >>> 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 + ... The terms have titles, which are message ids that show the plugin title or id and whether the plugin is a utility or just contained in the auth utility. We'll give one of the plugins a dublin core title just to show the functionality. - >>> import zope.dublincore.interfaces - >>> class ISpecial(interface.Interface): - ... pass - ... - >>> interface.directlyProvides(contained_plugins[1], ISpecial) - >>> class DemoDCAdapter(object): - ... interface.implements( - ... zope.dublincore.interfaces.IDCDescriptiveProperties) - ... component.adapts(ISpecial) - ... def __init__(self, context): - ... pass - ... title = u'Special Title' - ... - >>> component.provideAdapter(DemoDCAdapter) + >>> import zope.dublincore.interfaces + >>> class ISpecial(interface.Interface): + ... pass + ... + >>> interface.directlyProvides(contained_plugins[1], ISpecial) + >>> class DemoDCAdapter(object): + ... interface.implements( + ... zope.dublincore.interfaces.IDCDescriptiveProperties) + ... component.adapts(ISpecial) + ... def __init__(self, context): + ... pass + ... title = u'Special Title' + ... + >>> component.provideAdapter(DemoDCAdapter) We need to regenerate the vocabulary, since it calculates all of its data at once. - >>> vocab = vocabulary.authenticatorPlugins(auth) + >>> vocab = vocabulary.authenticatorPlugins(auth) Now we'll check the titles. We'll have to translate them to see what we expect. - >>> 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)'] + >>> 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 ------------------ For completeness, we'll do the same review of the credentialsPlugins. - >>> class DemoPlugin(object): - ... interface.implements(interfaces.ICredentialsPlugin) - ... 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) + >>> class DemoPlugin(object): + ... interface.implements(interfaces.ICredentialsPlugin) + ... 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) Iterating over the vocabulary results in all of the terms, in a relatively arbitrary order of their names. (This vocabulary should typically use a @@ -1170,28 +1170,28 @@ titles.) Similarly, we can use `in` to test for the presence of values in the vocabulary. The length reports the expected value. - >>> [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 + >>> [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 One can get a term for a given value using `getTerm()`; its token, in turn, should also return the same effective term from `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 - ... + >>> 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 + ... The terms have titles, which are message ids that show the plugin title or id and whether the plugin is a utility or just contained in the auth utility. @@ -1200,96 +1200,100 @@ of its data at once. Then we'll check the titles. We'll have to translate them to see what we expect. - >>> 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)'] + >>> 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)'] ======= Changes ======= + 3.9 (2010-10-18) + ---------------- + + * Move concrete IAuthenticatorPlugin implementations to + zope.pluggableauth.plugins. Leave backwards compatibility imports. + + * Use zope.formlib throughout to lift the dependency on zope.app.form. As it + turns out, zope.app.form is still a indirect test dependency though. + 3.8.0 (2010-09-25) ------------------ * Using python's ``doctest`` module instead of deprecated - ``zope.testing.doctest[unit]``. + ``zope.testing.doctest[unit]``. * Moved the following views from `zope.app.securitypolicy` here, to inverse - dependency between these two packages, as `zope.app.securitypolicy` - deprecated in ZTK 1.0: - - - ``@@grant.html`` - - ``@@AllRolePermissions.html`` - - ``@@RolePermissions.html`` - - ``@@RolesWithPermission.html`` + dependency between these two packages, as `zope.app.securitypolicy` + deprecated in ZTK 1.0: + - ``@@grant.html`` + - ``@@AllRolePermissions.html`` + - ``@@RolePermissions.html`` + - ``@@RolesWithPermission.html`` 3.7.1 (2010-02-11) ------------------ * Using the new `principalfactories.zcml` file, from ``zope.pluggableauth``, - to avoid duplication errors, in the adapters registration. - + to avoid duplication errors, in the adapters registration. 3.7.0 (2010-02-08) ------------------ * The Pluggable Authentication utility has been severed and released - in a standalone package: `zope.pluggableauth`. We are now using this - new package, providing backward compatibility imports to assure a - smooth transition. - + in a standalone package: `zope.pluggableauth`. We are now using this + new package, providing backward compatibility imports to assure a + smooth transition. 3.6.2 (2010-01-05) ------------------ * Fix tests by using zope.login, and require new zope.publisher 3.12. - 3.6.1 (2009-10-07) ------------------ * Fix ftesting.zcml due to ``zope.securitypolicy`` update. * Don't use ``zope.app.testing.ztapi`` in tests, use zope.component's - testing functions instead. + testing functions instead. * Fix functional tests and stop using port 8081. Redirecting to - different port without trusted flag is not allowed. + different port without trusted flag is not allowed. 3.6.0 (2009-03-14) ------------------ * Separate the presentation template and camefrom/redirection logic for the - ``loginForm.html`` view. Now the logic is contained in the - ``zope.app.authentication.browser.loginform.LoginForm`` class. + ``loginForm.html`` view. Now the logic is contained in the + ``zope.app.authentication.browser.loginform.LoginForm`` class. * Fix login form redirection failure in some cases with Python 2.6. * Use the new ``zope.authentication`` package instead of ``zope.app.security``. * The "Password Manager Names" vocabulary and simple password manager registry - were moved to the ``zope.password`` package. + were moved to the ``zope.password`` package. * Remove deprecated code. - 3.5.0 (2009-03-06) ------------------ * Split password manager functionality off to the new ``zope.password`` - package. Backward-compatibility imports are left in place. + package. Backward-compatibility imports are left in place. * Use ``zope.site`` instead of ``zope.app.component``. (Browser code still - needs ``zope.app.component`` as it depends on view classes of this - package.) + needs ``zope.app.component`` as it depends on view classes of this + package.) 3.5.0a2 (2009-02-01) -------------------- @@ -1300,16 +1304,16 @@ -------------------- * Use ``zope.container`` instead of ``zope.app.container``. (Browser code - still needs ``zope.app.container`` as it depends on view classes of this - package.) + still needs ``zope.app.container`` as it depends on view classes of this + package.) * Encoded passwords are now stored with a prefix ({MD5}, {SHA1}, - {SSHA}) indicating the used encoding schema. Old (encoded) passwords - can still be used. + {SSHA}) indicating the used encoding schema. Old (encoded) passwords + can still be used. * Add an SSHA password manager that is compatible with standard LDAP - passwords. As this encoding gives better security agains dictionary - attacks, users are encouraged to switch to this new password schema. + passwords. As this encoding gives better security agains dictionary + attacks, users are encouraged to switch to this new password schema. * InternalPrincipal now uses SSHA password manager by default. @@ -1317,7 +1321,7 @@ ------------------ * Depend on zope.session instead of zope.app.session. The first one - currently has all functionality we need. + currently has all functionality we need. * Fix deprecation warnings for ``md5`` and ``sha`` on Python 2.6. 3.4.3 (2008-08-07) @@ -1329,8 +1333,8 @@ ------------------- * Make it compatible with zope.app.container 3.6.1 and 3.5.4 changes, - Changed ``super(BTreeContainer, self).__init__()`` to - ``super(GroupFolder, self).__init__()`` in ``GroupFolder`` class. + Changed ``super(BTreeContainer, self).__init__()`` to + ``super(GroupFolder, self).__init__()`` in ``GroupFolder`` class. 3.4.1 (2007-10-24) ------------------ diff -Nru zope.app.authentication-3.8.0/buildout.cfg zope.app.authentication-3.9/buildout.cfg --- zope.app.authentication-3.8.0/buildout.cfg 2010-09-25 09:07:17.000000000 +0000 +++ zope.app.authentication-3.9/buildout.cfg 2010-10-18 09:51:14.000000000 +0000 @@ -1,7 +1,13 @@ [buildout] +extends = + http://download.zope.org/zopetoolkit/index/1.0/ztk-versions.cfg + http://download.zope.org/zopetoolkit/index/1.0/zopeapp-versions.cfg develop = . parts = test +[versions] +zope.pluggableauth = 1.1.0 + [test] recipe = zc.recipe.testrunner eggs = zope.app.authentication [test] diff -Nru zope.app.authentication-3.8.0/debian/changelog zope.app.authentication-3.9/debian/changelog --- zope.app.authentication-3.8.0/debian/changelog 2011-12-31 08:17:29.000000000 +0000 +++ zope.app.authentication-3.9/debian/changelog 2012-11-13 20:31:13.000000000 +0000 @@ -1,3 +1,16 @@ +zope.app.authentication (3.9-0ubuntu1) raring; urgency=low + + * New upstream release. + * Lintian fixes: + - debian/copyright: + + Update obsolete field names. + + Update Format URI. + - debian/control: + + Add Homepage field. + + Bump Standards-Version to 3.9.3. + + -- Logan Rosen Sun, 11 Nov 2012 12:51:53 -0500 + zope.app.authentication (3.8.0-0ubuntu4) precise; urgency=low * Rebuild to drop python2.6 dependencies. diff -Nru zope.app.authentication-3.8.0/debian/control zope.app.authentication-3.9/debian/control --- zope.app.authentication-3.8.0/debian/control 2011-06-30 16:23:28.000000000 +0000 +++ zope.app.authentication-3.9/debian/control 2012-11-13 20:31:13.000000000 +0000 @@ -1,11 +1,12 @@ Source: zope.app.authentication Section: zope Priority: extra +Homepage: http://pypi.python.org/pypi/zope.app.authentication Maintainer: Ubuntu Developers XSBC-Original-Maintainer: Gediminas Paulauskas Build-Depends: debhelper (>= 7), python-all (>= 2.6.6-3~), python-setuptools (>= 0.6b3), python-van.pydeb (>= 1.3.0-4) -Standards-Version: 3.9.2 +Standards-Version: 3.9.3 X-Python-Version: >= 2.5 Package: python-zope.app.authentication diff -Nru zope.app.authentication-3.8.0/debian/copyright zope.app.authentication-3.9/debian/copyright --- zope.app.authentication-3.8.0/debian/copyright 2011-06-30 16:23:28.000000000 +0000 +++ zope.app.authentication-3.9/debian/copyright 2012-11-13 20:31:13.000000000 +0000 @@ -1,6 +1,6 @@ -Format-Specification: http://dep.debian.net/deps/dep5/ -Name: zope.app.authentication -Maintainer: Zope Corporation and Contributors +Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ +Upstream-Name: zope.app.authentication +Upstream-Contact: Zope Corporation and Contributors Source: http://pypi.python.org/pypi/zope.app.authentication Files: * diff -Nru zope.app.authentication-3.8.0/setup.py zope.app.authentication-3.9/setup.py --- zope.app.authentication-3.8.0/setup.py 2010-09-25 09:07:17.000000000 +0000 +++ zope.app.authentication-3.9/setup.py 2010-10-18 09:51:14.000000000 +0000 @@ -18,7 +18,7 @@ ############################################################################## """Setup for zope.app.authentication package -$Id: setup.py 116804 2010-09-25 09:06:39Z icemac $ +$Id: setup.py 117636 2010-10-18 09:50:37Z janwijbrand $ """ import os from setuptools import setup, find_packages @@ -27,7 +27,7 @@ return open(os.path.join(os.path.dirname(__file__), *rnames)).read() setup(name='zope.app.authentication', - version='3.8.0', + version='3.9', author='Zope Corporation and Contributors', author_email='zope-dev@zope.org', description=('Principals and groups management for ' @@ -73,27 +73,27 @@ 'zope.login',]), namespace_packages=['zope', 'zope.app'], install_requires=[ - 'ZODB3', 'setuptools', - 'zope.app.form',# needed by browser code - 'zope.app.container', # needed for browser code - 'zope.app.component', # needed for browser code + 'ZODB3', 'zope.authentication', 'zope.component', 'zope.container', 'zope.dublincore', 'zope.event', 'zope.exceptions', - 'zope.formlib', + 'zope.formlib >= 4.0.2', 'zope.i18n', 'zope.i18nmessageid', 'zope.interface', 'zope.location', - 'zope.password>=3.5.1', - 'zope.pluggableauth>=1.0.1', + 'zope.password >= 3.5.1', + 'zope.pluggableauth >= 1.1', 'zope.schema', 'zope.security', 'zope.traversing', + # Needed for browser code. + 'zope.app.container', + 'zope.app.component', ], include_package_data = True, zip_safe = False, diff -Nru zope.app.authentication-3.8.0/src/zope/app/authentication/browser/configure.zcml zope.app.authentication-3.9/src/zope/app/authentication/browser/configure.zcml --- zope.app.authentication-3.8.0/src/zope/app/authentication/browser/configure.zcml 2010-09-25 09:07:17.000000000 +0000 +++ zope.app.authentication-3.9/src/zope/app/authentication/browser/configure.zcml 2010-10-18 09:51:14.000000000 +0000 @@ -16,7 +16,7 @@ @@ -84,7 +84,7 @@ diff -Nru zope.app.authentication-3.8.0/src/zope/app/authentication/browser/grant.zcml zope.app.authentication-3.9/src/zope/app/authentication/browser/grant.zcml --- zope.app.authentication-3.8.0/src/zope/app/authentication/browser/grant.zcml 2010-09-25 09:07:17.000000000 +0000 +++ zope.app.authentication-3.9/src/zope/app/authentication/browser/grant.zcml 2010-10-18 09:51:14.000000000 +0000 @@ -46,7 +46,7 @@ type="zope.publisher.interfaces.browser.IBrowserRequest" for="zope.schema.interfaces.IChoice zope.securitypolicy.interfaces.IGrantVocabulary" - provides="zope.app.form.interfaces.IInputWidget" + provides="zope.formlib.interfaces.IInputWidget" factory=".granting.GrantWidget" permission="zope.Public" /> diff -Nru zope.app.authentication-3.8.0/src/zope/app/authentication/browser/granting.py zope.app.authentication-3.9/src/zope/app/authentication/browser/granting.py --- zope.app.authentication-3.8.0/src/zope/app/authentication/browser/granting.py 2010-09-25 09:07:17.000000000 +0000 +++ zope.app.authentication-3.9/src/zope/app/authentication/browser/granting.py 2010-10-18 09:51:14.000000000 +0000 @@ -13,7 +13,7 @@ ############################################################################## """Granting Roles and Permissions to Principals -$Id: granting.py 116801 2010-09-25 09:04:32Z icemac $ +$Id: granting.py 117475 2010-10-12 14:42:30Z janwijbrand $ """ __docformat__ = "reStructuredText" @@ -28,11 +28,11 @@ from zope.securitypolicy.vocabulary import GrantVocabulary from zope.authentication.principal import PrincipalSource -from zope.app.form.utility import setUpWidget -from zope.app.form.browser import RadioWidget -from zope.app.form.browser.widget import renderElement -from zope.app.form.interfaces import MissingInputError -from zope.app.form.interfaces import IInputWidget +from zope.formlib.interfaces import IInputWidget +from zope.formlib.interfaces import MissingInputError +from zope.formlib.utility import setUpWidget +from zope.formlib.widget import renderElement +from zope.formlib.widgets import RadioWidget from zope.security.interfaces import IPermission @@ -68,7 +68,7 @@ def renderItem(self, index, text, value, name, cssClass): - """Render an item of the list. + """Render an item of the list. Revert the order of label and text. Added field id to the lable attribute. @@ -89,7 +89,7 @@ return self._tdTemplate % (tdClass, id, text, elem) def renderSelectedItem(self, index, text, value, name, cssClass): - """Render a selected item of the list. + """Render a selected item of the list. Revert the order of label and text. Added field id to the lable attribute. diff -Nru zope.app.authentication-3.8.0/src/zope/app/authentication/browser/granting.txt zope.app.authentication-3.9/src/zope/app/authentication/browser/granting.txt --- zope.app.authentication-3.8.0/src/zope/app/authentication/browser/granting.txt 2010-09-25 09:07:17.000000000 +0000 +++ zope.app.authentication-3.9/src/zope/app/authentication/browser/granting.txt 2010-10-18 09:51:14.000000000 +0000 @@ -71,13 +71,13 @@ - Vocabulary Choice Widgets >>> from zope.schema.interfaces import IChoice - >>> from zope.app.form.browser import ChoiceInputWidget - >>> from zope.app.form.interfaces import IInputWidget + >>> from zope.formlib.interfaces import IInputWidget + >>> from zope.formlib.widgets import ChoiceInputWidget >>> ztapi.browserViewProviding(IChoice, ChoiceInputWidget, IInputWidget) >>> from zope.schema.interfaces import IVocabularyTokenized >>> from zope.publisher.interfaces.browser import IBrowserRequest - >>> from zope.app.form.browser import DropdownWidget + >>> from zope.formlib.widgets import DropdownWidget >>> ztapi.provideMultiView((IChoice, IVocabularyTokenized), ... IBrowserRequest, IInputWidget, '', ... DropdownWidget) @@ -90,14 +90,14 @@ >>> ztapi.browserViewProviding(IPrincipalSource, PrincipalTerms, ITerms) >>> from zope.app.security.browser.auth import AuthUtilitySearchView - >>> from zope.app.form.browser.interfaces import ISourceQueryView + >>> from zope.formlib.interfaces import ISourceQueryView >>> ztapi.browserViewProviding(IAuthentication, ... AuthUtilitySearchView, ... ISourceQueryView) >>> from zope.schema.interfaces import ISource - >>> from zope.app.form.browser.source import SourceInputWidget + >>> from zope.formlib.source import SourceInputWidget >>> ztapi.provideMultiView((IChoice, ISource), IBrowserRequest, ... IInputWidget, '', SourceInputWidget) diff -Nru zope.app.authentication-3.8.0/src/zope/app/authentication/browser/schemasearch.py zope.app.authentication-3.9/src/zope/app/authentication/browser/schemasearch.py --- zope.app.authentication-3.8.0/src/zope/app/authentication/browser/schemasearch.py 2010-09-25 09:07:17.000000000 +0000 +++ zope.app.authentication-3.9/src/zope/app/authentication/browser/schemasearch.py 2010-10-18 09:51:14.000000000 +0000 @@ -13,14 +13,15 @@ ############################################################################## """Search interface for queriables. -$Id: schemasearch.py 113109 2010-06-04 11:54:46Z janwijbrand $ +$Id: schemasearch.py 117475 2010-10-12 14:42:30Z janwijbrand $ """ __docformat__ = "reStructuredText" from zope.app.authentication.i18n import ZopeMessageFactory as _ -from zope.app.form.browser.interfaces import ISourceQueryView -from zope.app.form.utility import setUpWidgets, getWidgetsData -from zope.formlib.interfaces import IInputWidget +from zope.formlib.interfaces import IInputWidget, InputErrors +from zope.formlib.interfaces import ISourceQueryView +from zope.formlib.interfaces import WidgetsError, MissingInputError +from zope.formlib.utility import setUpWidgets from zope.i18n import translate from zope.interface import implements from zope.schema import getFieldsInOrder @@ -106,5 +107,21 @@ return None schema = self.context.schema setUpWidgets(self, schema, IInputWidget, prefix=name+'.field') - data = getWidgetsData(self, schema) + # XXX inline the original getWidgetsData call in + # zope.app.form.utility to lift the dependency on zope.app.form. + data = {} + errors = [] + for name, field in getFieldsInOrder(schema): + widget = getattr(self, name + '_widget') + if IInputWidget.providedBy(widget): + if widget.hasInput(): + try: + data[name] = widget.getInputValue() + except InputErrors, error: + errors.append(error) + elif field.required: + errors.append(MissingInputError( + name, widget.label, 'the field is required')) + if errors: + raise WidgetsError(errors, widgetsData=data) return self.context.search(data) diff -Nru zope.app.authentication-3.8.0/src/zope/app/authentication/browser/schemasearch.txt zope.app.authentication-3.9/src/zope/app/authentication/browser/schemasearch.txt --- zope.app.authentication-3.8.0/src/zope/app/authentication/browser/schemasearch.txt 2010-09-25 09:07:17.000000000 +0000 +++ zope.app.authentication-3.9/src/zope/app/authentication/browser/schemasearch.txt 2010-10-18 09:51:14.000000000 +0000 @@ -6,8 +6,8 @@ >>> from zope.component import provideAdapter >>> from zope.schema.interfaces import ITextLine >>> from zope.publisher.interfaces.browser import IDefaultBrowserLayer - >>> from zope.app.form.browser import TextWidget - >>> from zope.app.form.interfaces import IInputWidget + >>> from zope.formlib.interfaces import IInputWidget + >>> from zope.formlib.widgets import TextWidget >>> from zope.app.testing.setup import placefulSetUp, placefulTearDown >>> site = placefulSetUp(True) >>> provideAdapter(TextWidget, (ITextLine, IDefaultBrowserLayer), @@ -46,7 +46,7 @@ then we can get a view:: >>> from zope.app.authentication.browser.schemasearch \ - ... import QuerySchemaSearchView + ... import QuerySchemaSearchView >>> from zope.publisher.browser import TestRequest >>> request = TestRequest() >>> view = QuerySchemaSearchView(MySearchPlugin(), request) diff -Nru zope.app.authentication-3.8.0/src/zope/app/authentication/configure.zcml zope.app.authentication-3.9/src/zope/app/authentication/configure.zcml --- zope.app.authentication-3.8.0/src/zope/app/authentication/configure.zcml 2010-09-25 09:07:17.000000000 +0000 +++ zope.app.authentication-3.9/src/zope/app/authentication/configure.zcml 2010-10-18 09:51:14.000000000 +0000 @@ -56,8 +56,10 @@ + + diff -Nru zope.app.authentication-3.8.0/src/zope/app/authentication/groupfolder.py zope.app.authentication-3.9/src/zope/app/authentication/groupfolder.py --- zope.app.authentication-3.8.0/src/zope/app/authentication/groupfolder.py 2010-09-25 09:07:17.000000000 +0000 +++ zope.app.authentication-3.9/src/zope/app/authentication/groupfolder.py 2010-10-18 09:51:14.000000000 +0000 @@ -13,351 +13,24 @@ ############################################################################## """Zope Groups Folder implementation -$Id: groupfolder.py 113109 2010-06-04 11:54:46Z janwijbrand $ +$Id: groupfolder.py 117633 2010-10-18 09:40:31Z janwijbrand $ """ -import BTrees.OOBTree -import persistent - -from zope import interface, event, schema, component -from zope.interface import alsoProvides -from zope.security.interfaces import ( - IGroup, IGroupAwarePrincipal, IMemberAwareGroup) - -from zope.container.btree import BTreeContainer -import zope.container.constraints -import zope.container.interfaces -from zope.app.authentication.i18n import ZopeMessageFactory as _ -import zope.authentication.principal -from zope.authentication.interfaces import ( - IAuthentication, IAuthenticatedGroup, IEveryoneGroup) -from zope.app.authentication import principalfolder, interfaces - - -class IGroupInformation(interface.Interface): - - title = schema.TextLine( - title=_("Title"), - description=_("Provides a title for the permission."), - required=True) - - description = schema.Text( - title=_("Description"), - description=_("Provides a description for the permission."), - required=False) - - principals = schema.List( - title=_("Principals"), - value_type=schema.Choice( - source=zope.authentication.principal.PrincipalSource()), - description=_( - "List of ids of principals which belong to the group"), - required=False) - - -class IGroupFolder(zope.container.interfaces.IContainer): - - zope.container.constraints.contains(IGroupInformation) - - prefix = schema.TextLine( - title=_("Group ID prefix"), - description=_("Prefix added to IDs of groups in this folder"), - readonly=True, - ) - - def getGroupsForPrincipal(principalid): - """Get groups the given principal belongs to""" - - def getPrincipalsForGroup(groupid): - """Get principals which belong to the group""" - - -class IGroupContained(zope.container.interfaces.IContained): - - zope.container.constraints.containers(IGroupFolder) - -class IGroupSearchCriteria(interface.Interface): - - search = schema.TextLine( - title=_("Group Search String"), - required=False, - missing_value=u'', - ) - -class IGroupPrincipalInfo(interfaces.IPrincipalInfo): - members = interface.Attribute('an iterable of members of the group') - -class GroupInfo(object): - """An implementation of IPrincipalInfo used by the group folder. - - A group info is created with id, title, and description: - - >>> class DemoGroupInformation(object): - ... interface.implements(IGroupInformation) - ... def __init__(self, title, description, principals): - ... self.title = title - ... self.description = description - ... self.principals = principals - ... - >>> i = DemoGroupInformation( - ... 'Managers', 'Taskmasters', ('joe', 'jane')) - ... - >>> info = GroupInfo('groups.managers', i) - >>> info - GroupInfo('groups.managers') - >>> info.id - 'groups.managers' - >>> info.title - 'Managers' - >>> info.description - 'Taskmasters' - >>> info.members - ('joe', 'jane') - >>> info.members = ('joe', 'jane', 'jaime') - >>> info.members - ('joe', 'jane', 'jaime') - - """ - interface.implements(IGroupPrincipalInfo) - - def __init__(self, id, information): - self.id = id - self._information = information - - @property - def title(self): - return self._information.title - - @property - def description(self): - return self._information.description - - @apply - def members(): - def get(self): - return self._information.principals - def set(self, value): - self._information.principals = value - return property(get, set) - - def __repr__(self): - return 'GroupInfo(%r)' % self.id - - -class GroupFolder(BTreeContainer): - - interface.implements( - interfaces.IAuthenticatorPlugin, - interfaces.IQuerySchemaSearch, - IGroupFolder) - - schema = IGroupSearchCriteria - - def __init__(self, prefix=u''): - super(GroupFolder, self).__init__() - self.prefix = prefix - # __inversemapping is used to map principals to groups - self.__inverseMapping = BTrees.OOBTree.OOBTree() - - def __setitem__(self, name, value): - BTreeContainer.__setitem__(self, name, value) - group_id = self._groupid(value) - self._addPrincipalsToGroup(value.principals, group_id) - if value.principals: - event.notify( - interfaces.PrincipalsAddedToGroup( - value.principals, self.__parent__.prefix + group_id)) - group = principalfolder.Principal(self.prefix + name) - event.notify(interfaces.GroupAdded(group)) - - def __delitem__(self, name): - value = self[name] - group_id = self._groupid(value) - self._removePrincipalsFromGroup(value.principals, group_id) - if value.principals: - event.notify( - interfaces.PrincipalsRemovedFromGroup( - value.principals, self.__parent__.prefix + group_id)) - BTreeContainer.__delitem__(self, name) - - def _groupid(self, group): - return self.prefix+group.__name__ - - def _addPrincipalsToGroup(self, principal_ids, group_id): - for principal_id in principal_ids: - self.__inverseMapping[principal_id] = ( - self.__inverseMapping.get(principal_id, ()) - + (group_id,)) - - def _removePrincipalsFromGroup(self, principal_ids, group_id): - for principal_id in principal_ids: - groups = self.__inverseMapping.get(principal_id) - if groups is None: - return - new = tuple([id for id in groups if id != group_id]) - if new: - self.__inverseMapping[principal_id] = new - else: - del self.__inverseMapping[principal_id] - - def getGroupsForPrincipal(self, principalid): - """Get groups the given principal belongs to""" - return self.__inverseMapping.get(principalid, ()) - - def getPrincipalsForGroup(self, groupid): - """Get principals which belong to the group""" - return self[groupid].principals - - def search(self, query, start=None, batch_size=None): - """ Search for groups""" - search = query.get('search') - if search is not None: - n = 0 - search = search.lower() - for i, (id, groupinfo) in enumerate(self.items()): - if (search in groupinfo.title.lower() or - (groupinfo.description and - search in groupinfo.description.lower())): - if not ((start is not None and i < start) - or - (batch_size is not None and n >= batch_size)): - n += 1 - yield self.prefix + id - - def authenticateCredentials(self, credentials): - # user folders don't authenticate - pass - - def principalInfo(self, id): - if id.startswith(self.prefix): - id = id[len(self.prefix):] - info = self.get(id) - if info is not None: - return GroupInfo( - self.prefix+id, info) - -class GroupCycle(Exception): - """There is a cyclic relationship among groups - """ - -class InvalidPrincipalIds(Exception): - """A user has a group id for a group that can't be found - """ - -class InvalidGroupId(Exception): - """A user has a group id for a group that can't be found - """ - -def nocycles(principal_ids, seen, getPrincipal): - for principal_id in principal_ids: - if principal_id in seen: - raise GroupCycle(principal_id, seen) - seen.append(principal_id) - principal = getPrincipal(principal_id) - nocycles(principal.groups, seen, getPrincipal) - seen.pop() - -class GroupInformation(persistent.Persistent): - - interface.implements(IGroupInformation, IGroupContained) - - __parent__ = __name__ = None - - _principals = () - - def __init__(self, title='', description=''): - self.title = title - self.description = description - - def setPrincipals(self, prinlist, check=True): - # method is not a part of the interface - parent = self.__parent__ - old = self._principals - self._principals = tuple(prinlist) - - if parent is not None: - oldset = set(old) - new = set(prinlist) - group_id = parent._groupid(self) - removed = oldset - new - added = new - oldset - try: - parent._removePrincipalsFromGroup(removed, group_id) - except AttributeError: - removed = None - - try: - parent._addPrincipalsToGroup(added, group_id) - except AttributeError: - added = None - - if check: - try: - principalsUtility = component.getUtility(IAuthentication) - nocycles(new, [], principalsUtility.getPrincipal) - except GroupCycle: - # abort - self.setPrincipals(old, False) - raise - # now that we've gotten past the checks, fire the events. - if removed: - event.notify( - interfaces.PrincipalsRemovedFromGroup( - removed, self.__parent__.__parent__.prefix + group_id)) - if added: - event.notify( - interfaces.PrincipalsAddedToGroup( - added, self.__parent__.__parent__.prefix + group_id)) - - principals = property(lambda self: self._principals, setPrincipals) - - -def specialGroups(event): - principal = event.principal - if (IGroup.providedBy(principal) or - not IGroupAwarePrincipal.providedBy(principal)): - return - - everyone = component.queryUtility(IEveryoneGroup) - if everyone is not None: - principal.groups.append(everyone.id) - - auth = component.queryUtility(IAuthenticatedGroup) - if auth is not None: - principal.groups.append(auth.id) - - -def setGroupsForPrincipal(event): - """Set group information when a principal is created""" - - principal = event.principal - if not IGroupAwarePrincipal.providedBy(principal): - return - - authentication = event.authentication - - for name, plugin in authentication.getAuthenticatorPlugins(): - if not IGroupFolder.providedBy(plugin): - continue - groupfolder = plugin - principal.groups.extend( - [authentication.prefix + id - for id in groupfolder.getGroupsForPrincipal(principal.id) - ]) - id = principal.id - prefix = authentication.prefix + groupfolder.prefix - if id.startswith(prefix) and id[len(prefix):] in groupfolder: - alsoProvides(principal, IGroup) - -@component.adapter(interfaces.IFoundPrincipalCreated) -def setMemberSubscriber(event): - """adds `getMembers`, `setMembers` to groups made from IGroupPrincipalInfo. - """ - info = event.info - if IGroupPrincipalInfo.providedBy(info): - principal = event.principal - principal.getMembers = lambda : info.members - def setMembers(value): - info.members = value - principal.setMembers = setMembers - alsoProvides(principal, IMemberAwareGroup) +# BBB using zope.pluggableauth.plugin.groupfolder +from zope.pluggableauth.plugins.groupfolder import ( + GroupCycle, + GroupFolder, + GroupInfo, + GroupInformation, + IGroupContained, + IGroupFolder, + IGroupInformation, + IGroupPrincipalInfo, + IGroupSearchCriteria, + InvalidGroupId, + InvalidPrincipalIds, + nocycles, + setGroupsForPrincipal, + setMemberSubscriber, + specialGroups, + ) diff -Nru zope.app.authentication-3.8.0/src/zope/app/authentication/groupfolder.zcml zope.app.authentication-3.9/src/zope/app/authentication/groupfolder.zcml --- zope.app.authentication-3.8.0/src/zope/app/authentication/groupfolder.zcml 2010-09-25 09:07:17.000000000 +0000 +++ zope.app.authentication-3.9/src/zope/app/authentication/groupfolder.zcml 2010-10-18 09:51:14.000000000 +0000 @@ -1,65 +1,47 @@ + xmlns="http://namespaces.zope.org/zope" + i18n_domain="zope" + > + + + interface="zope.annotation.interfaces.IAttributeAnnotatable" + /> + permission="zope.ManageServices" + interface=".groupfolder.IGroupInformation + .groupfolder.IGroupContained" + set_schema=".groupfolder.IGroupInformation" + /> + interface=".groupfolder.IGroupFolder" + /> + permission="zope.ManageServices" + interface="zope.container.interfaces.IContainer + zope.container.interfaces.INameChooser" + /> - - - - - - - - + xmlns:apidoc="http://namespaces.zope.org/apidoc" + xmlns:zcml="http://namespaces.zope.org/zcml" + zcml:condition="have apidoc"> + id="groupfolder" + title="Group Folders" + doc_path="groupfolder.txt" + parent="security/authentication" + /> diff -Nru zope.app.authentication-3.8.0/src/zope/app/authentication/idpicker.py zope.app.authentication-3.9/src/zope/app/authentication/idpicker.py --- zope.app.authentication-3.8.0/src/zope/app/authentication/idpicker.py 2010-09-25 09:07:17.000000000 +0000 +++ zope.app.authentication-3.9/src/zope/app/authentication/idpicker.py 2010-10-18 09:51:14.000000000 +0000 @@ -14,97 +14,9 @@ ############################################################################## """Helper base class that picks principal ids -$Id: idpicker.py 116325 2010-09-12 13:54:36Z hannosch $ +$Id: idpicker.py 117633 2010-10-18 09:40:31Z janwijbrand $ """ __docformat__ = 'restructuredtext' -import re -from zope.exceptions.interfaces import UserError -from zope.container.contained import NameChooser -from zope.app.authentication.i18n import ZopeMessageFactory as _ - -ok = re.compile('[!-~]+$').match -class IdPicker(NameChooser): - """Helper base class that picks principal ids. - - Add numbers to ids given by users to make them unique. - - The Id picker is a variation on the name chooser that picks numeric - ids when no name is given. - - >>> from zope.app.authentication.idpicker import IdPicker - >>> IdPicker({}).chooseName('', None) - u'1' - - >>> IdPicker({'1': 1}).chooseName('', None) - u'2' - - >>> IdPicker({'2': 1}).chooseName('', None) - u'1' - - >>> IdPicker({'1': 1}).chooseName('bob', None) - u'bob' - - >>> IdPicker({'bob': 1}).chooseName('bob', None) - u'bob1' - - """ - def chooseName(self, name, object): - i = 0 - name = unicode(name) - orig = name - while (not name) or (name in self.context): - i += 1 - name = orig+str(i) - - self.checkName(name, object) - return name - - def checkName(self, name, object): - """Limit ids - - Ids can only contain printable, non-space, 7-bit ASCII strings: - - >>> from zope.app.authentication.idpicker import IdPicker - >>> IdPicker({}).checkName(u'1', None) - True - - >>> IdPicker({}).checkName(u'bob', None) - True - - >>> try: - ... IdPicker({}).checkName(u'bob\xfa', None) - ... except UserError, e: - ... print e - ... # doctest: +NORMALIZE_WHITESPACE - Ids must contain only printable 7-bit non-space ASCII characters - - >>> try: - ... IdPicker({}).checkName(u'big bob', None) - ... except UserError, e: - ... print e - ... # doctest: +NORMALIZE_WHITESPACE - Ids must contain only printable 7-bit non-space ASCII characters - - Ids also can't be over 100 characters long: - - >>> IdPicker({}).checkName(u'x' * 100, None) - True - - >>> IdPicker({}).checkName(u'x' * 101, None) - Traceback (most recent call last): - ... - UserError: Ids can't be more than 100 characters long. - - """ - NameChooser.checkName(self, name, object) - if not ok(name): - raise UserError( - _("Ids must contain only printable 7-bit non-space" - " ASCII characters") - ) - if len(name) > 100: - raise UserError( - _("Ids can't be more than 100 characters long.") - ) - return True +# BBB using zope.pluggableauth.plugins.idpicker +from zope.pluggableauth.plugins.idpicker import IdPicker diff -Nru zope.app.authentication-3.8.0/src/zope/app/authentication/interfaces.py zope.app.authentication-3.9/src/zope/app/authentication/interfaces.py --- zope.app.authentication-3.8.0/src/zope/app/authentication/interfaces.py 2010-09-25 09:07:17.000000000 +0000 +++ zope.app.authentication-3.9/src/zope/app/authentication/interfaces.py 2010-10-18 09:51:14.000000000 +0000 @@ -13,7 +13,7 @@ ############################################################################## """Pluggable Authentication Utility Interfaces -$Id: interfaces.py 113109 2010-06-04 11:54:46Z janwijbrand $ +$Id: interfaces.py 117633 2010-10-18 09:40:31Z janwijbrand $ """ __docformat__ = "reStructuredText" @@ -27,108 +27,28 @@ # BBB: using zope.pluggableauth from zope.pluggableauth.interfaces import ( - IPlugin, IAuthenticatorPlugin, ICredentialsPlugin, - IPluggableAuthentication, IPrincipalInfo, - IFoundPrincipalFactory, IPrincipalFactory, IAuthenticatedPrincipalFactory, - IPrincipalCreated, IAuthenticatedPrincipalCreated, - AuthenticatedPrincipalCreated, IFoundPrincipalCreated, - FoundPrincipalCreated, IQueriableAuthenticator) + AuthenticatedPrincipalCreated, + FoundPrincipalCreated, + IAuthenticatedPrincipalCreated, + IAuthenticatedPrincipalFactory, + IAuthenticatorPlugin, + ICredentialsPlugin, + IFoundPrincipalCreated, + IFoundPrincipalFactory, + IGroupAdded, + IPluggableAuthentication, + IPlugin, + IPrincipal, + IPrincipalCreated, + IPrincipalFactory, + IPrincipalInfo, + IPrincipalsAddedToGroup, + IPrincipalsRemovedFromGroup, + IQueriableAuthenticator, + IQuerySchemaSearch, + ) - -class IPrincipal(zope.security.interfaces.IGroupClosureAwarePrincipal): - - groups = zope.schema.List( - title=_("Groups"), - description=_( - """ids of groups to which the principal directly belongs. - - Plugins may append to this list. Mutating the list only affects - the life of the principal object, and does not persist (so - persistently adding groups to a principal should be done by working - with a plugin that mutates this list every time the principal is - created, like the group folder in this package.) - """), - value_type=zope.schema.TextLine(), - required=False) - - -class IQuerySchemaSearch(zope.interface.Interface): - """An interface for searching using schema-constrained input.""" - - schema = zope.interface.Attribute(""" - The schema that constrains the input provided to the search method. - - A mapping of name/value pairs for each field in this schema is used - as the query argument in the search method. - """) - - def search(query, start=None, batch_size=None): - """Returns an iteration of principal IDs matching the query. - - query is a mapping of name/value pairs for fields specified by the - schema. - - If the start argument is provided, then it should be an - integer and the given number of initial items should be - skipped. - - If the batch_size argument is provided, then it should be an - integer and no more than the given number of items should be - returned. - """ - - -class IGroupAdded(zope.interface.Interface): - """A group has been added.""" - - group = zope.interface.Attribute("""The group that was defined""") - - -class GroupAdded: - """ - >>> from zope.interface.verify import verifyObject - >>> event = GroupAdded("group") - >>> verifyObject(IGroupAdded, event) - True - """ - - zope.interface.implements(IGroupAdded) - - def __init__(self, group): - self.group = group - - def __repr__(self): - return "" % self.group.id - - -class IPrincipalsAddedToGroup(zope.interface.Interface): - group_id = zope.interface.Attribute( - 'the id of the group to which the principal was added') - principal_ids = zope.interface.Attribute( - 'an iterable of one or more ids of principals added') - - -class IPrincipalsRemovedFromGroup(zope.interface.Interface): - group_id = zope.interface.Attribute( - 'the id of the group from which the principal was removed') - principal_ids = zope.interface.Attribute( - 'an iterable of one or more ids of principals removed') - - -class AbstractMembersChanged(object): - - def __init__(self, principal_ids, group_id): - self.principal_ids = principal_ids - self.group_id = group_id - - def __repr__(self): - return "<%s %r %r>" % ( - self.__class__.__name__, sorted(self.principal_ids), self.group_id) - - -class PrincipalsAddedToGroup(AbstractMembersChanged): - zope.interface.implements(IPrincipalsAddedToGroup) - - -class PrincipalsRemovedFromGroup(AbstractMembersChanged): - zope.interface.implements(IPrincipalsRemovedFromGroup) +# BBB: using zope.pluggableauth +from zope.pluggableauth.plugins.groupfolder import ( + GroupAdded, + ) diff -Nru zope.app.authentication-3.8.0/src/zope/app/authentication/principalfolder.py zope.app.authentication-3.9/src/zope/app/authentication/principalfolder.py --- zope.app.authentication-3.8.0/src/zope/app/authentication/principalfolder.py 2010-09-25 09:07:17.000000000 +0000 +++ zope.app.authentication-3.9/src/zope/app/authentication/principalfolder.py 2010-10-18 09:51:14.000000000 +0000 @@ -13,274 +13,24 @@ ############################################################################## """ZODB-based Authentication Source -$Id: principalfolder.py 113109 2010-06-04 11:54:46Z janwijbrand $ +$Id: principalfolder.py 117633 2010-10-18 09:40:31Z janwijbrand $ """ __docformat__ = "reStructuredText" -from persistent import Persistent -from zope.app.authentication.i18n import ZopeMessageFactory as _ -from zope.app.authentication.interfaces import IQuerySchemaSearch -from zope.component import getUtility -from zope.container.btree import BTreeContainer -from zope.container.constraints import contains, containers -from zope.container.contained import Contained -from zope.container.interfaces import DuplicateIDError -from zope.interface import implements, Interface -from zope.password.interfaces import IPasswordManager -from zope.schema import Text, TextLine, Password, Choice - # BBB using zope.pluggableauth from zope.pluggableauth.interfaces import IAuthenticatorPlugin from zope.pluggableauth.factories import ( - Principal, PrincipalInfo, - FoundPrincipalFactory, AuthenticatedPrincipalFactory) - - -class IInternalPrincipal(Interface): - """Principal information""" - - login = TextLine( - title=_("Login"), - description=_("The Login/Username of the principal. " - "This value can change.")) - - def setPassword(password, passwordManagerName=None): - pass - - password = Password( - title=_("Password"), - description=_("The password for the principal.")) - - passwordManagerName = Choice( - title=_("Password Manager"), - vocabulary="Password Manager Names", - description=_("The password manager will be used" - " for encode/check the password"), - default="SSHA", - # TODO: The password manager name may be changed only - # if the password changed - readonly=True - ) - - title = TextLine( - title=_("Title"), - description=_("Provides a title for the principal.")) - - description = Text( - title=_("Description"), - description=_("Provides a description for the principal."), - required=False, - missing_value='', - default=u'') - - -class IInternalPrincipalContainer(Interface): - """A container that contains internal principals.""" - - prefix = TextLine( - title=_("Prefix"), - description=_( - "Prefix to be added to all principal ids to assure " - "that all ids are unique within the authentication service"), - missing_value=u"", - default=u'', - readonly=True) - - def getIdByLogin(login): - """Return the principal id currently associated with login. - - The return value includes the container prefix, but does not - include the PAU prefix. - - KeyError is raised if no principal is associated with login. - - """ - - contains(IInternalPrincipal) - - -class IInternalPrincipalContained(Interface): - """Principal information""" - - containers(IInternalPrincipalContainer) - - -class ISearchSchema(Interface): - """Search Interface for this Principal Provider""" - - search = TextLine( - title=_("Search String"), - description=_("A Search String"), - required=False, - default=u'', - missing_value=u'') - - -class InternalPrincipal(Persistent, Contained): - """An internal principal for Persistent Principal Folder.""" - - implements(IInternalPrincipal, IInternalPrincipalContained) - - # If you're searching for self._passwordManagerName, or self._password - # probably you just need to evolve the database to new generation - # at /++etc++process/@@generations.html - - # NOTE: All changes needs to be synchronized with the evolver at - # zope.app.zopeappgenerations.evolve2 - - def __init__(self, login, password, title, description=u'', - passwordManagerName="SSHA"): - self._login = login - self._passwordManagerName = passwordManagerName - self.password = password - self.title = title - self.description = description - - def getPasswordManagerName(self): - return self._passwordManagerName - - passwordManagerName = property(getPasswordManagerName) - - def _getPasswordManager(self): - return getUtility(IPasswordManager, self.passwordManagerName) - - def getPassword(self): - return self._password - - def setPassword(self, password, passwordManagerName=None): - if passwordManagerName is not None: - self._passwordManagerName = passwordManagerName - passwordManager = self._getPasswordManager() - self._password = passwordManager.encodePassword(password) - - password = property(getPassword, setPassword) - - def checkPassword(self, password): - passwordManager = self._getPasswordManager() - return passwordManager.checkPassword(self.password, password) - - def getLogin(self): - return self._login - - def setLogin(self, login): - oldLogin = self._login - self._login = login - if self.__parent__ is not None: - try: - self.__parent__.notifyLoginChanged(oldLogin, self) - except ValueError: - self._login = oldLogin - raise - - login = property(getLogin, setLogin) - - -class PrincipalFolder(BTreeContainer): - """A Persistent Principal Folder and Authentication plugin. - - See principalfolder.txt for details. - """ - - implements(IAuthenticatorPlugin, - IQuerySchemaSearch, - IInternalPrincipalContainer) - - schema = ISearchSchema - - def __init__(self, prefix=''): - self.prefix = unicode(prefix) - super(PrincipalFolder, self).__init__() - self.__id_by_login = self._newContainerData() - - def notifyLoginChanged(self, oldLogin, principal): - """Notify the Container about changed login of a principal. - - We need this, so that our second tree can be kept up-to-date. - """ - # A user with the new login already exists - if principal.login in self.__id_by_login: - raise ValueError('Principal Login already taken!') - - del self.__id_by_login[oldLogin] - self.__id_by_login[principal.login] = principal.__name__ - - def __setitem__(self, id, principal): - """Add principal information. - - Create a Principal Folder - - >>> pf = PrincipalFolder() - - Create a principal with 1 as id - Add a login attr since __setitem__ is in need of one - - >>> principal = Principal(1) - >>> principal.login = 1 - - Add the principal within the Principal Folder - - >>> pf.__setitem__(u'1', principal) - - Try to add another principal with the same id. - It should raise a DuplicateIDError - - >>> try: - ... pf.__setitem__(u'1', principal) - ... except DuplicateIDError, e: - ... pass - >>> - """ - # A user with the new login already exists - if principal.login in self.__id_by_login: - raise DuplicateIDError('Principal Login already taken!') - - super(PrincipalFolder, self).__setitem__(id, principal) - self.__id_by_login[principal.login] = id - - def __delitem__(self, id): - """Remove principal information.""" - principal = self[id] - super(PrincipalFolder, self).__delitem__(id) - del self.__id_by_login[principal.login] - - def authenticateCredentials(self, credentials): - """Return principal info if credentials can be authenticated - """ - if not isinstance(credentials, dict): - return None - if not ('login' in credentials and 'password' in credentials): - return None - id = self.__id_by_login.get(credentials['login']) - if id is None: - return None - internal = self[id] - if not internal.checkPassword(credentials["password"]): - return None - return PrincipalInfo(self.prefix + id, internal.login, internal.title, - internal.description) - - def principalInfo(self, id): - if id.startswith(self.prefix): - internal = self.get(id[len(self.prefix):]) - if internal is not None: - return PrincipalInfo(id, internal.login, internal.title, - internal.description) - - def getIdByLogin(self, login): - return self.prefix + self.__id_by_login[login] - - def search(self, query, start=None, batch_size=None): - """Search through this principal provider.""" - search = query.get('search') - if search is None: - return - search = search.lower() - n = 1 - for i, value in enumerate(self.values()): - if (search in value.title.lower() or - search in value.description.lower() or - search in value.login.lower()): - if not ((start is not None and i < start) - or (batch_size is not None and n > batch_size)): - n += 1 - yield self.prefix + value.__name__ + AuthenticatedPrincipalFactory, + FoundPrincipalFactory, + Principal, + PrincipalInfo, + ) +# BBB using zope.pluggableauth.plugins.principalfolder +from zope.pluggableauth.plugins.principalfolder import ( + IInternalPrincipal, + IInternalPrincipalContained, + IInternalPrincipalContainer, + InternalPrincipal, + ISearchSchema, + PrincipalFolder, + ) diff -Nru zope.app.authentication-3.8.0/src/zope/app/authentication/principalfolder.zcml zope.app.authentication-3.9/src/zope/app/authentication/principalfolder.zcml --- zope.app.authentication-3.8.0/src/zope/app/authentication/principalfolder.zcml 2010-09-25 09:07:17.000000000 +0000 +++ zope.app.authentication-3.9/src/zope/app/authentication/principalfolder.zcml 2010-10-18 09:51:14.000000000 +0000 @@ -1,39 +1,29 @@ - + + permission="zope.ManageServices" + interface=".principalfolder.IInternalPrincipal" + set_schema=".principalfolder.IInternalPrincipal" + /> - - + interface="zope.annotation.interfaces.IAttributeAnnotatable" + /> - + permission="zope.ManageServices" + interface="zope.container.interfaces.IContainer" + /> - + permission="zope.ManageServices" + attributes="prefix" + /> - - diff -Nru zope.app.authentication-3.8.0/src/zope.app.authentication.egg-info/PKG-INFO zope.app.authentication-3.9/src/zope.app.authentication.egg-info/PKG-INFO --- zope.app.authentication-3.8.0/src/zope.app.authentication.egg-info/PKG-INFO 2010-09-25 09:07:21.000000000 +0000 +++ zope.app.authentication-3.9/src/zope.app.authentication.egg-info/PKG-INFO 2010-10-18 09:51:16.000000000 +0000 @@ -1,6 +1,6 @@ Metadata-Version: 1.0 Name: zope.app.authentication -Version: 3.8.0 +Version: 3.9 Summary: Principals and groups management for the pluggable authentication utility Home-page: http://pypi.python.org/pypi/zope.app.authentication Author: Zope Corporation and Contributors @@ -30,9 +30,9 @@ The primary job of PAU is to authenticate principals. It uses two types of plug-ins in its work: - - Credentials Plugins + - Credentials Plugins - - Authenticator Plugins + - Authenticator Plugins Credentials plugins are responsible for extracting user credentials from a request. A credentials plugin may in some cases issue a 'challenge' to obtain @@ -61,26 +61,26 @@ To illustrate, we'll create a simple credentials plugin:: - >>> from zope import interface - >>> from zope.app.authentication import interfaces + >>> from zope import interface + >>> from zope.app.authentication import interfaces - >>> class MyCredentialsPlugin(object): - ... - ... interface.implements(interfaces.ICredentialsPlugin) - ... - ... 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 + >>> class MyCredentialsPlugin(object): + ... + ... interface.implements(interfaces.ICredentialsPlugin) + ... + ... 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 As a plugin, MyCredentialsPlugin needs to be registered as a named utility:: - >>> myCredentialsPlugin = MyCredentialsPlugin() - >>> provideUtility(myCredentialsPlugin, name='My Credentials Plugin') + >>> myCredentialsPlugin = MyCredentialsPlugin() + >>> provideUtility(myCredentialsPlugin, name='My Credentials Plugin') Simple Authenticator Plugin ~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -88,36 +88,36 @@ Next we'll create a simple authenticator plugin. For our plugin, we'll need an implementation of IPrincipalInfo:: - >>> class PrincipalInfo(object): - ... - ... interface.implements(interfaces.IPrincipalInfo) - ... - ... def __init__(self, id, title, description): - ... self.id = id - ... self.title = title - ... self.description = description - ... - ... def __repr__(self): - ... return 'PrincipalInfo(%r)' % self.id + >>> class PrincipalInfo(object): + ... + ... interface.implements(interfaces.IPrincipalInfo) + ... + ... def __init__(self, id, title, description): + ... self.id = id + ... self.title = title + ... self.description = description + ... + ... def __repr__(self): + ... return 'PrincipalInfo(%r)' % self.id Our authenticator uses this type when it creates a principal info:: - >>> class MyAuthenticatorPlugin(object): - ... - ... interface.implements(interfaces.IAuthenticatorPlugin) - ... - ... def authenticateCredentials(self, credentials): - ... if credentials == 'secretcode': - ... return PrincipalInfo('bob', 'Bob', '') - ... - ... def principalInfo(self, id): - ... pass # plugin not currently supporting search + >>> class MyAuthenticatorPlugin(object): + ... + ... interface.implements(interfaces.IAuthenticatorPlugin) + ... + ... def authenticateCredentials(self, credentials): + ... if credentials == 'secretcode': + ... return PrincipalInfo('bob', 'Bob', '') + ... + ... def principalInfo(self, id): + ... pass # plugin not currently supporting search As with the credentials plugin, the authenticator plugin must be registered as a named utility:: - >>> myAuthenticatorPlugin = MyAuthenticatorPlugin() - >>> provideUtility(myAuthenticatorPlugin, name='My Authenticator Plugin') + >>> myAuthenticatorPlugin = MyAuthenticatorPlugin() + >>> provideUtility(myAuthenticatorPlugin, name='My Authenticator Plugin') Principal Factories ~~~~~~~~~~~~~~~~~~~ @@ -126,9 +126,9 @@ for creating principals. This function is performed by factory adapters. For these tests we'll borrow some factories from the principal folder:: - >>> from zope.app.authentication import principalfolder - >>> provideAdapter(principalfolder.AuthenticatedPrincipalFactory) - >>> provideAdapter(principalfolder.FoundPrincipalFactory) + >>> from zope.app.authentication import principalfolder + >>> provideAdapter(principalfolder.AuthenticatedPrincipalFactory) + >>> provideAdapter(principalfolder.FoundPrincipalFactory) For more information on these factories, see their docstrings. @@ -137,35 +137,35 @@ Finally, we'll create the PAU itself:: - >>> from zope.app import authentication - >>> pau = authentication.PluggableAuthentication('xyz_') + >>> from zope.app import authentication + >>> pau = authentication.PluggableAuthentication('xyz_') and configure it with the two plugins:: - >>> pau.credentialsPlugins = ('My Credentials Plugin', ) - >>> pau.authenticatorPlugins = ('My Authenticator Plugin', ) + >>> pau.credentialsPlugins = ('My Credentials Plugin', ) + >>> pau.authenticatorPlugins = ('My Authenticator Plugin', ) Using the PAU to Authenticate ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ We can now use the PAU to authenticate a sample request:: - >>> from zope.publisher.browser import TestRequest - >>> print pau.authenticate(TestRequest()) - None + >>> 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:: - >>> print pau.authenticate(TestRequest(credentials='let me in!')) - None + >>> print pau.authenticate(TestRequest(credentials='let me in!')) + None However, if we provide the proper credentials:: - >>> request = TestRequest(credentials='secretcode') - >>> principal = pau.authenticate(request) - >>> principal - Principal('xyz_bob') + >>> request = TestRequest(credentials='secretcode') + >>> principal = pau.authenticate(request) + >>> principal + Principal('xyz_bob') we get an authenticated principal. @@ -174,24 +174,24 @@ We can verify that the appropriate event was published:: - >>> [event] = getEvents(interfaces.IAuthenticatedPrincipalCreated) - >>> event.principal is principal - True - >>> event.info - PrincipalInfo('bob') - >>> event.request is request - True + >>> [event] = getEvents(interfaces.IAuthenticatedPrincipalCreated) + >>> event.principal is principal + True + >>> event.info + PrincipalInfo('bob') + >>> event.request is request + True 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:: - >>> event.info.title - 'Bob' - >>> event.info.id # does not include pau prefix - 'bob' - >>> event.info.description - '' + >>> event.info.title + 'Bob' + >>> event.info.id # does not include pau prefix + 'bob' + >>> event.info.description + '' It is also decorated with two other attributes, credentialsPlugin and authenticatorPlugin: these are the plugins used to extract credentials for and @@ -200,24 +200,24 @@ determine that a given credential plugin does or does not support logout, and provide information usable to show or hide logout user interface:: - >>> event.info.credentialsPlugin is myCredentialsPlugin - True - >>> event.info.authenticatorPlugin is myAuthenticatorPlugin - True + >>> event.info.credentialsPlugin is myCredentialsPlugin + True + >>> event.info.authenticatorPlugin is myAuthenticatorPlugin + True Normally, we provide subscribers to these events that add additional information to the principal. For example, we'll add one that sets the title:: - >>> def add_info(event): - ... event.principal.title = event.info.title - >>> provideHandler(add_info, [interfaces.IAuthenticatedPrincipalCreated]) + >>> def add_info(event): + ... event.principal.title = event.info.title + >>> provideHandler(add_info, [interfaces.IAuthenticatedPrincipalCreated]) Now, if we authenticate a principal, its title is set:: - >>> principal = pau.authenticate(request) - >>> principal.title - 'Bob' + >>> principal = pau.authenticate(request) + >>> principal.title + 'Bob' Multiple Authenticator Plugins ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -228,47 +228,47 @@ To illustrate, we'll create another authenticator:: - >>> class MyAuthenticatorPlugin2(MyAuthenticatorPlugin): - ... - ... def authenticateCredentials(self, credentials): - ... if credentials == 'secretcode': - ... return PrincipalInfo('black', 'Black Spy', '') - ... elif credentials == 'hiddenkey': - ... return PrincipalInfo('white', 'White Spy', '') + >>> 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') + >>> provideUtility(MyAuthenticatorPlugin2(), name='My Authenticator Plugin 2') If we put it before the original authenticator:: - >>> pau.authenticatorPlugins = ( - ... 'My Authenticator Plugin 2', - ... 'My Authenticator Plugin') + >>> pau.authenticatorPlugins = ( + ... 'My Authenticator Plugin 2', + ... 'My Authenticator Plugin') Then it will be given the first opportunity to authenticate a request:: - >>> pau.authenticate(TestRequest(credentials='secretcode')) - Principal('xyz_black') + >>> pau.authenticate(TestRequest(credentials='secretcode')) + Principal('xyz_black') If neither plugins can authenticate, pau returns None:: - >>> print pau.authenticate(TestRequest(credentials='let me in!!')) - None + >>> print pau.authenticate(TestRequest(credentials='let me in!!')) + None When we change the order of the authenticator plugins:: - >>> pau.authenticatorPlugins = ( - ... 'My Authenticator Plugin', - ... 'My Authenticator Plugin 2') + >>> pau.authenticatorPlugins = ( + ... 'My Authenticator Plugin', + ... 'My Authenticator Plugin 2') we see that our original plugin is now acting first:: - >>> pau.authenticate(TestRequest(credentials='secretcode')) - Principal('xyz_bob') + >>> pau.authenticate(TestRequest(credentials='secretcode')) + Principal('xyz_bob') The second plugin, however, gets a chance to authenticate if first does not:: - >>> pau.authenticate(TestRequest(credentials='hiddenkey')) - Principal('xyz_white') + >>> pau.authenticate(TestRequest(credentials='hiddenkey')) + Principal('xyz_white') Multiple Credentials Plugins ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -277,96 +277,96 @@ illustrate, we'll create a credentials plugin that extracts credentials from a request form:: - >>> class FormCredentialsPlugin: - ... - ... interface.implements(interfaces.ICredentialsPlugin) - ... - ... def extractCredentials(self, request): - ... return request.form.get('my_credentials') - ... - ... def challenge(self, request): - ... pass - ... - ... def logout(request): - ... pass + >>> class FormCredentialsPlugin: + ... + ... interface.implements(interfaces.ICredentialsPlugin) + ... + ... 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') + >>> provideUtility(FormCredentialsPlugin(), + ... name='Form Credentials Plugin') and insert the new credentials plugin before the existing plugin:: - >>> pau.credentialsPlugins = ( - ... 'Form Credentials Plugin', - ... 'My Credentials 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:: - >>> pau.authenticate(TestRequest(credentials='secretcode', - ... form={'my_credentials': 'hiddenkey'})) - Principal('xyz_white') + >>> pau.authenticate(TestRequest(credentials='secretcode', + ... form={'my_credentials': 'hiddenkey'})) + Principal('xyz_white') In this case, the first credentials plugin succeeded in getting credentials from the form and the second authenticator was able to authenticate the credentials. Specifically, the PAU went through these steps: - - Get credentials using 'Form Credentials Plugin' + - Get credentials using 'Form Credentials Plugin' - - Got 'hiddenkey' credentials using 'Form Credentials Plugin', try to - authenticate using 'My Authenticator Plugin' + - Got 'hiddenkey' credentials using 'Form Credentials Plugin', try to + authenticate using 'My Authenticator Plugin' - - Failed to authenticate 'hiddenkey' with 'My Authenticator Plugin', try - 'My Authenticator Plugin 2' + - Failed to authenticate 'hiddenkey' with 'My Authenticator Plugin', try + 'My Authenticator Plugin 2' - - Succeeded in authenticating with 'My Authenticator Plugin 2' + - Succeeded in authenticating with 'My Authenticator Plugin 2' Let's try a different scenario:: - >>> pau.authenticate(TestRequest(credentials='secretcode')) - Principal('xyz_bob') + >>> pau.authenticate(TestRequest(credentials='secretcode')) + Principal('xyz_bob') In this case, the PAU went through these steps:: - - Get credentials using 'Form Credentials Plugin' + - Get credentials using 'Form Credentials Plugin' - - Failed to get credentials using 'Form Credentials Plugin', try - 'My 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' + - Got 'scecretcode' credentials using 'My Credentials Plugin', try to + authenticate using 'My Authenticator Plugin' - - Succeeded in authenticating with 'My Authenticator Plugin' + - Succeeded in authenticating with 'My Authenticator Plugin' Let's try a slightly more complex scenario:: - >>> pau.authenticate(TestRequest(credentials='hiddenkey', - ... form={'my_credentials': 'bogusvalue'})) - Principal('xyz_white') + >>> pau.authenticate(TestRequest(credentials='hiddenkey', + ... form={'my_credentials': 'bogusvalue'})) + Principal('xyz_white') This highlights PAU's ability to use multiple plugins for authentication: - - Get credentials using 'Form Credentials Plugin' + - Get credentials using 'Form Credentials Plugin' - - Got 'bogusvalue' credentials using 'Form Credentials Plugin', try to - authenticate using 'My Authenticator Plugin' + - Got 'bogusvalue' credentials using 'Form Credentials Plugin', try to + authenticate using 'My Authenticator Plugin' - - Failed to authenticate 'boguskey' with 'My Authenticator Plugin', try - 'My Authenticator Plugin 2' + - Failed to authenticate 'boguskey' with 'My Authenticator Plugin', try + 'My Authenticator Plugin 2' - - Failed to authenticate 'boguskey' with 'My Authenticator Plugin 2' -- - there are no more authenticators to try, so lets try the next credentials - plugin for some new credentials + - Failed to authenticate 'boguskey' with 'My Authenticator Plugin 2' -- + there are no more authenticators to try, so lets try the next credentials + plugin for some new credentials - - Get credentials using 'My Credentials Plugin' + - Get credentials using 'My Credentials Plugin' - - Got 'hiddenkey' credentials using 'My Credentials Plugin', try to - authenticate using 'My Authenticator Plugin' + - Got 'hiddenkey' credentials using 'My Credentials Plugin', try to + authenticate using 'My Authenticator Plugin' - - Failed to authenticate 'hiddenkey' using 'My Authenticator Plugin', try - 'My Authenticator Plugin 2' + - Failed to authenticate 'hiddenkey' using 'My Authenticator Plugin', try + 'My Authenticator Plugin 2' - - Succeeded in authenticating with 'My Authenticator Plugin 2' (shouts and - cheers!) + - Succeeded in authenticating with 'My Authenticator Plugin 2' (shouts and + cheers!) Principal Searching @@ -377,41 +377,41 @@ its authenticators. In our example, none of the authenticators implement this search capability, so when we look for a principal:: - >>> print pau.getPrincipal('xyz_bob') - Traceback (most recent call last): - PrincipalLookupError: bob - - >>> print pau.getPrincipal('white') - Traceback (most recent call last): - PrincipalLookupError: white - - >>> print pau.getPrincipal('black') - Traceback (most recent call last): - PrincipalLookupError: black + >>> print pau.getPrincipal('xyz_bob') + Traceback (most recent call last): + PrincipalLookupError: bob + + >>> print pau.getPrincipal('white') + Traceback (most recent call last): + PrincipalLookupError: white + + >>> print pau.getPrincipal('black') + Traceback (most recent call last): + PrincipalLookupError: black 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:: - >>> class SearchableAuthenticatorPlugin: - ... - ... interface.implements(interfaces.IAuthenticatorPlugin) - ... - ... 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 + >>> class SearchableAuthenticatorPlugin: + ... + ... interface.implements(interfaces.IAuthenticatorPlugin) + ... + ... 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 This class is typical of an authenticator plugin. It can both authenticate principals and find principals given a ID. While there are cases @@ -420,28 +420,28 @@ As with any plugin, we need to register it as a utility:: - >>> searchable = SearchableAuthenticatorPlugin() - >>> provideUtility(searchable, name='Searchable Authentication Plugin') + >>> searchable = SearchableAuthenticatorPlugin() + >>> provideUtility(searchable, name='Searchable Authentication Plugin') We'll now configure the PAU to use only the searchable authenticator:: - >>> pau.authenticatorPlugins = ('Searchable Authentication Plugin',) + >>> pau.authenticatorPlugins = ('Searchable Authentication Plugin',) and add some principals to the authenticator:: - >>> searchable.add('bob', 'Bob', 'A nice guy', 'b0b') - >>> searchable.add('white', 'White Spy', 'Sneaky', 'deathtoblack') + >>> 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:: - >>> pau.getPrincipal('xyz_bob') - Principal('xyz_bob') + >>> pau.getPrincipal('xyz_bob') + Principal('xyz_bob') but only those it knows about:: - >>> print pau.getPrincipal('black') - Traceback (most recent call last): - PrincipalLookupError: black + >>> print pau.getPrincipal('black') + Traceback (most recent call last): + PrincipalLookupError: black Found Principal Creates Events ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -450,30 +450,30 @@ a FoundPrincipalCreatedEvent is published when the authenticator finds a principal on behalf of PAU's 'getPrincipal':: - >>> clearEvents() - >>> principal = pau.getPrincipal('xyz_white') - >>> principal - Principal('xyz_white') - - >>> [event] = getEvents(interfaces.IFoundPrincipalCreated) - >>> event.principal is principal - True - >>> event.info - PrincipalInfo('white') + >>> clearEvents() + >>> principal = pau.getPrincipal('xyz_white') + >>> principal + Principal('xyz_white') + + >>> [event] = getEvents(interfaces.IFoundPrincipalCreated) + >>> event.principal is principal + True + >>> event.info + PrincipalInfo('white') The info has an authenticatorPlugin, but no credentialsPlugin, since none was used:: - >>> event.info.credentialsPlugin is None - True - >>> event.info.authenticatorPlugin is searchable - True + >>> event.info.credentialsPlugin is None + True + >>> event.info.authenticatorPlugin is searchable + True 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:: - >>> provideHandler(add_info, [interfaces.IFoundPrincipalCreated]) + >>> provideHandler(add_info, [interfaces.IFoundPrincipalCreated]) Now when a principal is created as a result of a search, it's title and description will be set (by the add_info handler function). @@ -487,46 +487,46 @@ To illustrate, we'll create and register a second searchable authenticator:: - >>> searchable2 = SearchableAuthenticatorPlugin() - >>> provideUtility(searchable2, name='Searchable Authentication Plugin 2') + >>> searchable2 = SearchableAuthenticatorPlugin() + >>> provideUtility(searchable2, name='Searchable Authentication Plugin 2') and add a principal to it:: - >>> searchable.add('black', 'Black Spy', 'Also sneaky', 'deathtowhite') + >>> searchable.add('black', 'Black Spy', 'Also sneaky', 'deathtowhite') When we configure the PAU to use both searchable authenticators (note the order):: - >>> pau.authenticatorPlugins = ( - ... 'Searchable Authentication Plugin 2', - ... 'Searchable Authentication Plugin') + >>> pau.authenticatorPlugins = ( + ... 'Searchable Authentication Plugin 2', + ... 'Searchable Authentication Plugin') we see how the PAU uses both plugins:: - >>> pau.getPrincipal('xyz_white') - Principal('xyz_white') + >>> pau.getPrincipal('xyz_white') + Principal('xyz_white') - >>> pau.getPrincipal('xyz_black') - Principal('xyz_black') + >>> pau.getPrincipal('xyz_black') + Principal('xyz_black') 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:: - >>> searchable2.add('white', 'White Rider', '', 'r1der') - >>> pau.getPrincipal('xyz_white').title - 'White Rider' + >>> searchable2.add('white', 'White Rider', '', 'r1der') + >>> pau.getPrincipal('xyz_white').title + 'White Rider' If we change the order of the plugins:: - >>> pau.authenticatorPlugins = ( - ... 'Searchable Authentication Plugin', - ... 'Searchable Authentication Plugin 2') + >>> pau.authenticatorPlugins = ( + ... 'Searchable Authentication Plugin', + ... 'Searchable Authentication Plugin 2') we get a different principal for ID 'white':: - >>> pau.getPrincipal('xyz_white').title - 'White Spy' + >>> pau.getPrincipal('xyz_white').title + 'White Spy' Issuing a Challenge @@ -536,14 +536,14 @@ credentials when its 'unauthorized' method is called. The need for this functionality is driven by the following use case: - - A user attempts to perform an operation he is not authorized to perform. + - A user attempts to perform an operation he is not authorized to perform. - - A handler responds to the unauthorized error by calling IAuthentication - 'unauthorized'. + - A handler responds to the unauthorized error by calling IAuthentication + 'unauthorized'. - - The authentication component (in our case, a PAU) issues a challenge to - the user to collect new credentials (typically in the form of logging in - as a new user). + - The authentication component (in our case, a PAU) issues a challenge to + the user to collect new credentials (typically in the form of logging in + as a new user). The PAU handles the credentials challenge by delegating to its credentials plugins. @@ -554,61 +554,61 @@ To illustrate challenges, we'll subclass an existing credentials plugin and do something in its 'challenge':: - >>> class LoginFormCredentialsPlugin(FormCredentialsPlugin): - ... - ... def __init__(self, loginForm): - ... self.loginForm = loginForm - ... - ... def challenge(self, request): - ... request.response.redirect(self.loginForm) - ... return True + >>> class LoginFormCredentialsPlugin(FormCredentialsPlugin): + ... + ... def __init__(self, loginForm): + ... self.loginForm = loginForm + ... + ... def challenge(self, request): + ... request.response.redirect(self.loginForm) + ... return True 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:: - >>> provideUtility(LoginFormCredentialsPlugin('simplelogin.html'), - ... name='Simple Login Form Plugin') + >>> provideUtility(LoginFormCredentialsPlugin('simplelogin.html'), + ... name='Simple Login Form Plugin') - >>> provideUtility(LoginFormCredentialsPlugin('advancedlogin.html'), - ... name='Advanced Login Form Plugin') + >>> provideUtility(LoginFormCredentialsPlugin('advancedlogin.html'), + ... name='Advanced Login Form Plugin') and configure the PAU to use them:: - >>> pau.credentialsPlugins = ( - ... 'Simple Login Form Plugin', - ... 'Advanced Login Form Plugin') + >>> pau.credentialsPlugins = ( + ... 'Simple Login Form Plugin', + ... 'Advanced Login Form Plugin') Now when we call 'unauthorized' on the PAU:: - >>> request = TestRequest() - >>> pau.unauthorized(id=None, request=request) + >>> request = TestRequest() + >>> pau.unauthorized(id=None, request=request) we see that the user is redirected to the simple login form:: - >>> request.response.getStatus() - 302 - >>> request.response.getHeader('location') - 'simplelogin.html' + >>> request.response.getStatus() + 302 + >>> request.response.getHeader('location') + 'simplelogin.html' We can change the challenge policy by reordering the plugins:: - >>> pau.credentialsPlugins = ( - ... 'Advanced Login Form Plugin', - ... 'Simple Login Form Plugin') + >>> pau.credentialsPlugins = ( + ... 'Advanced Login Form Plugin', + ... 'Simple Login Form Plugin') Now when we call 'unauthorized':: - >>> request = TestRequest() - >>> pau.unauthorized(id=None, request=request) + >>> request = TestRequest() + >>> pau.unauthorized(id=None, request=request) the advanced plugin is used because it's first:: - >>> request.response.getStatus() - 302 - >>> request.response.getHeader('location') - 'advancedlogin.html' + >>> request.response.getStatus() + 302 + >>> request.response.getHeader('location') + 'advancedlogin.html' Challenge Protocols ~~~~~~~~~~~~~~~~~~~ @@ -627,45 +627,45 @@ Let's look at an example. We'll define a new plugin that specifies an 'X-Challenge' protocol:: - >>> 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 + >>> 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 and register a couple instances as utilities:: - >>> provideUtility(XChallengeCredentialsPlugin('basic'), - ... name='Basic X-Challenge Plugin') + >>> provideUtility(XChallengeCredentialsPlugin('basic'), + ... name='Basic X-Challenge Plugin') - >>> provideUtility(XChallengeCredentialsPlugin('advanced'), - ... name='Advanced X-Challenge Plugin') + >>> provideUtility(XChallengeCredentialsPlugin('advanced'), + ... name='Advanced X-Challenge Plugin') When we use both plugins with the PAU:: - >>> pau.credentialsPlugins = ( - ... 'Basic X-Challenge Plugin', - ... 'Advanced X-Challenge Plugin') + >>> pau.credentialsPlugins = ( + ... 'Basic X-Challenge Plugin', + ... 'Advanced X-Challenge Plugin') and call 'unauthorized':: - >>> request = TestRequest() - >>> pau.unauthorized(None, request) + >>> request = TestRequest() + >>> pau.unauthorized(None, request) we see that both plugins participate in the challange, rather than just the first plugin:: - >>> request.response.getHeader('X-Challenge') - 'advanced basic' + >>> request.response.getHeader('X-Challenge') + 'advanced basic' Pluggable-Authentication Prefixes @@ -678,25 +678,25 @@ that principal ids from different PAUs don't conflict. We can provide a prefix when a PAU is created:: - >>> pau = authentication.PluggableAuthentication('mypau_') - >>> pau.credentialsPlugins = ('My Credentials Plugin', ) - >>> pau.authenticatorPlugins = ('My Authenticator Plugin', ) + >>> pau = authentication.PluggableAuthentication('mypau_') + >>> pau.credentialsPlugins = ('My Credentials Plugin', ) + >>> pau.authenticatorPlugins = ('My Authenticator Plugin', ) When we create a request and try to authenticate:: - >>> pau.authenticate(TestRequest(credentials='secretcode')) - Principal('mypau_bob') + >>> pau.authenticate(TestRequest(credentials='secretcode')) + Principal('mypau_bob') Note that now, our principal's id has the pluggable-authentication utility prefix. We can still lookup a principal, as long as we supply the prefix:: - >> pau.getPrincipal('mypas_42') - Principal('mypas_42', "{'domain': 42}") + >> pau.getPrincipal('mypas_42') + Principal('mypas_42', "{'domain': 42}") - >> pau.getPrincipal('mypas_41') - OddPrincipal('mypas_41', "{'int': 41}") + >> pau.getPrincipal('mypas_41') + OddPrincipal('mypas_41', "{'int': 41}") Searching @@ -704,9 +704,9 @@ PAU implements ISourceQueriables:: - >>> from zope.schema.interfaces import ISourceQueriables - >>> ISourceQueriables.providedBy(pau) - True + >>> from zope.schema.interfaces import ISourceQueriables + >>> ISourceQueriables.providedBy(pau) + True This means a PAU can be used in a principal source vocabulary (Zope provides a sophisticated searching UI for principal sources). @@ -718,61 +718,61 @@ Currently, our list of authenticators:: - >>> pau.authenticatorPlugins - ('My Authenticator Plugin',) + >>> pau.authenticatorPlugins + ('My Authenticator Plugin',) does not include a queriable authenticator. PAU cannot therefore provide any queriables:: - >>> list(pau.getQueriables()) - [] + >>> list(pau.getQueriables()) + [] Before we illustrate how an authenticator is used by the PAU to search for principals, we need to setup an adapter used by PAU:: - >>> provideAdapter( - ... authentication.authentication.QuerySchemaSearchAdapter, - ... provides=interfaces.IQueriableAuthenticator) + >>> provideAdapter( + ... authentication.authentication.QuerySchemaSearchAdapter, + ... provides=interfaces.IQueriableAuthenticator) This adapter delegates search responsibility to an authenticator, but prepends the PAU prefix to any principal IDs returned in a search. Next, we'll create a plugin that provides a search interface:: - >>> class QueriableAuthenticatorPlugin(MyAuthenticatorPlugin): - ... - ... interface.implements(interfaces.IQuerySchemaSearch) - ... - ... schema = None - ... - ... def search(self, query, start=None, batch_size=None): - ... yield 'foo' - ... + >>> class QueriableAuthenticatorPlugin(MyAuthenticatorPlugin): + ... + ... interface.implements(interfaces.IQuerySchemaSearch) + ... + ... schema = None + ... + ... def search(self, query, start=None, batch_size=None): + ... yield 'foo' + ... and install it as a plugin:: - >>> plugin = QueriableAuthenticatorPlugin() - >>> provideUtility(plugin, - ... provides=interfaces.IAuthenticatorPlugin, - ... name='Queriable') - >>> pau.authenticatorPlugins += ('Queriable',) + >>> plugin = QueriableAuthenticatorPlugin() + >>> provideUtility(plugin, + ... provides=interfaces.IAuthenticatorPlugin, + ... name='Queriable') + >>> pau.authenticatorPlugins += ('Queriable',) Now, the PAU provides a single queriable:: - >>> list(pau.getQueriables()) # doctest: +ELLIPSIS - [('Queriable', ...QuerySchemaSearchAdapter object...)] + >>> list(pau.getQueriables()) # doctest: +ELLIPSIS + [('Queriable', ...QuerySchemaSearchAdapter object...)] We can use this queriable to search for our principal:: - >>> queriable = list(pau.getQueriables())[0][1] - >>> list(queriable.search('not-used')) - ['mypau_foo'] + >>> queriable = list(pau.getQueriables())[0][1] + >>> list(queriable.search('not-used')) + ['mypau_foo'] Note that the resulting principal ID includes the PAU prefix. Were we to search the plugin directly:: - >>> list(plugin.search('not-used')) - ['foo'] + >>> list(plugin.search('not-used')) + ['foo'] The result does not include the PAU prefix. The prepending of the prefix is handled by the PluggableAuthenticationQueriable. @@ -782,58 +782,58 @@ 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.site.hooks - >>> site = zope.site.hooks.getSite() - >>> plugin = LocatedQueriableAuthenticatorPlugin() - >>> plugin.__parent__ = site - >>> plugin.__name__ = 'localname' - >>> provideUtility(plugin, - ... provides=interfaces.IAuthenticatorPlugin, - ... name='location-queriable') - >>> pau.authenticatorPlugins = ('location-queriable',) + >>> import zope.location.interfaces + >>> class LocatedQueriableAuthenticatorPlugin(QueriableAuthenticatorPlugin): + ... + ... interface.implements(zope.location.interfaces.ILocation) + ... + ... __parent__ = __name__ = None + ... + >>> import zope.site.hooks + >>> site = zope.site.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...)] + >>> 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 + >>> 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',) + >>> 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...)] + >>> 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 - - >>> queriable.__parent__ is pau - True + >>> queriable = queriables[0][1] + >>> queriable.__parent__ # doctest: +ELLIPSIS + + >>> queriable.__parent__ is pau + True ================ @@ -844,17 +844,17 @@ information. We create an internal principal using the `InternalPrincipal` class: - >>> 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 InternalPrincipal + >>> p1 = InternalPrincipal('login1', '123', "Principal 1", + ... passwordManagerName="SHA1") + >>> p2 = InternalPrincipal('login2', '456', "The Other One") and add them to a principal folder: - >>> from zope.app.authentication.principalfolder import PrincipalFolder - >>> principals = PrincipalFolder('principal.') - >>> principals['p1'] = p1 - >>> principals['p2'] = p2 + >>> from zope.app.authentication.principalfolder import PrincipalFolder + >>> principals = PrincipalFolder('principal.') + >>> principals['p1'] = p1 + >>> principals['p2'] = p2 Authentication -------------- @@ -862,9 +862,9 @@ Principal folders provide the `IAuthenticatorPlugin` interface. When we provide suitable credentials: - >>> from pprint import pprint - >>> principals.authenticateCredentials({'login': 'login1', 'password': '123'}) - PrincipalInfo(u'principal.p1') + >>> from pprint import pprint + >>> principals.authenticateCredentials({'login': 'login1', 'password': '123'}) + PrincipalInfo(u'principal.p1') We get back a principal id and supplementary information, including the principal title and description. Note that the principal id is a concatenation @@ -873,9 +873,9 @@ None is returned if the credentials are invalid: - >>> principals.authenticateCredentials({'login': 'login1', - ... 'password': '1234'}) - >>> principals.authenticateCredentials(42) + >>> principals.authenticateCredentials({'login': 'login1', + ... 'password': '1234'}) + >>> principals.authenticateCredentials(42) Search ------ @@ -883,127 +883,127 @@ Principal folders also provide the IQuerySchemaSearch interface. This supports both finding principal information based on their ids: - >>> principals.principalInfo('principal.p1') - PrincipalInfo('principal.p1') + >>> principals.principalInfo('principal.p1') + PrincipalInfo('principal.p1') - >>> principals.principalInfo('p1') + >>> principals.principalInfo('p1') and searching for principals based on a search string: - >>> list(principals.search({'search': 'other'})) - [u'principal.p2'] + >>> list(principals.search({'search': 'other'})) + [u'principal.p2'] - >>> list(principals.search({'search': 'OTHER'})) - [u'principal.p2'] + >>> list(principals.search({'search': 'OTHER'})) + [u'principal.p2'] - >>> list(principals.search({'search': ''})) - [u'principal.p1', u'principal.p2'] + >>> list(principals.search({'search': ''})) + [u'principal.p1', u'principal.p2'] - >>> list(principals.search({'search': 'eek'})) - [] + >>> list(principals.search({'search': 'eek'})) + [] - >>> list(principals.search({})) - [] + >>> list(principals.search({})) + [] If there are a large number of matches: - >>> for i in range(20): - ... i = str(i) - ... p = InternalPrincipal('l'+i, i, "Dude "+i) - ... principals[i] = p - - >>> pprint(list(principals.search({'search': 'D'}))) - [u'principal.0', - u'principal.1', - u'principal.10', - u'principal.11', - u'principal.12', - u'principal.13', - u'principal.14', - u'principal.15', - u'principal.16', - u'principal.17', - u'principal.18', - u'principal.19', - u'principal.2', - u'principal.3', - u'principal.4', - u'principal.5', - u'principal.6', - u'principal.7', - u'principal.8', - u'principal.9'] + >>> for i in range(20): + ... i = str(i) + ... p = InternalPrincipal('l'+i, i, "Dude "+i) + ... principals[i] = p + + >>> pprint(list(principals.search({'search': 'D'}))) + [u'principal.0', + u'principal.1', + u'principal.10', + u'principal.11', + u'principal.12', + u'principal.13', + u'principal.14', + u'principal.15', + u'principal.16', + u'principal.17', + u'principal.18', + u'principal.19', + u'principal.2', + u'principal.3', + u'principal.4', + u'principal.5', + u'principal.6', + u'principal.7', + u'principal.8', + u'principal.9'] We can use batching parameters to specify a subset of results: - >>> pprint(list(principals.search({'search': 'D'}, start=17))) - [u'principal.7', u'principal.8', u'principal.9'] + >>> pprint(list(principals.search({'search': 'D'}, start=17))) + [u'principal.7', u'principal.8', u'principal.9'] - >>> pprint(list(principals.search({'search': 'D'}, batch_size=5))) - [u'principal.0', - u'principal.1', - u'principal.10', - u'principal.11', - u'principal.12'] - - >>> pprint(list(principals.search({'search': 'D'}, start=5, batch_size=5))) - [u'principal.13', - u'principal.14', - u'principal.15', - u'principal.16', - u'principal.17'] + >>> pprint(list(principals.search({'search': 'D'}, batch_size=5))) + [u'principal.0', + u'principal.1', + u'principal.10', + u'principal.11', + u'principal.12'] + + >>> pprint(list(principals.search({'search': 'D'}, start=5, batch_size=5))) + [u'principal.13', + u'principal.14', + u'principal.15', + u'principal.16', + u'principal.17'] There is an additional method that allows requesting the principal id associated with a login id. The method raises KeyError when there is no associated principal:: - >>> principals.getIdByLogin("not-there") - Traceback (most recent call last): - KeyError: 'not-there' + >>> principals.getIdByLogin("not-there") + Traceback (most recent call last): + KeyError: 'not-there' If there is a matching principal, the id is returned:: - >>> principals.getIdByLogin("login1") - u'principal.p1' + >>> principals.getIdByLogin("login1") + u'principal.p1' Changing credentials -------------------- Credentials can be changed by modifying principal-information objects: - >>> p1.login = 'bob' - >>> p1.password = 'eek' + >>> p1.login = 'bob' + >>> p1.password = 'eek' - >>> principals.authenticateCredentials({'login': 'bob', 'password': 'eek'}) - PrincipalInfo(u'principal.p1') + >>> principals.authenticateCredentials({'login': 'bob', 'password': 'eek'}) + PrincipalInfo(u'principal.p1') - >>> principals.authenticateCredentials({'login': 'login1', - ... 'password': 'eek'}) + >>> principals.authenticateCredentials({'login': 'login1', + ... 'password': 'eek'}) - >>> principals.authenticateCredentials({'login': 'bob', - ... 'password': '123'}) + >>> principals.authenticateCredentials({'login': 'bob', + ... 'password': '123'}) It is an error to try to pick a login name that is already taken: - >>> p1.login = 'login2' - Traceback (most recent call last): - ... - ValueError: Principal Login already taken! + >>> p1.login = 'login2' + Traceback (most recent call last): + ... + ValueError: Principal Login already taken! If such an attempt is made, the data are unchanged: - >>> principals.authenticateCredentials({'login': 'bob', 'password': 'eek'}) - PrincipalInfo(u'principal.p1') + >>> principals.authenticateCredentials({'login': 'bob', 'password': 'eek'}) + PrincipalInfo(u'principal.p1') Removing principals ------------------- Of course, if a principal is removed, we can no-longer authenticate it: - >>> del principals['p1'] - >>> principals.authenticateCredentials({'login': 'bob', - ... 'password': 'eek'}) + >>> del principals['p1'] + >>> principals.authenticateCredentials({'login': 'bob', + ... 'password': 'eek'}) ============ @@ -1031,138 +1031,138 @@ authentication plugins, and register some of them as utilities and put others in a faux PAU. - >>> from zope.app.authentication import interfaces - >>> from zope import interface, component - >>> class DemoPlugin(object): - ... interface.implements(interfaces.IAuthenticatorPlugin) - ... 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] - >>> class DemoAuth(dict): - ... interface.implements(interfaces.IPluggableAuthentication) - ... 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) + >>> from zope.app.authentication import interfaces + >>> from zope import interface, component + >>> class DemoPlugin(object): + ... interface.implements(interfaces.IAuthenticatorPlugin) + ... 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] + >>> class DemoAuth(dict): + ... interface.implements(interfaces.IPluggableAuthentication) + ... 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) We are now ready to create a vocabulary that we can use. The context is our faux authentication utility, `auth`. - >>> from zope.app.authentication import vocabulary - >>> vocab = vocabulary.authenticatorPlugins(auth) + >>> from zope.app.authentication import vocabulary + >>> vocab = vocabulary.authenticatorPlugins(auth) Iterating over the vocabulary results in all of the terms, in a relatively arbitrary order of their names. (This vocabulary should typically use a widget that sorts values on the basis of localized collation order of the term titles.) - >>> [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'] + >>> [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'] Similarly, we can use `in` to test for the presence of values in the vocabulary. - >>> ['Plugin %s' % i in vocab for i in range(-1, 6)] - [False, True, True, True, True, True, False] - >>> 'Plugin X' in vocab - True + >>> ['Plugin %s' % i in vocab for i in range(-1, 6)] + [False, True, True, True, True, True, False] + >>> 'Plugin X' in vocab + True The length reports the expected value. - >>> len(vocab) - 6 + >>> len(vocab) + 6 One can get a term for a given value using `getTerm()`; its token, in turn, should also return the same effective term from `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 - ... + >>> 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 + ... The terms have titles, which are message ids that show the plugin title or id and whether the plugin is a utility or just contained in the auth utility. We'll give one of the plugins a dublin core title just to show the functionality. - >>> import zope.dublincore.interfaces - >>> class ISpecial(interface.Interface): - ... pass - ... - >>> interface.directlyProvides(contained_plugins[1], ISpecial) - >>> class DemoDCAdapter(object): - ... interface.implements( - ... zope.dublincore.interfaces.IDCDescriptiveProperties) - ... component.adapts(ISpecial) - ... def __init__(self, context): - ... pass - ... title = u'Special Title' - ... - >>> component.provideAdapter(DemoDCAdapter) + >>> import zope.dublincore.interfaces + >>> class ISpecial(interface.Interface): + ... pass + ... + >>> interface.directlyProvides(contained_plugins[1], ISpecial) + >>> class DemoDCAdapter(object): + ... interface.implements( + ... zope.dublincore.interfaces.IDCDescriptiveProperties) + ... component.adapts(ISpecial) + ... def __init__(self, context): + ... pass + ... title = u'Special Title' + ... + >>> component.provideAdapter(DemoDCAdapter) We need to regenerate the vocabulary, since it calculates all of its data at once. - >>> vocab = vocabulary.authenticatorPlugins(auth) + >>> vocab = vocabulary.authenticatorPlugins(auth) Now we'll check the titles. We'll have to translate them to see what we expect. - >>> 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)'] + >>> 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 ------------------ For completeness, we'll do the same review of the credentialsPlugins. - >>> class DemoPlugin(object): - ... interface.implements(interfaces.ICredentialsPlugin) - ... 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) + >>> class DemoPlugin(object): + ... interface.implements(interfaces.ICredentialsPlugin) + ... 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) Iterating over the vocabulary results in all of the terms, in a relatively arbitrary order of their names. (This vocabulary should typically use a @@ -1170,28 +1170,28 @@ titles.) Similarly, we can use `in` to test for the presence of values in the vocabulary. The length reports the expected value. - >>> [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 + >>> [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 One can get a term for a given value using `getTerm()`; its token, in turn, should also return the same effective term from `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 - ... + >>> 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 + ... The terms have titles, which are message ids that show the plugin title or id and whether the plugin is a utility or just contained in the auth utility. @@ -1200,96 +1200,100 @@ of its data at once. Then we'll check the titles. We'll have to translate them to see what we expect. - >>> 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)'] + >>> 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)'] ======= Changes ======= + 3.9 (2010-10-18) + ---------------- + + * Move concrete IAuthenticatorPlugin implementations to + zope.pluggableauth.plugins. Leave backwards compatibility imports. + + * Use zope.formlib throughout to lift the dependency on zope.app.form. As it + turns out, zope.app.form is still a indirect test dependency though. + 3.8.0 (2010-09-25) ------------------ * Using python's ``doctest`` module instead of deprecated - ``zope.testing.doctest[unit]``. + ``zope.testing.doctest[unit]``. * Moved the following views from `zope.app.securitypolicy` here, to inverse - dependency between these two packages, as `zope.app.securitypolicy` - deprecated in ZTK 1.0: - - - ``@@grant.html`` - - ``@@AllRolePermissions.html`` - - ``@@RolePermissions.html`` - - ``@@RolesWithPermission.html`` + dependency between these two packages, as `zope.app.securitypolicy` + deprecated in ZTK 1.0: + - ``@@grant.html`` + - ``@@AllRolePermissions.html`` + - ``@@RolePermissions.html`` + - ``@@RolesWithPermission.html`` 3.7.1 (2010-02-11) ------------------ * Using the new `principalfactories.zcml` file, from ``zope.pluggableauth``, - to avoid duplication errors, in the adapters registration. - + to avoid duplication errors, in the adapters registration. 3.7.0 (2010-02-08) ------------------ * The Pluggable Authentication utility has been severed and released - in a standalone package: `zope.pluggableauth`. We are now using this - new package, providing backward compatibility imports to assure a - smooth transition. - + in a standalone package: `zope.pluggableauth`. We are now using this + new package, providing backward compatibility imports to assure a + smooth transition. 3.6.2 (2010-01-05) ------------------ * Fix tests by using zope.login, and require new zope.publisher 3.12. - 3.6.1 (2009-10-07) ------------------ * Fix ftesting.zcml due to ``zope.securitypolicy`` update. * Don't use ``zope.app.testing.ztapi`` in tests, use zope.component's - testing functions instead. + testing functions instead. * Fix functional tests and stop using port 8081. Redirecting to - different port without trusted flag is not allowed. + different port without trusted flag is not allowed. 3.6.0 (2009-03-14) ------------------ * Separate the presentation template and camefrom/redirection logic for the - ``loginForm.html`` view. Now the logic is contained in the - ``zope.app.authentication.browser.loginform.LoginForm`` class. + ``loginForm.html`` view. Now the logic is contained in the + ``zope.app.authentication.browser.loginform.LoginForm`` class. * Fix login form redirection failure in some cases with Python 2.6. * Use the new ``zope.authentication`` package instead of ``zope.app.security``. * The "Password Manager Names" vocabulary and simple password manager registry - were moved to the ``zope.password`` package. + were moved to the ``zope.password`` package. * Remove deprecated code. - 3.5.0 (2009-03-06) ------------------ * Split password manager functionality off to the new ``zope.password`` - package. Backward-compatibility imports are left in place. + package. Backward-compatibility imports are left in place. * Use ``zope.site`` instead of ``zope.app.component``. (Browser code still - needs ``zope.app.component`` as it depends on view classes of this - package.) + needs ``zope.app.component`` as it depends on view classes of this + package.) 3.5.0a2 (2009-02-01) -------------------- @@ -1300,16 +1304,16 @@ -------------------- * Use ``zope.container`` instead of ``zope.app.container``. (Browser code - still needs ``zope.app.container`` as it depends on view classes of this - package.) + still needs ``zope.app.container`` as it depends on view classes of this + package.) * Encoded passwords are now stored with a prefix ({MD5}, {SHA1}, - {SSHA}) indicating the used encoding schema. Old (encoded) passwords - can still be used. + {SSHA}) indicating the used encoding schema. Old (encoded) passwords + can still be used. * Add an SSHA password manager that is compatible with standard LDAP - passwords. As this encoding gives better security agains dictionary - attacks, users are encouraged to switch to this new password schema. + passwords. As this encoding gives better security agains dictionary + attacks, users are encouraged to switch to this new password schema. * InternalPrincipal now uses SSHA password manager by default. @@ -1317,7 +1321,7 @@ ------------------ * Depend on zope.session instead of zope.app.session. The first one - currently has all functionality we need. + currently has all functionality we need. * Fix deprecation warnings for ``md5`` and ``sha`` on Python 2.6. 3.4.3 (2008-08-07) @@ -1329,8 +1333,8 @@ ------------------- * Make it compatible with zope.app.container 3.6.1 and 3.5.4 changes, - Changed ``super(BTreeContainer, self).__init__()`` to - ``super(GroupFolder, self).__init__()`` in ``GroupFolder`` class. + Changed ``super(BTreeContainer, self).__init__()`` to + ``super(GroupFolder, self).__init__()`` in ``GroupFolder`` class. 3.4.1 (2007-10-24) ------------------ diff -Nru zope.app.authentication-3.8.0/src/zope.app.authentication.egg-info/requires.txt zope.app.authentication-3.9/src/zope.app.authentication.egg-info/requires.txt --- zope.app.authentication-3.8.0/src/zope.app.authentication.egg-info/requires.txt 2010-09-25 09:07:21.000000000 +0000 +++ zope.app.authentication-3.9/src/zope.app.authentication.egg-info/requires.txt 2010-10-18 09:51:16.000000000 +0000 @@ -1,24 +1,23 @@ -ZODB3 setuptools -zope.app.form -zope.app.container -zope.app.component +ZODB3 zope.authentication zope.component zope.container zope.dublincore zope.event zope.exceptions -zope.formlib +zope.formlib >= 4.0.2 zope.i18n zope.i18nmessageid zope.interface zope.location -zope.password>=3.5.1 -zope.pluggableauth>=1.0.1 +zope.password >= 3.5.1 +zope.pluggableauth >= 1.1 zope.schema zope.security zope.traversing +zope.app.container +zope.app.component [test] zope.app.testing