Commits

Peter Sagerson committed f796783

Fix #33: Reject empty passwords by default.

Unless AUTH_LDAP_PERMIT_EMPTY_PASSWORD is set to True, LDAPBackend.authenticate() will immediately return None if the password is empty. This is technically backwards-incompatible, but it's a more secure default for those LDAP servers that are configured such that binds without passwords always succeed.

Comments (0)

Files changed (6)

-include README.rst
-recursive-include docs Makefile *.py *.rst
+include README LICENSE CHANGES
+
+recursive-include docs *
+prune docs/build
+global-exclude *.pyc .DS_Store .workon

django_auth_ldap/backend.py

     #
 
     def authenticate(self, username, password):
+        if len(password) == 0 and not self.settings.PERMIT_EMPTY_PASSWORD:
+            logger.debug('Rejecting empty password for %s' % username)
+            return None
+
         ldap_user = _LDAPUser(self, username=username.strip())
         user = ldap_user.authenticate(password)
 
         except self.ldap.LDAPError, e:
             logger.warning(u"Caught LDAPError while authenticating %s: %s",
                 self._username, pprint.pformat(e))
-        except Exception, e:
-            logger.error(u"Caught Exception while authenticating %s: %s",
-                self._username, pprint.pformat(e))
-            logger.error(''.join(traceback.format_tb(sys.exc_info()[2])))
+        except Exception:
+            logger.exception(u"Caught Exception while authenticating %s",
+                self._username)
             raise
 
         return user
         'GROUP_SEARCH': None,
         'GROUP_TYPE': None,
         'MIRROR_GROUPS': False,
+        'PERMIT_EMPTY_PASSWORD': False,
         'PROFILE_ATTR_MAP': {},
         'PROFILE_FLAGS_BY_GROUP': {},
         'REQUIRE_GROUP': None,

django_auth_ldap/tests.py

 
 
 class LDAPTest(TestCase):
-
     # Following are the objecgs in our mock LDAP directory
     alice = ("uid=alice,ou=people,o=test", {
         "uid": ["alice"],
 
     def setUp(self):
         self.configure_logger()
-        self.mock_ldap.reset()
 
         self.ldap = _LDAPConfig.ldap = self.mock_ldap
+
         self.backend = backend.LDAPBackend()
+        self.backend.ldap_module() # Force global configuration
+
+        self.mock_ldap.reset()
 
     def tearDown(self):
         pass
             ['initialize', 'simple_bind_s', 'search', 'search', 'result',
                 'result', 'simple_bind_s'])
 
+    def test_deny_empty_password(self):
+        self._init_settings(
+            USER_DN_TEMPLATE='uid=%(user)s,ou=people,o=test',
+        )
+
+        alice = self.backend.authenticate(username=u'alice', password=u'')
+
+        self.assertEqual(alice, None)
+        self.assertEqual(self.mock_ldap.ldap_methods_called(), [])
+
+    def test_permit_empty_password(self):
+        self._init_settings(
+            USER_DN_TEMPLATE='uid=%(user)s,ou=people,o=test',
+            PERMIT_EMPTY_PASSWORD=True,
+        )
+
+        alice = self.backend.authenticate(username=u'alice', password=u'')
+
+        self.assertEqual(alice, None)
+        self.assertEqual(self.mock_ldap.ldap_methods_called(),
+            ['initialize', 'simple_bind_s'])
+
     def _init_settings(self, **kwargs):
         self.backend.settings = TestSettings(**kwargs)
 

docs/source/multiconfig.rst

 each backend in turn until one of them succeeds. When a particular backend
 successfully authenticates a user, that user will be linked to the backend for
 the duration of their session.
+
+.. note::
+
+    Due to its global nature, :setting:`AUTH_LDAP_GLOBAL_OPTIONS` ignores the
+    settings prefix. Regardless of how many backends are installed, this setting
+    is referenced once by its default name at the time we load the ldap module.

docs/source/reference.rst

 A dictionary of options to pass to ``ldap.set_option()``. Keys are
 `ldap.OPT_* <http://python-ldap.org/doc/html/ldap.html#options>`_ constants.
 
+.. note::
+
+    Due to its global nature, this setting ignores the :doc:`settings prefix
+    <multiconfig>`. Regardless of how many backends are installed, this setting
+    is referenced once by its default name at the time we load the ldap module.
+
 
 .. setting:: AUTH_LDAP_GROUP_CACHE_TIMEOUT
 
     name="django-auth-ldap",
     version="1.1.2",
     description="Django LDAP authentication backend",
-    long_description=open('README.rst').read(),
+    long_description=open('README').read(),
     url="http://bitbucket.org/psagers/django-auth-ldap/",
     author="Peter Sagerson",
     author_email="psagers.pypi@ignorare.net",