Hello community,
here is the log from the commit of package python-django-auth-ldap for
openSUSE:Factory checked in at 2015-04-25 09:52:51
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-django-auth-ldap (Old)
and /work/SRC/openSUSE:Factory/.python-django-auth-ldap.new (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-django-auth-ldap"
Changes:
--------
---
/work/SRC/openSUSE:Factory/python-django-auth-ldap/python-django-auth-ldap.changes
2014-10-09 12:52:38.000000000 +0200
+++
/work/SRC/openSUSE:Factory/.python-django-auth-ldap.new/python-django-auth-ldap.changes
2015-04-25 11:25:37.000000000 +0200
@@ -1,0 +2,17 @@
+Thu Apr 23 06:54:50 UTC 2015 - [email protected]
+
+- Update to 1.2.6:
+ - Performance improvements to group mirroring (from
+ `Denver Janke <https://bitbucket.org/denverjanke>`_).
+ - Add :data:`django_auth_ldap.backend.ldap_error` signal for custom handling
of
+ :exc:`~ldap.LDAPError` exceptions.
+ - Add :data:`django_auth_ldap.backend.LDAPBackend.default_settings` for
+ per-subclass default settings.
+ - Fix interaction between :setting:`AUTH_LDAP_AUTHORIZE_ALL_USERS` and
+ :setting:`AUTH_LDAP_USER_SEARCH`.
+ - Add support for nisNetgroup groups (thanks to Christopher Bartz).
+ - Fix `#50`_: Improved escaping for filter strings.
+ - Accept (and ignore) arbitrary keyword arguments to
+ :meth:`~django_auth_ldap.backend.LDAPBackend.authenticate`.
+
+-------------------------------------------------------------------
Old:
----
django-auth-ldap-1.2.2.tar.gz
New:
----
django-auth-ldap-1.2.6.tar.gz
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Other differences:
------------------
++++++ python-django-auth-ldap.spec ++++++
--- /var/tmp/diff_new_pack.mTk6hO/_old 2015-04-25 11:25:37.000000000 +0200
+++ /var/tmp/diff_new_pack.mTk6hO/_new 2015-04-25 11:25:37.000000000 +0200
@@ -1,7 +1,7 @@
#
# spec file for package python-django-auth-ldap
#
-# Copyright (c) 2014 SUSE LINUX Products GmbH, Nuernberg, Germany.
+# Copyright (c) 2015 SUSE LINUX GmbH, Nuernberg, Germany.
#
# All modifications and additions to the file contributed by third parties
# remain the property of their copyright owners, unless otherwise agreed
@@ -20,7 +20,7 @@
%{!?python_sitearch: %global python_sitearch %(%{__python} -c "from
distutils.sysconfig import get_python_lib; print get_python_lib(1)")}
Name: python-django-auth-ldap
-Version: 1.2.2
+Version: 1.2.6
Release: 0
Url: http://bitbucket.org/psagers/django-auth-ldap/
Summary: Django LDAP authentication backend
++++++ django-auth-ldap-1.2.2.tar.gz -> django-auth-ldap-1.2.6.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/django-auth-ldap-1.2.2/CHANGES
new/django-auth-ldap-1.2.6/CHANGES
--- old/django-auth-ldap-1.2.2/CHANGES 2014-09-22 17:34:33.000000000 +0200
+++ new/django-auth-ldap-1.2.6/CHANGES 2015-03-29 20:23:50.000000000 +0200
@@ -1,3 +1,40 @@
+v1.2.6 - 2015-03-29
+-------------------
+
+- Performance improvements to group mirroring (from
+ `Denver Janke <https://bitbucket.org/denverjanke>`_).
+
+- Add :data:`django_auth_ldap.backend.ldap_error` signal for custom handling of
+ :exc:`~ldap.LDAPError` exceptions.
+
+- Add :data:`django_auth_ldap.backend.LDAPBackend.default_settings` for
+ per-subclass default settings.
+
+
+v1.2.5 - 2015-01-30
+-------------------
+
+- Fix interaction between :setting:`AUTH_LDAP_AUTHORIZE_ALL_USERS` and
+ :setting:`AUTH_LDAP_USER_SEARCH`.
+
+
+v1.2.4 - 2014-12-28
+-------------------
+
+- Add support for nisNetgroup groups (thanks to Christopher Bartz).
+
+
+v1.2.3 - 2014-11-18
+-------------------
+
+- Fix `#50`_: Improved escaping for filter strings.
+
+- Accept (and ignore) arbitrary keyword arguments to
+ :meth:`~django_auth_ldap.backend.LDAPBackend.authenticate`.
+
+.. _#50: https://bitbucket.org/psagers/django-auth-ldap/issue/50/
+
+
v1.2.2 - 2014-09-22
-------------------
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/django-auth-ldap-1.2.2/PKG-INFO
new/django-auth-ldap-1.2.6/PKG-INFO
--- old/django-auth-ldap-1.2.2/PKG-INFO 2014-09-22 17:42:05.000000000 +0200
+++ new/django-auth-ldap-1.2.6/PKG-INFO 2015-03-29 20:25:36.000000000 +0200
@@ -1,6 +1,6 @@
Metadata-Version: 1.1
Name: django-auth-ldap
-Version: 1.2.2
+Version: 1.2.6
Summary: Django LDAP authentication backend
Home-page: http://bitbucket.org/psagers/django-auth-ldap/
Author: Peter Sagerson
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/django-auth-ldap-1.2.2/django_auth_ldap/__init__.py
new/django-auth-ldap-1.2.6/django_auth_ldap/__init__.py
--- old/django-auth-ldap-1.2.2/django_auth_ldap/__init__.py 2014-09-22
17:33:16.000000000 +0200
+++ new/django-auth-ldap-1.2.6/django_auth_ldap/__init__.py 2015-03-29
20:24:03.000000000 +0200
@@ -1,2 +1,2 @@
-version = (1, 2, 2)
+version = (1, 2, 6)
version_string = '.'.join(map(str, version))
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/django-auth-ldap-1.2.2/django_auth_ldap/backend.py
new/django-auth-ldap-1.2.6/django_auth_ldap/backend.py
--- old/django-auth-ldap-1.2.2/django_auth_ldap/backend.py 2014-08-24
17:46:09.000000000 +0200
+++ new/django-auth-ldap-1.2.6/django_auth_ldap/backend.py 2015-03-29
20:10:10.000000000 +0200
@@ -52,6 +52,7 @@
import copy
from django.contrib.auth.models import User, Group, Permission
+import django.conf
from django.core.cache import cache
from django.core.exceptions import ImproperlyConfigured, ObjectDoesNotExist
import django.dispatch
@@ -86,10 +87,16 @@
logger = _LDAPConfig.get_logger()
-# Signals for populating user objects.
+# Exported signals
+
+# Allows clients to perform custom user/profile population.
populate_user = django.dispatch.Signal(providing_args=["user", "ldap_user"])
populate_user_profile = django.dispatch.Signal(providing_args=["profile",
"ldap_user"])
+# Allows clients to inspect and perform special handling of LDAPError
+# exceptions. Exceptions raised by handlers will be propagated out.
+ldap_error = django.dispatch.Signal(providing_args=['context', 'exception'])
+
class LDAPBackend(object):
"""
@@ -108,6 +115,9 @@
# support multiple collections of settings.
settings_prefix = 'AUTH_LDAP_'
+ # Default settings to override the built-in defaults.
+ default_settings = {}
+
def __getstate__(self):
"""
Exclude certain cached properties from pickling.
@@ -117,7 +127,8 @@
def _get_settings(self):
if self._settings is None:
- self._settings = LDAPSettings(self.settings_prefix)
+ self._settings = LDAPSettings(self.settings_prefix,
+ self.default_settings)
return self._settings
@@ -128,9 +139,7 @@
def _get_ldap(self):
if self._ldap is None:
- from django.conf import settings
-
- options = getattr(settings, 'AUTH_LDAP_GLOBAL_OPTIONS', None)
+ options = getattr(django.conf.settings,
'AUTH_LDAP_GLOBAL_OPTIONS', None)
self._ldap = _LDAPConfig.get_ldap(options)
@@ -149,7 +158,7 @@
# The Django auth backend API
#
- def authenticate(self, username, password):
+ def authenticate(self, username, password, **kwargs):
if len(password) == 0 and not self.settings.PERMIT_EMPTY_PASSWORD:
logger.debug('Rejecting empty password for %s' % username)
return None
@@ -187,7 +196,7 @@
if not hasattr(user, 'ldap_user') and
self.settings.AUTHORIZE_ALL_USERS:
_LDAPUser(self, user=user) # This sets user.ldap_user
- if hasattr(user, 'ldap_user'):
+ if hasattr(user, 'ldap_user') and (user.ldap_user.dn is not None):
return user.ldap_user.get_group_permissions()
else:
return set()
@@ -333,8 +342,11 @@
except self.AuthenticationFailed as e:
logger.debug(u"Authentication failed for %s: %s" %
(self._username, e))
except ldap.LDAPError as e:
- logger.warning(u"Caught LDAPError while authenticating %s: %s",
- self._username, pprint.pformat(e))
+ results = ldap_error.send(self.backend.__class__,
+ context='authenticate', exception=e)
+ if len(results) == 0:
+ logger.warning(u"Caught LDAPError while authenticating %s: %s",
+ self._username, pprint.pformat(e))
except Exception:
logger.exception(u"Caught Exception while authenticating %s",
self._username)
@@ -354,8 +366,12 @@
try:
self._load_group_permissions()
except ldap.LDAPError as e:
- logger.warning("Caught LDAPError loading group
permissions: %s",
- pprint.pformat(e))
+ results = ldap_error.send(self.backend.__class__,
+ context='get_group_permissions',
+ exception=e)
+ if len(results) == 0:
+ logger.warning("Caught LDAPError loading group
permissions: %s",
+ pprint.pformat(e))
return self._group_permissions
@@ -373,8 +389,11 @@
user = self._user
except ldap.LDAPError as e:
- logger.warning(u"Caught LDAPError while authenticating %s: %s",
- self._username, pprint.pformat(e))
+ results = ldap_error.send(self.backend.__class__,
+ context='populate_user', exception=e)
+ if len(results) == 0:
+ logger.warning(u"Caught LDAPError while authenticating %s: %s",
+ self._username, pprint.pformat(e))
except Exception as e:
logger.error(u"Caught Exception while authenticating %s: %s",
self._username, pprint.pformat(e))
@@ -558,7 +577,7 @@
# We populate the profile after the user model is saved to give the
# client a chance to create the profile. Custom user models in Django
# 1.5 probably won't have a get_profile method.
- if should_populate and django.VERSION < (1, 7) and hasattr(self._user,
'get_profile'):
+ if should_populate and self._should_populate_profile():
self._populate_and_save_user_profile()
def _populate_user(self):
@@ -582,6 +601,11 @@
value = any(self._get_groups().is_member_of(dn) for dn in
group_dns)
setattr(self._user, field, value)
+ def _should_populate_profile(self):
+ return ((django.VERSION < (1, 7)) and
+ (getattr(django.conf.settings, 'AUTH_PROFILE_MODULE', None) is
not None) and
+ hasattr(self._user, 'get_profile'))
+
def _populate_and_save_user_profile(self):
"""
Populates a User profile object with fields from the LDAP directory.
@@ -642,11 +666,17 @@
Mirrors the user's LDAP groups in the Django database and updates the
user's membership.
"""
- group_names = self._get_groups().get_group_names()
- groups = [Group.objects.get_or_create(name=group_name)[0] for
group_name
- in group_names]
+ target_group_names = frozenset(self._get_groups().get_group_names())
+ current_group_names = frozenset(self._user.groups.values_list('name',
flat=True).iterator())
+
+ if target_group_names != current_group_names:
+ existing_groups =
list(Group.objects.filter(name__in=target_group_names).iterator())
+ existing_group_names = frozenset(group.name for group in
existing_groups)
+
+ new_groups = [Group.objects.get_or_create(name=name)[0] for name
+ in target_group_names if name not in
existing_group_names]
- self._user.groups = groups
+ self._user.groups = existing_groups + new_groups
#
# Group information
@@ -866,13 +896,13 @@
'USER_SEARCH': None,
}
- def __init__(self, prefix='AUTH_LDAP_'):
+ def __init__(self, prefix='AUTH_LDAP_', defaults={}):
"""
Loads our settings from django.conf.settings, applying defaults for any
that are omitted.
"""
- from django.conf import settings
+ defaults = dict(self.defaults, **defaults)
- for name, default in self.defaults.items():
- value = getattr(settings, prefix + name, default)
+ for name, default in defaults.items():
+ value = getattr(django.conf.settings, prefix + name, default)
setattr(self, name, value)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/django-auth-ldap-1.2.2/django_auth_ldap/config.py
new/django-auth-ldap-1.2.6/django_auth_ldap/config.py
--- old/django-auth-ldap-1.2.2/django_auth_ldap/config.py 2014-04-10
17:18:23.000000000 +0200
+++ new/django-auth-ldap-1.2.6/django_auth_ldap/config.py 2014-12-28
20:06:03.000000000 +0100
@@ -142,15 +142,19 @@
return self.__class__(self.base_dn, self.scope, filterstr)
- def execute(self, connection, filterargs=()):
+ def execute(self, connection, filterargs=(), escape=True):
"""
Executes the search on the given connection (an LDAPObject). filterargs
is an object that will be used for expansion of the filter string.
+ If escape is True, values in filterargs will be escaped.
The python-ldap library returns utf8-encoded strings. For the sake of
sanity, this method will decode all result strings and return them as
Unicode.
"""
+ if escape:
+ filterargs = self._escape_filterargs(filterargs)
+
try:
filterstr = self.filterstr % filterargs
results = connection.search_s(force_str(self.base_dn),
@@ -163,11 +167,18 @@
return self._process_results(results)
- def _begin(self, connection, filterargs=()):
+ def _begin(self, connection, filterargs=(), escape=True):
"""
Begins an asynchronous search and returns the message id to retrieve
the results.
+
+ filterargs is an object that will be used for expansion of the filter
+ string. If escape is True, values in filterargs will be escaped.
+
"""
+ if escape:
+ filterargs = self._escape_filterargs(filterargs)
+
try:
filterstr = self.filterstr % filterargs
msgid = connection.search(force_str(self.base_dn),
@@ -193,6 +204,26 @@
return self._process_results(results)
+ def _escape_filterargs(self, filterargs):
+ """
+ Escapes values in filterargs.
+
+ filterargs is a value suitable for Django's string formatting operator
+ (%), which means it's either a tuple or a dict. This return a new tuple
+ or dict with all values escaped for use in filter strings.
+
+ """
+ if isinstance(filterargs, tuple):
+ filterargs = tuple(self.ldap.filter.escape_filter_chars(value)
+ for value in filterargs)
+ elif isinstance(filterargs, dict):
+ filterargs = dict((key,
self.ldap.filter.escape_filter_chars(value))
+ for key, value in filterargs.items())
+ else:
+ raise TypeError("filterargs must be a tuple or dict.")
+
+ return filterargs
+
def _process_results(self, results):
"""
Returns a sanitized copy of raw LDAP results. This scrubs out
@@ -238,8 +269,9 @@
results = {}
for search, msgid in zip(self.searches, msgids):
- result = search._results(connection, msgid)
- results.update(dict(result))
+ if msgid is not None:
+ result = search._results(connection, msgid)
+ results.update(dict(result))
return results.items()
@@ -437,6 +469,43 @@
result = 0
return result
+
+
+class NISGroupType(LDAPGroupType):
+ """
+ A group type that handles nisNetgroup.
+ """
+ def user_groups(self, ldap_user, group_search):
+ try:
+ user_uid = ldap_user.attrs['uid'][0]
+ filterstr = u'(|(nisNetgroupTriple=%s)(nisNetgroupTriple=%s))' % (
+ self.ldap.filter.escape_filter_chars('(,%s,)' % user_uid),
+ self.ldap.filter.escape_filter_chars('(-,%s,-)' % user_uid)
+ )
+ search = group_search.search_with_additional_term_string(filterstr)
+ groups = search.execute(ldap_user.connection)
+ except (KeyError, IndexError):
+ pass
+ return groups
+
+ def is_member(self, ldap_user, group_dn):
+ try:
+ user_uid = ldap_user.attrs['uid'][0]
+ result = ldap_user.connection.compare_s(
+ force_str(group_dn),
+ force_str('nisNetgroupTriple'),
+ force_str('(,%s,)' % (user_uid))
+ )
+ if result == 0:
+ result = ldap_user.connection.compare_s(
+ force_str(group_dn),
+ force_str('nisNetgroupTriple'),
+ force_str('(-,%s,-)' % (user_uid))
+ )
+ except (ldap.UNDEFINED_TYPE, ldap.NO_SUCH_ATTRIBUTE, KeyError,
IndexError):
+ result = 0
+
+ return result
class NestedMemberDNGroupType(LDAPGroupType):
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/django-auth-ldap-1.2.2/django_auth_ldap/tests.py
new/django-auth-ldap-1.2.6/django_auth_ldap/tests.py
--- old/django-auth-ldap-1.2.2/django_auth_ldap/tests.py 2014-06-25
03:09:49.000000000 +0200
+++ new/django-auth-ldap-1.2.6/django_auth_ldap/tests.py 2015-03-29
20:09:30.000000000 +0200
@@ -28,6 +28,7 @@
from copy import deepcopy
import logging
import pickle
+import warnings
import ldap
try:
@@ -53,7 +54,7 @@
from django_auth_ldap.models import TestUser, TestProfile
from django_auth_ldap import backend
from django_auth_ldap.config import LDAPSearch, LDAPSearchUnion
-from django_auth_ldap.config import PosixGroupType, MemberDNGroupType,
NestedMemberDNGroupType
+from django_auth_ldap.config import PosixGroupType, MemberDNGroupType,
NestedMemberDNGroupType, NISGroupType
from django_auth_ldap.config import GroupOfNamesType
@@ -68,6 +69,7 @@
setattr(self, name, value)
[email protected](mockldap is None, "django_auth_ldap tests require the
mockldap package.")
class LDAPTest(TestCase):
top = ("o=test", {"o": "test"})
people = ("ou=people,o=test", {"ou": "people"})
@@ -155,6 +157,23 @@
"member": ["uid=bob,ou=people,o=test"]
})
+ # nisGroup objects
+ active_nis = ("cn=active_nis,ou=groups,o=test", {
+ "cn": ["active_nis"],
+ "objectClass": ["nisNetgroup"],
+ "nisNetgroupTriple": ["(,alice,)"]
+ })
+ staff_nis = ("cn=staff_nis,ou=groups,o=test", {
+ "cn": ["staff_nis"],
+ "objectClass": ["nisNetgroup"],
+ "nisNetgroupTriple": ["(,alice,)"],
+ })
+ superuser_nis = ("cn=superuser_nis,ou=groups,o=test", {
+ "cn": ["superuser_nis"],
+ "objectClass": ["nisNetgroup"],
+ "nisNetgroupTriple": ["(,alice,)"],
+ })
+
# Nested groups with a circular reference
parent_gon = ("cn=parent_gon,ou=groups,o=test", {
"cn": ["parent_gon"],
@@ -178,6 +197,7 @@
directory = dict([top, people, groups, moregroups, alice, bob, dressler,
nobody, active_px, staff_px, superuser_px, empty_gon,
active_gon, staff_gon, superuser_gon, other_gon,
+ active_nis, staff_nis, superuser_nis,
parent_gon, nested_gon, circular_gon])
@classmethod
@@ -197,6 +217,8 @@
cls.configure_logger()
cls.mockldap = mockldap.MockLdap(cls.directory)
+ warnings.filterwarnings('ignore', message='.*?AUTH_PROFILE_MODULE',
category=DeprecationWarning, module='django_auth_ldap')
+
@classmethod
def tearDownClass(cls):
del cls.mockldap
@@ -212,6 +234,10 @@
self.mockldap.stop()
del self.ldapobj
+ #
+ # Tests
+ #
+
def test_options(self):
self._init_settings(
USER_DN_TEMPLATE='uid=%(user)s,ou=people,o=test',
@@ -252,6 +278,40 @@
['initialize', 'simple_bind_s']
)
+ def test_default_settings(self):
+ class MyBackend(backend.LDAPBackend):
+ default_settings = dict(
+ USER_DN_TEMPLATE='uid=%(user)s,ou=people,o=test'
+ )
+ self.backend = MyBackend()
+
+ user_count = User.objects.count()
+
+ user = self.backend.authenticate(username='alice', password='password')
+
+ self.assertTrue(not user.has_usable_password())
+ self.assertEqual(user.username, 'alice')
+ self.assertEqual(User.objects.count(), user_count + 1)
+ self.assertEqual(
+ self.ldapobj.methods_called(),
+ ['initialize', 'simple_bind_s']
+ )
+
+ def test_simple_bind_escaped(self):
+ """ Bind with a username that requires escaping. """
+ self._init_settings(
+ USER_DN_TEMPLATE='uid=%(user)s,ou=people,o=test'
+ )
+
+ user = self.backend.authenticate(username='alice,1',
password='password')
+
+ self.assertEqual(user, None)
+ self.assertEqual(
+ self.ldapobj.methods_called(with_args=True),
+ [('initialize', ('ldap://localhost',), {}),
+ ('simple_bind_s', ('uid=alice\\,1,ou=people,o=test', 'password'),
{})]
+ )
+
def test_new_user_lowercase(self):
self._init_settings(
USER_DN_TEMPLATE='uid=%(user)s,ou=people,o=test'
@@ -412,6 +472,24 @@
['initialize', 'simple_bind_s', 'search_s', 'simple_bind_s']
)
+ def test_search_bind_escaped(self):
+ """ Search for a username that requires escaping. """
+ self._init_settings(
+ USER_SEARCH=LDAPSearch(
+ "ou=people,o=test", ldap.SCOPE_SUBTREE, '(uid=%(user)s)'
+ )
+ )
+
+ user = self.backend.authenticate(username='alice*',
password='password')
+
+ self.assertEqual(user, None)
+ self.assertEqual(
+ self.ldapobj.methods_called(with_args=True),
+ [('initialize', ('ldap://localhost',), {}),
+ ('simple_bind_s', ('', ''), {}),
+ ('search_s', ('ou=people,o=test', ldap.SCOPE_SUBTREE,
'(uid=alice\\2a)'), {})]
+ )
+
def test_search_bind_no_user(self):
self._init_settings(
USER_SEARCH=LDAPSearch(
@@ -559,12 +637,14 @@
def handle_populate_user(sender, **kwargs):
self.assertTrue('user' in kwargs and 'ldap_user' in kwargs)
kwargs['user'].populate_user_handled = True
- backend.populate_user.connect(handle_populate_user)
+ backend.populate_user.connect(handle_populate_user)
user = self.backend.authenticate(username='alice', password='password')
self.assertTrue(user.populate_user_handled)
+ backend.populate_user.disconnect(handle_populate_user)
+
@unittest.skipIf(django.VERSION >= (1, 7), "Skip profile tests in
Django>=1.7")
def test_signal_populate_user_profile(self):
settings.AUTH_PROFILE_MODULE = 'django_auth_ldap.TestProfile'
@@ -583,11 +663,31 @@
django.db.models.signals.post_save.connect(handle_user_saved,
sender=User)
backend.populate_user_profile.connect(handle_populate_user_profile)
-
user = self.backend.authenticate(username='alice', password='password')
self.assertTrue(user.get_profile().populated)
+ backend.populate_user_profile.disconnect(handle_populate_user_profile)
+ django.db.models.signals.post_save.disconnect(handle_user_saved,
sender=User)
+
+ def test_signal_ldap_error(self):
+ self._init_settings(
+ BIND_DN='uid=bob,ou=people,o=test',
+ BIND_PASSWORD='bogus',
+ USER_SEARCH=LDAPSearch(
+ "ou=people,o=test", ldap.SCOPE_SUBTREE, '(uid=%(user)s)'
+ )
+ )
+
+ def handle_ldap_error(sender, **kwargs):
+ self.assertEqual(kwargs['context'], 'authenticate')
+ raise kwargs['exception']
+
+ backend.ldap_error.connect(handle_ldap_error)
+ with self.assertRaises(ldap.LDAPError):
+ self.backend.authenticate(username='alice', password='password')
+ backend.ldap_error.disconnect(handle_ldap_error)
+
def test_no_update_existing(self):
self._init_settings(
USER_DN_TEMPLATE='uid=%(user)s,ou=people,o=test',
@@ -743,6 +843,28 @@
self.assertTrue(not bob.is_staff)
self.assertTrue(not bob.is_superuser)
+ def test_nis_membership(self):
+ self._init_settings(
+ USER_DN_TEMPLATE='uid=%(user)s,ou=people,o=test',
+ GROUP_SEARCH=LDAPSearch('ou=groups,o=test', ldap.SCOPE_SUBTREE),
+ GROUP_TYPE=NISGroupType(),
+ USER_FLAGS_BY_GROUP={
+ 'is_active': "cn=active_nis,ou=groups,o=test",
+ 'is_staff': "cn=staff_nis,ou=groups,o=test",
+ 'is_superuser': "cn=superuser_nis,ou=groups,o=test"
+ }
+ )
+
+ alice = self.backend.authenticate(username='alice',
password='password')
+ bob = self.backend.authenticate(username='bob', password='password')
+
+ self.assertTrue(alice.is_active)
+ self.assertTrue(alice.is_staff)
+ self.assertTrue(alice.is_superuser)
+ self.assertTrue(not bob.is_active)
+ self.assertTrue(not bob.is_staff)
+ self.assertTrue(not bob.is_superuser)
+
def test_nested_dn_group_membership(self):
self._init_settings(
USER_DN_TEMPLATE='uid=%(user)s,ou=people,o=test',
@@ -872,6 +994,24 @@
self.assertTrue(self.backend.has_perm(alice, "auth.add_user"))
self.assertTrue(self.backend.has_module_perms(alice, "auth"))
+ def test_nis_group_permissions(self):
+ self._init_settings(
+ USER_DN_TEMPLATE='uid=%(user)s,ou=people,o=test',
+ GROUP_SEARCH=LDAPSearch('ou=groups,o=test', ldap.SCOPE_SUBTREE,
+ '(objectClass=nisNetgroup)'),
+ GROUP_TYPE=NISGroupType(),
+ FIND_GROUP_PERMS=True
+ )
+ self._init_groups()
+
+ alice = User.objects.create(username='alice')
+ alice = self.backend.get_user(alice.pk)
+
+ self.assertEqual(self.backend.get_group_permissions(alice),
set(["auth.add_user", "auth.change_user"]))
+ self.assertEqual(self.backend.get_all_permissions(alice),
set(["auth.add_user", "auth.change_user"]))
+ self.assertTrue(self.backend.has_perm(alice, "auth.add_user"))
+ self.assertTrue(self.backend.has_module_perms(alice, "auth"))
+
def test_foreign_user_permissions(self):
self._init_settings(
USER_DN_TEMPLATE='uid=%(user)s,ou=people,o=test',
@@ -964,6 +1104,22 @@
self.assertEqual(self.backend.get_group_permissions(alice),
set(["auth.add_user", "auth.change_user"]))
+ def test_authorize_external_unknown(self):
+ self._init_settings(
+ USER_SEARCH=LDAPSearch(
+ "ou=people,o=test", ldap.SCOPE_SUBTREE, '(uid=%(user)s)'
+ ),
+ GROUP_SEARCH=LDAPSearch('ou=groups,o=test', ldap.SCOPE_SUBTREE),
+ GROUP_TYPE=MemberDNGroupType(member_attr='member'),
+ FIND_GROUP_PERMS=True,
+ AUTHORIZE_ALL_USERS=True
+ )
+ self._init_groups()
+
+ alice = User.objects.create(username='not-in-ldap')
+
+ self.assertEqual(self.backend.get_group_permissions(alice), set())
+
def test_create_without_auth(self):
self._init_settings(
USER_DN_TEMPLATE='uid=%(user)s,ou=people,o=test',
@@ -1120,6 +1276,10 @@
self.assertTrue(self.backend.has_perm(alice, "auth.add_user"))
self.assertTrue(self.backend.has_module_perms(alice, "auth"))
+ #
+ # Utilities
+ #
+
def _init_settings(self, **kwargs):
self.backend.settings = TestSettings(**kwargs)
@@ -1135,6 +1295,5 @@
active_px = Group.objects.create(name='active_px')
active_px.permissions.add(*permissions)
-
-# Python 2.5-compatible class decoration
-LDAPTest = unittest.skipIf(mockldap is None, "django_auth_ldap tests require
the mockldap package.")(LDAPTest)
+ active_nis = Group.objects.create(name='active_nis')
+ active_nis.permissions.add(*permissions)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/django-auth-ldap-1.2.2/django_auth_ldap.egg-info/PKG-INFO
new/django-auth-ldap-1.2.6/django_auth_ldap.egg-info/PKG-INFO
--- old/django-auth-ldap-1.2.2/django_auth_ldap.egg-info/PKG-INFO
2014-09-22 17:42:03.000000000 +0200
+++ new/django-auth-ldap-1.2.6/django_auth_ldap.egg-info/PKG-INFO
2015-03-29 20:25:33.000000000 +0200
@@ -1,6 +1,6 @@
Metadata-Version: 1.1
Name: django-auth-ldap
-Version: 1.2.2
+Version: 1.2.6
Summary: Django LDAP authentication backend
Home-page: http://bitbucket.org/psagers/django-auth-ldap/
Author: Peter Sagerson
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/django-auth-ldap-1.2.2/django_auth_ldap.egg-info/SOURCES.txt
new/django-auth-ldap-1.2.6/django_auth_ldap.egg-info/SOURCES.txt
--- old/django-auth-ldap-1.2.2/django_auth_ldap.egg-info/SOURCES.txt
2014-09-22 17:42:05.000000000 +0200
+++ new/django-auth-ldap-1.2.6/django_auth_ldap.egg-info/SOURCES.txt
2015-03-29 20:25:36.000000000 +0200
@@ -61,7 +61,6 @@
docs/source/permissions.rst
docs/source/reference.rst
docs/source/users.rst
-test/.coverage
test/.coveragerc
test/manage.py
test/settings.py
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/django-auth-ldap-1.2.2/docs/source/authentication.rst
new/django-auth-ldap-1.2.6/docs/source/authentication.rst
--- old/django-auth-ldap-1.2.2/docs/source/authentication.rst 2013-11-18
00:00:43.000000000 +0100
+++ new/django-auth-ldap-1.2.6/docs/source/authentication.rst 2015-03-29
19:54:59.000000000 +0200
@@ -133,3 +133,10 @@
enable StartTLS, set :setting:`AUTH_LDAP_START_TLS` to ``True``::
AUTH_LDAP_START_TLS = True
+
+If :class:`~django_auth_ldap.backend.LDAPBackend` receives an
+:exc:`~ldap.LDAPError` from python_ldap, it will normally swallow it and log a
+warning. If you'd like to perform any special handling for these exceptions,
you
+can add a signal handler to :data:`django_auth_ldap.backend.ldap_error`. The
+signal handler can handle the exception any way you like, including re-raising
+it or any other exception.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/django-auth-ldap-1.2.2/docs/source/conf.py
new/django-auth-ldap-1.2.6/docs/source/conf.py
--- old/django-auth-ldap-1.2.2/docs/source/conf.py 2014-09-22
17:35:23.000000000 +0200
+++ new/django-auth-ldap-1.2.6/docs/source/conf.py 2015-03-29
20:24:09.000000000 +0200
@@ -59,7 +59,7 @@
# The short X.Y version.
version = '1.1'
# The full version, including alpha/beta/rc tags.
-release = '1.2.2'
+release = '1.2.6'
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/django-auth-ldap-1.2.2/docs/source/groups.rst
new/django-auth-ldap-1.2.6/docs/source/groups.rst
--- old/django-auth-ldap-1.2.2/docs/source/groups.rst 2013-11-17
23:59:53.000000000 +0100
+++ new/django-auth-ldap-1.2.6/docs/source/groups.rst 2014-12-28
19:50:24.000000000 +0100
@@ -9,18 +9,20 @@
any kind of group and includes implementations for the most common ones.
:class:`~django_auth_ldap.config.LDAPGroupType` is a base class whose concrete
subclasses can determine group membership for particular grouping mechanisms.
-Three built-in subclasses cover most grouping mechanisms:
+Four built-in subclasses cover most grouping mechanisms:
* :class:`~django_auth_ldap.config.PosixGroupType`
+ * :class:`~django_auth_ldap.config.NISGroupType`
* :class:`~django_auth_ldap.config.MemberDNGroupType`
* :class:`~django_auth_ldap.config.NestedMemberDNGroupType`
-posixGroup objects are somewhat specialized, so they get their own class. The
-other two cover mechanisms whereby a group object stores a list of its members
-as distinguished names. This includes groupOfNames, groupOfUniqueNames, and
-Active Directory groups, among others. The nested variant allows groups to
-contain other groups, to as many levels as you like. For convenience and
-readability, several trivial subclasses of the above are provided:
+posixGroup and nisNetgroup objects are somewhat specialized, so they get their
+own classes. The other two cover mechanisms whereby a group object stores a
list
+of its members as distinguished names. This includes groupOfNames,
+groupOfUniqueNames, and Active Directory groups, among others. The nested
+variant allows groups to contain other groups, to as many levels as you like.
+For convenience and readability, several trivial subclasses of the above are
+provided:
* :class:`~django_auth_ldap.config.GroupOfNamesType`
* :class:`~django_auth_ldap.config.NestedGroupOfNamesType`
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/django-auth-ldap-1.2.2/docs/source/reference.rst
new/django-auth-ldap-1.2.6/docs/source/reference.rst
--- old/django-auth-ldap-1.2.2/docs/source/reference.rst 2013-11-18
00:00:43.000000000 +0100
+++ new/django-auth-ldap-1.2.6/docs/source/reference.rst 2015-03-29
20:12:17.000000000 +0200
@@ -358,7 +358,6 @@
first value of the cn attribute. You can specify a different attribute
with ``name_attr``.
-
.. class:: PosixGroupType
A concrete subclass of :class:`~django_auth_ldap.config.LDAPGroupType` that
@@ -367,6 +366,13 @@
.. method:: __init__(name_attr='cn')
+.. class:: NISGroupType
+
+ A concrete subclass of :class:`~django_auth_ldap.config.LDAPGroupType` that
+ handles the ``nisNetgroup`` object class.
+
+ .. method:: __init__(name_attr='cn')
+
.. class:: MemberDNGroupType
A concrete subclass of
@@ -493,6 +499,16 @@
``ldap_user`` is the same as ``user.ldap_user``. The sender is the
:class:`~django_auth_ldap.backend.LDAPBackend` class.
+.. data:: ldap_error
+
+ This is a Django signal that is sent when we receive an
+ :exc:`ldap.LDAPError` exception. The signal has two keyword arguments:
+ ``'context'`` is one of ``'authenticate'``, ``'get_group_permissions'``, or
+ ``'populate_user'``, indicating which API was being called when the
+ exception was caught; and ``'exception'`` is the :exc:`~ldap.LDAPError`
+ object itself. The sender is the
+ :class:`~django_auth_ldap.backend.LDAPBackend` class.
+
.. class:: LDAPBackend
:class:`~django_auth_ldap.backend.LDAPBackend` has one method that may be
@@ -501,10 +517,17 @@
.. data:: settings_prefix
A prefix for all of our Django settings. By default, this is
- ``"AUTH_LDAP_"``, but subclasses can override this. When different
+ ``'AUTH_LDAP_'``, but subclasses can override this. When different
subclasses use different prefixes, they can both be installed and
operate independently.
+ .. data:: default_settings
+
+ A dictionary of default settings. This is empty in
+ :class:`~django_auth_ldap.backend.LDAPBackend`, but subclasses can
+ populate this with values that will override the built-in defaults.
Note
+ that the keys should omit the ``'AUTH_LDAP_'`` prefix.
+
.. method:: populate_user(username)
Populates the Django user for the given LDAP username. This connects to
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/django-auth-ldap-1.2.2/setup.py
new/django-auth-ldap-1.2.6/setup.py
--- old/django-auth-ldap-1.2.2/setup.py 2014-09-22 17:35:11.000000000 +0200
+++ new/django-auth-ldap-1.2.6/setup.py 2015-03-29 20:23:58.000000000 +0200
@@ -4,7 +4,7 @@
setup(
name="django-auth-ldap",
- version="1.2.2",
+ version="1.2.6",
description="Django LDAP authentication backend",
long_description=open('README').read(),
url="http://bitbucket.org/psagers/django-auth-ldap/",
Files old/django-auth-ldap-1.2.2/test/.coverage and
new/django-auth-ldap-1.2.6/test/.coverage differ