[Python-modules-commits] [django-auth-ldap] 01/08: Import django-auth-ldap_1.2.13+dfsg.orig.tar.gz

Michael Fladischer fladi at moszumanska.debian.org
Thu Jul 6 20:41:27 UTC 2017


This is an automated email from the git hooks/post-receive script.

fladi pushed a commit to branch master
in repository django-auth-ldap.

commit 058058d4997149c3ae278a2ff7812814d62c02d2
Author: Michael Fladischer <FladischerMichael at fladi.at>
Date:   Thu Jul 6 21:00:38 2017 +0200

    Import django-auth-ldap_1.2.13+dfsg.orig.tar.gz
---
 CHANGES                                |  11 +++
 PKG-INFO                               |   4 +-
 django_auth_ldap.egg-info/PKG-INFO     |   4 +-
 django_auth_ldap.egg-info/SOURCES.txt  |   1 +
 django_auth_ldap.egg-info/requires.txt |   2 +-
 django_auth_ldap/__init__.py           |   2 +-
 django_auth_ldap/backend.py            |  84 +++++++++++++++++--
 django_auth_ldap/config.py             |   4 +
 django_auth_ldap/tests.py              | 149 +++++++++++++++++++++++++++++++--
 docs/source/.conf.py.swp               | Bin 0 -> 16384 bytes
 docs/source/conf.py                    |   2 +-
 docs/source/permissions.rst            |  39 +++++----
 docs/source/reference.rst              |  26 +++++-
 setup.py                               |   4 +-
 test/settings.py                       |   5 ++
 tox.ini                                |   6 +-
 16 files changed, 292 insertions(+), 51 deletions(-)

diff --git a/CHANGES b/CHANGES
index db447f4..bcbb03c 100644
--- a/CHANGES
+++ b/CHANGES
@@ -1,3 +1,14 @@
+v1.2.13 - 2017-06-19 - Selective group mirroring
+------------------------------------------------
+
+- Support selective group mirroring with :setting:`AUTH_LDAP_MIRROR_GROUPS` and
+  :setting:`AUTH_LDAP_MIRROR_GROUPS_EXCEPT`.
+
+- Fix `#73`_: Work around Django 1.11 bug with multiple authentication backends.
+
+.. _#73: https://bitbucket.org/psagers/django-auth-ldap/issues/73/
+
+
 v1.2.12 - 2017-05-20 - Complex group queries
 --------------------------------------------
 
diff --git a/PKG-INFO b/PKG-INFO
index 8f0b616..30b644a 100644
--- a/PKG-INFO
+++ b/PKG-INFO
@@ -1,6 +1,6 @@
 Metadata-Version: 1.1
 Name: django-auth-ldap
-Version: 1.2.12
+Version: 1.2.13
 Summary: Django LDAP authentication backend
 Home-page: http://bitbucket.org/psagers/django-auth-ldap/
 Author: Peter Sagerson
@@ -78,7 +78,6 @@ Classifier: Development Status :: 5 - Production/Stable
 Classifier: Environment :: Web Environment
 Classifier: Programming Language :: Python
 Classifier: Programming Language :: Python :: 2
-Classifier: Programming Language :: Python :: 2.6
 Classifier: Programming Language :: Python :: 2.7
 Classifier: Programming Language :: Python :: 3
 Classifier: Programming Language :: Python :: 3.3
@@ -86,7 +85,6 @@ Classifier: Programming Language :: Python :: 3.4
 Classifier: Programming Language :: Python :: 3.5
 Classifier: Programming Language :: Python :: 3.6
 Classifier: Framework :: Django
-Classifier: Framework :: Django :: 1.4
 Classifier: Framework :: Django :: 1.5
 Classifier: Framework :: Django :: 1.6
 Classifier: Framework :: Django :: 1.7
diff --git a/django_auth_ldap.egg-info/PKG-INFO b/django_auth_ldap.egg-info/PKG-INFO
index 8f0b616..30b644a 100644
--- a/django_auth_ldap.egg-info/PKG-INFO
+++ b/django_auth_ldap.egg-info/PKG-INFO
@@ -1,6 +1,6 @@
 Metadata-Version: 1.1
 Name: django-auth-ldap
-Version: 1.2.12
+Version: 1.2.13
 Summary: Django LDAP authentication backend
 Home-page: http://bitbucket.org/psagers/django-auth-ldap/
 Author: Peter Sagerson
@@ -78,7 +78,6 @@ Classifier: Development Status :: 5 - Production/Stable
 Classifier: Environment :: Web Environment
 Classifier: Programming Language :: Python
 Classifier: Programming Language :: Python :: 2
-Classifier: Programming Language :: Python :: 2.6
 Classifier: Programming Language :: Python :: 2.7
 Classifier: Programming Language :: Python :: 3
 Classifier: Programming Language :: Python :: 3.3
@@ -86,7 +85,6 @@ Classifier: Programming Language :: Python :: 3.4
 Classifier: Programming Language :: Python :: 3.5
 Classifier: Programming Language :: Python :: 3.6
 Classifier: Framework :: Django
-Classifier: Framework :: Django :: 1.4
 Classifier: Framework :: Django :: 1.5
 Classifier: Framework :: Django :: 1.6
 Classifier: Framework :: Django :: 1.7
diff --git a/django_auth_ldap.egg-info/SOURCES.txt b/django_auth_ldap.egg-info/SOURCES.txt
index a3bec28..3c08f18 100644
--- a/django_auth_ldap.egg-info/SOURCES.txt
+++ b/django_auth_ldap.egg-info/SOURCES.txt
@@ -46,6 +46,7 @@ docs/archive/versions/1.0.19/_static/up-pressed.png
 docs/archive/versions/1.0.19/_static/up.png
 docs/archive/versions/1.0.19/_static/websupport.js
 docs/ext/daldocs.py
+docs/source/.conf.py.swp
 docs/source/.spell.utf-8.add
 docs/source/.spell.utf-8.add.spl
 docs/source/authentication.rst
diff --git a/django_auth_ldap.egg-info/requires.txt b/django_auth_ldap.egg-info/requires.txt
index 6d5787b..8f1fe2c 100644
--- a/django_auth_ldap.egg-info/requires.txt
+++ b/django_auth_ldap.egg-info/requires.txt
@@ -1,2 +1,2 @@
 django
-python-ldap >= 2.0
+pyldap
diff --git a/django_auth_ldap/__init__.py b/django_auth_ldap/__init__.py
index 4bdfabd..b426daa 100644
--- a/django_auth_ldap/__init__.py
+++ b/django_auth_ldap/__init__.py
@@ -1,2 +1,2 @@
-version = (1, 2, 12)
+version = (1, 2, 13)
 version_string = '.'.join(map(str, version))
diff --git a/django_auth_ldap/backend.py b/django_auth_ldap/backend.py
index 4c6d698..70f2e76 100644
--- a/django_auth_ldap/backend.py
+++ b/django_auth_ldap/backend.py
@@ -52,6 +52,7 @@ import operator
 import pprint
 import sys
 import traceback
+import warnings
 
 from django.contrib.auth.models import User, Group, Permission
 import django.conf
@@ -88,7 +89,7 @@ try:
 except NameError:
     basestring = str
 
-from django_auth_ldap.config import _LDAPConfig, LDAPGroupQuery, LDAPSearch
+from django_auth_ldap.config import ConfigurationWarning, _LDAPConfig, LDAPGroupQuery, LDAPSearch
 
 
 logger = _LDAPConfig.get_logger()
@@ -165,7 +166,7 @@ class LDAPBackend(object):
     # The Django auth backend API
     #
 
-    def authenticate(self, username, password, **kwargs):
+    def authenticate(self, request=None, username=None, password=None, **kwargs):
         if bool(password) or self.settings.PERMIT_EMPTY_PASSWORD:
             ldap_user = _LDAPUser(self, username=username.strip())
             user = ldap_user.authenticate(password)
@@ -570,7 +571,8 @@ class _LDAPUser(object):
             self._populate_user()
             save_user = True
 
-        if self.settings.MIRROR_GROUPS:
+        if bool(self.settings.MIRROR_GROUPS) or bool(self.settings.MIRROR_GROUPS_EXCEPT):
+            self._normalize_mirror_settings()
             self._mirror_groups()
 
         # Give the client a chance to finish populating the user just before
@@ -696,6 +698,55 @@ class _LDAPUser(object):
 
         return query
 
+    def _normalize_mirror_settings(self):
+        """
+        Validates the group mirroring settings and converts them as necessary.
+        """
+        def malformed_mirror_groups_except():
+            return ImproperlyConfigured(
+                "{} must be a collection of group names".format(
+                    self.settings._name('MIRROR_GROUPS_EXCEPT')
+                )
+            )
+
+        def malformed_mirror_groups():
+            return ImproperlyConfigured(
+                "{} must be True or a collection of group names".format(
+                    self.settings._name('MIRROR_GROUPS')
+                )
+            )
+
+        mge = self.settings.MIRROR_GROUPS_EXCEPT
+        mg = self.settings.MIRROR_GROUPS
+
+        if mge is not None:
+            if isinstance(mge, (set, frozenset)):
+                pass
+            elif isinstance(mge, (list, tuple)):
+                mge = self.settings.MIRROR_GROUPS_EXCEPT = frozenset(mge)
+            else:
+                raise malformed_mirror_groups_except()
+
+            if not all(isinstance(value, basestring) for value in mge):
+                raise malformed_mirror_groups_except()
+            elif bool(mg):
+                warnings.warn(ConfigurationWarning("Ignoring {} in favor of {}".format(
+                    self.settings._name("MIRROR_GROUPS"),
+                    self.settings._name("MIRROR_GROUPS_EXCEPT")
+                )))
+                mg = self.settings.MIRROR_GROUPS = None
+
+        if mg is not None:
+            if isinstance(mg, (bool, set, frozenset)):
+                pass
+            elif isinstance(mg, (list, tuple)):
+                mg = self.settings.MIRROR_GROUPS = frozenset(mg)
+            else:
+                raise malformed_mirror_groups()
+
+            if isinstance(mg, (set, frozenset)) and (not all(isinstance(value, basestring) for value in mg)):
+                raise malformed_mirror_groups()
+
     def _mirror_groups(self):
         """
         Mirrors the user's LDAP groups in the Django database and updates the
@@ -704,6 +755,24 @@ class _LDAPUser(object):
         target_group_names = frozenset(self._get_groups().get_group_names())
         current_group_names = frozenset(self._user.groups.values_list('name', flat=True).iterator())
 
+        # These were normalized to sets above.
+        MIRROR_GROUPS_EXCEPT = self.settings.MIRROR_GROUPS_EXCEPT
+        MIRROR_GROUPS = self.settings.MIRROR_GROUPS
+
+        # If the settings are white- or black-listing groups, we'll update
+        # target_group_names such that we won't modify the membership of groups
+        # beyond our purview.
+        if isinstance(MIRROR_GROUPS_EXCEPT, (set, frozenset)):
+            target_group_names = (
+                (target_group_names - MIRROR_GROUPS_EXCEPT) |
+                (current_group_names & MIRROR_GROUPS_EXCEPT)
+            )
+        elif isinstance(MIRROR_GROUPS, (set, frozenset)):
+            target_group_names = (
+                (target_group_names & MIRROR_GROUPS) |
+                (current_group_names - MIRROR_GROUPS)
+            )
+
         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)
@@ -804,8 +873,10 @@ class _LDAPUserGroups(object):
 
     def _init_group_settings(self):
         """
-        Loads the settings we need to deal with groups. Raises
-        ImproperlyConfigured if anything's not right.
+        Loads the settings we need to deal with groups.
+
+        Raises ImproperlyConfigured if anything's not right.
+
         """
         self._group_type = self.settings.GROUP_TYPE
         if self._group_type is None:
@@ -920,7 +991,8 @@ class LDAPSettings(object):
         'GROUP_CACHE_TIMEOUT': None,
         'GROUP_SEARCH': None,
         'GROUP_TYPE': None,
-        'MIRROR_GROUPS': False,
+        'MIRROR_GROUPS': None,
+        'MIRROR_GROUPS_EXCEPT': None,
         'PERMIT_EMPTY_PASSWORD': False,
         'PROFILE_ATTR_MAP': {},
         'PROFILE_FLAGS_BY_GROUP': {},
diff --git a/django_auth_ldap/config.py b/django_auth_ldap/config.py
index 58c612e..b38e5fa 100644
--- a/django_auth_ldap/config.py
+++ b/django_auth_ldap/config.py
@@ -41,6 +41,10 @@ except ImportError:  # Django < 1.5
     from django.utils.encoding import smart_str as force_str
 
 
+class ConfigurationWarning(UserWarning):
+    pass
+
+
 class _LDAPConfig(object):
     """
     A private class that loads and caches some global objects.
diff --git a/django_auth_ldap/tests.py b/django_auth_ldap/tests.py
index 8661808..6b877b5 100644
--- a/django_auth_ldap/tests.py
+++ b/django_auth_ldap/tests.py
@@ -84,6 +84,7 @@ class LDAPTest(TestCase):
     people = ("ou=people,o=test", {"ou": "people"})
     groups = ("ou=groups,o=test", {"ou": "groups"})
     moregroups = ("ou=moregroups,o=test", {"ou": "moregroups"})
+    mirror_groups = ("ou=mirror_groups,o=test", {"ou": "mirror_groups"})
 
     alice = ("uid=alice,ou=people,o=test", {
         "uid": ["alice"],
@@ -166,7 +167,7 @@ class LDAPTest(TestCase):
         "member": ["uid=bob,ou=people,o=test"]
     })
 
-    # grouOfNames objects for LDAPGroupQuery testing
+    # groupOfNames objects for LDAPGroupQuery testing
     alice_gon = ("cn=alice_gon,ou=query_groups,o=test", {
         "cn": ["alice_gon"],
         "objectClass": ["groupOfNames"],
@@ -183,6 +184,28 @@ class LDAPTest(TestCase):
         "member": ["uid=bob,ou=people,o=test"]
     })
 
+    # groupOfNames objects for selective group mirroring.
+    mirror1 = ("cn=mirror1,ou=mirror_groups,o=test", {
+        "cn": ["mirror1"],
+        "objectClass": ["groupOfNames"],
+        "member": ["uid=alice,ou=people,o=test"]
+    })
+    mirror2 = ("cn=mirror2,ou=mirror_groups,o=test", {
+        "cn": ["mirror2"],
+        "objectClass": ["groupOfNames"],
+        "member": []
+    })
+    mirror3 = ("cn=mirror3,ou=mirror_groups,o=test", {
+        "cn": ["mirror3"],
+        "objectClass": ["groupOfNames"],
+        "member": ["uid=alice,ou=people,o=test"]
+    })
+    mirror4 = ("cn=mirror4,ou=mirror_groups,o=test", {
+        "cn": ["mirror4"],
+        "objectClass": ["groupOfNames"],
+        "member": []
+    })
+
     # nisGroup objects
     active_nis = ("cn=active_nis,ou=groups,o=test", {
         "cn": ["active_nis"],
@@ -220,12 +243,13 @@ class LDAPTest(TestCase):
         "member": ["cn=parent_gon,ou=groups,o=test"]
     })
 
-    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,
-                      alice_gon, mutual_gon, bob_gon,
-                      active_nis, staff_nis, superuser_nis,
-                      parent_gon, nested_gon, circular_gon])
+    directory = dict([
+        top, people, groups, moregroups, mirror_groups, alice, bob, dressler,
+        nobody, active_px, staff_px, superuser_px, empty_gon, active_gon,
+        staff_gon, superuser_gon, other_gon, alice_gon, mutual_gon, bob_gon,
+        mirror1, mirror2, mirror3, mirror4, active_nis, staff_nis,
+        superuser_nis, parent_gon, nested_gon, circular_gon
+    ])
 
     @classmethod
     def configure_logger(cls):
@@ -326,6 +350,16 @@ class LDAPTest(TestCase):
             ['initialize', 'simple_bind_s']
         )
 
+    @override_settings(AUTH_LDAP_USER_SEARCH=LDAPSearch("ou=people,o=test", ldap.SCOPE_SUBTREE, '(uid=%(user)s)'))
+    def test_login_with_multiple_auth_backends(self):
+        auth = self.client.login(username='alice', password='password')
+        self.assertTrue(auth)
+
+    @override_settings(AUTH_LDAP_USER_SEARCH=LDAPSearch("ou=people,o=test", ldap.SCOPE_SUBTREE, '(uid=%(user)s)'))
+    def test_bad_login_with_multiple_auth_backends(self):
+        auth = self.client.login(username='invalid', password='i_do_not_exist')
+        self.assertFalse(auth)
+
     def test_simple_bind_escaped(self):
         """ Bind with a username that requires escaping. """
         self._init_settings(
@@ -639,7 +673,7 @@ class LDAPTest(TestCase):
             ['initialize', 'simple_bind_s', 'simple_bind_s', 'search_s']
         )
 
-    def test_poplate_with_attrlist(self):
+    def test_populate_with_attrlist(self):
         self._init_settings(
             USER_DN_TEMPLATE='uid=%(user)s,ou=people,o=test',
             USER_ATTR_MAP={'first_name': 'givenName', 'last_name': 'sn'},
@@ -1249,6 +1283,105 @@ class LDAPTest(TestCase):
         )
         self.assertEqual(set(alice.groups.all()), set(Group.objects.all()))
 
+    #
+    # When selectively mirroring groups, there are eight scenarios for any
+    # given user/group pair:
+    #
+    #   (is-member-in-LDAP, not-member-in-LDAP)
+    #   x (is-member-in-Django, not-member-in-Django)
+    #   x (synced, not-synced)
+    #
+    # The four test cases below take these scenarios four at a time for each of
+    # the two settings.
+
+    def test_group_mirroring_whitelist_update(self):
+        self._init_settings(
+            USER_DN_TEMPLATE='uid=%(user)s,ou=people,o=test',
+            GROUP_SEARCH=LDAPSearch('ou=mirror_groups,o=test', ldap.SCOPE_SUBTREE,
+                                    '(objectClass=groupOfNames)'),
+            GROUP_TYPE=GroupOfNamesType(),
+            MIRROR_GROUPS=['mirror1', 'mirror2']
+        )
+
+        groups = {}
+        for name in ('mirror{}'.format(i) for i in range(1, 5)):
+            groups[name] = Group.objects.create(name=name)
+        alice = self.backend.populate_user('alice')
+        alice.groups = [groups['mirror2'], groups['mirror4']]
+
+        alice = self.backend.authenticate(username='alice', password='password')
+
+        self.assertEqual(
+            set(alice.groups.values_list("name", flat=True)),
+            {'mirror1', 'mirror4'}
+        )
+
+    def test_group_mirroring_whitelist_noop(self):
+        self._init_settings(
+            USER_DN_TEMPLATE='uid=%(user)s,ou=people,o=test',
+            GROUP_SEARCH=LDAPSearch('ou=mirror_groups,o=test', ldap.SCOPE_SUBTREE,
+                                    '(objectClass=groupOfNames)'),
+            GROUP_TYPE=GroupOfNamesType(),
+            MIRROR_GROUPS=['mirror1', 'mirror2']
+        )
+
+        groups = {}
+        for name in ('mirror{}'.format(i) for i in range(1, 5)):
+            groups[name] = Group.objects.create(name=name)
+        alice = self.backend.populate_user('alice')
+        alice.groups = [groups['mirror1'], groups['mirror3']]
+
+        alice = self.backend.authenticate(username='alice', password='password')
+
+        self.assertEqual(
+            set(alice.groups.values_list("name", flat=True)),
+            {'mirror1', 'mirror3'}
+        )
+
+    def test_group_mirroring_blacklist_update(self):
+        self._init_settings(
+            USER_DN_TEMPLATE='uid=%(user)s,ou=people,o=test',
+            GROUP_SEARCH=LDAPSearch('ou=mirror_groups,o=test', ldap.SCOPE_SUBTREE,
+                                    '(objectClass=groupOfNames)'),
+            GROUP_TYPE=GroupOfNamesType(),
+            MIRROR_GROUPS_EXCEPT=['mirror1', 'mirror2']
+        )
+
+        groups = {}
+        for name in ('mirror{}'.format(i) for i in range(1, 5)):
+            groups[name] = Group.objects.create(name=name)
+        alice = self.backend.populate_user('alice')
+        alice.groups = [groups['mirror2'], groups['mirror4']]
+
+        alice = self.backend.authenticate(username='alice', password='password')
+
+        self.assertEqual(
+            set(alice.groups.values_list("name", flat=True)),
+            {'mirror2', 'mirror3'}
+        )
+
+    def test_group_mirroring_blacklist_noop(self):
+        self._init_settings(
+            USER_DN_TEMPLATE='uid=%(user)s,ou=people,o=test',
+            GROUP_SEARCH=LDAPSearch('ou=mirror_groups,o=test', ldap.SCOPE_SUBTREE,
+                                    '(objectClass=groupOfNames)'),
+            GROUP_TYPE=GroupOfNamesType(),
+            MIRROR_GROUPS_EXCEPT=['mirror1', 'mirror2']
+        )
+
+        groups = {}
+        for name in ('mirror{}'.format(i) for i in range(1, 5)):
+            groups[name] = Group.objects.create(name=name)
+        alice = self.backend.populate_user('alice')
+        alice.groups = [groups['mirror1'], groups['mirror3']]
+
+        alice = self.backend.authenticate(username='alice', password='password')
+
+        self.assertEqual(
+            set(alice.groups.values_list("name", flat=True)),
+            {'mirror1', 'mirror3'}
+        )
+
     def test_authorize_external_users(self):
         self._init_settings(
             USER_DN_TEMPLATE='uid=%(user)s,ou=people,o=test',
diff --git a/docs/source/.conf.py.swp b/docs/source/.conf.py.swp
new file mode 100644
index 0000000..6b50423
Binary files /dev/null and b/docs/source/.conf.py.swp differ
diff --git a/docs/source/conf.py b/docs/source/conf.py
index e0ac649..fd0d8bc 100644
--- a/docs/source/conf.py
+++ b/docs/source/conf.py
@@ -59,7 +59,7 @@ copyright = u'2009, Peter Sagerson'
 # The short X.Y version.
 version = '1.1'
 # The full version, including alpha/beta/rc tags.
-release = '1.2.12'
+release = '1.2.13'
 
 # The language for content autogenerated by Sphinx. Refer to documentation
 # for a list of supported languages.
diff --git a/docs/source/permissions.rst b/docs/source/permissions.rst
index 9a15103..1146c6d 100644
--- a/docs/source/permissions.rst
+++ b/docs/source/permissions.rst
@@ -43,22 +43,29 @@ Group Mirroring
 ---------------
 
 The second way to turn LDAP group memberships into permissions is to mirror the
-groups themselves. If :setting:`AUTH_LDAP_MIRROR_GROUPS` is ``True``, then every
-time a user logs in, :class:`~django_auth_ldap.backend.LDAPBackend` will update
-the database with the user's LDAP groups. Any group that doesn't exist will be
-created and the user's Django group membership will be updated to exactly match
-his LDAP group membership. Note that if the LDAP server has nested groups, the
-Django database will end up with a flattened representation. For group mirroring
-to have any effect, you of course need
-:class:`~django.contrib.auth.backends.ModelBackend` installed as an
-authentication backend.
-
-The main difference between this approach and
-:setting:`AUTH_LDAP_FIND_GROUP_PERMS` is that
-:setting:`AUTH_LDAP_FIND_GROUP_PERMS` will query for LDAP group membership
-either for every request or according to the cache timeout. With group
-mirroring, membership will be updated when the user authenticates. This may not
-be appropriate for sites with long session timeouts.
+groups themselves. This approach has some important disadvantages and should be
+avoided if possible. For one thing, membership will only be updated when the
+user authenticates, which may be especially inappropriate for sites with long
+session timeouts.
+
+If :setting:`AUTH_LDAP_MIRROR_GROUPS` is ``True``, then every time a user logs
+in, :class:`~django_auth_ldap.backend.LDAPBackend` will update the database with
+the user's LDAP groups. Any group that doesn't exist will be created and the
+user's Django group membership will be updated to exactly match their LDAP group
+membership. If the LDAP server has nested groups, the Django database will end
+up with a flattened representation. For group mirroring to have any effect, you
+of course need :class:`~django.contrib.auth.backends.ModelBackend` installed as
+an authentication backend.
+
+By default, we assume that LDAP is the sole authority on group membership; if
+you remove a user from a group in LDAP, they will be removed from the
+corresponding Django group the next time they log in. It is also possible to
+have django-auth-ldap ignore some Django groups, presumably because they are
+managed manually or through some other mechanism. If
+:setting:`AUTH_LDAP_MIRROR_GROUPS` is a list of group names, we will manage
+these groups and no others. If :setting:`AUTH_LDAP_MIRROR_GROUPS_EXCEPT` is a
+list of group names, we will manage all groups except those named;
+:setting:`AUTH_LDAP_MIRROR_GROUPS` is ignored in this case.
 
 
 Non-LDAP Users
diff --git a/docs/source/reference.rst b/docs/source/reference.rst
index 834f120..84c64de 100644
--- a/docs/source/reference.rst
+++ b/docs/source/reference.rst
@@ -176,13 +176,31 @@ of group returned by :setting:`AUTH_LDAP_GROUP_SEARCH`.
 AUTH_LDAP_MIRROR_GROUPS
 ~~~~~~~~~~~~~~~~~~~~~~~
 
-Default: ``False``
+Default: ``None``
 
 If ``True``, :class:`~django_auth_ldap.backend.LDAPBackend` will mirror a user's
 LDAP group membership in the Django database. Any time a user authenticates, we
-will create all of his LDAP groups as Django groups and update his Django group
-membership to exactly match his LDAP group membership. If the LDAP server has
-nested groups, the Django database will end up with a flattened representation.
+will create all of their LDAP groups as Django groups and update their Django
+group membership to exactly match their LDAP group membership. If the LDAP
+server has nested groups, the Django database will end up with a flattened
+representation.
+
+This can also be a list or other collection of group names, in which case we'll
+only mirror those groups and leave the rest alone. This is ignored if
+:setting:`AUTH_LDAP_MIRROR_GROUPS_EXCEPT` is set.
+
+
+.. setting:: AUTH_LDAP_MIRROR_GROUPS_EXCEPT
+
+AUTH_LDAP_MIRROR_GROUPS_EXCEPT
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Default: ``None``
+
+If this is not ``None``, it must be a list or other collection of group names.
+This will enable group mirroring, except that we'll never change the membership
+of the indicated groups. :setting:`AUTH_LDAP_MIRROR_GROUPS` is ignored in this
+case.
 
 
 .. setting:: AUTH_LDAP_PERMIT_EMPTY_PASSWORD
diff --git a/setup.py b/setup.py
index c32174e..6397eff 100644
--- a/setup.py
+++ b/setup.py
@@ -10,7 +10,7 @@ PY3 = (sys.version_info[0] == 3)
 
 setup(
     name="django-auth-ldap",
-    version="1.2.12",
+    version="1.2.13",
     description="Django LDAP authentication backend",
     long_description=open('README').read(),
     url="http://bitbucket.org/psagers/django-auth-ldap/",
@@ -23,7 +23,6 @@ setup(
         "Environment :: Web Environment",
         "Programming Language :: Python",
         "Programming Language :: Python :: 2",
-        "Programming Language :: Python :: 2.6",
         "Programming Language :: Python :: 2.7",
         "Programming Language :: Python :: 3",
         "Programming Language :: Python :: 3.3",
@@ -31,7 +30,6 @@ setup(
         "Programming Language :: Python :: 3.5",
         "Programming Language :: Python :: 3.6",
         "Framework :: Django",
-        "Framework :: Django :: 1.4",
         "Framework :: Django :: 1.5",
         "Framework :: Django :: 1.6",
         "Framework :: Django :: 1.7",
diff --git a/test/settings.py b/test/settings.py
index 43b5801..0c1dd84 100644
--- a/test/settings.py
+++ b/test/settings.py
@@ -31,3 +31,8 @@ INSTALLED_APPS = (
 MIDDLEWARE_CLASSES = []
 
 AUTH_USER_MODEL = 'auth.User'
+
+AUTHENTICATION_BACKENDS = [
+    'django_auth_ldap.backend.LDAPBackend',
+    'django.contrib.auth.backends.ModelBackend',
+]
diff --git a/tox.ini b/tox.ini
index 5c97066..8a6e543 100644
--- a/tox.ini
+++ b/tox.ini
@@ -1,4 +1,4 @@
-[pep8]
+[pycodestyle]
 max-line-length = 120
 
 [flake8]
@@ -7,7 +7,6 @@ max-line-length = 120
 
 [tox]
 envlist = py36-static,
-          py26-django{13,14,15},
           py27-django15
           py{27,33}-django{15,16},
           py{27,33,34}-django17
@@ -19,8 +18,6 @@ envlist = py36-static,
 changedir = test
 commands = {envpython} manage.py test django_auth_ldap
 deps = mockldap>=0.2.7
-       django13: Django>=1.3,<1.4
-       django14: Django>=1.4,<1.5
        django15: Django>=1.5,<1.6
        django16: Django>=1.6,<1.7
        django17: Django>=1.7,<1.8
@@ -30,7 +27,6 @@ deps = mockldap>=0.2.7
        django111: Django>=1.11,<2.0
 
 basepython =
-    py26: python2.6
     py27: python2.7
     py33: python3.3
     py34: python3.4

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/python-modules/packages/django-auth-ldap.git



More information about the Python-modules-commits mailing list