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


Reply via email to