zope.pluggableauth-1.3/0000755000175000017500000000000011524202367014241 5ustar jwjw00000000000000zope.pluggableauth-1.3/CHANGES.txt0000644000175000017500000000313311524202361016044 0ustar jwjw00000000000000======= Changes ======= 1.3 (2011-02-08) ---------------- - As the camefrom information is most probably used for a redirect, require it to be an absolute URL (see also http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.30). 1.2 (2010-12-16) ---------------- - SessionCredentialsPlugin has a hook (_makeCredentials) that can be overriden in subclasses to store the credentials in the session differently. For example, you could use keas.kmi and encrypt the passwords of the currently logged-in users so they don't appear in plain text in the ZODB. 1.1 (2010-10-18) ---------------- * Moved concrete IAuthenticatorPlugin implementations from zope.app.authentication to zope.pluggableauth.plugins. As a result projects that want to use the IAuthenticator plugins (previously found in zope.app.authentication) do not automatically also pull in the zope.app.* dependencies that are needed to register the ZMI views. 1.0.3 (2010-07-09) ------------------ * Fixed dependency declaration. 1.0.2 (2010-07-90) ------------------ * Added persistent.Persistent and zope.container.contained.Contained as bases zope.pluggableauth.plugins.session.SessionCredentialsPlugin, so instances of zope.app.authentication.session.SessionCredentialsPlugin won't be changed. (https://mail.zope.org/pipermail/zope-dev/2010-July/040898.html) 1.0.1 (2010-02-11) ------------------ * Adapters are now declared in a new ZCML file : `principalfactories.zcml`. This avoids duplication errors in ``zope.app.authentication``. 1.0 (2010-02-05) ---------------- * Splitting off from zope.app.authentication zope.pluggableauth-1.3/setup.cfg0000644000175000017500000000007311524202367016062 0ustar jwjw00000000000000[egg_info] tag_build = tag_date = 0 tag_svn_revision = 0 zope.pluggableauth-1.3/LICENSE.txt0000644000175000017500000000402611524202361016060 0ustar jwjw00000000000000Zope Public License (ZPL) Version 2.1 A copyright notice accompanies this license document that identifies the copyright holders. This license has been certified as open source. It has also been designated as GPL compatible by the Free Software Foundation (FSF). Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions in source code must retain the accompanying copyright notice, this list of conditions, and the following disclaimer. 2. Redistributions in binary form must reproduce the accompanying copyright notice, this list of conditions, and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. Names of the copyright holders must not be used to endorse or promote products derived from this software without prior written permission from the copyright holders. 4. The right to distribute this software or to use it for any purpose does not give you the right to use Servicemarks (sm) or Trademarks (tm) of the copyright holders. Use of them is covered by separate agreement with the copyright holders. 5. If any files are modified, you must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. Disclaimer THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. zope.pluggableauth-1.3/setup.py0000644000175000017500000000547511524202361015760 0ustar jwjw00000000000000############################################################################## # # Copyright (c) 2004 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## ############################################################################## # This package is developed by the Zope Toolkit project, documented here: # http://docs.zope.org/zopetoolkit # When developing and releasing this package, please follow the documented # Zope Toolkit policies as described by this documentation. ############################################################################## """Pluggable Authentication Utility """ import os from setuptools import setup, find_packages def read(*rnames): return open(os.path.join(os.path.dirname(__file__), *rnames)).read() setup(name='zope.pluggableauth', version='1.3', author='Zope Foundation and Contributors', author_email='zope-dev@zope.org', description='Pluggable Authentication Utility', long_description= "\n".join(( read('README.txt'), '.. contents::\n\n', read('CHANGES.txt'), read('src', 'zope', 'pluggableauth', 'README.txt'), )), url='http://pypi.python.org/pypi/zope.pluggableauth', license='ZPL 2.1', keywords='zope3 ztk authentication pluggable', packages=find_packages('src'), package_dir = {'': 'src'}, namespace_packages=['zope'], include_package_data = True, zip_safe = False, extras_require=dict(test=['zope.component[test]']), install_requires=[ 'ZODB3', 'setuptools', 'zope.authentication', 'zope.component', 'zope.container', 'zope.event', 'zope.i18nmessageid', 'zope.interface', 'zope.password >= 3.5.1', 'zope.publisher>=3.12', 'zope.schema', 'zope.security', 'zope.session', 'zope.site', 'zope.traversing', ], classifiers = [ 'Development Status :: 5 - Production/Stable', 'Environment :: Web Environment', 'Intended Audience :: Developers', 'License :: OSI Approved :: Zope Public License', 'Programming Language :: Python', 'Natural Language :: English', 'Operating System :: OS Independent', 'Topic :: Internet :: WWW/HTTP', 'Framework :: Zope3'], ) zope.pluggableauth-1.3/bootstrap.py0000644000175000017500000000330211524202361016620 0ustar jwjw00000000000000############################################################################## # # Copyright (c) 2006 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Bootstrap a buildout-based project Simply run this script in a directory containing a buildout.cfg. The script accepts buildout command-line options, so you can use the -c option to specify an alternate configuration file. """ import os, shutil, sys, tempfile, urllib2 tmpeggs = tempfile.mkdtemp() ez = {} exec urllib2.urlopen('http://peak.telecommunity.com/dist/ez_setup.py' ).read() in ez ez['use_setuptools'](to_dir=tmpeggs, download_delay=0) import pkg_resources cmd = 'from setuptools.command.easy_install import main; main()' if sys.platform == 'win32': cmd = '"%s"' % cmd # work around spawn lamosity on windows ws = pkg_resources.working_set assert os.spawnle( os.P_WAIT, sys.executable, sys.executable, '-c', cmd, '-mqNxd', tmpeggs, 'zc.buildout', dict(os.environ, PYTHONPATH= ws.find(pkg_resources.Requirement.parse('setuptools')).location ), ) == 0 ws.add_entry(tmpeggs) ws.require('zc.buildout') import zc.buildout.buildout zc.buildout.buildout.main(sys.argv[1:] + ['bootstrap']) shutil.rmtree(tmpeggs) zope.pluggableauth-1.3/README.txt0000644000175000017500000000033311524202361015730 0ustar jwjw00000000000000========================== Pluggable Authentication ========================== Based on zope.authentication, this package provides a flexible and pluggable authentication utility. Several common plugins are provided. zope.pluggableauth-1.3/src/0000755000175000017500000000000011524202367015030 5ustar jwjw00000000000000zope.pluggableauth-1.3/src/zope.pluggableauth.egg-info/0000755000175000017500000000000011524202367022322 5ustar jwjw00000000000000zope.pluggableauth-1.3/src/zope.pluggableauth.egg-info/namespace_packages.txt0000644000175000017500000000000511524202364026645 0ustar jwjw00000000000000zope zope.pluggableauth-1.3/src/zope.pluggableauth.egg-info/SOURCES.txt0000644000175000017500000000270711524202364024211 0ustar jwjw00000000000000CHANGES.txt COPYRIGHT.txt LICENSE.txt README.txt ZPL.txt bootstrap.py buildout.cfg setup.py src/zope/__init__.py src/zope.pluggableauth.egg-info/PKG-INFO src/zope.pluggableauth.egg-info/SOURCES.txt src/zope.pluggableauth.egg-info/dependency_links.txt src/zope.pluggableauth.egg-info/namespace_packages.txt src/zope.pluggableauth.egg-info/not-zip-safe src/zope.pluggableauth.egg-info/requires.txt src/zope.pluggableauth.egg-info/top_level.txt src/zope/pluggableauth/README.txt src/zope/pluggableauth/__init__.py src/zope/pluggableauth/authentication.py src/zope/pluggableauth/configure.zcml src/zope/pluggableauth/factories.py src/zope/pluggableauth/interfaces.py src/zope/pluggableauth/principalfactories.zcml src/zope/pluggableauth/tests.py src/zope/pluggableauth/plugins/__init__.py src/zope/pluggableauth/plugins/ftpplugins.py src/zope/pluggableauth/plugins/ftpplugins.zcml src/zope/pluggableauth/plugins/generic.py src/zope/pluggableauth/plugins/generic.zcml src/zope/pluggableauth/plugins/groupfolder.py src/zope/pluggableauth/plugins/groupfolder.txt src/zope/pluggableauth/plugins/groupfolder.zcml src/zope/pluggableauth/plugins/httpplugins.py src/zope/pluggableauth/plugins/httpplugins.zcml src/zope/pluggableauth/plugins/idpicker.py src/zope/pluggableauth/plugins/principalfolder.py src/zope/pluggableauth/plugins/principalfolder.txt src/zope/pluggableauth/plugins/principalfolder.zcml src/zope/pluggableauth/plugins/session.py src/zope/pluggableauth/plugins/session.zcmlzope.pluggableauth-1.3/src/zope.pluggableauth.egg-info/dependency_links.txt0000644000175000017500000000000111524202364026365 0ustar jwjw00000000000000 zope.pluggableauth-1.3/src/zope.pluggableauth.egg-info/not-zip-safe0000644000175000017500000000000111524202361024542 0ustar jwjw00000000000000 zope.pluggableauth-1.3/src/zope.pluggableauth.egg-info/requires.txt0000644000175000017500000000037111524202364024720 0ustar jwjw00000000000000ZODB3 setuptools zope.authentication zope.component zope.container zope.event zope.i18nmessageid zope.interface zope.password >= 3.5.1 zope.publisher>=3.12 zope.schema zope.security zope.session zope.site zope.traversing [test] zope.component[test]zope.pluggableauth-1.3/src/zope.pluggableauth.egg-info/top_level.txt0000644000175000017500000000000511524202364025044 0ustar jwjw00000000000000zope zope.pluggableauth-1.3/src/zope.pluggableauth.egg-info/PKG-INFO0000644000175000017500000006215411524202364023424 0ustar jwjw00000000000000Metadata-Version: 1.0 Name: zope.pluggableauth Version: 1.3 Summary: Pluggable Authentication Utility Home-page: http://pypi.python.org/pypi/zope.pluggableauth Author: Zope Foundation and Contributors Author-email: zope-dev@zope.org License: ZPL 2.1 Description: ========================== Pluggable Authentication ========================== Based on zope.authentication, this package provides a flexible and pluggable authentication utility. Several common plugins are provided. .. contents:: ======= Changes ======= 1.3 (2011-02-08) ---------------- - As the camefrom information is most probably used for a redirect, require it to be an absolute URL (see also http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.30). 1.2 (2010-12-16) ---------------- - SessionCredentialsPlugin has a hook (_makeCredentials) that can be overriden in subclasses to store the credentials in the session differently. For example, you could use keas.kmi and encrypt the passwords of the currently logged-in users so they don't appear in plain text in the ZODB. 1.1 (2010-10-18) ---------------- * Moved concrete IAuthenticatorPlugin implementations from zope.app.authentication to zope.pluggableauth.plugins. As a result projects that want to use the IAuthenticator plugins (previously found in zope.app.authentication) do not automatically also pull in the zope.app.* dependencies that are needed to register the ZMI views. 1.0.3 (2010-07-09) ------------------ * Fixed dependency declaration. 1.0.2 (2010-07-90) ------------------ * Added persistent.Persistent and zope.container.contained.Contained as bases zope.pluggableauth.plugins.session.SessionCredentialsPlugin, so instances of zope.app.authentication.session.SessionCredentialsPlugin won't be changed. (https://mail.zope.org/pipermail/zope-dev/2010-July/040898.html) 1.0.1 (2010-02-11) ------------------ * Adapters are now declared in a new ZCML file : `principalfactories.zcml`. This avoids duplication errors in ``zope.app.authentication``. 1.0 (2010-02-05) ---------------- * Splitting off from zope.app.authentication ================================ Pluggable-Authentication Utility ================================ The Pluggable-Authentication Utility (PAU) provides a framework for authenticating principals and associating information with them. It uses plugins and subscribers to get its work done. For a pluggable-authentication utility to be used, it should be registered as a utility providing the `zope.authentication.interfaces.IAuthentication` interface. Authentication -------------- The primary job of PAU is to authenticate principals. It uses two types of plug-ins in its work: - Credentials 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 credentials. For example, a 'session' credentials plugin reads credentials from a session (the "extraction"). If it cannot find credentials, it will redirect the user to a login form in order to provide them (the "challenge"). Authenticator plugins are responsible for authenticating the credentials extracted by a credentials plugin. They are also typically able to create principal objects for credentials they successfully authenticate. Given a request object, the PAU returns a principal object, if it can. The PAU does this by first iterateing through its credentials plugins to obtain a set of credentials. If it gets credentials, it iterates through its authenticator plugins to authenticate them. If an authenticator succeeds in authenticating a set of credentials, the PAU uses the authenticator to create a principal corresponding to the credentials. The authenticator notifies subscribers if an authenticated principal is created. Subscribers are responsible for adding data, especially groups, to the principal. Typically, if a subscriber adds data, it should also add corresponding interface declarations. Simple Credentials Plugin ~~~~~~~~~~~~~~~~~~~~~~~~~ To illustrate, we'll create a simple credentials plugin:: >>> from zope import interface >>> from zope.pluggableauth.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 As a plugin, MyCredentialsPlugin needs to be registered as a named utility:: >>> myCredentialsPlugin = MyCredentialsPlugin() >>> provideUtility(myCredentialsPlugin, name='My Credentials Plugin') Simple Authenticator Plugin ~~~~~~~~~~~~~~~~~~~~~~~~~~~ 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 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 As with the credentials plugin, the authenticator plugin must be registered as a named utility:: >>> myAuthenticatorPlugin = MyAuthenticatorPlugin() >>> provideUtility(myAuthenticatorPlugin, name='My Authenticator Plugin') Configuring a PAU ~~~~~~~~~~~~~~~~~ Finally, we'll create the PAU itself:: >>> from zope.pluggableauth import authentication >>> pau = authentication.PluggableAuthentication('xyz_') and configure it with the two plugins:: >>> pau.credentialsPlugins = ('My Credentials Plugin', ) >>> pau.authenticatorPlugins = ('My Authenticator Plugin', ) Using the PAU to Authenticate ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ >>> from zope.pluggableauth.factories import AuthenticatedPrincipalFactory >>> provideAdapter(AuthenticatedPrincipalFactory) We can now use the PAU to authenticate a sample request:: >>> from zope.publisher.browser import TestRequest >>> print pau.authenticate(TestRequest()) None In this case, we cannot authenticate an empty request. In the same way, we will not be able to authenticate a request with the wrong credentials:: >>> 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') we get an authenticated principal. Multiple Authenticator Plugins ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ The PAU works with multiple authenticator plugins. It uses each plugin, in the order specified in the PAU's authenticatorPlugins attribute, to authenticate a set of credentials. 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', '') >>> provideUtility(MyAuthenticatorPlugin2(), name='My Authenticator Plugin 2') If we put it before the original authenticator:: >>> pau.authenticatorPlugins = ( ... 'My Authenticator Plugin 2', ... 'My Authenticator Plugin') Then it will be given the first opportunity to authenticate a request:: >>> 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 When we change the order of the authenticator plugins:: >>> pau.authenticatorPlugins = ( ... 'My Authenticator Plugin', ... 'My Authenticator Plugin 2') we see that our original plugin is now acting first:: >>> 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') Multiple Credentials Plugins ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ As with with authenticators, we can specify multiple credentials plugins. To illustrate, we'll create a credentials plugin that extracts credentials from a request form:: >>> 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') and insert the new credentials plugin before the existing plugin:: >>> pau.credentialsPlugins = ( ... 'Form Credentials Plugin', ... 'My Credentials Plugin') The PAU will use each plugin in order to try and obtain credentials from a request:: >>> 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' - 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' - Succeeded in authenticating with 'My Authenticator Plugin 2' Let's try a different scenario:: >>> pau.authenticate(TestRequest(credentials='secretcode')) Principal('xyz_bob') In this case, the PAU went through these steps:: - Get credentials using 'Form Credentials Plugin' - Failed to get credentials using 'Form Credentials Plugin', try 'My Credentials Plugin' - Got 'scecretcode' credentials using 'My Credentials Plugin', try to authenticate using 'My Authenticator Plugin' - Succeeded in authenticating with 'My Authenticator Plugin' Let's try a slightly more complex scenario:: >>> 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' - 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 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' - 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' - Succeeded in authenticating with 'My Authenticator Plugin 2' (shouts and cheers!) Multiple Authenticator Plugins ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ As with the other operations we've seen, the PAU uses multiple plugins to find a principal. If the first authenticator plugin can't find the requested principal, the next plugin is used, and so on. >>> class AnotherAuthenticatorPlugin: ... ... 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 To illustrate, we'll create and register two authenticators:: >>> authenticator1 = AnotherAuthenticatorPlugin() >>> provideUtility(authenticator1, name='Authentication Plugin 1') >>> authenticator2 = AnotherAuthenticatorPlugin() >>> provideUtility(authenticator2, name='Authentication Plugin 2') and add a principal to them:: >>> authenticator1.add('bob', 'Bob', 'A nice guy', 'b0b') >>> authenticator1.add('white', 'White Spy', 'Sneaky', 'deathtoblack') >>> authenticator2.add('black', 'Black Spy', 'Also sneaky', 'deathtowhite') When we configure the PAU to use both searchable authenticators (note the order):: >>> pau.authenticatorPlugins = ( ... 'Authentication Plugin 2', ... 'Authentication Plugin 1') we register the factories for our principals:: >>> from zope.pluggableauth.factories import FoundPrincipalFactory >>> provideAdapter(FoundPrincipalFactory) we see how the PAU uses both plugins:: >>> pau.getPrincipal('xyz_white') Principal('xyz_white') >>> 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:: >>> authenticator2.add('white', 'White Rider', '', 'r1der') >>> pau.getPrincipal('xyz_white').title 'White Rider' If we change the order of the plugins:: >>> pau.authenticatorPlugins = ( ... 'Authentication Plugin 1', ... 'Authentication Plugin 2') we get a different principal for ID 'white':: >>> pau.getPrincipal('xyz_white').title 'White Spy' Issuing a Challenge ------------------- Part of PAU's IAuthentication contract is to challenge the user for 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 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 PAU handles the credentials challenge by delegating to its credentials plugins. Currently, the PAU is configured with the credentials plugins that don't perform any action when asked to challenge (see above the 'challenge' methods). To illustrate challenges, we'll subclass an existing credentials plugin and do something in its 'challenge':: >>> 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('advancedlogin.html'), ... name='Advanced Login Form Plugin') and configure the PAU to use them:: >>> pau.credentialsPlugins = ( ... 'Simple Login Form Plugin', ... 'Advanced Login Form Plugin') Now when we call 'unauthorized' on the PAU:: >>> 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' We can change the challenge policy by reordering the plugins:: >>> pau.credentialsPlugins = ( ... 'Advanced Login Form Plugin', ... 'Simple Login Form Plugin') Now when we call 'unauthorized':: >>> 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' Challenge Protocols ~~~~~~~~~~~~~~~~~~~ Sometimes, we want multiple challengers to work together. For example, the HTTP specification allows multiple challenges to be issued in a response. A challenge plugin can provide a `challengeProtocol` attribute that effectively groups related plugins together for challenging. If a plugin returns `True` from its challenge and provides a non-None challengeProtocol, subsequent plugins in the credentialsPlugins list that have the same challenge protocol will also be used to challenge. Without a challengeProtocol, only the first plugin to succeed in a challenge will be used. 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 and register a couple instances as utilities:: >>> provideUtility(XChallengeCredentialsPlugin('basic'), ... name='Basic 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') and call 'unauthorized':: >>> 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' Pluggable-Authentication Prefixes --------------------------------- Principal ids are required to be unique system wide. Plugins will often provide options for providing id prefixes, so that different sets of plugins provide unique ids within a PAU. If there are multiple pluggable-authentication utilities in a system, it's a good idea to give each PAU a unique prefix, so 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', ) When we create a request and try to authenticate:: >>> 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_41') OddPrincipal('mypas_41', "{'int': 41}") Keywords: zope3 ztk authentication pluggable Platform: UNKNOWN Classifier: Development Status :: 5 - Production/Stable Classifier: Environment :: Web Environment Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: Zope Public License Classifier: Programming Language :: Python Classifier: Natural Language :: English Classifier: Operating System :: OS Independent Classifier: Topic :: Internet :: WWW/HTTP Classifier: Framework :: Zope3 zope.pluggableauth-1.3/src/zope/0000755000175000017500000000000011524202367016005 5ustar jwjw00000000000000zope.pluggableauth-1.3/src/zope/pluggableauth/0000755000175000017500000000000011524202367020631 5ustar jwjw00000000000000zope.pluggableauth-1.3/src/zope/pluggableauth/plugins/0000755000175000017500000000000011524202367022312 5ustar jwjw00000000000000zope.pluggableauth-1.3/src/zope/pluggableauth/plugins/ftpplugins.zcml0000644000175000017500000000040211524202361025362 0ustar jwjw00000000000000 zope.pluggableauth-1.3/src/zope/pluggableauth/plugins/session.zcml0000644000175000017500000000040711524202361024657 0ustar jwjw00000000000000 zope.pluggableauth-1.3/src/zope/pluggableauth/plugins/groupfolder.zcml0000644000175000017500000000103411524202361025521 0ustar jwjw00000000000000 zope.pluggableauth-1.3/src/zope/pluggableauth/plugins/generic.py0000644000175000017500000000626011524202361024276 0ustar jwjw00000000000000############################################################################## # # Copyright (c) 2004 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Generic PAS Plugins """ __docformat__ = "reStructuredText" from zope.authentication.interfaces import IUnauthenticatedPrincipal from zope.interface import implements from zope.pluggableauth import interfaces class NoChallengeCredentialsPlugin(object): """A plugin that doesn't challenge if the principal is authenticated. There are two reasonable ways to handle an unauthorized error for an authenticated principal: - Inform the user of the unauthorized error - Let the user login with a different set of credentials Since either approach is reasonable, we need to give the site manager some way of specifying one of the two policies. By default, a user will be challenged for a new set of credentials if unauthorized. A site manager can insert this plugin in the front of the plugin list to prevent that challenge from occurring. This will typically result in an 'Unauthorized' message to the user. The 'challenge' behavior of the plugin is simple. To illustrate, we'll create a plugin: >>> challenger = NoChallengeCredentialsPlugin() and a test request with an authenticated principal: >>> from zope.publisher.browser import TestRequest >>> request = TestRequest() >>> IUnauthenticatedPrincipal.providedBy(request.principal) False When we challenge using the plugin: >>> challenger.challenge(request) True we get a value that signals the PAU that this plugin successfully challenged the user (even though it actually did nothing). The PAU will stop trying to challenge and the user will not get a chance to provide different credentials. The result is typically an error message. On the other hand, if the user is unauthenticated: >>> class Principal(object): ... implements(IUnauthenticatedPrincipal) >>> request.setPrincipal(Principal()) >>> IUnauthenticatedPrincipal.providedBy(request.principal) True the plugin challenge will return None: >>> print challenger.challenge(request) None signaling the PAU that it should try the next plugin for a challenge. If the PAU is configured properly, the user will receive a challenge and be allowed to provide different credentials. """ implements(interfaces.ICredentialsPlugin) def extractCredentials(self, request): return None def challenge(self, request): if not IUnauthenticatedPrincipal.providedBy(request.principal): return True return None def logout(self, request): return False zope.pluggableauth-1.3/src/zope/pluggableauth/plugins/groupfolder.txt0000644000175000017500000003531711524202361025406 0ustar jwjw00000000000000============= Group Folders ============= Group folders provide support for groups information stored in the ZODB. They are persistent, and must be contained within the PAUs that use them. Like other principals, groups are created when they are needed. Group folders contain group-information objects that contain group information. We create group information using the `GroupInformation` class: >>> import zope.pluggableauth.plugins.groupfolder >>> g1 = zope.pluggableauth.plugins.groupfolder.GroupInformation("Group 1") >>> groups = zope.pluggableauth.plugins.groupfolder.GroupFolder('group.') >>> groups['g1'] = g1 Note that when group-info is added, a GroupAdded event is generated: >>> from zope.pluggableauth import interfaces >>> from zope.component.eventtesting import getEvents >>> getEvents(interfaces.IGroupAdded) [] Groups are defined with respect to an authentication service. Groups must be accessible via an authentication service and can contain principals accessible via an authentication service. To illustrate the group interaction with the authentication service, we'll create a sample authentication service: >>> from zope import interface >>> from zope.authentication.interfaces import IAuthentication >>> from zope.authentication.interfaces import PrincipalLookupError >>> from zope.security.interfaces import IGroupAwarePrincipal >>> from zope.pluggableauth.plugins.groupfolder import setGroupsForPrincipal >>> class Principal: ... interface.implements(IGroupAwarePrincipal) ... def __init__(self, id, title='', description=''): ... self.id, self.title, self.description = id, title, description ... self.groups = [] >>> class PrincipalCreatedEvent: ... def __init__(self, authentication, principal): ... self.authentication = authentication ... self.principal = principal >>> from zope.pluggableauth.plugins import principalfolder >>> class Principals: ... ... interface.implements(IAuthentication) ... ... def __init__(self, groups, prefix='auth.'): ... self.prefix = prefix ... self.principals = { ... 'p1': principalfolder.PrincipalInfo('p1', '', '', ''), ... 'p2': principalfolder.PrincipalInfo('p2', '', '', ''), ... 'p3': principalfolder.PrincipalInfo('p3', '', '', ''), ... 'p4': principalfolder.PrincipalInfo('p4', '', '', ''), ... } ... self.groups = groups ... groups.__parent__ = self ... ... def getAuthenticatorPlugins(self): ... return [('principals', self.principals), ('groups', self.groups)] ... ... def getPrincipal(self, id): ... if not id.startswith(self.prefix): ... raise PrincipalLookupError(id) ... id = id[len(self.prefix):] ... info = self.principals.get(id) ... if info is None: ... info = self.groups.principalInfo(id) ... if info is None: ... raise PrincipalLookupError(id) ... principal = Principal(self.prefix+info.id, ... info.title, info.description) ... setGroupsForPrincipal(PrincipalCreatedEvent(self, principal)) ... return principal This class doesn't really implement the full `IAuthentication` interface, but it implements the `getPrincipal` method used by groups. It works very much like the pluggable authentication utility. It creates principals on demand. It calls `setGroupsForPrincipal`, which is normally called as an event subscriber, when principals are created. In order for `setGroupsForPrincipal` to find out group folder, we have to register it as a utility: >>> from zope.pluggableauth.interfaces import IAuthenticatorPlugin >>> from zope.component import provideUtility >>> provideUtility(groups, IAuthenticatorPlugin) We will create and register a new principals utility: >>> principals = Principals(groups) >>> provideUtility(principals, IAuthentication) Now we can set the principals on the group: >>> g1.principals = ['auth.p1', 'auth.p2'] >>> g1.principals ('auth.p1', 'auth.p2') Adding principals fires an event. >>> getEvents(interfaces.IPrincipalsAddedToGroup)[-1] We can now look up groups for the principals: >>> groups.getGroupsForPrincipal('auth.p1') (u'group.g1',) Note that the group id is a concatenation of the group-folder prefix and the name of the group-information object within the folder. If we delete a group: >>> del groups['g1'] then the groups folder loses the group information for that group's principals: >>> groups.getGroupsForPrincipal('auth.p1') () but the principal information on the group is unchanged: >>> g1.principals ('auth.p1', 'auth.p2') It also fires an event showing that the principals are removed from the group (g1 is group information, not a zope.security.interfaces.IGroup). >>> getEvents(interfaces.IPrincipalsRemovedFromGroup)[-1] Adding the group sets the folder principal information. Let's use a different group name: >>> groups['G1'] = g1 >>> groups.getGroupsForPrincipal('auth.p1') (u'group.G1',) Here we see that the new name is reflected in the group information. An event is fired, as usual. >>> getEvents(interfaces.IPrincipalsAddedToGroup)[-1] In terms of member events (principals added and removed from groups), we have now seen that events are fired when a group information object is added and when it is removed from a group folder; and we have seen that events are fired when a principal is added to an already-registered group. Events are also fired when a principal is removed from an already-registered group. Let's quickly see some more examples. >>> g1.principals = ('auth.p1', 'auth.p3', 'auth.p4') >>> getEvents(interfaces.IPrincipalsAddedToGroup)[-1] >>> getEvents(interfaces.IPrincipalsRemovedFromGroup)[-1] >>> g1.principals = ('auth.p1', 'auth.p2') >>> getEvents(interfaces.IPrincipalsAddedToGroup)[-1] >>> getEvents(interfaces.IPrincipalsRemovedFromGroup)[-1] Groups can contain groups: >>> g2 = zope.pluggableauth.plugins.groupfolder.GroupInformation("Group Two") >>> groups['G2'] = g2 >>> g2.principals = ['auth.group.G1'] >>> groups.getGroupsForPrincipal('auth.group.G1') (u'group.G2',) >>> old = getEvents(interfaces.IPrincipalsAddedToGroup)[-1] >>> old Groups cannot contain cycles: >>> g1.principals = ('auth.p1', 'auth.p2', 'auth.group.G2') ... # doctest: +NORMALIZE_WHITESPACE Traceback (most recent call last): ... GroupCycle: (u'auth.group.G1', ['auth.p2', u'auth.group.G1', u'auth.group.G2']) Trying to do so does not fire an event. >>> getEvents(interfaces.IPrincipalsAddedToGroup)[-1] is old True They need not be hierarchical: >>> ga = zope.pluggableauth.plugins.groupfolder.GroupInformation("Group A") >>> groups['GA'] = ga >>> gb = zope.pluggableauth.plugins.groupfolder.GroupInformation("Group B") >>> groups['GB'] = gb >>> gb.principals = ['auth.group.GA'] >>> gc = zope.pluggableauth.plugins.groupfolder.GroupInformation("Group C") >>> groups['GC'] = gc >>> gc.principals = ['auth.group.GA'] >>> gd = zope.pluggableauth.plugins.groupfolder.GroupInformation("Group D") >>> groups['GD'] = gd >>> gd.principals = ['auth.group.GA', 'auth.group.GB'] >>> ga.principals = ['auth.p1'] Group folders provide a very simple search interface. They perform simple string searches on group titles and descriptions. >>> list(groups.search({'search': 'grou'})) # doctest: +NORMALIZE_WHITESPACE [u'group.G1', u'group.G2', u'group.GA', u'group.GB', u'group.GC', u'group.GD'] >>> list(groups.search({'search': 'two'})) [u'group.G2'] They also support batching: >>> list(groups.search({'search': 'grou'}, 2, 3)) [u'group.GA', u'group.GB', u'group.GC'] If you don't supply a search key, no results will be returned: >>> list(groups.search({})) [] Identifying groups ------------------ The function, `setGroupsForPrincipal`, is a subscriber to principal-creation events. It adds any group-folder-defined groups to users in those groups: >>> principal = principals.getPrincipal('auth.p1') >>> principal.groups [u'auth.group.G1', u'auth.group.GA'] Of course, this applies to groups too: >>> principal = principals.getPrincipal('auth.group.G1') >>> principal.id 'auth.group.G1' >>> principal.groups [u'auth.group.G2'] In addition to setting principal groups, the `setGroupsForPrincipal` function also declares the `IGroup` interface on groups: >>> [iface.__name__ for iface in interface.providedBy(principal)] ['IGroup', 'IGroupAwarePrincipal'] >>> [iface.__name__ ... for iface in interface.providedBy(principals.getPrincipal('auth.p1'))] ['IGroupAwarePrincipal'] Special groups -------------- Two special groups, Authenticated, and Everyone may apply to users created by the pluggable-authentication utility. There is a subscriber, specialGroups, that will set these groups on any non-group principals if IAuthenticatedGroup, or IEveryoneGroup utilities are provided. Lets define a group-aware principal: >>> import zope.security.interfaces >>> class GroupAwarePrincipal(Principal): ... interface.implements(zope.security.interfaces.IGroupAwarePrincipal) ... def __init__(self, id): ... Principal.__init__(self, id) ... self.groups = [] If we notify the subscriber with this principal, nothing will happen because the groups haven't been defined: >>> prin = GroupAwarePrincipal('x') >>> event = interfaces.FoundPrincipalCreated(42, prin, {}) >>> zope.pluggableauth.plugins.groupfolder.specialGroups(event) >>> prin.groups [] Now, if we define the Everybody group: >>> import zope.authentication.interfaces >>> class EverybodyGroup(Principal): ... interface.implements(zope.authentication.interfaces.IEveryoneGroup) >>> everybody = EverybodyGroup('all') >>> provideUtility(everybody, zope.authentication.interfaces.IEveryoneGroup) Then the group will be added to the principal: >>> zope.pluggableauth.plugins.groupfolder.specialGroups(event) >>> prin.groups ['all'] Similarly for the authenticated group: >>> class AuthenticatedGroup(Principal): ... interface.implements( ... zope.authentication.interfaces.IAuthenticatedGroup) >>> authenticated = AuthenticatedGroup('auth') >>> provideUtility(authenticated, zope.authentication.interfaces.IAuthenticatedGroup) Then the group will be added to the principal: >>> prin.groups = [] >>> zope.pluggableauth.plugins.groupfolder.specialGroups(event) >>> prin.groups.sort() >>> prin.groups ['all', 'auth'] These groups are only added to non-group principals: >>> prin.groups = [] >>> interface.directlyProvides(prin, zope.security.interfaces.IGroup) >>> zope.pluggableauth.plugins.groupfolder.specialGroups(event) >>> prin.groups [] And they are only added to group aware principals: >>> class SolitaryPrincipal: ... interface.implements(zope.security.interfaces.IPrincipal) ... id = title = description = '' >>> event = interfaces.FoundPrincipalCreated(42, SolitaryPrincipal(), {}) >>> zope.pluggableauth.plugins.groupfolder.specialGroups(event) >>> prin.groups [] Member-aware groups ------------------- The groupfolder includes a subscriber that gives group principals the zope.security.interfaces.IGroupAware interface and an implementation thereof. This allows groups to be able to get and set their members. Given an info object and a group... >>> class DemoGroupInformation(object): ... interface.implements( ... zope.pluggableauth.plugins.groupfolder.IGroupInformation) ... def __init__(self, title, description, principals): ... self.title = title ... self.description = description ... self.principals = principals ... >>> i = DemoGroupInformation( ... 'Managers', 'Taskmasters', ('joe', 'jane')) ... >>> info = zope.pluggableauth.plugins.groupfolder.GroupInfo( ... 'groups.managers', i) >>> class DummyGroup(object): ... interface.implements(IGroupAwarePrincipal) ... def __init__(self, id, title=u'', description=u''): ... self.id = id ... self.title = title ... self.description = description ... self.groups = [] ... >>> principal = DummyGroup('foo') >>> zope.security.interfaces.IMemberAwareGroup.providedBy(principal) False ...when you call the subscriber, it adds the two pseudo-methods to the principal and makes the principal provide the IMemberAwareGroup interface. >>> zope.pluggableauth.plugins.groupfolder.setMemberSubscriber( ... interfaces.FoundPrincipalCreated( ... 'dummy auth (ignored)', principal, info)) >>> principal.getMembers() ('joe', 'jane') >>> principal.setMembers(('joe', 'jane', 'jaimie')) >>> principal.getMembers() ('joe', 'jane', 'jaimie') >>> zope.security.interfaces.IMemberAwareGroup.providedBy(principal) True The two methods work with the value on the IGroupInformation object. >>> i.principals == principal.getMembers() True Limitation ========== The current group-folder design has an important limitation! There is no point in assigning principals to a group from a group folder unless the principal is from the same pluggable authentication utility. o If a principal is from a higher authentication utility, the user will not get the group definition. Why? Because the principals group assignments are set when the principal is authenticated. At that point, the current site is the site containing the principal definition. Groups defined in lower sites will not be consulted, o It is impossible to assign users from lower authentication utilities because they can't be seen when managing the group, from the site containing the group. A better design might be to store user-role assignments independent of the group definitions and to look for assignments during (url) traversal. This could get quite complex though. While it is possible to have multiple authentication utilities long a URL path, it is generally better to stick to a simpler model in which there is only one authentication utility along a URL path (in addition to the global utility, which is used for bootstrapping purposes). zope.pluggableauth-1.3/src/zope/pluggableauth/plugins/httpplugins.py0000644000175000017500000001102411524202361025235 0ustar jwjw00000000000000############################################################################## # # Copyright (c) 2004 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """PAS plugins related to HTTP """ __docformat__ = "reStructuredText" import base64 from zope.interface import implements, Interface from zope.publisher.interfaces.http import IHTTPRequest from zope.schema import TextLine from zope.pluggableauth import interfaces class IHTTPBasicAuthRealm(Interface): """HTTP Basic Auth Realm Represents the realm string that is used during basic HTTP authentication """ realm = TextLine(title=u'Realm', description=u'HTTP Basic Authentication Realm', required=True, default=u'Zope') class HTTPBasicAuthCredentialsPlugin(object): implements(interfaces.ICredentialsPlugin, IHTTPBasicAuthRealm) realm = 'Zope' protocol = 'http auth' def extractCredentials(self, request): """Extracts HTTP basic auth credentials from a request. First we need to create a request that contains some credentials. >>> from zope.publisher.browser import TestRequest >>> request = TestRequest( ... environ={'HTTP_AUTHORIZATION': u'Basic bWdyOm1ncnB3'}) Now create the plugin and get the credentials. >>> plugin = HTTPBasicAuthCredentialsPlugin() >>> plugin.extractCredentials(request) {'login': u'mgr', 'password': u'mgrpw'} Make sure we return `None`, if no authentication header has been specified. >>> print plugin.extractCredentials(TestRequest()) None Also, this plugin can *only* handle basic authentication. >>> request = TestRequest(environ={'HTTP_AUTHORIZATION': 'foo bar'}) >>> print plugin.extractCredentials(TestRequest()) None This plugin only works with HTTP requests. >>> from zope.publisher.base import TestRequest >>> print plugin.extractCredentials(TestRequest('/')) None """ if not IHTTPRequest.providedBy(request): return None if request._auth: if request._auth.lower().startswith(u'basic '): credentials = request._auth.split()[-1] login, password = base64.decodestring(credentials).split(':') return {'login': login.decode('utf-8'), 'password': password.decode('utf-8')} return None def challenge(self, request): """Issues an HTTP basic auth challenge for credentials. The challenge is issued by setting the appropriate response headers. To illustrate, we'll create a plugin: >>> plugin = HTTPBasicAuthCredentialsPlugin() The plugin adds its challenge to the HTTP response. >>> from zope.publisher.browser import TestRequest >>> request = TestRequest() >>> response = request.response >>> plugin.challenge(request) True >>> response._status 401 >>> response.getHeader('WWW-Authenticate', literal=True) 'basic realm="Zope"' Notice that the realm is quoted, as per RFC 2617. The plugin only works with HTTP requests. >>> from zope.publisher.base import TestRequest >>> request = TestRequest('/') >>> response = request.response >>> print plugin.challenge(request) False """ if not IHTTPRequest.providedBy(request): return False request.response.setHeader("WWW-Authenticate", 'basic realm="%s"' % self.realm, literal=True) request.response.setStatus(401) return True def logout(self, request): """Always returns False as logout is not supported by basic auth. >>> plugin = HTTPBasicAuthCredentialsPlugin() >>> from zope.publisher.browser import TestRequest >>> plugin.logout(TestRequest()) False """ return False zope.pluggableauth-1.3/src/zope/pluggableauth/plugins/generic.zcml0000644000175000017500000000042411524202361024607 0ustar jwjw00000000000000 zope.pluggableauth-1.3/src/zope/pluggableauth/plugins/principalfolder.py0000644000175000017500000002245011524202361026036 0ustar jwjw00000000000000############################################################################## # # Copyright (c) 2004 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """ZODB-based Authentication Source $Id: principalfolder.py 117625 2010-10-18 09:12:52Z janwijbrand $ """ __docformat__ = "reStructuredText" from persistent import Persistent 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.i18nmessageid import MessageFactory from zope.interface import implements, Interface from zope.password.interfaces import IPasswordManager from zope.schema import Text, TextLine, Password, Choice from zope.pluggableauth.interfaces import ( IAuthenticatorPlugin, IQuerySchemaSearch) from zope.pluggableauth.factories import PrincipalInfo _ = MessageFactory('zope') 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 >>> from zope.pluggableauth.factories import Principal >>> 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__ zope.pluggableauth-1.3/src/zope/pluggableauth/plugins/httpplugins.zcml0000644000175000017500000000042311524202361025553 0ustar jwjw00000000000000 zope.pluggableauth-1.3/src/zope/pluggableauth/plugins/principalfolder.txt0000644000175000017500000001120311524202361026217 0ustar jwjw00000000000000================ Principal Folder ================ Principal folders contain principal-information objects that contain principal information. We create an internal principal using the `InternalPrincipal` class: >>> from zope.pluggableauth.plugins.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.pluggableauth.plugins.principalfolder import PrincipalFolder >>> principals = PrincipalFolder('principal.') >>> principals['p1'] = p1 >>> principals['p2'] = p2 Authentication -------------- 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') We get back a principal id and supplementary information, including the principal title and description. Note that the principal id is a concatenation of the principal-folder prefix and the name of the principal-information object within the folder. None is returned if the credentials are invalid: >>> principals.authenticateCredentials({'login': 'login1', ... 'password': '1234'}) >>> principals.authenticateCredentials(42) Search ------ 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('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': ''})) [u'principal.p1', u'principal.p2'] >>> list(principals.search({'search': 'eek'})) [] >>> 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'] 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'}, 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' If there is a matching principal, the id is returned:: >>> principals.getIdByLogin("login1") u'principal.p1' Changing credentials -------------------- Credentials can be changed by modifying principal-information objects: >>> p1.login = 'bob' >>> p1.password = 'eek' >>> principals.authenticateCredentials({'login': 'bob', 'password': 'eek'}) PrincipalInfo(u'principal.p1') >>> principals.authenticateCredentials({'login': 'login1', ... 'password': 'eek'}) >>> principals.authenticateCredentials({'login': 'bob', ... 'password': '123'}) 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! If such an attempt is made, the data are unchanged: >>> 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'}) zope.pluggableauth-1.3/src/zope/pluggableauth/plugins/__init__.py0000644000175000017500000000000011524202361024403 0ustar jwjw00000000000000zope.pluggableauth-1.3/src/zope/pluggableauth/plugins/principalfolder.zcml0000644000175000017500000000047611524202361026357 0ustar jwjw00000000000000 zope.pluggableauth-1.3/src/zope/pluggableauth/plugins/session.py0000644000175000017500000002640711524202361024352 0ustar jwjw00000000000000############################################################################## # # Copyright (c) 2004 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """ Session-based and cookie-based extractor and challenge plugins. """ __docformat__ = 'restructuredtext' import persistent import transaction import zope.container.contained from urllib import urlencode from zope.interface import implements, Interface from zope.schema import TextLine from zope.publisher.interfaces.http import IHTTPRequest from zope.session.interfaces import ISession from zope.traversing.browser.absoluteurl import absoluteURL from zope.site import hooks from zope.pluggableauth.interfaces import ICredentialsPlugin class ISessionCredentials(Interface): """Interface for storing and accessing credentials in a session. We use a real class with interface here to prevent unauthorized access to the credentials. """ def __init__(login, password): pass def getLogin(): """Return login name.""" def getPassword(): """Return password.""" class SessionCredentials(object): """Credentials class for use with sessions. A session credential is created with a login and a password: >>> cred = SessionCredentials('scott', 'tiger') Logins are read using getLogin: >>> cred.getLogin() 'scott' and passwords with getPassword: >>> cred.getPassword() 'tiger' """ implements(ISessionCredentials) def __init__(self, login, password): self.login = login self.password = password def getLogin(self): return self.login def getPassword(self): return self.password def __str__(self): return self.getLogin() + ':' + self.getPassword() class IBrowserFormChallenger(Interface): """A challenger that uses a browser form to collect user credentials.""" loginpagename = TextLine( title=u'Loginpagename', description=u"""Name of the login form used by challenger. The form must provide 'login' and 'password' input fields. """, default=u'loginForm.html') loginfield = TextLine( title=u'Loginfield', description=u"Field of the login page in which is looked for the login user name.", default=u"login") passwordfield = TextLine( title=u'Passwordfield', description=u"Field of the login page in which is looked for the password.", default=u"password") class SessionCredentialsPlugin(persistent.Persistent, zope.container.contained.Contained): """A credentials plugin that uses Zope sessions to get/store credentials. To illustrate how a session plugin works, we'll first setup some session machinery: >>> from zope.session.session import RAMSessionDataContainer >>> from zope.pluggableauth.tests import sessionSetUp >>> sessionSetUp(RAMSessionDataContainer) This lets us retrieve the same session info from any test request, which simulates what happens when a user submits a session ID as a cookie. We also need a session plugin: >>> plugin = SessionCredentialsPlugin() A session plugin uses an ISession component to store the last set of credentials it gets from a request. Credentials can be retrieved from subsequent requests using the session-stored credentials. Our test environment is initially configured without credentials: >>> from zope.pluggableauth.tests import sessionSetUp >>> from zope.publisher.browser import TestRequest >>> request = TestRequest() >>> print plugin.extractCredentials(request) None We must explicitly provide credentials once so the plugin can store them in a session: >>> request = TestRequest(login='scott', password='tiger') >>> plugin.extractCredentials(request) {'login': 'scott', 'password': 'tiger'} Subsequent requests now have access to the credentials even if they're not explicitly in the request: >>> plugin.extractCredentials(TestRequest()) {'login': 'scott', 'password': 'tiger'} We can always provide new credentials explicitly in the request: >>> plugin.extractCredentials(TestRequest( ... login='harry', password='hirsch')) {'login': 'harry', 'password': 'hirsch'} and these will be used on subsequent requests: >>> plugin.extractCredentials(TestRequest()) {'login': 'harry', 'password': 'hirsch'} We can also change the fields from which the credentials are extracted: >>> plugin.loginfield = "my_new_login_field" >>> plugin.passwordfield = "my_new_password_field" Now we build a request that uses the new fields: >>> request = TestRequest(my_new_login_field='luke', ... my_new_password_field='the_force') The plugin now extracts the credentials information from these new fields: >>> plugin.extractCredentials(request) {'login': 'luke', 'password': 'the_force'} Finally, we clear the session credentials using the logout method: >>> plugin.logout(TestRequest()) True >>> print plugin.extractCredentials(TestRequest()) None Instances are persistent: >>> import persistent.interfaces >>> persistent.interfaces.IPersistent.providedBy(plugin) True >>> isinstance(plugin, persistent.Persistent) True Instances provide IContained: >>> import zope.container.interfaces >>> zope.container.interfaces.IContained.providedBy(plugin) True """ implements(ICredentialsPlugin, IBrowserFormChallenger) loginpagename = 'loginForm.html' loginfield = 'login' passwordfield = 'password' def extractCredentials(self, request): """Extracts credentials from a session if they exist.""" if not IHTTPRequest.providedBy(request): return None session = ISession(request) sessionData = session.get( 'zope.pluggableauth.browserplugins') login = request.get(self.loginfield, None) password = request.get(self.passwordfield, None) credentials = None if login and password: credentials = self._makeCredentials(login, password) elif not sessionData: return None sessionData = session[ 'zope.pluggableauth.browserplugins'] if credentials: sessionData['credentials'] = credentials else: credentials = sessionData.get('credentials', None) if not credentials: return None return {'login': credentials.getLogin(), 'password': credentials.getPassword()} def _makeCredentials(self, login, password): """Create an ISessionCredentials. You can override this if you desire a different implementation, e.g. one that encrypts the password, so it's not stored in plain text in the ZODB. """ return SessionCredentials(login, password) def challenge(self, request): """Challenges by redirecting to a login form. To illustrate, we'll create a test request: >>> from zope.publisher.browser import TestRequest >>> request = TestRequest() and confirm its response's initial status and 'location' header: >>> request.response.getStatus() 599 >>> request.response.getHeader('location') When we issue a challenge using a session plugin: >>> plugin = SessionCredentialsPlugin() >>> plugin.challenge(request) True we get a redirect: >>> request.response.getStatus() 302 >>> request.response.getHeader('location') 'http://127.0.0.1/@@loginForm.html?camefrom=http%3A%2F%2F127.0.0.1' The plugin redirects to the page defined by the loginpagename attribute: >>> plugin.loginpagename = 'mylogin.html' >>> plugin.challenge(request) True >>> request.response.getHeader('location') 'http://127.0.0.1/@@mylogin.html?camefrom=http%3A%2F%2F127.0.0.1' It also provides the request URL as a 'camefrom' GET style parameter. To illustrate, we'll pretend we've traversed a couple names: >>> env = { ... 'REQUEST_URI': '/foo/bar/folder/page%201.html?q=value', ... 'QUERY_STRING': 'q=value' ... } >>> request = TestRequest(environ=env) >>> request._traversed_names = [u'foo', u'bar'] >>> request._traversal_stack = [u'page 1.html', u'folder'] >>> request['REQUEST_URI'] '/foo/bar/folder/page%201.html?q=value' When we challenge: >>> plugin.challenge(request) True We see the 'camefrom' points to the requested URL: >>> request.response.getHeader('location') 'http://127.0.0.1/@@mylogin.html?camefrom=http%3A%2F%2F127.0.0.1%2Ffoo%2Fbar%2Ffolder%2Fpage+1.html%3Fq%3Dvalue' This can be used by the login form to redirect the user back to the originating URL upon successful authentication. Now that the 'camefrom' is an absolute URL, quickly demonstrate that 'camefrom' information that inadvertently points to a different host, will by default not be trusted in a redirect: >>> camefrom = request.response.getHeader('location') >>> request.response.redirect(camefrom) 'http://127.0.0.1/@@mylogin.html?camefrom=http%3A%2F%2F127.0.0.1%2Ffoo%2Fbar%2Ffolder%2Fpage+1.html%3Fq%3Dvalue' >>> suspicious_camefrom = 'http://example.com/foobar' >>> request.response.redirect(suspicious_camefrom) # doctest: +ELLIPSIS Traceback (most recent call last): ... ValueError: Untrusted redirect to host 'example.com:80' not allowed. """ if not IHTTPRequest.providedBy(request): return False site = hooks.getSite() # We need the traversal stack to complete the 'camefrom' parameter stack = request.getTraversalStack() stack.reverse() # Better to add the query string, if present query = request.get('QUERY_STRING') camefrom = '/'.join([request.getURL()] + stack) if query: camefrom = camefrom + '?' + query url = '%s/@@%s?%s' % (absoluteURL(site, request), self.loginpagename, urlencode({'camefrom': camefrom})) request.response.redirect(url) return True def logout(self, request): """Performs logout by clearing session data credentials.""" if not IHTTPRequest.providedBy(request): return False sessionData = ISession(request)[ 'zope.pluggableauth.browserplugins'] sessionData['credentials'] = None transaction.commit() return True zope.pluggableauth-1.3/src/zope/pluggableauth/plugins/ftpplugins.py0000644000175000017500000000424211524202361025053 0ustar jwjw00000000000000############################################################################## # # Copyright (c) 2004 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """PAS plugins related to FTP """ __docformat__ = 'restructuredtext' from zope.interface import implements from zope.pluggableauth import interfaces from zope.publisher.interfaces.ftp import IFTPRequest class FTPCredentialsPlugin(object): implements(interfaces.ICredentialsPlugin) def extractCredentials(self, request): """Extracts the FTP credentials from a request. First we need to create a FTP request that contains some credentials. Note the path is a required in the envirnoment. >>> from zope.publisher.ftp import FTPRequest >>> from StringIO import StringIO >>> request = FTPRequest(StringIO(''), ... {'credentials': ('bob', '123'), ... 'path': '/a/b/c'}) Now we create the plugin and get the credentials. >>> plugin = FTPCredentialsPlugin() >>> plugin.extractCredentials(request) {'login': u'bob', 'password': u'123'} This only works for FTPRequests. >>> from zope.publisher.base import TestRequest >>> print plugin.extractCredentials(TestRequest('/')) None """ if not IFTPRequest.providedBy(request): return None if request._auth: login, password = request._auth return {'login': login.decode('utf-8'), 'password': password.decode('utf-8')} return None def challenge(self, request): return False def logout(self, request): return False zope.pluggableauth-1.3/src/zope/pluggableauth/plugins/groupfolder.py0000644000175000017500000003126211524202361025212 0ustar jwjw00000000000000############################################################################## # # Copyright (c) 2004 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Zope Groups Folder implementation $Id: groupfolder.py 117625 2010-10-18 09:12:52Z janwijbrand $ """ import BTrees.OOBTree import persistent from zope import interface, event, schema, component from zope.interface import alsoProvides, implements from zope.security.interfaces import ( IGroup, IGroupAwarePrincipal, IMemberAwareGroup) from zope.container.btree import BTreeContainer import zope.container.constraints import zope.container.interfaces from zope.i18nmessageid import MessageFactory import zope.authentication.principal from zope.authentication.interfaces import ( IAuthentication, IAuthenticatedGroup, IEveryoneGroup) from zope.pluggableauth.interfaces import ( IPrincipalInfo, IFoundPrincipalCreated, IAuthenticatorPlugin, IQuerySchemaSearch, IPrincipalsAddedToGroup, IPrincipalsRemovedFromGroup, IGroupAdded) from zope.pluggableauth import factories _ = MessageFactory('zope') 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(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( IAuthenticatorPlugin, 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( PrincipalsAddedToGroup( value.principals, self.__parent__.prefix + group_id)) group = factories.Principal(self.prefix + name) event.notify(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( 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( PrincipalsRemovedFromGroup( removed, self.__parent__.__parent__.prefix + group_id)) if added: event.notify( 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(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) 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 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): implements(IPrincipalsAddedToGroup) class PrincipalsRemovedFromGroup(AbstractMembersChanged): implements(IPrincipalsRemovedFromGroup) zope.pluggableauth-1.3/src/zope/pluggableauth/plugins/idpicker.py0000644000175000017500000000657511524202361024465 0ustar jwjw00000000000000# -*- coding: utf-8 -*- # ############################################################################## # # Copyright (c) 2004 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Helper base class that picks principal ids $Id: idpicker.py 117492 2010-10-13 08:17:55Z janwijbrand $ """ __docformat__ = 'restructuredtext' import re from zope.container.contained import NameChooser from zope.exceptions.interfaces import UserError from zope.i18nmessageid import MessageFactory _ = MessageFactory('zope') 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.pluggableauth.plugins.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.pluggableauth.plugins.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 zope.pluggableauth-1.3/src/zope/pluggableauth/README.txt0000644000175000017500000004345011524202361022327 0ustar jwjw00000000000000================================ Pluggable-Authentication Utility ================================ The Pluggable-Authentication Utility (PAU) provides a framework for authenticating principals and associating information with them. It uses plugins and subscribers to get its work done. For a pluggable-authentication utility to be used, it should be registered as a utility providing the `zope.authentication.interfaces.IAuthentication` interface. Authentication -------------- The primary job of PAU is to authenticate principals. It uses two types of plug-ins in its work: - Credentials 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 credentials. For example, a 'session' credentials plugin reads credentials from a session (the "extraction"). If it cannot find credentials, it will redirect the user to a login form in order to provide them (the "challenge"). Authenticator plugins are responsible for authenticating the credentials extracted by a credentials plugin. They are also typically able to create principal objects for credentials they successfully authenticate. Given a request object, the PAU returns a principal object, if it can. The PAU does this by first iterateing through its credentials plugins to obtain a set of credentials. If it gets credentials, it iterates through its authenticator plugins to authenticate them. If an authenticator succeeds in authenticating a set of credentials, the PAU uses the authenticator to create a principal corresponding to the credentials. The authenticator notifies subscribers if an authenticated principal is created. Subscribers are responsible for adding data, especially groups, to the principal. Typically, if a subscriber adds data, it should also add corresponding interface declarations. Simple Credentials Plugin ~~~~~~~~~~~~~~~~~~~~~~~~~ To illustrate, we'll create a simple credentials plugin:: >>> from zope import interface >>> from zope.pluggableauth.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 As a plugin, MyCredentialsPlugin needs to be registered as a named utility:: >>> myCredentialsPlugin = MyCredentialsPlugin() >>> provideUtility(myCredentialsPlugin, name='My Credentials Plugin') Simple Authenticator Plugin ~~~~~~~~~~~~~~~~~~~~~~~~~~~ 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 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 As with the credentials plugin, the authenticator plugin must be registered as a named utility:: >>> myAuthenticatorPlugin = MyAuthenticatorPlugin() >>> provideUtility(myAuthenticatorPlugin, name='My Authenticator Plugin') Configuring a PAU ~~~~~~~~~~~~~~~~~ Finally, we'll create the PAU itself:: >>> from zope.pluggableauth import authentication >>> pau = authentication.PluggableAuthentication('xyz_') and configure it with the two plugins:: >>> pau.credentialsPlugins = ('My Credentials Plugin', ) >>> pau.authenticatorPlugins = ('My Authenticator Plugin', ) Using the PAU to Authenticate ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ >>> from zope.pluggableauth.factories import AuthenticatedPrincipalFactory >>> provideAdapter(AuthenticatedPrincipalFactory) We can now use the PAU to authenticate a sample request:: >>> from zope.publisher.browser import TestRequest >>> print pau.authenticate(TestRequest()) None In this case, we cannot authenticate an empty request. In the same way, we will not be able to authenticate a request with the wrong credentials:: >>> 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') we get an authenticated principal. Multiple Authenticator Plugins ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ The PAU works with multiple authenticator plugins. It uses each plugin, in the order specified in the PAU's authenticatorPlugins attribute, to authenticate a set of credentials. 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', '') >>> provideUtility(MyAuthenticatorPlugin2(), name='My Authenticator Plugin 2') If we put it before the original authenticator:: >>> pau.authenticatorPlugins = ( ... 'My Authenticator Plugin 2', ... 'My Authenticator Plugin') Then it will be given the first opportunity to authenticate a request:: >>> 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 When we change the order of the authenticator plugins:: >>> pau.authenticatorPlugins = ( ... 'My Authenticator Plugin', ... 'My Authenticator Plugin 2') we see that our original plugin is now acting first:: >>> 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') Multiple Credentials Plugins ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ As with with authenticators, we can specify multiple credentials plugins. To illustrate, we'll create a credentials plugin that extracts credentials from a request form:: >>> 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') and insert the new credentials plugin before the existing plugin:: >>> pau.credentialsPlugins = ( ... 'Form Credentials Plugin', ... 'My Credentials Plugin') The PAU will use each plugin in order to try and obtain credentials from a request:: >>> 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' - 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' - Succeeded in authenticating with 'My Authenticator Plugin 2' Let's try a different scenario:: >>> pau.authenticate(TestRequest(credentials='secretcode')) Principal('xyz_bob') In this case, the PAU went through these steps:: - Get credentials using 'Form Credentials Plugin' - Failed to get credentials using 'Form Credentials Plugin', try 'My Credentials Plugin' - Got 'scecretcode' credentials using 'My Credentials Plugin', try to authenticate using 'My Authenticator Plugin' - Succeeded in authenticating with 'My Authenticator Plugin' Let's try a slightly more complex scenario:: >>> 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' - 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 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' - 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' - Succeeded in authenticating with 'My Authenticator Plugin 2' (shouts and cheers!) Multiple Authenticator Plugins ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ As with the other operations we've seen, the PAU uses multiple plugins to find a principal. If the first authenticator plugin can't find the requested principal, the next plugin is used, and so on. >>> class AnotherAuthenticatorPlugin: ... ... 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 To illustrate, we'll create and register two authenticators:: >>> authenticator1 = AnotherAuthenticatorPlugin() >>> provideUtility(authenticator1, name='Authentication Plugin 1') >>> authenticator2 = AnotherAuthenticatorPlugin() >>> provideUtility(authenticator2, name='Authentication Plugin 2') and add a principal to them:: >>> authenticator1.add('bob', 'Bob', 'A nice guy', 'b0b') >>> authenticator1.add('white', 'White Spy', 'Sneaky', 'deathtoblack') >>> authenticator2.add('black', 'Black Spy', 'Also sneaky', 'deathtowhite') When we configure the PAU to use both searchable authenticators (note the order):: >>> pau.authenticatorPlugins = ( ... 'Authentication Plugin 2', ... 'Authentication Plugin 1') we register the factories for our principals:: >>> from zope.pluggableauth.factories import FoundPrincipalFactory >>> provideAdapter(FoundPrincipalFactory) we see how the PAU uses both plugins:: >>> pau.getPrincipal('xyz_white') Principal('xyz_white') >>> 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:: >>> authenticator2.add('white', 'White Rider', '', 'r1der') >>> pau.getPrincipal('xyz_white').title 'White Rider' If we change the order of the plugins:: >>> pau.authenticatorPlugins = ( ... 'Authentication Plugin 1', ... 'Authentication Plugin 2') we get a different principal for ID 'white':: >>> pau.getPrincipal('xyz_white').title 'White Spy' Issuing a Challenge ------------------- Part of PAU's IAuthentication contract is to challenge the user for 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 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 PAU handles the credentials challenge by delegating to its credentials plugins. Currently, the PAU is configured with the credentials plugins that don't perform any action when asked to challenge (see above the 'challenge' methods). To illustrate challenges, we'll subclass an existing credentials plugin and do something in its 'challenge':: >>> 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('advancedlogin.html'), ... name='Advanced Login Form Plugin') and configure the PAU to use them:: >>> pau.credentialsPlugins = ( ... 'Simple Login Form Plugin', ... 'Advanced Login Form Plugin') Now when we call 'unauthorized' on the PAU:: >>> 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' We can change the challenge policy by reordering the plugins:: >>> pau.credentialsPlugins = ( ... 'Advanced Login Form Plugin', ... 'Simple Login Form Plugin') Now when we call 'unauthorized':: >>> 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' Challenge Protocols ~~~~~~~~~~~~~~~~~~~ Sometimes, we want multiple challengers to work together. For example, the HTTP specification allows multiple challenges to be issued in a response. A challenge plugin can provide a `challengeProtocol` attribute that effectively groups related plugins together for challenging. If a plugin returns `True` from its challenge and provides a non-None challengeProtocol, subsequent plugins in the credentialsPlugins list that have the same challenge protocol will also be used to challenge. Without a challengeProtocol, only the first plugin to succeed in a challenge will be used. 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 and register a couple instances as utilities:: >>> provideUtility(XChallengeCredentialsPlugin('basic'), ... name='Basic 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') and call 'unauthorized':: >>> 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' Pluggable-Authentication Prefixes --------------------------------- Principal ids are required to be unique system wide. Plugins will often provide options for providing id prefixes, so that different sets of plugins provide unique ids within a PAU. If there are multiple pluggable-authentication utilities in a system, it's a good idea to give each PAU a unique prefix, so 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', ) When we create a request and try to authenticate:: >>> 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_41') OddPrincipal('mypas_41', "{'int': 41}") zope.pluggableauth-1.3/src/zope/pluggableauth/__init__.py0000644000175000017500000000135311524202361022736 0ustar jwjw00000000000000############################################################################## # # Copyright (c) 2004 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Pluggable Authentication Utility """ from zope.pluggableauth.authentication import PluggableAuthentication zope.pluggableauth-1.3/src/zope/pluggableauth/configure.zcml0000644000175000017500000000046211524202361023475 0ustar jwjw00000000000000 zope.pluggableauth-1.3/src/zope/pluggableauth/factories.py0000644000175000017500000002466011524202361023164 0ustar jwjw00000000000000############################################################################## # # Copyright (c) 2004 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Principals related factories """ __docformat__ = "reStructuredText" from zope import component from zope import interface from zope.authentication.interfaces import IAuthentication from zope.security.interfaces import IGroupClosureAwarePrincipal as IPrincipal from zope.event import notify from zope.pluggableauth import interfaces from zope.publisher.interfaces import IRequest class PrincipalInfo(object): """An implementation of IPrincipalInfo used by the principal folder. A principal info is created with id, login, title, and description: >>> info = PrincipalInfo('users.foo', 'foo', 'Foo', 'An over-used term.') >>> info PrincipalInfo('users.foo') >>> info.id 'users.foo' >>> info.login 'foo' >>> info.title 'Foo' >>> info.description 'An over-used term.' """ interface.implements(interfaces.IPrincipalInfo) def __init__(self, id, login, title, description): self.id = id self.login = login self.title = title self.description = description def __repr__(self): return 'PrincipalInfo(%r)' % self.id class Principal(object): """A group-aware implementation of zope.security.interfaces.IPrincipal. A principal is created with an ID: >>> p = Principal(1) >>> p Principal(1) >>> p.id 1 title and description may also be provided: >>> p = Principal('george', 'George', 'A site member.') >>> p Principal('george') >>> p.id 'george' >>> p.title 'George' >>> p.description 'A site member.' The `groups` is a simple list, filled in by plugins. >>> p.groups [] The `allGroups` attribute is a readonly iterable of the full closure of the groups in the `groups` attribute--that is, if the principal is a direct member of the 'Administrators' group, and the 'Administrators' group is a member of the 'Reviewers' group, then p.groups would be ['Administrators'] and list(p.allGroups) would be ['Administrators', 'Reviewers']. To illustrate this, we'll need to set up a dummy authentication utility, and a few principals. Our main principal will also gain some groups, as if plugins had added the groups to the list. This is all setup--skip to the next block to actually see `allGroups` in action. >>> p.groups.extend( ... ['content_administrators', 'zope_3_project', ... 'list_administrators', 'zpug']) >>> editor = Principal('editors', 'Content Editors') >>> creator = Principal('creators', 'Content Creators') >>> reviewer = Principal('reviewers', 'Content Reviewers') >>> reviewer.groups.extend(['editors', 'creators']) >>> usermanager = Principal('user_managers', 'User Managers') >>> contentAdmin = Principal( ... 'content_administrators', 'Content Administrators') >>> contentAdmin.groups.extend(['reviewers', 'user_managers']) >>> zope3Dev = Principal('zope_3_project', 'Zope 3 Developer') >>> zope3ListAdmin = Principal( ... 'zope_3_list_admin', 'Zope 3 List Administrators') >>> zope3ListAdmin.groups.append('zope_3_project') # duplicate, but ... # should only appear in allGroups once >>> listAdmin = Principal('list_administrators', 'List Administrators') >>> listAdmin.groups.append('zope_3_list_admin') >>> zpugMember = Principal('zpug', 'ZPUG Member') >>> martians = Principal('martians', 'Martians') # not in p's allGroups >>> group_data = dict((p.id, p) for p in ( ... editor, creator, reviewer, usermanager, contentAdmin, ... zope3Dev, zope3ListAdmin, listAdmin, zpugMember, martians)) >>> class DemoAuth(object): ... interface.implements(IAuthentication) ... def getPrincipal(self, id): ... return group_data[id] ... >>> demoAuth = DemoAuth() >>> component.provideUtility(demoAuth) Now, we have a user with the following groups (lowest level are p's direct groups, and lines show membership): editors creators \------/ | zope_3_project (duplicate) reviewers user_managers | \---------/ zope_3_list_admin | | content_administrators zope_3_project list_administrators zpug The allGroups value includes all of the shown groups, and with 'zope_3_project' only appearing once. >>> p.groups # doctest: +NORMALIZE_WHITESPACE ['content_administrators', 'zope_3_project', 'list_administrators', 'zpug'] >>> list(p.allGroups) # doctest: +NORMALIZE_WHITESPACE ['content_administrators', 'reviewers', 'editors', 'creators', 'user_managers', 'zope_3_project', 'list_administrators', 'zope_3_list_admin', 'zpug'] """ interface.implements(IPrincipal) def __init__(self, id, title=u'', description=u''): self.id = id self.title = title self.description = description self.groups = [] def __repr__(self): return 'Principal(%r)' % self.id @property def allGroups(self): if self.groups: seen = set() principals = component.getUtility(IAuthentication) stack = [iter(self.groups)] while stack: try: group_id = stack[-1].next() except StopIteration: stack.pop() else: if group_id not in seen: yield group_id seen.add(group_id) group = principals.getPrincipal(group_id) stack.append(iter(group.groups)) class AuthenticatedPrincipalFactory(object): """Creates 'authenticated' principals. An authenticated principal is created as a result of an authentication operation. To use the factory, create it with the info (interfaces.IPrincipalInfo) of the principal to create and a request: >>> info = PrincipalInfo('users.mary', 'mary', 'Mary', 'The site admin.') >>> from zope.publisher.base import TestRequest >>> request = TestRequest('/') >>> factory = AuthenticatedPrincipalFactory(info, request) The factory must be called with a pluggable-authentication object: >>> class Auth: ... prefix = 'auth.' >>> auth = Auth() >>> principal = factory(auth) The factory uses the pluggable authentication and the info to create a principal with the same ID, title, and description: >>> principal.id 'auth.users.mary' >>> principal.title 'Mary' >>> principal.description 'The site admin.' It also fires an AuthenticatedPrincipalCreatedEvent: >>> from zope.component.eventtesting import getEvents >>> [event] = getEvents(interfaces.IAuthenticatedPrincipalCreated) >>> event.principal is principal, event.authentication is auth (True, True) >>> event.info PrincipalInfo('users.mary') >>> event.request is request True Listeners can subscribe to this event to perform additional operations when the authenticated principal is created. For information on how factories are used in the authentication process, see README.txt. """ component.adapts(interfaces.IPrincipalInfo, IRequest) interface.implements(interfaces.IAuthenticatedPrincipalFactory) def __init__(self, info, request): self.info = info self.request = request def __call__(self, authentication): principal = Principal(authentication.prefix + self.info.id, self.info.title, self.info.description) notify(interfaces.AuthenticatedPrincipalCreated( authentication, principal, self.info, self.request)) return principal class FoundPrincipalFactory(object): """Creates 'found' principals. A 'found' principal is created as a result of a principal lookup. To use the factory, create it with the info (interfaces.IPrincipalInfo) of the principal to create: >>> info = PrincipalInfo('users.sam', 'sam', 'Sam', 'A site user.') >>> factory = FoundPrincipalFactory(info) The factory must be called with a pluggable-authentication object: >>> class Auth: ... prefix = 'auth.' >>> auth = Auth() >>> principal = factory(auth) The factory uses the pluggable-authentication object and the info to create a principal with the same ID, title, and description: >>> principal.id 'auth.users.sam' >>> principal.title 'Sam' >>> principal.description 'A site user.' It also fires a FoundPrincipalCreatedEvent: >>> from zope.component.eventtesting import getEvents >>> [event] = getEvents(interfaces.IFoundPrincipalCreated) >>> event.principal is principal, event.authentication is auth (True, True) >>> event.info PrincipalInfo('users.sam') Listeners can subscribe to this event to perform additional operations when the 'found' principal is created. For information on how factories are used in the authentication process, see README.txt. """ component.adapts(interfaces.IPrincipalInfo) interface.implements(interfaces.IFoundPrincipalFactory) def __init__(self, info): self.info = info def __call__(self, authentication): principal = Principal(authentication.prefix + self.info.id, self.info.title, self.info.description) notify(interfaces.FoundPrincipalCreated(authentication, principal, self.info)) return principal zope.pluggableauth-1.3/src/zope/pluggableauth/authentication.py0000644000175000017500000001240711524202361024220 0ustar jwjw00000000000000############################################################################## # # Copyright (c) 2004 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Pluggable Authentication Utility implementation """ from zope import component from zope.authentication.interfaces import ( IAuthentication, PrincipalLookupError) from zope.container.btree import BTreeContainer from zope.interface import implements from zope.pluggableauth import interfaces from zope.schema.interfaces import ISourceQueriables from zope.site.next import queryNextUtility class PluggableAuthentication(BTreeContainer): implements( IAuthentication, interfaces.IPluggableAuthentication, ISourceQueriables) authenticatorPlugins = () credentialsPlugins = () def __init__(self, prefix=''): super(PluggableAuthentication, self).__init__() self.prefix = prefix def _plugins(self, names, interface): for name in names: plugin = self.get(name) if not interface.providedBy(plugin): plugin = component.queryUtility(interface, name, context=self) if plugin is not None: yield name, plugin def getAuthenticatorPlugins(self): return self._plugins( self.authenticatorPlugins, interfaces.IAuthenticatorPlugin) def getCredentialsPlugins(self): return self._plugins( self.credentialsPlugins, interfaces.ICredentialsPlugin) def authenticate(self, request): authenticatorPlugins = [p for n, p in self.getAuthenticatorPlugins()] for name, credplugin in self.getCredentialsPlugins(): credentials = credplugin.extractCredentials(request) for authplugin in authenticatorPlugins: if authplugin is None: continue info = authplugin.authenticateCredentials(credentials) if info is None: continue info.credentialsPlugin = credplugin info.authenticatorPlugin = authplugin principal = component.getMultiAdapter((info, request), interfaces.IAuthenticatedPrincipalFactory)(self) principal.id = self.prefix + info.id return principal return None def getPrincipal(self, id): if not id.startswith(self.prefix): next = queryNextUtility(self, IAuthentication) if next is None: raise PrincipalLookupError(id) return next.getPrincipal(id) id = id[len(self.prefix):] for name, authplugin in self.getAuthenticatorPlugins(): info = authplugin.principalInfo(id) if info is None: continue info.credentialsPlugin = None info.authenticatorPlugin = authplugin principal = interfaces.IFoundPrincipalFactory(info)(self) principal.id = self.prefix + info.id return principal next = queryNextUtility(self, IAuthentication) if next is not None: return next.getPrincipal(self.prefix + id) raise PrincipalLookupError(id) def getQueriables(self): for name, authplugin in self.getAuthenticatorPlugins(): queriable = component.queryMultiAdapter((authplugin, self), interfaces.IQueriableAuthenticator) if queriable is not None: yield name, queriable def unauthenticatedPrincipal(self): return None def unauthorized(self, id, request): challengeProtocol = None for name, credplugin in self.getCredentialsPlugins(): protocol = getattr(credplugin, 'challengeProtocol', None) if challengeProtocol is None or protocol == challengeProtocol: if credplugin.challenge(request): if protocol is None: return elif challengeProtocol is None: challengeProtocol = protocol if challengeProtocol is None: next = queryNextUtility(self, IAuthentication) if next is not None: next.unauthorized(id, request) def logout(self, request): challengeProtocol = None for name, credplugin in self.getCredentialsPlugins(): protocol = getattr(credplugin, 'challengeProtocol', None) if challengeProtocol is None or protocol == challengeProtocol: if credplugin.logout(request): if protocol is None: return elif challengeProtocol is None: challengeProtocol = protocol if challengeProtocol is None: next = queryNextUtility(self, IAuthentication) if next is not None: next.logout(request) zope.pluggableauth-1.3/src/zope/pluggableauth/interfaces.py0000644000175000017500000002516211524202361023326 0ustar jwjw00000000000000############################################################################## # # Copyright (c) 2004 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Pluggable Authentication Utility Interfaces """ __docformat__ = "reStructuredText" import zope.interface from zope.i18nmessageid import MessageFactory from zope.authentication.interfaces import ILogout from zope.container.constraints import contains, containers from zope.container.interfaces import IContainer _ = MessageFactory('zope') class IPlugin(zope.interface.Interface): """A plugin for a pluggable authentication component.""" class IPluggableAuthentication(ILogout, IContainer): """Provides authentication services with the help of various plugins. IPluggableAuthentication implementations will also implement zope.authentication.interfaces.IAuthentication. The `authenticate` method of this interface in an IPluggableAuthentication should annotate the IPrincipalInfo with the credentials plugin and authentication plugin used. The `getPrincipal` method should annotate the IPrincipalInfo with the authentication plugin used. """ contains(IPlugin) credentialsPlugins = zope.schema.List( title=_('Credentials Plugins'), description=_("""Used for extracting credentials. Names may be of ids of non-utility ICredentialsPlugins contained in the IPluggableAuthentication, or names of registered ICredentialsPlugins utilities. Contained non-utility ids mask utility names."""), value_type=zope.schema.Choice(vocabulary='CredentialsPlugins'), default=[], ) authenticatorPlugins = zope.schema.List( title=_('Authenticator Plugins'), description=_("""Used for converting credentials to principals. Names may be of ids of non-utility IAuthenticatorPlugins contained in the IPluggableAuthentication, or names of registered IAuthenticatorPlugins utilities. Contained non-utility ids mask utility names."""), value_type=zope.schema.Choice(vocabulary='AuthenticatorPlugins'), default=[], ) def getCredentialsPlugins(): """Return iterable of (plugin name, actual credentials plugin) pairs. Looks up names in credentialsPlugins as contained ids of non-utility ICredentialsPlugins first, then as registered ICredentialsPlugin utilities. Names that do not resolve are ignored.""" def getAuthenticatorPlugins(): """Return iterable of (plugin name, actual authenticator plugin) pairs. Looks up names in authenticatorPlugins as contained ids of non-utility IAuthenticatorPlugins first, then as registered IAuthenticatorPlugin utilities. Names that do not resolve are ignored.""" prefix = zope.schema.TextLine( title=_('Prefix'), default=u'', required=True, readonly=True, ) def logout(request): """Performs a logout by delegating to its authenticator plugins.""" class ICredentialsPlugin(IPlugin): """Handles credentials extraction and challenges per request.""" containers(IPluggableAuthentication) challengeProtocol = zope.interface.Attribute( """A challenge protocol used by the plugin. If a credentials plugin works with other credentials pluggins, it and the other cooperating plugins should specify a common (non-None) protocol. If a plugin returns True from its challenge method, then other credentials plugins will be called only if they have the same protocol. """) def extractCredentials(request): """Ties to extract credentials from a request. A return value of None indicates that no credentials could be found. Any other return value is treated as valid credentials. """ def challenge(request): """Possibly issues a challenge. This is typically done in a protocol-specific way. If a challenge was issued, return True, otherwise return False. """ def logout(request): """Possibly logout. If a logout was performed, return True, otherwise return False. """ class IAuthenticatorPlugin(IPlugin): """Authenticates a principal using credentials. An authenticator may also be responsible for providing information about and creating principals. """ containers(IPluggableAuthentication) def authenticateCredentials(credentials): """Authenticates credentials. If the credentials can be authenticated, return an object that provides IPrincipalInfo. If the plugin cannot authenticate the credentials, returns None. """ def principalInfo(id): """Returns an IPrincipalInfo object for the specified principal id. If the plugin cannot find information for the id, returns None. """ class IPrincipalInfo(zope.interface.Interface): """Minimal information about a principal.""" id = zope.interface.Attribute("The principal id.") title = zope.interface.Attribute("The principal title.") description = zope.interface.Attribute("A description of the principal.") credentialsPlugin = zope.interface.Attribute( """Plugin used to generate the credentials for this principal info. Optional. Should be set in IPluggableAuthentication.authenticate. """) authenticatorPlugin = zope.interface.Attribute( """Plugin used to authenticate the credentials for this principal info. Optional. Should be set in IPluggableAuthentication.authenticate and IPluggableAuthentication.getPrincipal. """) class IPrincipalFactory(zope.interface.Interface): """A principal factory.""" def __call__(authentication): """Creates a principal. The authentication utility that called the factory is passed and should be included in the principal-created event. """ class IFoundPrincipalFactory(IPrincipalFactory): """A found principal factory.""" class IAuthenticatedPrincipalFactory(IPrincipalFactory): """An authenticated principal factory.""" class IPrincipalCreated(zope.interface.Interface): """A principal has been created.""" principal = zope.interface.Attribute("The principal that was created") authentication = zope.interface.Attribute( "The authentication utility that created the principal") info = zope.interface.Attribute("An object providing IPrincipalInfo.") class IAuthenticatedPrincipalCreated(IPrincipalCreated): """A principal has been created by way of an authentication operation.""" request = zope.interface.Attribute( "The request the user was authenticated against") class AuthenticatedPrincipalCreated: """ >>> from zope.interface.verify import verifyObject >>> event = AuthenticatedPrincipalCreated("authentication", "principal", ... "info", "request") >>> verifyObject(IAuthenticatedPrincipalCreated, event) True """ zope.interface.implements(IAuthenticatedPrincipalCreated) def __init__(self, authentication, principal, info, request): self.authentication = authentication self.principal = principal self.info = info self.request = request class IFoundPrincipalCreated(IPrincipalCreated): """A principal has been created by way of a search operation.""" class FoundPrincipalCreated: """ >>> from zope.interface.verify import verifyObject >>> event = FoundPrincipalCreated("authentication", "principal", ... "info") >>> verifyObject(IFoundPrincipalCreated, event) True """ zope.interface.implements(IFoundPrincipalCreated) def __init__(self, authentication, principal, info): self.authentication = authentication self.principal = principal self.info = info class IQueriableAuthenticator(zope.interface.Interface): """Indicates the authenticator provides a search UI for principals.""" 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 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') zope.pluggableauth-1.3/src/zope/pluggableauth/tests.py0000644000175000017500000001330011524202361022334 0ustar jwjw00000000000000############################################################################## # # Copyright (c) 2004 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Pluggable Authentication Service Tests """ __docformat__ = "reStructuredText" import doctest import unittest import zope.component from zope.component.interfaces import IComponentLookup from zope.container.interfaces import ISimpleReadContainer from zope.container.traversal import ContainerTraversable from zope.interface import implements from zope.interface import Interface from zope.pluggableauth.plugins.session import SessionCredentialsPlugin from zope.publisher import base from zope.publisher.interfaces import IRequest from zope.session.http import CookieClientIdManager from zope.site.folder import rootFolder from zope.site.site import LocalSiteManager, SiteManagerAdapter from zope.traversing.interfaces import ITraversable from zope.traversing.testing import setUp import zope.component.eventtesting import zope.password from zope.session.interfaces import ( IClientId, IClientIdManager, ISession, ISessionDataContainer) from zope.session.session import ( ClientId, Session, PersistentSessionDataContainer) class TestClientId(object): implements(IClientId) def __new__(cls, request): return 'dummyclientidfortesting' def siteSetUp(test): zope.component.hooks.setHooks() # Set up site manager adapter zope.component.provideAdapter( SiteManagerAdapter, (Interface,), IComponentLookup) # Set up traversal setUp() zope.component.provideAdapter( ContainerTraversable, (ISimpleReadContainer,), ITraversable) # Set up site site = rootFolder() site.setSiteManager(LocalSiteManager(site)) zope.component.hooks.setSite(site) return site def siteTearDown(test): zope.component.hooks.resetHooks() zope.component.hooks.setSite() def sessionSetUp(container=PersistentSessionDataContainer): zope.component.provideAdapter(TestClientId, [IRequest], IClientId) zope.component.provideAdapter(Session, [IRequest], ISession) zope.component.provideUtility(CookieClientIdManager(), IClientIdManager) zope.component.provideUtility(container(), ISessionDataContainer, '') def nonHTTPSessionTestCaseSetUp(container=PersistentSessionDataContainer): # I am getting an error with ClientId and not TestClientId zope.component.provideAdapter(ClientId, [IRequest], IClientId) zope.component.provideAdapter(Session, [IRequest], ISession) zope.component.provideUtility(CookieClientIdManager(), IClientIdManager) zope.component.provideUtility(container(), ISessionDataContainer, '') class NonHTTPSessionTestCase(unittest.TestCase): """Small test suite to catch an error with non HTTP protocols, like FTP and SessionCredentialsPlugin. """ def setUp(self): nonHTTPSessionTestCaseSetUp() def tearDown(self): zope.component.hooks.resetHooks() zope.component.hooks.setSite() def test_exeractCredentials(self): plugin = SessionCredentialsPlugin() self.assertEqual( plugin.extractCredentials(base.TestRequest('/')), None) def test_challenge(self): plugin = SessionCredentialsPlugin() self.assertEqual( plugin.challenge(base.TestRequest('/')), False) def test_logout(self): plugin = SessionCredentialsPlugin() self.assertEqual( plugin.logout(base.TestRequest('/')), False) def setupPassword(test): from zope.password.interfaces import IPasswordManager from zope.password.password import SHA1PasswordManager, SSHAPasswordManager zope.component.provideUtility( SHA1PasswordManager(), IPasswordManager, 'SHA1') zope.component.provideUtility( SSHAPasswordManager(), IPasswordManager, 'SSHA') def test_suite(): suite = unittest.TestSuite(( unittest.makeSuite(NonHTTPSessionTestCase), doctest.DocTestSuite('zope.pluggableauth.interfaces'), doctest.DocTestSuite('zope.pluggableauth.plugins.generic'), doctest.DocTestSuite('zope.pluggableauth.plugins.ftpplugins'), doctest.DocTestSuite('zope.pluggableauth.plugins.httpplugins'), doctest.DocTestSuite('zope.pluggableauth.plugins.principalfolder'), doctest.DocFileSuite( 'plugins/principalfolder.txt', setUp=setupPassword), doctest.DocTestSuite('zope.pluggableauth.plugins.groupfolder'), doctest.DocFileSuite( 'plugins/groupfolder.txt', setUp=zope.component.eventtesting.setUp), doctest.DocTestSuite( 'zope.pluggableauth.plugins.session', setUp=siteSetUp, tearDown=siteTearDown), doctest.DocFileSuite( 'README.txt', setUp=siteSetUp, tearDown=siteTearDown, globs={'provideUtility': zope.component.provideUtility, 'provideAdapter': zope.component.provideAdapter, 'provideHandler': zope.component.provideHandler, 'getEvents': zope.component.eventtesting.getEvents, 'clearEvents': zope.component.eventtesting.clearEvents, }), )) return suite if __name__ == '__main__': unittest.main(defaultTest='test_suite') zope.pluggableauth-1.3/src/zope/pluggableauth/principalfactories.zcml0000644000175000017500000000033011524202361025367 0ustar jwjw00000000000000 zope.pluggableauth-1.3/src/zope/__init__.py0000644000175000017500000000031011524202361020102 0ustar jwjw00000000000000# this is a namespace package try: import pkg_resources pkg_resources.declare_namespace(__name__) except ImportError: import pkgutil __path__ = pkgutil.extend_path(__path__, __name__) zope.pluggableauth-1.3/PKG-INFO0000644000175000017500000006215411524202367015346 0ustar jwjw00000000000000Metadata-Version: 1.0 Name: zope.pluggableauth Version: 1.3 Summary: Pluggable Authentication Utility Home-page: http://pypi.python.org/pypi/zope.pluggableauth Author: Zope Foundation and Contributors Author-email: zope-dev@zope.org License: ZPL 2.1 Description: ========================== Pluggable Authentication ========================== Based on zope.authentication, this package provides a flexible and pluggable authentication utility. Several common plugins are provided. .. contents:: ======= Changes ======= 1.3 (2011-02-08) ---------------- - As the camefrom information is most probably used for a redirect, require it to be an absolute URL (see also http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.30). 1.2 (2010-12-16) ---------------- - SessionCredentialsPlugin has a hook (_makeCredentials) that can be overriden in subclasses to store the credentials in the session differently. For example, you could use keas.kmi and encrypt the passwords of the currently logged-in users so they don't appear in plain text in the ZODB. 1.1 (2010-10-18) ---------------- * Moved concrete IAuthenticatorPlugin implementations from zope.app.authentication to zope.pluggableauth.plugins. As a result projects that want to use the IAuthenticator plugins (previously found in zope.app.authentication) do not automatically also pull in the zope.app.* dependencies that are needed to register the ZMI views. 1.0.3 (2010-07-09) ------------------ * Fixed dependency declaration. 1.0.2 (2010-07-90) ------------------ * Added persistent.Persistent and zope.container.contained.Contained as bases zope.pluggableauth.plugins.session.SessionCredentialsPlugin, so instances of zope.app.authentication.session.SessionCredentialsPlugin won't be changed. (https://mail.zope.org/pipermail/zope-dev/2010-July/040898.html) 1.0.1 (2010-02-11) ------------------ * Adapters are now declared in a new ZCML file : `principalfactories.zcml`. This avoids duplication errors in ``zope.app.authentication``. 1.0 (2010-02-05) ---------------- * Splitting off from zope.app.authentication ================================ Pluggable-Authentication Utility ================================ The Pluggable-Authentication Utility (PAU) provides a framework for authenticating principals and associating information with them. It uses plugins and subscribers to get its work done. For a pluggable-authentication utility to be used, it should be registered as a utility providing the `zope.authentication.interfaces.IAuthentication` interface. Authentication -------------- The primary job of PAU is to authenticate principals. It uses two types of plug-ins in its work: - Credentials 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 credentials. For example, a 'session' credentials plugin reads credentials from a session (the "extraction"). If it cannot find credentials, it will redirect the user to a login form in order to provide them (the "challenge"). Authenticator plugins are responsible for authenticating the credentials extracted by a credentials plugin. They are also typically able to create principal objects for credentials they successfully authenticate. Given a request object, the PAU returns a principal object, if it can. The PAU does this by first iterateing through its credentials plugins to obtain a set of credentials. If it gets credentials, it iterates through its authenticator plugins to authenticate them. If an authenticator succeeds in authenticating a set of credentials, the PAU uses the authenticator to create a principal corresponding to the credentials. The authenticator notifies subscribers if an authenticated principal is created. Subscribers are responsible for adding data, especially groups, to the principal. Typically, if a subscriber adds data, it should also add corresponding interface declarations. Simple Credentials Plugin ~~~~~~~~~~~~~~~~~~~~~~~~~ To illustrate, we'll create a simple credentials plugin:: >>> from zope import interface >>> from zope.pluggableauth.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 As a plugin, MyCredentialsPlugin needs to be registered as a named utility:: >>> myCredentialsPlugin = MyCredentialsPlugin() >>> provideUtility(myCredentialsPlugin, name='My Credentials Plugin') Simple Authenticator Plugin ~~~~~~~~~~~~~~~~~~~~~~~~~~~ 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 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 As with the credentials plugin, the authenticator plugin must be registered as a named utility:: >>> myAuthenticatorPlugin = MyAuthenticatorPlugin() >>> provideUtility(myAuthenticatorPlugin, name='My Authenticator Plugin') Configuring a PAU ~~~~~~~~~~~~~~~~~ Finally, we'll create the PAU itself:: >>> from zope.pluggableauth import authentication >>> pau = authentication.PluggableAuthentication('xyz_') and configure it with the two plugins:: >>> pau.credentialsPlugins = ('My Credentials Plugin', ) >>> pau.authenticatorPlugins = ('My Authenticator Plugin', ) Using the PAU to Authenticate ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ >>> from zope.pluggableauth.factories import AuthenticatedPrincipalFactory >>> provideAdapter(AuthenticatedPrincipalFactory) We can now use the PAU to authenticate a sample request:: >>> from zope.publisher.browser import TestRequest >>> print pau.authenticate(TestRequest()) None In this case, we cannot authenticate an empty request. In the same way, we will not be able to authenticate a request with the wrong credentials:: >>> 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') we get an authenticated principal. Multiple Authenticator Plugins ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ The PAU works with multiple authenticator plugins. It uses each plugin, in the order specified in the PAU's authenticatorPlugins attribute, to authenticate a set of credentials. 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', '') >>> provideUtility(MyAuthenticatorPlugin2(), name='My Authenticator Plugin 2') If we put it before the original authenticator:: >>> pau.authenticatorPlugins = ( ... 'My Authenticator Plugin 2', ... 'My Authenticator Plugin') Then it will be given the first opportunity to authenticate a request:: >>> 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 When we change the order of the authenticator plugins:: >>> pau.authenticatorPlugins = ( ... 'My Authenticator Plugin', ... 'My Authenticator Plugin 2') we see that our original plugin is now acting first:: >>> 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') Multiple Credentials Plugins ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ As with with authenticators, we can specify multiple credentials plugins. To illustrate, we'll create a credentials plugin that extracts credentials from a request form:: >>> 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') and insert the new credentials plugin before the existing plugin:: >>> pau.credentialsPlugins = ( ... 'Form Credentials Plugin', ... 'My Credentials Plugin') The PAU will use each plugin in order to try and obtain credentials from a request:: >>> 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' - 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' - Succeeded in authenticating with 'My Authenticator Plugin 2' Let's try a different scenario:: >>> pau.authenticate(TestRequest(credentials='secretcode')) Principal('xyz_bob') In this case, the PAU went through these steps:: - Get credentials using 'Form Credentials Plugin' - Failed to get credentials using 'Form Credentials Plugin', try 'My Credentials Plugin' - Got 'scecretcode' credentials using 'My Credentials Plugin', try to authenticate using 'My Authenticator Plugin' - Succeeded in authenticating with 'My Authenticator Plugin' Let's try a slightly more complex scenario:: >>> 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' - 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 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' - 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' - Succeeded in authenticating with 'My Authenticator Plugin 2' (shouts and cheers!) Multiple Authenticator Plugins ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ As with the other operations we've seen, the PAU uses multiple plugins to find a principal. If the first authenticator plugin can't find the requested principal, the next plugin is used, and so on. >>> class AnotherAuthenticatorPlugin: ... ... 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 To illustrate, we'll create and register two authenticators:: >>> authenticator1 = AnotherAuthenticatorPlugin() >>> provideUtility(authenticator1, name='Authentication Plugin 1') >>> authenticator2 = AnotherAuthenticatorPlugin() >>> provideUtility(authenticator2, name='Authentication Plugin 2') and add a principal to them:: >>> authenticator1.add('bob', 'Bob', 'A nice guy', 'b0b') >>> authenticator1.add('white', 'White Spy', 'Sneaky', 'deathtoblack') >>> authenticator2.add('black', 'Black Spy', 'Also sneaky', 'deathtowhite') When we configure the PAU to use both searchable authenticators (note the order):: >>> pau.authenticatorPlugins = ( ... 'Authentication Plugin 2', ... 'Authentication Plugin 1') we register the factories for our principals:: >>> from zope.pluggableauth.factories import FoundPrincipalFactory >>> provideAdapter(FoundPrincipalFactory) we see how the PAU uses both plugins:: >>> pau.getPrincipal('xyz_white') Principal('xyz_white') >>> 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:: >>> authenticator2.add('white', 'White Rider', '', 'r1der') >>> pau.getPrincipal('xyz_white').title 'White Rider' If we change the order of the plugins:: >>> pau.authenticatorPlugins = ( ... 'Authentication Plugin 1', ... 'Authentication Plugin 2') we get a different principal for ID 'white':: >>> pau.getPrincipal('xyz_white').title 'White Spy' Issuing a Challenge ------------------- Part of PAU's IAuthentication contract is to challenge the user for 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 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 PAU handles the credentials challenge by delegating to its credentials plugins. Currently, the PAU is configured with the credentials plugins that don't perform any action when asked to challenge (see above the 'challenge' methods). To illustrate challenges, we'll subclass an existing credentials plugin and do something in its 'challenge':: >>> 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('advancedlogin.html'), ... name='Advanced Login Form Plugin') and configure the PAU to use them:: >>> pau.credentialsPlugins = ( ... 'Simple Login Form Plugin', ... 'Advanced Login Form Plugin') Now when we call 'unauthorized' on the PAU:: >>> 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' We can change the challenge policy by reordering the plugins:: >>> pau.credentialsPlugins = ( ... 'Advanced Login Form Plugin', ... 'Simple Login Form Plugin') Now when we call 'unauthorized':: >>> 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' Challenge Protocols ~~~~~~~~~~~~~~~~~~~ Sometimes, we want multiple challengers to work together. For example, the HTTP specification allows multiple challenges to be issued in a response. A challenge plugin can provide a `challengeProtocol` attribute that effectively groups related plugins together for challenging. If a plugin returns `True` from its challenge and provides a non-None challengeProtocol, subsequent plugins in the credentialsPlugins list that have the same challenge protocol will also be used to challenge. Without a challengeProtocol, only the first plugin to succeed in a challenge will be used. 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 and register a couple instances as utilities:: >>> provideUtility(XChallengeCredentialsPlugin('basic'), ... name='Basic 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') and call 'unauthorized':: >>> 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' Pluggable-Authentication Prefixes --------------------------------- Principal ids are required to be unique system wide. Plugins will often provide options for providing id prefixes, so that different sets of plugins provide unique ids within a PAU. If there are multiple pluggable-authentication utilities in a system, it's a good idea to give each PAU a unique prefix, so 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', ) When we create a request and try to authenticate:: >>> 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_41') OddPrincipal('mypas_41', "{'int': 41}") Keywords: zope3 ztk authentication pluggable Platform: UNKNOWN Classifier: Development Status :: 5 - Production/Stable Classifier: Environment :: Web Environment Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: Zope Public License Classifier: Programming Language :: Python Classifier: Natural Language :: English Classifier: Operating System :: OS Independent Classifier: Topic :: Internet :: WWW/HTTP Classifier: Framework :: Zope3 zope.pluggableauth-1.3/ZPL.txt0000644000175000017500000000413511524202361015444 0ustar jwjw00000000000000Zope Public License (ZPL) Version 2.1 A copyright notice accompanies this license document that identifies the copyright holders. This license has been certified as open source. It has also been designated as GPL compatible by the Free Software Foundation (FSF). Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions in source code must retain the accompanying copyright notice, this list of conditions, and the following disclaimer. 2. Redistributions in binary form must reproduce the accompanying copyright notice, this list of conditions, and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. Names of the copyright holders must not be used to endorse or promote products derived from this software without prior written permission from the copyright holders. 4. The right to distribute this software or to use it for any purpose does not give you the right to use Servicemarks (sm) or Trademarks (tm) of the copyright holders. Use of them is covered by separate agreement with the copyright holders. 5. If any files are modified, you must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. Disclaimer THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. zope.pluggableauth-1.3/COPYRIGHT.txt0000644000175000017500000000004011524202361016336 0ustar jwjw00000000000000Zope Foundation and Contributorszope.pluggableauth-1.3/buildout.cfg0000644000175000017500000000015311524202361016542 0ustar jwjw00000000000000[buildout] develop = . parts = test [test] recipe = zc.recipe.testrunner eggs = zope.pluggableauth [test]