Peter Sagerson avatar Peter Sagerson committed a79a03c

Fix #39: Support pickling LDAP-authenticated users.

Comments (0)

Files changed (2)

django_auth_ldap/backend.py

     supports_object_permissions = True
     supports_inactive_user = False
 
-    ldap = None # The cached ldap module (or mock object)
+    _settings = None
+    _ldap = None # The cached ldap module (or mock object)
 
     # This is prepended to our internal setting names to produce the names we
     # expect in Django's settings file. Subclasses can change this in order to
     # support multiple collections of settings.
     settings_prefix = 'AUTH_LDAP_'
 
-    def __init__(self):
-        self.settings = LDAPSettings(self.settings_prefix)
-        self.ldap = self.ldap_module()
+    def __getstate__(self):
+        """
+        Exclude certain cached properties from pickling.
+        """
+        state = filter(
+            lambda (k, v): k not in ['_settings', '_ldap'],
+            self.__dict__.iteritems()
+        )
 
-    def ldap_module(self):
-        """
-        Requests the ldap module from _LDAPConfig. Under a test harness, this
-        will be a mock object.
-        """
-        from django.conf import settings
+        return dict(state)
 
-        options = getattr(settings, 'AUTH_LDAP_GLOBAL_OPTIONS', None)
+    def _get_settings(self):
+        if self._settings is None:
+            self._settings = LDAPSettings(self.settings_prefix)
 
-        return _LDAPConfig.get_ldap(options)
+        return self._settings
+
+    def _set_settings(self, settings):
+        self._settings = settings
+
+    settings = property(_get_settings, _set_settings)
+
+    def _get_ldap(self):
+        if self._ldap is None:
+            from django.conf import settings
+
+            options = getattr(settings, 'AUTH_LDAP_GLOBAL_OPTIONS', None)
+
+            self._ldap = _LDAPConfig.get_ldap(options)
+
+        return self._ldap
+    ldap = property(_get_ldap)
 
     #
     # The Django auth backend API
     class AuthenticationFailed(Exception):
         pass
 
+    # Defaults
+    _user = None
+    _user_dn = None
+    _user_attrs = None
+    _groups = None
+    _group_permissions = None
+    _connection = None
+    _connection_bound = False
+
     #
     # Initialization
     #
         ignored.
         """
         self.backend = backend
-        self.ldap = backend.ldap
-        self.settings = backend.settings
         self._username = username
-        self._user_dn = None
-        self._user_attrs = None
-        self._user = None
-        self._groups = None
-        self._group_permissions = None
-        self._connection = None
-        self._connection_bound = False  # True if we're bound as AUTH_LDAP_BIND_*
 
         if user is not None:
             self._set_authenticated_user(user)
     def __deepcopy__(self, memo):
         obj = object.__new__(self.__class__)
         obj.backend = self.backend
-        obj.ldap = self.ldap
         obj._user = copy.deepcopy(self._user, memo)
 
         # This is all just cached immutable data. There's no point copying it.
 
         return obj
 
+    def __getstate__(self):
+        """
+        Most of our properties are cached from the LDAP server. We only want to
+        pickle a few crucial things.
+        """
+        state = filter(
+            lambda (k, v): k in ['backend', '_username', '_user'],
+            self.__dict__.iteritems()
+        )
+
+        return dict(state)
+
     def _set_authenticated_user(self, user):
         self._user = user
         self._username = self.backend.django_to_ldap_username(user.username)
         user.ldap_user = self
         user.ldap_username = self._username
 
+    def _get_ldap(self):
+        return self.backend.ldap
+    ldap = property(_get_ldap)
+
+    def _get_settings(self):
+        return self.backend.settings
+    settings = property(_get_settings)
+
     #
     # Entry points
     #

django_auth_ldap/tests.py

 except NameError:
     from sets import Set as set     # Python 2.3 fallback
 
+from collections import defaultdict
+import logging
+import pickle
 import sys
-import logging
-from collections import defaultdict
 
 from django.conf import settings
 import django.db.models.signals
         self.ldap = _LDAPConfig.ldap = self.mock_ldap
 
         self.backend = backend.LDAPBackend()
-        self.backend.ldap_module() # Force global configuration
+        self.backend.ldap # Force global configuration
 
         self.mock_ldap.reset()
 
         self.assertEqual(self.mock_ldap.ldap_methods_called(),
             ['initialize', 'simple_bind_s'])
 
+    def test_pickle(self):
+        self._init_settings(
+            USER_DN_TEMPLATE='uid=%(user)s,ou=people,o=test',
+            GROUP_SEARCH=LDAPSearch('ou=groups,o=test', self.mock_ldap.SCOPE_SUBTREE),
+            GROUP_TYPE=MemberDNGroupType(member_attr='member'),
+            FIND_GROUP_PERMS=True
+        )
+        self._init_groups()
+        self.mock_ldap.set_return_value('search_s',
+            ("ou=groups,o=test", 2, "(&(objectClass=*)(member=uid=alice,ou=people,o=test))", None, 0),
+            [self.active_gon, self.staff_gon, self.superuser_gon, self.nested_gon]
+        )
+
+        alice0 = self.backend.authenticate(username=u'alice', password=u'password')
+
+        pickled = pickle.dumps(alice0, pickle.HIGHEST_PROTOCOL)
+        alice = pickle.loads(pickled)
+        alice.ldap_user.backend.settings = alice0.ldap_user.backend.settings
+
+        self.assert_(alice is not None)
+        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.assert_(self.backend.has_perm(alice, "auth.add_user"))
+        self.assert_(self.backend.has_module_perms(alice, "auth"))
+
     def _init_settings(self, **kwargs):
         self.backend.settings = TestSettings(**kwargs)
 
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.