[Zope-CVS] SVN: ldappas/trunk/ First implementation of a PAS LDAP
Authentication plugin
Florent Guillaume
fg at nuxeo.com
Wed Oct 13 10:15:16 EDT 2004
Log message for revision 28082:
First implementation of a PAS LDAP Authentication plugin
Changed:
A ldappas/trunk/__init__.py
A ldappas/trunk/authentication.py
A ldappas/trunk/configure.zcml
A ldappas/trunk/interfaces.py
A ldappas/trunk/ldappas-configure.zcml.in
A ldappas/trunk/tests/
A ldappas/trunk/tests/__init__.py
A ldappas/trunk/tests/test_authentication.py
-=-
Added: ldappas/trunk/__init__.py
===================================================================
--- ldappas/trunk/__init__.py 2004-10-13 14:13:19 UTC (rev 28081)
+++ ldappas/trunk/__init__.py 2004-10-13 14:15:14 UTC (rev 28082)
@@ -0,0 +1,17 @@
+##############################################################################
+#
+# Copyright (c) 2004 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE
+#
+##############################################################################
+"""LDAP PAS Plugins packages.
+
+$Id$
+"""
Property changes on: ldappas/trunk/__init__.py
___________________________________________________________________
Name: svn:keywords
+ Id
Name: svn:eol-style
+ native
Added: ldappas/trunk/authentication.py
===================================================================
--- ldappas/trunk/authentication.py 2004-10-13 14:13:19 UTC (rev 28081)
+++ ldappas/trunk/authentication.py 2004-10-13 14:15:14 UTC (rev 28082)
@@ -0,0 +1,202 @@
+##############################################################################
+#
+# Copyright (c) 2004 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""LDAP PAS Authentication plugin
+
+$Id$
+"""
+
+from zope.app import zapi
+from persistent import Persistent
+from zope.app.container.contained import Contained
+from zope.interface import implements
+
+from zope.interface import Interface
+from zope.app.pas.interfaces import IAuthenticationPlugin
+from ldapadapter.interfaces import ILDAPAdapter
+from interfaces import ILDAPAuthentication
+
+from ldap.filter import filter_format
+from ldapadapter.exceptions import ServerDown
+from ldapadapter.exceptions import InvalidCredentials
+from ldapadapter.exceptions import NoSuchObject
+
+
+class LDAPAuthentication(Persistent, Contained):
+ """A Persistent LDAP Authentication plugin for PAS.
+
+ An authentication plugin is configured using an LDAP Adapter that
+ will be use to check user credentials that encapsulates server
+ information, and additional authentication-specific informations.
+ """
+
+ implements(IAuthenticationPlugin, ILDAPAuthentication)
+
+ adapterName = ''
+ searchBase = ''
+ searchScope = ''
+ loginAttribute = ''
+ principalIdPrefix = ''
+ idAttribute = ''
+
+ def __init__(self):
+ pass
+
+ def getLDAPAdapter(self):
+ """Get the LDAP adapter according to our configuration.
+
+ Returns None if adapter connection is configured or available.
+ """
+ da = zapi.queryUtility(ILDAPAdapter, self.adapterName)
+ return da
+
+ def authenticateCredentials(self, credentials):
+ r"""See zope.app.pas.interfaces.IAuthenticationPlugin.
+
+ An LDAP Adapter has to be registered, we'll use a fake one
+ (registered by the test framework).
+
+ >>> auth = LDAPAuthentication()
+ >>> auth.adapterName = 'fake_ldap_adapter'
+ >>> auth.searchBase = 'dc=test'
+ >>> auth.searchScope = 'sub'
+ >>> auth.loginAttribute = 'cn'
+ >>> auth.principalIdPrefix = ''
+ >>> auth.idAttribute = 'uid'
+ >>> da = auth.getLDAPAdapter()
+ >>> authCreds = auth.authenticateCredentials
+
+ Incorrect credentials types are rejected.
+
+ >>> authCreds(123) is None
+ True
+ >>> authCreds({'glop': 'bzz'}) is None
+ True
+
+ You cannot authenticate if the search returns several results.
+
+ >>> len(da.connect().search('dc=test', 'sub', '(cn=many)')) > 1
+ True
+ >>> authCreds({'login': 'many', 'password': 'p'}) is None
+ True
+
+ You cannot authenticate if the search returns nothing.
+
+ >>> conn = da.connect()
+ >>> len(conn.search('dc=test', 'sub', '(cn=none)')) == 0
+ True
+ >>> authCreds({'login': 'none', 'password': 'p'}) is None
+ True
+
+ You cannot authenticate with the wrong password.
+
+ >>> authCreds({'login': 'ok', 'password': 'hm'}) is None
+ True
+
+ Authentication succeeds if you provide the correct password.
+
+ >>> authCreds({'login': 'ok', 'password': '42pw'})
+ (u'42', {'login': 'ok'})
+
+ The id returned comes from a configurable attribute, and can be
+ prefixed so that it is unique.
+
+ >>> auth.principalIdPrefix = 'ldap.'
+ >>> auth.idAttribute = 'cn'
+ >>> authCreds({'login': 'ok', 'password': '42pw'})
+ (u'ldap.ok', {'login': 'ok'})
+
+ The id attribute 'dn' can be specified to use the full dn as id.
+
+ >>> auth.idAttribute = 'dn'
+ >>> authCreds({'login': 'ok', 'password': '42pw'})
+ (u'ldap.uid=42,dc=test', {'login': 'ok'})
+
+ If the id attribute returns several values, the first one is
+ used.
+
+ >>> auth.idAttribute = 'mult'
+ >>> conn.search('dc=test', 'sub', '(cn=ok)')[0][1]['mult']
+ [u'm1', u'm2']
+ >>> authCreds({'login': 'ok', 'password': '42pw'})
+ (u'ldap.m1', {'login': 'ok'})
+
+ Authentication fails if the id attribute is not present:
+
+ >>> auth.idAttribute = 'nonesuch'
+ >>> conn.search('dc=test', 'sub', '(cn=ok)')[0][1]['nonesuch']
+ Traceback (most recent call last):
+ ...
+ KeyError: 'nonesuch'
+ >>> authCreds({'login': 'ok', 'password': '42pw'}) is None
+ True
+
+ You cannot authenticate if the server to which the adapter
+ connects is down.
+
+ >>> da._isDown = True
+ >>> authCreds({'login': 'ok', 'password': '42pw'}) is None
+ True
+ >>> da._isDown = False
+
+ You cannot authenticate if the plugin has a bad configuration.
+
+ >>> auth.searchBase = 'dc=bzzt'
+ >>> authCreds({'login': 'ok', 'password': '42pw'}) is None
+ True
+ """
+
+ if not isinstance(credentials, dict):
+ return None
+ if not ('login' in credentials and 'password' in credentials):
+ return None
+
+ da = self.getLDAPAdapter()
+ if da is None:
+ return None
+
+ login = credentials['login']
+ password = credentials['password']
+
+ # Search for a matching entry.
+ try:
+ conn = da.connect()
+ except ServerDown:
+ return None
+ filter = filter_format('(%s=%s)', (self.loginAttribute, login))
+ try:
+ res = conn.search(self.searchBase, self.searchScope, filter=filter)
+ except NoSuchObject:
+ return None
+ if len(res) != 1:
+ # Search returned no result or too many.
+ return None
+ dn, entry = res[0]
+
+ # Find the id we'll return.
+ id_attr = self.idAttribute
+ if id_attr == 'dn':
+ id = dn
+ elif entry.get(id_attr):
+ id = entry[id_attr][0]
+ else:
+ return None
+ id = self.principalIdPrefix + id
+
+ # Check authentication.
+ try:
+ conn = da.connect(dn, password)
+ except InvalidCredentials:
+ return None
+
+ return id, {'login': login}
Property changes on: ldappas/trunk/authentication.py
___________________________________________________________________
Name: svn:keywords
+ Id
Name: svn:eol-style
+ native
Added: ldappas/trunk/configure.zcml
===================================================================
--- ldappas/trunk/configure.zcml 2004-10-13 14:13:19 UTC (rev 28081)
+++ ldappas/trunk/configure.zcml 2004-10-13 14:15:14 UTC (rev 28082)
@@ -0,0 +1,50 @@
+<configure
+ xmlns="http://namespaces.zope.org/zope"
+ xmlns:browser="http://namespaces.zope.org/browser"
+ i18n_domain="ldappas"
+ >
+
+ <localUtility class=".authentication.LDAPAuthentication">
+
+ <implements
+ interface="zope.app.annotation.interfaces.IAttributeAnnotatable"
+ />
+
+ <require
+ permission="zope.ManageServices"
+ interface="zope.app.pas.interfaces.IAuthenticationPlugin"
+ />
+
+ <require
+ permission="zope.ManageServices"
+ interface="ldappas.interfaces.ILDAPAuthentication"
+ set_schema="ldappas.interfaces.ILDAPAuthentication"
+ />
+
+ </localUtility>
+
+ <browser:addMenuItem
+ title="PAS LDAP Authentication Plugin"
+ description="A PAS LDAP Authentication Plugin"
+ class=".authentication.LDAPAuthentication"
+ permission="zope.ManageServices"
+ view="addPASLDAPAuthenticationPlugin"
+ />
+
+ <browser:addform
+ schema="ldappas.interfaces.ILDAPAuthentication"
+ label="Add a PAS LDAP Authentication Plugin"
+ content_factory=".authentication.LDAPAuthentication"
+ name="addPASLDAPAuthenticationPlugin"
+ permission="zope.ManageServices"
+ />
+
+ <browser:editform
+ schema="ldappas.interfaces.ILDAPAuthentication"
+ name="edit.html"
+ menu="zmi_views"
+ title="Edit"
+ permission="zope.ManageServices"
+ />
+
+</configure>
Property changes on: ldappas/trunk/configure.zcml
___________________________________________________________________
Name: svn:eol-style
+ native
Added: ldappas/trunk/interfaces.py
===================================================================
--- ldappas/trunk/interfaces.py 2004-10-13 14:13:19 UTC (rev 28081)
+++ ldappas/trunk/interfaces.py 2004-10-13 14:15:14 UTC (rev 28082)
@@ -0,0 +1,57 @@
+##############################################################################
+#
+# Copyright (c) 2004 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""LDAP PAS Plugin interfaces
+
+$Id$
+"""
+
+import re
+from zope.app.i18n import ZopeMessageIDFactory as _
+from zope.interface import Interface
+from zope.schema import TextLine
+
+class ILDAPAuthentication(Interface):
+ adapterName = TextLine(
+ title=_("LDAP Adapter"),
+ default=u'ldapadapter',
+ required=True,
+ )
+ searchBase = TextLine(
+ title=_("Search base"),
+ default=u'dc=example,dc=org',
+ required=True,
+ )
+ searchScope = TextLine(
+ title=_("Search scope"),
+ default=u'sub',
+ required=True,
+ )
+ loginAttribute = TextLine(
+ title=_("Login attribute"),
+ constraint=re.compile("[a-zA-Z][-a-zA-Z0-9]*$").match,
+ default=u'uid',
+ required=True,
+ )
+ principalIdPrefix = TextLine(
+ title=_("Principal id prefix"),
+ default=u'ldap.',
+ required=False,
+ )
+ idAttribute = TextLine(
+ title=_("Id attribute"),
+ constraint=re.compile("[a-zA-Z][-a-zA-Z0-9]*$").match,
+ default=u'uid',
+ required=True,
+ )
+ #searchObjectClasses
Property changes on: ldappas/trunk/interfaces.py
___________________________________________________________________
Name: svn:eol-style
+ native
Added: ldappas/trunk/ldappas-configure.zcml.in
===================================================================
--- ldappas/trunk/ldappas-configure.zcml.in 2004-10-13 14:13:19 UTC (rev 28081)
+++ ldappas/trunk/ldappas-configure.zcml.in 2004-10-13 14:15:14 UTC (rev 28082)
@@ -0,0 +1,5 @@
+<configure xmlns='http://namespaces.zope.org/zope'>
+
+ <include package="ldappas" />
+
+</configure>
Property changes on: ldappas/trunk/ldappas-configure.zcml.in
___________________________________________________________________
Name: svn:eol-style
+ native
Added: ldappas/trunk/tests/__init__.py
===================================================================
--- ldappas/trunk/tests/__init__.py 2004-10-13 14:13:19 UTC (rev 28081)
+++ ldappas/trunk/tests/__init__.py 2004-10-13 14:15:14 UTC (rev 28082)
@@ -0,0 +1,17 @@
+##############################################################################
+#
+# Copyright (c) 2004 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE
+#
+##############################################################################
+"""LDAP PAS Plugins test packages.
+
+$Id$
+"""
Property changes on: ldappas/trunk/tests/__init__.py
___________________________________________________________________
Name: svn:keywords
+ Id
Name: svn:eol-style
+ native
Added: ldappas/trunk/tests/test_authentication.py
===================================================================
--- ldappas/trunk/tests/test_authentication.py 2004-10-13 14:13:19 UTC (rev 28081)
+++ ldappas/trunk/tests/test_authentication.py 2004-10-13 14:15:14 UTC (rev 28082)
@@ -0,0 +1,80 @@
+##############################################################################
+#
+# Copyright (c) 2004 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""LDAP PAS Plugins tests
+
+$Id$
+"""
+
+import sys
+import unittest
+from zope.testing import doctest
+
+from zope.interface import implements
+from zope.app import zapi
+from zope.app.tests import setup
+from zope.app.servicenames import Utilities
+from zope.app.utility import LocalUtilityService
+
+from ldapadapter.interfaces import ILDAPAdapter
+from ldapadapter.exceptions import ServerDown
+from ldapadapter.exceptions import InvalidCredentials
+from ldapadapter.exceptions import NoSuchObject
+
+class FakeLDAPAdapter:
+ implements(ILDAPAdapter)
+ _isDown = False
+ def connect(self, dn=None, password=None):
+ if self._isDown:
+ raise ServerDown
+ if not dn and not password:
+ return FakeLDAPConnection()
+ if dn == 'uid=42,dc=test' and password == '42pw':
+ return FakeLDAPConnection()
+ raise InvalidCredentials
+
+class FakeLDAPConnection:
+ def search(self, base, scope='sub', filter='(objectClass=*)', attrs=[]):
+ if base.endswith('dc=bzzt'):
+ raise NoSuchObject
+ if filter == '(cn=many)':
+ return [(u'uid=1,dc=test', {'cn': [u'many']}),
+ (u'uid=2,dc=test', {'cn': [u'many']})]
+ if filter == '(cn=none)':
+ return []
+ if filter == '(cn=ok)':
+ return [(u'uid=42,dc=test', {'cn': [u'ok'],
+ 'uid': [u'42'],
+ 'mult': [u'm1', u'm2'],
+ })]
+ return []
+
+def setUp(test):
+ root = setup.placefulSetUp(site=True)
+ sm = zapi.getServices(root)
+ setup.addService(sm, Utilities, LocalUtilityService())
+ setup.addUtility(sm, 'fake_ldap_adapter', ILDAPAdapter,
+ FakeLDAPAdapter())
+
+def tearDown(test):
+ setup.placefulTearDown()
+
+def test_suite():
+ return unittest.TestSuite((
+ doctest.DocTestSuite('ldappas.authentication',
+ setUp=setUp, tearDown=tearDown,
+ ),
+ ))
+
+if __name__ == '__main__':
+ unittest.main(defaultTest='test_suite')
Property changes on: ldappas/trunk/tests/test_authentication.py
___________________________________________________________________
Name: svn:keywords
+ Id
Name: svn:eol-style
+ native
More information about the Zope-CVS
mailing list