zope.app.authentication-3.9/0000755000175000017500000000000011457014227013764 5ustar jwjwzope.app.authentication-3.9/CHANGES.txt0000644000175000017500000000763311457014222015601 0ustar jwjw======= Changes ======= 3.9 (2010-10-18) ---------------- * Move concrete IAuthenticatorPlugin implementations to zope.pluggableauth.plugins. Leave backwards compatibility imports. * Use zope.formlib throughout to lift the dependency on zope.app.form. As it turns out, zope.app.form is still a indirect test dependency though. 3.8.0 (2010-09-25) ------------------ * Using python's ``doctest`` module instead of deprecated ``zope.testing.doctest[unit]``. * Moved the following views from `zope.app.securitypolicy` here, to inverse dependency between these two packages, as `zope.app.securitypolicy` deprecated in ZTK 1.0: - ``@@grant.html`` - ``@@AllRolePermissions.html`` - ``@@RolePermissions.html`` - ``@@RolesWithPermission.html`` 3.7.1 (2010-02-11) ------------------ * Using the new `principalfactories.zcml` file, from ``zope.pluggableauth``, to avoid duplication errors, in the adapters registration. 3.7.0 (2010-02-08) ------------------ * The Pluggable Authentication utility has been severed and released in a standalone package: `zope.pluggableauth`. We are now using this new package, providing backward compatibility imports to assure a smooth transition. 3.6.2 (2010-01-05) ------------------ * Fix tests by using zope.login, and require new zope.publisher 3.12. 3.6.1 (2009-10-07) ------------------ * Fix ftesting.zcml due to ``zope.securitypolicy`` update. * Don't use ``zope.app.testing.ztapi`` in tests, use zope.component's testing functions instead. * Fix functional tests and stop using port 8081. Redirecting to different port without trusted flag is not allowed. 3.6.0 (2009-03-14) ------------------ * Separate the presentation template and camefrom/redirection logic for the ``loginForm.html`` view. Now the logic is contained in the ``zope.app.authentication.browser.loginform.LoginForm`` class. * Fix login form redirection failure in some cases with Python 2.6. * Use the new ``zope.authentication`` package instead of ``zope.app.security``. * The "Password Manager Names" vocabulary and simple password manager registry were moved to the ``zope.password`` package. * Remove deprecated code. 3.5.0 (2009-03-06) ------------------ * Split password manager functionality off to the new ``zope.password`` package. Backward-compatibility imports are left in place. * Use ``zope.site`` instead of ``zope.app.component``. (Browser code still needs ``zope.app.component`` as it depends on view classes of this package.) 3.5.0a2 (2009-02-01) -------------------- * Make old encoded passwords really work. 3.5.0a1 (2009-01-31) -------------------- * Use ``zope.container`` instead of ``zope.app.container``. (Browser code still needs ``zope.app.container`` as it depends on view classes of this package.) * Encoded passwords are now stored with a prefix ({MD5}, {SHA1}, {SSHA}) indicating the used encoding schema. Old (encoded) passwords can still be used. * Add an SSHA password manager that is compatible with standard LDAP passwords. As this encoding gives better security agains dictionary attacks, users are encouraged to switch to this new password schema. * InternalPrincipal now uses SSHA password manager by default. 3.4.4 (2008-12-12) ------------------ * Depend on zope.session instead of zope.app.session. The first one currently has all functionality we need. * Fix deprecation warnings for ``md5`` and ``sha`` on Python 2.6. 3.4.3 (2008-08-07) ------------------ * No changes. Retag for correct release on PyPI. 3.4.2 (2008-07-09) ------------------- * Make it compatible with zope.app.container 3.6.1 and 3.5.4 changes, Changed ``super(BTreeContainer, self).__init__()`` to ``super(GroupFolder, self).__init__()`` in ``GroupFolder`` class. 3.4.1 (2007-10-24) ------------------ * Avoid deprecation warning. 3.4.0 (2007-10-11) ------------------ * Updated package meta-data. 3.4.0b1 (2007-09-27) -------------------- * First release independent of Zope. zope.app.authentication-3.9/setup.cfg0000644000175000017500000000007311457014227015605 0ustar jwjw[egg_info] tag_build = tag_date = 0 tag_svn_revision = 0 zope.app.authentication-3.9/LICENSE.txt0000644000175000017500000000402611457014222015604 0ustar jwjwZope 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.app.authentication-3.9/setup.py0000644000175000017500000000715211457014222015476 0ustar jwjw############################################################################## # # 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. # ############################################################################## # 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. ############################################################################## """Setup for zope.app.authentication package $Id: setup.py 117636 2010-10-18 09:50:37Z janwijbrand $ """ 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.app.authentication', version='3.9', author='Zope Corporation and Contributors', author_email='zope-dev@zope.org', description=('Principals and groups management for ' 'the pluggable authentication utility'), long_description=( read('README.txt') + '\n\n.. contents::\n\n' + read('src', 'zope', 'app', 'authentication', 'README.txt') + '\n\n' + read('src', 'zope', 'app', 'authentication', 'principalfolder.txt') + '\n\n' + read('src', 'zope', 'app', 'authentication', 'vocabulary.txt') + '\n\n' + read('CHANGES.txt') ), url='http://pypi.python.org/pypi/zope.app.authentication', license='ZPL 2.1', 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'], keywords='zope3 authentication pluggable principal group', packages=find_packages('src'), package_dir = {'': 'src'}, extras_require=dict(test=[ 'zope.app.testing', 'zope.securitypolicy', 'zope.app.zcmlfiles', 'zope.securitypolicy', 'zope.testbrowser', 'zope.publisher', 'zope.testing', 'zope.session', 'zope.formlib', 'zope.publisher>=3.12', 'zope.site', 'zope.login',]), namespace_packages=['zope', 'zope.app'], install_requires=[ 'setuptools', 'ZODB3', 'zope.authentication', 'zope.component', 'zope.container', 'zope.dublincore', 'zope.event', 'zope.exceptions', 'zope.formlib >= 4.0.2', 'zope.i18n', 'zope.i18nmessageid', 'zope.interface', 'zope.location', 'zope.password >= 3.5.1', 'zope.pluggableauth >= 1.1', 'zope.schema', 'zope.security', 'zope.traversing', # Needed for browser code. 'zope.app.container', 'zope.app.component', ], include_package_data = True, zip_safe = False, ) zope.app.authentication-3.9/bootstrap.py0000644000175000017500000000337711457014222016360 0ustar jwjw############################################################################## # # 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. $Id: bootstrap.py 113109 2010-06-04 11:54:46Z janwijbrand $ """ 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.app.authentication-3.9/README.txt0000644000175000017500000000022311457014222015452 0ustar jwjwThis package provides a flexible and pluggable authentication utility for Zope 3, using `zope.pluggableauth`. Several common plugins are provided. zope.app.authentication-3.9/src/0000755000175000017500000000000011457014227014553 5ustar jwjwzope.app.authentication-3.9/src/zope.app.authentication.egg-info/0000755000175000017500000000000011457014227023017 5ustar jwjwzope.app.authentication-3.9/src/zope.app.authentication.egg-info/namespace_packages.txt0000644000175000017500000000001611457014224027344 0ustar jwjwzope zope.app zope.app.authentication-3.9/src/zope.app.authentication.egg-info/SOURCES.txt0000644000175000017500000000644511457014224024711 0ustar jwjwCHANGES.txt COPYRIGHT.txt LICENSE.txt README.txt bootstrap.py buildout.cfg setup.py src/zope/__init__.py src/zope.app.authentication.egg-info/PKG-INFO src/zope.app.authentication.egg-info/SOURCES.txt src/zope.app.authentication.egg-info/dependency_links.txt src/zope.app.authentication.egg-info/namespace_packages.txt src/zope.app.authentication.egg-info/not-zip-safe src/zope.app.authentication.egg-info/requires.txt src/zope.app.authentication.egg-info/top_level.txt src/zope/app/__init__.py src/zope/app/authentication/README.txt src/zope/app/authentication/__init__.py src/zope/app/authentication/authentication.py src/zope/app/authentication/configure.zcml src/zope/app/authentication/ftesting.zcml src/zope/app/authentication/ftpplugins.py src/zope/app/authentication/ftpplugins.zcml src/zope/app/authentication/generic.py src/zope/app/authentication/groupfolder.py src/zope/app/authentication/groupfolder.txt src/zope/app/authentication/groupfolder.zcml src/zope/app/authentication/httpplugins.py src/zope/app/authentication/httpplugins.zcml src/zope/app/authentication/i18n.py src/zope/app/authentication/idpicker.py src/zope/app/authentication/interfaces.py src/zope/app/authentication/password.py src/zope/app/authentication/password.zcml src/zope/app/authentication/placelesssetup.py src/zope/app/authentication/principalfolder.py src/zope/app/authentication/principalfolder.txt src/zope/app/authentication/principalfolder.zcml src/zope/app/authentication/session.py src/zope/app/authentication/session.zcml src/zope/app/authentication/testing.py src/zope/app/authentication/tests.py src/zope/app/authentication/vocabulary.py src/zope/app/authentication/vocabulary.txt src/zope/app/authentication/browser/__init__.py src/zope/app/authentication/browser/adding.py src/zope/app/authentication/browser/configure.zcml src/zope/app/authentication/browser/grant.zcml src/zope/app/authentication/browser/granting.pt src/zope/app/authentication/browser/granting.py src/zope/app/authentication/browser/granting.txt src/zope/app/authentication/browser/group_searching_with_empty_string.txt src/zope/app/authentication/browser/groupfolder.txt src/zope/app/authentication/browser/groupfolder.zcml src/zope/app/authentication/browser/httpplugins.zcml src/zope/app/authentication/browser/issue663.txt src/zope/app/authentication/browser/loginform.pt src/zope/app/authentication/browser/loginform.py src/zope/app/authentication/browser/manage_access.pt src/zope/app/authentication/browser/manage_permissionform.pt src/zope/app/authentication/browser/manage_roleform.pt src/zope/app/authentication/browser/pau_prefix_and_searching.txt src/zope/app/authentication/browser/principalfolder.txt src/zope/app/authentication/browser/principalfolder.zcml src/zope/app/authentication/browser/register.py src/zope/app/authentication/browser/rolepermissionview.py src/zope/app/authentication/browser/schemasearch.py src/zope/app/authentication/browser/schemasearch.txt src/zope/app/authentication/browser/session.zcml src/zope/app/authentication/browser/special-groups.txt src/zope/app/authentication/browser/tests/__init__.py src/zope/app/authentication/browser/tests/rolepermissionmanager.py src/zope/app/authentication/browser/tests/test_doctests.py src/zope/app/authentication/browser/tests/test_granting.py src/zope/app/authentication/browser/tests/test_rolepermissionview.pyzope.app.authentication-3.9/src/zope.app.authentication.egg-info/dependency_links.txt0000644000175000017500000000000111457014224027062 0ustar jwjw zope.app.authentication-3.9/src/zope.app.authentication.egg-info/not-zip-safe0000644000175000017500000000000111457014223025241 0ustar jwjw zope.app.authentication-3.9/src/zope.app.authentication.egg-info/requires.txt0000644000175000017500000000100311457014224025406 0ustar jwjwsetuptools ZODB3 zope.authentication zope.component zope.container zope.dublincore zope.event zope.exceptions zope.formlib >= 4.0.2 zope.i18n zope.i18nmessageid zope.interface zope.location zope.password >= 3.5.1 zope.pluggableauth >= 1.1 zope.schema zope.security zope.traversing zope.app.container zope.app.component [test] zope.app.testing zope.securitypolicy zope.app.zcmlfiles zope.securitypolicy zope.testbrowser zope.publisher zope.testing zope.session zope.formlib zope.publisher>=3.12 zope.site zope.loginzope.app.authentication-3.9/src/zope.app.authentication.egg-info/top_level.txt0000644000175000017500000000000511457014224025541 0ustar jwjwzope zope.app.authentication-3.9/src/zope.app.authentication.egg-info/PKG-INFO0000644000175000017500000015423511457014224024123 0ustar jwjwMetadata-Version: 1.0 Name: zope.app.authentication Version: 3.9 Summary: Principals and groups management for the pluggable authentication utility Home-page: http://pypi.python.org/pypi/zope.app.authentication Author: Zope Corporation and Contributors Author-email: zope-dev@zope.org License: ZPL 2.1 Description: This package provides a flexible and pluggable authentication utility for Zope 3, using `zope.pluggableauth`. Several common plugins are provided. .. contents:: ================================ 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.app.authentication import interfaces >>> class MyCredentialsPlugin(object): ... ... interface.implements(interfaces.ICredentialsPlugin) ... ... def extractCredentials(self, request): ... return request.get('credentials') ... ... def challenge(self, request): ... pass # challenge is a no-op for this plugin ... ... def logout(self, request): ... pass # logout is a no-op for this plugin 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') Principal Factories ~~~~~~~~~~~~~~~~~~~ While authenticator plugins provide principal info, they are not responsible for creating principals. This function is performed by factory adapters. For these tests we'll borrow some factories from the principal folder:: >>> from zope.app.authentication import principalfolder >>> provideAdapter(principalfolder.AuthenticatedPrincipalFactory) >>> provideAdapter(principalfolder.FoundPrincipalFactory) For more information on these factories, see their docstrings. Configuring a PAU ~~~~~~~~~~~~~~~~~ Finally, we'll create the PAU itself:: >>> from zope.app import authentication >>> pau = authentication.PluggableAuthentication('xyz_') and configure it with the two plugins:: >>> pau.credentialsPlugins = ('My Credentials Plugin', ) >>> pau.authenticatorPlugins = ('My Authenticator Plugin', ) Using the PAU to Authenticate ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ We can now use the PAU to authenticate a sample request:: >>> from zope.publisher.browser import TestRequest >>> print pau.authenticate(TestRequest()) None 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. Authenticated Principal Creates Events ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ We can verify that the appropriate event was published:: >>> [event] = getEvents(interfaces.IAuthenticatedPrincipalCreated) >>> event.principal is principal True >>> event.info PrincipalInfo('bob') >>> event.request is request True The info object has the id, title, and description of the principal. The info object is also generated by the authenticator plugin, so the plugin may itself have provided additional information on the info object:: >>> event.info.title 'Bob' >>> event.info.id # does not include pau prefix 'bob' >>> event.info.description '' It is also decorated with two other attributes, credentialsPlugin and authenticatorPlugin: these are the plugins used to extract credentials for and authenticate this principal. These attributes can be useful for subscribers that want to react to the plugins used. For instance, subscribers can determine that a given credential plugin does or does not support logout, and provide information usable to show or hide logout user interface:: >>> event.info.credentialsPlugin is myCredentialsPlugin True >>> event.info.authenticatorPlugin is myAuthenticatorPlugin True Normally, we provide subscribers to these events that add additional information to the principal. For example, we'll add one that sets the title:: >>> def add_info(event): ... event.principal.title = event.info.title >>> provideHandler(add_info, [interfaces.IAuthenticatedPrincipalCreated]) Now, if we authenticate a principal, its title is set:: >>> principal = pau.authenticate(request) >>> principal.title 'Bob' 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!) Principal Searching ------------------- As a component that provides IAuthentication, a PAU lets you lookup a principal with a principal ID. The PAU looks up a principal by delegating to its authenticators. In our example, none of the authenticators implement this search capability, so when we look for a principal:: >>> print pau.getPrincipal('xyz_bob') Traceback (most recent call last): PrincipalLookupError: bob >>> print pau.getPrincipal('white') Traceback (most recent call last): PrincipalLookupError: white >>> print pau.getPrincipal('black') Traceback (most recent call last): PrincipalLookupError: black For a PAU to support search, it needs to be configured with one or more authenticator plugins that support search. To illustrate, we'll create a new authenticator:: >>> class SearchableAuthenticatorPlugin: ... ... interface.implements(interfaces.IAuthenticatorPlugin) ... ... def __init__(self): ... self.infos = {} ... self.ids = {} ... ... def principalInfo(self, id): ... return self.infos.get(id) ... ... def authenticateCredentials(self, credentials): ... id = self.ids.get(credentials) ... if id is not None: ... return self.infos[id] ... ... def add(self, id, title, description, credentials): ... self.infos[id] = PrincipalInfo(id, title, description) ... self.ids[credentials] = id This class is typical of an authenticator plugin. It can both authenticate principals and find principals given a ID. While there are cases where an authenticator may opt to not perform one of these two functions, they are less typical. As with any plugin, we need to register it as a utility:: >>> searchable = SearchableAuthenticatorPlugin() >>> provideUtility(searchable, name='Searchable Authentication Plugin') We'll now configure the PAU to use only the searchable authenticator:: >>> pau.authenticatorPlugins = ('Searchable Authentication Plugin',) and add some principals to the authenticator:: >>> searchable.add('bob', 'Bob', 'A nice guy', 'b0b') >>> searchable.add('white', 'White Spy', 'Sneaky', 'deathtoblack') Now when we ask the PAU to find a principal:: >>> pau.getPrincipal('xyz_bob') Principal('xyz_bob') but only those it knows about:: >>> print pau.getPrincipal('black') Traceback (most recent call last): PrincipalLookupError: black Found Principal Creates Events ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ As evident in the authenticator's 'createFoundPrincipal' method (see above), a FoundPrincipalCreatedEvent is published when the authenticator finds a principal on behalf of PAU's 'getPrincipal':: >>> clearEvents() >>> principal = pau.getPrincipal('xyz_white') >>> principal Principal('xyz_white') >>> [event] = getEvents(interfaces.IFoundPrincipalCreated) >>> event.principal is principal True >>> event.info PrincipalInfo('white') The info has an authenticatorPlugin, but no credentialsPlugin, since none was used:: >>> event.info.credentialsPlugin is None True >>> event.info.authenticatorPlugin is searchable True As we have seen with authenticated principals, it is common to subscribe to principal created events to add information to the newly created principal. In this case, we need to subscribe to IFoundPrincipalCreated events:: >>> provideHandler(add_info, [interfaces.IFoundPrincipalCreated]) Now when a principal is created as a result of a search, it's title and description will be set (by the add_info handler function). 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. To illustrate, we'll create and register a second searchable authenticator:: >>> searchable2 = SearchableAuthenticatorPlugin() >>> provideUtility(searchable2, name='Searchable Authentication Plugin 2') and add a principal to it:: >>> searchable.add('black', 'Black Spy', 'Also sneaky', 'deathtowhite') When we configure the PAU to use both searchable authenticators (note the order):: >>> pau.authenticatorPlugins = ( ... 'Searchable Authentication Plugin 2', ... 'Searchable Authentication Plugin') 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:: >>> searchable2.add('white', 'White Rider', '', 'r1der') >>> pau.getPrincipal('xyz_white').title 'White Rider' If we change the order of the plugins:: >>> pau.authenticatorPlugins = ( ... 'Searchable Authentication Plugin', ... 'Searchable Authentication Plugin 2') 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}") Searching --------- PAU implements ISourceQueriables:: >>> from zope.schema.interfaces import ISourceQueriables >>> ISourceQueriables.providedBy(pau) True This means a PAU can be used in a principal source vocabulary (Zope provides a sophisticated searching UI for principal sources). As we've seen, a PAU uses each of its authenticator plugins to locate a principal with a given ID. However, plugins may also provide the interface IQuerySchemaSearch to indicate they can be used in the PAU's principal search scheme. Currently, our list of authenticators:: >>> pau.authenticatorPlugins ('My Authenticator Plugin',) does not include a queriable authenticator. PAU cannot therefore provide any queriables:: >>> list(pau.getQueriables()) [] Before we illustrate how an authenticator is used by the PAU to search for principals, we need to setup an adapter used by PAU:: >>> provideAdapter( ... authentication.authentication.QuerySchemaSearchAdapter, ... provides=interfaces.IQueriableAuthenticator) This adapter delegates search responsibility to an authenticator, but prepends the PAU prefix to any principal IDs returned in a search. Next, we'll create a plugin that provides a search interface:: >>> class QueriableAuthenticatorPlugin(MyAuthenticatorPlugin): ... ... interface.implements(interfaces.IQuerySchemaSearch) ... ... schema = None ... ... def search(self, query, start=None, batch_size=None): ... yield 'foo' ... and install it as a plugin:: >>> plugin = QueriableAuthenticatorPlugin() >>> provideUtility(plugin, ... provides=interfaces.IAuthenticatorPlugin, ... name='Queriable') >>> pau.authenticatorPlugins += ('Queriable',) Now, the PAU provides a single queriable:: >>> list(pau.getQueriables()) # doctest: +ELLIPSIS [('Queriable', ...QuerySchemaSearchAdapter object...)] We can use this queriable to search for our principal:: >>> queriable = list(pau.getQueriables())[0][1] >>> list(queriable.search('not-used')) ['mypau_foo'] Note that the resulting principal ID includes the PAU prefix. Were we to search the plugin directly:: >>> list(plugin.search('not-used')) ['foo'] The result does not include the PAU prefix. The prepending of the prefix is handled by the PluggableAuthenticationQueriable. Queryiable plugins can provide the ILocation interface. In this case the QuerySchemaSearchAdapter's __parent__ is the same as the __parent__ of the plugin:: >>> import zope.location.interfaces >>> class LocatedQueriableAuthenticatorPlugin(QueriableAuthenticatorPlugin): ... ... interface.implements(zope.location.interfaces.ILocation) ... ... __parent__ = __name__ = None ... >>> import zope.site.hooks >>> site = zope.site.hooks.getSite() >>> plugin = LocatedQueriableAuthenticatorPlugin() >>> plugin.__parent__ = site >>> plugin.__name__ = 'localname' >>> provideUtility(plugin, ... provides=interfaces.IAuthenticatorPlugin, ... name='location-queriable') >>> pau.authenticatorPlugins = ('location-queriable',) We have one queriable again:: >>> queriables = list(pau.getQueriables()) >>> queriables # doctest: +ELLIPSIS [('location-queriable', ...QuerySchemaSearchAdapter object...)] The queriable's __parent__ is the site as set above:: >>> queriable = queriables[0][1] >>> queriable.__parent__ is site True If the queriable provides ILocation but is not actually locatable (i.e. the parent is None) the pau itself becomes the parent:: >>> plugin = LocatedQueriableAuthenticatorPlugin() >>> provideUtility(plugin, ... provides=interfaces.IAuthenticatorPlugin, ... name='location-queriable-wo-parent') >>> pau.authenticatorPlugins = ('location-queriable-wo-parent',) We have one queriable again:: >>> queriables = list(pau.getQueriables()) >>> queriables # doctest: +ELLIPSIS [('location-queriable-wo-parent', ...QuerySchemaSearchAdapter object...)] And the parent is the pau:: >>> queriable = queriables[0][1] >>> queriable.__parent__ # doctest: +ELLIPSIS >>> queriable.__parent__ is pau True ================ Principal Folder ================ Principal folders contain principal-information objects that contain principal information. We create an internal principal using the `InternalPrincipal` class: >>> from zope.app.authentication.principalfolder import InternalPrincipal >>> p1 = InternalPrincipal('login1', '123', "Principal 1", ... passwordManagerName="SHA1") >>> p2 = InternalPrincipal('login2', '456', "The Other One") and add them to a principal folder: >>> from zope.app.authentication.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'}) ============ Vocabularies ============ The vocabulary module provides vocabularies for the authenticator plugins and the credentials plugins. The options should include the unique names of all of the plugins that provide the appropriate interface (interfaces.ICredentialsPlugin or interfaces.IAuthentiatorPlugin, respectively) for the current context-- which is expected to be a pluggable authentication utility, hereafter referred to as a PAU. These names may be for objects contained within the PAU ("contained plugins"), or may be utilities registered for the specified interface, found in the context of the PAU ("utility plugins"). Contained plugins mask utility plugins of the same name. They also may be names currently selected in the PAU that do not actually have a corresponding plugin at this time. Here is a short example of how the vocabulary should work. Let's say we're working with authentication plugins. We'll create some faux authentication plugins, and register some of them as utilities and put others in a faux PAU. >>> from zope.app.authentication import interfaces >>> from zope import interface, component >>> class DemoPlugin(object): ... interface.implements(interfaces.IAuthenticatorPlugin) ... def __init__(self, name): ... self.name = name ... >>> utility_plugins = dict( ... (i, DemoPlugin(u'Plugin %d' % i)) for i in range(4)) >>> contained_plugins = dict( ... (i, DemoPlugin(u'Plugin %d' % i)) for i in range(1, 5)) >>> sorted(utility_plugins.keys()) [0, 1, 2, 3] >>> for p in utility_plugins.values(): ... component.provideUtility(p, name=p.name) ... >>> sorted(contained_plugins.keys()) # 1 will mask utility plugin 1 [1, 2, 3, 4] >>> class DemoAuth(dict): ... interface.implements(interfaces.IPluggableAuthentication) ... def __init__(self, *args, **kwargs): ... super(DemoAuth, self).__init__(*args, **kwargs) ... self.authenticatorPlugins = (u'Plugin 3', u'Plugin X') ... self.credentialsPlugins = (u'Plugin 4', u'Plugin X') ... >>> auth = DemoAuth((p.name, p) for p in contained_plugins.values()) >>> @component.adapter(interface.Interface) ... @interface.implementer(component.IComponentLookup) ... def getSiteManager(context): ... return component.getGlobalSiteManager() ... >>> component.provideAdapter(getSiteManager) We are now ready to create a vocabulary that we can use. The context is our faux authentication utility, `auth`. >>> from zope.app.authentication import vocabulary >>> vocab = vocabulary.authenticatorPlugins(auth) Iterating over the vocabulary results in all of the terms, in a relatively arbitrary order of their names. (This vocabulary should typically use a widget that sorts values on the basis of localized collation order of the term titles.) >>> [term.value for term in vocab] # doctest: +NORMALIZE_WHITESPACE [u'Plugin 0', u'Plugin 1', u'Plugin 2', u'Plugin 3', u'Plugin 4', u'Plugin X'] Similarly, we can use `in` to test for the presence of values in the vocabulary. >>> ['Plugin %s' % i in vocab for i in range(-1, 6)] [False, True, True, True, True, True, False] >>> 'Plugin X' in vocab True The length reports the expected value. >>> len(vocab) 6 One can get a term for a given value using `getTerm()`; its token, in turn, should also return the same effective term from `getTermByToken`. >>> values = ['Plugin 0', 'Plugin 1', 'Plugin 2', 'Plugin 3', 'Plugin 4', ... 'Plugin X'] >>> for val in values: ... term = vocab.getTerm(val) ... assert term.value == val ... term2 = vocab.getTermByToken(term.token) ... assert term2.token == term.token ... assert term2.value == val ... The terms have titles, which are message ids that show the plugin title or id and whether the plugin is a utility or just contained in the auth utility. We'll give one of the plugins a dublin core title just to show the functionality. >>> import zope.dublincore.interfaces >>> class ISpecial(interface.Interface): ... pass ... >>> interface.directlyProvides(contained_plugins[1], ISpecial) >>> class DemoDCAdapter(object): ... interface.implements( ... zope.dublincore.interfaces.IDCDescriptiveProperties) ... component.adapts(ISpecial) ... def __init__(self, context): ... pass ... title = u'Special Title' ... >>> component.provideAdapter(DemoDCAdapter) We need to regenerate the vocabulary, since it calculates all of its data at once. >>> vocab = vocabulary.authenticatorPlugins(auth) Now we'll check the titles. We'll have to translate them to see what we expect. >>> from zope import i18n >>> import pprint >>> pprint.pprint([i18n.translate(term.title) for term in vocab]) [u'Plugin 0 (a utility)', u'Special Title (in contents)', u'Plugin 2 (in contents)', u'Plugin 3 (in contents)', u'Plugin 4 (in contents)', u'Plugin X (not found; deselecting will remove)'] credentialsPlugins ------------------ For completeness, we'll do the same review of the credentialsPlugins. >>> class DemoPlugin(object): ... interface.implements(interfaces.ICredentialsPlugin) ... def __init__(self, name): ... self.name = name ... >>> utility_plugins = dict( ... (i, DemoPlugin(u'Plugin %d' % i)) for i in range(4)) >>> contained_plugins = dict( ... (i, DemoPlugin(u'Plugin %d' % i)) for i in range(1, 5)) >>> for p in utility_plugins.values(): ... component.provideUtility(p, name=p.name) ... >>> auth = DemoAuth((p.name, p) for p in contained_plugins.values()) >>> vocab = vocabulary.credentialsPlugins(auth) Iterating over the vocabulary results in all of the terms, in a relatively arbitrary order of their names. (This vocabulary should typically use a widget that sorts values on the basis of localized collation order of the term titles.) Similarly, we can use `in` to test for the presence of values in the vocabulary. The length reports the expected value. >>> [term.value for term in vocab] # doctest: +NORMALIZE_WHITESPACE [u'Plugin 0', u'Plugin 1', u'Plugin 2', u'Plugin 3', u'Plugin 4', u'Plugin X'] >>> ['Plugin %s' % i in vocab for i in range(-1, 6)] [False, True, True, True, True, True, False] >>> 'Plugin X' in vocab True >>> len(vocab) 6 One can get a term for a given value using `getTerm()`; its token, in turn, should also return the same effective term from `getTermByToken`. >>> values = ['Plugin 0', 'Plugin 1', 'Plugin 2', 'Plugin 3', 'Plugin 4', ... 'Plugin X'] >>> for val in values: ... term = vocab.getTerm(val) ... assert term.value == val ... term2 = vocab.getTermByToken(term.token) ... assert term2.token == term.token ... assert term2.value == val ... The terms have titles, which are message ids that show the plugin title or id and whether the plugin is a utility or just contained in the auth utility. We'll give one of the plugins a dublin core title just to show the functionality. We need to regenerate the vocabulary, since it calculates all of its data at once. Then we'll check the titles. We'll have to translate them to see what we expect. >>> interface.directlyProvides(contained_plugins[1], ISpecial) >>> vocab = vocabulary.credentialsPlugins(auth) >>> pprint.pprint([i18n.translate(term.title) for term in vocab]) [u'Plugin 0 (a utility)', u'Special Title (in contents)', u'Plugin 2 (in contents)', u'Plugin 3 (in contents)', u'Plugin 4 (in contents)', u'Plugin X (not found; deselecting will remove)'] ======= Changes ======= 3.9 (2010-10-18) ---------------- * Move concrete IAuthenticatorPlugin implementations to zope.pluggableauth.plugins. Leave backwards compatibility imports. * Use zope.formlib throughout to lift the dependency on zope.app.form. As it turns out, zope.app.form is still a indirect test dependency though. 3.8.0 (2010-09-25) ------------------ * Using python's ``doctest`` module instead of deprecated ``zope.testing.doctest[unit]``. * Moved the following views from `zope.app.securitypolicy` here, to inverse dependency between these two packages, as `zope.app.securitypolicy` deprecated in ZTK 1.0: - ``@@grant.html`` - ``@@AllRolePermissions.html`` - ``@@RolePermissions.html`` - ``@@RolesWithPermission.html`` 3.7.1 (2010-02-11) ------------------ * Using the new `principalfactories.zcml` file, from ``zope.pluggableauth``, to avoid duplication errors, in the adapters registration. 3.7.0 (2010-02-08) ------------------ * The Pluggable Authentication utility has been severed and released in a standalone package: `zope.pluggableauth`. We are now using this new package, providing backward compatibility imports to assure a smooth transition. 3.6.2 (2010-01-05) ------------------ * Fix tests by using zope.login, and require new zope.publisher 3.12. 3.6.1 (2009-10-07) ------------------ * Fix ftesting.zcml due to ``zope.securitypolicy`` update. * Don't use ``zope.app.testing.ztapi`` in tests, use zope.component's testing functions instead. * Fix functional tests and stop using port 8081. Redirecting to different port without trusted flag is not allowed. 3.6.0 (2009-03-14) ------------------ * Separate the presentation template and camefrom/redirection logic for the ``loginForm.html`` view. Now the logic is contained in the ``zope.app.authentication.browser.loginform.LoginForm`` class. * Fix login form redirection failure in some cases with Python 2.6. * Use the new ``zope.authentication`` package instead of ``zope.app.security``. * The "Password Manager Names" vocabulary and simple password manager registry were moved to the ``zope.password`` package. * Remove deprecated code. 3.5.0 (2009-03-06) ------------------ * Split password manager functionality off to the new ``zope.password`` package. Backward-compatibility imports are left in place. * Use ``zope.site`` instead of ``zope.app.component``. (Browser code still needs ``zope.app.component`` as it depends on view classes of this package.) 3.5.0a2 (2009-02-01) -------------------- * Make old encoded passwords really work. 3.5.0a1 (2009-01-31) -------------------- * Use ``zope.container`` instead of ``zope.app.container``. (Browser code still needs ``zope.app.container`` as it depends on view classes of this package.) * Encoded passwords are now stored with a prefix ({MD5}, {SHA1}, {SSHA}) indicating the used encoding schema. Old (encoded) passwords can still be used. * Add an SSHA password manager that is compatible with standard LDAP passwords. As this encoding gives better security agains dictionary attacks, users are encouraged to switch to this new password schema. * InternalPrincipal now uses SSHA password manager by default. 3.4.4 (2008-12-12) ------------------ * Depend on zope.session instead of zope.app.session. The first one currently has all functionality we need. * Fix deprecation warnings for ``md5`` and ``sha`` on Python 2.6. 3.4.3 (2008-08-07) ------------------ * No changes. Retag for correct release on PyPI. 3.4.2 (2008-07-09) ------------------- * Make it compatible with zope.app.container 3.6.1 and 3.5.4 changes, Changed ``super(BTreeContainer, self).__init__()`` to ``super(GroupFolder, self).__init__()`` in ``GroupFolder`` class. 3.4.1 (2007-10-24) ------------------ * Avoid deprecation warning. 3.4.0 (2007-10-11) ------------------ * Updated package meta-data. 3.4.0b1 (2007-09-27) -------------------- * First release independent of Zope. Keywords: zope3 authentication pluggable principal group 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.app.authentication-3.9/src/zope/0000755000175000017500000000000011457014227015530 5ustar jwjwzope.app.authentication-3.9/src/zope/__init__.py0000644000175000017500000000031111457014222017627 0ustar jwjw# 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.app.authentication-3.9/src/zope/app/0000755000175000017500000000000011457014227016310 5ustar jwjwzope.app.authentication-3.9/src/zope/app/__init__.py0000644000175000017500000000031111457014222020407 0ustar jwjw# 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.app.authentication-3.9/src/zope/app/authentication/0000755000175000017500000000000011457014227021327 5ustar jwjwzope.app.authentication-3.9/src/zope/app/authentication/vocabulary.py0000644000175000017500000000750611457014222024053 0ustar jwjw############################################################################## # # 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. # ############################################################################## """Plugin Vocabulary. This vocabulary provides terms for authentication utility plugins. $Id: vocabulary.py 113109 2010-06-04 11:54:46Z janwijbrand $ """ __docformat__ = "reStructuredText" import zope.dublincore.interfaces from zope import interface, component, i18n from zope.schema import vocabulary from zope.schema.interfaces import IVocabularyFactory from zope.app.authentication.i18n import ZopeMessageFactory as _ from zope.pluggableauth import interfaces UTILITY_TITLE = _( 'zope.app.authentication.vocabulary-utility-plugin-title', '${name} (a utility)') CONTAINED_TITLE = _( 'zope.app.authentication.vocabulary-contained-plugin-title', '${name} (in contents)') MISSING_TITLE = _( 'zope.app.authentication.vocabulary-missing-plugin-title', '${name} (not found; deselecting will remove)') def _pluginVocabulary(context, interface, attr_name): """Vocabulary that provides names of plugins of a specified interface. Given an interface, the options should include the unique names of all of the plugins that provide the specified interface for the current context-- which is expected to be a pluggable authentication utility, hereafter referred to as a PAU). These plugins may be objects contained within the PAU ("contained plugins"), or may be utilities registered for the specified interface, found in the context of the PAU ("utility plugins"). Contained plugins mask utility plugins of the same name. The vocabulary also includes the current values of the PAU even if they do not correspond to a contained or utility plugin. """ terms = {} isPAU = interfaces.IPluggableAuthentication.providedBy(context) if isPAU: for k, v in context.items(): if interface.providedBy(v): dc = zope.dublincore.interfaces.IDCDescriptiveProperties( v, None) if dc is not None and dc.title: title = dc.title else: title = k terms[k] = vocabulary.SimpleTerm( k, k.encode('base64').strip(), i18n.Message( CONTAINED_TITLE, mapping={'name': title})) utils = component.getUtilitiesFor(interface, context) for nm, util in utils: if nm not in terms: terms[nm] = vocabulary.SimpleTerm( nm, nm.encode('base64').strip(), i18n.Message( UTILITY_TITLE, mapping={'name': nm})) if isPAU: for nm in set(getattr(context, attr_name)): if nm not in terms: terms[nm] = vocabulary.SimpleTerm( nm, nm.encode('base64').strip(), i18n.Message( MISSING_TITLE, mapping={'name': nm})) return vocabulary.SimpleVocabulary( [term for nm, term in sorted(terms.items())]) def authenticatorPlugins(context): return _pluginVocabulary( context, interfaces.IAuthenticatorPlugin, 'authenticatorPlugins') interface.alsoProvides(authenticatorPlugins, IVocabularyFactory) def credentialsPlugins(context): return _pluginVocabulary( context, interfaces.ICredentialsPlugin, 'credentialsPlugins') interface.alsoProvides(credentialsPlugins, IVocabularyFactory) zope.app.authentication-3.9/src/zope/app/authentication/vocabulary.txt0000644000175000017500000001733111457014222024237 0ustar jwjw============ Vocabularies ============ The vocabulary module provides vocabularies for the authenticator plugins and the credentials plugins. The options should include the unique names of all of the plugins that provide the appropriate interface (interfaces.ICredentialsPlugin or interfaces.IAuthentiatorPlugin, respectively) for the current context-- which is expected to be a pluggable authentication utility, hereafter referred to as a PAU. These names may be for objects contained within the PAU ("contained plugins"), or may be utilities registered for the specified interface, found in the context of the PAU ("utility plugins"). Contained plugins mask utility plugins of the same name. They also may be names currently selected in the PAU that do not actually have a corresponding plugin at this time. Here is a short example of how the vocabulary should work. Let's say we're working with authentication plugins. We'll create some faux authentication plugins, and register some of them as utilities and put others in a faux PAU. >>> from zope.app.authentication import interfaces >>> from zope import interface, component >>> class DemoPlugin(object): ... interface.implements(interfaces.IAuthenticatorPlugin) ... def __init__(self, name): ... self.name = name ... >>> utility_plugins = dict( ... (i, DemoPlugin(u'Plugin %d' % i)) for i in range(4)) >>> contained_plugins = dict( ... (i, DemoPlugin(u'Plugin %d' % i)) for i in range(1, 5)) >>> sorted(utility_plugins.keys()) [0, 1, 2, 3] >>> for p in utility_plugins.values(): ... component.provideUtility(p, name=p.name) ... >>> sorted(contained_plugins.keys()) # 1 will mask utility plugin 1 [1, 2, 3, 4] >>> class DemoAuth(dict): ... interface.implements(interfaces.IPluggableAuthentication) ... def __init__(self, *args, **kwargs): ... super(DemoAuth, self).__init__(*args, **kwargs) ... self.authenticatorPlugins = (u'Plugin 3', u'Plugin X') ... self.credentialsPlugins = (u'Plugin 4', u'Plugin X') ... >>> auth = DemoAuth((p.name, p) for p in contained_plugins.values()) >>> @component.adapter(interface.Interface) ... @interface.implementer(component.IComponentLookup) ... def getSiteManager(context): ... return component.getGlobalSiteManager() ... >>> component.provideAdapter(getSiteManager) We are now ready to create a vocabulary that we can use. The context is our faux authentication utility, `auth`. >>> from zope.app.authentication import vocabulary >>> vocab = vocabulary.authenticatorPlugins(auth) Iterating over the vocabulary results in all of the terms, in a relatively arbitrary order of their names. (This vocabulary should typically use a widget that sorts values on the basis of localized collation order of the term titles.) >>> [term.value for term in vocab] # doctest: +NORMALIZE_WHITESPACE [u'Plugin 0', u'Plugin 1', u'Plugin 2', u'Plugin 3', u'Plugin 4', u'Plugin X'] Similarly, we can use `in` to test for the presence of values in the vocabulary. >>> ['Plugin %s' % i in vocab for i in range(-1, 6)] [False, True, True, True, True, True, False] >>> 'Plugin X' in vocab True The length reports the expected value. >>> len(vocab) 6 One can get a term for a given value using `getTerm()`; its token, in turn, should also return the same effective term from `getTermByToken`. >>> values = ['Plugin 0', 'Plugin 1', 'Plugin 2', 'Plugin 3', 'Plugin 4', ... 'Plugin X'] >>> for val in values: ... term = vocab.getTerm(val) ... assert term.value == val ... term2 = vocab.getTermByToken(term.token) ... assert term2.token == term.token ... assert term2.value == val ... The terms have titles, which are message ids that show the plugin title or id and whether the plugin is a utility or just contained in the auth utility. We'll give one of the plugins a dublin core title just to show the functionality. >>> import zope.dublincore.interfaces >>> class ISpecial(interface.Interface): ... pass ... >>> interface.directlyProvides(contained_plugins[1], ISpecial) >>> class DemoDCAdapter(object): ... interface.implements( ... zope.dublincore.interfaces.IDCDescriptiveProperties) ... component.adapts(ISpecial) ... def __init__(self, context): ... pass ... title = u'Special Title' ... >>> component.provideAdapter(DemoDCAdapter) We need to regenerate the vocabulary, since it calculates all of its data at once. >>> vocab = vocabulary.authenticatorPlugins(auth) Now we'll check the titles. We'll have to translate them to see what we expect. >>> from zope import i18n >>> import pprint >>> pprint.pprint([i18n.translate(term.title) for term in vocab]) [u'Plugin 0 (a utility)', u'Special Title (in contents)', u'Plugin 2 (in contents)', u'Plugin 3 (in contents)', u'Plugin 4 (in contents)', u'Plugin X (not found; deselecting will remove)'] credentialsPlugins ------------------ For completeness, we'll do the same review of the credentialsPlugins. >>> class DemoPlugin(object): ... interface.implements(interfaces.ICredentialsPlugin) ... def __init__(self, name): ... self.name = name ... >>> utility_plugins = dict( ... (i, DemoPlugin(u'Plugin %d' % i)) for i in range(4)) >>> contained_plugins = dict( ... (i, DemoPlugin(u'Plugin %d' % i)) for i in range(1, 5)) >>> for p in utility_plugins.values(): ... component.provideUtility(p, name=p.name) ... >>> auth = DemoAuth((p.name, p) for p in contained_plugins.values()) >>> vocab = vocabulary.credentialsPlugins(auth) Iterating over the vocabulary results in all of the terms, in a relatively arbitrary order of their names. (This vocabulary should typically use a widget that sorts values on the basis of localized collation order of the term titles.) Similarly, we can use `in` to test for the presence of values in the vocabulary. The length reports the expected value. >>> [term.value for term in vocab] # doctest: +NORMALIZE_WHITESPACE [u'Plugin 0', u'Plugin 1', u'Plugin 2', u'Plugin 3', u'Plugin 4', u'Plugin X'] >>> ['Plugin %s' % i in vocab for i in range(-1, 6)] [False, True, True, True, True, True, False] >>> 'Plugin X' in vocab True >>> len(vocab) 6 One can get a term for a given value using `getTerm()`; its token, in turn, should also return the same effective term from `getTermByToken`. >>> values = ['Plugin 0', 'Plugin 1', 'Plugin 2', 'Plugin 3', 'Plugin 4', ... 'Plugin X'] >>> for val in values: ... term = vocab.getTerm(val) ... assert term.value == val ... term2 = vocab.getTermByToken(term.token) ... assert term2.token == term.token ... assert term2.value == val ... The terms have titles, which are message ids that show the plugin title or id and whether the plugin is a utility or just contained in the auth utility. We'll give one of the plugins a dublin core title just to show the functionality. We need to regenerate the vocabulary, since it calculates all of its data at once. Then we'll check the titles. We'll have to translate them to see what we expect. >>> interface.directlyProvides(contained_plugins[1], ISpecial) >>> vocab = vocabulary.credentialsPlugins(auth) >>> pprint.pprint([i18n.translate(term.title) for term in vocab]) [u'Plugin 0 (a utility)', u'Special Title (in contents)', u'Plugin 2 (in contents)', u'Plugin 3 (in contents)', u'Plugin 4 (in contents)', u'Plugin X (not found; deselecting will remove)'] zope.app.authentication-3.9/src/zope/app/authentication/password.zcml0000644000175000017500000000015111457014222024050 0ustar jwjw zope.app.authentication-3.9/src/zope/app/authentication/ftpplugins.zcml0000644000175000017500000000025011457014222024401 0ustar jwjw zope.app.authentication-3.9/src/zope/app/authentication/session.zcml0000644000175000017500000000117311457014222023676 0ustar jwjw zope.app.authentication-3.9/src/zope/app/authentication/groupfolder.zcml0000644000175000017500000000231111457014222024536 0ustar jwjw zope.app.authentication-3.9/src/zope/app/authentication/generic.py0000644000175000017500000000151011457014222023305 0ustar jwjw############################################################################## # # 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 $Id: generic.py 113109 2010-06-04 11:54:46Z janwijbrand $ """ __docformat__ = "reStructuredText" # BBB from zope.pluggableauth.plugins.generic import NoChallengeCredentialsPlugin zope.app.authentication-3.9/src/zope/app/authentication/groupfolder.txt0000644000175000017500000003524611457014222024425 0ustar jwjw============= 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.app.authentication.groupfolder >>> g1 = zope.app.authentication.groupfolder.GroupInformation("Group 1") >>> groups = zope.app.authentication.groupfolder.GroupFolder('group.') >>> groups['g1'] = g1 Note that when group-info is added, a GroupAdded event is generated: >>> from zope.app.authentication 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.app.authentication.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.app.authentication 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.app.authentication.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.app.authentication.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.app.authentication.groupfolder.GroupInformation("Group A") >>> groups['GA'] = ga >>> gb = zope.app.authentication.groupfolder.GroupInformation("Group B") >>> groups['GB'] = gb >>> gb.principals = ['auth.group.GA'] >>> gc = zope.app.authentication.groupfolder.GroupInformation("Group C") >>> groups['GC'] = gc >>> gc.principals = ['auth.group.GA'] >>> gd = zope.app.authentication.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.app.authentication.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.app.authentication.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.app.authentication.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.app.authentication.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.app.authentication.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.app.authentication.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.app.authentication.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.app.authentication.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.app.authentication-3.9/src/zope/app/authentication/httpplugins.py0000644000175000017500000000157011457014222024260 0ustar jwjw############################################################################## # # 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 $Id: httpplugins.py 113109 2010-06-04 11:54:46Z janwijbrand $ """ __docformat__ = "reStructuredText" ### BBB from zope.pluggableauth.plugins.httpplugins import ( IHTTPBasicAuthRealm, HTTPBasicAuthCredentialsPlugin) zope.app.authentication-3.9/src/zope/app/authentication/placelesssetup.py0000644000175000017500000000175011457014222024733 0ustar jwjw############################################################################## # # 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 Placeless Setup $Id: placelesssetup.py 113109 2010-06-04 11:54:46Z janwijbrand $ """ __docformat__ = "reStructuredText" # BBB: the password managers were moved to zope.password package from zope.password.testing import setUpPasswordManagers class PlacelessSetup(object): def setUp(self): setUpPasswordManagers() zope.app.authentication-3.9/src/zope/app/authentication/password.py0000644000175000017500000000210111457014222023530 0ustar jwjw############################################################################## # # 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. # ############################################################################## """Backward compatibility imports for password managers $Id: password.py 113109 2010-06-04 11:54:46Z janwijbrand $ """ __docformat__ = 'restructuredtext' # BBB: the password managers were moved into zope.password package. from zope.password.password import ( PlainTextPasswordManager, MD5PasswordManager, SHA1PasswordManager, SSHAPasswordManager, managers, ) from zope.password.vocabulary import PasswordManagerNamesVocabulary zope.app.authentication-3.9/src/zope/app/authentication/principalfolder.py0000644000175000017500000000241111457014222025047 0ustar jwjw############################################################################## # # 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 117633 2010-10-18 09:40:31Z janwijbrand $ """ __docformat__ = "reStructuredText" # BBB using zope.pluggableauth from zope.pluggableauth.interfaces import IAuthenticatorPlugin from zope.pluggableauth.factories import ( AuthenticatedPrincipalFactory, FoundPrincipalFactory, Principal, PrincipalInfo, ) # BBB using zope.pluggableauth.plugins.principalfolder from zope.pluggableauth.plugins.principalfolder import ( IInternalPrincipal, IInternalPrincipalContained, IInternalPrincipalContainer, InternalPrincipal, ISearchSchema, PrincipalFolder, ) zope.app.authentication-3.9/src/zope/app/authentication/httpplugins.zcml0000644000175000017500000000121411457014222024570 0ustar jwjw zope.app.authentication-3.9/src/zope/app/authentication/README.txt0000644000175000017500000006474611457014222023041 0ustar jwjw================================ 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.app.authentication import interfaces >>> class MyCredentialsPlugin(object): ... ... interface.implements(interfaces.ICredentialsPlugin) ... ... def extractCredentials(self, request): ... return request.get('credentials') ... ... def challenge(self, request): ... pass # challenge is a no-op for this plugin ... ... def logout(self, request): ... pass # logout is a no-op for this plugin 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') Principal Factories ~~~~~~~~~~~~~~~~~~~ While authenticator plugins provide principal info, they are not responsible for creating principals. This function is performed by factory adapters. For these tests we'll borrow some factories from the principal folder:: >>> from zope.app.authentication import principalfolder >>> provideAdapter(principalfolder.AuthenticatedPrincipalFactory) >>> provideAdapter(principalfolder.FoundPrincipalFactory) For more information on these factories, see their docstrings. Configuring a PAU ~~~~~~~~~~~~~~~~~ Finally, we'll create the PAU itself:: >>> from zope.app import authentication >>> pau = authentication.PluggableAuthentication('xyz_') and configure it with the two plugins:: >>> pau.credentialsPlugins = ('My Credentials Plugin', ) >>> pau.authenticatorPlugins = ('My Authenticator Plugin', ) Using the PAU to Authenticate ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ We can now use the PAU to authenticate a sample request:: >>> from zope.publisher.browser import TestRequest >>> print pau.authenticate(TestRequest()) None 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. Authenticated Principal Creates Events ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ We can verify that the appropriate event was published:: >>> [event] = getEvents(interfaces.IAuthenticatedPrincipalCreated) >>> event.principal is principal True >>> event.info PrincipalInfo('bob') >>> event.request is request True The info object has the id, title, and description of the principal. The info object is also generated by the authenticator plugin, so the plugin may itself have provided additional information on the info object:: >>> event.info.title 'Bob' >>> event.info.id # does not include pau prefix 'bob' >>> event.info.description '' It is also decorated with two other attributes, credentialsPlugin and authenticatorPlugin: these are the plugins used to extract credentials for and authenticate this principal. These attributes can be useful for subscribers that want to react to the plugins used. For instance, subscribers can determine that a given credential plugin does or does not support logout, and provide information usable to show or hide logout user interface:: >>> event.info.credentialsPlugin is myCredentialsPlugin True >>> event.info.authenticatorPlugin is myAuthenticatorPlugin True Normally, we provide subscribers to these events that add additional information to the principal. For example, we'll add one that sets the title:: >>> def add_info(event): ... event.principal.title = event.info.title >>> provideHandler(add_info, [interfaces.IAuthenticatedPrincipalCreated]) Now, if we authenticate a principal, its title is set:: >>> principal = pau.authenticate(request) >>> principal.title 'Bob' 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!) Principal Searching ------------------- As a component that provides IAuthentication, a PAU lets you lookup a principal with a principal ID. The PAU looks up a principal by delegating to its authenticators. In our example, none of the authenticators implement this search capability, so when we look for a principal:: >>> print pau.getPrincipal('xyz_bob') Traceback (most recent call last): PrincipalLookupError: bob >>> print pau.getPrincipal('white') Traceback (most recent call last): PrincipalLookupError: white >>> print pau.getPrincipal('black') Traceback (most recent call last): PrincipalLookupError: black For a PAU to support search, it needs to be configured with one or more authenticator plugins that support search. To illustrate, we'll create a new authenticator:: >>> class SearchableAuthenticatorPlugin: ... ... interface.implements(interfaces.IAuthenticatorPlugin) ... ... def __init__(self): ... self.infos = {} ... self.ids = {} ... ... def principalInfo(self, id): ... return self.infos.get(id) ... ... def authenticateCredentials(self, credentials): ... id = self.ids.get(credentials) ... if id is not None: ... return self.infos[id] ... ... def add(self, id, title, description, credentials): ... self.infos[id] = PrincipalInfo(id, title, description) ... self.ids[credentials] = id This class is typical of an authenticator plugin. It can both authenticate principals and find principals given a ID. While there are cases where an authenticator may opt to not perform one of these two functions, they are less typical. As with any plugin, we need to register it as a utility:: >>> searchable = SearchableAuthenticatorPlugin() >>> provideUtility(searchable, name='Searchable Authentication Plugin') We'll now configure the PAU to use only the searchable authenticator:: >>> pau.authenticatorPlugins = ('Searchable Authentication Plugin',) and add some principals to the authenticator:: >>> searchable.add('bob', 'Bob', 'A nice guy', 'b0b') >>> searchable.add('white', 'White Spy', 'Sneaky', 'deathtoblack') Now when we ask the PAU to find a principal:: >>> pau.getPrincipal('xyz_bob') Principal('xyz_bob') but only those it knows about:: >>> print pau.getPrincipal('black') Traceback (most recent call last): PrincipalLookupError: black Found Principal Creates Events ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ As evident in the authenticator's 'createFoundPrincipal' method (see above), a FoundPrincipalCreatedEvent is published when the authenticator finds a principal on behalf of PAU's 'getPrincipal':: >>> clearEvents() >>> principal = pau.getPrincipal('xyz_white') >>> principal Principal('xyz_white') >>> [event] = getEvents(interfaces.IFoundPrincipalCreated) >>> event.principal is principal True >>> event.info PrincipalInfo('white') The info has an authenticatorPlugin, but no credentialsPlugin, since none was used:: >>> event.info.credentialsPlugin is None True >>> event.info.authenticatorPlugin is searchable True As we have seen with authenticated principals, it is common to subscribe to principal created events to add information to the newly created principal. In this case, we need to subscribe to IFoundPrincipalCreated events:: >>> provideHandler(add_info, [interfaces.IFoundPrincipalCreated]) Now when a principal is created as a result of a search, it's title and description will be set (by the add_info handler function). 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. To illustrate, we'll create and register a second searchable authenticator:: >>> searchable2 = SearchableAuthenticatorPlugin() >>> provideUtility(searchable2, name='Searchable Authentication Plugin 2') and add a principal to it:: >>> searchable.add('black', 'Black Spy', 'Also sneaky', 'deathtowhite') When we configure the PAU to use both searchable authenticators (note the order):: >>> pau.authenticatorPlugins = ( ... 'Searchable Authentication Plugin 2', ... 'Searchable Authentication Plugin') 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:: >>> searchable2.add('white', 'White Rider', '', 'r1der') >>> pau.getPrincipal('xyz_white').title 'White Rider' If we change the order of the plugins:: >>> pau.authenticatorPlugins = ( ... 'Searchable Authentication Plugin', ... 'Searchable Authentication Plugin 2') 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}") Searching --------- PAU implements ISourceQueriables:: >>> from zope.schema.interfaces import ISourceQueriables >>> ISourceQueriables.providedBy(pau) True This means a PAU can be used in a principal source vocabulary (Zope provides a sophisticated searching UI for principal sources). As we've seen, a PAU uses each of its authenticator plugins to locate a principal with a given ID. However, plugins may also provide the interface IQuerySchemaSearch to indicate they can be used in the PAU's principal search scheme. Currently, our list of authenticators:: >>> pau.authenticatorPlugins ('My Authenticator Plugin',) does not include a queriable authenticator. PAU cannot therefore provide any queriables:: >>> list(pau.getQueriables()) [] Before we illustrate how an authenticator is used by the PAU to search for principals, we need to setup an adapter used by PAU:: >>> provideAdapter( ... authentication.authentication.QuerySchemaSearchAdapter, ... provides=interfaces.IQueriableAuthenticator) This adapter delegates search responsibility to an authenticator, but prepends the PAU prefix to any principal IDs returned in a search. Next, we'll create a plugin that provides a search interface:: >>> class QueriableAuthenticatorPlugin(MyAuthenticatorPlugin): ... ... interface.implements(interfaces.IQuerySchemaSearch) ... ... schema = None ... ... def search(self, query, start=None, batch_size=None): ... yield 'foo' ... and install it as a plugin:: >>> plugin = QueriableAuthenticatorPlugin() >>> provideUtility(plugin, ... provides=interfaces.IAuthenticatorPlugin, ... name='Queriable') >>> pau.authenticatorPlugins += ('Queriable',) Now, the PAU provides a single queriable:: >>> list(pau.getQueriables()) # doctest: +ELLIPSIS [('Queriable', ...QuerySchemaSearchAdapter object...)] We can use this queriable to search for our principal:: >>> queriable = list(pau.getQueriables())[0][1] >>> list(queriable.search('not-used')) ['mypau_foo'] Note that the resulting principal ID includes the PAU prefix. Were we to search the plugin directly:: >>> list(plugin.search('not-used')) ['foo'] The result does not include the PAU prefix. The prepending of the prefix is handled by the PluggableAuthenticationQueriable. Queryiable plugins can provide the ILocation interface. In this case the QuerySchemaSearchAdapter's __parent__ is the same as the __parent__ of the plugin:: >>> import zope.location.interfaces >>> class LocatedQueriableAuthenticatorPlugin(QueriableAuthenticatorPlugin): ... ... interface.implements(zope.location.interfaces.ILocation) ... ... __parent__ = __name__ = None ... >>> import zope.site.hooks >>> site = zope.site.hooks.getSite() >>> plugin = LocatedQueriableAuthenticatorPlugin() >>> plugin.__parent__ = site >>> plugin.__name__ = 'localname' >>> provideUtility(plugin, ... provides=interfaces.IAuthenticatorPlugin, ... name='location-queriable') >>> pau.authenticatorPlugins = ('location-queriable',) We have one queriable again:: >>> queriables = list(pau.getQueriables()) >>> queriables # doctest: +ELLIPSIS [('location-queriable', ...QuerySchemaSearchAdapter object...)] The queriable's __parent__ is the site as set above:: >>> queriable = queriables[0][1] >>> queriable.__parent__ is site True If the queriable provides ILocation but is not actually locatable (i.e. the parent is None) the pau itself becomes the parent:: >>> plugin = LocatedQueriableAuthenticatorPlugin() >>> provideUtility(plugin, ... provides=interfaces.IAuthenticatorPlugin, ... name='location-queriable-wo-parent') >>> pau.authenticatorPlugins = ('location-queriable-wo-parent',) We have one queriable again:: >>> queriables = list(pau.getQueriables()) >>> queriables # doctest: +ELLIPSIS [('location-queriable-wo-parent', ...QuerySchemaSearchAdapter object...)] And the parent is the pau:: >>> queriable = queriables[0][1] >>> queriable.__parent__ # doctest: +ELLIPSIS >>> queriable.__parent__ is pau True zope.app.authentication-3.9/src/zope/app/authentication/principalfolder.txt0000644000175000017500000001117511457014222025245 0ustar jwjw================ Principal Folder ================ Principal folders contain principal-information objects that contain principal information. We create an internal principal using the `InternalPrincipal` class: >>> from zope.app.authentication.principalfolder import InternalPrincipal >>> p1 = InternalPrincipal('login1', '123', "Principal 1", ... passwordManagerName="SHA1") >>> p2 = InternalPrincipal('login2', '456', "The Other One") and add them to a principal folder: >>> from zope.app.authentication.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.app.authentication-3.9/src/zope/app/authentication/ftesting.zcml0000644000175000017500000000302411457014222024033 0ustar jwjw zope.app.authentication-3.9/src/zope/app/authentication/testing.py0000644000175000017500000000200711457014222023350 0ustar jwjw############################################################################## # # Copyright (c) 2007 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.app.authentication common test related classes/functions/objects. $Id: testing.py 113109 2010-06-04 11:54:46Z janwijbrand $ """ __docformat__ = "reStructuredText" import os from zope.app.testing.functional import ZCMLLayer AppAuthenticationLayer = ZCMLLayer( os.path.join(os.path.split(__file__)[0], 'ftesting.zcml'), __name__, 'AppAuthenticationLayer', allow_teardown=True) zope.app.authentication-3.9/src/zope/app/authentication/browser/0000755000175000017500000000000011457014227023012 5ustar jwjwzope.app.authentication-3.9/src/zope/app/authentication/browser/granting.pt0000644000175000017500000000632211457014222025166 0ustar jwjw

Granting Roles and Permissions to Principals

                     

...

Grants for the selected principal

roles widget
Roles  Allow  Unset  Deny 
^ top
permission widget
Permissions  Allow  Unset  Deny 
^ top
zope.app.authentication-3.9/src/zope/app/authentication/browser/session.zcml0000644000175000017500000000115611457014222025362 0ustar jwjw ././@LongLink0000000000000000000000000000014600000000000011566 Lustar rootrootzope.app.authentication-3.9/src/zope/app/authentication/browser/group_searching_with_empty_string.txtzope.app.authentication-3.9/src/zope/app/authentication/browser/group_searching_with_empty_string.tx0000644000175000017500000001475011457014222032407 0ustar jwjwWe can search group folder with an empty string. We'll add a pluggable authentication utility: >>> print http(r""" ... POST /++etc++site/default/@@contents.html HTTP/1.1 ... Authorization: Basic bWdyOm1ncnB3 ... Content-Length: 98 ... Content-Type: application/x-www-form-urlencoded ... Referer: http://localhost/++etc++site/default/@@contents.html?type_name=BrowserAdd__zope.pluggableauth.authentication.PluggableAuthentication ... ... type_name=BrowserAdd__zope.pluggableauth.authentication.PluggableAuthentication&new_value=PAU""") HTTP/1.1 303 See Other ... And register it: >>> print http(r""" ... POST /++etc++site/default/PAU/addRegistration.html HTTP/1.1 ... Authorization: Basic bWdyOm1ncnB3 ... Content-Length: 699 ... Content-Type: multipart/form-data; boundary=---------------------------191720529414243436931796477300 ... Referer: http://localhost/++etc++site/default/PAU/addRegistration.html ... ... -----------------------------191720529414243436931796477300 ... Content-Disposition: form-data; name="field.comment" ... ... ... -----------------------------191720529414243436931796477300 ... Content-Disposition: form-data; name="field.actions.register" ... ... Register ... -----------------------------191720529414243436931796477300-- ... """) HTTP/1.1 303 See Other ... Next, we'll add the group folder: >>> print http(r""" ... POST /++etc++site/default/PAU/+/AddGroupFolder.html%3D HTTP/1.1 ... Authorization: Basic bWdyOm1ncnB3 ... Content-Length: 427 ... Content-Type: multipart/form-data; boundary=---------------------------4150524541658557772058105275 ... Referer: http://localhost/++etc++site/default/PAU/+/AddGroupFolder.html= ... ... -----------------------------4150524541658557772058105275 ... Content-Disposition: form-data; name="field.prefix" ... ... groups ... -----------------------------4150524541658557772058105275 ... Content-Disposition: form-data; name="UPDATE_SUBMIT" ... ... Add ... -----------------------------4150524541658557772058105275 ... Content-Disposition: form-data; name="add_input_name" ... ... groups ... -----------------------------4150524541658557772058105275-- ... """) HTTP/1.1 303 See Other ... And add some groups: >>> print http(r""" ... POST /++etc++site/default/PAU/groups/+/AddGroupInformation.html%3D HTTP/1.1 ... Authorization: Basic bWdyOm1ncnB3 ... Content-Length: 550 ... Content-Type: multipart/form-data; boundary=---------------------------12719796373012316301953477158 ... Referer: http://localhost/++etc++site/default/PAU/groups/+/AddGroupInformation.html= ... ... -----------------------------12719796373012316301953477158 ... Content-Disposition: form-data; name="field.title" ... ... Test1 ... -----------------------------12719796373012316301953477158 ... Content-Disposition: form-data; name="field.description" ... ... ... -----------------------------12719796373012316301953477158 ... Content-Disposition: form-data; name="UPDATE_SUBMIT" ... ... Add ... -----------------------------12719796373012316301953477158 ... Content-Disposition: form-data; name="add_input_name" ... ... Test1 ... -----------------------------12719796373012316301953477158-- ... """) HTTP/1.1 303 See Other ... >>> print http(r""" ... POST /++etc++site/default/PAU/groups/+/AddGroupInformation.html%3D HTTP/1.1 ... Authorization: Basic bWdyOm1ncnB3 ... Content-Length: 550 ... Content-Type: multipart/form-data; boundary=---------------------------10816732208483809451400699513 ... Referer: http://localhost/++etc++site/default/PAU/groups/+/AddGroupInformation.html= ... ... -----------------------------10816732208483809451400699513 ... Content-Disposition: form-data; name="field.title" ... ... Test2 ... -----------------------------10816732208483809451400699513 ... Content-Disposition: form-data; name="field.description" ... ... ... -----------------------------10816732208483809451400699513 ... Content-Disposition: form-data; name="UPDATE_SUBMIT" ... ... Add ... -----------------------------10816732208483809451400699513 ... Content-Disposition: form-data; name="add_input_name" ... ... Test2 ... -----------------------------10816732208483809451400699513-- ... """) HTTP/1.1 303 See Other ... Now we'll configure our pluggable-authentication utility to use the group folder: >>> print http(r""" ... POST /++etc++site/default/PAU/@@configure.html HTTP/1.1 ... Authorization: Basic bWdyOm1ncnB3 ... Content-Length: 1040 ... Content-Type: multipart/form-data; boundary=---------------------------1786480431902757372789659730 ... Referer: http://localhost/++etc++site/default/PAU/@@configure.html ... ... -----------------------------1786480431902757372789659730 ... Content-Disposition: form-data; name="field.credentialsPlugins.to" ... ... U2Vzc2lvbiBDcmVkZW50aWFscw== ... -----------------------------1786480431902757372789659730 ... Content-Disposition: form-data; name="field.credentialsPlugins-empty-marker" ... ... ... -----------------------------1786480431902757372789659730 ... Content-Disposition: form-data; name="field.authenticatorPlugins.to" ... ... Z3JvdXBz ... -----------------------------1786480431902757372789659730 ... Content-Disposition: form-data; name="field.authenticatorPlugins-empty-marker" ... ... ... -----------------------------1786480431902757372789659730 ... Content-Disposition: form-data; name="UPDATE_SUBMIT" ... ... Change ... -----------------------------1786480431902757372789659730 ... Content-Disposition: form-data; name="field.credentialsPlugins" ... ... U2Vzc2lvbiBDcmVkZW50aWFscw== ... -----------------------------1786480431902757372789659730 ... Content-Disposition: form-data; name="field.authenticatorPlugins" ... ... Z3JvdXBz ... -----------------------------1786480431902757372789659730-- ... """) HTTP/1.1 200 OK ... Now, if we search for a group, but don't supply a string: >>> print http(r""" ... POST /@@grant.html HTTP/1.1 ... Authorization: Basic bWdyOm1ncnB3 ... Content-Length: 166 ... Content-Type: application/x-www-form-urlencoded ... Referer: http://localhost/@@grant.html ... ... field.principal.displayed=y&""" ... "field.principal.MC5ncm91cHM_.field.search=&" ... "field.principal.MC5ncm91cHM_.search=Search&" ... "field.principal.MQ__.searchstring=") HTTP/1.1 200 OK ...Test1...Test2... We get both of our groups in the result. zope.app.authentication-3.9/src/zope/app/authentication/browser/groupfolder.zcml0000644000175000017500000000243411457014222026227 0ustar jwjw zope.app.authentication-3.9/src/zope/app/authentication/browser/groupfolder.txt0000644000175000017500000005761311457014222026112 0ustar jwjwUsing Group Folders =================== Group folders are used to define groups. Before you can define groups, you have to create a group folder and configure it in a pluggable authentication utility. The group folder has to be registered with a pluggable authentication utility before defining any groups. This is because the groups folder needs to use the pluggable authentication utility to find all of the groups containing a given group so that it can check for group cycles. Not all of a group's groups need to be defined in it's group folder. Other groups folders or group-defining plugins could define groups for a group. Let's walk through an example. First, We need to create and register a pluggable authentication utility. >>> print http(r""" ... POST /++etc++site/default/@@contents.html HTTP/1.1 ... Authorization: Basic bWdyOm1ncnB3 ... Content-Length: 98 ... Content-Type: application/x-www-form-urlencoded ... Cookie: zope3_cs_6a553b3=-j7C3CdeW9sUK8BP5x97u2d9o242xMJDzJd8HCQ5AAi9xeFcGTFkAs ... Referer: http://localhost/++etc++site/default/@@contents.html?type_name=BrowserAdd__zope.pluggableauth.authentication.PluggableAuthentication ... ... type_name=BrowserAdd__zope.pluggableauth.authentication.PluggableAuthentication&new_value=PAU""") HTTP/1.1 303 See Other ... >>> print http(r""" ... GET /++etc++site/default/PAU/@@registration.html HTTP/1.1 ... Authorization: Basic bWdyOm1ncnB3 ... Cookie: zope3_cs_6a553b3=-j7C3CdeW9sUK8BP5x97u2d9o242xMJDzJd8HCQ5AAi9xeFcGTFkAs ... Referer: http://localhost/++etc++site/default/@@contents.html?type_name=BrowserAdd__zope.pluggableauth.authentication.PluggableAuthentication ... """) HTTP/1.1 200 OK ... Register PAU. >>> print http(r""" ... POST /++etc++site/default/PAU/addRegistration.html HTTP/1.1 ... Authorization: Basic bWdyOm1ncnB3 ... Content-Length: 687 ... Content-Type: multipart/form-data; boundary=---------------------------5559795404609280911441883437 ... Cookie: zope3_cs_6a553b3=-j7C3CdeW9sUK8BP5x97u2d9o242xMJDzJd8HCQ5AAi9xeFcGTFkAs ... Referer: http://localhost/++etc++site/default/PAU/addRegistration.html ... ... -----------------------------5559795404609280911441883437 ... Content-Disposition: form-data; name="field.comment" ... ... ... -----------------------------5559795404609280911441883437 ... Content-Disposition: form-data; name="field.actions.register" ... ... Register ... -----------------------------5559795404609280911441883437-- ... """) HTTP/1.1 303 See Other ... Add a Principal folder plugin `users` to PAU. >>> print http(r""" ... POST /++etc++site/default/PAU/+/AddPrincipalFolder.html%3D HTTP/1.1 ... Authorization: Basic bWdyOm1ncnB3 ... Content-Length: 429 ... Content-Type: multipart/form-data; boundary=---------------------------95449631112274213651507932125 ... Cookie: zope3_cs_6a553b3=-j7C3CdeW9sUK8BP5x97u2d9o242xMJDzJd8HCQ5AAi9xeFcGTFkAs ... Referer: http://localhost/++etc++site/default/PAU/+/AddPrincipalFolder.html= ... ... -----------------------------95449631112274213651507932125 ... Content-Disposition: form-data; name="field.prefix" ... ... users ... -----------------------------95449631112274213651507932125 ... Content-Disposition: form-data; name="UPDATE_SUBMIT" ... ... Add ... -----------------------------95449631112274213651507932125 ... Content-Disposition: form-data; name="add_input_name" ... ... users ... -----------------------------95449631112274213651507932125-- ... """) HTTP/1.1 303 See Other ... Next we will add some users. >>> print http(r""" ... POST /++etc++site/default/PAU/users/+/AddPrincipalInformation.html%3D HTTP/1.1 ... Authorization: Basic bWdyOm1ncnB3 ... Content-Length: 780 ... Content-Type: multipart/form-data; boundary=---------------------------5110544421083023415453147877 ... Cookie: zope3_cs_6a553b3=-j7C3CdeW9sUK8BP5x97u2d9o242xMJDzJd8HCQ5AAi9xeFcGTFkAs ... Referer: http://localhost/++etc++site/default/PAU/users/+/AddPrincipalInformation.html%3D ... ... -----------------------------5110544421083023415453147877 ... Content-Disposition: form-data; name="field.login" ... ... bob ... -----------------------------5110544421083023415453147877 ... Content-Disposition: form-data; name="field.passwordManagerName" ... ... Plain Text ... -----------------------------5110544421083023415453147877 ... Content-Disposition: form-data; name="field.password" ... ... 123 ... -----------------------------5110544421083023415453147877 ... Content-Disposition: form-data; name="field.title" ... ... Bob ... -----------------------------5110544421083023415453147877 ... Content-Disposition: form-data; name="field.description" ... ... ... -----------------------------5110544421083023415453147877 ... Content-Disposition: form-data; name="UPDATE_SUBMIT" ... ... Add ... -----------------------------5110544421083023415453147877 ... Content-Disposition: form-data; name="add_input_name" ... ... ... -----------------------------5110544421083023415453147877-- ... """) HTTP/1.1 303 See Other ... >>> print http(r""" ... POST /++etc++site/default/PAU/users/+/AddPrincipalInformation.html%3D HTTP/1.1 ... Authorization: Basic bWdyOm1ncnB3 ... Content-Length: 780 ... Content-Type: multipart/form-data; boundary=---------------------------5110544421083023415453147877 ... Cookie: zope3_cs_6a553b3=-j7C3CdeW9sUK8BP5x97u2d9o242xMJDzJd8HCQ5AAi9xeFcGTFkAs ... Referer: http://localhost/++etc++site/default/PAU/users/+/AddPrincipalInformation.html%3D ... ... -----------------------------5110544421083023415453147877 ... Content-Disposition: form-data; name="field.login" ... ... bill ... -----------------------------5110544421083023415453147877 ... Content-Disposition: form-data; name="field.passwordManagerName" ... ... Plain Text ... -----------------------------5110544421083023415453147877 ... Content-Disposition: form-data; name="field.password" ... ... 123 ... -----------------------------5110544421083023415453147877 ... Content-Disposition: form-data; name="field.title" ... ... Bill ... -----------------------------5110544421083023415453147877 ... Content-Disposition: form-data; name="field.description" ... ... ... -----------------------------5110544421083023415453147877 ... Content-Disposition: form-data; name="UPDATE_SUBMIT" ... ... Add ... -----------------------------5110544421083023415453147877 ... Content-Disposition: form-data; name="add_input_name" ... ... ... -----------------------------5110544421083023415453147877-- ... """) HTTP/1.1 303 See Other ... >>> print http(r""" ... POST /++etc++site/default/PAU/users/+/AddPrincipalInformation.html%3D HTTP/1.1 ... Authorization: Basic bWdyOm1ncnB3 ... Content-Length: 780 ... Content-Type: multipart/form-data; boundary=---------------------------5110544421083023415453147877 ... Cookie: zope3_cs_6a553b3=-j7C3CdeW9sUK8BP5x97u2d9o242xMJDzJd8HCQ5AAi9xeFcGTFkAs ... Referer: http://localhost/++etc++site/default/PAU/users/+/AddPrincipalInformation.html%3D ... ... -----------------------------5110544421083023415453147877 ... Content-Disposition: form-data; name="field.login" ... ... betty ... -----------------------------5110544421083023415453147877 ... Content-Disposition: form-data; name="field.passwordManagerName" ... ... Plain Text ... -----------------------------5110544421083023415453147877 ... Content-Disposition: form-data; name="field.password" ... ... 123 ... -----------------------------5110544421083023415453147877 ... Content-Disposition: form-data; name="field.title" ... ... Betty ... -----------------------------5110544421083023415453147877 ... Content-Disposition: form-data; name="field.description" ... ... ... -----------------------------5110544421083023415453147877 ... Content-Disposition: form-data; name="UPDATE_SUBMIT" ... ... Add ... -----------------------------5110544421083023415453147877 ... Content-Disposition: form-data; name="add_input_name" ... ... ... -----------------------------5110544421083023415453147877-- ... """) HTTP/1.1 303 See Other ... >>> print http(r""" ... POST /++etc++site/default/PAU/users/+/AddPrincipalInformation.html%3D HTTP/1.1 ... Authorization: Basic bWdyOm1ncnB3 ... Content-Length: 780 ... Content-Type: multipart/form-data; boundary=---------------------------5110544421083023415453147877 ... Cookie: zope3_cs_6a553b3=-j7C3CdeW9sUK8BP5x97u2d9o242xMJDzJd8HCQ5AAi9xeFcGTFkAs ... Referer: http://localhost/++etc++site/default/PAU/users/+/AddPrincipalInformation.html%3D ... ... -----------------------------5110544421083023415453147877 ... Content-Disposition: form-data; name="field.login" ... ... sally ... -----------------------------5110544421083023415453147877 ... Content-Disposition: form-data; name="field.passwordManagerName" ... ... Plain Text ... -----------------------------5110544421083023415453147877 ... Content-Disposition: form-data; name="field.password" ... ... 123 ... -----------------------------5110544421083023415453147877 ... Content-Disposition: form-data; name="field.title" ... ... Sally ... -----------------------------5110544421083023415453147877 ... Content-Disposition: form-data; name="field.description" ... ... ... -----------------------------5110544421083023415453147877 ... Content-Disposition: form-data; name="UPDATE_SUBMIT" ... ... Add ... -----------------------------5110544421083023415453147877 ... Content-Disposition: form-data; name="add_input_name" ... ... ... -----------------------------5110544421083023415453147877-- ... """) HTTP/1.1 303 See Other ... >>> print http(r""" ... POST /++etc++site/default/PAU/users/+/AddPrincipalInformation.html%3D HTTP/1.1 ... Authorization: Basic bWdyOm1ncnB3 ... Content-Length: 780 ... Content-Type: multipart/form-data; boundary=---------------------------5110544421083023415453147877 ... Cookie: zope3_cs_6a553b3=-j7C3CdeW9sUK8BP5x97u2d9o242xMJDzJd8HCQ5AAi9xeFcGTFkAs ... Referer: http://localhost/++etc++site/default/PAU/users/+/AddPrincipalInformation.html%3D ... ... -----------------------------5110544421083023415453147877 ... Content-Disposition: form-data; name="field.login" ... ... george ... -----------------------------5110544421083023415453147877 ... Content-Disposition: form-data; name="field.passwordManagerName" ... ... Plain Text ... -----------------------------5110544421083023415453147877 ... Content-Disposition: form-data; name="field.password" ... ... 123 ... -----------------------------5110544421083023415453147877 ... Content-Disposition: form-data; name="field.title" ... ... George ... -----------------------------5110544421083023415453147877 ... Content-Disposition: form-data; name="field.description" ... ... ... -----------------------------5110544421083023415453147877 ... Content-Disposition: form-data; name="UPDATE_SUBMIT" ... ... Add ... -----------------------------5110544421083023415453147877 ... Content-Disposition: form-data; name="add_input_name" ... ... ... -----------------------------5110544421083023415453147877-- ... """) HTTP/1.1 303 See Other ... >>> print http(r""" ... POST /++etc++site/default/PAU/users/+/AddPrincipalInformation.html%3D HTTP/1.1 ... Authorization: Basic bWdyOm1ncnB3 ... Content-Length: 780 ... Content-Type: multipart/form-data; boundary=---------------------------5110544421083023415453147877 ... Cookie: zope3_cs_6a553b3=-j7C3CdeW9sUK8BP5x97u2d9o242xMJDzJd8HCQ5AAi9xeFcGTFkAs ... Referer: http://localhost/++etc++site/default/PAU/users/+/AddPrincipalInformation.html%3D ... ... -----------------------------5110544421083023415453147877 ... Content-Disposition: form-data; name="field.login" ... ... mike ... -----------------------------5110544421083023415453147877 ... Content-Disposition: form-data; name="field.passwordManagerName" ... ... Plain Text ... -----------------------------5110544421083023415453147877 ... Content-Disposition: form-data; name="field.password" ... ... 123 ... -----------------------------5110544421083023415453147877 ... Content-Disposition: form-data; name="field.title" ... ... Mike ... -----------------------------5110544421083023415453147877 ... Content-Disposition: form-data; name="field.description" ... ... ... -----------------------------5110544421083023415453147877 ... Content-Disposition: form-data; name="UPDATE_SUBMIT" ... ... Add ... -----------------------------5110544421083023415453147877 ... Content-Disposition: form-data; name="add_input_name" ... ... ... -----------------------------5110544421083023415453147877-- ... """) HTTP/1.1 303 See Other ... >>> print http(r""" ... POST /++etc++site/default/PAU/users/+/AddPrincipalInformation.html%3D HTTP/1.1 ... Authorization: Basic bWdyOm1ncnB3 ... Content-Length: 780 ... Content-Type: multipart/form-data; boundary=---------------------------5110544421083023415453147877 ... Cookie: zope3_cs_6a553b3=-j7C3CdeW9sUK8BP5x97u2d9o242xMJDzJd8HCQ5AAi9xeFcGTFkAs ... Referer: http://localhost/++etc++site/default/PAU/users/+/AddPrincipalInformation.html%3D ... ... -----------------------------5110544421083023415453147877 ... Content-Disposition: form-data; name="field.login" ... ... mary ... -----------------------------5110544421083023415453147877 ... Content-Disposition: form-data; name="field.passwordManagerName" ... ... Plain Text ... -----------------------------5110544421083023415453147877 ... Content-Disposition: form-data; name="field.password" ... ... 123 ... -----------------------------5110544421083023415453147877 ... Content-Disposition: form-data; name="field.title" ... ... Mary ... -----------------------------5110544421083023415453147877 ... Content-Disposition: form-data; name="field.description" ... ... ... -----------------------------5110544421083023415453147877 ... Content-Disposition: form-data; name="UPDATE_SUBMIT" ... ... Add ... -----------------------------5110544421083023415453147877 ... Content-Disposition: form-data; name="add_input_name" ... ... ... -----------------------------5110544421083023415453147877-- ... """) HTTP/1.1 303 See Other ... Next, We'll add out group folder plugin in PAU. >>> print http(r""" ... POST /++etc++site/default/PAU/+/AddGroupFolder.html%3D HTTP/1.1 ... Authorization: Basic bWdyOm1ncnB3 ... Content-Length: 427 ... Content-Type: multipart/form-data; boundary=---------------------------4150524541658557772058105275 ... Referer: http://localhost/++etc++site/default/PAU/+/AddGroupFolder.html= ... ... -----------------------------4150524541658557772058105275 ... Content-Disposition: form-data; name="field.prefix" ... ... groups ... -----------------------------4150524541658557772058105275 ... Content-Disposition: form-data; name="UPDATE_SUBMIT" ... ... Add ... -----------------------------4150524541658557772058105275 ... Content-Disposition: form-data; name="add_input_name" ... ... groups ... -----------------------------4150524541658557772058105275-- ... """) HTTP/1.1 303 See Other ... Next we'll select the credentials and authenticators for the PAU: >>> print http(r""" ... POST /++etc++site/default/PAU/@@configure.html HTTP/1.1 ... Authorization: Basic bWdyOm1ncnB3 ... Content-Length: 1313 ... Content-Type: multipart/form-data; boundary=---------------------------2026736768606413562109112352 ... Referer: http://localhost/++etc++site/default/PAU/@@configure.html ... ... -----------------------------2026736768606413562109112352 ... Content-Disposition: form-data; name="field.credentialsPlugins.to" ... ... U2Vzc2lvbiBDcmVkZW50aWFscw== ... -----------------------------2026736768606413562109112352 ... Content-Disposition: form-data; name="field.credentialsPlugins-empty-marker" ... ... ... -----------------------------2026736768606413562109112352 ... Content-Disposition: form-data; name="field.authenticatorPlugins.to" ... ... dXNlcnM= ... -----------------------------2026736768606413562109112352 ... Content-Disposition: form-data; name="field.authenticatorPlugins.to" ... ... Z3JvdXBz ... -----------------------------2026736768606413562109112352 ... Content-Disposition: form-data; name="field.authenticatorPlugins-empty-marker" ... ... ... -----------------------------2026736768606413562109112352 ... Content-Disposition: form-data; name="UPDATE_SUBMIT" ... ... Change ... -----------------------------2026736768606413562109112352 ... Content-Disposition: form-data; name="field.credentialsPlugins" ... ... U2Vzc2lvbiBDcmVkZW50aWFscw== ... -----------------------------2026736768606413562109112352 ... Content-Disposition: form-data; name="field.authenticatorPlugins" ... ... dXNlcnM= ... -----------------------------2026736768606413562109112352 ... Content-Disposition: form-data; name="field.authenticatorPlugins" ... ... Z3JvdXBz ... -----------------------------2026736768606413562109112352-- ... """) HTTP/1.1 200 OK ... Now, we can define some groups. Let's start with a group named "Admin": >>> print http(r""" ... POST /++etc++site/default/PAU/groups/+/AddGroupInformation.html%3D HTTP/1.1 ... Authorization: Basic bWdyOm1ncnB3 ... Content-Length: 550 ... Content-Type: multipart/form-data; boundary=---------------------------20619400354342370301249668954 ... Referer: http://localhost/++etc++site/default/PAU/groups/+/AddGroupInformation.html= ... ... -----------------------------20619400354342370301249668954 ... Content-Disposition: form-data; name="field.title" ... ... Admin ... -----------------------------20619400354342370301249668954 ... Content-Disposition: form-data; name="field.description" ... ... ... -----------------------------20619400354342370301249668954 ... Content-Disposition: form-data; name="UPDATE_SUBMIT" ... ... Add ... -----------------------------20619400354342370301249668954 ... Content-Disposition: form-data; name="add_input_name" ... ... admin ... -----------------------------20619400354342370301249668954-- ... """) HTTP/1.1 303 See Other ... That includes Betty, Mary and Mike: >>> print http(r""" ... POST /++etc++site/default/PAU/groups/admin/@@edit.html HTTP/1.1 ... Authorization: Basic bWdyOm1ncnB3 ... Content-Length: 1509 ... Content-Type: multipart/form-data; boundary=---------------------------6981402699601872602121555350 ... Referer: http://localhost/++etc++site/default/PAU/groups/admin/@@edit.html ... ... -----------------------------6981402699601872602121555350 ... Content-Disposition: form-data; name="field.title" ... ... Admin ... -----------------------------6981402699601872602121555350 ... Content-Disposition: form-data; name="field.description" ... ... ... -----------------------------6981402699601872602121555350 ... Content-Disposition: form-data; name="field.principals.displayed" ... ... y ... -----------------------------6981402699601872602121555350 ... Content-Disposition: form-data; name="field.principals.MC51c2Vycw__.query.field.search" ... ... ... -----------------------------6981402699601872602121555350 ... Content-Disposition: form-data; name="field.principals:list" ... ... dXNlcnMz ... -----------------------------6981402699601872602121555350 ... Content-Disposition: form-data; name="field.principals:list" ... ... dXNlcnM3 ... -----------------------------6981402699601872602121555350 ... Content-Disposition: form-data; name="field.principals:list" ... ... dXNlcnM2 ... -----------------------------6981402699601872602121555350 ... Content-Disposition: form-data; name="field.principals.MC51c2Vycw__.apply" ... ... Apply ... -----------------------------6981402699601872602121555350 ... Content-Disposition: form-data; name="field.principals.MC5ncm91cHM_.query.field.search" ... ... ... -----------------------------6981402699601872602121555350 ... Content-Disposition: form-data; name="field.principals.MQ__.query.searchstring" ... ... ... -----------------------------6981402699601872602121555350-- ... """) HTTP/1.1 200 OK ... and a group "Power Users" >>> print http(r""" ... POST /++etc++site/default/PAU/groups/+/AddGroupInformation.html%3D HTTP/1.1 ... Authorization: Basic bWdyOm1ncnB3 ... Content-Length: 561 ... Content-Type: multipart/form-data; boundary=---------------------------168380148515549442351132560943 ... Referer: http://localhost/++etc++site/default/PAU/groups/+/AddGroupInformation.html= ... ... -----------------------------168380148515549442351132560943 ... Content-Disposition: form-data; name="field.title" ... ... Power Users ... -----------------------------168380148515549442351132560943 ... Content-Disposition: form-data; name="field.description" ... ... ... -----------------------------168380148515549442351132560943 ... Content-Disposition: form-data; name="UPDATE_SUBMIT" ... ... Add ... -----------------------------168380148515549442351132560943 ... Content-Disposition: form-data; name="add_input_name" ... ... power ... -----------------------------168380148515549442351132560943-- ... """) HTTP/1.1 303 See Other ... with Bill and Betty as members: >>> print http(r""" ... POST /++etc++site/default/PAU/groups/power/@@edit.html HTTP/1.1 ... Authorization: Basic bWdyOm1ncnB3 ... Content-Length: 1729 ... Content-Type: multipart/form-data; boundary=---------------------------181944013812647128322134918391 ... Referer: http://localhost/++etc++site/default/PAU/groups/power/@@edit.html ... ... -----------------------------181944013812647128322134918391 ... Content-Disposition: form-data; name="field.title" ... ... Power Users ... -----------------------------181944013812647128322134918391 ... Content-Disposition: form-data; name="field.description" ... ... ... -----------------------------181944013812647128322134918391 ... Content-Disposition: form-data; name="field.principals:list" ... ... dXNlcnMz ... -----------------------------181944013812647128322134918391 ... Content-Disposition: form-data; name="field.principals:list" ... ... dXNlcnMy ... -----------------------------181944013812647128322134918391 ... Content-Disposition: form-data; name="field.principals.displayed" ... ... y ... -----------------------------181944013812647128322134918391 ... Content-Disposition: form-data; name="field.principals.MC51c2Vycw__.query.field.search" ... ... ... -----------------------------181944013812647128322134918391 ... Content-Disposition: form-data; name="field.principals.MC5ncm91cHM_.query.field.search" ... ... ... -----------------------------181944013812647128322134918391 ... Content-Disposition: form-data; name="field.principals.MQ__.query.searchstring" ... ... ... -----------------------------181944013812647128322134918391 ... Content-Disposition: form-data; name="UPDATE_SUBMIT" ... ... Change ... -----------------------------181944013812647128322134918391-- ... """) HTTP/1.1 200 OK ... Now, with these groups set up, we should see these groups on the affected principals. First, we'll make the root folder the thread-local site: >>> from zope.site.hooks import setSite >>> setSite(getRootFolder()) and we'll get the pluggable authentication utility: >>> from zope.authentication.interfaces import IAuthentication >>> from zope.component import getUtility >>> principals = getUtility(IAuthentication) Finally we'll get Betty and see that she is in the admin and power-user groups: >>> betty = principals.getPrincipal(u'users3') >>> betty.groups.sort() >>> betty.groups [u'groupspower', 'zope.Authenticated', 'zope.Everybody'] And we'll get Bill, and see that he is only in the power-user group: >>> bill = principals.getPrincipal(u'users2') >>> bill.groups ['zope.Everybody', 'zope.Authenticated', u'groupspower'] zope.app.authentication-3.9/src/zope/app/authentication/browser/loginform.py0000644000175000017500000000300511457014222025351 0ustar jwjw############################################################################## # # Copyright (c) 2009 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. # ############################################################################## """Login Form $Id$ """ from zope.authentication.interfaces import IUnauthenticatedPrincipal class LoginForm(object): """Mix-in class to implement login form logic""" def __call__(self): request = self.request principal = request.principal unauthenticated = IUnauthenticatedPrincipal.providedBy(principal) self.unauthenticated = unauthenticated camefrom = request.get('camefrom') if isinstance(camefrom, list): # this can happen on python2.6, as it changed the # behaviour of cgi.FieldStorage a bit. camefrom = camefrom[0] self.camefrom = camefrom if (not unauthenticated) and ('SUBMIT' in request): # authenticated by submitting request.response.redirect(camefrom or '.') return '' return self.index() # call template zope.app.authentication-3.9/src/zope/app/authentication/browser/granting.txt0000644000175000017500000001643511457014222025370 0ustar jwjwGranting View ============= The granting view allows the user to grant permissions and roles to principals. The view unfortunately depends on a lot of other components: - Roles >>> from zope.app.testing import ztapi >>> from zope.securitypolicy.role import Role >>> from zope.securitypolicy.interfaces import IRole >>> ztapi.provideUtility(IRole, Role(u'role1', u'Role 1'), u'role1') >>> ztapi.provideUtility(IRole, Role(u'role2', u'Role 2'), u'role2') >>> ztapi.provideUtility(IRole, Role(u'role3', u'Role 3'), u'role3') - Permissions >>> from zope.security.permission import Permission >>> from zope.security.interfaces import IPermission >>> ztapi.provideUtility(IPermission, Permission(u'permission1', ... u'Permission 1'), u'permission1') >>> ztapi.provideUtility(IPermission, Permission(u'permission2', ... u'Permission 2'), u'permission2') >>> ztapi.provideUtility(IPermission, Permission(u'permission3', ... u'Permission 3'), u'permission3') - Authentication Utility >>> class Principal: ... def __init__(self, id, title): self.id, self.title = id, title >>> from zope.app.security.interfaces import IAuthentication >>> from zope.app.security.interfaces import PrincipalLookupError >>> from zope.interface import implements >>> class AuthUtility: ... implements(IAuthentication) ... data = {'jim': Principal('jim', 'Jim Fulton'), ... 'stephan': Principal('stephan', 'Stephan Richter')} ... ... def getPrincipal(self, id): ... try: ... return self.data.get(id) ... except KeyError: ... raise PrincipalLookupError(id) ... ... def getPrincipals(self, search): ... return [principal ... for principal in self.data.values() ... if search in principal.title] >>> ztapi.provideUtility(IAuthentication, AuthUtility()) - Security-related Adapters >>> from zope.annotation.interfaces import IAnnotatable >>> from zope.securitypolicy.interfaces import IPrincipalRoleManager >>> from zope.securitypolicy.principalrole import \ ... AnnotationPrincipalRoleManager >>> ztapi.provideAdapter(IAnnotatable, IPrincipalRoleManager, ... AnnotationPrincipalRoleManager) >>> from zope.securitypolicy.interfaces import \ ... IPrincipalPermissionManager >>> from zope.securitypolicy.principalpermission import \ ... AnnotationPrincipalPermissionManager >>> ztapi.provideAdapter(IAnnotatable, IPrincipalPermissionManager, ... AnnotationPrincipalPermissionManager) - Vocabulary Choice Widgets >>> from zope.schema.interfaces import IChoice >>> from zope.formlib.interfaces import IInputWidget >>> from zope.formlib.widgets import ChoiceInputWidget >>> ztapi.browserViewProviding(IChoice, ChoiceInputWidget, IInputWidget) >>> from zope.schema.interfaces import IVocabularyTokenized >>> from zope.publisher.interfaces.browser import IBrowserRequest >>> from zope.formlib.widgets import DropdownWidget >>> ztapi.provideMultiView((IChoice, IVocabularyTokenized), ... IBrowserRequest, IInputWidget, '', ... DropdownWidget) - Support Views for the Principal Source Widget >>> from zope.app.security.interfaces import IPrincipalSource >>> from zope.app.security.browser.principalterms import PrincipalTerms >>> from zope.browser.interfaces import ITerms >>> ztapi.browserViewProviding(IPrincipalSource, PrincipalTerms, ITerms) >>> from zope.app.security.browser.auth import AuthUtilitySearchView >>> from zope.formlib.interfaces import ISourceQueryView >>> ztapi.browserViewProviding(IAuthentication, ... AuthUtilitySearchView, ... ISourceQueryView) >>> from zope.schema.interfaces import ISource >>> from zope.formlib.source import SourceInputWidget >>> ztapi.provideMultiView((IChoice, ISource), IBrowserRequest, ... IInputWidget, '', SourceInputWidget) - Attribute Annotatable Adapter >>> from zope.app.testing import setup >>> setup.setUpAnnotations() >>> setup.setUpSiteManagerLookup() - Content Object >>> from zope.annotation.interfaces import IAttributeAnnotatable >>> class Content: ... implements(IAttributeAnnotatable) ... __annotations__ = {} (This is Jim's understanding of a "easy" setup!) Now that we have all the components we need, let's create *the* view. >>> ob = Content() >>> from zope.publisher.browser import TestRequest >>> request = TestRequest() >>> from zope.app.authentication.browser.granting import Granting >>> view = Granting(ob, request) If we call status, we get nothing and the view's principal attribute is `None`: >>> view.status() u'' >>> view.principal Since we have not selected a principal, we have no role or permission widgets: >>> getattr(view, 'roles', None) >>> getattr(view, 'permissions', None) Now that we have a selected principal, then >>> view.request.form['field.principal.displayed'] = 'y' >>> view.request.form['field.principal'] = 'amlt' (Yes, 'amlt' is the base 64 code for 'jim'.) >>> view.status() u'' and now the `view.principal` is set: >>> view.principal 'jim' Now we should have a list of role and permission widgets, and all of them should be unset, because do not have any settings for 'jim'. >>> [role.context.title for role in view.roles] [u'Role 1', u'Role 2', u'Role 3'] >>> [perm.context.title for perm in view.permissions] [u'Permission 1', u'Permission 2', u'Permission 3'] Now we change some settings and submit the form: >>> from zope.securitypolicy.interfaces import Allow, Deny, Unset >>> view.request.form['field.amlt.role.role1'] = 'unset' >>> view.request.form['field.amlt.role.role1-empty-makrer'] = 1 >>> view.request.form['field.amlt.role.role2'] = 'allow' >>> view.request.form['field.amlt.role.role2-empty-makrer'] = 1 >>> view.request.form['field.amlt.role.role3'] = 'deny' >>> view.request.form['field.amlt.role.role3-empty-makrer'] = 1 >>> view.request.form['field.amlt.permission.permission1'] = 'unset' >>> view.request.form['field.amlt.permission.permission1-empty-makrer'] = 1 >>> view.request.form['field.amlt.permission.permission2'] = 'allow' >>> view.request.form['field.amlt.permission.permission2-empty-makrer'] = 1 >>> view.request.form['field.amlt.permission.permission3'] = 'deny' >>> view.request.form['field.amlt.permission.permission3-empty-makrer'] = 1 >>> view.request.form['GRANT_SUBMIT'] = 'Submit' If we get the status now, the data should be written and a status message should be returned: >>> view.status() u'Grants updated.' >>> roles = IPrincipalRoleManager(ob) >>> roles.getSetting('role1', 'jim') is Unset True >>> roles.getSetting('role2', 'jim') is Allow True >>> roles.getSetting('role3', 'jim') is Deny True >>> roles = IPrincipalPermissionManager(ob) >>> roles.getSetting('permission1', 'jim') is Unset True >>> roles.getSetting('permission2', 'jim') is Allow True >>> roles.getSetting('permission3', 'jim') is Deny True zope.app.authentication-3.9/src/zope/app/authentication/browser/manage_permissionform.pt0000644000175000017500000000534511457014222027745 0ustar jwjw

Roles which are granted the permission Change DTML Methods (id: Zope.Some.Permission)

Role
Setting
Manager
zope.app.authentication-3.9/src/zope/app/authentication/browser/tests/0000755000175000017500000000000011457014227024154 5ustar jwjwzope.app.authentication-3.9/src/zope/app/authentication/browser/tests/test_granting.py0000644000175000017500000000166711457014222027403 0ustar jwjw############################################################################## # # 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. # ############################################################################## """Security Policy Granting Views Tests""" from zope.app.testing import placelesssetup import doctest import unittest def test_suite(): return doctest.DocFileSuite('../granting.txt', setUp=placelesssetup.setUp, tearDown=placelesssetup.tearDown) zope.app.authentication-3.9/src/zope/app/authentication/browser/tests/test_rolepermissionview.py0000644000175000017500000001750611457014222031536 0ustar jwjw############################################################################## # # Copyright (c) 2001, 2002 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. # ############################################################################## """Role-Permission View Tests $Id: test_rolepermissionview.py 116801 2010-09-25 09:04:32Z icemac $ """ import unittest import zope.interface from zope.i18n.interfaces import ITranslationDomain from zope.i18nmessageid import Message from zope.publisher.browser import TestRequest, BrowserView from zope.exceptions.interfaces import UserError from zope.security.permission import Permission from zope.security.interfaces import IPermission from zope.app.testing import ztapi from zope.app.component.testing import PlacefulSetup from zope.securitypolicy.role import Role from zope.securitypolicy.interfaces import IRole from zope.app.authentication.browser.tests.rolepermissionmanager import \ RolePermissionManager from zope.app.authentication.browser.rolepermissionview import \ RolePermissionView class RolePermissionView(RolePermissionView, BrowserView): """Adding BrowserView to Utilities; this is usually done by ZCML.""" class TranslationDomain: zope.interface.implements(ITranslationDomain) def __init__(self, **translations): self.translations = translations def translate(self, msgid, *ignored, **also_ignored): return self.translations.get(msgid, msgid) def defineRole(id, title=None, description=None): role = Role(id, title, description) ztapi.provideUtility(IRole, role, name=role.id) return role def definePermission(id, title=None, description=None): permission = Permission(id, title, description) ztapi.provideUtility(IPermission, permission, name=permission.id) return permission class FakeSiteManager: def __init__(self, site): self.__parent__ = site class Test(PlacefulSetup, unittest.TestCase): def setUp(self): PlacefulSetup.setUp(self) defineRole('manager', Message('Manager', 'testdomain')) defineRole('member', Message('Member', 'testdomain')) definePermission('read', Message('Read', 'testdomain')) definePermission('write', Message('Write', 'testdomain')) site = RolePermissionManager() self.view = RolePermissionView(FakeSiteManager(site), None) ztapi.provideUtility(ITranslationDomain, TranslationDomain(Member="A Member", Write="A Write", ), 'testdomain') def testRoles(self): self.assertEqual([role.title for role in self.view.roles()], ["Member", "Manager"]) def testPermisssions(self): self.assertEqual([role.title for role in self.view.permissions()], ["Write", "Read"]) def testMatrix(self): roles = self.view.roles() permissions = self.view.permissions() # manager member # read + # write . - env = { 'p0': 'read', 'p1': 'write', 'r0': 'manager', 'r1': 'member', 'p0r0': 'Allow', 'p1r0': 'Unset', 'p1r1': 'Deny', 'SUBMIT': 1 } self.view.request = TestRequest(environ=env) self.view.update() permissionRoles = self.view.permissionRoles() for ip in range(len(permissionRoles)): permissionRole = permissionRoles[ip] rset = permissionRole.roleSettings() for ir in range(len(rset)): setting = rset[ir] r = roles[ir].id p = permissions[ip].id if setting == 'Allow': self.failUnless(r == 'manager' and p == 'read') elif setting == 'Deny': self.failUnless(r == 'member' and p == 'write') else: self.failUnless(setting == 'Unset') # manager member # read - # write + env = { 'p0': 'read', 'p1': 'write', 'r0': 'manager', 'r1': 'member', 'p0r0': 'Deny', 'p1r0': 'Allow', 'p1r1': 'Unset', 'SUBMIT': 1 } self.view.request = TestRequest(environ=env) self.view.update() permissionRoles = self.view.permissionRoles() for ip in range(len(permissionRoles)): permissionRole = permissionRoles[ip] rset = permissionRole.roleSettings() for ir in range(len(rset)): setting = rset[ir] r = roles[ir].id p = permissions[ip].id if setting == 'Allow': self.failUnless(r == 'manager' and p == 'write') elif setting == 'Deny': self.failUnless(r == 'manager' and p == 'read') else: self.failUnless(setting == 'Unset') def testPermissionRoles(self): env={'permission_id': 'write', 'settings': ['Allow', 'Unset'], 'SUBMIT_PERMS': 1} self.view.request = TestRequest(environ=env) self.view.update() permission = self.view.permissionForID('write') settings = permission.roleSettings() self.assertEquals(settings, ['Allow', 'Unset']) env={'permission_id': 'write', 'settings': ['Unset', 'Deny'], 'SUBMIT_PERMS': 1} self.view.request = TestRequest(environ=env) self.view.update() permission = self.view.permissionForID('write') settings = permission.roleSettings() self.assertEquals(settings, ['Unset', 'Deny']) env={'permission_id': 'write', 'settings': ['Unset', 'foo'], 'SUBMIT_PERMS': 1} self.view.request = TestRequest(environ=env) self.assertRaises(ValueError, self.view.update) def testRolePermissions(self): env={'Allow': ['read'], 'Deny': ['write'], 'SUBMIT_ROLE': 1, 'role_id': 'member'} self.view.request = TestRequest(environ=env) self.view.update(1) role = self.view.roleForID('member') pinfos = role.permissionsInfo() for pinfo in pinfos: pid = pinfo['id'] if pid == 'read': self.assertEquals(pinfo['setting'], 'Allow') if pid == 'write': self.assertEquals(pinfo['setting'], 'Deny') env={'Allow': [], 'Deny': ['read'], 'SUBMIT_ROLE': 1, 'role_id': 'member'} self.view.request = TestRequest(environ=env) self.view.update() role = self.view.roleForID('member') pinfos = role.permissionsInfo() for pinfo in pinfos: pid = pinfo['id'] if pid == 'read': self.assertEquals(pinfo['setting'], 'Deny') if pid == 'write': self.assertEquals(pinfo['setting'], 'Unset') def testRolePermissions_UserError(self): env={'Allow': ['read'], 'Deny': ['read'], 'SUBMIT_ROLE': 1, 'role_id': 'member'} self.view.request = TestRequest(environ=env) self.assertRaises(UserError, self.view.update, 1) def test_suite(): loader=unittest.TestLoader() return loader.loadTestsFromTestCase(Test) if __name__=='__main__': unittest.TextTestRunner().run(test_suite()) zope.app.authentication-3.9/src/zope/app/authentication/browser/tests/rolepermissionmanager.py0000644000175000017500000000607611457014222031137 0ustar jwjw############################################################################## # # Copyright (c) 2001, 2002 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. # ############################################################################## """Test IRolePermissionManager class that has no context. $Id: rolepermissionmanager.py 116801 2010-09-25 09:04:32Z icemac $ """ from zope.interface import implements from zope.securitypolicy.interfaces import Allow, Deny, Unset from zope.securitypolicy.interfaces import IRolePermissionManager from zope.securitypolicy.interfaces import IRolePermissionMap from zope.securitypolicy.securitymap import SecurityMap class RolePermissionManager(object): """ provide adapter that manages role permission data in an object attribute """ implements(IRolePermissionManager, IRolePermissionMap) def __init__(self): self._rp = SecurityMap() def grantPermissionToRole(self, permission_id, role_id): ''' See the interface IRolePermissionManager ''' rp = self._getRolePermissions(create=1) rp.addCell(permission_id, role_id, Allow) def denyPermissionToRole(self, permission_id, role_id): ''' See the interface IRolePermissionManager ''' rp = self._getRolePermissions(create=1) rp.addCell(permission_id, role_id, Deny) def unsetPermissionFromRole(self, permission_id, role_id): ''' See the interface IRolePermissionManager ''' rp = self._getRolePermissions() # Only unset if there is a security map, otherwise, we're done if rp: rp.delCell(permission_id, role_id) def getRolesForPermission(self, permission_id): '''See interface IRolePermissionMap''' rp = self._getRolePermissions() if rp: return rp.getRow(permission_id) else: return [] def getPermissionsForRole(self, role_id): '''See interface IRolePermissionMap''' rp = self._getRolePermissions() if rp: return rp.getCol(role_id) else: return [] def getRolesAndPermissions(self): '''See interface IRolePermissionMap''' rp = self._getRolePermissions() if rp: return rp.getAllCells() else: return [] def getSetting(self, permission_id, role_id): '''See interface IRolePermissionMap''' rp = self._getRolePermissions() if rp: return rp.queryCell(permission_id, role_id) else: return Unset def _getRolePermissions(self, create=0): """Get the role permission map stored in the context, optionally creating one if necessary""" return self._rp zope.app.authentication-3.9/src/zope/app/authentication/browser/tests/__init__.py0000644000175000017500000000000011457014222026246 0ustar jwjwzope.app.authentication-3.9/src/zope/app/authentication/browser/tests/test_doctests.py0000644000175000017500000001272111457014222027413 0ustar jwjw############################################################################## # # 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 $Id: test_doctests.py 116785 2010-09-24 11:52:41Z icemac $ """ __docformat__ = "reStructuredText" import re import unittest import doctest from zope.testing import renormalizing from zope.app.testing.setup import placefulSetUp, placefulTearDown import transaction from zope.interface import directlyProvides from zope.exceptions.interfaces import UserError from zope.app.testing import functional from zope.pluggableauth.factories import Principal from zope.app.authentication.principalfolder import PrincipalFolder from zope.app.authentication.principalfolder import IInternalPrincipal from zope.app.authentication.testing import AppAuthenticationLayer def schemaSearchSetUp(self): placefulSetUp(site=True) def schemaSearchTearDown(self): placefulTearDown() class FunkTest(functional.BrowserTestCase): def test_copypaste_duplicated_id_object(self): root = self.getRootFolder() # Create a principal Folder root['pf'] = PrincipalFolder() pf = root['pf'] # Create a principal with p1 as login principal = Principal('p1') principal.login = 'p1' directlyProvides(principal, IInternalPrincipal) pf['p1'] = principal transaction.commit() self.assertEqual(len(pf.keys()), 1) #raise str([x for x in pf.keys()]) response = self.publish('/pf/@@contents.html', basic='mgr:mgrpw', form={'ids': [u'p1'], 'container_copy_button': u'Copy'}) self.assertEqual(response.getStatus(), 302) # Try to paste the file try: response = self.publish('/pf/@@contents.html', basic='mgr:mgrpw', form={'container_paste_button': ''}) except UserError, e: self.assertEqual( str(e), "The given name(s) [u'p1'] is / are already being used") else: # test failed ! self.asserEqual(1, 0) def test_cutpaste_duplicated_id_object(self): root = self.getRootFolder() # Create a principal Folder root['pf'] = PrincipalFolder() pf = root['pf'] # Create a principal with p1 as login principal = Principal('p1') principal.login = 'p1' directlyProvides(principal, IInternalPrincipal) pf['p1'] = principal transaction.commit() self.assertEqual(len(pf.keys()), 1) #raise str([x for x in pf.keys()]) response = self.publish('/pf/@@contents.html', basic='mgr:mgrpw', form={'ids': [u'p1'], 'container_cut_button': u'Cut'}) self.assertEqual(response.getStatus(), 302) # Try to paste the file try: response = self.publish('/pf/@@contents.html', basic='mgr:mgrpw', form={'container_paste_button': ''}) except UserError, e: self.assertEqual( str(e), "The given name(s) [u'p1'] is / are already being used") else: # test failed ! self.asserEqual(1, 0) checker = renormalizing.RENormalizing([ (re.compile(r"HTTP/1\.1 200 .*"), "HTTP/1.1 200 OK"), (re.compile(r"HTTP/1\.1 303 .*"), "HTTP/1.1 303 See Other"), (re.compile(r"HTTP/1\.1 401 .*"), "HTTP/1.1 401 Unauthorized"), ]) def test_suite(): FunkTest.layer = AppAuthenticationLayer principalfolder = functional.FunctionalDocFileSuite( '../principalfolder.txt', checker=checker) principalfolder.layer = AppAuthenticationLayer groupfolder = functional.FunctionalDocFileSuite( '../groupfolder.txt', checker=checker) groupfolder.layer = AppAuthenticationLayer pau_prefix_and_searching = functional.FunctionalDocFileSuite( '../pau_prefix_and_searching.txt', checker=checker) pau_prefix_and_searching.layer = AppAuthenticationLayer group_searching_with_empty_string = functional.FunctionalDocFileSuite( '../group_searching_with_empty_string.txt', checker=checker) group_searching_with_empty_string.layer = AppAuthenticationLayer special_groups = functional.FunctionalDocFileSuite( '../special-groups.txt', checker=checker) special_groups.layer = AppAuthenticationLayer issue663 = functional.FunctionalDocFileSuite('../issue663.txt') issue663.layer = AppAuthenticationLayer return unittest.TestSuite(( principalfolder, groupfolder, pau_prefix_and_searching, group_searching_with_empty_string, special_groups, unittest.makeSuite(FunkTest), issue663, doctest.DocFileSuite('../schemasearch.txt'), )) zope.app.authentication-3.9/src/zope/app/authentication/browser/manage_access.pt0000644000175000017500000000573011457014222026130 0ustar jwjw

Permission
Roles
zope.app.authentication-3.9/src/zope/app/authentication/browser/httpplugins.zcml0000644000175000017500000000070311457014222026255 0ustar jwjw zope.app.authentication-3.9/src/zope/app/authentication/browser/special-groups.txt0000644000175000017500000002712011457014222026505 0ustar jwjwGranting to unauthenticated =========================== There are 3 special groups: - Everybody, that everybody belongs to, - Unauthenticated, that unauthenticated users belong to, and - Authenticating, that authenticated users belong to. Here's an example: First, we'll set up a pluggable authentication utility containing a principal folder, which we'll create first. Create pluggable authentication utility and register it. >>> print http(r""" ... POST /++etc++site/default/@@contents.html HTTP/1.1 ... Authorization: Basic bWdyOm1ncnB3 ... Content-Length: 98 ... Content-Type: application/x-www-form-urlencoded ... Cookie: zope3_cs_6a553b3=-j7C3CdeW9sUK8BP5x97u2d9o242xMJDzJd8HCQ5AAi9xeFcGTFkAs ... Referer: http://localhost/++etc++site/default/@@contents.html?type_name=BrowserAdd__zope.pluggableauth.authentication.PluggableAuthentication ... ... type_name=BrowserAdd__zope.pluggableauth.authentication.PluggableAuthentication&new_value=PAU""") HTTP/1.1 303 See Other ... >>> print http(r""" ... POST /++etc++site/default/PAU/addRegistration.html HTTP/1.1 ... Authorization: Basic bWdyOm1ncnB3 ... Content-Length: 687 ... Content-Type: multipart/form-data; boundary=---------------------------5559795404609280911441883437 ... Cookie: zope3_cs_6a553b3=-j7C3CdeW9sUK8BP5x97u2d9o242xMJDzJd8HCQ5AAi9xeFcGTFkAs ... Referer: http://localhost/++etc++site/default/PAU/addRegistration.html ... ... -----------------------------5559795404609280911441883437 ... Content-Disposition: form-data; name="field.comment" ... ... ... -----------------------------5559795404609280911441883437 ... Content-Disposition: form-data; name="field.actions.register" ... ... Register ... -----------------------------5559795404609280911441883437-- ... """) HTTP/1.1 303 See Other ... Add a Principal folder plugin to PAU. >>> print http(r""" ... POST /++etc++site/default/PAU/+/AddPrincipalFolder.html%3D HTTP/1.1 ... Authorization: Basic bWdyOm1ncnB3 ... Content-Length: 429 ... Content-Type: multipart/form-data; boundary=---------------------------95449631112274213651507932125 ... Cookie: zope3_cs_6a553b3=-j7C3CdeW9sUK8BP5x97u2d9o242xMJDzJd8HCQ5AAi9xeFcGTFkAs ... Referer: http://localhost/++etc++site/default/PAU/+/AddPrincipalFolder.html= ... ... -----------------------------95449631112274213651507932125 ... Content-Disposition: form-data; name="field.prefix" ... ... users ... -----------------------------95449631112274213651507932125 ... Content-Disposition: form-data; name="UPDATE_SUBMIT" ... ... Add ... -----------------------------95449631112274213651507932125 ... Content-Disposition: form-data; name="add_input_name" ... ... users ... -----------------------------95449631112274213651507932125-- ... """) HTTP/1.1 303 See Other ... Add a principal to it: >>> print http(r""" ... POST /++etc++site/default/PAU/users/+/AddPrincipalInformation.html%3D HTTP/1.1 ... Authorization: Basic bWdyOm1ncnB3 ... Content-Length: 780 ... Content-Type: multipart/form-data; boundary=---------------------------5110544421083023415453147877 ... Cookie: zope3_cs_6a553b3=-j7C3CdeW9sUK8BP5x97u2d9o242xMJDzJd8HCQ5AAi9xeFcGTFkAs ... Referer: http://localhost/++etc++site/default/PAU/users/+/AddPrincipalInformation.html%3D ... ... -----------------------------5110544421083023415453147877 ... Content-Disposition: form-data; name="field.login" ... ... bob ... -----------------------------5110544421083023415453147877 ... Content-Disposition: form-data; name="field.passwordManagerName" ... ... Plain Text ... -----------------------------5110544421083023415453147877 ... Content-Disposition: form-data; name="field.password" ... ... bob ... -----------------------------5110544421083023415453147877 ... Content-Disposition: form-data; name="field.title" ... ... bob ... -----------------------------5110544421083023415453147877 ... Content-Disposition: form-data; name="field.description" ... ... ... -----------------------------5110544421083023415453147877 ... Content-Disposition: form-data; name="UPDATE_SUBMIT" ... ... Add ... -----------------------------5110544421083023415453147877 ... Content-Disposition: form-data; name="add_input_name" ... ... bob ... -----------------------------5110544421083023415453147877-- ... """) HTTP/1.1 303 See Other ... Configure PAU, with registered principal folder plugin. >>> print http(r""" ... POST /++etc++site/default/PAU/@@configure.html HTTP/1.1 ... Authorization: Basic bWdyOm1ncnB3 ... Content-Length: 1038 ... Content-Type: multipart/form-data; boundary=---------------------------6519411471194050603270010787 ... Cookie: zope3_cs_6a553b3=-j7C3CdeW9sUK8BP5x97u2d9o242xMJDzJd8HCQ5AAi9xeFcGTFkAs ... Referer: http://localhost/++etc++site/default/PAU/@@configure.html ... ... -----------------------------6519411471194050603270010787 ... Content-Disposition: form-data; name="field.credentialsPlugins.to" ... ... U2Vzc2lvbiBDcmVkZW50aWFscw== ... -----------------------------6519411471194050603270010787 ... Content-Disposition: form-data; name="field.credentialsPlugins-empty-marker" ... ... ... -----------------------------6519411471194050603270010787 ... Content-Disposition: form-data; name="field.authenticatorPlugins.to" ... ... dXNlcnM= ... -----------------------------6519411471194050603270010787 ... Content-Disposition: form-data; name="field.authenticatorPlugins-empty-marker" ... ... ... -----------------------------6519411471194050603270010787 ... Content-Disposition: form-data; name="UPDATE_SUBMIT" ... ... Change ... -----------------------------6519411471194050603270010787 ... Content-Disposition: form-data; name="field.credentialsPlugins" ... ... U2Vzc2lvbiBDcmVkZW50aWFscw== ... -----------------------------6519411471194050603270010787 ... Content-Disposition: form-data; name="field.authenticatorPlugins" ... ... dXNlcnM= ... -----------------------------6519411471194050603270010787-- ... """) HTTP/1.1 200 OK ... Normally, the anonymous role has view, we'll deny it: >>> print http(r""" ... POST /++etc++site/AllRolePermissions.html HTTP/1.1 ... Authorization: Basic mgr:mgrpw ... Content-Type: application/x-www-form-urlencoded ... ... role_id=zope.Anonymous""" ... """&Deny%3Alist=zope.View""" ... """&Deny%3Alist=zope.app.dublincore.view""" ... """&SUBMIT_ROLE=Save+Changes""") HTTP/1.1 200 OK ... Now, if we try to access the main page as an anonymous user, we'll be unauthorized: >>> print http(r""" ... GET / HTTP/1.1 ... """) ... HTTP/1.1 303 See Other ... We'll even be unauthorized if we try to access it as bob: >>> print http(r""" ... POST /@@loginForm.html?camefrom=http%3A%2F%2Flocalhost%2F%40%40index.html HTTP/1.1 ... Content-Length: 94 ... Content-Type: application/x-www-form-urlencoded ... Cookie: zope3_cs_6a60902=cxcKJetHJjB2Px2umkzvTjeVI1E3aOpirHSjOYlxUPF.VX9DNjybrE ... Referer: http://localhost/@@loginForm.html?camefrom=http%3A%2F%2Flocalhost%2F%40%40index.html ... ... login=bob&password=bob&SUBMIT=Log+in&camefrom=http%3A%2F%2Flocalhost%2F%40%40index.html""") ... HTTP/1.1 303 See Other ... No, let's grant view to the authenticated group: >>> print http(r""" ... POST /@@grant.html HTTP/1.1 ... Authorization: Basic mgr:mgrpw ... Content-Type: application/x-www-form-urlencoded ... ... field.principal=em9wZS5BdXRoZW50aWNhdGVk&field.principal.displayed=y""" ... """&field.em9wZS5BdXRoZW50aWNhdGVk.permission.zope.View=allow""" ... """&field.em9wZS5BdXRoZW50aWNhdGVk.permission.zope.app.dublincore.view=allow""" ... """&GRANT_SUBMIT=Change""") HTTP/1.1 200 OK ... Now, with this, we can access the main page as bob, but not as an anonymous user: >>> print http(r""" ... GET / HTTP/1.1 ... Authorization: Basic bob:123 ... """) HTTP/1.1 200 OK ... >>> print http(r""" ... GET / HTTP/1.1 ... """) HTTP/1.1 200 OK ... ###401 Unauthorized Now, we'll grant to unauthenticated: >>> print http(r""" ... POST /@@grant.html HTTP/1.1 ... Authorization: Basic mgr:mgrpw ... Content-Type: application/x-www-form-urlencoded ... Referer: http://localhost/@@grant.html ... ... field.principal=em9wZS5Bbnlib2R5""" ... """&field.em9wZS5Bbnlib2R5.permission.zope.View=allow""" ... """&field.em9wZS5Bbnlib2R5.permission.zope.app.dublincore.view=allow""" ... """&GRANT_SUBMIT=Change""") HTTP/1.1 200 OK ... With this, we can access the page as either bob or anonymous: >>> print http(r""" ... GET / HTTP/1.1 ... Authorization: Basic bob:123 ... """) HTTP/1.1 200 OK ... >>> print http(r""" ... GET / HTTP/1.1 ... """) HTTP/1.1 200 OK ... Now, we'll remove the authenticated group grant: >>> print http(r""" ... POST /@@grant.html HTTP/1.1 ... Authorization: Basic mgr:mgrpw ... Content-Type: application/x-www-form-urlencoded ... ... field.principal=em9wZS5BdXRoZW50aWNhdGVk""" ... """&field.em9wZS5BdXRoZW50aWNhdGVk.permission.zope.View=unset""" ... """&field.em9wZS5BdXRoZW50aWNhdGVk.permission.zope.app.dublincore.view=unset""" ... """&GRANT_SUBMIT=Change""") HTTP/1.1 200 OK ... And anonymous people will be able to access the page, but bob won't be able to: >>> print http(r""" ... POST /@@loginForm.html?camefrom=http%3A%2F%2Flocalhost%2F%40%40index.html HTTP/1.1 ... Content-Length: 94 ... Content-Type: application/x-www-form-urlencoded ... Cookie: zope3_cs_6a60902=cxcKJetHJjB2Px2umkzvTjeVI1E3aOpirHSjOYlxUPF.VX9DNjybrE ... Referer: http://localhost/@@loginForm.html?camefrom=http%3A%2F%2Flocalhost%2F%40%40index.html ... ... login=bob&password=bob&SUBMIT=Log+in&camefrom=http%3A%2F%2Flocalhost%2F%40%40index.html""") ... HTTP/1.1 303 See Other ... >>> print http(r""" ... GET / HTTP/1.1 ... """) HTTP/1.1 303 See Other ... Now, we'll remove the unauthenticated group grant: >>> print http(r""" ... POST /@@grant.html HTTP/1.1 ... Authorization: Basic mgr:mgrpw ... Content-Type: application/x-www-form-urlencoded ... Referer: http://localhost/@@grant.html ... ... field.principal=em9wZS5Bbnlib2R5""" ... """&field.em9wZS5Bbnlib2R5.permission.zope.View=unset""" ... """&field.em9wZS5Bbnlib2R5.permission.zope.app.dublincore.view=unset""" ... """&GRANT_SUBMIT=Change""") HTTP/1.1 200 OK ... >>> print http(r""" ... POST /@@loginForm.html?camefrom=http%3A%2F%2Flocalhost%2F%40%40index.html HTTP/1.1 ... Content-Length: 94 ... Content-Type: application/x-www-form-urlencoded ... Cookie: zope3_cs_6a60902=cxcKJetHJjB2Px2umkzvTjeVI1E3aOpirHSjOYlxUPF.VX9DNjybrE ... Referer: http://localhost/@@loginForm.html?camefrom=http%3A%2F%2Flocalhost%2F%40%40index.html ... ... login=bob&password=bob&SUBMIT=Log+in&camefrom=http%3A%2F%2Flocalhost%2F%40%40index.html""") ... HTTP/1.1 303 See Other ... >>> print http(r""" ... GET / HTTP/1.1 ... """) HTTP/1.1 303 See Other ... Finally, we'll grant to everybody: >>> print http(r""" ... POST /@@grant.html HTTP/1.1 ... Authorization: Basic mgr:mgrpw ... Content-Type: application/x-www-form-urlencoded ... ... field.principal=em9wZS5FdmVyeWJvZHk_""" ... """&field.em9wZS5FdmVyeWJvZHk_.permission.zope.View=allow""" ... """&field.em9wZS5FdmVyeWJvZHk_.permission.zope.app.dublincore.view=allow""" ... """&GRANT_SUBMIT=Change""", handle_errors = False) HTTP/1.1 200 OK ... and both bob nor anonymous can access: >>> print http(r""" ... GET / HTTP/1.1 ... Authorization: Basic bob:123 ... """) HTTP/1.1 200 OK ... >>> print http(r""" ... GET / HTTP/1.1 ... """) HTTP/1.1 200 OK ... zope.app.authentication-3.9/src/zope/app/authentication/browser/loginform.pt0000644000175000017500000000252711457014222025354 0ustar jwjw Sign in

Please provide Login Information

You are not authorized to perform this action. However, you may login as a different user who is authorized.

zope.app.authentication-3.9/src/zope/app/authentication/browser/principalfolder.txt0000644000175000017500000003327711457014222026737 0ustar jwjwUsing Principal Folders ======================= Principal folders are Pluggable-Authentication plugins that manage principal information, especially authentication credentials. To use a principal folder, you need add a principal folder plugin to the PAU and to configure the PAU to use plugin. Let's look at an example, in which we'll define a new manager named Bob. Initially, attempts to log in as Bob fail: >>> print http(r""" ... GET /manage HTTP/1.1 ... Authorization: Basic Ym9iOjEyMw== ... """) HTTP/1.1 401 Unauthorized ... To allow Bob to log in, we'll start by adding a principal folder to PAU: We need to create and register a pluggable authentication utility. >>> print http(r""" ... POST /++etc++site/default/@@contents.html HTTP/1.1 ... Authorization: Basic bWdyOm1ncnB3 ... Content-Length: 98 ... Content-Type: application/x-www-form-urlencoded ... Cookie: zope3_cs_6a553b3=-j7C3CdeW9sUK8BP5x97u2d9o242xMJDzJd8HCQ5AAi9xeFcGTFkAs ... Referer: http://localhost/++etc++site/default/@@contents.html?type_name=BrowserAdd__zope.pluggableauth.authentication.PluggableAuthentication ... ... type_name=BrowserAdd__zope.pluggableauth.authentication.PluggableAuthentication&new_value=PAU""", ... handle_errors=False) HTTP/1.1 303 See Other ... >>> print http(r""" ... GET /++etc++site/default/PAU/@@registration.html HTTP/1.1 ... Authorization: Basic bWdyOm1ncnB3 ... Cookie: zope3_cs_6a553b3=-j7C3CdeW9sUK8BP5x97u2d9o242xMJDzJd8HCQ5AAi9xeFcGTFkAs ... Referer: http://localhost/++etc++site/default/@@contents.html?type_name=BrowserAdd__zope.app.authentication.authentication.PluggableAuthentication ... """) HTTP/1.1 200 OK ... Register PAU. >>> print http(r""" ... POST /++etc++site/default/PAU/addRegistration.html HTTP/1.1 ... Authorization: Basic bWdyOm1ncnB3 ... Content-Length: 687 ... Content-Type: multipart/form-data; boundary=---------------------------5559795404609280911441883437 ... Cookie: zope3_cs_6a553b3=-j7C3CdeW9sUK8BP5x97u2d9o242xMJDzJd8HCQ5AAi9xeFcGTFkAs ... Referer: http://localhost/++etc++site/default/PAU/addRegistration.html ... ... -----------------------------5559795404609280911441883437 ... Content-Disposition: form-data; name="field.comment" ... ... ... -----------------------------5559795404609280911441883437 ... Content-Disposition: form-data; name="field.actions.register" ... ... Register ... -----------------------------5559795404609280911441883437-- ... """, handle_errors=False) HTTP/1.1 303 See Other ... Add a Principal folder plugin to PAU. >>> print http(r""" ... POST /++etc++site/default/PAU/+/AddPrincipalFolder.html%3D HTTP/1.1 ... Authorization: Basic bWdyOm1ncnB3 ... Content-Length: 429 ... Content-Type: multipart/form-data; boundary=---------------------------95449631112274213651507932125 ... Cookie: zope3_cs_6a553b3=-j7C3CdeW9sUK8BP5x97u2d9o242xMJDzJd8HCQ5AAi9xeFcGTFkAs ... Referer: http://localhost/++etc++site/default/PAU/+/AddPrincipalFolder.html= ... ... -----------------------------95449631112274213651507932125 ... Content-Disposition: form-data; name="field.prefix" ... ... users ... -----------------------------95449631112274213651507932125 ... Content-Disposition: form-data; name="UPDATE_SUBMIT" ... ... Add ... -----------------------------95449631112274213651507932125 ... Content-Disposition: form-data; name="add_input_name" ... ... users ... -----------------------------95449631112274213651507932125-- ... """) HTTP/1.1 303 See Other ... We specify a prefix, `users.`. This is used to make sure that ids used by this plugin don't conflict with ids of other plugins. We also name ths plugin `users`. This is the name we'll use when we configure the pluggable authentiaction service. Next we'll view the contents page of the principal folder: >>> print http(r""" ... GET /++etc++site/default/PAU/users/@@contents.html HTTP/1.1 ... Authorization: Basic bWdyOm1ncnB3 ... Cookie: zope3_cs_6a553b3=-j7C3CdeW9sUK8BP5x97u2d9o242xMJDzJd8HCQ5AAi9xeFcGTFkAs ... Referer: http://localhost/++etc++site/default/PAU/users/addRegistration.html ... """) HTTP/1.1 200 OK ... And we'll add a principal, Bob: >>> print http(r""" ... POST /++etc++site/default/PAU/users/+/AddPrincipalInformation.html%3D HTTP/1.1 ... Authorization: Basic bWdyOm1ncnB3 ... Content-Length: 780 ... Content-Type: multipart/form-data; boundary=---------------------------5110544421083023415453147877 ... Cookie: zope3_cs_6a553b3=-j7C3CdeW9sUK8BP5x97u2d9o242xMJDzJd8HCQ5AAi9xeFcGTFkAs ... Referer: http://localhost/++etc++site/default/PAU/users/+/AddPrincipalInformation.html%3D ... ... -----------------------------5110544421083023415453147877 ... Content-Disposition: form-data; name="field.login" ... ... bob ... -----------------------------5110544421083023415453147877 ... Content-Disposition: form-data; name="field.passwordManagerName" ... ... SHA1 ... -----------------------------5110544421083023415453147877 ... Content-Disposition: form-data; name="field.password" ... ... bob ... -----------------------------5110544421083023415453147877 ... Content-Disposition: form-data; name="field.title" ... ... bob ... -----------------------------5110544421083023415453147877 ... Content-Disposition: form-data; name="field.description" ... ... ... -----------------------------5110544421083023415453147877 ... Content-Disposition: form-data; name="UPDATE_SUBMIT" ... ... Add ... -----------------------------5110544421083023415453147877 ... Content-Disposition: form-data; name="add_input_name" ... ... bob ... -----------------------------5110544421083023415453147877-- ... """) HTTP/1.1 303 See Other ... Note that we didn't pick a name. The name, together with the folder prefix. If we don't choose a name, a numeric id is chosen. Now we have a principal folder with a principal. Configure PAU, with registered principal folder plugin and select any one credentials. >>> print http(r""" ... POST /++etc++site/default/PAU/@@configure.html HTTP/1.1 ... Authorization: Basic bWdyOm1ncnB3 ... Content-Length: 1038 ... Content-Type: multipart/form-data; boundary=---------------------------6519411471194050603270010787 ... Cookie: zope3_cs_6a553b3=-j7C3CdeW9sUK8BP5x97u2d9o242xMJDzJd8HCQ5AAi9xeFcGTFkAs ... Referer: http://localhost/++etc++site/default/PAU/@@configure.html ... ... -----------------------------6519411471194050603270010787 ... Content-Disposition: form-data; name="field.credentialsPlugins.to" ... ... U2Vzc2lvbiBDcmVkZW50aWFscw== ... -----------------------------6519411471194050603270010787 ... Content-Disposition: form-data; name="field.credentialsPlugins-empty-marker" ... ... ... -----------------------------6519411471194050603270010787 ... Content-Disposition: form-data; name="field.authenticatorPlugins.to" ... ... dXNlcnM= ... -----------------------------6519411471194050603270010787 ... Content-Disposition: form-data; name="field.authenticatorPlugins-empty-marker" ... ... ... -----------------------------6519411471194050603270010787 ... Content-Disposition: form-data; name="UPDATE_SUBMIT" ... ... Change ... -----------------------------6519411471194050603270010787 ... Content-Disposition: form-data; name="field.credentialsPlugins" ... ... U2Vzc2lvbiBDcmVkZW50aWFscw== ... -----------------------------6519411471194050603270010787 ... Content-Disposition: form-data; name="field.authenticatorPlugins" ... ... dXNlcnM= ... -----------------------------6519411471194050603270010787-- ... """, handle_errors=False) HTTP/1.1 200 OK ... Now, with this in place, Bob can log in, but he isn't allowed to access the management interface. When he attempts to do so, the PAU issues a challenge to let bob login as a different user >>> print http(r""" ... POST /@@loginForm.html?camefrom=http%3A%2F%2Flocalhost%2F%40%40login.html HTTP/1.1 ... Content-Length: 94 ... Content-Type: application/x-www-form-urlencoded ... Cookie: zope3_cs_6a58ae0=zt1tvSi4JRxMD4bggPyUqMA70iE3bgAqvQB.y.ZeOhMmkfbens3-pU ... Referer: http://localhost/@@loginForm.html?camefrom=http%3A%2F%2Flocalhost%2F%40%40login.html ... ... login=bob&password=bob&SUBMIT=Log+in&camefrom=http%3A%2F%2Flocalhost%2F%40%40login.html""") HTTP/1.1 303 See Other ... When he attempts to do so, the PAU issues a challenge to let bob login as a different user >>> print http(r""" ... GET /+ HTTP/1.1 ... Cookie: zope3_cs_6a58ae0=zt1tvSi4JRxMD4bggPyUqMA70iE3bgAqvQB.y.ZeOhMmkfbens3-pU ... """) HTTP/1.1 303 See Other ... We go to the granting interface and search for and find a principal named Bob: >>> print http(r""" ... GET /@@grant.html HTTP/1.1 ... Authorization: Basic bWdyOm1ncnB3 ... Cookie: zope3_cs_6a58ae0=zt1tvSi4JRxMD4bggPyUqMA70iE3bgAqvQB.y.ZeOhMmkfbens3-pU ... Referer: http://localhost/@@contents.html ... """) HTTP/1.1 200 OK ... >>> print http(r""" ... POST /@@grant.html HTTP/1.1 ... Authorization: Basic bWdyOm1ncnB3 ... Content-Length: 210 ... Content-Type: application/x-www-form-urlencoded ... Cookie: zope3_cs_6a58ae0=zt1tvSi4JRxMD4bggPyUqMA70iE3bgAqvQB.y.ZeOhMmkfbens3-pU ... Referer: http://localhost/@@grant.html ... ... field.principal.displayed=y&field.principal.MC51c2Vycw__.query.field.search=&field.principal.MC51c2Vycw__.selection=dXNlcnNib2I_&field.principal.MC51c2Vycw__.apply=Apply&field.principal.MQ__.query.searchstring=""") HTTP/1.1 200 OK ... >>> print http(r""" ... POST /@@grant.html HTTP/1.1 ... Authorization: Basic bWdyOm1ncnB3 ... Content-Length: 210 ... Content-Type: application/x-www-form-urlencoded ... Cookie: zope3_cs_6a58ae0=zt1tvSi4JRxMD4bggPyUqMA70iE3bgAqvQB.y.ZeOhMmkfbens3-pU ... Referer: http://localhost/@@grant.html ... ... field.principal.displayed=y&field.principal.MC51c2Vycw__.query.field.search=&field.principal.MC51c2Vycw__.selection=dXNlcnNib2I_&field.principal.MC51c2Vycw__.apply=Apply&field.principal.MQ__.query.searchstring=""") HTTP/1.1 200 OK ... We select Bob and grant him the Manager role: >>> print http(r""" ... POST /@@grant.html HTTP/1.1 ... Authorization: Basic mgr:mgrpw ... Content-Length: 5316 ... Content-Type: application/x-www-form-urlencoded ... Referer: http://localhost/@@grant.html ... ... field.principal=dXNlcnMuMQ__""" ... """&field.principal.displayed=y""" ... """&field.principal.MC51c2Vycw__.query.field.search=bob""" ... """&field.principal.MA__.query.searchstring=""" ... """&GRANT_SUBMIT=Change""" ... """&field.dXNlcnMuMQ__.role.zope.Manager=allow""" ... """&field.dXNlcnMuMQ__.role.zope.Manager-empty-marker=1""") HTTP/1.1 200 OK ... >>> print http(r""" ... POST /@@grant.html HTTP/1.1 ... Authorization: Basic bWdyOm1ncnB3 ... Content-Length: 2598 ... Content-Type: application/x-www-form-urlencoded ... Cookie: zope3_cs_6a58ae0=zt1tvSi4JRxMD4bggPyUqMA70iE3bgAqvQB.y.ZeOhMmkfbens3-pU ... Referer: http://localhost/@@grant.html ... ... field.principal=dXNlcnNib2I_&field.principal.displayed=y&field.principal.MC51c2Vycw__.query.field.search=&field.principal.MQ__.query.searchstring=&GRANT_SUBMIT=Change&field.dXNlcnNib2I_.role.bugtracker.Admin=unset&field.dXNlcnNib2I_.role.bugtracker.Editor=unset&field.dXNlcnNib2I_.role.bugtracker.User=unset&field.dXNlcnNib2I_.role.zope.Anonymous=unset&field.dXNlcnNib2I_.role.zope.Manager=allow&field.dXNlcnNib2I_.role.zope.Member=unset&field.dXNlcnNib2I_.role.zwiki.Admin=unset&field.dXNlcnNib2I_.role.zwiki.Editor=unset&field.dXNlcnNib2I_.role.zwiki.User=unset&field.dXNlcnNib2I_.permission.bugtracker.AddBug=unset&field.dXNlcnNib2I_.permission.bugtracker.AddAttachment=unset&field.dXNlcnNib2I_.permission.bugtracker.AddComment=unset&field.dXNlcnNib2I_.permission.zwiki.AddWikiPage=unset&field.dXNlcnNib2I_.permission.zwiki.CommentWikiPage=unset&field.dXNlcnNib2I_.permission.zwiki.DeleteWikiPage=unset&field.dXNlcnNib2I_.permission.bugtracker.EditBug=unset&field.dXNlcnNib2I_.permission.zwiki.EditWikiPage=unset&field.dXNlcnNib2I_.permission.bugtracker.ManageBugTracker=unset&field.dXNlcnNib2I_.permission.zwiki.ReparentWikiPage=unset&field.dXNlcnNib2I_.permission.bugtracker.ViewBug=unset&field.dXNlcnNib2I_.permission.bugtracker.ViewBugTracker=unset&field.dXNlcnNib2I_.permission.zwiki.ViewWikiPage=unset&field.dXNlcnNib2I_.permission.zope.AddImages=unset&field.dXNlcnNib2I_.permission.zope.AddSQLScripts=unset&field.dXNlcnNib2I_.permission.zope.Security=unset&field.dXNlcnNib2I_.permission.zope.workflow.CreateProcessInstances=unset&field.dXNlcnNib2I_.permission.zope.ManageApplication=unset&field.dXNlcnNib2I_.permission.zope.ManageCode=unset&field.dXNlcnNib2I_.permission.zope.ManageContent=unset&field.dXNlcnNib2I_.permission.zope.ManagePrincipals=unset&field.dXNlcnNib2I_.permission.zope.ManageBindings=unset&field.dXNlcnNib2I_.permission.zope.ManageServices=unset&field.dXNlcnNib2I_.permission.zope.ManageSite=unset&field.dXNlcnNib2I_.permission.zope.workflow.ManageProcessDefinitions=unset&field.dXNlcnNib2I_.permission.zope.SendMail=unset&field.dXNlcnNib2I_.permission.zope.UndoAllTransactions=unset&field.dXNlcnNib2I_.permission.zope.UndoOwnTransactions=unset&field.dXNlcnNib2I_.permission.zope.workflow.UseProcessInstances=unset&field.dXNlcnNib2I_.permission.zope.View=unset&field.dXNlcnNib2I_.permission.zope.app.apidoc.UseAPIDoc=unset&field.dXNlcnNib2I_.permission.zope.app.dublincore.change=unset&field.dXNlcnNib2I_.permission.zope.app.dublincore.view=unset&field.dXNlcnNib2I_.permission.zope.app.introspector.Introspect=unset&field.dXNlcnNib2I_.permission.zope.app.rdb.Use=unset""") HTTP/1.1 200 OK ... At which point, Bob can access the management interface: >>> print http(r""" ... GET /@@contents.html HTTP/1.1 ... Authorization: Basic Ym9iOjEyMw== ... """) HTTP/1.1 200 OK ... zope.app.authentication-3.9/src/zope/app/authentication/browser/manage_roleform.pt0000644000175000017500000000512411457014222026511 0ustar jwjw

This page shows the permissions allowed and denied the role Great Master Guru (id: Zope.Some.Role). To change settings, simply select different permissions in the Allow or Deny lists. Make sure you don't select the same permission in both lists though.

Allow
zope.app.authentication-3.9/src/zope/app/authentication/browser/grant.zcml0000644000175000017500000000365311457014222025016 0ustar jwjw zope.app.authentication-3.9/src/zope/app/authentication/browser/register.py0000644000175000017500000000223411457014222025204 0ustar jwjw############################################################################## # # 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. # ############################################################################## """Improved registration UI for registering pluggable authentication utilities $Id: register.py 113109 2010-06-04 11:54:46Z janwijbrand $ """ from zope.app.authentication.i18n import ZopeMessageFactory as _ import zope.app.component.browser.registration import zope.authentication.interfaces class AddAuthenticationRegistration( zope.app.component.browser.registration.AddUtilityRegistration, ): label = _("Register a pluggable authentication utility") name = '' provided = zope.authentication.interfaces.IAuthentication zope.app.authentication-3.9/src/zope/app/authentication/browser/adding.py0000644000175000017500000000175011457014222024610 0ustar jwjw############################################################################## # # Copyright (c) 2005 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. # ############################################################################## """Adding that redirects to plugins.html. $Id: adding.py 113109 2010-06-04 11:54:46Z janwijbrand $ """ import zope.app.container.browser.adding from zope.traversing.browser.absoluteurl import absoluteURL class Adding(zope.app.container.browser.adding.Adding): def nextURL(self): return absoluteURL(self.context, self.request) + '/@@contents.html' zope.app.authentication-3.9/src/zope/app/authentication/browser/granting.py0000644000175000017500000002132311457014222025171 0ustar jwjw############################################################################## # # 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. # ############################################################################## """Granting Roles and Permissions to Principals $Id: granting.py 117475 2010-10-12 14:42:30Z janwijbrand $ """ __docformat__ = "reStructuredText" import zope.schema from zope.component import getUtilitiesFor from zope.schema.vocabulary import SimpleTerm from zope.i18nmessageid import ZopeMessageFactory as _ from zope.securitypolicy.interfaces import Allow, Unset, Deny from zope.securitypolicy.interfaces import IPrincipalPermissionManager from zope.securitypolicy.interfaces import IPrincipalRoleManager from zope.securitypolicy.interfaces import IRole from zope.securitypolicy.vocabulary import GrantVocabulary from zope.authentication.principal import PrincipalSource from zope.formlib.interfaces import IInputWidget from zope.formlib.interfaces import MissingInputError from zope.formlib.utility import setUpWidget from zope.formlib.widget import renderElement from zope.formlib.widgets import RadioWidget from zope.security.interfaces import IPermission settings_vocabulary = GrantVocabulary( [SimpleTerm(Allow, token="allow", title=_('Allow')), SimpleTerm(Unset, token="unset", title=_('Unset')), SimpleTerm(Deny, token='deny', title=_('Deny')), ]) class GrantWidget(RadioWidget): """Grant widget for building a colorized matrix. The matrix shows anytime the status if you edit the radio widgets. This special widget shows the radio input field without labels. The labels are added in the header of the table. The order of the radio input fields is 'Allowed', 'Unset', 'Deny'. """ orientation = "horizontal" _tdTemplate = ( u'\n\n
\n\n
\n\n' ) def __call__(self): """See IBrowserWidget.""" value = self._getFormValue() contents = [] have_results = False return self.renderValue(value) def renderItem(self, index, text, value, name, cssClass): """Render an item of the list. Revert the order of label and text. Added field id to the lable attribute. Added tabel td tags for fit in the matrix table. """ tdClass = '' id = '%s.%s' % (name, index) elem = renderElement(u'input', value=value, name=name, id=id, cssClass=cssClass, type='radio', extra = 'onclick="changeMatrix(this);"') return self._tdTemplate % (tdClass, id, text, elem) def renderSelectedItem(self, index, text, value, name, cssClass): """Render a selected item of the list. Revert the order of label and text. Added field id to the lable attribute. """ tdClass = 'default' id = '%s.%s' % (name, index) elem = renderElement(u'input', value=value, name=name, id=id, cssClass=cssClass, checked="checked", type='radio', extra = 'onclick="changeMatrix(this);"') return self._tdTemplate % (tdClass, id, text, elem) def renderItems(self, value): # check if we want to select first item, the previously selected item # or the "no value" item. no_value = None if (value == self.context.missing_value and getattr(self, 'firstItem', False) and len(self.vocabulary) > 0): if self.context.required: # Grab the first item from the iterator: values = [iter(self.vocabulary).next().value] else: # the "no value" option will be checked no_value = 'checked' elif value != self.context.missing_value: values = [value] else: values = [] items = self.renderItemsWithValues(values) return items def renderValue(self, value): rendered_items = self.renderItems(value) return " ".join(rendered_items) class Granting(object): principal = None principal_field = zope.schema.Choice( __name__ = 'principal', source=PrincipalSource(), required=True) def __init__(self, context, request): self.context = context self.request = request def status(self): setUpWidget(self, 'principal', self.principal_field, IInputWidget) if not self.principal_widget.hasInput(): return u'' try: principal = self.principal_widget.getInputValue() except MissingInputError: return u'' self.principal = principal # Make sure we can use the principal id in a form by base64ing it principal_token = unicode(principal).encode('base64').strip().replace( '=', '_') roles = [role for name, role in getUtilitiesFor(IRole)] roles.sort(lambda x, y: cmp(x.title, y.title)) principal_roles = IPrincipalRoleManager(self.context) self.roles = [] for role in roles: name = principal_token + '.role.'+role.id field = zope.schema.Choice(__name__= name, title=role.title, vocabulary=settings_vocabulary) setUpWidget(self, name, field, IInputWidget, principal_roles.getSetting(role.id, principal)) self.roles.append(getattr(self, name+'_widget')) perms = [perm for name, perm in getUtilitiesFor(IPermission)] perms.sort(lambda x, y: cmp(x.title, y.title)) principal_perms = IPrincipalPermissionManager(self.context) self.permissions = [] for perm in perms: if perm.id == 'zope.Public': continue name = principal_token + '.permission.'+perm.id field = zope.schema.Choice(__name__=name, title=perm.title, vocabulary=settings_vocabulary) setUpWidget(self, name, field, IInputWidget, principal_perms.getSetting(perm.id, principal)) self.permissions.append( getattr(self, name+'_widget')) if 'GRANT_SUBMIT' not in self.request: return u'' for role in roles: name = principal_token + '.role.'+role.id role_widget = getattr(self, name+'_widget') if role_widget.hasInput(): try: setting = role_widget.getInputValue() except MissingInputError: pass else: # Arrgh! if setting is Allow: principal_roles.assignRoleToPrincipal( role.id, principal) elif setting is Deny: principal_roles.removeRoleFromPrincipal( role.id, principal) else: principal_roles.unsetRoleForPrincipal( role.id, principal) for perm in perms: if perm.id == 'zope.Public': continue name = principal_token + '.permission.'+perm.id perm_widget = getattr(self, name+'_widget') if perm_widget.hasInput(): try: setting = perm_widget.getInputValue() except MissingInputError: pass else: # Arrgh! if setting is Allow: principal_perms.grantPermissionToPrincipal( perm.id, principal) elif setting is Deny: principal_perms.denyPermissionToPrincipal( perm.id, principal) else: principal_perms.unsetPermissionForPrincipal( perm.id, principal) return _('Grants updated.') zope.app.authentication-3.9/src/zope/app/authentication/browser/rolepermissionview.py0000644000175000017500000002146211457014222027331 0ustar jwjw############################################################################## # # Copyright (c) 2001, 2002 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. # ############################################################################## """Role Permission View Classes $Id: rolepermissionview.py 116801 2010-09-25 09:04:32Z icemac $ """ from datetime import datetime from zope.component import getUtilitiesFor, getUtility from zope.i18n import translate from zope.interface import implements from zope.exceptions.interfaces import UserError from zope.i18nmessageid import ZopeMessageFactory as _ from zope.security.interfaces import IPermission from zope.securitypolicy.interfaces import Unset, Allow, Deny from zope.securitypolicy.interfaces import IRole, IRolePermissionManager class RolePermissionView(object): _pagetip = _("""For each permission you want to grant (or deny) to a role, set the entry for that permission and role to a '+' (or '-'). Permissions are shown on the left side, going down. Roles are shown accross the top. """) def pagetip(self): return translate(self._pagetip, context=self.request) def roles(self): roles = getattr(self, '_roles', None) if roles is None: roles = [ (translate(role.title, context=self.request).strip(), role) for name, role in getUtilitiesFor(IRole)] roles.sort() roles = self._roles = [role for name, role in roles] return roles def permissions(self): permissions = getattr(self, '_permissions', None) if permissions is None: permissions = [ (translate(perm.title, context=self.request).strip(), perm) for name, perm in getUtilitiesFor(IPermission) if name != 'zope.Public'] permissions.sort() permissions = self._permissions = [perm for name, perm in permissions] return permissions def availableSettings(self, noacquire=False): aq = {'id': Unset.getName(), 'shorttitle': ' ', 'title': _('permission-acquire', 'Acquire')} rest = [{'id': Allow.getName(), 'shorttitle': '+', 'title': _('permission-allow', 'Allow')}, {'id': Deny.getName(), 'shorttitle': '-', 'title': _('permission-deny', 'Deny')}, ] if noacquire: return rest else: return [aq]+rest def permissionRoles(self): context = self.context.__parent__ roles = self.roles() return [PermissionRoles(permission, context, roles) for permission in self.permissions()] def permissionForID(self, pid): roles = self.roles() perm = getUtility(IPermission, pid) return PermissionRoles(perm, self.context.__parent__, roles) def roleForID(self, rid): permissions = self.permissions() role = getUtility(IRole, rid) return RolePermissions(role, self.context.__parent__, permissions) def update(self, testing=None): status = '' changed = False if 'SUBMIT' in self.request: roles = [r.id for r in self.roles()] permissions = [p.id for p in self.permissions()] prm = IRolePermissionManager(self.context.__parent__) for ip in range(len(permissions)): rperm = self.request.get("p%s" % ip) if rperm not in permissions: continue for ir in range(len(roles)): rrole = self.request.get("r%s" % ir) if rrole not in roles: continue setting = self.request.get("p%sr%s" % (ip, ir), None) if setting is not None: if setting == Unset.getName(): prm.unsetPermissionFromRole(rperm, rrole) elif setting == Allow.getName(): prm.grantPermissionToRole(rperm, rrole) elif setting == Deny.getName(): prm.denyPermissionToRole(rperm, rrole) else: raise ValueError("Incorrect setting: %s" % setting) changed = True if 'SUBMIT_PERMS' in self.request: prm = IRolePermissionManager(self.context.__parent__) roles = self.roles() rperm = self.request.get('permission_id') settings = self.request.get('settings', ()) for ir in range(len(roles)): rrole = roles[ir].id setting = settings[ir] if setting == Unset.getName(): prm.unsetPermissionFromRole(rperm, rrole) elif setting == Allow.getName(): prm.grantPermissionToRole(rperm, rrole) elif setting == Deny.getName(): prm.denyPermissionToRole(rperm, rrole) else: raise ValueError("Incorrect setting: %s" % setting) changed = True if 'SUBMIT_ROLE' in self.request: role_id = self.request.get('role_id') prm = IRolePermissionManager(self.context.__parent__) allowed = self.request.get(Allow.getName(), ()) denied = self.request.get(Deny.getName(), ()) for permission in self.permissions(): rperm = permission.id if rperm in allowed and rperm in denied: permission_translated = translate( permission.title, context=self.request) msg = _('You choose both allow and deny for permission' ' "${permission}". This is not allowed.', mapping = {'permission': permission_translated}) raise UserError(msg) if rperm in allowed: prm.grantPermissionToRole(rperm, role_id) elif rperm in denied: prm.denyPermissionToRole(rperm, role_id) else: prm.unsetPermissionFromRole(rperm, role_id) changed = True if changed: formatter = self.request.locale.dates.getFormatter( 'dateTime', 'medium') status = _("Settings changed at ${date_time}", mapping={'date_time': formatter.format(datetime.utcnow())}) return status class PermissionRoles(object): implements(IPermission) def __init__(self, permission, context, roles): self._permission = permission self._context = context self._roles = roles def _getId(self): return self._permission.id id = property(_getId) def _getTitle(self): return self._permission.title title = property(_getTitle) def _getDescription(self): return self._permission.description description = property(_getDescription) def roleSettings(self): """ Returns the list of setting names of each role for this permission. """ prm = IRolePermissionManager(self._context) proles = prm.getRolesForPermission(self._permission.id) settings = {} for role, setting in proles: settings[role] = setting.getName() nosetting = Unset.getName() return [settings.get(role.id, nosetting) for role in self._roles] class RolePermissions(object): implements(IRole) def __init__(self, role, context, permissions): self._role = role self._context = context self._permissions = permissions def _getId(self): return self._role.id id = property(_getId) def _getTitle(self): return self._role.title title = property(_getTitle) def _getDescription(self): return self._role.description description = property(_getDescription) def permissionsInfo(self): prm = IRolePermissionManager(self._context) rperms = prm.getPermissionsForRole(self._role.id) settings = {} for permission, setting in rperms: settings[permission] = setting.getName() nosetting = Unset.getName() return [{'id': permission.id, 'title': permission.title, 'setting': settings.get(permission.id, nosetting)} for permission in self._permissions] zope.app.authentication-3.9/src/zope/app/authentication/browser/schemasearch.py0000644000175000017500000001140611457014222026007 0ustar jwjw############################################################################## # # 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. # ############################################################################## """Search interface for queriables. $Id: schemasearch.py 117475 2010-10-12 14:42:30Z janwijbrand $ """ __docformat__ = "reStructuredText" from zope.app.authentication.i18n import ZopeMessageFactory as _ from zope.formlib.interfaces import IInputWidget, InputErrors from zope.formlib.interfaces import ISourceQueryView from zope.formlib.interfaces import WidgetsError, MissingInputError from zope.formlib.utility import setUpWidgets from zope.i18n import translate from zope.interface import implements from zope.schema import getFieldsInOrder from zope.traversing.api import getName, getPath search_label = _('search-button', 'Search') source_label = _(u"Source path") source_title = _(u"Path to the source utility") class QuerySchemaSearchView(object): implements(ISourceQueryView) def __init__(self, context, request): self.context = context self.request = request def render(self, name): schema = self.context.schema sourcename = getName(self.context) sourcepath = getPath(self.context) setUpWidgets(self, schema, IInputWidget, prefix=name+'.field') html = [] # add sub title for source search field html.append('

%s

' % sourcename) # start row for path display field html.append('
') # for each source add path of source html.append('
') label = translate(source_label, context=self.request) title = translate(source_title, context=self.request) html.append(' ') html.append('
') html.append('
') html.append(' %s' % sourcepath) html.append('
') html.append('
') # start row for search fields html.append('
') for field_name, field in getFieldsInOrder(schema): widget = getattr(self, field_name+'_widget') # for each field add label... html.append('
') html.append(' ') html.append('
') # ...and field widget html.append('
') html.append(' %s' % widget()) if widget.error(): html.append('
') html.append(' %s' % widget.error()) html.append('
') html.append('
') # end row html.append('
') # add search button for search fields html.append('
') html.append('
') html.append(' ' % (name+'.search', translate(search_label, context=self.request))) html.append('
') html.append('
') return '\n'.join(html) def results(self, name): if not (name+'.search' in self.request): return None schema = self.context.schema setUpWidgets(self, schema, IInputWidget, prefix=name+'.field') # XXX inline the original getWidgetsData call in # zope.app.form.utility to lift the dependency on zope.app.form. data = {} errors = [] for name, field in getFieldsInOrder(schema): widget = getattr(self, name + '_widget') if IInputWidget.providedBy(widget): if widget.hasInput(): try: data[name] = widget.getInputValue() except InputErrors, error: errors.append(error) elif field.required: errors.append(MissingInputError( name, widget.label, 'the field is required')) if errors: raise WidgetsError(errors, widgetsData=data) return self.context.search(data) zope.app.authentication-3.9/src/zope/app/authentication/browser/__init__.py0000644000175000017500000000133611457014222025121 0ustar jwjw############################################################################## # # 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 Views $Id: __init__.py 113109 2010-06-04 11:54:46Z janwijbrand $ """ zope.app.authentication-3.9/src/zope/app/authentication/browser/configure.zcml0000644000175000017500000000460411457014222025661 0ustar jwjw zope.app.authentication-3.9/src/zope/app/authentication/browser/issue663.txt0000644000175000017500000000352111457014222025136 0ustar jwjwhttp://www.zope.org/Collectors/Zope3-dev/663 ============================================ Two plugins(basic-auth and session credentials) link on PAU add menu are broken and can't add them. For IPluggableAuthentication, "plugins.html" is a correct view name but "contents.html" is used. because menu implementation supporsing that all view uses "zope.app.container.browser.contents.Contents" are named "contents.html". In Zope3.2, PluggableAuthentication inherits SiteManagementFolder that provides "contents.html" view. >>> from zope.testbrowser.testing import Browser >>> browser = Browser() Create a pau >>> browser.addHeader('Authorization', 'Basic mgr:mgrpw') >>> browser.open('http://localhost/@@contents.html') >>> browser.getLink('Pluggable Authentication Utility').click() >>> browser.getControl(name='add_input_name').value = 'auth' >>> browser.getControl('Add').click() >>> browser.getLink('auth').click() Go to the plugins view >>> browser.getLink('Plugins').click() Add aa basic auth plugin >>> browser.getLink('HTTP Basic-Auth Plugin').click() >>> browser.getControl(name='new_value').value = 'basic' >>> browser.getControl('Apply').click() Add a session-credential plugin >>> browser.getLink('Session Credentials Plugin').click() >>> browser.getControl(name='new_value').value = 'session' >>> browser.getControl('Apply').click() Make sure we can use them: >>> browser.getLink('Configure').click() >>> browser.getControl(name='field.credentialsPlugins.from').value = [ ... 'Wm9wZSBSZWFsbSBCYXNpYy1BdXRo'] >>> browser.getControl(name='field.credentialsPlugins.from').value = [ ... 'YmFzaWM='] >>> browser.getControl(name='field.credentialsPlugins.from').value = [ ... 'U2Vzc2lvbiBDcmVkZW50aWFscw=='] >>> browser.getControl('Change').click() zope.app.authentication-3.9/src/zope/app/authentication/browser/schemasearch.txt0000644000175000017500000000613711457014222026203 0ustar jwjwThe Query View for Schema Search Plugins ======================================== Placefull setup for making the search plugin IPhysicallyLocatable:: >>> from zope.component import provideAdapter >>> from zope.schema.interfaces import ITextLine >>> from zope.publisher.interfaces.browser import IDefaultBrowserLayer >>> from zope.formlib.interfaces import IInputWidget >>> from zope.formlib.widgets import TextWidget >>> from zope.app.testing.setup import placefulSetUp, placefulTearDown >>> site = placefulSetUp(True) >>> provideAdapter(TextWidget, (ITextLine, IDefaultBrowserLayer), ... IInputWidget) If a plugin supports `IQuerySchemaSearch`:: >>> from zope.interface import Interface >>> import zope.schema >>> class ISearchCriteria(Interface): ... search = zope.schema.TextLine(title=u"Search String") >>> from zope.interface import implements >>> class MySearchPlugin: ... __name__ = 'searchplugin' ... __parent__ = site ... schema = ISearchCriteria ... data = ['foo', 'bar', 'blah'] ... ... def get(self, id): ... if id in self.data: ... return {} ... ... def search(self, query, start=None, batch_size=None): ... search = query.get('search') ... if search is not None: ... i = 0 ... n = 0 ... for value in self.data: ... if search in value: ... if not ((start is not None and i < start) ... or ... (batch_size is not None and n > batch_size)): ... yield value then we can get a view:: >>> from zope.app.authentication.browser.schemasearch \ ... import QuerySchemaSearchView >>> from zope.publisher.browser import TestRequest >>> request = TestRequest() >>> view = QuerySchemaSearchView(MySearchPlugin(), request) This allows us to render a search form:: >>> print view.render('test') # doctest: +NORMALIZE_WHITESPACE

searchplugin

/searchplugin
If we ask for results:: >>> view.results('test') We don't get any, since we did not provide any. But if we give input:: >>> request.form['test.field.search'] = 'a' we still don't get any:: >>> view.results('test') because we did not press the button. So let's press the button:: >>> request.form['test.search'] = 'Search' so that we now get results (!):: >>> list(view.results('test')) ['bar', 'blah'] >>> placefulTearDown() zope.app.authentication-3.9/src/zope/app/authentication/browser/principalfolder.zcml0000644000175000017500000000361011457014222027051 0ustar jwjw zope.app.authentication-3.9/src/zope/app/authentication/browser/pau_prefix_and_searching.txt0000644000175000017500000002047311457014222030563 0ustar jwjw================================ Using a PAU Prefix and Searching ================================ This test confirms that both principals and groups can be searched for in PAUs that have prefixes. First we'll create a PAU with a prefix of `pau1_` and and register: >>> print http(r""" ... POST /++etc++site/default/+/AddPluggableAuthentication.html%3D HTTP/1.1 ... Authorization: Basic bWdyOm1ncnB3 ... Content-Length: 372 ... Content-Type: multipart/form-data; boundary=---------------------------318183180122653 ... ... -----------------------------318183180122653 ... Content-Disposition: form-data; name="field.prefix" ... ... pau1_ ... -----------------------------318183180122653 ... Content-Disposition: form-data; name="UPDATE_SUBMIT" ... ... Add ... -----------------------------318183180122653 ... Content-Disposition: form-data; name="add_input_name" ... ... PAU1 ... -----------------------------318183180122653-- ... """) HTTP/1.1 303 See Other ... >>> print http(r""" ... POST /++etc++site/default/PAU1/addRegistration.html HTTP/1.1 ... Authorization: Basic bWdyOm1ncnB3 ... Content-Length: 591 ... Content-Type: multipart/form-data; boundary=---------------------------516441125097 ... ... -----------------------------516441125097 ... Content-Disposition: form-data; name="field.comment" ... ... ... -----------------------------516441125097 ... Content-Disposition: form-data; name="field.actions.register" ... ... Register ... -----------------------------516441125097-- ... """) HTTP/1.1 303 See Other ... Next we'll create and register a principal folder: >>> print http(r""" ... POST /++etc++site/default/PAU1/+/AddPrincipalFolder.html%3D HTTP/1.1 ... Authorization: Basic bWdyOm1ncnB3 ... Content-Length: 374 ... Content-Type: multipart/form-data; boundary=---------------------------266241536215161 ... ... -----------------------------266241536215161 ... Content-Disposition: form-data; name="field.prefix" ... ... users_ ... -----------------------------266241536215161 ... Content-Disposition: form-data; name="UPDATE_SUBMIT" ... ... Add ... -----------------------------266241536215161 ... Content-Disposition: form-data; name="add_input_name" ... ... Users ... -----------------------------266241536215161-- ... """) HTTP/1.1 303 See Other ... and add a principal that we'll later search for: >>> print http(r""" ... POST /++etc++site/default/PAU1/Users/+/AddPrincipalInformation.html%3D HTTP/1.1 ... Authorization: Basic bWdyOm1ncnB3 ... Content-Length: 686 ... Content-Type: multipart/form-data; boundary=---------------------------300171485226567 ... ... -----------------------------300171485226567 ... Content-Disposition: form-data; name="field.login" ... ... bob ... -----------------------------300171485226567 ... Content-Disposition: form-data; name="field.passwordManagerName" ... ... Plain Text ... -----------------------------300171485226567 ... Content-Disposition: form-data; name="field.password" ... ... bob ... -----------------------------300171485226567 ... Content-Disposition: form-data; name="field.title" ... ... Bob ... -----------------------------300171485226567 ... Content-Disposition: form-data; name="field.description" ... ... ... -----------------------------300171485226567 ... Content-Disposition: form-data; name="UPDATE_SUBMIT" ... ... Add ... -----------------------------300171485226567 ... Content-Disposition: form-data; name="add_input_name" ... ... ... -----------------------------300171485226567-- ... """) HTTP/1.1 303 See Other ... Next, we'll add and register a group folder: >>> print http(r""" ... POST /++etc++site/default/PAU1/+/AddGroupFolder.html%3D HTTP/1.1 ... Authorization: Basic bWdyOm1ncnB3 ... Content-Length: 372 ... Content-Type: multipart/form-data; boundary=---------------------------17420126702455 ... ... -----------------------------17420126702455 ... Content-Disposition: form-data; name="field.prefix" ... ... groups_ ... -----------------------------17420126702455 ... Content-Disposition: form-data; name="UPDATE_SUBMIT" ... ... Add ... -----------------------------17420126702455 ... Content-Disposition: form-data; name="add_input_name" ... ... Groups ... -----------------------------17420126702455-- ... """) HTTP/1.1 303 See Other ... and add a group to search for: >>> print http(r""" ... POST /++etc++site/default/PAU1/Groups/+/AddGroupInformation.html%3D HTTP/1.1 ... Authorization: Basic bWdyOm1ncnB3 ... Content-Length: 485 ... Content-Type: multipart/form-data; boundary=---------------------------323081358415654 ... ... -----------------------------323081358415654 ... Content-Disposition: form-data; name="field.title" ... ... Nice People ... -----------------------------323081358415654 ... Content-Disposition: form-data; name="field.description" ... ... ... -----------------------------323081358415654 ... Content-Disposition: form-data; name="UPDATE_SUBMIT" ... ... Add ... -----------------------------323081358415654 ... Content-Disposition: form-data; name="add_input_name" ... ... nice ... -----------------------------323081358415654-- ... """) HTTP/1.1 303 See Other ... Since we're only searching in this test, we won't bother to add anyone to the group. Before we search, we need to register the two authenticator plugins with the PAU: >>> print http(r""" ... POST /++etc++site/default/PAU1/@@configure.html HTTP/1.1 ... Authorization: Basic bWdyOm1ncnB3 ... Content-Length: 888 ... Content-Type: multipart/form-data; boundary=---------------------------610310492754 ... ... -----------------------------610310492754 ... Content-Disposition: form-data; name="field.credentialsPlugins-empty-marker" ... ... ... -----------------------------610310492754 ... Content-Disposition: form-data; name="field.authenticatorPlugins.to" ... ... R3JvdXBz ... -----------------------------610310492754 ... Content-Disposition: form-data; name="field.authenticatorPlugins.to" ... ... VXNlcnM= ... -----------------------------610310492754 ... Content-Disposition: form-data; name="field.authenticatorPlugins-empty-marker" ... ... ... -----------------------------610310492754 ... Content-Disposition: form-data; name="UPDATE_SUBMIT" ... ... Change ... -----------------------------610310492754 ... Content-Disposition: form-data; name="field.authenticatorPlugins" ... ... R3JvdXBz ... -----------------------------610310492754 ... Content-Disposition: form-data; name="field.authenticatorPlugins" ... ... VXNlcnM= ... -----------------------------610310492754-- ... """) HTTP/1.1 200 OK ... Now we'll use the 'grant' interface of the root folder to search for all of the available groups: >>> print http(r""" ... POST /@@grant.html HTTP/1.1 ... Authorization: Basic bWdyOm1ncnB3 ... Content-Length: 191 ... Content-Type: application/x-www-form-urlencoded ... ... field.principal.displayed=y&""" ... "field.principal.MC5Hcm91cHM_.field.search=&" ... "field.principal.MC5Hcm91cHM_.search=Search&" ... "field.principal.MC5Vc2Vycw__.field.search=&" ... "field.principal.MQ__.searchstring=") HTTP/1.1 200 OK ... ... Note in the results that the dropdown box (i.e. the select element) has the single group 'Nice People' that we added earlier. Next, we'll use the same 'grant' interface to search for all of the available principals: >>> print http(r""" ... POST /@@grant.html HTTP/1.1 ... Authorization: Basic bWdyOm1ncnB3 ... Content-Length: 255 ... Content-Type: application/x-www-form-urlencoded ... ... field.principal.displayed=y&""" ... "field.principal.MC5Hcm91cHM_.field.search=&" ... "field.principal.MC5Hcm91cHM_.selection=cGF1MV9ncm91cHNfbmljZQ__&" ... "field.principal.MC5Vc2Vycw__.field.search=&" ... "field.principal.MC5Vc2Vycw__.search=Search&" ... "field.principal.MQ__.searchstring=") HTTP/1.1 200 OK ... ... Note here the dropdown contains Bob, the principal we added earlier. zope.app.authentication-3.9/src/zope/app/authentication/__init__.py0000644000175000017500000000147111457014222023436 0ustar jwjw############################################################################## # # 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 $Id: __init__.py 113109 2010-06-04 11:54:46Z janwijbrand $ """ import interfaces from zope.pluggableauth.authentication import PluggableAuthentication zope.app.authentication-3.9/src/zope/app/authentication/configure.zcml0000644000175000017500000000351511457014222024176 0ustar jwjw zope.app.authentication-3.9/src/zope/app/authentication/i18n.py0000644000175000017500000000166211457014222022460 0ustar jwjw############################################################################## # # Copyright (c) 2003 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. # ############################################################################## """Customization of zope.i18n for the Zope application server $Id: i18n.py 113109 2010-06-04 11:54:46Z janwijbrand $ """ __docformat__ = 'restructuredtext' # import this as _ to create i18n messages in the zope domain from zope.i18nmessageid import MessageFactory ZopeMessageFactory = MessageFactory('zope') zope.app.authentication-3.9/src/zope/app/authentication/principalfolder.zcml0000644000175000017500000000227411457014222025373 0ustar jwjw zope.app.authentication-3.9/src/zope/app/authentication/authentication.py0000644000175000017500000000450611457014222024720 0ustar jwjw############################################################################## # # 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 $Id: authentication.py 113109 2010-06-04 11:54:46Z janwijbrand $ """ import zope.interface from zope import component from zope.location.interfaces import ILocation from zope.app.authentication import interfaces ### BBB using zope.pluggableauth from zope.pluggableauth import PluggableAuthentication from zope.pluggableauth.interfaces import ( IQueriableAuthenticator, IPluggableAuthentication) class QuerySchemaSearchAdapter(object): """Performs schema-based principal searches on behalf of a PAU. Delegates the search to the adapted authenticator (which also provides IQuerySchemaSearch) and prepends the PAU prefix to the resulting principal IDs. """ component.adapts( interfaces.IQuerySchemaSearch, IPluggableAuthentication) zope.interface.implements( ILocation, IQueriableAuthenticator, interfaces.IQuerySchemaSearch) def __init__(self, authplugin, pau): if (ILocation.providedBy(authplugin) and authplugin.__parent__ is not None): # Checking explicitly for the parent, because providing ILocation # basically means that the object *could* be located. It doesn't # say the object must be located. self.__parent__ = authplugin.__parent__ self.__name__ = authplugin.__name__ else: self.__parent__ = pau self.__name__ = "" self.authplugin = authplugin self.pau = pau self.schema = authplugin.schema def search(self, query, start=None, batch_size=None): for id in self.authplugin.search(query, start, batch_size): yield self.pau.prefix + id zope.app.authentication-3.9/src/zope/app/authentication/session.py0000644000175000017500000000174011457014222023361 0ustar jwjw############################################################################## # # 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. # ############################################################################## """ Implementations of the session-based and cookie-based extractor and challenge plugins. $Id: session.py 113109 2010-06-04 11:54:46Z janwijbrand $ """ __docformat__ = 'restructuredtext' ### BBB from zope.pluggableauth.plugins.session import ( ISessionCredentials, SessionCredentials, IBrowserFormChallenger, SessionCredentialsPlugin, ) zope.app.authentication-3.9/src/zope/app/authentication/ftpplugins.py0000644000175000017500000000142111457014222024065 0ustar jwjw############################################################################## # # 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' ### BBB from zope.pluggableauth.plugins.ftpplugins import FTPCredentialsPlugin zope.app.authentication-3.9/src/zope/app/authentication/interfaces.py0000644000175000017500000000330611457014222024021 0ustar jwjw############################################################################## # # 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 $Id: interfaces.py 117633 2010-10-18 09:40:31Z janwijbrand $ """ __docformat__ = "reStructuredText" import zope.interface import zope.schema import zope.security.interfaces from zope.app.authentication.i18n import ZopeMessageFactory as _ # BBB: the password managers were moved into zope.password package. from zope.password.interfaces import IPasswordManager # BBB: using zope.pluggableauth from zope.pluggableauth.interfaces import ( AuthenticatedPrincipalCreated, FoundPrincipalCreated, IAuthenticatedPrincipalCreated, IAuthenticatedPrincipalFactory, IAuthenticatorPlugin, ICredentialsPlugin, IFoundPrincipalCreated, IFoundPrincipalFactory, IGroupAdded, IPluggableAuthentication, IPlugin, IPrincipal, IPrincipalCreated, IPrincipalFactory, IPrincipalInfo, IPrincipalsAddedToGroup, IPrincipalsRemovedFromGroup, IQueriableAuthenticator, IQuerySchemaSearch, ) # BBB: using zope.pluggableauth from zope.pluggableauth.plugins.groupfolder import ( GroupAdded, ) zope.app.authentication-3.9/src/zope/app/authentication/groupfolder.py0000644000175000017500000000221311457014222024222 0ustar jwjw############################################################################## # # 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 117633 2010-10-18 09:40:31Z janwijbrand $ """ # BBB using zope.pluggableauth.plugin.groupfolder from zope.pluggableauth.plugins.groupfolder import ( GroupCycle, GroupFolder, GroupInfo, GroupInformation, IGroupContained, IGroupFolder, IGroupInformation, IGroupPrincipalInfo, IGroupSearchCriteria, InvalidGroupId, InvalidPrincipalIds, nocycles, setGroupsForPrincipal, setMemberSubscriber, specialGroups, ) zope.app.authentication-3.9/src/zope/app/authentication/tests.py0000644000175000017500000001231211457014222023035 0ustar jwjw############################################################################## # # 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 $Id: tests.py 115827 2010-08-20 10:36:51Z icemac $ """ __docformat__ = "reStructuredText" import unittest import doctest from zope.interface import implements from zope.component import provideUtility, provideAdapter, provideHandler from zope.component.eventtesting import getEvents, clearEvents from zope.publisher.interfaces import IRequest from zope.app.testing import placelesssetup from zope.app.testing.setup import placefulSetUp, placefulTearDown from zope.session.interfaces import \ IClientId, IClientIdManager, ISession, ISessionDataContainer from zope.session.session import \ ClientId, Session, PersistentSessionDataContainer from zope.session.http import CookieClientIdManager from zope.publisher import base from zope.pluggableauth.plugins.session import SessionCredentialsPlugin class TestClientId(object): implements(IClientId) def __new__(cls, request): return 'dummyclientidfortesting' def siteSetUp(test): placefulSetUp(site=True) def siteTearDown(test): placefulTearDown() def sessionSetUp(session_data_container_class=PersistentSessionDataContainer): placelesssetup.setUp() provideAdapter(TestClientId, [IRequest], IClientId) provideAdapter(Session, [IRequest], ISession) provideUtility(CookieClientIdManager(), IClientIdManager) sdc = session_data_container_class() provideUtility(sdc, ISessionDataContainer, '') def nonHTTPSessionTestCaseSetUp(sdc_class=PersistentSessionDataContainer): # I am getting an error with ClientId and not TestClientId placelesssetup.setUp() provideAdapter(ClientId, [IRequest], IClientId) provideAdapter(Session, [IRequest], ISession) provideUtility(CookieClientIdManager(), IClientIdManager) sdc = sdc_class() provideUtility(sdc, 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): placefulTearDown() 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 test_suite(): return unittest.TestSuite(( doctest.DocTestSuite('zope.app.authentication.interfaces'), doctest.DocTestSuite('zope.app.authentication.password'), doctest.DocTestSuite('zope.app.authentication.generic'), doctest.DocTestSuite('zope.app.authentication.httpplugins'), doctest.DocTestSuite('zope.app.authentication.ftpplugins'), doctest.DocTestSuite('zope.app.authentication.groupfolder'), doctest.DocFileSuite('principalfolder.txt', setUp=placelesssetup.setUp, tearDown=placelesssetup.tearDown), doctest.DocTestSuite('zope.app.authentication.principalfolder', setUp=placelesssetup.setUp, tearDown=placelesssetup.tearDown), doctest.DocTestSuite('zope.app.authentication.idpicker'), doctest.DocTestSuite('zope.app.authentication.session', setUp=siteSetUp, tearDown=siteTearDown), doctest.DocFileSuite('README.txt', setUp=siteSetUp, tearDown=siteTearDown, globs={'provideUtility': provideUtility, 'provideAdapter': provideAdapter, 'provideHandler': provideHandler, 'getEvents': getEvents, 'clearEvents': clearEvents, }), doctest.DocFileSuite('groupfolder.txt', setUp=placelesssetup.setUp, tearDown=placelesssetup.tearDown, ), doctest.DocFileSuite('vocabulary.txt', setUp=placelesssetup.setUp, tearDown=placelesssetup.tearDown, ), unittest.makeSuite(NonHTTPSessionTestCase), )) if __name__ == '__main__': unittest.main(defaultTest='test_suite') zope.app.authentication-3.9/src/zope/app/authentication/idpicker.py0000644000175000017500000000162111457014222023466 0ustar jwjw# -*- 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 117633 2010-10-18 09:40:31Z janwijbrand $ """ __docformat__ = 'restructuredtext' # BBB using zope.pluggableauth.plugins.idpicker from zope.pluggableauth.plugins.idpicker import IdPicker zope.app.authentication-3.9/PKG-INFO0000644000175000017500000015423511457014227015073 0ustar jwjwMetadata-Version: 1.0 Name: zope.app.authentication Version: 3.9 Summary: Principals and groups management for the pluggable authentication utility Home-page: http://pypi.python.org/pypi/zope.app.authentication Author: Zope Corporation and Contributors Author-email: zope-dev@zope.org License: ZPL 2.1 Description: This package provides a flexible and pluggable authentication utility for Zope 3, using `zope.pluggableauth`. Several common plugins are provided. .. contents:: ================================ 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.app.authentication import interfaces >>> class MyCredentialsPlugin(object): ... ... interface.implements(interfaces.ICredentialsPlugin) ... ... def extractCredentials(self, request): ... return request.get('credentials') ... ... def challenge(self, request): ... pass # challenge is a no-op for this plugin ... ... def logout(self, request): ... pass # logout is a no-op for this plugin 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') Principal Factories ~~~~~~~~~~~~~~~~~~~ While authenticator plugins provide principal info, they are not responsible for creating principals. This function is performed by factory adapters. For these tests we'll borrow some factories from the principal folder:: >>> from zope.app.authentication import principalfolder >>> provideAdapter(principalfolder.AuthenticatedPrincipalFactory) >>> provideAdapter(principalfolder.FoundPrincipalFactory) For more information on these factories, see their docstrings. Configuring a PAU ~~~~~~~~~~~~~~~~~ Finally, we'll create the PAU itself:: >>> from zope.app import authentication >>> pau = authentication.PluggableAuthentication('xyz_') and configure it with the two plugins:: >>> pau.credentialsPlugins = ('My Credentials Plugin', ) >>> pau.authenticatorPlugins = ('My Authenticator Plugin', ) Using the PAU to Authenticate ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ We can now use the PAU to authenticate a sample request:: >>> from zope.publisher.browser import TestRequest >>> print pau.authenticate(TestRequest()) None 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. Authenticated Principal Creates Events ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ We can verify that the appropriate event was published:: >>> [event] = getEvents(interfaces.IAuthenticatedPrincipalCreated) >>> event.principal is principal True >>> event.info PrincipalInfo('bob') >>> event.request is request True The info object has the id, title, and description of the principal. The info object is also generated by the authenticator plugin, so the plugin may itself have provided additional information on the info object:: >>> event.info.title 'Bob' >>> event.info.id # does not include pau prefix 'bob' >>> event.info.description '' It is also decorated with two other attributes, credentialsPlugin and authenticatorPlugin: these are the plugins used to extract credentials for and authenticate this principal. These attributes can be useful for subscribers that want to react to the plugins used. For instance, subscribers can determine that a given credential plugin does or does not support logout, and provide information usable to show or hide logout user interface:: >>> event.info.credentialsPlugin is myCredentialsPlugin True >>> event.info.authenticatorPlugin is myAuthenticatorPlugin True Normally, we provide subscribers to these events that add additional information to the principal. For example, we'll add one that sets the title:: >>> def add_info(event): ... event.principal.title = event.info.title >>> provideHandler(add_info, [interfaces.IAuthenticatedPrincipalCreated]) Now, if we authenticate a principal, its title is set:: >>> principal = pau.authenticate(request) >>> principal.title 'Bob' 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!) Principal Searching ------------------- As a component that provides IAuthentication, a PAU lets you lookup a principal with a principal ID. The PAU looks up a principal by delegating to its authenticators. In our example, none of the authenticators implement this search capability, so when we look for a principal:: >>> print pau.getPrincipal('xyz_bob') Traceback (most recent call last): PrincipalLookupError: bob >>> print pau.getPrincipal('white') Traceback (most recent call last): PrincipalLookupError: white >>> print pau.getPrincipal('black') Traceback (most recent call last): PrincipalLookupError: black For a PAU to support search, it needs to be configured with one or more authenticator plugins that support search. To illustrate, we'll create a new authenticator:: >>> class SearchableAuthenticatorPlugin: ... ... interface.implements(interfaces.IAuthenticatorPlugin) ... ... def __init__(self): ... self.infos = {} ... self.ids = {} ... ... def principalInfo(self, id): ... return self.infos.get(id) ... ... def authenticateCredentials(self, credentials): ... id = self.ids.get(credentials) ... if id is not None: ... return self.infos[id] ... ... def add(self, id, title, description, credentials): ... self.infos[id] = PrincipalInfo(id, title, description) ... self.ids[credentials] = id This class is typical of an authenticator plugin. It can both authenticate principals and find principals given a ID. While there are cases where an authenticator may opt to not perform one of these two functions, they are less typical. As with any plugin, we need to register it as a utility:: >>> searchable = SearchableAuthenticatorPlugin() >>> provideUtility(searchable, name='Searchable Authentication Plugin') We'll now configure the PAU to use only the searchable authenticator:: >>> pau.authenticatorPlugins = ('Searchable Authentication Plugin',) and add some principals to the authenticator:: >>> searchable.add('bob', 'Bob', 'A nice guy', 'b0b') >>> searchable.add('white', 'White Spy', 'Sneaky', 'deathtoblack') Now when we ask the PAU to find a principal:: >>> pau.getPrincipal('xyz_bob') Principal('xyz_bob') but only those it knows about:: >>> print pau.getPrincipal('black') Traceback (most recent call last): PrincipalLookupError: black Found Principal Creates Events ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ As evident in the authenticator's 'createFoundPrincipal' method (see above), a FoundPrincipalCreatedEvent is published when the authenticator finds a principal on behalf of PAU's 'getPrincipal':: >>> clearEvents() >>> principal = pau.getPrincipal('xyz_white') >>> principal Principal('xyz_white') >>> [event] = getEvents(interfaces.IFoundPrincipalCreated) >>> event.principal is principal True >>> event.info PrincipalInfo('white') The info has an authenticatorPlugin, but no credentialsPlugin, since none was used:: >>> event.info.credentialsPlugin is None True >>> event.info.authenticatorPlugin is searchable True As we have seen with authenticated principals, it is common to subscribe to principal created events to add information to the newly created principal. In this case, we need to subscribe to IFoundPrincipalCreated events:: >>> provideHandler(add_info, [interfaces.IFoundPrincipalCreated]) Now when a principal is created as a result of a search, it's title and description will be set (by the add_info handler function). 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. To illustrate, we'll create and register a second searchable authenticator:: >>> searchable2 = SearchableAuthenticatorPlugin() >>> provideUtility(searchable2, name='Searchable Authentication Plugin 2') and add a principal to it:: >>> searchable.add('black', 'Black Spy', 'Also sneaky', 'deathtowhite') When we configure the PAU to use both searchable authenticators (note the order):: >>> pau.authenticatorPlugins = ( ... 'Searchable Authentication Plugin 2', ... 'Searchable Authentication Plugin') 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:: >>> searchable2.add('white', 'White Rider', '', 'r1der') >>> pau.getPrincipal('xyz_white').title 'White Rider' If we change the order of the plugins:: >>> pau.authenticatorPlugins = ( ... 'Searchable Authentication Plugin', ... 'Searchable Authentication Plugin 2') 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}") Searching --------- PAU implements ISourceQueriables:: >>> from zope.schema.interfaces import ISourceQueriables >>> ISourceQueriables.providedBy(pau) True This means a PAU can be used in a principal source vocabulary (Zope provides a sophisticated searching UI for principal sources). As we've seen, a PAU uses each of its authenticator plugins to locate a principal with a given ID. However, plugins may also provide the interface IQuerySchemaSearch to indicate they can be used in the PAU's principal search scheme. Currently, our list of authenticators:: >>> pau.authenticatorPlugins ('My Authenticator Plugin',) does not include a queriable authenticator. PAU cannot therefore provide any queriables:: >>> list(pau.getQueriables()) [] Before we illustrate how an authenticator is used by the PAU to search for principals, we need to setup an adapter used by PAU:: >>> provideAdapter( ... authentication.authentication.QuerySchemaSearchAdapter, ... provides=interfaces.IQueriableAuthenticator) This adapter delegates search responsibility to an authenticator, but prepends the PAU prefix to any principal IDs returned in a search. Next, we'll create a plugin that provides a search interface:: >>> class QueriableAuthenticatorPlugin(MyAuthenticatorPlugin): ... ... interface.implements(interfaces.IQuerySchemaSearch) ... ... schema = None ... ... def search(self, query, start=None, batch_size=None): ... yield 'foo' ... and install it as a plugin:: >>> plugin = QueriableAuthenticatorPlugin() >>> provideUtility(plugin, ... provides=interfaces.IAuthenticatorPlugin, ... name='Queriable') >>> pau.authenticatorPlugins += ('Queriable',) Now, the PAU provides a single queriable:: >>> list(pau.getQueriables()) # doctest: +ELLIPSIS [('Queriable', ...QuerySchemaSearchAdapter object...)] We can use this queriable to search for our principal:: >>> queriable = list(pau.getQueriables())[0][1] >>> list(queriable.search('not-used')) ['mypau_foo'] Note that the resulting principal ID includes the PAU prefix. Were we to search the plugin directly:: >>> list(plugin.search('not-used')) ['foo'] The result does not include the PAU prefix. The prepending of the prefix is handled by the PluggableAuthenticationQueriable. Queryiable plugins can provide the ILocation interface. In this case the QuerySchemaSearchAdapter's __parent__ is the same as the __parent__ of the plugin:: >>> import zope.location.interfaces >>> class LocatedQueriableAuthenticatorPlugin(QueriableAuthenticatorPlugin): ... ... interface.implements(zope.location.interfaces.ILocation) ... ... __parent__ = __name__ = None ... >>> import zope.site.hooks >>> site = zope.site.hooks.getSite() >>> plugin = LocatedQueriableAuthenticatorPlugin() >>> plugin.__parent__ = site >>> plugin.__name__ = 'localname' >>> provideUtility(plugin, ... provides=interfaces.IAuthenticatorPlugin, ... name='location-queriable') >>> pau.authenticatorPlugins = ('location-queriable',) We have one queriable again:: >>> queriables = list(pau.getQueriables()) >>> queriables # doctest: +ELLIPSIS [('location-queriable', ...QuerySchemaSearchAdapter object...)] The queriable's __parent__ is the site as set above:: >>> queriable = queriables[0][1] >>> queriable.__parent__ is site True If the queriable provides ILocation but is not actually locatable (i.e. the parent is None) the pau itself becomes the parent:: >>> plugin = LocatedQueriableAuthenticatorPlugin() >>> provideUtility(plugin, ... provides=interfaces.IAuthenticatorPlugin, ... name='location-queriable-wo-parent') >>> pau.authenticatorPlugins = ('location-queriable-wo-parent',) We have one queriable again:: >>> queriables = list(pau.getQueriables()) >>> queriables # doctest: +ELLIPSIS [('location-queriable-wo-parent', ...QuerySchemaSearchAdapter object...)] And the parent is the pau:: >>> queriable = queriables[0][1] >>> queriable.__parent__ # doctest: +ELLIPSIS >>> queriable.__parent__ is pau True ================ Principal Folder ================ Principal folders contain principal-information objects that contain principal information. We create an internal principal using the `InternalPrincipal` class: >>> from zope.app.authentication.principalfolder import InternalPrincipal >>> p1 = InternalPrincipal('login1', '123', "Principal 1", ... passwordManagerName="SHA1") >>> p2 = InternalPrincipal('login2', '456', "The Other One") and add them to a principal folder: >>> from zope.app.authentication.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'}) ============ Vocabularies ============ The vocabulary module provides vocabularies for the authenticator plugins and the credentials plugins. The options should include the unique names of all of the plugins that provide the appropriate interface (interfaces.ICredentialsPlugin or interfaces.IAuthentiatorPlugin, respectively) for the current context-- which is expected to be a pluggable authentication utility, hereafter referred to as a PAU. These names may be for objects contained within the PAU ("contained plugins"), or may be utilities registered for the specified interface, found in the context of the PAU ("utility plugins"). Contained plugins mask utility plugins of the same name. They also may be names currently selected in the PAU that do not actually have a corresponding plugin at this time. Here is a short example of how the vocabulary should work. Let's say we're working with authentication plugins. We'll create some faux authentication plugins, and register some of them as utilities and put others in a faux PAU. >>> from zope.app.authentication import interfaces >>> from zope import interface, component >>> class DemoPlugin(object): ... interface.implements(interfaces.IAuthenticatorPlugin) ... def __init__(self, name): ... self.name = name ... >>> utility_plugins = dict( ... (i, DemoPlugin(u'Plugin %d' % i)) for i in range(4)) >>> contained_plugins = dict( ... (i, DemoPlugin(u'Plugin %d' % i)) for i in range(1, 5)) >>> sorted(utility_plugins.keys()) [0, 1, 2, 3] >>> for p in utility_plugins.values(): ... component.provideUtility(p, name=p.name) ... >>> sorted(contained_plugins.keys()) # 1 will mask utility plugin 1 [1, 2, 3, 4] >>> class DemoAuth(dict): ... interface.implements(interfaces.IPluggableAuthentication) ... def __init__(self, *args, **kwargs): ... super(DemoAuth, self).__init__(*args, **kwargs) ... self.authenticatorPlugins = (u'Plugin 3', u'Plugin X') ... self.credentialsPlugins = (u'Plugin 4', u'Plugin X') ... >>> auth = DemoAuth((p.name, p) for p in contained_plugins.values()) >>> @component.adapter(interface.Interface) ... @interface.implementer(component.IComponentLookup) ... def getSiteManager(context): ... return component.getGlobalSiteManager() ... >>> component.provideAdapter(getSiteManager) We are now ready to create a vocabulary that we can use. The context is our faux authentication utility, `auth`. >>> from zope.app.authentication import vocabulary >>> vocab = vocabulary.authenticatorPlugins(auth) Iterating over the vocabulary results in all of the terms, in a relatively arbitrary order of their names. (This vocabulary should typically use a widget that sorts values on the basis of localized collation order of the term titles.) >>> [term.value for term in vocab] # doctest: +NORMALIZE_WHITESPACE [u'Plugin 0', u'Plugin 1', u'Plugin 2', u'Plugin 3', u'Plugin 4', u'Plugin X'] Similarly, we can use `in` to test for the presence of values in the vocabulary. >>> ['Plugin %s' % i in vocab for i in range(-1, 6)] [False, True, True, True, True, True, False] >>> 'Plugin X' in vocab True The length reports the expected value. >>> len(vocab) 6 One can get a term for a given value using `getTerm()`; its token, in turn, should also return the same effective term from `getTermByToken`. >>> values = ['Plugin 0', 'Plugin 1', 'Plugin 2', 'Plugin 3', 'Plugin 4', ... 'Plugin X'] >>> for val in values: ... term = vocab.getTerm(val) ... assert term.value == val ... term2 = vocab.getTermByToken(term.token) ... assert term2.token == term.token ... assert term2.value == val ... The terms have titles, which are message ids that show the plugin title or id and whether the plugin is a utility or just contained in the auth utility. We'll give one of the plugins a dublin core title just to show the functionality. >>> import zope.dublincore.interfaces >>> class ISpecial(interface.Interface): ... pass ... >>> interface.directlyProvides(contained_plugins[1], ISpecial) >>> class DemoDCAdapter(object): ... interface.implements( ... zope.dublincore.interfaces.IDCDescriptiveProperties) ... component.adapts(ISpecial) ... def __init__(self, context): ... pass ... title = u'Special Title' ... >>> component.provideAdapter(DemoDCAdapter) We need to regenerate the vocabulary, since it calculates all of its data at once. >>> vocab = vocabulary.authenticatorPlugins(auth) Now we'll check the titles. We'll have to translate them to see what we expect. >>> from zope import i18n >>> import pprint >>> pprint.pprint([i18n.translate(term.title) for term in vocab]) [u'Plugin 0 (a utility)', u'Special Title (in contents)', u'Plugin 2 (in contents)', u'Plugin 3 (in contents)', u'Plugin 4 (in contents)', u'Plugin X (not found; deselecting will remove)'] credentialsPlugins ------------------ For completeness, we'll do the same review of the credentialsPlugins. >>> class DemoPlugin(object): ... interface.implements(interfaces.ICredentialsPlugin) ... def __init__(self, name): ... self.name = name ... >>> utility_plugins = dict( ... (i, DemoPlugin(u'Plugin %d' % i)) for i in range(4)) >>> contained_plugins = dict( ... (i, DemoPlugin(u'Plugin %d' % i)) for i in range(1, 5)) >>> for p in utility_plugins.values(): ... component.provideUtility(p, name=p.name) ... >>> auth = DemoAuth((p.name, p) for p in contained_plugins.values()) >>> vocab = vocabulary.credentialsPlugins(auth) Iterating over the vocabulary results in all of the terms, in a relatively arbitrary order of their names. (This vocabulary should typically use a widget that sorts values on the basis of localized collation order of the term titles.) Similarly, we can use `in` to test for the presence of values in the vocabulary. The length reports the expected value. >>> [term.value for term in vocab] # doctest: +NORMALIZE_WHITESPACE [u'Plugin 0', u'Plugin 1', u'Plugin 2', u'Plugin 3', u'Plugin 4', u'Plugin X'] >>> ['Plugin %s' % i in vocab for i in range(-1, 6)] [False, True, True, True, True, True, False] >>> 'Plugin X' in vocab True >>> len(vocab) 6 One can get a term for a given value using `getTerm()`; its token, in turn, should also return the same effective term from `getTermByToken`. >>> values = ['Plugin 0', 'Plugin 1', 'Plugin 2', 'Plugin 3', 'Plugin 4', ... 'Plugin X'] >>> for val in values: ... term = vocab.getTerm(val) ... assert term.value == val ... term2 = vocab.getTermByToken(term.token) ... assert term2.token == term.token ... assert term2.value == val ... The terms have titles, which are message ids that show the plugin title or id and whether the plugin is a utility or just contained in the auth utility. We'll give one of the plugins a dublin core title just to show the functionality. We need to regenerate the vocabulary, since it calculates all of its data at once. Then we'll check the titles. We'll have to translate them to see what we expect. >>> interface.directlyProvides(contained_plugins[1], ISpecial) >>> vocab = vocabulary.credentialsPlugins(auth) >>> pprint.pprint([i18n.translate(term.title) for term in vocab]) [u'Plugin 0 (a utility)', u'Special Title (in contents)', u'Plugin 2 (in contents)', u'Plugin 3 (in contents)', u'Plugin 4 (in contents)', u'Plugin X (not found; deselecting will remove)'] ======= Changes ======= 3.9 (2010-10-18) ---------------- * Move concrete IAuthenticatorPlugin implementations to zope.pluggableauth.plugins. Leave backwards compatibility imports. * Use zope.formlib throughout to lift the dependency on zope.app.form. As it turns out, zope.app.form is still a indirect test dependency though. 3.8.0 (2010-09-25) ------------------ * Using python's ``doctest`` module instead of deprecated ``zope.testing.doctest[unit]``. * Moved the following views from `zope.app.securitypolicy` here, to inverse dependency between these two packages, as `zope.app.securitypolicy` deprecated in ZTK 1.0: - ``@@grant.html`` - ``@@AllRolePermissions.html`` - ``@@RolePermissions.html`` - ``@@RolesWithPermission.html`` 3.7.1 (2010-02-11) ------------------ * Using the new `principalfactories.zcml` file, from ``zope.pluggableauth``, to avoid duplication errors, in the adapters registration. 3.7.0 (2010-02-08) ------------------ * The Pluggable Authentication utility has been severed and released in a standalone package: `zope.pluggableauth`. We are now using this new package, providing backward compatibility imports to assure a smooth transition. 3.6.2 (2010-01-05) ------------------ * Fix tests by using zope.login, and require new zope.publisher 3.12. 3.6.1 (2009-10-07) ------------------ * Fix ftesting.zcml due to ``zope.securitypolicy`` update. * Don't use ``zope.app.testing.ztapi`` in tests, use zope.component's testing functions instead. * Fix functional tests and stop using port 8081. Redirecting to different port without trusted flag is not allowed. 3.6.0 (2009-03-14) ------------------ * Separate the presentation template and camefrom/redirection logic for the ``loginForm.html`` view. Now the logic is contained in the ``zope.app.authentication.browser.loginform.LoginForm`` class. * Fix login form redirection failure in some cases with Python 2.6. * Use the new ``zope.authentication`` package instead of ``zope.app.security``. * The "Password Manager Names" vocabulary and simple password manager registry were moved to the ``zope.password`` package. * Remove deprecated code. 3.5.0 (2009-03-06) ------------------ * Split password manager functionality off to the new ``zope.password`` package. Backward-compatibility imports are left in place. * Use ``zope.site`` instead of ``zope.app.component``. (Browser code still needs ``zope.app.component`` as it depends on view classes of this package.) 3.5.0a2 (2009-02-01) -------------------- * Make old encoded passwords really work. 3.5.0a1 (2009-01-31) -------------------- * Use ``zope.container`` instead of ``zope.app.container``. (Browser code still needs ``zope.app.container`` as it depends on view classes of this package.) * Encoded passwords are now stored with a prefix ({MD5}, {SHA1}, {SSHA}) indicating the used encoding schema. Old (encoded) passwords can still be used. * Add an SSHA password manager that is compatible with standard LDAP passwords. As this encoding gives better security agains dictionary attacks, users are encouraged to switch to this new password schema. * InternalPrincipal now uses SSHA password manager by default. 3.4.4 (2008-12-12) ------------------ * Depend on zope.session instead of zope.app.session. The first one currently has all functionality we need. * Fix deprecation warnings for ``md5`` and ``sha`` on Python 2.6. 3.4.3 (2008-08-07) ------------------ * No changes. Retag for correct release on PyPI. 3.4.2 (2008-07-09) ------------------- * Make it compatible with zope.app.container 3.6.1 and 3.5.4 changes, Changed ``super(BTreeContainer, self).__init__()`` to ``super(GroupFolder, self).__init__()`` in ``GroupFolder`` class. 3.4.1 (2007-10-24) ------------------ * Avoid deprecation warning. 3.4.0 (2007-10-11) ------------------ * Updated package meta-data. 3.4.0b1 (2007-09-27) -------------------- * First release independent of Zope. Keywords: zope3 authentication pluggable principal group 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.app.authentication-3.9/COPYRIGHT.txt0000644000175000017500000000004011457014222016062 0ustar jwjwZope Foundation and Contributorszope.app.authentication-3.9/buildout.cfg0000644000175000017500000000045111457014222016267 0ustar jwjw[buildout] extends = http://download.zope.org/zopetoolkit/index/1.0/ztk-versions.cfg http://download.zope.org/zopetoolkit/index/1.0/zopeapp-versions.cfg develop = . parts = test [versions] zope.pluggableauth = 1.1.0 [test] recipe = zc.recipe.testrunner eggs = zope.app.authentication [test]