Эх сурвалжийг харах

HUE-607. LDAP/PAM authentication

This commit adds an LDAP and PAM authentication backend. The backend is
configurable already, and this adds several configuration parameters for
defining the LDAP settings. The LDAP backend will support both Active Directory
and LDAP, although there are slightly different configurations depending upon
which is being used.

Add PAM authentication package to ext-py

This package is already listed as being a part of Hue in README.md, despite not
actually being there, so I haven't updated that file.

Adding django-auth-ldap to ext-py

python-ldap library added to ext-py
Jon Natkins 14 жил өмнө
parent
commit
c87e4f6ca5
100 өөрчлөгдсөн 10727 нэмэгдсэн , 0 устгасан
  1. BIN
      desktop/core/ext-py/django-auth-ldap-1.0.7/._setup.py
  2. 24 0
      desktop/core/ext-py/django-auth-ldap-1.0.7/PKG-INFO
  3. BIN
      desktop/core/ext-py/django-auth-ldap-1.0.7/django_auth_ldap/._backend.py
  4. BIN
      desktop/core/ext-py/django-auth-ldap-1.0.7/django_auth_ldap/._config.py
  5. BIN
      desktop/core/ext-py/django-auth-ldap-1.0.7/django_auth_ldap/._tests.py
  6. 730 0
      desktop/core/ext-py/django-auth-ldap-1.0.7/django_auth_ldap/backend.py
  7. 433 0
      desktop/core/ext-py/django-auth-ldap-1.0.7/django_auth_ldap/config.py
  8. 32 0
      desktop/core/ext-py/django-auth-ldap-1.0.7/django_auth_ldap/dn.py
  9. 2 0
      desktop/core/ext-py/django-auth-ldap-1.0.7/django_auth_ldap/models.py
  10. 1037 0
      desktop/core/ext-py/django-auth-ldap-1.0.7/django_auth_ldap/tests.py
  11. BIN
      desktop/core/ext-py/django-auth-ldap-1.0.7/docs/._.DS_Store
  12. BIN
      desktop/core/ext-py/django-auth-ldap-1.0.7/docs/._conf.py
  13. BIN
      desktop/core/ext-py/django-auth-ldap-1.0.7/docs/._index.rst
  14. 92 0
      desktop/core/ext-py/django-auth-ldap-1.0.7/docs/Makefile
  15. 196 0
      desktop/core/ext-py/django-auth-ldap-1.0.7/docs/conf.py
  16. 853 0
      desktop/core/ext-py/django-auth-ldap-1.0.7/docs/index.rst
  17. 31 0
      desktop/core/ext-py/django-auth-ldap-1.0.7/setup.py
  18. 19 0
      desktop/core/ext-py/pam/PKG-INFO
  19. 123 0
      desktop/core/ext-py/pam/pam.py
  20. 5 0
      desktop/core/ext-py/pam/setup.cfg
  21. 32 0
      desktop/core/ext-py/pam/setup.py
  22. 7 0
      desktop/core/ext-py/python-ldap-2.4.6/.cvsignore
  23. 31 0
      desktop/core/ext-py/python-ldap-2.4.6/Build/build-openbsd/Makefile
  24. 1 0
      desktop/core/ext-py/python-ldap-2.4.6/Build/build-openbsd/pkg/COMMENT
  25. 2 0
      desktop/core/ext-py/python-ldap-2.4.6/Build/build-openbsd/pkg/DESCR
  26. 3 0
      desktop/core/ext-py/python-ldap-2.4.6/Build/build-openbsd/pkg/PLIST
  27. 35 0
      desktop/core/ext-py/python-ldap-2.4.6/Build/setup.cfg.mingw
  28. 33 0
      desktop/core/ext-py/python-ldap-2.4.6/Build/setup.cfg.suse-linux
  29. 33 0
      desktop/core/ext-py/python-ldap-2.4.6/Build/setup.cfg.win32
  30. 967 0
      desktop/core/ext-py/python-ldap-2.4.6/CHANGES
  31. 4 0
      desktop/core/ext-py/python-ldap-2.4.6/Demo/.cvsignore
  32. 4 0
      desktop/core/ext-py/python-ldap-2.4.6/Demo/Lib/.cvsignore
  33. 4 0
      desktop/core/ext-py/python-ldap-2.4.6/Demo/Lib/ldap/.cvsignore
  34. 4 0
      desktop/core/ext-py/python-ldap-2.4.6/Demo/Lib/ldap/async/.cvsignore
  35. 80 0
      desktop/core/ext-py/python-ldap-2.4.6/Demo/Lib/ldap/async/deltree.py
  36. 42 0
      desktop/core/ext-py/python-ldap-2.4.6/Demo/Lib/ldap/async/ldifwriter.py
  37. 42 0
      desktop/core/ext-py/python-ldap-2.4.6/Demo/Lib/ldap/async/sizelimit.py
  38. 4 0
      desktop/core/ext-py/python-ldap-2.4.6/Demo/Lib/ldapurl/.cvsignore
  39. 32 0
      desktop/core/ext-py/python-ldap-2.4.6/Demo/Lib/ldapurl/urlsearch.py
  40. 4 0
      desktop/core/ext-py/python-ldap-2.4.6/Demo/Lib/ldif/.cvsignore
  41. 28 0
      desktop/core/ext-py/python-ldap-2.4.6/Demo/Lib/ldif/ldifcopy.py
  42. 91 0
      desktop/core/ext-py/python-ldap-2.4.6/Demo/initialize.py
  43. 33 0
      desktop/core/ext-py/python-ldap-2.4.6/Demo/ldapcontrols.py
  44. 63 0
      desktop/core/ext-py/python-ldap-2.4.6/Demo/matchedvalues.py
  45. 38 0
      desktop/core/ext-py/python-ldap-2.4.6/Demo/ms_ad_bind.py
  46. 29 0
      desktop/core/ext-py/python-ldap-2.4.6/Demo/options.py
  47. 64 0
      desktop/core/ext-py/python-ldap-2.4.6/Demo/page_control.py
  48. 109 0
      desktop/core/ext-py/python-ldap-2.4.6/Demo/paged_search_ext_s.py
  49. 32 0
      desktop/core/ext-py/python-ldap-2.4.6/Demo/passwd_ext_op.py
  50. 12 0
      desktop/core/ext-py/python-ldap-2.4.6/Demo/pickle_ldapobject.py
  51. 6 0
      desktop/core/ext-py/python-ldap-2.4.6/Demo/pyasn1/README
  52. 55 0
      desktop/core/ext-py/python-ldap-2.4.6/Demo/pyasn1/dds.py
  53. 50 0
      desktop/core/ext-py/python-ldap-2.4.6/Demo/pyasn1/ppolicy.py
  54. 79 0
      desktop/core/ext-py/python-ldap-2.4.6/Demo/pyasn1/psearch.py
  55. 112 0
      desktop/core/ext-py/python-ldap-2.4.6/Demo/pyasn1/readentrycontrol.py
  56. 59 0
      desktop/core/ext-py/python-ldap-2.4.6/Demo/pyasn1/sessiontrack.py
  57. 93 0
      desktop/core/ext-py/python-ldap-2.4.6/Demo/pyasn1/syncrepl.py
  58. 24 0
      desktop/core/ext-py/python-ldap-2.4.6/Demo/reconnect.py
  59. 52 0
      desktop/core/ext-py/python-ldap-2.4.6/Demo/rename.py
  60. 26 0
      desktop/core/ext-py/python-ldap-2.4.6/Demo/resiter.py
  61. 84 0
      desktop/core/ext-py/python-ldap-2.4.6/Demo/sasl_bind.py
  62. 63 0
      desktop/core/ext-py/python-ldap-2.4.6/Demo/schema.py
  63. 100 0
      desktop/core/ext-py/python-ldap-2.4.6/Demo/schema_tree.py
  64. 106 0
      desktop/core/ext-py/python-ldap-2.4.6/Demo/simple.py
  65. 128 0
      desktop/core/ext-py/python-ldap-2.4.6/Demo/simplebrowse.py
  66. 4 0
      desktop/core/ext-py/python-ldap-2.4.6/Doc/.cvsignore
  67. 68 0
      desktop/core/ext-py/python-ldap-2.4.6/Doc/Makefile
  68. 135 0
      desktop/core/ext-py/python-ldap-2.4.6/Doc/conf.py
  69. 28 0
      desktop/core/ext-py/python-ldap-2.4.6/Doc/dsml.rst
  70. 44 0
      desktop/core/ext-py/python-ldap-2.4.6/Doc/index.rst
  71. 111 0
      desktop/core/ext-py/python-ldap-2.4.6/Doc/installing.rst
  72. 110 0
      desktop/core/ext-py/python-ldap-2.4.6/Doc/ldap-async.rst
  73. 204 0
      desktop/core/ext-py/python-ldap-2.4.6/Doc/ldap-controls.rst
  74. 114 0
      desktop/core/ext-py/python-ldap-2.4.6/Doc/ldap-dn.rst
  75. 43 0
      desktop/core/ext-py/python-ldap-2.4.6/Doc/ldap-extop.rst
  76. 41 0
      desktop/core/ext-py/python-ldap-2.4.6/Doc/ldap-filter.rst
  77. 53 0
      desktop/core/ext-py/python-ldap-2.4.6/Doc/ldap-modlist.rst
  78. 52 0
      desktop/core/ext-py/python-ldap-2.4.6/Doc/ldap-resiter.rst
  79. 87 0
      desktop/core/ext-py/python-ldap-2.4.6/Doc/ldap-schema.rst
  80. 25 0
      desktop/core/ext-py/python-ldap-2.4.6/Doc/ldap-syncrepl.rst
  81. 1125 0
      desktop/core/ext-py/python-ldap-2.4.6/Doc/ldap.rst
  82. 126 0
      desktop/core/ext-py/python-ldap-2.4.6/Doc/ldapurl.rst
  83. 83 0
      desktop/core/ext-py/python-ldap-2.4.6/Doc/ldif.rst
  84. 28 0
      desktop/core/ext-py/python-ldap-2.4.6/INSTALL
  85. 13 0
      desktop/core/ext-py/python-ldap-2.4.6/LICENCE
  86. 4 0
      desktop/core/ext-py/python-ldap-2.4.6/Lib/.cvsignore
  87. 297 0
      desktop/core/ext-py/python-ldap-2.4.6/Lib/dsml.py
  88. 4 0
      desktop/core/ext-py/python-ldap-2.4.6/Lib/ldap/.cvsignore
  89. 88 0
      desktop/core/ext-py/python-ldap-2.4.6/Lib/ldap/__init__.py
  90. 306 0
      desktop/core/ext-py/python-ldap-2.4.6/Lib/ldap/async.py
  91. 125 0
      desktop/core/ext-py/python-ldap-2.4.6/Lib/ldap/cidict.py
  92. 148 0
      desktop/core/ext-py/python-ldap-2.4.6/Lib/ldap/controls/__init__.py
  93. 77 0
      desktop/core/ext-py/python-ldap-2.4.6/Lib/ldap/controls/libldap.py
  94. 96 0
      desktop/core/ext-py/python-ldap-2.4.6/Lib/ldap/controls/ppolicy.py
  95. 134 0
      desktop/core/ext-py/python-ldap-2.4.6/Lib/ldap/controls/psearch.py
  96. 90 0
      desktop/core/ext-py/python-ldap-2.4.6/Lib/ldap/controls/readentry.py
  97. 64 0
      desktop/core/ext-py/python-ldap-2.4.6/Lib/ldap/controls/sessiontrack.py
  98. 139 0
      desktop/core/ext-py/python-ldap-2.4.6/Lib/ldap/controls/simple.py
  99. 110 0
      desktop/core/ext-py/python-ldap-2.4.6/Lib/ldap/dn.py
  100. 74 0
      desktop/core/ext-py/python-ldap-2.4.6/Lib/ldap/extop/__init__.py

BIN
desktop/core/ext-py/django-auth-ldap-1.0.7/._setup.py


+ 24 - 0
desktop/core/ext-py/django-auth-ldap-1.0.7/PKG-INFO

@@ -0,0 +1,24 @@
+Metadata-Version: 1.0
+Name: django-auth-ldap
+Version: 1.0.7
+Summary: Django LDAP authentication backend
+Home-page: http://bitbucket.org/psagers/django-auth-ldap/
+Author: Peter Sagerson
+Author-email: psagers_pypi@ignorare.net
+License: BSD
+Description: This is a Django authentication backend that authenticates against an LDAP service. Configuration can be as simple as a single distinguished name template, but there are many rich configuration options for working with users, groups, and permissions.
+            
+        This package requires at least Python 2.3, Django 1.0, and python-ldap 2.0. Documentation can be found at http://packages.python.org/django-auth-ldap/.
+            
+Keywords: django,ldap,authentication,auth
+Platform: UNKNOWN
+Classifier: Development Status :: 5 - Production/Stable
+Classifier: Environment :: Web Environment
+Classifier: Programming Language :: Python
+Classifier: Framework :: Django
+Classifier: Intended Audience :: Developers
+Classifier: Intended Audience :: System Administrators
+Classifier: License :: OSI Approved :: BSD License
+Classifier: Topic :: Internet :: WWW/HTTP
+Classifier: Topic :: System :: Systems Administration :: Authentication/Directory :: LDAP
+Classifier: Topic :: Software Development :: Libraries :: Python Modules

BIN
desktop/core/ext-py/django-auth-ldap-1.0.7/django_auth_ldap/._backend.py


BIN
desktop/core/ext-py/django-auth-ldap-1.0.7/django_auth_ldap/._config.py


BIN
desktop/core/ext-py/django-auth-ldap-1.0.7/django_auth_ldap/._tests.py


+ 730 - 0
desktop/core/ext-py/django-auth-ldap-1.0.7/django_auth_ldap/backend.py

@@ -0,0 +1,730 @@
+# Copyright (c) 2009, Peter Sagerson
+# All rights reserved.
+# 
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are met:
+# 
+# - Redistributions of source code must retain the above copyright notice, this
+# list of conditions and the following disclaimer.
+# 
+# - Redistributions in binary form must reproduce the above copyright notice,
+# this list of conditions and the following disclaimer in the documentation
+# and/or other materials provided with the distribution.
+# 
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+"""
+LDAP authentication backend
+
+Complete documentation can be found in docs/howto/auth-ldap.txt (or the thing it
+compiles to).
+
+Use of this backend requires the python-ldap module. To support unit tests, we
+import ldap in a single centralized place (config._LDAPConfig) so that the test
+harness can insert a mock object.
+
+A few notes on naming conventions. If an identifier ends in _dn, it is a string
+representation of a distinguished name. If it ends in _info, it is a 2-tuple
+containing a DN and a dictionary of lists of attributes. ldap.search_s returns a
+list of such structures. An identifier that ends in _attrs is the dictionary of
+attributes from the _info structure.
+
+A connection is an LDAPObject that has been successfully bound with a DN and
+password. The identifier 'user' always refers to a User model object; LDAP user
+information will be user_dn or user_info.
+
+Additional classes can be found in the config module next to this one.
+"""
+
+try:
+    set
+except NameError:
+    from sets import Set as set     # Python 2.3 fallback
+
+import sys
+import traceback
+import pprint
+import copy
+
+import django.db
+from django.contrib.auth.models import User, Group, SiteProfileNotAvailable
+from django.core.cache import cache
+from django.core.exceptions import ImproperlyConfigured, ObjectDoesNotExist
+
+from django_auth_ldap.config import _LDAPConfig, LDAPSearch, LDAPGroupType
+
+
+logger = _LDAPConfig.get_logger()
+
+
+class LDAPBackend(object):
+    """
+    The main backend class. This implements the auth backend API, although it
+    actually delegates most of its work to _LDAPUser, which is defined next.
+    """
+    ldap = None # The cached ldap module (or mock object)
+    
+    def __init__(self):
+        self.ldap = self.ldap_module()
+    
+    def ldap_module(cls):
+        """
+        Requests the ldap module from _LDAPConfig. Under a test harness, this
+        will be a mock object. We only do this once because this is where we
+        apply AUTH_LDAP_GLOBAL_OPTIONS.
+        """
+        if cls.ldap is None:
+            cls.ldap = _LDAPConfig.get_ldap()
+            
+            for opt, value in ldap_settings.AUTH_LDAP_GLOBAL_OPTIONS.iteritems():
+                cls.ldap.set_option(opt, value)
+        
+        return cls.ldap
+    ldap_module = classmethod(ldap_module)
+
+
+    #
+    # The Django auth backend API
+    #
+
+    def authenticate(self, username, password):
+        ldap_user = _LDAPUser(self, username=username)
+        user = ldap_user.authenticate(password)
+        
+        return user
+    
+    def get_user(self, user_id):
+        user = None
+        
+        try:
+            user = User.objects.get(pk=user_id)
+            _LDAPUser(self, user=user) # This sets user.ldap_user
+        except User.DoesNotExist:
+            pass
+        
+        return user
+    
+    def has_perm(self, user, perm):
+        return perm in self.get_all_permissions(user)
+
+    def has_module_perms(self, user, app_label):
+        for perm in self.get_all_permissions(user):
+            if perm[:perm.index('.')] == app_label:
+                return True
+
+        return False
+
+    def get_all_permissions(self, user):
+        return self.get_group_permissions(user)
+
+    def get_group_permissions(self, user):
+        if not hasattr(user, 'ldap_user') and ldap_settings.AUTH_LDAP_AUTHORIZE_ALL_USERS:
+            _LDAPUser(self, user=user) # This sets user.ldap_user
+        
+        if hasattr(user, 'ldap_user'):
+            return user.ldap_user.get_group_permissions()
+        else:
+            return set()
+
+    #
+    # Bonus API: populate the Django user from LDAP without authenticating.
+    #
+
+    def populate_user(self, username):
+        ldap_user = _LDAPUser(self, username=username)
+        user = ldap_user.populate_user()
+        
+        return user
+    
+    #
+    # Hooks for subclasses
+    #
+
+    def get_or_create_user(self, username, ldap_user):
+        """
+        This must return a (User, created) 2-tuple for the given LDAP user.
+        username is the Django-friendly username of the user. ldap_user.dn is
+        the user's DN and ldap_user.attrs contains all of their LDAP attributes.
+        """
+        return User.objects.get_or_create(username=username)
+
+    def ldap_to_django_username(self, username):
+        return username
+
+    def django_to_ldap_username(self, username):
+        return username
+
+
+class _LDAPUser(object):
+    """
+    Represents an LDAP user and ultimately fields all requests that the
+    backend receives. This class exists for two reasons. First, it's
+    convenient to have a separate object for each request so that we can use
+    object attributes without running into threading problems. Second, these
+    objects get attached to the User objects, which allows us to cache
+    expensive LDAP information, especially around groups and permissions.
+    
+    self.backend is a reference back to the LDAPBackend instance, which we need
+    to access the ldap module and any hooks that a subclass has overridden.
+    """
+    class AuthenticationFailed(Exception):
+        pass
+    
+    #
+    # Initialization
+    #
+    
+    def __init__(self, backend, username=None, user=None):
+        """
+        A new LDAPUser must be initialized with either a username or an
+        authenticated User object. If a user is given, the username will be
+        ignored.
+        """
+        self.backend = backend
+        self.ldap = backend.ldap_module()
+        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)
+        
+        if username is None and user is None:
+            raise Exception("Internal error: _LDAPUser improperly initialized.")
+
+    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.
+        obj._username = self._username
+        obj._user_dn = self._user_dn
+        obj._user_attrs = self._user_attrs
+        obj._groups = self._groups
+        obj._group_permissions = self._group_permissions
+        
+        # The connection couldn't be copied even if we wanted to
+        obj._connection = self._connection
+        obj._connection_bound = self._connection_bound
+
+        return obj
+
+    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
+    
+    #
+    # Entry points
+    #
+    
+    def authenticate(self, password):
+        """
+        Authenticates against the LDAP directory and returns the corresponding
+        User object if successful. Returns None on failure.
+        """
+        user = None
+        
+        try:
+            self._authenticate_user_dn(password)
+            self._check_requirements()
+            self._get_or_create_user()
+
+            user = self._user
+        except self.AuthenticationFailed, e:
+            logger.debug(u"Authentication failed for %s" % self._username)
+        except self.ldap.LDAPError, e:
+            logger.warning(u"Caught LDAPError while authenticating %s: %s",
+                self._username, pprint.pformat(e))
+        except Exception, e:
+            logger.error(u"Caught Exception while authenticating %s: %s",
+                self._username, pprint.pformat(e))
+            logger.error(''.join(traceback.format_tb(sys.exc_info()[2])))
+            raise
+
+        return user
+
+    def get_group_permissions(self):
+        """
+        If allowed by the configuration, this returns the set of permissions
+        defined by the user's LDAP group memberships.
+        """
+        if self._group_permissions is None:
+            self._group_permissions = set()
+
+            if ldap_settings.AUTH_LDAP_FIND_GROUP_PERMS:
+                try:
+                    self._load_group_permissions()
+                except self.ldap.LDAPError, e:
+                    logger.warning("Caught LDAPError loading group permissions: %s",
+                        pprint.pformat(e))
+        
+        return self._group_permissions
+
+    def populate_user(self):
+        """
+        Populates the Django user object using the default bind credentials.
+        """
+        user = None
+        
+        try:
+            self._get_or_create_user(force_populate=True)
+            
+            user = self._user
+        except self.ldap.LDAPError, e:
+            logger.warning(u"Caught LDAPError while authenticating %s: %s",
+                self._username, pprint.pformat(e))
+        except Exception, e:
+            logger.error(u"Caught Exception while authenticating %s: %s",
+                self._username, pprint.pformat(e))
+            logger.error(''.join(traceback.format_tb(sys.exc_info()[2])))
+            raise
+        
+        return user
+
+    #
+    # Public properties (callbacks). These are all lazy for performance reasons.
+    #
+
+    def _get_user_dn(self):
+        if self._user_dn is None:
+            self._load_user_dn()
+
+        return self._user_dn
+    dn = property(_get_user_dn)
+
+    def _get_user_attrs(self):
+        if self._user_attrs is None:
+            self._load_user_attrs()
+        
+        return self._user_attrs
+    attrs = property(_get_user_attrs)
+
+    def _get_bound_connection(self):
+        if not self._connection_bound:
+            self._bind()
+        
+        return self._get_connection()
+    connection = property(_get_bound_connection)
+
+    #
+    # Authentication
+    #
+
+    def _authenticate_user_dn(self, password):
+        """
+        Binds to the LDAP server with the user's DN and password. Raises
+        AuthenticationFailed on failure.
+        """
+        if self.dn is None:
+            raise self.AuthenticationFailed("Failed to map the username to a DN.")
+
+        try:
+            self._bind_as(self.dn, password)
+        except self.ldap.INVALID_CREDENTIALS:
+            raise self.AuthenticationFailed("User DN/password rejected by LDAP server.")
+    
+    def _load_user_attrs(self):
+        if self.dn is not None:
+            search = LDAPSearch(self.dn, self.ldap.SCOPE_BASE)
+            results = search.execute(self.connection)
+
+            if results is not None and len(results) > 0:
+                self._user_attrs = results[0][1]
+    
+    def _load_user_dn(self):
+        """
+        Populates self._user_dn with the distinguished name of our user. This
+        will either construct the DN from a template in
+        AUTH_LDAP_USER_DN_TEMPLATE or connect to the server and search for it.
+        """
+        if self._using_simple_bind_mode():
+            self._construct_simple_user_dn()
+        else:
+            self._search_for_user_dn()
+
+    def _using_simple_bind_mode(self):
+        return (ldap_settings.AUTH_LDAP_USER_DN_TEMPLATE is not None)
+
+    def _construct_simple_user_dn(self):
+        template = ldap_settings.AUTH_LDAP_USER_DN_TEMPLATE
+        username = self.ldap.dn.escape_dn_chars(self._username)
+        
+        self._user_dn = template % {'user': username}
+
+    def _search_for_user_dn(self):
+        """
+        Searches the directory for a user matching AUTH_LDAP_USER_SEARCH.
+        Populates self._user_dn and self._user_attrs.
+        """
+        search = ldap_settings.AUTH_LDAP_USER_SEARCH
+        if search is None:
+            raise ImproperlyConfigured('AUTH_LDAP_USER_SEARCH must be an LDAPSearch instance.')
+        
+        results = search.execute(self.connection, {'user': self._username})
+        if results is not None and len(results) == 1:
+            (self._user_dn, self._user_attrs) = results[0]
+
+    def _check_requirements(self):
+        """
+        Checks all authentication requirements beyond credentials. Raises
+        AuthenticationFailed on failure.
+        """
+        self._check_required_group()
+    
+    def _check_required_group(self):
+        """
+        Returns True if the group requirement (AUTH_LDAP_REQUIRE_GROUP) is
+        met. Always returns True if AUTH_LDAP_REQUIRE_GROUP is None.
+        """
+        required_group_dn = ldap_settings.AUTH_LDAP_REQUIRE_GROUP
+        
+        if required_group_dn is not None:
+            is_member = self._get_groups().is_member_of(required_group_dn)
+            if not is_member:
+                raise self.AuthenticationFailed("User is not a member of AUTH_LDAP_REQUIRE_GROUP")
+
+    #
+    # User management
+    #
+
+    def _get_or_create_user(self, force_populate=False):
+        """
+        Loads the User model object from the database or creates it if it
+        doesn't exist. Also populates the fields, subject to
+        AUTH_LDAP_ALWAYS_UPDATE_USER.
+        """
+        save_user = False
+        
+        username = self.backend.ldap_to_django_username(self._username)
+
+        (self._user, created) = self.backend.get_or_create_user(username, self)
+
+        if created:
+            logger.debug("Created Django user %s", username)
+            self._user.set_unusable_password()
+            save_user = True
+
+        if(force_populate or ldap_settings.AUTH_LDAP_ALWAYS_UPDATE_USER or created):
+            logger.debug("Populating Django user %s", username)
+            self._populate_user()
+            self._populate_and_save_user_profile()
+            save_user = True
+
+        if ldap_settings.AUTH_LDAP_MIRROR_GROUPS:
+            self._mirror_groups()
+
+        if save_user:
+            self._user.save()
+
+        self._user.ldap_user = self
+        self._user.ldap_username = self._username
+
+    def _populate_user(self):
+        """
+        Populates our User object with information from the LDAP directory.
+        """
+        self._populate_user_from_attributes()
+        self._populate_user_from_group_memberships()
+    
+    def _populate_user_from_attributes(self):
+        for field, attr in ldap_settings.AUTH_LDAP_USER_ATTR_MAP.iteritems():
+            try:
+                setattr(self._user, field, self.attrs[attr][0])
+            except (KeyError, IndexError):
+                pass
+    
+    def _populate_user_from_group_memberships(self):
+        for field, group_dn in ldap_settings.AUTH_LDAP_USER_FLAGS_BY_GROUP.iteritems():
+            value = self._get_groups().is_member_of(group_dn)
+            setattr(self._user, field, value)
+
+    def _populate_and_save_user_profile(self):
+        """
+        Populates a User profile object with fields from the LDAP directory.
+        """
+        try:
+            profile = self._user.get_profile()
+
+            for field, attr in ldap_settings.AUTH_LDAP_PROFILE_ATTR_MAP.iteritems():
+                try:
+                    # user_attrs is a hash of lists of attribute values
+                    setattr(profile, field, self.attrs[attr][0])
+                except (KeyError, IndexError):
+                    pass
+
+            if len(ldap_settings.AUTH_LDAP_PROFILE_ATTR_MAP) > 0:
+                profile.save()
+        except (SiteProfileNotAvailable, ObjectDoesNotExist):
+            pass
+    
+    def _mirror_groups(self):
+        """
+        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]
+        
+        self._user.groups = groups
+    
+    #
+    # Group information
+    #
+    
+    def _load_group_permissions(self):
+        """
+        Populates self._group_permissions based on LDAP group membership and
+        Django group permissions.
+        
+        The SQL is lifted from ModelBackend, with modifications.
+        """
+        group_names = self._get_groups().get_group_names()
+        placeholders = ', '.join(['%s'] * len(group_names))
+        
+        cursor = django.db.connection.cursor()
+        # The SQL below works out to the following, after DB quoting:
+        # cursor.execute("""
+        #     SELECT ct."app_label", p."codename"
+        #     FROM "auth_permission" p, "auth_group_permissions" gp, "auth_group" g, "django_content_type" ct
+        #     WHERE p."id" = gp."permission_id"
+        #         AND gp."group_id" = g."id"
+        #         AND ct."id" = p."content_type_id"
+        #         AND g."name" IN (%s, %s, ...)""", ['group1', 'group2', ...])
+        qn = django.db.connection.ops.quote_name
+        sql = u"""
+            SELECT ct.%s, p.%s
+            FROM %s p, %s gp, %s g, %s ct
+            WHERE p.%s = gp.%s
+                AND gp.%s = g.%s
+                AND ct.%s = p.%s
+                AND g.%s IN (%s)""" % (
+            qn('app_label'), qn('codename'),
+            qn('auth_permission'), qn('auth_group_permissions'),
+            qn('auth_group'), qn('django_content_type'),
+            qn('id'), qn('permission_id'),
+            qn('group_id'), qn('id'),
+            qn('id'), qn('content_type_id'),
+            qn('name'), placeholders)
+        
+        cursor.execute(sql, group_names)
+        self._group_permissions = \
+            set([u"%s.%s" % (row[0], row[1]) for row in cursor.fetchall()])
+
+    def _get_groups(self):
+        """
+        Returns an _LDAPUserGroups object, which can determine group
+        membership.
+        """
+        if self._groups is None:
+            self._groups = _LDAPUserGroups(self)
+        
+        return self._groups
+
+    #
+    # LDAP connection
+    #
+
+    def _bind(self):
+        """
+        Binds to the LDAP server with AUTH_LDAP_BIND_DN and
+        AUTH_LDAP_BIND_PASSWORD.
+        """
+        self._bind_as(ldap_settings.AUTH_LDAP_BIND_DN,
+            ldap_settings.AUTH_LDAP_BIND_PASSWORD)
+
+        self._connection_bound = True
+
+    def _bind_as(self, bind_dn, bind_password):
+        """
+        Binds to the LDAP server with the given credentials. This does not trap
+        exceptions.
+
+        If successful, we set self._connection_bound to False under the
+        assumption that we're not binding as the default user. Callers can set
+        it to True as appropriate.
+        """
+        self._get_connection().simple_bind_s(bind_dn.encode('utf-8'),
+            bind_password.encode('utf-8'))
+
+        self._connection_bound = False
+
+    def _get_connection(self):
+        """
+        Returns our cached LDAPObject, which may or may not be bound.
+        """
+        if self._connection is None:
+            self._connection = self.ldap.initialize(ldap_settings.AUTH_LDAP_SERVER_URI)
+            
+            for opt, value in ldap_settings.AUTH_LDAP_CONNECTION_OPTIONS.iteritems():
+                self._connection.set_option(opt, value)
+
+            if ldap_settings.AUTH_LDAP_START_TLS:
+                logger.debug("Initiating TLS")
+                self._connection.start_tls_s()
+
+        return self._connection
+
+
+
+class _LDAPUserGroups(object):
+    """
+    Represents the set of groups that a user belongs to.
+    """
+    def __init__(self, ldap_user):
+        self._ldap_user = ldap_user
+        self._group_type = None
+        self._group_search = None
+        self._group_infos = None
+        self._group_dns = None
+        self._group_names = None
+        
+        self._init_group_settings()
+    
+    def _init_group_settings(self):
+        """
+        Loads the settings we need to deal with groups. Raises
+        ImproperlyConfigured if anything's not right.
+        """
+        self._group_type = ldap_settings.AUTH_LDAP_GROUP_TYPE
+        if self._group_type is None:
+            raise ImproperlyConfigured("AUTH_LDAP_GROUP_TYPE must be an LDAPGroupType instance.")
+        
+        self._group_search = ldap_settings.AUTH_LDAP_GROUP_SEARCH
+        if self._group_search is None:
+            raise ImproperlyConfigured("AUTH_LDAP_GROUP_SEARCH must be an LDAPSearch instance.")
+    
+    def get_group_names(self):
+        """
+        Returns the list of Django group names that this user belongs to by
+        virtue of LDAP group memberships.
+        """
+        if self._group_names is None:
+            self._load_cached_attr("_group_names")
+        
+        if self._group_names is None:
+            group_infos = self._get_group_infos()
+            self._group_names = [self._group_type.group_name_from_info(group_info)
+                for group_info in group_infos]
+            self._cache_attr("_group_names")
+        
+        return self._group_names
+    
+    def is_member_of(self, group_dn):
+        """
+        Returns true if our user is a member of the given group.
+        """
+        is_member = None
+        
+        # If we have self._group_dns, we'll use it. Otherwise, we'll try to
+        # avoid the cost of loading it.
+        if self._group_dns is None:
+            is_member = self._group_type.is_member(self._ldap_user, group_dn)
+        
+        if is_member is None:
+            is_member = (group_dn in self._get_group_dns())
+        
+        logger.debug("%s is%sa member of %s", self._ldap_user.dn,
+                     is_member and " " or " not ", group_dn)
+
+        return is_member
+    
+    def _get_group_dns(self):
+        """
+        Returns a (cached) set of the distinguished names in self._group_infos.
+        """
+        if self._group_dns is None:
+            group_infos = self._get_group_infos()
+            self._group_dns = set([group_info[0] for group_info in group_infos])
+        
+        return self._group_dns
+    
+    def _get_group_infos(self):
+        """
+        Returns a (cached) list of group_info structures for the groups that our
+        user is a member of.
+        """
+        if self._group_infos is None:
+            self._group_infos = self._group_type.user_groups(self._ldap_user,
+                self._group_search)
+        
+        return self._group_infos
+
+    def _load_cached_attr(self, attr_name):
+        if ldap_settings.AUTH_LDAP_CACHE_GROUPS:
+            key = self._cache_key(attr_name)
+            value = cache.get(key)
+            setattr(self, attr_name, value)
+    
+    def _cache_attr(self, attr_name):
+        if ldap_settings.AUTH_LDAP_CACHE_GROUPS:
+            key = self._cache_key(attr_name)
+            value = getattr(self, attr_name, None)
+            cache.set(key, value, ldap_settings.AUTH_LDAP_GROUP_CACHE_TIMEOUT)
+    
+    def _cache_key(self, attr_name):
+        return u'auth_ldap.%s.%s.%s' % (self.__class__.__name__, attr_name, self._ldap_user.dn)
+
+
+class LDAPSettings(object):
+    """
+    This is a simple class to take the place of the global settings object. An
+    instance will contain all of our settings as attributes, with default values
+    if they are not specified by the configuration.
+    """
+    defaults = {
+        'AUTH_LDAP_ALWAYS_UPDATE_USER': True,
+        'AUTH_LDAP_AUTHORIZE_ALL_USERS': False,
+        'AUTH_LDAP_BIND_DN': '',
+        'AUTH_LDAP_BIND_PASSWORD': '',
+        'AUTH_LDAP_CACHE_GROUPS': False,
+        'AUTH_LDAP_CONNECTION_OPTIONS': {},
+        'AUTH_LDAP_FIND_GROUP_PERMS': False,
+        'AUTH_LDAP_GLOBAL_OPTIONS': {},
+        'AUTH_LDAP_GROUP_CACHE_TIMEOUT': None,
+        'AUTH_LDAP_GROUP_SEARCH': None,
+        'AUTH_LDAP_GROUP_TYPE': None,
+        'AUTH_LDAP_MIRROR_GROUPS': False,
+        'AUTH_LDAP_PROFILE_ATTR_MAP': {},
+        'AUTH_LDAP_REQUIRE_GROUP': None,
+        'AUTH_LDAP_SERVER_URI': 'ldap://localhost',
+        'AUTH_LDAP_START_TLS': False,
+        'AUTH_LDAP_USER_ATTR_MAP': {},
+        'AUTH_LDAP_USER_DN_TEMPLATE': None,
+        'AUTH_LDAP_USER_FLAGS_BY_GROUP': {},
+        'AUTH_LDAP_USER_SEARCH': None,
+    }
+    
+    def __init__(self):
+        """
+        Loads our settings from django.conf.settings, applying defaults for any
+        that are omitted.
+        """
+        from django.conf import settings
+        
+        for name, default in self.defaults.iteritems():
+            value = getattr(settings, name, default)
+            setattr(self, name, value)
+
+
+# Our global settings object
+ldap_settings = LDAPSettings()

+ 433 - 0
desktop/core/ext-py/django-auth-ldap-1.0.7/django_auth_ldap/config.py

@@ -0,0 +1,433 @@
+# Copyright (c) 2009, Peter Sagerson
+# All rights reserved.
+# 
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are met:
+# 
+# - Redistributions of source code must retain the above copyright notice, this
+# list of conditions and the following disclaimer.
+# 
+# - Redistributions in binary form must reproduce the above copyright notice,
+# this list of conditions and the following disclaimer in the documentation
+# and/or other materials provided with the distribution.
+# 
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+
+"""
+This module contains classes that will be needed for configuration of LDAP
+authentication. Unlike backend.py, this is safe to import into settings.py.
+Please see the docstring on the backend module for more information, including
+notes on naming conventions.
+"""
+
+try:
+    set
+except NameError:
+    from sets import Set as set     # Python 2.3 fallback
+
+import logging
+import pprint
+
+
+class _LDAPConfig(object):
+    """
+    A private class that loads and caches some global objects.
+    """
+    ldap = None
+    logger = None
+    
+    def get_ldap(cls):
+        """
+        Returns the ldap module. The unit test harness will assign a mock object
+        to _LDAPConfig.ldap. It is imperative that the ldap module not be
+        imported anywhere else so that the unit tests will pass in the absence
+        of python-ldap.
+        """
+        if cls.ldap is None:
+            import ldap
+            import ldap.filter
+
+            # Support for python-ldap < 2.0.6
+            try:
+                import ldap.dn
+            except ImportError:
+                from django_auth_ldap import dn
+                ldap.dn = dn
+            
+            cls.ldap = ldap
+        
+        return cls.ldap
+    get_ldap = classmethod(get_ldap)
+
+    def get_logger(cls):
+        """
+        Initializes and returns our logger instance.
+        """
+        if cls.logger is None:
+            class NullHandler(logging.Handler):
+                def emit(self, record):
+                    pass
+
+            cls.logger = logging.getLogger('django_auth_ldap')
+            cls.logger.addHandler(NullHandler())
+
+        return cls.logger
+    get_logger = classmethod(get_logger)
+
+
+# Our global logger
+logger = _LDAPConfig.get_logger()
+
+
+class LDAPSearch(object):
+    """
+    Public class that holds a set of LDAP search parameters. Objects of this
+    class should be considered immutable. Only the initialization method is
+    documented for configuration purposes. Internal clients may use the other
+    methods to refine and execute the search.
+    """
+    def __init__(self, base_dn, scope, filterstr=u'(objectClass=*)'):
+        """
+        These parameters are the same as the first three parameters to
+        ldap.search_s.
+        """
+        self.base_dn = base_dn
+        self.scope = scope
+        self.filterstr = filterstr
+        self.ldap = _LDAPConfig.get_ldap()
+    
+    def search_with_additional_terms(self, term_dict, escape=True):
+        """
+        Returns a new search object with additional search terms and-ed to the
+        filter string. term_dict maps attribute names to assertion values. If
+        you don't want the values escaped, pass escape=False.
+        """
+        term_strings = [self.filterstr]
+        
+        for name, value in term_dict.iteritems():
+            if escape:
+                value = self.ldap.filter.escape_filter_chars(value)
+            term_strings.append(u'(%s=%s)' % (name, value))
+        
+        filterstr = u'(&%s)' % ''.join(term_strings)
+        
+        return self.__class__(self.base_dn, self.scope, filterstr)
+    
+    def search_with_additional_term_string(self, filterstr):
+        """
+        Returns a new search object with filterstr and-ed to the original filter
+        string. The caller is responsible for passing in a properly escaped
+        string.
+        """
+        filterstr = u'(&%s%s)' % (self.filterstr, filterstr)
+        
+        return self.__class__(self.base_dn, self.scope, filterstr)
+    
+    def execute(self, connection, filterargs=()):
+        """
+        Executes the search on the given connection (an LDAPObject). filterargs
+        is an object that will be used for expansion of the filter string.
+        
+        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.
+        """
+        try:
+            filterstr = self.filterstr % filterargs
+            results = connection.search_s(self.base_dn.encode('utf-8'),
+                self.scope, filterstr.encode('utf-8'))
+
+            # There's been a report of an LDAP server returning extraneous
+            # entries with DNs of None. This will filter them out.
+            results = filter(lambda r: r[0] is not None, results)
+
+            results = _DeepStringCoder('utf-8').decode(results)
+
+            result_dns = [result[0] for result in results]
+            logger.debug(u"search_s('%s', %d, '%s') returned %d objects: %s" %
+                (self.base_dn, self.scope, filterstr, len(result_dns), "; ".join(result_dns)))
+        except self.ldap.LDAPError, e:
+            results = []
+            logger.error(u"search_s('%s', %d, '%s') raised %s" %
+                (self.base_dn, self.scope, filterstr, pprint.pformat(e)))
+        
+        return results
+
+
+class _DeepStringCoder(object):
+    """
+    Encodes and decodes strings in a nested structure of lists, tuples, and
+    dicts. This is helpful when interacting with the Unicode-unaware
+    python-ldap.
+    """
+    def __init__(self, encoding):
+        self.encoding = encoding
+    
+    def decode(self, value):
+        try:
+            if isinstance(value, str):
+                value = value.decode(self.encoding)
+            elif isinstance(value, list):
+                value = self._decode_list(value)
+            elif isinstance(value, tuple):
+                value = tuple(self._decode_list(value))
+            elif isinstance(value, dict):
+                value = self._decode_dict(value)
+        except UnicodeDecodeError:
+            pass
+        
+        return value
+    
+    def _decode_list(self, value):
+        return [self.decode(v) for v in value]
+    
+    def _decode_dict(self, value):
+        return dict([(self.decode(k), self.decode(v)) for k,v in value.iteritems()])
+
+
+class LDAPGroupType(object):
+    """
+    This is an abstract base class for classes that determine LDAP group
+    membership. A group can mean many different things in LDAP, so we will need
+    a concrete subclass for each grouping mechanism. Clients may subclass this
+    if they have a group mechanism that is not handled by a built-in
+    implementation.
+    
+    name_attr is the name of the LDAP attribute from which we will take the
+    Django group name.
+    
+    Subclasses in this file must use self.ldap to access the python-ldap module.
+    This will be a mock object during unit tests.
+    """
+    def __init__(self, name_attr="cn"):
+        self.name_attr = name_attr
+        self.ldap = _LDAPConfig.get_ldap()
+
+    def user_groups(self, ldap_user, group_search):
+        """
+        Returns a list of group_info structures, each one a group to which
+        ldap_user belongs. group_search is an LDAPSearch object that returns all
+        of the groups that the user might belong to. Typical implementations
+        will apply additional filters to group_search and return the results of
+        the search. ldap_user represents the user and has the following three
+        properties:
+        
+        dn: the distinguished name
+        attrs: a dictionary of LDAP attributes (with lists of values)
+        connection: an LDAPObject that has been bound with credentials
+        
+        This is the primitive method in the API and must be implemented.
+        """
+        return []
+    
+    def is_member(self, ldap_user, group_dn):
+        """
+        This method is an optimization for determining group membership without
+        loading all of the user's groups. Subclasses that are able to do this
+        may return True or False. ldap_user is as above. group_dn is the
+        distinguished name of the group in question.
+        
+        The base implementation returns None, which means we don't have enough
+        information. The caller will have to call user_groups() instead and look
+        for group_dn in the results.
+        """
+        return None
+
+    def group_name_from_info(self, group_info):
+        """
+        Given the (DN, attrs) 2-tuple of an LDAP group, this returns the name of
+        the Django group. This may return None to indicate that a particular
+        LDAP group has no corresponding Django group.
+        
+        The base implementation returns the value of the cn attribute, or
+        whichever attribute was given to __init__ in the name_attr
+        parameter.
+        """
+        try:
+            name = group_info[1][self.name_attr][0]
+        except (KeyError, IndexError):
+            name = None
+        
+        return name
+
+
+class PosixGroupType(LDAPGroupType):
+    """
+    An LDAPGroupType subclass that handles groups of class posixGroup.
+    """
+    def user_groups(self, ldap_user, group_search):
+        """
+        Searches for any group that is either the user's primary or contains the
+        user as a member.
+        """
+        groups = []
+        
+        try:
+            user_uid = ldap_user.attrs['uid'][0]
+            user_gid = ldap_user.attrs['gidNumber'][0]
+            
+            filterstr = u'(|(gidNumber=%s)(memberUid=%s))' % (
+                self.ldap.filter.escape_filter_chars(user_gid),
+                self.ldap.filter.escape_filter_chars(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):
+        """
+        Returns True if the group is the user's primary group or if the user is
+        listed in the group's memberUid attribute.
+        """
+        try:
+            user_uid = ldap_user.attrs['uid'][0]
+            user_gid = ldap_user.attrs['gidNumber'][0]
+
+            is_member = ldap_user.connection.compare_s(group_dn.encode('utf-8'), 'memberUid', user_uid.encode('utf-8'))
+            if not is_member:
+                is_member = ldap_user.connection.compare_s(group_dn.encode('utf-8'), 'gidNumber', user_gid.encode('utf-8'))
+        except (KeyError, IndexError):
+            is_member = False
+        
+        return is_member
+
+
+class MemberDNGroupType(LDAPGroupType):
+    """
+    A group type that stores lists of members as distinguished names.
+    """
+    def __init__(self, member_attr, name_attr='cn'):
+        """
+        member_attr is the attribute on the group object that holds the list of
+        member DNs.
+        """
+        self.member_attr = member_attr
+        
+        super(MemberDNGroupType, self).__init__(name_attr)
+    
+    def user_groups(self, ldap_user, group_search):
+        search = group_search.search_with_additional_terms(
+            {self.member_attr: ldap_user.dn})
+        groups = search.execute(ldap_user.connection)
+        
+        return groups
+
+    def is_member(self, ldap_user, group_dn):
+        return ldap_user.connection.compare_s(group_dn.encode('utf-8'),
+            self.member_attr.encode('utf-8'), ldap_user.dn.encode('utf-8'))
+
+
+class NestedMemberDNGroupType(LDAPGroupType):
+    """
+    A group type that stores lists of members as distinguished names and
+    supports nested groups. There is no shortcut for is_member in this case, so
+    it's left unimplemented.
+    """
+    def __init__(self, member_attr, name_attr='cn'):
+        """
+        member_attr is the attribute on the group object that holds the list of
+        member DNs.
+        """
+        self.member_attr = member_attr
+
+        super(NestedMemberDNGroupType, self).__init__(name_attr)
+        
+    def user_groups(self, ldap_user, group_search):
+        """
+        This searches for all of a user's groups from the bottom up. In other
+        words, it returns the groups that the user belongs to, the groups that
+        those groups belong to, etc. Circular references will be detected and
+        pruned.
+        """
+        group_info_map = {} # Maps group_dn to group_info of groups we've found
+        member_dn_set = set([ldap_user.dn]) # Member DNs to search with next
+        handled_dn_set = set() # Member DNs that we've already searched with
+        
+        while len(member_dn_set) > 0:
+            group_infos = self.find_groups_with_any_member(member_dn_set,
+                group_search, ldap_user.connection)
+            new_group_info_map = dict([(info[0], info) for info in group_infos])
+            group_info_map.update(new_group_info_map)
+            handled_dn_set.update(member_dn_set)
+
+            # Get ready for the next iteration. To avoid cycles, we make sure
+            # never to search with the same member DN twice.
+            member_dn_set = set(new_group_info_map.keys()) - handled_dn_set
+        
+        return group_info_map.values()
+        
+    def find_groups_with_any_member(self, member_dn_set, group_search, connection):
+        terms = [
+            u"(%s=%s)" % (self.member_attr, self.ldap.filter.escape_filter_chars(dn))
+            for dn in member_dn_set
+        ]
+        
+        filterstr = u"(|%s)" % "".join(terms)
+        search = group_search.search_with_additional_term_string(filterstr)
+        
+        return search.execute(connection)
+
+
+class GroupOfNamesType(MemberDNGroupType):
+    """
+    An LDAPGroupType subclass that handles groups of class groupOfNames.
+    """
+    def __init__(self, name_attr='cn'):
+        super(GroupOfNamesType, self).__init__('member', name_attr)
+
+
+class NestedGroupOfNamesType(NestedMemberDNGroupType):
+    """
+    An LDAPGroupType subclass that handles groups of class groupOfNames with
+    nested group references.
+    """
+    def __init__(self, name_attr='cn'):
+        super(NestedGroupOfNamesType, self).__init__('member', name_attr)
+
+
+class GroupOfUniqueNamesType(MemberDNGroupType):
+    """
+    An LDAPGroupType subclass that handles groups of class groupOfUniqueNames.
+    """
+    def __init__(self, name_attr='cn'):
+        super(GroupOfUniqueNamesType, self).__init__('uniqueMember', name_attr)
+
+
+class NestedGroupOfUniqueNamesType(NestedMemberDNGroupType):
+    """
+    An LDAPGroupType subclass that handles groups of class groupOfUniqueNames
+    with nested group references.
+    """
+    def __init__(self, name_attr='cn'):
+        super(NestedGroupOfUniqueNamesType, self).__init__('uniqueMember', name_attr)
+
+
+class ActiveDirectoryGroupType(MemberDNGroupType):
+    """
+    An LDAPGroupType subclass that handles Active Directory groups.
+    """
+    def __init__(self, name_attr='cn'):
+        super(ActiveDirectoryGroupType, self).__init__('member', name_attr)
+
+
+class NestedActiveDirectoryGroupType(NestedMemberDNGroupType):
+    """
+    An LDAPGroupType subclass that handles Active Directory groups with nested
+    group references.
+    """
+    def __init__(self, name_attr='cn'):
+        super(NestedActiveDirectoryGroupType, self).__init__('member', name_attr)

+ 32 - 0
desktop/core/ext-py/django-auth-ldap-1.0.7/django_auth_ldap/dn.py

@@ -0,0 +1,32 @@
+# Copyright (c) 2009, Peter Sagerson
+# All rights reserved.
+# 
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are met:
+# 
+# - Redistributions of source code must retain the above copyright notice, this
+# list of conditions and the following disclaimer.
+# 
+# - Redistributions in binary form must reproduce the above copyright notice,
+# this list of conditions and the following disclaimer in the documentation
+# and/or other materials provided with the distribution.
+# 
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+"""
+This is an ldap.dn replacement for old versions of python-ldap. It contains
+(often naive) implementations of the methods we care about.
+"""
+
+def escape_dn_chars(dn):
+    "Old versions of python-ldap won't get DN escaping. Use with care."
+    return dn

+ 2 - 0
desktop/core/ext-py/django-auth-ldap-1.0.7/django_auth_ldap/models.py

@@ -0,0 +1,2 @@
+# This is only here so that this looks like an app for the purpose of unit
+# testing.

+ 1037 - 0
desktop/core/ext-py/django-auth-ldap-1.0.7/django_auth_ldap/tests.py

@@ -0,0 +1,1037 @@
+# coding: utf-8
+
+# Copyright (c) 2009, Peter Sagerson
+# All rights reserved.
+# 
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are met:
+# 
+# - Redistributions of source code must retain the above copyright notice, this
+# list of conditions and the following disclaimer.
+# 
+# - Redistributions in binary form must reproduce the above copyright notice,
+# this list of conditions and the following disclaimer in the documentation
+# and/or other materials provided with the distribution.
+# 
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+try:
+    set
+except NameError:
+    from sets import Set as set     # Python 2.3 fallback
+
+import sys
+import logging
+
+from django.contrib.auth.models import User, Permission, Group
+from django.test import TestCase
+
+from django_auth_ldap import backend
+from django_auth_ldap.config import _LDAPConfig, LDAPSearch
+from django_auth_ldap.config import PosixGroupType, MemberDNGroupType, NestedMemberDNGroupType
+from django_auth_ldap.config import GroupOfNamesType, NestedGroupOfNamesType
+from django_auth_ldap.config import GroupOfUniqueNamesType, NestedGroupOfUniqueNamesType
+from django_auth_ldap.config import ActiveDirectoryGroupType, NestedActiveDirectoryGroupType
+
+
+class TestSettings(backend.LDAPSettings):
+    """
+    A replacement for backend.LDAPSettings that does not load settings
+    from django.conf.
+    """
+    def __init__(self, **kwargs):
+        for name, default in self.defaults.iteritems():
+            value = kwargs.get(name, default)
+            setattr(self, name, value)
+
+
+class MockLDAP(object):
+    """
+    This is a stand-in for the python-ldap module; it serves as both the ldap
+    module and the LDAPObject class. While it's temping to add some real LDAP
+    capabilities here, this is designed to remain as simple as possible, so as
+    to minimize the risk of creating bogus unit tests through a buggy test
+    harness.
+    
+    Simple operations can be simulated, but for nontrivial searches, the client
+    will have to seed the mock object with return values for expected API calls.
+    This may sound like cheating, but it's really no more so than a simulated
+    LDAP server. The fact is we can not require python-ldap to be installed in
+    order to run the unit tests, so all we can do is verify that LDAPBackend is
+    calling the APIs that we expect.
+
+    set_return_value takes the name of an API, a tuple of arguments, and a
+    return value. Every time an API is called, it looks for a predetermined
+    return value based on the arguments received. If it finds one, then it
+    returns it, or raises it if it's an Exception. If it doesn't find one, then
+    it tries to satisfy the request internally. If it can't, it raises a
+    PresetReturnRequiredError.
+    
+    At any time, the client may call ldap_methods_called_with_arguments() or
+    ldap_methods_called() to get a record of all of the LDAP API calls that have
+    been made, with or without arguments.
+    """
+    
+    class PresetReturnRequiredError(Exception): pass
+    
+    SCOPE_BASE = 0
+    SCOPE_ONELEVEL = 1
+    SCOPE_SUBTREE = 2
+    
+    class LDAPError(Exception): pass
+    class INVALID_CREDENTIALS(LDAPError): pass
+    class NO_SUCH_OBJECT(LDAPError): pass
+    
+    #
+    # Submodules
+    #
+    class dn(object):
+        def escape_dn_chars(s):
+            return s
+        escape_dn_chars = staticmethod(escape_dn_chars)
+
+    class filter(object):
+        def escape_filter_chars(s):
+            return s
+        escape_filter_chars = staticmethod(escape_filter_chars)
+
+
+    def __init__(self, directory):
+        """
+        directory is a complex structure with the entire contents of the
+        mock LDAP directory. directory must be a dictionary mapping
+        distinguished names to dictionaries of attributes. Each attribute
+        dictionary maps attribute names to lists of values. e.g.:
+        
+        {
+            "uid=alice,ou=users,dc=example,dc=com":
+            { 
+                "uid": ["alice"],
+                "userPassword": ["secret"],
+            },
+        }
+        """
+        self.directory = directory
+
+        self.reset()
+    
+    def reset(self):
+        """
+        Resets our recorded API calls and queued return values as well as
+        miscellaneous configuration options.
+        """
+        self.calls = []
+        self.return_value_maps = {}
+        self.options = {}
+        self.tls_enabled = False
+    
+    def set_return_value(self, api_name, arguments, value):
+        """
+        Stores a preset return value for a given API with a given set of
+        arguments.
+        """
+        self.return_value_maps.setdefault(api_name, {})[arguments] = value
+    
+    def ldap_methods_called_with_arguments(self):
+        """
+        Returns a list of 2-tuples, one for each API call made since the last
+        reset. Each tuple contains the name of the API and a dictionary of
+        arguments. Argument defaults are included.
+        """
+        return self.calls
+    
+    def ldap_methods_called(self):
+        """
+        Returns the list of API names called.
+        """
+        return [call[0] for call in self.calls]
+    
+    #
+    # Begin LDAP methods
+    #
+    
+    def set_option(self, option, invalue):
+        self._record_call('set_option', {
+            'option': option,
+            'invalue': invalue
+        })
+        
+        self.options[option] = invalue
+    
+    def initialize(self, uri, trace_level=0, trace_file=sys.stdout, trace_stack_limit=None):
+        self._record_call('initialize', {
+            'uri': uri,
+            'trace_level': trace_level,
+            'trace_file': trace_file,
+            'trace_stack_limit': trace_stack_limit
+        })
+        
+        value = self._get_return_value('initialize',
+            (uri, trace_level, trace_file, trace_stack_limit))
+        if value is None:
+            value = self
+        
+        return value
+
+    def simple_bind_s(self, who='', cred=''):
+        self._record_call('simple_bind_s', {
+            'who': who,
+            'cred': cred
+        })
+        
+        value = self._get_return_value('simple_bind_s', (who, cred))
+        if value is None:
+            value = self._simple_bind_s(who, cred)
+        
+        return value
+
+    def search_s(self, base, scope, filterstr='(objectClass=*)', attrlist=None, attrsonly=0):
+        self._record_call('search_s', {
+            'base': base,
+            'scope': scope,
+            'filterstr':filterstr,
+            'attrlist':attrlist,
+            'attrsonly':attrsonly
+        })
+        
+        value = self._get_return_value('search_s',
+            (base, scope, filterstr, attrlist, attrsonly))
+        if value is None:
+            value = self._search_s(base, scope, filterstr, attrlist, attrsonly)
+        
+        return value
+
+    def start_tls_s(self):
+        self.tls_enabled = True
+    
+    def compare_s(self, dn, attr, value):
+        self._record_call('compare_s', {
+            'dn': dn,
+            'attr': attr,
+            'value': value
+        })
+        
+        result = self._get_return_value('compare_s', (dn, attr, value))
+        if result is None:
+            result = self._compare_s(dn, attr, value)
+        
+        # print "compare_s('%s', '%s', '%s'): %d" % (dn, attr, value, result)
+        
+        return result
+
+    #
+    # Internal implementations
+    #
+
+    def _simple_bind_s(self, who='', cred=''):
+        success = False
+        
+        if(who == '' and cred == ''):
+            success = True 
+        elif self._compare_s(who, 'userPassword', cred):
+            success = True
+
+        if success:
+            return (97, []) # python-ldap returns this; I don't know what it means
+        else:
+            raise self.INVALID_CREDENTIALS('%s:%s' % (who, cred))
+    
+    def _compare_s(self, dn, attr, value):
+        try:
+            found = (value in self.directory[dn][attr])
+        except KeyError:
+            found = False
+        
+        return found and 1 or 0
+    
+    def _search_s(self, base, scope, filterstr, attrlist, attrsonly):
+        """
+        We can do a SCOPE_BASE search with the default filter. Beyond that,
+        you're on your own.
+        """
+        if scope != self.SCOPE_BASE:
+            raise self.PresetReturnRequiredError('search_s("%s", %d, "%s", "%s", %d)' %
+                (base, scope, filterstr, attrlist, attrsonly))
+        
+        if filterstr != '(objectClass=*)':
+            raise self.PresetReturnRequiredError('search_s("%s", %d, "%s", "%s", %d)' %
+                (base, scope, filterstr, attrlist, attrsonly))
+        
+        attrs = self.directory.get(base)
+        if attrs is None:
+            raise self.NO_SUCH_OBJECT()
+        
+        return [(base, attrs)]
+    
+    #
+    # Utils
+    #
+
+    def _record_call(self, api_name, arguments):
+        self.calls.append((api_name, arguments))
+
+    def _get_return_value(self, api_name, arguments):
+        try:
+            value = self.return_value_maps[api_name][arguments]
+        except KeyError:
+            value = None
+        
+        if isinstance(value, Exception):
+            raise value
+        
+        return value
+
+
+class LDAPTest(TestCase):
+    
+    # Following are the objecgs in our mock LDAP directory
+    alice = ("uid=alice,ou=people,o=test", {
+        "uid": ["alice"],
+        "objectClass": ["person", "organizationalPerson", "inetOrgPerson", "posixAccount"],
+        "userPassword": ["password"],
+        "uidNumber": ["1000"],
+        "gidNumber": ["1000"],
+        "givenName": ["Alice"],
+        "sn": ["Adams"]
+    })
+    bob = ("uid=bob,ou=people,o=test", {
+        "uid": ["bob"],
+        "objectClass": ["person", "organizationalPerson", "inetOrgPerson", "posixAccount"],
+        "userPassword": ["password"],
+        "uidNumber": ["1001"],
+        "gidNumber": ["50"],
+        "givenName": ["Robert"],
+        "sn": ["Barker"]
+    })
+    dressler = (u"uid=dreßler,ou=people,o=test".encode('utf-8'), {
+        "uid": [u"dreßler".encode('utf-8')],
+        "objectClass": ["person", "organizationalPerson", "inetOrgPerson", "posixAccount"],
+        "userPassword": ["password"],
+        "uidNumber": ["1002"],
+        "gidNumber": ["50"],
+        "givenName": ["Wolfgang"],
+        "sn": [u"Dreßler".encode('utf-8')]
+    })
+    nobody = ("uid=nobody,ou=people,o=test", {
+        "uid": ["nobody"],
+        "objectClass": ["person", "organizationalPerson", "inetOrgPerson", "posixAccount"],
+        "userPassword": ["password"],
+        "binaryAttr": ["\xb2"]  # Invalid UTF-8
+    })
+
+    # posixGroup objects
+    active_px = ("cn=active_px,ou=groups,o=test", {
+        "cn": ["active_px"],
+        "objectClass": ["posixGroup"],
+        "gidNumber": ["1000"],
+    })
+    staff_px = ("cn=staff_px,ou=groups,o=test", {
+        "cn": ["staff_px"],
+        "objectClass": ["posixGroup"],
+        "gidNumber": ["1001"],
+        "memberUid": ["alice"],
+    })
+    superuser_px = ("cn=superuser_px,ou=groups,o=test", {
+        "cn": ["superuser_px"],
+        "objectClass": ["posixGroup"],
+        "gidNumber": ["1002"],
+        "memberUid": ["alice"],
+    })
+
+    # groupOfUniqueName groups
+    active_gon = ("cn=active_gon,ou=groups,o=test", {
+        "cn": ["active_gon"],
+        "objectClass": ["groupOfNames"],
+        "member": ["uid=alice,ou=people,o=test"]
+    })
+    staff_gon = ("cn=staff_gon,ou=groups,o=test", {
+        "cn": ["staff_gon"],
+        "objectClass": ["groupOfNames"],
+        "member": ["uid=alice,ou=people,o=test"]
+    })
+    superuser_gon = ("cn=superuser_gon,ou=groups,o=test", {
+        "cn": ["superuser_gon"],
+        "objectClass": ["groupOfNames"],
+        "member": ["uid=alice,ou=people,o=test"]
+    })
+    
+    # Nested groups with a circular reference
+    parent_gon = ("cn=parent_gon,ou=groups,o=test", {
+        "cn": ["parent_gon"],
+        "objectClass": ["groupOfNames"],
+        "member": ["cn=nested_gon,ou=groups,o=test"]
+    })
+    nested_gon = ("cn=nested_gon,ou=groups,o=test", {
+        "cn": ["nested_gon"],
+        "objectClass": ["groupOfNames"],
+        "member": [
+            "uid=alice,ou=people,o=test",
+            "cn=circular_gon,ou=groups,o=test"
+        ]
+    })
+    circular_gon = ("cn=circular_gon,ou=groups,o=test", {
+        "cn": ["circular_gon"],
+        "objectClass": ["groupOfNames"],
+        "member": ["cn=parent_gon,ou=groups,o=test"]
+    })
+    
+
+    mock_ldap = MockLDAP({
+        alice[0]: alice[1],
+        bob[0]: bob[1],
+        dressler[0]: dressler[1],
+        nobody[0]: nobody[1],
+        active_gon[0]: active_gon[1],
+        staff_gon[0]: staff_gon[1],
+        superuser_gon[0]: superuser_gon[1],
+        parent_gon[0]: parent_gon[1],
+        nested_gon[0]: nested_gon[1],
+        circular_gon[0]: circular_gon[1],
+        active_px[0]: active_px[1],
+        staff_px[0]: staff_px[1],
+        superuser_px[0]: superuser_px[1],
+    })
+    
+
+    logging_configured = False
+    def configure_logger(cls):
+        if not cls.logging_configured:
+            logger = logging.getLogger('django_auth_ldap')
+            formatter = logging.Formatter("LDAP auth - %(levelname)s - %(message)s")
+            handler = logging.StreamHandler()
+        
+            handler.setLevel(logging.DEBUG)
+            handler.setFormatter(formatter)
+            logger.addHandler(handler)
+        
+            logger.setLevel(logging.CRITICAL)
+            
+            cls.logging_configured = True
+    configure_logger = classmethod(configure_logger)
+    
+
+    def setUp(self):
+        self.configure_logger()
+        self.mock_ldap.reset()
+
+        _LDAPConfig.ldap = self.mock_ldap
+        self.backend = backend.LDAPBackend()
+    
+    
+    def tearDown(self):
+        pass
+
+    
+    def test_options(self):
+        self._init_settings(
+            AUTH_LDAP_USER_DN_TEMPLATE='uid=%(user)s,ou=people,o=test',
+            AUTH_LDAP_CONNECTION_OPTIONS={'opt1': 'value1'}
+        )
+        
+        user = self.backend.authenticate(username='alice', password='password')
+        
+        self.assertEqual(self.mock_ldap.options, {'opt1': 'value1'})
+
+    def test_simple_bind(self):
+        self._init_settings(
+            AUTH_LDAP_USER_DN_TEMPLATE='uid=%(user)s,ou=people,o=test'
+        )
+        user_count = User.objects.count()
+        
+        user = self.backend.authenticate(username='alice', password='password')
+        
+        self.assert_(not user.has_usable_password())
+        self.assertEqual(user.username, 'alice')
+        self.assertEqual(User.objects.count(), user_count + 1)
+        self.assertEqual(self.mock_ldap.ldap_methods_called(),
+            ['initialize', 'simple_bind_s'])
+
+    def test_simple_bind_bad_user(self):
+        self._init_settings(
+            AUTH_LDAP_USER_DN_TEMPLATE='uid=%(user)s,ou=people,o=test'
+        )
+        user_count = User.objects.count()
+
+        user = self.backend.authenticate(username='evil_alice', password='password')
+
+        self.assert_(user is None)
+        self.assertEqual(User.objects.count(), user_count)
+        self.assertEqual(self.mock_ldap.ldap_methods_called(),
+            ['initialize', 'simple_bind_s'])
+
+    def test_simple_bind_bad_password(self):
+        self._init_settings(
+            AUTH_LDAP_USER_DN_TEMPLATE='uid=%(user)s,ou=people,o=test'
+        )
+        user_count = User.objects.count()
+
+        user = self.backend.authenticate(username='alice', password='bogus')
+
+        self.assert_(user is None)
+        self.assertEqual(User.objects.count(), user_count)
+        self.assertEqual(self.mock_ldap.ldap_methods_called(),
+            ['initialize', 'simple_bind_s'])
+    
+    def test_existing_user(self):
+        self._init_settings(
+            AUTH_LDAP_USER_DN_TEMPLATE='uid=%(user)s,ou=people,o=test'
+        )
+        User.objects.create(username='alice')
+        user_count = User.objects.count()
+        
+        user = self.backend.authenticate(username='alice', password='password')
+        
+        # Make sure we only created one user
+        self.assert_(user is not None)
+        self.assertEqual(User.objects.count(), user_count)
+
+    def test_convert_username(self):
+        class MyBackend(backend.LDAPBackend):
+            def ldap_to_django_username(self, username):
+                return 'ldap_%s' % username
+            def django_to_ldap_username(self, username):
+                return username[5:]
+        
+        self._init_settings(
+            AUTH_LDAP_USER_DN_TEMPLATE='uid=%(user)s,ou=people,o=test'
+        )
+        user_count = User.objects.count()
+        self.backend = MyBackend()
+        
+        user1 = self.backend.authenticate(username='alice', password='password')
+        user2 = self.backend.get_user(user1.pk)
+        
+        self.assertEqual(User.objects.count(), user_count + 1)
+        self.assertEqual(user1.username, 'ldap_alice')
+        self.assertEqual(user1.ldap_user._username, 'alice')
+        self.assertEqual(user1.ldap_username, 'alice')
+        self.assertEqual(user2.username, 'ldap_alice')
+        self.assertEqual(user2.ldap_user._username, 'alice')
+        self.assertEqual(user2.ldap_username, 'alice')
+
+    def test_search_bind(self):
+        self._init_settings(
+            AUTH_LDAP_USER_SEARCH=LDAPSearch(
+                "ou=people,o=test", self.mock_ldap.SCOPE_SUBTREE, '(uid=%(user)s)'
+                )
+            )
+        self.mock_ldap.set_return_value('search_s',
+            ("ou=people,o=test", 2, "(uid=alice)", None, 0), [self.alice])
+        user_count = User.objects.count()
+        
+        user = self.backend.authenticate(username='alice', password='password')
+        
+        self.assert_(user is not None)
+        self.assertEqual(User.objects.count(), user_count + 1)
+        self.assertEqual(self.mock_ldap.ldap_methods_called(),
+            ['initialize', 'simple_bind_s', 'search_s', 'simple_bind_s'])
+
+    def test_search_bind_no_user(self):
+        self._init_settings(
+            AUTH_LDAP_USER_SEARCH=LDAPSearch(
+                "ou=people,o=test", self.mock_ldap.SCOPE_SUBTREE, '(cn=%(user)s)'
+                )
+            )
+        self.mock_ldap.set_return_value('search_s',
+            ("ou=people,o=test", 2, "(cn=alice)", None, 0), [])
+
+        user = self.backend.authenticate(username='alice', password='password')
+
+        self.assert_(user is None)
+        self.assertEqual(self.mock_ldap.ldap_methods_called(),
+            ['initialize', 'simple_bind_s', 'search_s'])
+
+    def test_search_bind_multiple_users(self):
+        self._init_settings(
+            AUTH_LDAP_USER_SEARCH=LDAPSearch(
+                "ou=people,o=test", self.mock_ldap.SCOPE_SUBTREE, '(uid=*)'
+                )
+            )
+        self.mock_ldap.set_return_value('search_s',
+            ("ou=people,o=test", 2, "(uid=*)", None, 0), [self.alice, self.bob])
+
+        user = self.backend.authenticate(username='alice', password='password')
+
+        self.assert_(user is None)
+        self.assertEqual(self.mock_ldap.ldap_methods_called(),
+            ['initialize', 'simple_bind_s', 'search_s'])
+
+    def test_search_bind_bad_password(self):
+        self._init_settings(
+            AUTH_LDAP_USER_SEARCH=LDAPSearch(
+                "ou=people,o=test", self.mock_ldap.SCOPE_SUBTREE, '(uid=%(user)s)'
+                )
+            )
+        self.mock_ldap.set_return_value('search_s',
+            ("ou=people,o=test", 2, "(uid=alice)", None, 0), [self.alice])
+
+        user = self.backend.authenticate(username='alice', password='bogus')
+
+        self.assert_(user is None)
+        self.assertEqual(self.mock_ldap.ldap_methods_called(),
+            ['initialize', 'simple_bind_s', 'search_s', 'simple_bind_s'])
+
+    def test_search_bind_with_credentials(self):
+        self._init_settings(
+            AUTH_LDAP_BIND_DN='uid=bob,ou=people,o=test',
+            AUTH_LDAP_BIND_PASSWORD='password',
+            AUTH_LDAP_USER_SEARCH=LDAPSearch(
+                "ou=people,o=test", self.mock_ldap.SCOPE_SUBTREE, '(uid=%(user)s)'
+                )
+            )
+        self.mock_ldap.set_return_value('search_s',
+            ("ou=people,o=test", 2, "(uid=alice)", None, 0), [self.alice])
+
+        user = self.backend.authenticate(username='alice', password='password')
+
+        self.assert_(user is not None)
+        self.assert_(user.ldap_user is not None)
+        self.assertEqual(user.ldap_user.dn, self.alice[0])
+        self.assertEqual(user.ldap_user.attrs, self.alice[1])
+        self.assertEqual(self.mock_ldap.ldap_methods_called(),
+            ['initialize', 'simple_bind_s', 'search_s', 'simple_bind_s'])
+
+    def test_search_bind_with_bad_credentials(self):
+        self._init_settings(
+            AUTH_LDAP_BIND_DN='uid=bob,ou=people,o=test',
+            AUTH_LDAP_BIND_PASSWORD='bogus',
+            AUTH_LDAP_USER_SEARCH=LDAPSearch(
+                "ou=people,o=test", self.mock_ldap.SCOPE_SUBTREE, '(uid=%(user)s)'
+                )
+            )
+
+        user = self.backend.authenticate(username='alice', password='password')
+        
+        self.assert_(user is None)
+        self.assertEqual(self.mock_ldap.ldap_methods_called(),
+            ['initialize', 'simple_bind_s'])
+    
+    def test_unicode_user(self):
+        self._init_settings(
+            AUTH_LDAP_USER_DN_TEMPLATE='uid=%(user)s,ou=people,o=test',
+            AUTH_LDAP_USER_ATTR_MAP={'first_name': 'givenName', 'last_name': 'sn'}
+        )
+        
+        user = self.backend.authenticate(username=u'dreßler', password='password')
+        
+        self.assert_(user is not None)
+        self.assertEqual(user.username, u'dreßler')
+        self.assertEqual(user.last_name, u'Dreßler')
+    
+    def test_populate_user(self):
+        self._init_settings(
+            AUTH_LDAP_USER_DN_TEMPLATE='uid=%(user)s,ou=people,o=test',
+            AUTH_LDAP_USER_ATTR_MAP={'first_name': 'givenName', 'last_name': 'sn'}
+        )
+
+        user = self.backend.authenticate(username='alice', password='password')
+
+        self.assertEqual(user.username, 'alice')
+        self.assertEqual(user.first_name, 'Alice')
+        self.assertEqual(user.last_name, 'Adams')
+
+    def test_no_update_existing(self):
+        self._init_settings(
+            AUTH_LDAP_USER_DN_TEMPLATE='uid=%(user)s,ou=people,o=test',
+            AUTH_LDAP_USER_ATTR_MAP={'first_name': 'givenName', 'last_name': 'sn'},
+            AUTH_LDAP_ALWAYS_UPDATE_USER=False
+        )
+        User.objects.create(username='alice', first_name='Alicia', last_name='Astro')
+
+        alice = self.backend.authenticate(username='alice', password='password')
+        bob = self.backend.authenticate(username='bob', password='password')
+
+        self.assertEqual(alice.first_name, 'Alicia')
+        self.assertEqual(alice.last_name, 'Astro')
+        self.assertEqual(bob.first_name, 'Robert')
+        self.assertEqual(bob.last_name, 'Barker')
+
+    def test_require_group(self):
+        self._init_settings(
+            AUTH_LDAP_USER_DN_TEMPLATE='uid=%(user)s,ou=people,o=test',
+            AUTH_LDAP_GROUP_SEARCH=LDAPSearch('ou=groups,o=test', self.mock_ldap.SCOPE_SUBTREE),
+            AUTH_LDAP_GROUP_TYPE=MemberDNGroupType(member_attr='member'),
+            AUTH_LDAP_REQUIRE_GROUP="cn=active_gon,ou=groups,o=test"
+        )
+        
+        alice = self.backend.authenticate(username='alice', password='password')
+        bob = self.backend.authenticate(username='bob', password='password')
+        
+        self.assert_(alice is not None)
+        self.assert_(bob is None)
+        self.assertEqual(self.mock_ldap.ldap_methods_called(),
+            ['initialize', 'simple_bind_s', 'simple_bind_s', 'compare_s', 'initialize', 'simple_bind_s', 'simple_bind_s', 'compare_s'])
+
+    def test_dn_group_membership(self):
+        self._init_settings(
+            AUTH_LDAP_USER_DN_TEMPLATE='uid=%(user)s,ou=people,o=test',
+            AUTH_LDAP_GROUP_SEARCH=LDAPSearch('ou=groups,o=test', self.mock_ldap.SCOPE_SUBTREE),
+            AUTH_LDAP_GROUP_TYPE=MemberDNGroupType(member_attr='member'),
+            AUTH_LDAP_USER_FLAGS_BY_GROUP={
+                'is_active': "cn=active_gon,ou=groups,o=test",
+                'is_staff': "cn=staff_gon,ou=groups,o=test",
+                'is_superuser': "cn=superuser_gon,ou=groups,o=test"
+            }
+        )
+        
+        alice = self.backend.authenticate(username='alice', password='password')
+        bob = self.backend.authenticate(username='bob', password='password')
+        
+        self.assert_(alice.is_active)
+        self.assert_(alice.is_staff)
+        self.assert_(alice.is_superuser)
+        self.assert_(not bob.is_active)
+        self.assert_(not bob.is_staff)
+        self.assert_(not bob.is_superuser)
+
+    def test_posix_membership(self):
+        self._init_settings(
+            AUTH_LDAP_USER_DN_TEMPLATE='uid=%(user)s,ou=people,o=test',
+            AUTH_LDAP_GROUP_SEARCH=LDAPSearch('ou=groups,o=test', self.mock_ldap.SCOPE_SUBTREE),
+            AUTH_LDAP_GROUP_TYPE=PosixGroupType(),
+            AUTH_LDAP_USER_FLAGS_BY_GROUP={
+                'is_active': "cn=active_px,ou=groups,o=test",
+                'is_staff': "cn=staff_px,ou=groups,o=test",
+                'is_superuser': "cn=superuser_px,ou=groups,o=test"
+            }
+        )
+        
+        alice = self.backend.authenticate(username='alice', password='password')
+        bob = self.backend.authenticate(username='bob', password='password')
+        
+        self.assert_(alice.is_active)
+        self.assert_(alice.is_staff)
+        self.assert_(alice.is_superuser)
+        self.assert_(not bob.is_active)
+        self.assert_(not bob.is_staff)
+        self.assert_(not bob.is_superuser)
+    
+    def test_nested_dn_group_membership(self):
+        self._init_settings(
+            AUTH_LDAP_USER_DN_TEMPLATE='uid=%(user)s,ou=people,o=test',
+            AUTH_LDAP_GROUP_SEARCH=LDAPSearch('ou=groups,o=test', self.mock_ldap.SCOPE_SUBTREE),
+            AUTH_LDAP_GROUP_TYPE=NestedMemberDNGroupType(member_attr='member'),
+            AUTH_LDAP_USER_FLAGS_BY_GROUP={
+                'is_active': "cn=parent_gon,ou=groups,o=test",
+                'is_staff': "cn=parent_gon,ou=groups,o=test",
+            }
+        )
+        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.nested_gon]
+        )
+        self.mock_ldap.set_return_value('search_s',
+            ("ou=groups,o=test", 2, "(&(objectClass=*)(|(member=cn=active_gon,ou=groups,o=test)(member=cn=nested_gon,ou=groups,o=test)))", None, 0),
+            [self.parent_gon]
+        )
+        self.mock_ldap.set_return_value('search_s',
+            ("ou=groups,o=test", 2, "(&(objectClass=*)(|(member=cn=parent_gon,ou=groups,o=test)))", None, 0),
+            [self.circular_gon]
+        )
+        self.mock_ldap.set_return_value('search_s',
+            ("ou=groups,o=test", 2, "(&(objectClass=*)(|(member=cn=circular_gon,ou=groups,o=test)))", None, 0),
+            [self.nested_gon]
+        )
+        
+        self.mock_ldap.set_return_value('search_s',
+            ("ou=groups,o=test", 2, "(&(objectClass=*)(|(member=uid=bob,ou=people,o=test)))", None, 0),
+            []
+        )
+        
+        alice = self.backend.authenticate(username='alice', password='password')
+        bob = self.backend.authenticate(username='bob', password='password')
+        
+        self.assert_(alice.is_active)
+        self.assert_(alice.is_staff)
+        self.assert_(not bob.is_active)
+        self.assert_(not bob.is_staff)
+    
+    def test_posix_missing_attributes(self):
+        self._init_settings(
+            AUTH_LDAP_USER_DN_TEMPLATE='uid=%(user)s,ou=people,o=test',
+            AUTH_LDAP_GROUP_SEARCH=LDAPSearch('ou=groups,o=test', self.mock_ldap.SCOPE_SUBTREE),
+            AUTH_LDAP_GROUP_TYPE=PosixGroupType(),
+            AUTH_LDAP_USER_FLAGS_BY_GROUP={
+                'is_active': "cn=active_px,ou=groups,o=test"
+            }
+        )
+        
+        nobody = self.backend.authenticate(username='nobody', password='password')
+
+        self.assert_(not nobody.is_active)
+    
+    def test_dn_group_permissions(self):
+        self._init_settings(
+            AUTH_LDAP_USER_DN_TEMPLATE='uid=%(user)s,ou=people,o=test',
+            AUTH_LDAP_GROUP_SEARCH=LDAPSearch('ou=groups,o=test', self.mock_ldap.SCOPE_SUBTREE),
+            AUTH_LDAP_GROUP_TYPE=MemberDNGroupType(member_attr='member'),
+            AUTH_LDAP_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]
+        )
+        
+        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.assert_(self.backend.has_perm(alice, "auth.add_user"))
+        self.assert_(self.backend.has_module_perms(alice, "auth"))
+    
+    def test_posix_group_permissions(self):
+        self._init_settings(
+            AUTH_LDAP_USER_DN_TEMPLATE='uid=%(user)s,ou=people,o=test',
+            AUTH_LDAP_GROUP_SEARCH=LDAPSearch('ou=groups,o=test',
+                self.mock_ldap.SCOPE_SUBTREE, "(objectClass=posixGroup)"
+            ),
+            AUTH_LDAP_GROUP_TYPE=PosixGroupType(),
+            AUTH_LDAP_FIND_GROUP_PERMS=True
+        )
+        self._init_groups()
+        self.mock_ldap.set_return_value('search_s',
+            ("ou=groups,o=test", 2, "(&(objectClass=posixGroup)(|(gidNumber=1000)(memberUid=alice)))", None, 0),
+            [self.active_px, self.staff_px, self.superuser_px]
+        )
+        
+        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.assert_(self.backend.has_perm(alice, "auth.add_user"))
+        self.assert_(self.backend.has_module_perms(alice, "auth"))
+
+    def test_foreign_user_permissions(self):
+        self._init_settings(
+            AUTH_LDAP_USER_DN_TEMPLATE='uid=%(user)s,ou=people,o=test',
+            AUTH_LDAP_GROUP_SEARCH=LDAPSearch('ou=groups,o=test', self.mock_ldap.SCOPE_SUBTREE),
+            AUTH_LDAP_GROUP_TYPE=MemberDNGroupType(member_attr='member'),
+            AUTH_LDAP_FIND_GROUP_PERMS=True
+        )
+        self._init_groups()
+        
+        alice = User.objects.create(username='alice')
+
+        self.assertEqual(self.backend.get_group_permissions(alice), set())
+    
+    def test_group_cache(self):
+        self._init_settings(
+            AUTH_LDAP_USER_DN_TEMPLATE='uid=%(user)s,ou=people,o=test',
+            AUTH_LDAP_GROUP_SEARCH=LDAPSearch('ou=groups,o=test', self.mock_ldap.SCOPE_SUBTREE),
+            AUTH_LDAP_GROUP_TYPE=MemberDNGroupType(member_attr='member'),
+            AUTH_LDAP_FIND_GROUP_PERMS=True,
+            AUTH_LDAP_CACHE_GROUPS=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]
+        )
+        self.mock_ldap.set_return_value('search_s',
+            ("ou=groups,o=test", 2, "(&(objectClass=*)(member=uid=bob,ou=people,o=test))", None, 0),
+            []
+        )
+        
+        alice_id = User.objects.create(username='alice').pk
+        bob_id = User.objects.create(username='bob').pk
+
+        # Check permissions twice for each user
+        for i in range(2):
+            alice = self.backend.get_user(alice_id)
+            self.assertEqual(self.backend.get_group_permissions(alice),
+                set(["auth.add_user", "auth.change_user"]))
+
+            bob = self.backend.get_user(bob_id)
+            self.assertEqual(self.backend.get_group_permissions(bob), set())
+        
+        # Should have executed one LDAP search per user
+        self.assertEqual(self.mock_ldap.ldap_methods_called(),
+            ['initialize', 'simple_bind_s', 'search_s', 'initialize', 'simple_bind_s', 'search_s'])
+
+    def test_group_mirroring(self):
+        self._init_settings(
+            AUTH_LDAP_USER_DN_TEMPLATE='uid=%(user)s,ou=people,o=test',
+            AUTH_LDAP_GROUP_SEARCH=LDAPSearch('ou=groups,o=test',
+                self.mock_ldap.SCOPE_SUBTREE, "(objectClass=posixGroup)"
+            ),
+            AUTH_LDAP_GROUP_TYPE=PosixGroupType(),
+            AUTH_LDAP_MIRROR_GROUPS=True,
+        )
+        self.mock_ldap.set_return_value('search_s',
+            ("ou=groups,o=test", 2, "(&(objectClass=posixGroup)(|(gidNumber=1000)(memberUid=alice)))", None, 0),
+            [self.active_px, self.staff_px, self.superuser_px]
+        )
+    
+        self.assertEqual(Group.objects.count(), 0)
+
+        alice = self.backend.authenticate(username='alice', password='password')
+        
+        self.assertEqual(Group.objects.count(), 3)
+        self.assertEqual(set(alice.groups.all()), set(Group.objects.all()))
+
+    def test_nested_group_mirroring(self):
+        self._init_settings(
+            AUTH_LDAP_USER_DN_TEMPLATE='uid=%(user)s,ou=people,o=test',
+            AUTH_LDAP_GROUP_SEARCH=LDAPSearch('ou=groups,o=test', self.mock_ldap.SCOPE_SUBTREE),
+            AUTH_LDAP_GROUP_TYPE=NestedMemberDNGroupType(member_attr='member'),
+            AUTH_LDAP_MIRROR_GROUPS=True,
+        )
+        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.nested_gon]
+        )
+        self.mock_ldap.set_return_value('search_s',
+            ("ou=groups,o=test", 2, "(&(objectClass=*)(|(member=cn=active_gon,ou=groups,o=test)(member=cn=nested_gon,ou=groups,o=test)))", None, 0),
+            [self.parent_gon]
+        )
+        self.mock_ldap.set_return_value('search_s',
+            ("ou=groups,o=test", 2, "(&(objectClass=*)(|(member=cn=parent_gon,ou=groups,o=test)))", None, 0),
+            [self.circular_gon]
+        )
+        self.mock_ldap.set_return_value('search_s',
+            ("ou=groups,o=test", 2, "(&(objectClass=*)(|(member=cn=circular_gon,ou=groups,o=test)))", None, 0),
+            [self.nested_gon]
+        )
+        
+        alice = self.backend.authenticate(username='alice', password='password')
+        
+        self.assertEqual(Group.objects.count(), 4)
+        self.assertEqual(set(Group.objects.all().values_list('name', flat=True)),
+            set(['active_gon', 'nested_gon', 'parent_gon', 'circular_gon']))
+        self.assertEqual(set(alice.groups.all()), set(Group.objects.all()))
+
+    def test_authorize_external_users(self):
+        self._init_settings(
+            AUTH_LDAP_USER_DN_TEMPLATE='uid=%(user)s,ou=people,o=test',
+            AUTH_LDAP_GROUP_SEARCH=LDAPSearch('ou=groups,o=test', self.mock_ldap.SCOPE_SUBTREE),
+            AUTH_LDAP_GROUP_TYPE=MemberDNGroupType(member_attr='member'),
+            AUTH_LDAP_FIND_GROUP_PERMS=True,
+            AUTH_LDAP_AUTHORIZE_ALL_USERS=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]
+        )
+        
+        alice = User.objects.create(username='alice')
+        
+        self.assertEqual(self.backend.get_group_permissions(alice), set(["auth.add_user", "auth.change_user"]))
+
+    def test_create_without_auth(self):
+        self._init_settings(
+            AUTH_LDAP_USER_DN_TEMPLATE='uid=%(user)s,ou=people,o=test',
+        )
+        
+        alice = self.backend.populate_user('alice')
+        bob = self.backend.populate_user('bob')
+        
+        self.assert_(alice is not None)
+        self.assertEqual(alice.first_name, u"")
+        self.assertEqual(alice.last_name, u"")
+        self.assert_(alice.is_active)
+        self.assert_(not alice.is_staff)
+        self.assert_(not alice.is_superuser)
+        self.assert_(bob is not None)
+        self.assertEqual(bob.first_name, u"")
+        self.assertEqual(bob.last_name, u"")
+        self.assert_(bob.is_active)
+        self.assert_(not bob.is_staff)
+        self.assert_(not bob.is_superuser)
+
+    def test_populate_without_auth(self):
+        self._init_settings(
+            AUTH_LDAP_USER_DN_TEMPLATE='uid=%(user)s,ou=people,o=test',
+            AUTH_LDAP_ALWAYS_UPDATE_USER=False,
+            AUTH_LDAP_USER_ATTR_MAP={'first_name': 'givenName', 'last_name': 'sn'},
+            AUTH_LDAP_GROUP_SEARCH=LDAPSearch('ou=groups,o=test', self.mock_ldap.SCOPE_SUBTREE),
+            AUTH_LDAP_GROUP_TYPE=GroupOfNamesType(),
+            AUTH_LDAP_USER_FLAGS_BY_GROUP={
+                'is_active': "cn=active_gon,ou=groups,o=test",
+                'is_staff': "cn=staff_gon,ou=groups,o=test",
+                'is_superuser': "cn=superuser_gon,ou=groups,o=test"
+            }
+        )
+        
+        User.objects.create(username='alice')
+        User.objects.create(username='bob')
+        
+        alice = self.backend.populate_user('alice')
+        bob = self.backend.populate_user('bob')
+        
+        self.assert_(alice is not None)
+        self.assertEqual(alice.first_name, u"Alice")
+        self.assertEqual(alice.last_name, u"Adams")
+        self.assert_(alice.is_active)
+        self.assert_(alice.is_staff)
+        self.assert_(alice.is_superuser)
+        self.assert_(bob is not None)
+        self.assertEqual(bob.first_name, u"Robert")
+        self.assertEqual(bob.last_name, u"Barker")
+        self.assert_(not bob.is_active)
+        self.assert_(not bob.is_staff)
+        self.assert_(not bob.is_superuser)
+
+    def test_start_tls_missing(self):
+        self._init_settings(
+            AUTH_LDAP_USER_DN_TEMPLATE='uid=%(user)s,ou=people,o=test',
+            AUTH_LDAP_START_TLS=False,
+        )
+
+        self.assert_(not self.mock_ldap.tls_enabled)
+        self.backend.authenticate(username='alice', password='password')
+        self.assert_(not self.mock_ldap.tls_enabled)
+
+    def test_start_tls(self):
+        self._init_settings(
+            AUTH_LDAP_USER_DN_TEMPLATE='uid=%(user)s,ou=people,o=test',
+            AUTH_LDAP_START_TLS=True,
+        )
+
+        self.assert_(not self.mock_ldap.tls_enabled)
+        self.backend.authenticate(username='alice', password='password')
+        self.assert_(self.mock_ldap.tls_enabled)
+
+    def test_null_search_results(self):
+        """
+        Reportedly, some servers under some circumstances can return search
+        results of the form (None, '<some ldap url>'). We're not sure what they
+        are, but we filter those out so we don't trip over them.
+        """
+        self._init_settings(
+            AUTH_LDAP_USER_SEARCH=LDAPSearch(
+                "ou=people,o=test", self.mock_ldap.SCOPE_SUBTREE, '(uid=%(user)s)'
+                )
+            )
+        self.mock_ldap.set_return_value('search_s',
+            ("ou=people,o=test", 2, "(uid=alice)", None, 0), [self.alice, (None, '')])
+
+        self.backend.authenticate(username='alice', password='password')
+
+
+    def _init_settings(self, **kwargs):
+        backend.ldap_settings = TestSettings(**kwargs)
+    
+    def _init_groups(self):
+        permissions = [
+            Permission.objects.get(codename="add_user"),
+            Permission.objects.get(codename="change_user")
+        ]
+
+        active_gon = Group.objects.create(name='active_gon')
+        active_gon.permissions.add(*permissions)
+
+        active_px = Group.objects.create(name='active_px')
+        active_px.permissions.add(*permissions)

BIN
desktop/core/ext-py/django-auth-ldap-1.0.7/docs/._.DS_Store


BIN
desktop/core/ext-py/django-auth-ldap-1.0.7/docs/._conf.py


BIN
desktop/core/ext-py/django-auth-ldap-1.0.7/docs/._index.rst


+ 92 - 0
desktop/core/ext-py/django-auth-ldap-1.0.7/docs/Makefile

@@ -0,0 +1,92 @@
+# Makefile for Sphinx documentation
+#
+
+# You can set these variables from the command line.
+SPHINXOPTS	  =
+SPHINXBUILD	  = sphinx-build
+PAPER		  =
+
+# Internal variables.
+PAPEROPT_a4		= -D latex_paper_size=a4
+PAPEROPT_letter = -D latex_paper_size=letter
+ALLSPHINXOPTS	= -d _build/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
+
+.PHONY: help clean html dirhtml pickle json htmlhelp qthelp latex changes linkcheck doctest
+
+help:
+	@echo "Please use 'make <target>' where <target> is one of"
+	@echo "	 html	   to make standalone HTML files"
+	@echo "	 dirhtml   to make HTML files named index.html in directories"
+	@echo "	 pickle	   to make pickle files"
+	@echo "	 json	   to make JSON files"
+	@echo "	 htmlhelp  to make HTML files and a HTML help project"
+	@echo "	 qthelp	   to make HTML files and a qthelp project"
+	@echo "	 latex	   to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
+	@echo "	 changes   to make an overview of all changed/added/deprecated items"
+	@echo "	 linkcheck to check all external links for integrity"
+	@echo "	 doctest   to run all doctests embedded in the documentation (if enabled)"
+
+clean:
+	-rm -rf _build
+
+html:
+	$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) _build/html
+	@echo
+	@echo "Build finished. The HTML pages are in _build/html."
+
+dirhtml:
+	$(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) _build/dirhtml
+	@echo
+	@echo "Build finished. The HTML pages are in _build/dirhtml."
+
+pickle:
+	$(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) _build/pickle
+	@echo
+	@echo "Build finished; now you can process the pickle files."
+
+json:
+	$(SPHINXBUILD) -b json $(ALLSPHINXOPTS) _build/json
+	@echo
+	@echo "Build finished; now you can process the JSON files."
+
+htmlhelp:
+	$(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) _build/htmlhelp
+	@echo
+	@echo "Build finished; now you can run HTML Help Workshop with the" \
+		  ".hhp project file in _build/htmlhelp."
+
+qthelp:
+	$(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) _build/qthelp
+	@echo
+	@echo "Build finished; now you can run "qcollectiongenerator" with the" \
+		  ".qhcp project file in _build/qthelp, like this:"
+	@echo "# qcollectiongenerator _build/qthelp/django-auth-ldap.qhcp"
+	@echo "To view the help file:"
+	@echo "# assistant -collectionFile _build/qthelp/django-auth-ldap.qhc"
+
+latex:
+	$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) _build/latex
+	@echo
+	@echo "Build finished; the LaTeX files are in _build/latex."
+	@echo "Run 'make all-pdf' or 'make all-ps' in that directory to" \
+		  "run these through (pdf)latex."
+
+changes:
+	$(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) _build/changes
+	@echo
+	@echo "The overview file is in _build/changes."
+
+linkcheck:
+	$(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) _build/linkcheck
+	@echo
+	@echo "Link check complete; look for any errors in the above output " \
+		  "or in _build/linkcheck/output.txt."
+
+doctest:
+	$(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) _build/doctest
+	@echo "Testing of doctests in the sources finished, look at the " \
+		  "results in _build/doctest/output.txt."
+
+zip:
+	rm _build/html.zip || true
+	cd _build/html && zip -R ../html.zip '*' -x .buildinfo -x '_sources/*'

+ 196 - 0
desktop/core/ext-py/django-auth-ldap-1.0.7/docs/conf.py

@@ -0,0 +1,196 @@
+# -*- coding: utf-8 -*-
+#
+# django-auth-ldap documentation build configuration file, created by
+# sphinx-quickstart on Wed Sep 23 18:06:43 2009.
+#
+# This file is execfile()d with the current directory set to its containing dir.
+#
+# Note that not all possible configuration values are present in this
+# autogenerated file.
+#
+# All configuration values have a default; values that are commented out
+# serve to show the default.
+
+import sys, os
+
+# If extensions (or modules to document with autodoc) are in another directory,
+# add these directories to sys.path here. If the directory is relative to the
+# documentation root, use os.path.abspath to make it absolute, like shown here.
+#sys.path.append(os.path.abspath('.'))
+
+# -- General configuration -----------------------------------------------------
+
+# Add any Sphinx extension module names here, as strings. They can be extensions
+# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
+extensions = []
+
+# Add any paths that contain templates here, relative to this directory.
+templates_path = ['_templates']
+
+# The suffix of source filenames.
+source_suffix = '.rst'
+
+# The encoding of source files.
+source_encoding = 'utf-8'
+
+# The master toctree document.
+master_doc = 'index'
+
+# General information about the project.
+project = u'django-auth-ldap'
+copyright = u'2009, Peter Sagerson'
+
+# The version info for the project you're documenting, acts as replacement for
+# |version| and |release|, also used in various other places throughout the
+# built documents.
+#
+# The short X.Y version.
+version = '1.0'
+# The full version, including alpha/beta/rc tags.
+release = '1.0.7'
+
+# The language for content autogenerated by Sphinx. Refer to documentation
+# for a list of supported languages.
+#language = None
+
+# There are two options for replacing |today|: either, you set today to some
+# non-false value, then it is used:
+#today = ''
+# Else, today_fmt is used as the format for a strftime call.
+#today_fmt = '%B %d, %Y'
+
+# List of documents that shouldn't be included in the build.
+#unused_docs = []
+
+# List of directories, relative to source directory, that shouldn't be searched
+# for source files.
+exclude_trees = ['_build']
+
+# The reST default role (used for this markup: `text`) to use for all documents.
+#default_role = None
+
+# If true, '()' will be appended to :func: etc. cross-reference text.
+#add_function_parentheses = True
+
+# If true, the current module name will be prepended to all description
+# unit titles (such as .. function::).
+#add_module_names = True
+
+# If true, sectionauthor and moduleauthor directives will be shown in the
+# output. They are ignored by default.
+#show_authors = False
+
+# The name of the Pygments (syntax highlighting) style to use.
+pygments_style = 'sphinx'
+
+# A list of ignored prefixes for module index sorting.
+#modindex_common_prefix = []
+
+
+# -- Options for HTML output ---------------------------------------------------
+
+# The theme to use for HTML and HTML Help pages.  Major themes that come with
+# Sphinx are currently 'default' and 'sphinxdoc'.
+html_theme = 'default'
+
+# Theme options are theme-specific and customize the look and feel of a theme
+# further.  For a list of options available for each theme, see the
+# documentation.
+html_theme_options = {
+    "rightsidebar": True,
+}
+
+# Add any paths that contain custom themes here, relative to this directory.
+#html_theme_path = []
+
+# The name for this set of Sphinx documents.  If None, it defaults to
+# "<project> v<release> documentation".
+#html_title = None
+
+# A shorter title for the navigation bar.  Default is the same as html_title.
+#html_short_title = None
+
+# The name of an image file (relative to this directory) to place at the top
+# of the sidebar.
+#html_logo = None
+
+# The name of an image file (within the static path) to use as favicon of the
+# docs.  This file should be a Windows icon file (.ico) being 16x16 or 32x32
+# pixels large.
+#html_favicon = None
+
+# Add any paths that contain custom static files (such as style sheets) here,
+# relative to this directory. They are copied after the builtin static files,
+# so a file named "default.css" will overwrite the builtin "default.css".
+#html_static_path = ['_static']
+
+# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
+# using the given strftime format.
+#html_last_updated_fmt = '%b %d, %Y'
+
+# If true, SmartyPants will be used to convert quotes and dashes to
+# typographically correct entities.
+#html_use_smartypants = True
+
+# Custom sidebar templates, maps document names to template names.
+#html_sidebars = {}
+
+# Additional templates that should be rendered to pages, maps page names to
+# template names.
+#html_additional_pages = {}
+
+# If false, no module index is generated.
+#html_use_modindex = True
+
+# If false, no index is generated.
+#html_use_index = True
+
+# If true, the index is split into individual pages for each letter.
+#html_split_index = False
+
+# If true, links to the reST sources are added to the pages.
+#html_show_sourcelink = True
+
+# If true, an OpenSearch description file will be output, and all pages will
+# contain a <link> tag referring to it.  The value of this option must be the
+# base URL from which the finished HTML is served.
+#html_use_opensearch = ''
+
+# If nonempty, this is the file name suffix for HTML files (e.g. ".xhtml").
+#html_file_suffix = ''
+
+# Output file base name for HTML help builder.
+htmlhelp_basename = 'django-auth-ldapdoc'
+
+
+# -- Options for LaTeX output --------------------------------------------------
+
+# The paper size ('letter' or 'a4').
+#latex_paper_size = 'letter'
+
+# The font size ('10pt', '11pt' or '12pt').
+#latex_font_size = '10pt'
+
+# Grouping the document tree into LaTeX files. List of tuples
+# (source start file, target name, title, author, documentclass [howto/manual]).
+latex_documents = [
+  ('index', 'django-auth-ldap.tex', u'django-auth-ldap Documentation',
+   u'Peter Sagerson', 'manual'),
+]
+
+# The name of an image file (relative to this directory) to place at the top of
+# the title page.
+#latex_logo = None
+
+# For "manual" documents, if this is true, then toplevel headings are parts,
+# not chapters.
+#latex_use_parts = False
+
+# Additional stuff for the LaTeX preamble.
+#latex_preamble = ''
+
+# Documents to append as an appendix to all manuals.
+#latex_appendices = []
+
+# If false, no module index is generated.
+#latex_use_modindex = True

+ 853 - 0
desktop/core/ext-py/django-auth-ldap-1.0.7/docs/index.rst

@@ -0,0 +1,853 @@
+================================
+Django authentication using LDAP
+================================
+
+This authentication backend enables a Django project to authenticate against any
+LDAP server. To use it, add :class:`django_auth_ldap.backend.LDAPBackend` to
+AUTHENTICATION_BACKENDS. It is not necessary to add `django_auth_ldap` to
+INSTALLED_APPLICATIONS unless you would like to run the unit tests. LDAP
+configuration can be as simple as a single distinguished name template, but
+there are many rich options for working with
+:class:`~django.contrib.auth.models.User` objects, groups, and permissions. This
+backend depends on the `python-ldap <http://www.python-ldap.org/>`_ module.
+
+.. note::
+
+    :class:`~django_auth_ldap.backend.LDAPBackend` does not inherit from
+    :class:`~django.contrib.auth.backends.ModelBackend`. It is possible to use
+    :class:`~django_auth_ldap.backend.LDAPBackend` exclusively by configuring it
+    to draw group membership from the LDAP server. However, if you would like to
+    assign permissions to individual users or add users to groups within Django,
+    you'll need to have both backends installed:
+
+    .. code-block:: python
+
+        AUTHENTICATION_BACKENDS = (
+            'django_auth_ldap.backend.LDAPBackend',
+            'django.contrib.auth.backends.ModelBackend',
+        )
+
+
+Configuring basic authentication
+================================
+
+If your LDAP server isn't running locally on the default port, you'll want to
+start by setting :ref:`AUTH_LDAP_SERVER_URI` to point to your server.
+
+.. code-block:: python
+
+    AUTH_LDAP_SERVER_URI = "ldap://ldap.example.com"
+
+That done, the first step is to authenticate a username and password against the
+LDAP service. There are two ways to do this, called search/bind and simply bind.
+The first one involves connecting to the LDAP server either anonymously or with
+a fixed account and searching for the distinguished name of the authenticating
+user. Then we can attempt to bind again with the user's password. The second
+method is to derive the user's DN from his username and attempt to bind as the
+user directly.
+
+Because LDAP searches appear elsewhere in the configuration, the
+:class:`~django_auth_ldap.config.LDAPSearch` class is provided to encapsulate
+search information. In this case, the filter parameter should contain the
+placeholder ``%(user)s``. A simple configuration for the search/bind approach
+looks like this (some defaults included for completeness)::
+
+    import ldap
+    from django_auth_ldap.config import LDAPSearch
+
+    AUTH_LDAP_BIND_DN = ""
+    AUTH_LDAP_BIND_PASSWORD = ""
+    AUTH_LDAP_USER_SEARCH = LDAPSearch("ou=users,dc=example,dc=com",
+        ldap.SCOPE_SUBTREE, "(uid=%(user)s)")
+
+This will perform an anonymous bind, search under
+``"ou=users,dc=example,dc=com"`` for an object with a uid matching the user's
+name, and try to bind using that DN and the user's password. The search must
+return exactly one result or authentication will fail. If you can't search
+anonymously, you can set :ref:`AUTH_LDAP_BIND_DN` to the distinguished name of
+an authorized user and :ref:`AUTH_LDAP_BIND_PASSWORD` to the password.
+
+To skip the search phase, set :ref:`AUTH_LDAP_USER_DN_TEMPLATE` to a template
+that will produce the authenticating user's DN directly. This template should
+have one placeholder, ``%(user)s``. If the previous example had used
+``ldap.SCOPE_ONELEVEL``, the following would be a more straightforward (and
+efficient) equivalent::
+
+    AUTH_LDAP_USER_DN_TEMPLATE = "uid=%(user)s,ou=users,dc=example,dc=com"
+
+By default, LDAP connections are unencrypted and make no attempt to protect
+sensitive information, such as passwords. When communicating with an LDAP server
+on localhost or on a local network, this might be fine. If you need a secure
+connection to the LDAP server, you can either use an ``ldaps://`` URL or enable
+the StartTLS extension. The latter is generally the preferred mechanism. To
+enable StartTLS, set :ref:`AUTH_LDAP_START_TLS` to ``True``::
+
+    AUTH_LDAP_START_TLS = True
+
+
+Working with groups
+===================
+
+Working with groups in LDAP can be a tricky business, mostly because there are
+so many different kinds. This module includes an extensible API for working with
+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:
+
+    * :class:`~django_auth_ldap.config.PosixGroupType`
+    * :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:
+
+    * :class:`~django_auth_ldap.config.GroupOfNamesType`
+    * :class:`~django_auth_ldap.config.NestedGroupOfNamesType`
+    * :class:`~django_auth_ldap.config.GroupOfUniqueNamesType`
+    * :class:`~django_auth_ldap.config.NestedGroupOfUniqueNamesType`
+    * :class:`~django_auth_ldap.config.ActiveDirectoryGroupType`
+    * :class:`~django_auth_ldap.config.NestedActiveDirectoryGroupType`
+
+To get started, you'll need to provide some basic information about your LDAP
+groups. :ref:`AUTH_LDAP_GROUP_SEARCH` is an
+:class:`~django_auth_ldap.config.LDAPSearch` object that identifies the set of
+relevant group objects. That is, all groups that users might belong to as well
+as any others that we might need to know about (in the case of nested groups,
+for example). :ref:`AUTH_LDAP_GROUP_TYPE` is an instance of the class
+corresponding to the type of group that will be returned by
+:ref:`AUTH_LDAP_GROUP_SEARCH`. All groups referenced elsewhere in the
+configuration must be of this type and part of the search results.
+
+.. code-block:: python
+
+    import ldap
+    from django_auth_ldap.config import LDAPSearch, GroupOfNamesType
+    
+    AUTH_LDAP_GROUP_SEARCH = LDAPSearch("ou=groups,dc=example,dc=com",
+        ldap.SCOPE_SUBTREE, "(objectClass=groupOfNames)"
+    )
+    AUTH_LDAP_GROUP_TYPE = GroupOfNamesType()
+
+The simplest use of groups is to limit the users who are allowed to log in. If
+:ref:`AUTH_LDAP_REQUIRE_GROUP` is set, then only users who are members of that
+group will successfully authenticate::
+
+    AUTH_LDAP_REQUIRE_GROUP = "cn=enabled,ou=groups,dc=example,dc=com"
+
+More advanced uses of groups are covered in the next two sections.
+
+
+User objects
+============
+
+Authenticating against an external source is swell, but Django's auth module is
+tightly bound to the :class:`django.contrib.auth.models.User` model. Thus, when
+a user logs in, we have to create a :class:`~django.contrib.auth.models.User`
+object to represent him in the database.
+
+The only required field for a user is the username, which we obviously have. The
+:class:`~django.contrib.auth.models.User` model is picky about the characters
+allowed in usernames, so :class:`~django_auth_ldap.backend.LDAPBackend` includes
+a pair of hooks,
+:meth:`~django_auth_ldap.backend.LDAPBackend.ldap_to_django_username` and
+:meth:`~django_auth_ldap.backend.LDAPBackend.django_to_ldap_username`, to
+translate between LDAP usernames and Django usernames. You'll need this, for
+example, if your LDAP names have periods in them. You can subclass
+:class:`~django_auth_ldap.backend.LDAPBackend` to implement these hooks; by
+default the username is not modified. :class:`~django.contrib.auth.models.User`
+objects that are authenticated by :class:`~django_auth_ldap.backend.LDAPBackend`
+will have an :attr:`~django.contrib.auth.models.User.ldap_username` attribute
+with the original (LDAP) username.
+:attr:`~django.contrib.auth.models.User.username` will, of course, be the Django
+username.
+
+LDAP directories tend to contain much more information about users that you may
+wish to propagate. A pair of settings, :ref:`AUTH_LDAP_USER_ATTR_MAP` and
+:ref:`AUTH_LDAP_PROFILE_ATTR_MAP`, serve to copy directory information into
+:class:`~django.contrib.auth.models.User` and profile objects. These are
+dictionaries that map user and profile model keys, respectively, to LDAP
+attribute names::
+
+    AUTH_LDAP_USER_ATTR_MAP = {"first_name": "givenName", "last_name": "sn"}
+    AUTH_LDAP_PROFILE_ATTR_MAP = {"home_directory": "homeDirectory"}
+
+Only string fields can be mapped to attributes. Boolean fields can be defined by
+group membership::
+
+    AUTH_LDAP_USER_FLAGS_BY_GROUP = {
+        "is_active": "cn=active,ou=groups,dc=example,dc=com",
+        "is_staff": "cn=staff,ou=groups,dc=example,dc=com",
+        "is_superuser": "cn=superuser,ou=groups,dc=example,dc=com"
+    }
+
+By default, all mapped user fields will be updated each time the user logs in.
+To disable this, set :ref:`AUTH_LDAP_ALWAYS_UPDATE_USER` to ``False``. If you
+need to populate a user outside of the authentication process—for example, to
+create associated model objects before the user logs in for the first time—you
+can call :meth:`django_auth_ldap.backend.LDAPBackend.populate_user`. You'll
+need an instance of :class:`~django_auth_ldap.backend.LDAPBackend`, which you
+can create yourself if necessary.
+
+If you need to access multi-value attributes or there is some other reason that
+the above is inadequate, you can also access the user's raw LDAP attributes.
+``user.ldap_user`` is an object with two public properties:
+
+    * ``dn``: The user's distinguished name.
+    * ``attrs``: The user's LDAP attributes as a dictionary of lists of string
+      values.
+
+Python-ldap returns all attribute values as utf8-encoded strings. For
+convenience, this module will try to decode all values into Unicode strings. Any
+string that can not be successfully decoded will be left as-is; this may apply
+to binary values such as Active Directory's objectSid.
+
+.. note::
+
+    Users created by :class:`~django_auth_ldap.backend.LDAPBackend` will have an
+    unusable password set. This will only happen when the user is created, so if
+    you set a valid password in Django, the user will be able to log in through
+    :class:`~django.contrib.auth.backends.ModelBackend` (if configured) even if
+    he is rejected by LDAP. This is not generally recommended, but could be
+    useful as a fail-safe for selected users in case the LDAP server is
+    unavailable.
+
+
+Permissions
+===========
+
+Groups are useful for more than just populating the user's ``is_*`` fields.
+:class:`~django_auth_ldap.backend.LDAPBackend` would not be complete without
+some way to turn a user's LDAP group memberships into Django model permissions.
+In fact, there are two ways to do this.
+
+Ultimately, both mechanisms need some way to map LDAP groups to Django groups.
+Implementations of :class:`~django_auth_ldap.config.LDAPGroupType` will have an
+algorithm for deriving the Django group name from the LDAP group. Clients that
+need to modify this behavior can subclass the
+:class:`~django_auth_ldap.config.LDAPGroupType` class. All of the built-in
+implementations take a ``name_attr`` argument to ``__init__``, which
+specifies the LDAP attribute from which to take the Django group name. By
+default, the ``cn`` attribute is used.
+
+The least invasive way to map group permissions is to set
+:ref:`AUTH_LDAP_FIND_GROUP_PERMS` to ``True``.
+:class:`~django_auth_ldap.backend.LDAPBackend` will then find all of the LDAP
+groups that a user belongs to, map them to Django groups, and load the
+permissions for those groups. You will need to create the Django groups
+yourself, generally through the admin interface.
+
+To minimize traffic to the LDAP server,
+:class:`~django_auth_ldap.backend.LDAPBackend` can make use of Django's cache
+framework to keep a copy of a user's LDAP group memberships. To enable this
+feature, set :ref:`AUTH_LDAP_CACHE_GROUPS` to ``True``. You can also set
+:ref:`AUTH_LDAP_GROUP_CACHE_TIMEOUT` to override the timeout of cache entries
+(in seconds).
+
+.. code-block:: python
+
+    AUTH_LDAP_CACHE_GROUPS = True
+    AUTH_LDAP_GROUP_CACHE_TIMEOUT = 300
+
+The second way to turn LDAP group memberships into permissions is to mirror the
+groups themselves. If :ref:`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.
+
+This approach has two main differences from :ref:`AUTH_LDAP_FIND_GROUP_PERMS`.
+First, :ref:`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. The second difference is
+that with :ref:`AUTH_LDAP_FIND_GROUP_PERMS`, there is no way for clients to
+determine a user's group memberships, only their permissions. If you want to
+make decisions based directly on group membership, you'll have to mirror the
+groups.
+
+:class:`~django_auth_ldap.backend.LDAPBackend` has one more feature pertaining
+to permissions, which is the ability to handle authorization for users that it
+did not authenticate. For example, you might be using Django's RemoteUserBackend
+to map externally authenticated users to Django users. By setting
+:ref:`AUTH_LDAP_AUTHORIZE_ALL_USERS`,
+:class:`~django_auth_ldap.backend.LDAPBackend` will map these users to LDAP
+users in the normal way in order to provide authorization information. Note that
+this does *not* work with :ref:`AUTH_LDAP_MIRROR_GROUPS`; group mirroring is a
+feature of authentication, not authorization.
+
+
+Logging
+=======
+
+:class:`~django_auth_ldap.backend.LDAPBackend` uses the standard logging module
+to log debug and warning messages to the logger named ``'django_auth_ldap'``. If
+you need debug messages to help with configuration issues, you should add a
+handler to this logger. Note that this logger is initialized with a level of
+NOTSET, so you may need to change the level of the logger in order to get debug
+messages.
+
+.. code-block:: python
+
+    import logging
+
+    logger = logging.getLogger('django_auth_ldap')
+    logger.addHandler(logging.StreamHandler())
+    logger.setLevel(logging.DEBUG)
+
+More options
+============
+
+Miscellaneous settings for :class:`~django_auth_ldap.backend.LDAPBackend`:
+
+    * :ref:`AUTH_LDAP_GLOBAL_OPTIONS`: A dictionary of options to pass to
+      python-ldap via ``ldap.set_option()``.
+    * :ref:`AUTH_LDAP_CONNECTION_OPTIONS`: A dictionary of options to pass to
+      each LDAPObject instance via ``LDAPObject.set_option()``.
+
+
+Performance
+===========
+
+:class:`~django_auth_ldap.backend.LDAPBackend` is carefully designed not to
+require a connection to the LDAP service for every request. Of course, this
+depends heavily on how it is configured. If LDAP traffic or latency is a concern
+for your deployment, this section has a few tips on minimizing it, in decreasing
+order of impact.
+
+    #. **Cache groups**. If :ref:`AUTH_LDAP_FIND_GROUP_PERMS` is ``True``, the
+       default behavior is to reload a user's group memberships on every
+       request. This is the safest behavior, as any membership change takes
+       effect immediately, but it is expensive. If possible, set
+       :ref:`AUTH_LDAP_CACHE_GROUPS` to ``True`` to remove most of this traffic.
+       Alternatively, you might consider using :ref:`AUTH_LDAP_MIRROR_GROUPS`
+       and relying on :class:`~django.contrib.auth.backends.ModelBackend` to
+       supply group permissions.
+    #. **Don't access user.ldap_user.***. These properties are only cached
+       on a per-request basis. If you can propagate LDAP attributes to a
+       :class:`~django.contrib.auth.models.User` or profile object, they will
+       only be updated at login. ``user.ldap_user.attrs`` triggers an LDAP
+       connection for every request in which it's accessed. If you're not using
+       :ref:`AUTH_LDAP_USER_DN_TEMPLATE`, then accessing ``user.ldap_user.dn``
+       will also trigger an LDAP connection.
+    #. **Use simpler group types**. Some grouping mechanisms are more expensive
+       than others. This will often be outside your control, but it's important
+       to note that the extra functionality of more complex group types like
+       :class:`~django_auth_ldap.config.NestedGroupOfNamesType` is not free and
+       will generally require a greater number and complexity of LDAP queries.
+    #. **Use direct binding**. Binding with
+       :ref:`AUTH_LDAP_USER_DN_TEMPLATE` is a little bit more efficient than
+       relying on :ref:`AUTH_LDAP_USER_SEARCH`. Specifically, it saves two LDAP
+       operations (one bind and one search) per login.
+
+
+Example configuration
+=====================
+
+Here is a complete example configuration from :file:`settings.py` that exercises
+nearly all of the features. In this example, we're authenticating against a
+global pool of users in the directory, but we have a special area set aside for
+Django groups (ou=django,ou=groups,dc=example,dc=com). Remember that most of
+this is optional if you just need simple authentication. Some default settings
+and arguments are included for completeness.
+
+.. code-block:: python
+
+    import ldap
+    from django_auth_ldap.config import LDAPSearch, GroupOfNamesType
+
+
+    # Baseline configuration.
+    AUTH_LDAP_SERVER_URI = "ldap://ldap.example.com"
+
+    AUTH_LDAP_BIND_DN = "cn=django-agent,dc=example,dc=com"
+    AUTH_LDAP_BIND_PASSWORD = "phlebotinum"
+    AUTH_LDAP_USER_SEARCH = LDAPSearch("ou=users,dc=example,dc=com",
+        ldap.SCOPE_SUBTREE, "(uid=%(user)s)")
+    # or perhaps:
+    # AUTH_LDAP_USER_DN_TEMPLATE = "uid=%(user)s,ou=users,dc=example,dc=com"
+
+    # Set up the basic group parameters.
+    AUTH_LDAP_GROUP_SEARCH = LDAPSearch("ou=django,ou=groups,dc=example,dc=com",
+        ldap.SCOPE_SUBTREE, "(objectClass=groupOfNames)"
+    )
+    AUTH_LDAP_GROUP_TYPE = GroupOfNamesType(name_attr="cn")
+
+    # Only users in this group can log in.
+    AUTH_LDAP_REQUIRE_GROUP = "cn=enabled,ou=django,ou=groups,dc=example,dc=com"
+
+    # Populate the Django user from the LDAP directory.
+    AUTH_LDAP_USER_ATTR_MAP = {
+        "first_name": "givenName",
+        "last_name": "sn",
+        "email": "mail"
+    }
+
+    AUTH_LDAP_PROFILE_ATTR_MAP = {
+        "employee_number": "employeeNumber"
+    }
+
+    AUTH_LDAP_USER_FLAGS_BY_GROUP = {
+        "is_active": "cn=active,ou=django,ou=groups,dc=example,dc=com",
+        "is_staff": "cn=staff,ou=django,ou=groups,dc=example,dc=com",
+        "is_superuser": "cn=superuser,ou=django,ou=groups,dc=example,dc=com"
+    }
+    
+    # This is the default, but I like to be explicit.
+    AUTH_LDAP_ALWAYS_UPDATE_USER = True
+    
+    # Use LDAP group membership to calculate group permissions.
+    AUTH_LDAP_FIND_GROUP_PERMS = True
+
+    # Cache group memberships for an hour to minimize LDAP traffic
+    AUTH_LDAP_CACHE_GROUPS = True
+    AUTH_LDAP_GROUP_CACHE_TIMEOUT = 3600
+
+
+    # Keep ModelBackend around for per-user permissions and maybe a local
+    # superuser.
+    AUTHENTICATION_BACKENDS = (
+        'django_auth_ldap.backend.LDAPBackend',
+        'django.contrib.auth.backends.ModelBackend',
+    )
+
+
+Reference
+=========
+
+Settings
+--------
+
+.. _AUTH_LDAP_ALWAYS_UPDATE_USER:
+
+AUTH_LDAP_ALWAYS_UPDATE_USER
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Default: ``True``
+
+If ``True``, the fields of a :class:`~django.contrib.auth.models.User` object
+will be updated with the latest values from the LDAP directory every time the
+user logs in. Otherwise the :class:`~django.contrib.auth.models.User` object
+will only be populated when it is automatically created.
+
+
+.. _AUTH_LDAP_AUTHORIZE_ALL_USERS:
+
+AUTH_LDAP_AUTHORIZE_ALL_USERS
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Default: ``False``
+
+If ``True``, :class:`~django_auth_ldap.backend.LDAPBackend` will be able furnish
+permissions for any Django user, regardless of which backend authenticated it.
+
+
+.. _AUTH_LDAP_BIND_DN:
+
+AUTH_LDAP_BIND_DN
+~~~~~~~~~~~~~~~~~
+
+Default: ``''`` (Empty string)
+
+The distinguished name to use when binding to the LDAP server (with
+:ref:`AUTH_LDAP_BIND_PASSWORD`). Use the empty string (the default) for an
+anonymous bind. To authenticate a user, we will bind with that user's DN and
+password, but for all other LDAP operations, we will be bound as the DN in this
+setting. For example, if :ref:`AUTH_LDAP_USER_DN_TEMPLATE` is not set, we'll use
+this to search for the user. If :ref:`AUTH_LDAP_FIND_GROUP_PERMS` is ``True``,
+we'll also use it to determine group membership on subsequent requests.
+
+
+.. _AUTH_LDAP_BIND_PASSWORD:
+
+AUTH_LDAP_BIND_PASSWORD
+~~~~~~~~~~~~~~~~~~~~~~~
+
+Default: ``''`` (Empty string)
+
+The password to use with :ref:`AUTH_LDAP_BIND_DN`.
+
+
+.. _AUTH_LDAP_CACHE_GROUPS:
+
+AUTH_LDAP_CACHE_GROUPS
+~~~~~~~~~~~~~~~~~~~~~~
+
+Default: ``False``
+
+If ``True``, LDAP group membership will be cached using Django's cache
+framework. The cache timeout can be customized with
+:ref:`AUTH_LDAP_GROUP_CACHE_TIMEOUT`.
+
+
+.. _AUTH_LDAP_CONNECTION_OPTIONS:
+
+AUTH_LDAP_CONNECTION_OPTIONS
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Default: ``{}``
+
+A dictionary of options to pass to each connection to the LDAP server via
+``LDAPObject.set_option()``. Keys are ``ldap.OPT_*`` constants.
+
+
+.. _AUTH_LDAP_GROUP_CACHE_TIMEOUT:
+
+AUTH_LDAP_GROUP_CACHE_TIMEOUT
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Default: ``None``
+
+If :ref:`AUTH_LDAP_CACHE_GROUPS` is ``True``, this is the cache timeout for
+group memberships. If ``None``, the global cache timeout will be used.
+
+
+.. _AUTH_LDAP_FIND_GROUP_PERMS:
+
+AUTH_LDAP_FIND_GROUP_PERMS
+~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Default: ``False``
+
+If ``True``, :class:`~django_auth_ldap.backend.LDAPBackend` will furnish group
+permissions based on the LDAP groups the authenticated user belongs to.
+:ref:`AUTH_LDAP_GROUP_SEARCH` and :ref:`AUTH_LDAP_GROUP_TYPE` must also be set.
+
+
+.. _AUTH_LDAP_GLOBAL_OPTIONS:
+
+AUTH_LDAP_GLOBAL_OPTIONS
+~~~~~~~~~~~~~~~~~~~~~~~~
+
+Default: ``{}``
+
+A dictionary of options to pass to ``ldap.set_option()``. Keys are
+``ldap.OPT_*`` constants.
+
+
+.. _AUTH_LDAP_GROUP_SEARCH:
+
+AUTH_LDAP_GROUP_SEARCH
+~~~~~~~~~~~~~~~~~~~~~~
+
+Default: ``None``
+
+An :class:`~django_auth_ldap.config.LDAPSearch` object that finds all LDAP
+groups that users might belong to. If your configuration makes any references to
+LDAP groups, this and :ref:`AUTH_LDAP_GROUP_TYPE` must be set.
+
+
+.. _AUTH_LDAP_GROUP_TYPE:
+
+AUTH_LDAP_GROUP_TYPE
+~~~~~~~~~~~~~~~~~~~~
+
+Default: ``None``
+
+An :class:`~django_auth_ldap.config.LDAPGroupType` instance describing the type
+of group returned by :ref:`AUTH_LDAP_GROUP_SEARCH`.
+
+
+.. _AUTH_LDAP_MIRROR_GROUPS:
+
+AUTH_LDAP_MIRROR_GROUPS
+~~~~~~~~~~~~~~~~~~~~~~~
+
+Default: ``False``
+
+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.
+
+
+.. _AUTH_LDAP_PROFILE_ATTR_MAP:
+
+AUTH_LDAP_PROFILE_ATTR_MAP
+~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Default: ``{}``
+
+A mapping from user profile field names to LDAP attribute names. A user's
+profile will be populated from his LDAP attributes at login.
+
+
+.. _AUTH_LDAP_REQUIRE_GROUP:
+
+AUTH_LDAP_REQUIRE_GROUP
+~~~~~~~~~~~~~~~~~~~~~~~
+
+Default: ``None``
+
+The distinguished name of a group that a user must belong to in order to
+successfully authenticate.
+
+
+.. _AUTH_LDAP_SERVER_URI:
+
+AUTH_LDAP_SERVER_URI
+~~~~~~~~~~~~~~~~~~~~
+
+Default: ``ldap://localhost``
+
+The URI of the LDAP server. This can be any URI that is supported by your
+underlying LDAP libraries.
+
+
+.. _AUTH_LDAP_START_TLS:
+
+AUTH_LDAP_START_TLS
+~~~~~~~~~~~~~~~~~~~
+
+Default: ``False``
+
+If ``True``, each connection to the LDAP server will call start_tls to enable
+TLS encryption over the standard LDAP port. There are a number of configuration
+options that can be given to :ref:`AUTH_LDAP_GLOBAL_OPTIONS` that affect the
+TLS connection. For example, ``ldap.OPT_X_TLS_REQUIRE_CERT`` can be set to
+``ldap.OPT_X_TLS_NEVER`` to disable certificate verification, perhaps to allow
+self-signed certificates.
+
+
+.. _AUTH_LDAP_USER_ATTR_MAP:
+
+AUTH_LDAP_USER_ATTR_MAP
+~~~~~~~~~~~~~~~~~~~~~~~
+
+Default: ``{}``
+
+A mapping from :class:`~django.contrib.auth.models.User` field names to LDAP
+attribute names. A users's :class:`~django.contrib.auth.models.User` object will
+be populated from his LDAP attributes at login.
+
+
+.. _AUTH_LDAP_USER_DN_TEMPLATE:
+
+AUTH_LDAP_USER_DN_TEMPLATE
+~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Default: ``None``
+
+A string template that describes any user's distinguished name based on the
+username. This must contain the placeholder ``%(user)s``.
+
+
+.. _AUTH_LDAP_USER_FLAGS_BY_GROUP:
+
+AUTH_LDAP_USER_FLAGS_BY_GROUP
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Default: ``{}``
+
+A mapping from boolean :class:`~django.contrib.auth.models.User` field names to
+distinguished names of LDAP groups. The corresponding field is set to ``True``
+or ``False`` according to whether the user is a member of the group.
+
+
+.. _AUTH_LDAP_USER_SEARCH:
+
+AUTH_LDAP_USER_SEARCH
+~~~~~~~~~~~~~~~~~~~~~
+
+Default: ``None``
+
+An :class:`~django_auth_ldap.config.LDAPSearch` object that will locate a user
+in the directory. The filter parameter should contain the placeholder
+``%(user)s`` for the username. It must return exactly one result for
+authentication to succeed.
+
+
+Configuration
+-------------
+
+.. module:: django_auth_ldap.config
+
+.. class:: LDAPSearch
+
+    .. method:: __init__(base_dn, scope, filterstr='(objectClass=*)')
+
+        * ``base_dn``: The distinguished name of the search base.
+        * ``scope``: One of ``ldap.SCOPE_*``.
+        * ``filterstr``: An optional filter string (e.g. '(objectClass=person)').
+          In order to be valid, ``filterstr`` must be enclosed in parentheses.
+
+
+.. class:: LDAPGroupType
+
+    The base class for objects that will determine group membership for various
+    LDAP grouping mechanisms. Implementations are provided for common group
+    types or you can write your own. See the source code for subclassing notes.
+    
+    .. method:: __init__(name_attr='cn')
+        
+        By default, LDAP groups will be mapped to Django groups by taking the
+        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
+    handles the ``posixGroup`` object class. This checks for both primary group
+    and group membership.
+
+    .. method:: __init__(name_attr='cn')
+
+.. class:: MemberDNGroupType
+
+    A concrete subclass of
+    :class:`~django_auth_ldap.config.LDAPGroupType` that handles grouping
+    mechanisms wherein the group object contains a list of its member DNs.
+    
+    .. method:: __init__(member_attr, name_attr='cn')
+    
+        * ``member_attr``: The attribute on the group object that contains a
+          list of member DNs. 'member' and 'uniqueMember' are common examples.
+
+
+.. class:: NestedMemberDNGroupType
+
+    Similar to :class:`~django_auth_ldap.config.MemberDNGroupType`, except this
+    allows groups to contain other groups as members. Group hierarchies will be
+    traversed to determine membership.
+
+    .. method:: __init__(member_attr, name_attr='cn')
+    
+        As above.
+
+
+.. class:: GroupOfNamesType
+
+    A concrete subclass of :class:`~django_auth_ldap.config.MemberDNGroupType`
+    that handles the ``groupOfNames`` object class. Equivalent to
+    ``MemberDNGroupType('member')``.
+
+    .. method:: __init__(name_attr='cn')
+
+
+.. class:: NestedGroupOfNamesType
+
+    A concrete subclass of
+    :class:`~django_auth_ldap.config.NestedMemberDNGroupType` that handles the
+    ``groupOfNames`` object class. Equivalent to
+    ``NestedMemberDNGroupType('member')``.
+
+    .. method:: __init__(name_attr='cn')
+
+
+.. class:: GroupOfUniqueNamesType
+
+    A concrete subclass of :class:`~django_auth_ldap.config.MemberDNGroupType`
+    that handles the ``groupOfUniqueNames`` object class. Equivalent to
+    ``MemberDNGroupType('uniqueMember')``.
+
+    .. method:: __init__(name_attr='cn')
+
+
+.. class:: NestedGroupOfUniqueNamesType
+
+    A concrete subclass of
+    :class:`~django_auth_ldap.config.NestedMemberDNGroupType` that handles the
+    ``groupOfUniqueNames`` object class. Equivalent to
+    ``NestedMemberDNGroupType('uniqueMember')``.
+
+    .. method:: __init__(name_attr='cn')
+
+
+.. class:: ActiveDirectoryGroupType
+
+    A concrete subclass of :class:`~django_auth_ldap.config.MemberDNGroupType`
+    that handles Active Directory groups. Equivalent to
+    ``MemberDNGroupType('member')``.
+
+    .. method:: __init__(name_attr='cn')
+
+
+.. class:: NestedActiveDirectoryGroupType
+
+    A concrete subclass of
+    :class:`~django_auth_ldap.config.NestedMemberDNGroupType` that handles
+    Active Directory groups. Equivalent to
+    ``NestedMemberDNGroupType('member')``.
+
+    .. method:: __init__(name_attr='cn')
+
+
+Backend
+-------
+
+.. module:: django_auth_ldap.backend
+
+.. class:: LDAPBackend
+
+    :class:`~django_auth_ldap.backend.LDAPBackend` has one method that may be
+    called directly and several that may be overridden in subclasses.
+
+    .. method:: populate_user(username)
+
+        Populates the Django user for the given LDAP username. This connects to
+        the LDAP directory with the default credentials and attempts to populate
+        the indicated Django user as if they had just logged in.
+        :ref:`AUTH_LDAP_ALWAYS_UPDATE_USER` is ignored (assumed ``True``).
+
+    .. method:: get_or_create_user(self, username, ldap_user)
+
+        Given a username and an LDAP user object, this must return the
+        associated Django User object. The ``username`` argument has already
+        been passed through
+        :meth:`~django_auth_ldap.backend.LDAPBackend.ldap_to_django_username`.
+        You can get information about the LDAP user via ``ldap_user.dn`` and
+        ``ldap_user.attrs``. The return value must be the same as
+        ``User.objects.get_or_create()``: a (User, created) two-tuple.
+
+        The default implementation simply calls ``User.objects.get_or_create()``
+        with the username. Subclasses are welcome to associate LDAP users to
+        Django users any way they like.
+
+    .. method:: ldap_to_django_username(username)
+    
+        Returns a valid Django username based on the given LDAP username (which
+        is what the user enters). By default, ``username`` is returned
+        unchanged. This can be overriden by subclasses.
+
+    .. method:: django_to_ldap_username(username)
+
+        The inverse of
+        :meth:`~django_auth_ldap.backend.LDAPBackend.ldap_to_django_username`.
+        If this is not symmetrical to
+        :meth:`~django_auth_ldap.backend.LDAPBackend.ldap_to_django_username`,
+        the behavior is undefined.
+
+
+License
+=======
+
+Copyright (c) 2009, Peter Sagerson
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without modification,
+are permitted provided that the following conditions are met:
+
+- Redistributions of source code must retain the above copyright notice, this
+  list of conditions and the following disclaimer.
+
+- Redistributions in binary form must reproduce the above copyright notice, this
+  list of conditions and the following disclaimer in the documentation and/or
+  other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
+ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

+ 31 - 0
desktop/core/ext-py/django-auth-ldap-1.0.7/setup.py

@@ -0,0 +1,31 @@
+#!/usr/bin/env python
+
+from setuptools import setup
+
+setup(
+    name="django-auth-ldap",
+    version="1.0.7",
+    description="Django LDAP authentication backend",
+    long_description="""This is a Django authentication backend that authenticates against an LDAP service. Configuration can be as simple as a single distinguished name template, but there are many rich configuration options for working with users, groups, and permissions.
+    
+This package requires at least Python 2.3, Django 1.0, and python-ldap 2.0. Documentation can be found at http://packages.python.org/django-auth-ldap/.
+    """,
+    url="http://bitbucket.org/psagers/django-auth-ldap/",
+    author="Peter Sagerson",
+    author_email="psagers_pypi@ignorare.net",
+    license="BSD",
+    packages=["django_auth_ldap"],
+    classifiers=[
+        "Development Status :: 5 - Production/Stable",
+        "Environment :: Web Environment",
+        "Programming Language :: Python",
+        "Framework :: Django",
+        "Intended Audience :: Developers",
+        "Intended Audience :: System Administrators",
+        "License :: OSI Approved :: BSD License",
+        "Topic :: Internet :: WWW/HTTP",
+        "Topic :: System :: Systems Administration :: Authentication/Directory :: LDAP",
+        "Topic :: Software Development :: Libraries :: Python Modules",
+    ],
+    keywords=["django", "ldap", "authentication", "auth"],
+)

+ 19 - 0
desktop/core/ext-py/pam/PKG-INFO

@@ -0,0 +1,19 @@
+Metadata-Version: 1.0
+Name: pam
+Version: 0.1.3
+Summary: PAM interface using ctypes
+Home-page: http://atlee.ca/software/pam
+Author: Chris AtLee
+Author-email: chris@atlee.ca
+License: MIT
+Download-URL: http://atlee.ca/software/pam/dist/0.1.3
+Description: An interface to the Pluggable Authentication Modules (PAM) library on linux, written in pure python (using ctypes)
+Platform: UNKNOWN
+Classifier: Development Status :: 3 - Alpha
+Classifier: Intended Audience :: Developers
+Classifier: License :: OSI Approved :: MIT License
+Classifier: Operating System :: POSIX :: Linux
+Classifier: Operating System :: MacOS :: MacOS X
+Classifier: Programming Language :: Python
+Classifier: Topic :: Software Development :: Libraries :: Python Modules
+Classifier: Topic :: System :: Systems Administration :: Authentication/Directory

+ 123 - 0
desktop/core/ext-py/pam/pam.py

@@ -0,0 +1,123 @@
+# (c) 2007 Chris AtLee <chris@atlee.ca>
+# Licensed under the MIT license:
+# http://www.opensource.org/licenses/mit-license.php
+"""
+PAM module for python
+
+Provides an authenticate function that will allow the caller to authenticate
+a user against the Pluggable Authentication Modules (PAM) on the system.
+
+Implemented using ctypes, so no compilation is necessary.
+"""
+__all__ = ['authenticate']
+
+from ctypes import CDLL, POINTER, Structure, CFUNCTYPE, cast, pointer, sizeof
+from ctypes import c_void_p, c_uint, c_char_p, c_char, c_int
+from ctypes.util import find_library
+
+LIBPAM = CDLL(find_library("pam"))
+LIBC = CDLL(find_library("c"))
+
+CALLOC = LIBC.calloc
+CALLOC.restype = c_void_p
+CALLOC.argtypes = [c_uint, c_uint]
+
+STRDUP = LIBC.strdup
+STRDUP.argstypes = [c_char_p]
+STRDUP.restype = POINTER(c_char) # NOT c_char_p !!!!
+
+# Various constants
+PAM_PROMPT_ECHO_OFF = 1
+PAM_PROMPT_ECHO_ON = 2
+PAM_ERROR_MSG = 3
+PAM_TEXT_INFO = 4
+
+class PamHandle(Structure):
+    """wrapper class for pam_handle_t"""
+    _fields_ = [
+            ("handle", c_void_p)
+            ]
+
+    def __init__(self):
+        Structure.__init__(self)
+        self.handle = 0
+
+class PamMessage(Structure):
+    """wrapper class for pam_message structure"""
+    _fields_ = [
+            ("msg_style", c_int),
+            ("msg", c_char_p),
+            ]
+
+    def __repr__(self):
+        return "<PamMessage %i '%s'>" % (self.msg_style, self.msg)
+
+class PamResponse(Structure):
+    """wrapper class for pam_response structure"""
+    _fields_ = [
+            ("resp", c_char_p),
+            ("resp_retcode", c_int),
+            ]
+
+    def __repr__(self):
+        return "<PamResponse %i '%s'>" % (self.resp_retcode, self.resp)
+
+CONV_FUNC = CFUNCTYPE(c_int,
+        c_int, POINTER(POINTER(PamMessage)),
+               POINTER(POINTER(PamResponse)), c_void_p)
+
+class PamConv(Structure):
+    """wrapper class for pam_conv structure"""
+    _fields_ = [
+            ("conv", CONV_FUNC),
+            ("appdata_ptr", c_void_p)
+            ]
+
+PAM_START = LIBPAM.pam_start
+PAM_START.restype = c_int
+PAM_START.argtypes = [c_char_p, c_char_p, POINTER(PamConv),
+        POINTER(PamHandle)]
+
+PAM_AUTHENTICATE = LIBPAM.pam_authenticate
+PAM_AUTHENTICATE.restype = c_int
+PAM_AUTHENTICATE.argtypes = [PamHandle, c_int]
+
+def authenticate(username, password, service='login'):
+    """Returns True if the given username and password authenticate for the
+    given service.  Returns False otherwise
+    
+    ``username``: the username to authenticate
+    
+    ``password``: the password in plain text
+    
+    ``service``: the PAM service to authenticate against.
+                 Defaults to 'login'"""
+    @CONV_FUNC
+    def my_conv(n_messages, messages, p_response, app_data):
+        """Simple conversation function that responds to any
+        prompt where the echo is off with the supplied password"""
+        # Create an array of n_messages response objects
+        addr = CALLOC(n_messages, sizeof(PamResponse))
+        p_response[0] = cast(addr, POINTER(PamResponse))
+        for i in range(n_messages):
+            if messages[i].contents.msg_style == PAM_PROMPT_ECHO_OFF:
+                pw_copy = STRDUP(str(password))
+                p_response.contents[i].resp = cast(pw_copy, c_char_p)
+                p_response.contents[i].resp_retcode = 0
+        return 0
+
+    handle = PamHandle()
+    conv = PamConv(my_conv, 0)
+    retval = PAM_START(service, username, pointer(conv), pointer(handle))
+
+    if retval != 0:
+        # TODO: This is not an authentication error, something
+        # has gone wrong starting up PAM
+        return False
+
+    retval = PAM_AUTHENTICATE(handle, 0)
+    return retval == 0
+
+if __name__ == "__main__":
+    import getpass
+    print authenticate(getpass.getuser(), getpass.getpass())

+ 5 - 0
desktop/core/ext-py/pam/setup.cfg

@@ -0,0 +1,5 @@
+[egg_info]
+tag_build = 
+tag_date = 0
+tag_svn_revision = 0
+

+ 32 - 0
desktop/core/ext-py/pam/setup.py

@@ -0,0 +1,32 @@
+from setuptools import setup, find_packages
+import sys, os
+
+version = '0.1.3'
+
+setup(name='pam',
+      version=version,
+      description="PAM interface using ctypes",
+      long_description="""\
+An interface to the Pluggable Authentication Modules (PAM) library on linux, written in pure python (using ctypes)""",
+      classifiers=["Development Status :: 3 - Alpha",
+          "Intended Audience :: Developers",
+          "License :: OSI Approved :: MIT License",
+          "Operating System :: POSIX :: Linux",
+          "Operating System :: MacOS :: MacOS X",
+          "Programming Language :: Python",
+          "Topic :: Software Development :: Libraries :: Python Modules",
+          "Topic :: System :: Systems Administration :: Authentication/Directory"
+          ],
+      keywords='',
+      author='Chris AtLee',
+      author_email='chris@atlee.ca',
+      url='http://atlee.ca/software/pam',
+      download_url = "http://atlee.ca/software/pam/dist/%s" % version,
+      license='MIT',
+      py_modules=["pam"],
+      zip_safe=True,
+      install_requires=[],
+      entry_points="""
+      # -*- Entry points: -*-
+      """,
+      )

+ 7 - 0
desktop/core/ext-py/python-ldap-2.4.6/.cvsignore

@@ -0,0 +1,7 @@
+build
+dist
+MANIFEST
+*.pyc
+*.pyo
+*.bck
+*~

+ 31 - 0
desktop/core/ext-py/python-ldap-2.4.6/Build/build-openbsd/Makefile

@@ -0,0 +1,31 @@
+# $Id: Makefile,v 1.3 2000/08/20 15:04:23 leonard Exp $
+
+WRKDIST=		${.CURDIR}/../..
+VERSION!=		sh ${WRKDIST}/Build/version.sh
+
+PKGNAME=		python-ldap-${VERSION}
+RUN_DEPENDS=		python:lang/python
+CATEGORIES=		misc
+MAINATINER=		leonard@users.sourceforge.net
+HOMEPAGE=		http://python-ldap.sourceforge.net/
+
+FAKE=			Yes
+CONFIGURE_STYLE=	gnu
+SEPARATE_BUILD=		Yes
+EXTRACT_ONLY=	
+
+CONFIGURE_ARGS+=	--with-ldap=${LOCALBASE}
+
+do-fetch:;
+
+#-- put package on sourceforge ftp server
+export-ftp: ${PKGFILE}
+	scp ${PKGFILE} python-ldap.sourceforge.net:/home/groups/ftp/pub/python-ldap/${PKGNAME}-${OPSYS}-${ARCH}${PKG_SUFX}
+
+post-build:
+	sed -e 's,^${PREFIX}/,,' <${WRKBUILD}/filelist >${WRKBUILD}/filelist2
+
+SED_PLIST+=	|sed -e '/^%%filelist/r${WRKBUILD}/filelist2' -e '//d'
+ALL_TARGET=	build filelist
+
+.include <bsd.port.mk>

+ 1 - 0
desktop/core/ext-py/python-ldap-2.4.6/Build/build-openbsd/pkg/COMMENT

@@ -0,0 +1 @@
+Python interface to LDAP

+ 2 - 0
desktop/core/ext-py/python-ldap-2.4.6/Build/build-openbsd/pkg/DESCR

@@ -0,0 +1,2 @@
+This Python library provides access to the LDAP (Lightweight Directory Access 
+Protocol) RFC1823 C interface.

+ 3 - 0
desktop/core/ext-py/python-ldap-2.4.6/Build/build-openbsd/pkg/PLIST

@@ -0,0 +1,3 @@
+@comment $OpenBSD$
+%%filelist
+@dirrm lib/python1.6/site-packages/python-ldap

+ 35 - 0
desktop/core/ext-py/python-ldap-2.4.6/Build/setup.cfg.mingw

@@ -0,0 +1,35 @@
+# Win32 setup.cfg
+# You have to edit this file to reflect your system configuration
+#
+# $Id: setup.cfg.mingw,v 1.1 2005/01/25 18:54:46 stroeder Exp $
+
+# Section for compiling the C extension module
+# for wrapping OpenLDAP 2 libs
+
+# Platform: Win32
+# Compile environment: MinGW
+[_ldap]
+class = OpenLDAP2
+maintainer = Mauro Cicognini
+defines = WIN32
+
+# modify these to fit your local configuration
+library_dirs = C:/msys/1.0/home/mcicogni/openldap-mingw-build-4/openldap-2.2.18/libraries/libldap_r/.libs C:/msys/1.0/home/mcicogni/openldap-mingw-build-4/openldap-2.2.18/libraries/liblber/.libs C:\msys\1.0\home\mcicogni\openldap-mingw-build-4\openssl-0.9.7e
+include_dirs = C:/msys/1.0/home/mcicogni/openldap-mingw-build-4/openldap-2.2.18/include
+
+extra_compile_args = 
+extra_objects = 
+
+libs = ldap_r lber ssl crypto ws2_32 gdi32
+
+[build]
+compiler = mingw32
+
+# Installation options
+[install]
+compile = 1
+optimize = 1
+record = python-ldap_install.log
+
+[bdist_wininst]
+target_version = 2.4

+ 33 - 0
desktop/core/ext-py/python-ldap-2.4.6/Build/setup.cfg.suse-linux

@@ -0,0 +1,33 @@
+# Example for setup.cfg
+# You have to edit this file to reflect your system configuation
+# $Id: setup.cfg.suse-linux,v 1.1 2003/08/20 10:04:34 stroeder Exp $
+
+[_ldap]
+# Section for compiling the C extension module
+# for wrapping OpenLDAP 2 libs
+
+library_dirs = /usr/lib/sasl2
+include_dirs = /usr/include/sasl
+
+extra_compile_args = 
+extra_objects =
+
+# Example for full-featured SuSE build:
+# Support for StartTLS/LDAPS, SASL bind and reentrant libldap_r.
+# This needs recent OpenLDAP 2.0.26+ or 2.1.3+ built with
+# ./configure --with-cyrus-sasl --with-tls
+libs = ldap_r lber sasl2 ssl crypto
+
+[install]
+# Installation options
+compile = 1
+optimize = 1
+
+# For SuSE Linux 8.2
+[bdist_rpm]
+provides = python-ldap
+requires = python openldap2-client openssl cyrus-sasl2
+distribution_name = SuSE Linux 8.2
+release = 1
+packager = Michael Stroeder <michael@stroeder.com>
+doc_files = CHANGES README INSTALL TODO Demo/

+ 33 - 0
desktop/core/ext-py/python-ldap-2.4.6/Build/setup.cfg.win32

@@ -0,0 +1,33 @@
+# Section for compiling the C extension module
+# for wrapping OpenLDAP 2 libs
+
+# Platform: Win32
+# Compile environment: Microsoft Visual Studio .NET 2003
+[_ldap]
+class = OpenLDAP2
+defines = WIN32
+
+# Cannot have SSL/TLS support under Win32 for the moment
+# (OpenLDAP 2.x port is incomplete)
+libs = olber32 oldap_r ws2_32 libsasl
+
+# Set these to your correct Openldap and Cyrus-sasl paths
+library_dirs = ../openldap/openldap-2.1.22/Release ../openldap/cyrus-sasl/lib
+include_dirs = ../openldap/openldap-2.1.22/include ../openldap/cyrus-sasl/include
+
+# Needs to compile as /MT ("MS libs to use: multithreaded statically-linked")
+# instead of /MD ("MS libs to use: multithreaded DLL") which is distutils' default
+# because OpenLDAP libs compile that way, too
+# This may change, however
+extra_compile_args = /MT
+extra_link_args = /NODEFAULTLIB:msvcrt.lib
+
+# Pull in SASL DLL as a convenience to end-user (which almost never will have it)
+# Destination path is a rather crude hack, but site-packages would be created anyway
+# Set source path to your Cyrus-sasl lib path
+extra_files = Lib/site-packages:../openldap/cyrus-sasl/lib/libsasl.dll
+
+# Installation options
+[install]
+compile = 1
+record = python-ldap_install.log

+ 967 - 0
desktop/core/ext-py/python-ldap-2.4.6/CHANGES

@@ -0,0 +1,967 @@
+----------------------------------------------------------------
+Released 2.4.6 2011-11-27
+
+Changes since 2.4.5:
+
+Lib/
+* ldap.controls.ppolicy:
+  Another fix for decoding the password policy response control
+
+----------------------------------------------------------------
+Released 2.4.5 2011-11-25
+
+Changes since 2.4.4:
+
+Installation:
+* defines for SASL and SSL in setup.cfg to be more friendly to
+  Python setup tools (easy_install)
+  
+Lib/
+* Fixed typo in ldap.functions._ldap_function_call() which
+  always released ldap._ldap_module_lock instead of local lock
+* ldap.controls.ppolicy:
+  Fixed decoding the password policy response control
+
+Demo/
+* Demo script for ldap.controls.ppolicy
+
+----------------------------------------------------------------
+Released 2.4.4 2011-10-26
+
+Changes since 2.4.3:
+
+Modules/
+* Format intermediate messages as 3-tuples instead of
+  4-tuples to match the format of other response messages.
+  (thanks to Chris Mikkelson)
+* Fixes for memory leaks (thanks to Chris Mikkelson)
+
+Lib/
+* New experimental(!) sub-module ldap.syncrepl implementing syncrepl
+  consumer (see RFC 4533, thanks to Chris Mikkelson)
+
+Doc/
+* Cleaned up rst files
+* Added missing classes
+
+----------------------------------------------------------------
+Released 2.4.3 2011-07-23
+
+Changes since 2.4.2:
+
+Lib/
+* Mostly corrected/updated __doc__ strings
+
+Doc/
+* Corrected rst files
+* Added missing modules, functions, classes, methods, parameters etc.
+  at least as auto-generated doc
+
+----------------------------------------------------------------
+Released 2.4.2 2011-07-21
+
+Changes since 2.4.1:
+
+Lib/
+
+Logging:
+* pprint.pformat() is now used when writing method/function
+  arguments to the trace log
+
+ldap.schema.subentry:
+* SubSchema.__init__() now has new key-word argument check_uniqueness
+  which enables checking whether OIDs are unique in the subschema subentry
+* Code-cleaning: consequent use of method SubSchema.getoid() instead of
+  accessing SubSchema.name2oid directly.
+* SubSchema.getoid() and SubSchema.getoid() now have key-word argument
+  raise_keyerror=0 and raise KeyError with appropriate description.
+
+----------------------------------------------------------------
+Released 2.4.1 2011-07-05
+
+Changes since 2.4.0:
+
+Modules:
+* New LDAP option OPT_X_TLS_PACKAGE available in OpenLDAP 2.4.26+
+  to determine the name of the SSL/TLS package OpenLDAP was
+  built with
+
+Lib/
+* ldap.modlist.modifyModlist(): New key-word argument
+  case_ignore_attr_types used to define attribute types for which
+  comparison of old and new values should be case-insensitive
+* Minor changes to which data is sent to debug output for various
+  trace levels
+* Now tag [1] is used in ldap.extop.dds.RefreshResponse in
+  compliance with RFC 2589 (fix available for OpenLDAP ITS#6886)
+* New sub-module ldap.controls.sessiontrack implements request control
+  as described in draft-wahl-ldap-session (needs pyasn1_modules)
+  
+----------------------------------------------------------------
+Released 2.4.0 2011-06-02
+
+Changes since 2.3.13:
+
+* OpenLDAP 2.4.11+ required to build
+* Support for extracting LDAPv3 extended controls in
+  LDAP_RES_SEARCH_ENTRY responses
+  (see SF#2829057, thanks to Rich)
+* Generic support for LDAPv3 extended operations (thanks to Rich)
+
+Lib/
+* new class API in ldap.controls, not backwards-compatible!
+* new sub-modules for ldap.controls, some require pyasn1 and pyasn1_modules
+* New methods LDAPObject.result4() and LDAPObject.extop_result()
+* New (optional) class ldap.controls.AssertionControl
+* New helper module ldap.logger contains file-like object which
+  sends trace messages to logging.log()
+* Removed non-functional method LDAPObject.set_cache_options()
+* Removed unused dictionary ldap.controls.knownLDAPControls
+
+Modules/
+* ldapcontrol.c: Fixed encode_assertion_control() and function is no longer
+  hidden behind ifdef-statement
+
+----------------------------------------------------------------
+Released 2.3.13 2011-02-19
+
+Changes since 2.3.12:
+
+Modules/
+* Correct #ifdef-statement for LDAP_OPT_X_TLS_CRLFILE in
+  constants.c fixes build with older OpenLDAP libs
+* Support for LDAP_OPT_DEFBASE (see SF#3072016, thanks to Johannes)
+
+----------------------------------------------------------------
+Released 2.3.12 2010-08-05
+
+Changes since 2.3.11:
+
+Lib/
+* Removed tabs from various modules to make things work with python -tt.
+* Quick fix to ldif.is_dn() to let multi-valued RDNs pass as valid.
+  Is too liberal in some corner-cases though...
+* Fix to ldif.is_dn() to allow dashes in attribute type (see SF#3020292)
+* ldap.open() now outputs a deprecation warning
+* module-wide locking is now limited to calling _ldap.initialize().
+  Still ldap.functions._ldap_function_call() is used to wrap all
+  calls for writing debug log.
+
+Modules/
+* New LDAP options available in OpenLDAP 2.4.18+ supported in
+  LDAPObject.get/set_option():
+  ldap.OPT_X_KEEPALIVE_IDLE, ldap.OPT_X_KEEPALIVE_PROBES,
+  ldap.OPT_X_KEEPALIVE_INTERVAL,
+  ldap.OPT_X_TLS_CRLCHECK, ldap.OPT_X_TLS_CRLFILE
+
+Doc/
+* Various small updates/improvements
+
+----------------------------------------------------------------
+Released 2.3.11 2010-02-26
+
+Changes since 2.3.10:
+
+Lib/
+* Fixed LDAP URL parsing with four ? but no real extensions
+* ldap.ldapobject.LDAPObject.rename_s() now also accepts arguments
+  serverctrls and clientctrls
+* Removed untested and undocumented class ldap.ldapobject.SmartLDAPObject
+* Removed broken method ldap.ldapobject.LDAPObject.manage_dsa_it()
+
+Modules/
+* Make use of LDAP_OPT_X_TLS_NEWCTX only if available in
+  OpenLDAP libs used for the build
+* Fixed #ifdef-statements for OPT_X_TLS_PROTOCOL_MIN
+
+Doc/
+* Some updates and corrections regarding description of use of
+  LDAPv3 controls
+* Some more descriptions for constants
+* Removed comments related to old LaTeX-based documentation system
+
+----------------------------------------------------------------
+Released 2.3.10 2009-10-30
+
+Changes since 2.3.9:
+
+Lib/
+* The diagnosticMessage returned by a server is written to the trace
+  output also for successful operations.
+* Fixed handling of LDAP URL extensions with implicit value None which are
+  mapped to class attributes of LDAPUrl.
+* Fixed handling of LDAP URLs with ? being part of extensions.
+* Fixed exceptions raised by get_option/set_option (SF#1964993)
+* ldap.functions: Fixed import trace-related variables from base-module ldap
+* Fixed ldap.resiter missing in RPMs built with python setup.py bdist_rpm
+* Fix in class ldap.schema.models.SchemaElement:
+  repr() was liberally used in methods key_attr() and key_list() to enclose
+  values in quotes.
+
+Modules/
+* Changed internal API List_to_LDAPControls() to LDAPControls_from_object()
+* Supported was added for retrieving the SASL username during SASL bind with
+  ldap_get_option(LDAP_OPT_X_SASL_USERNAME) if available in libldap.
+* New LDAP option constant ldap.OPT_X_TLS_NEWCTX supported
+  in LDAPObject.set_option()
+* New LDAP option constants supported in LDAPObject.get/set_option():
+  ldap.OPT_X_TLS_PROTOCOL_MIN, ldap.OPT_CONNECT_ASYNC, ldap.OPT_X_TLS_DHFILE
+* Fixed setting _ldap.OPT_ON and _ldap.OPT_OFF
+* l_ldap_result3(): controls are now parsed for all response types (SF#2829057)
+
+Doc/
+* Added example for ldap.resiter
+
+----------------------------------------------------------------
+Released 2.3.9 2009-07-26
+
+Changes since 2.3.8:
+
+Lib/
+* All modules (ldap, ldif, dsml and ldapurl) have common version number now
+* Non-exported function ldif.needs_base64() was abandoned and is now
+  implemented as method LDIFWriter._needs_base64_encoding().
+  This allows sub-classes of LDIFWriter to implement determining whether
+  attribute values have to be base64-encoded in a different manner and is
+  the same approach like in class dsml.DSMLWriter.
+* LDAPUrlExtension._parse() now gracefully handles LDAP URL extensions
+  without explicit exvalue as being set with implicit value None.
+
+Modules/
+* New LDAP option constant ldap.OPT_X_SASL_NOCANON supported
+  in LDAPObject.get/set_option()
+
+----------------------------------------------------------------
+Released 2.3.8 2009-04-30
+
+Changes since 2.3.7:
+
+Lib/
+* ldap.schema.models: More fault-tolerant parsing of SYNTAX in
+  AttributeTypeDescription
+* ldap.schema.tokenizer.split_tokens():
+  More tolerant parsing of items separated only with a DOLLAR without
+  surrounding white-spaces (because WSP is declared as zero or more spaces
+  in RFC 4512)
+
+----------------------------------------------------------------
+Released 2.3.7 2009-04-09
+
+Changes since 2.3.6:
+
+Lib/
+* urllib.quote() is now used in LDAPUrlExtension.unparse() to quote
+  all special URL characters in extension values
+
+Modules/
+* Fixed ldapcontrol.c not to raise ldap.ENCODING_ERROR in
+  function encode_rfc2696() on 64-bit systems
+* Fixed seg fault if error code in a LDAP response was outside
+  the known error codes and could not be mapped to a specific
+  exception class (thanks to Sean)
+* errors.c: LDAP_ERROR_MAX set to LDAP_PROXIED_AUTHORIZATION_DENIED
+  if available in OpenLDAP header
+* new exception class ldap.PROXIED_AUTHORIZATION_DENIED
+  if available in OpenLDAP header
+* Fixed functions.c not to raise ldap.ENCODING_ERROR in
+  function l_ldap_str2dn() on 64-bit systems (see SF#2725356)
+
+----------------------------------------------------------------
+Released 2.3.6 2009-02-22
+
+Changes since 2.3.5:
+
+Lib/
+* Importing ldap.str2dn() which directly imported _ldap.str2dn()
+  is prohibited now (see SF#2181141)
+
+Modules/
+* get_option(): Added support for reading more SASL options.
+  (OPT_X_SASL_MECH, OPT_X_SASL_REALM, OPT_X_SASL_AUTHCID and
+  OPT_X_SASL_AUTHZID)
+* Added some explicit type casts to fix issues while building
+  with SunStudio
+* Fixed compiling issue with GCC 4.4
+  (see SF#2555793, thanks to Matej and Martin)
+
+Doc/
+* Clarified not to use ldap_get_dn() directly
+* Fixed description of ldap.SASL_AVAIL and ldap.TLS_AVAIL
+  (see SF#2555804, thanks to Matej and Martin)
+
+----------------------------------------------------------------
+Released 2.3.5 2008-07-06
+
+Changes since 2.3.4:
+
+Lib/
+* Fixed methods ldap.cidict.__contains__() and
+  ldap.schema.models.Entry.__contains__()
+* FWIW method LDAPObject.cancel_s() returns a result now
+* Fixed ldap.schema.models.NameForm: Class attribute oc is now
+  of type string, not tuple to be compliant with RFC 4512
+----------------------------------------------------------------
+Released 2.3.4 2008-03-29
+
+Changes since 2.3.3:
+
+Modules/
+* Fixed seg fault when calling LDAPObject.get_option()
+  (see SF#1926507, thanks to Matej)
+
+----------------------------------------------------------------
+Released 2.3.3 2008-03-26
+
+Changes since 2.3.2:
+
+Fixed backward-compability when building with OpenLDAP 2.3.x libs.
+
+----------------------------------------------------------------
+Released 2.3.2 2008-03-26
+
+Changes since 2.3.1:
+
+Lib/
+* ldap.dn.escape_dn_chars() now really adheres to
+  RFC 4514 section 2.4 by escaping null characters and a
+  space occurring at the beginning of the string
+* New method ldap.cidict.cidict.__contains__()
+* ldap.dn.explode_dn() and ldap.dn.explode_rdn()
+  have a new optional key-word argument flags which is
+  passed to ldap.dn.str2dn().
+
+Modules/
+* Removed unused OPT_PRIVATE_EXTENSION_BASE from constants.c
+
+Doc/
+* Various additions, updates, polishing (thanks to James).
+
+----------------------------------------------------------------
+Released 2.3.1 2007-07-25
+
+Changes since 2.3.0:
+
+* Support for setuptools (building .egg, thanks to Torsten)
+* Support for matched values control (RFC 3876, thanks to Andreas)
+
+Lib/
+* Fixed ldif (see SF#1709111, thanks to Dmitry)
+* ldap.schema.models:
+  SUP now separated by $ (method __str__() of classes
+  AttributeType, ObjectClass and DITStructureRule, thanks to Stefan)
+
+Modules/
+* Added constant MOD_INCREMENT to support
+  modify+increment extension (see RFC 4525, thanks to Andreas)
+----------------------------------------------------------------
+Released 2.3.0 2007-03-27
+
+Changes since 2.2.1:
+
+* OpenLDAP 2.3+ required now to build.
+* Added support for Cancel operation ext. op. if supported
+in OpenLDAP API of the libs used for the build.
+
+Modules/
+* Removed deprecated code for setting options by name
+* Added l_ldap_cancel()
+* Some modifications related to PEP 353 for
+  Python 2.5 on 64-bit platforms (see SF#1467529, thanks to Matej)
+* Added new function l_ldap_str2dn(), removed functions
+  l_ldap_explode_dn() and l_ldap_explode_rdn()
+  (see SF#1657848, thanks to David)
+
+Lib/
+* Added method ldapobject.LDAPObject.cancel()
+* ldap.schema.subentry.urlfetch() now can do non-anonymous
+  simple bind if the LDAP URL provided contains extensions
+  'bindname' and 'X-BINDPW'. (see SF#1589206)
+* ldap.filter.escape_filter_chars() has new a key-word argument
+  escape_mode now which defines which chars to be escaped
+  (see SF#1193271).
+* Various important fixes to ldapobject.ReconnectLDAPObject
+* Moved all DN-related functions to sub-module ldap.dn,
+  import them in ldap.functions for backward compability
+* ldap.dn.explode_dn() and ldap.dn.explode_rdn() use the new
+  wrapper function ldap.dn.str2dn() (related to SF#1657848)
+* changetype issue partially fixed (see SF#1683746)
+
+----------------------------------------------------------------
+Released 2.2.1 2006-11-15
+
+Changes since 2.2.0:
+
+Modules/
+* Fix for Python 2.5 free(): invalid pointer (see SF#1575329)
+* passwd() accepts None for arguments user, oldpw, newpw
+  (see SF#1440151)
+
+Lib/
+* ldif.LDIFWriter.unparse() now accepts instances of
+  derived dict and list classes (see SF#1489898)
+
+----------------------------------------------------------------
+Released 2.2.0 2006-04-10
+
+Changes since 2.0.11:
+
+* OpenLDAP 2.2+ required now to build.
+
+Modules/
+* Dropped all occurences of '#ifdef #LDAP_VENDOR_VERSION'.
+* Fixed wrong tuple size in l_ldap_result3() (see SF#1368108)
+* Fixed get_option(ldap.OPT_API_INFO) (see SF#1440165)
+* Fixed memory leak in l_ldap_result3() when all=0
+  (see SF#1457325)
+* Fixed memory leak in l_ldap_result3() in error cases
+  (see SF#1464085)
+
+Lib/
+* Fixed ldap.schema.models.DITStructureRule.__str__() to
+  separate SUP rule-ids with a single space instead of ' $ '
+* Fixed ldap.async.Dict
+* Added ldap.async.IndexedDict
+* ldap.schema.subentry.SubSchema.attribute_types() has new
+  key-word argument ignore_dit_content_rule
+----------------------------------------------------------------
+Released 2.0.11 2005-11-07
+
+Changes since 2.0.10:
+
+Lib/
+* Class ldap.ldapobject.LDAPObject:
+  Each method returns a result now
+* Class ldap.ldapobject.ReconnectLDAPObject:
+  Some methods called the wrong methods of LDAPObject. Fixed.
+* Added new class ldap.async.Dict
+* Slightly cleaned up ldap.schema.subentry.attribute_types()
+* New sub-module ldap.resiter which simply provides a mix-in
+  class for ldap.ldapobject.LDAPObject with a generator method
+  allresults().
+  Obviously this only works with Python 2.3+. And
+  it's still experimental.
+
+----------------------------------------------------------------
+Released 2.0.10 2005-09-23
+
+Changes since 2.0.9:
+
+Lib/
+* Switched back to old implementation of
+  ldap.schema.tokenizer.split_tokens() since the new one
+  had a bug which deletes the spaces from DESC
+* ldap.INSUFFICIENT_ACCESS is now ignored in 
+  ldap.ldapobject.LDAPObject.search_subschemasubentry_s()
+
+----------------------------------------------------------------
+Released 2.0.9 2005-07-28
+
+Changes since 2.0.8:
+
+Modules/
+* Removed __doc__ strings from ldapcontrol.c to "fix"
+  build problems with Python versions 2.2 and earlier.
+
+----------------------------------------------------------------
+Released 2.0.8 2005-06-22 at Linuxtag 2005, Karlsruhe, Germany
+
+Changes since 2.0.7:
+
+* Preliminary support for receiving LDAP controls added.
+  Contributor:
+  - Andreas Ames
+
+Lib/
+- Added classes in module ldif to ldif.__all__ to fix
+  from ldif import *
+- Removed BitString syntax from
+  ldap.schema.models.NOT_HUMAN_READABLE_LDAP_SYNTAXES
+  since the LDAP encoding is in fact human-readable
+- ldapurl.LDAPUrlExtension.unparse() outputs empty string
+  if LDAPUrlExtension.exvalue is None
+- Added ldap.controls.SimplePagedResultsControl
+
+----------------------------------------------------------------
+Released 2.0.7 2005-04-29
+
+Changes since 2.0.6:
+
+* Added preliminary support for sending LDAP controls
+  with a request.
+  Contributors:
+  - Deepak Giridharagopal
+  - Ingo Steuwer
+  (Receiving controls in LDAP results still not supported.)
+
+Modules:
+* LDAPObject.c: removed l_ldap_manage_dsa_it()
+* LDAPObject.c: Added missing #ifdef around l_ldap_passwd()
+  for compability with older OpenLDAP libs.
+
+Lib/
+* New algorithm in ldap.schema.tokenizer.split_tokens()
+  contributed by Wido Depping which is more robust
+  when parsing very broken schema elements
+  (e.g. Oracle's OID).
+* Fixed argument list (position of timeout) when calling
+  LDAPObject.search_ext_s() from search_st() and search_s().
+* LDAPObject.search_ext_s() correctly calls search_ext_s() now.
+* Re-implemented LDAPObject.manage_dsa_it() without calling _ldap.
+
+----------------------------------------------------------------
+Released 2.0.6 2004-12-03
+
+Changes since 2.0.5:
+
+Lib/
+* Added sub-module ldap.dn
+* Added function ldap.dn.escape_dn_chars()
+* Special check when implicitly setting SUP 'top' to
+  structural object classes without SUP defined to avoid
+  a loop in the super class chain.
+
+----------------------------------------------------------------
+Released 2.0.5 2004-11-11
+
+Changes since 2.0.4:
+
+Some small improvements for SASL:
+The noisy output during SASL bind is avoided now. Interaction
+with output on stderr can be enabled by the calling application
+by explicitly defining SASL flags.
+
+Removed obsolete directory Win32/.
+
+Lib/
+* Make sure that ldap.sasl.sasl.cb_value_dict is a dictionary
+  even when the caller passes in None to argument cb_value_dict
+* Added new key-word arg sasl_flags to method
+  LDAPObject.sasl_interactive_bind_s()
+
+Modules/
+* l_ldap_sasl_interactive_bind_s():
+  New key-word arg sasl_flags passed to
+  ldap_sasl_interactive_bind_s()
+
+----------------------------------------------------------------
+Released 2.0.4 2004-10-27
+
+Changes since 2.0.3:
+
+Modules/
+* Applied some fixes for 64-bit platforms to LDAPObject.c
+* Constants ldap.TLS_AVAIL and ldap.SASL_AVAIL will indicate
+  whether python-ldap was built with support for SSL/TLS
+  and/or SASL
+
+setup.py and Modules/
+* Applied some fixes for building under Win32
+
+----------------------------------------------------------------
+Released 2.0.3 2004-10-06
+
+Changes since 2.0.2:
+
+* Added support for LDAP Password Modify Extended Operation
+  (see RFC 3062)
+
+Demo/:
+* Added passwd_ext_op.py
+
+Modules/
+* Added l_ldap_passwd() in LDAPObject.c
+
+Lib/
+* Added methods passwd() and passwd_s() to
+  ldap.ldapobject.LDAPObject
+
+----------------------------------------------------------------
+Released 2.0.2 2004-07-29
+
+Changes since 2.0.1:
+
+Modules/
+* Fixed detecting appropriate OpenLDAP libs version for
+  determining whether ldap_whoami_s() is available or not.
+  This fixes build problems with OpenLDAP libs 2.1.0 up
+  to 2.1.12.
+
+----------------------------------------------------------------
+Released 2.0.1 2004-06-29
+
+Changes since 2.0.0:
+
+dsml:
+* Fixed wrong exception message format string
+
+ldap.schema.models:
+* Fixed Entry.__delitem__() to delete really everything
+  when deleting an attribute dictionary item.
+
+----------------------------------------------------------------
+Released 2.0.0 2004-05-18
+
+Changes since 2.0.0pre21:
+
+ldif:
+* Empty records are simply ignored in ldif.LDIFWriter.unparse()
+
+Modules/
+* New method result2() returns 3-tuple containing the msgid
+  of the outstanding operation.
+
+ldap.ldapobject:
+* New _ldap wrapper method LDAPObject.result2() (see above)
+  which is now used by LDAPObject.result().
+
+----------------------------------------------------------------
+Released 2.0.0pre21 2004-03-29
+
+Changes since 2.0.0pre20:
+
+setup.py:
+* runtime_library_dirs is set
+
+Modules/
+* (Hopefully) fixed building with OpenLDAP 2.2 libs in errors.c
+* Removed meaningless repr() function from LDAPObject.c
+* Removed setting LDAP_OPT_PROTOCOL_VERSION in l_ldap_sasl_bind_s()
+* Modified string handling via berval instead of *char
+  in l_ldap_compare_ext() makes it possible to compare attribute
+  values with null chars.
+* Wrapped ldap_sasl_bind() for simple binds instead of ldap_bind()
+  since 1. the latter is marked deprecated and 2. ldap_sasl_bind()
+  allows password credentials with null chars.
+* Removed unused sources linkedlist.c and linkedlist.h
+* Function l_ldap_whoami_s() only added if built against
+  OpenLDAP 2.1.x+ libs (should preserve compability with 2.0 libs)
+
+ldap.ldapobject:
+* LDAPObject.bind() only allows simple binds since Kerberos V4
+  binds of LDAPv2 are not supported anymore. An assert statement
+  was added to make the coder aware of that.
+* Renamed former LDAPObject.sasl_bind_s() to
+  LDAPObject.sasl_interactive_bind_s() since it wraps OpenLDAP's
+  ldap_sasl_interactive_bind_s()
+
+----------------------------------------------------------------
+Released 2.0.0pre20 2004-03-19
+
+Changes since 2.0.0pre19:
+
+Modules/
+* Removed doc strings from functions.c
+* Removed probably unused wrapper function l_ldap_dn2ufn() since
+  ldap_dn2ufn() is deprecated in OpenLDAP 2.1+
+* Removed wrapper function l_ldap_is_ldap_url().
+* Removed macro add_int_r() from constants.c since it caused
+  incompability issues with OpenLDAP 2.2 libs
+  (Warning: all result types are Integers now! Use the constants!)
+* New wrapper function l_ldap_whoami_s()
+
+ldap.ldapobject:
+* New wrapper method LDAPObject.whoami_s()
+
+ldap.functions:
+* Removed is_ldap_url(). The more general function
+  ldapurl.isLDAPUrl() should be used instead.
+
+ldap.sasl:
+* Added class cram_md5 (for SASL mech CRAM-MD5)
+
+ldap.async:
+* Use constants for search result types (see note about
+  add_int_r() above).
+
+----------------------------------------------------------------
+Released 2.0.0pre19 2004-01-22
+
+Changes since 2.0.0pre18:
+
+Modules/
+* LDAPObject.c:
+  Most deprecated functions of OpenLDAP C API are not used anymore.
+* functions.c:
+  Removed unused default_ldap_port().
+* constants.c:
+  Removed unused or silly constants
+  AUTH_KRBV4, AUTH_KRBV41, AUTH_KRBV42, URL_ERR_BADSCOPE, URL_ERR_MEM
+* errors.c:
+  Fixed building with OpenLDAP 2.2.x
+  (errors caused by negative error constants in ldap.h)
+
+ldap.ldapobject.LDAPObject:
+* Removed unused wrapper methods uncache_entry(), uncache_request(),
+  url_search(), url_search_st() and url_search_s()
+* New wrapper methods for all the _ext() methods in _ldap.LDAPObject.
+
+ldap.modlist:
+* Some performance optimizations and simplifications
+  in function modifyModlist()
+
+----------------------------------------------------------------
+Released 2.0.0pre18 2003-12-09
+
+Changes since 2.0.0pre17:
+
+ldap.ldapobject:
+* Fixed missing ldap._ldap_function_call() in
+  ReconnectLDAPObject.reconnect()
+
+----------------------------------------------------------------
+Released 2.0.0pre17 2003-12-03
+
+Changes since 2.0.0pre16:
+
+ldap.functions:
+* Fixed ImportError when running python -O
+
+----------------------------------------------------------------
+Released 2.0.0pre16 2003-12-02
+
+Changes since 2.0.0pre15:
+
+Modules/
+* Removed definition of unused constant RES_EXTENDED_PARTIAL since
+  the corresponding symbol LDAP_RES_EXTENDED_PARTIAL seems to not
+  be available in OpenLDAP-HEAD (pre 2.2) anymore.
+
+All in Lib/
+* Fixed some subtle bugs/oddities mentioned by pychecker.
+
+dsml:
+* Renamed DSMLWriter._f to DSMLWriter._output_file
+* Added wrapper method DSMLWriter.unparse() which simply
+  calls DSMLWriter.writeRecord()
+
+ldap.ldapobject:
+* Simplified LDAPObject.search_subschemasubentry_s()
+
+ldap.functions:
+* Moved ldap._ldap_function_call() into ldap.functions.
+* apply() is not used anymore since it seems deprecated
+
+ldap.async:
+* Added class DSMLWriter
+
+ldap.schema:
+* Removed unused key-word argument strict from
+  ldap.schema.subentry.SubSchema.attribute_types()
+* Fixed backward compability issue (for Python prior to 2.2) in
+  ldap.schema.subentry.SubSchema.listall()
+----------------------------------------------------------------
+Released 2.0.0pre15 2003-11-11
+
+Changes since 2.0.0pre14:
+
+Modules/
+Follow rule "Always include Python.h first"
+
+ldap.schema.subentry:
+* Added new method SubSchema.get_structural_oc()
+* Added new method SubSchema.get_applicable_aux_classes()
+* Methods SubSchema.listall() and SubSchema.tree() have
+  new key-word argument schema_element_filters
+* Support for DIT content rules in SubSchema.attribute_types()
+
+----------------------------------------------------------------
+Released 2.0.0pre14 2003-10-03
+
+Changes since 2.0.0pre13:
+
+setup.py:
+* Some modifications to ease building for Win32
+* Added directory Build/ mainly intended for platform-specific
+  examples of setup.cfg
+* Fixed installing ldap.filter
+
+ldap.ldapobject:
+* Added class attribute LDAPObject.network_timeout mapped to
+  set_option(ldap.OPT_NETWORK_TIMEOUT,..)
+* LDAPObject.search_ext(): Pass arguments serverctrls,clientctrls
+  to _ldap.search_ext()
+
+ldap.sasl:
+* Added class ldap.sasl.external for handling
+  the SASL mechanism EXTERNAL
+* Dictionary ldap.sasl.saslmech_handler_class built during import
+  for all the known SASL mechanisms derived from class definitions
+
+ldap.schema:
+* More graceful handling of KeyError in SubSchema.attribute_types()
+* New method SubSchema.get_inheritedattr() for retrieving inherited
+  class attributes
+* New method SubSchema.get_inheritedobj() for retrieving a
+  schema element instance including all inherited class attributes
+
+----------------------------------------------------------------
+Released 2.0.0pre13 2003-06-02
+
+Changes since 2.0.0pre12:
+
+ldap.async:
+* Checking type of argument writer_obj relaxed in
+  LDIFWriter.__init__() since file-like objects are
+  not necessarily an instance of file.
+
+ldap.schema:
+* ldap.schema.subentry.SubSchema.attribute_types() now correctly
+  handles attribute types without NAME set
+* If SUP is not defined for a structural object class 'top' is
+  assumed to be the only super-class by default
+* '_' is now the abstract top node in SubSchema.tree() for all
+  schema element classes since ABSTRACT and AUXILIARY object
+  classes are not derived from 'top' by default
+
+----------------------------------------------------------------
+Released 2.0.0pre12 2003-05-27
+
+Changes since 2.0.0pre11:
+
+New sub-module ldap.filter:
+* Added functions escape_filter_chars() and filter_format()
+
+ldap.ldapobject:
+* Trace log writes LDAP URI of connection instead of module name
+* search_s() passes self.timeout as argument timeout when
+  calling search_ext_s()
+* Key-word arguments for simple_bind() and simple_bind_s()
+  with defaults for anonymous bind.
+* LDAPObject.protocol_version is set to LDAPv3 as default
+  (this might make code changes necessary in a real LDAPv2
+   environment)
+* Default for key-word argument trace_stack_limit passed to
+  __init__() is 5
+* Updated __doc__ strings
+* Aligned and tested ReconnectLDAPObject and SmartLDAPObject
+
+ldap.async:
+* LDIFWriter uses ldif.LDIFWriter instead of calling
+  function ldif.CreateLDIF
+* LDIFWriter accepts either file-like object or ldif.LDIFWriter
+  instance as argument for specifying the output
+
+ldif:
+* Abandoned argument all_records of LDIFRecordList.__init__()
+
+ldapurl:
+* urllib.unquote() used instead of urllib.unquote_plus()
+
+----------------------------------------------------------------
+Released 2.0.0pre11 2003-05-02
+
+Changes since 2.0.0pre10:
+
+ldap.ldapobject:
+* Cosmetic change: Named argument list for LDAPObject.compare()
+  instead of *args,**kwargs.
+* Fixed bug in ReconnectLDAPObject._apply_method_s() affecting
+  compability with Python 2.0. The bug was introduced with
+  2.0.0pre09 by dropping use of apply().
+
+ldap.modlist:
+* modifyModlist(): Only None is filtered from attribute value lists,
+  '' is preserved as valid attribute value. But filtering applies
+  to old_value and new_value now.
+
+ldap.schema:
+* Zero-length attribute values for schema elements are ignored
+  (needed on e.g. Active Directory)
+
+dsml:
+Added support for parsing and generating DSMLv1.
+Still experimental though.
+
+
+----------------------------------------------------------------
+Released 2.0.0pre10 2003-04-19
+
+Changes since 2.0.0pre09:
+
+ldap.schema:
+* Emulate BooleanType for compability with Python2.3 in assert
+  statements
+
+----------------------------------------------------------------
+Released 2.0.0pre09 2003-04-19
+
+Changes since 2.0.0pre08:
+
+Modified setup.py to support Cyrus-SASL 2.x.
+
+ldap.ldapobject:
+* apply() is not used anymore since it seems deprecated
+* Fixed __setstate__() and __getstate__() of ReconnectLDAPObject
+
+ldap.schema:
+* Completed classes for nameForms, dITStructureRules and
+  dITContentRules
+
+----------------------------------------------------------------
+Released 2.0.0pre08 2003-04-11
+
+Changes since 2.0.0pre07:
+
+ldap.schema:
+* For backward compability with Python versions prior to 2.2
+  Lib/ldap/schema/tokenizer.py and Lib/ldap/schema/models.py use
+  (()) instead of tuple() for creating empty tuples.
+
+----------------------------------------------------------------
+Released 2.0.0pre07 2003-04-03
+
+Changes since 2.0.0pre06:
+
+LDAPObject.c:
+  * Wrapped OpenLDAP's ldap_search_ext()
+  * Removed empty __doc__ strings
+  * Removed fileno
+  * Removed all stuff related to caching in OpenLDAP libs
+
+ldap.ldapobject:
+  * Fixed SASL rebind in ldap.ldapobject.ReconnectLDAPObject
+  * use search_ext() instead ldap_search()
+  * new class attribute timeout for setting a global time-out
+    value for all synchronous operations
+
+ldap.schema:
+* Fixed two typos in ldap.schema.models
+* Some attempts to improve performance of parser/tokenizer
+* Completely reworked to have separate OID dictionaries for
+  the different schema element classes
+* Fixed the Demo/schema*.py to reflect changes to ldap.schema
+
+Documentation updates and various __doc__ string modifications.
+
+ldapurl:
+  * Removed all Unicode stuff from module ldapurl
+  * Consistent URL encoding in module ldapurl
+
+ldif:
+  * Removed ldif.FileWriter
+  * Proper handling of FILL (see RFC 2849)
+
+----------------------------------------------------------------
+Released 2.0.0pre06 2002-09-23
+
+Changes since 2.0.0pre05:
+- Fine-grained locking when linking against libldap_r
+- New wrapper class ldap.ReconnectLDAPObject
+- Security fix to module ldapurl
+- Other fixes and improvements to whole package
+- LDAPv3 schema support
+  (still somewhat premature and undocumented)
+
+----------------------------------------------------------------
+Released 2.0.0pre05 2002-07-20
+
+----------------------------------------------------------------
+Released 2.0.0pre04 2002-02-09
+
+----------------------------------------------------------------
+Released 2.0.0pre02 2002-02-01
+
+----------------------------------------------------------------
+Released 1.10alpha3 2000-09-19
+
+$Id: CHANGES,v 1.273 2011/11/27 15:26:06 stroeder Exp $

+ 4 - 0
desktop/core/ext-py/python-ldap-2.4.6/Demo/.cvsignore

@@ -0,0 +1,4 @@
+*.pyc
+*.pyo
+*.bck
+*~

+ 4 - 0
desktop/core/ext-py/python-ldap-2.4.6/Demo/Lib/.cvsignore

@@ -0,0 +1,4 @@
+*.pyc
+*.pyo
+*.bck
+*~

+ 4 - 0
desktop/core/ext-py/python-ldap-2.4.6/Demo/Lib/ldap/.cvsignore

@@ -0,0 +1,4 @@
+*.pyc
+*.pyo
+*.bck
+*~

+ 4 - 0
desktop/core/ext-py/python-ldap-2.4.6/Demo/Lib/ldap/async/.cvsignore

@@ -0,0 +1,4 @@
+*.pyc
+*.pyo
+*.bck
+*~

+ 80 - 0
desktop/core/ext-py/python-ldap-2.4.6/Demo/Lib/ldap/async/deltree.py

@@ -0,0 +1,80 @@
+import ldap,ldap.async
+
+class DeleteLeafs(ldap.async.AsyncSearchHandler):
+  """
+  Class for deleting entries which are results of a search.
+  
+  DNs of Non-leaf entries are collected in DeleteLeafs.nonLeafEntries.
+  """
+  _entryResultTypes = ldap.async._entryResultTypes
+
+  def __init__(self,l):
+    ldap.async.AsyncSearchHandler.__init__(self,l)
+    self.nonLeafEntries = []
+    self.deletedEntries = 0
+
+  def startSearch(self,searchRoot,searchScope):
+    if not searchScope in [ldap.SCOPE_ONELEVEL,ldap.SCOPE_SUBTREE]:
+      raise ValueError, "Parameter searchScope must be either ldap.SCOPE_ONELEVEL or ldap.SCOPE_SUBTREE."
+    self.nonLeafEntries = []
+    self.deletedEntries = 0
+    ldap.async.AsyncSearchHandler.startSearch(
+      self,
+      searchRoot,
+      searchScope,
+      filterStr='(objectClass=*)',
+      attrList=['hasSubordinates','numSubordinates'],
+      attrsOnly=0,
+    )
+
+  def _processSingleResult(self,resultType,resultItem):
+    if self._entryResultTypes.has_key(resultType):
+      # Don't process search references
+      dn,entry = resultItem
+      hasSubordinates = entry.get(
+        'hasSubordinates',
+        entry.get('hassubordinates',['FALSE']
+        )
+      )[0]
+      numSubordinates = entry.get(
+        'numSubordinates',
+        entry.get('numsubordinates',['0'])
+      )[0]
+      if hasSubordinates=='TRUE' or int(numSubordinates):
+        self.nonLeafEntries.append(dn)
+      else:
+        try:
+          self._l.delete_s(dn)
+        except ldap.NOT_ALLOWED_ON_NONLEAF,e:
+          self.nonLeafEntries.append(dn)
+        else:
+          self.deletedEntries = self.deletedEntries+1
+
+
+def DelTree(l,dn,scope=ldap.SCOPE_ONELEVEL):
+  """
+  Recursively delete entries below or including entry with name dn.
+  """
+  leafs_deleter = DeleteLeafs(l)
+  leafs_deleter.startSearch(dn,scope)
+  leafs_deleter.processResults()
+  deleted_entries = leafs_deleter.deletedEntries
+  non_leaf_entries = leafs_deleter.nonLeafEntries[:]
+  while non_leaf_entries:
+    dn = non_leaf_entries.pop()
+    print deleted_entries,len(non_leaf_entries),dn
+    leafs_deleter.startSearch(dn,ldap.SCOPE_SUBTREE)
+    leafs_deleter.processResults()
+    deleted_entries = deleted_entries+leafs_deleter.deletedEntries
+    non_leaf_entries.extend(leafs_deleter.nonLeafEntries)
+  return # DelTree()
+
+
+# Create LDAPObject instance
+l = ldap.initialize('ldap://localhost:1390')
+
+# Try a bind to provoke failure if protocol version is not supported
+l.simple_bind_s('cn=Directory Manager,dc=IMC,dc=org','controller')
+
+# Delete all entries *below* the entry dc=Delete,dc=IMC,dc=org
+DelTree(l,'dc=Delete,dc=IMC,dc=org',ldap.SCOPE_ONELEVEL)

+ 42 - 0
desktop/core/ext-py/python-ldap-2.4.6/Demo/Lib/ldap/async/ldifwriter.py

@@ -0,0 +1,42 @@
+"""
+ldifwriter - using ldap.async module for output of LDIF stream
+             of LDAP search results
+
+Written by Michael Stroeder <michael@stroeder.com>
+
+$Id: ldifwriter.py,v 1.4 2006/03/26 12:23:07 stroeder Exp $
+
+This example translates the naming context of data read from
+input, sanitizes some attributes, maps/removes object classes,
+maps/removes attributes., etc. It's far from being complete though.
+
+Python compability note:
+Tested on Python 2.0+, should run on Python 1.5.x.
+"""
+
+import sys,ldap,ldap.async
+
+s = ldap.async.LDIFWriter(
+  ldap.initialize('ldap://localhost:1390'),
+  sys.stdout
+)
+
+s.startSearch(
+  'dc=stroeder,dc=de',
+  ldap.SCOPE_SUBTREE,
+  '(objectClass=*)',
+)
+
+try:
+  partial = s.processResults()
+except ldap.SIZELIMIT_EXCEEDED:
+  sys.stderr.write('Warning: Server-side size limit exceeded.\n')
+else:
+  if partial:
+    sys.stderr.write('Warning: Only partial results received.\n')
+
+sys.stderr.write(
+  '%d results received.\n' % (
+    s.endResultBreak-s.beginResultsDropped
+  )
+)

+ 42 - 0
desktop/core/ext-py/python-ldap-2.4.6/Demo/Lib/ldap/async/sizelimit.py

@@ -0,0 +1,42 @@
+"""
+ldifwriter - using ldap.async module for retrieving partial results
+             in a list even though the exception ldap.SIZELIMIT_EXCEEDED
+             was raised.output of LDIF stream
+
+Written by Michael Stroeder <michael@stroeder.com>
+
+$Id: sizelimit.py,v 1.4 2006/03/26 12:23:07 stroeder Exp $
+
+This example translates the naming context of data read from
+input, sanitizes some attributes, maps/removes object classes,
+maps/removes attributes., etc. It's far from being complete though.
+
+Python compability note:
+Tested on Python 2.0+, should run on Python 1.5.x.
+"""
+
+import sys,ldap,ldap.async
+
+s = ldap.async.List(
+  ldap.initialize('ldap://localhost:1390'),
+)
+
+s.startSearch(
+  'dc=stroeder,dc=de',
+  ldap.SCOPE_SUBTREE,
+  '(objectClass=*)',
+)
+
+try:
+  partial = s.processResults()
+except ldap.SIZELIMIT_EXCEEDED:
+  sys.stderr.write('Warning: Server-side size limit exceeded.\n')
+else:
+  if partial:
+    sys.stderr.write('Warning: Only partial results received.\n')
+
+sys.stderr.write(
+  '%d results received.\n' % (
+    len(s.allResults)
+  )
+)

+ 4 - 0
desktop/core/ext-py/python-ldap-2.4.6/Demo/Lib/ldapurl/.cvsignore

@@ -0,0 +1,4 @@
+*.pyc
+*.pyo
+*.bck
+*~

+ 32 - 0
desktop/core/ext-py/python-ldap-2.4.6/Demo/Lib/ldapurl/urlsearch.py

@@ -0,0 +1,32 @@
+"""
+Do a search with the LDAP URL specified at command-line.
+
+No output of LDAP data is produced except trace output.
+"""
+
+import sys,getpass,ldap,ldapurl
+
+try:
+  ldapUrl = ldapurl.LDAPUrl(ldapUrl=sys.argv[1])
+except IndexError:
+  print 'Usage: %s [LDAP URL]' % (sys.argv[0])
+  sys.exit(1)
+
+for a in [
+  'urlscheme','hostport','dn','attrs','scope',
+  'filterstr','extensions','who','cred'
+]:
+  print a,repr(getattr(ldapUrl,a))
+
+l = ldap.initialize(ldapUrl.initializeUrl(),trace_level=1)
+if ldapUrl.who!=None:
+  if ldapUrl.cred!=None:
+    cred=ldapUrl.cred
+  else:
+    print 'Enter password for simple bind with',repr(ldapUrl.who)
+    cred=getpass.getpass()
+  l.simple_bind_s(ldapUrl.who,cred)
+
+res = l.search_s(ldapUrl.dn,ldapUrl.scope,ldapUrl.filterstr,ldapUrl.attrs)
+
+print len(res),'search results'

+ 4 - 0
desktop/core/ext-py/python-ldap-2.4.6/Demo/Lib/ldif/.cvsignore

@@ -0,0 +1,4 @@
+*.pyc
+*.pyo
+*.bck
+*~

+ 28 - 0
desktop/core/ext-py/python-ldap-2.4.6/Demo/Lib/ldif/ldifcopy.py

@@ -0,0 +1,28 @@
+"""
+ldifcopy - reads LDIF from stdin, retrieve values by URL and
+           write resulting LDIF to stdout
+
+Written by Michael Stroeder <michael@stroeder.com>
+
+$Id: ldifcopy.py,v 1.2 2001/12/12 22:04:49 stroeder Exp $
+
+This example translates the naming context of data read from
+input, sanitizes some attributes, maps/removes object classes,
+maps/removes attributes., etc. It's far from being complete though.
+
+Python compability note:
+Tested on Python 2.0+, should run on Python 1.5.x.
+"""
+
+import sys,ldif
+
+infile = sys.stdin
+outfile = sys.stdout
+
+ldif_collector = ldif.LDIFCopy(
+  infile,
+  outfile,
+  process_url_schemes=['file','ftp','http']
+)
+ldif_collector.parse()
+

+ 91 - 0
desktop/core/ext-py/python-ldap-2.4.6/Demo/initialize.py

@@ -0,0 +1,91 @@
+"""
+Various examples how to connect to a LDAP host with the new
+factory function ldap.initialize() introduced in OpenLDAP 2 API.
+
+Assuming you have LDAP servers running on
+ldap://localhost:1390 (LDAP with StartTLS)
+ldaps://localhost:1391 (LDAP over SSL)
+ldapi://%2ftmp%2fopenldap2 (domain socket /tmp/openldap2)
+"""
+
+import sys,ldap
+
+# Set debugging level
+ldap.set_option(ldap.OPT_DEBUG_LEVEL,0)
+ldapmodule_trace_level = 1
+ldapmodule_trace_file = sys.stderr
+
+# Complete path name of the file containing all trusted CA certs
+CACERTFILE='/etc/apache2/ssl.crt/ca-bundle.crt'
+
+# TLS-related options have to be set globally since the TLS context is only initialized once
+
+# Force cert validation
+ldap.set_option(ldap.OPT_X_TLS_REQUIRE_CERT,ldap.OPT_X_TLS_DEMAND)
+# Set path name of file containing all trusted CA certificates
+ldap.set_option(ldap.OPT_X_TLS_CACERTFILE,CACERTFILE)
+
+
+print """##################################################################
+# LDAPv3 connection with StartTLS
+##################################################################
+"""
+
+# Create LDAPObject instance
+l = ldap.initialize('ldap://localhost:1390',trace_level=ldapmodule_trace_level,trace_file=ldapmodule_trace_file)
+
+# Set LDAP protocol version used
+l.protocol_version=ldap.VERSION3
+# Force libldap to create a new SSL context
+#l.set_option(ldap.OPT_X_TLS_NEWCTX,ldap.OPT_X_TLS_DEMAND)
+# Force cert validation
+#l.set_option(ldap.OPT_X_TLS_REQUIRE_CERT,ldap.OPT_X_TLS_DEMAND)
+# Set path name of file containing all trusted CA certificates
+#l.set_option(ldap.OPT_X_TLS_CACERTFILE,CACERTFILE)
+
+# Now try StartTLS extended operation
+l.start_tls_s()
+
+# Try a bind to provoke failure if protocol version is not supported
+l.simple_bind_s('','')
+
+# Close connection
+l.unbind_s()
+
+print """##################################################################
+# LDAPv3 connection over SSL
+##################################################################
+"""
+
+# Create LDAPObject instance
+l = ldap.initialize('ldaps://localhost:1391',trace_level=ldapmodule_trace_level,trace_file=ldapmodule_trace_file)
+
+# Set LDAP protocol version used
+l.protocol_version=ldap.VERSION3
+# Force libldap to create a new SSL context
+#l.set_option(ldap.OPT_X_TLS_NEWCTX,ldap.OPT_X_TLS_DEMAND)
+# Force cert validation
+#l.set_option(ldap.OPT_X_TLS_REQUIRE_CERT,ldap.OPT_X_TLS_DEMAND)
+# Set path name of file containing all trusted CA certificates
+#l.set_option(ldap.OPT_X_TLS_CACERTFILE,CACERTFILE)
+
+# Try a bind to provoke failure if protocol version is not supported
+l.simple_bind_s('','')
+
+# Close connection
+l.unbind_s()
+
+print """##################################################################
+# LDAPv3 connection over Unix domain socket
+##################################################################
+"""
+
+# Create LDAPObject instance
+l = ldap.initialize('ldapi://%2ftmp%2fopenldap-socket',trace_level=ldapmodule_trace_level,trace_file=ldapmodule_trace_file)
+# Set LDAP protocol version used
+l.protocol_version=ldap.VERSION3
+# Try a bind to provoke failure if protocol version is not supported
+l.bind_s('','',ldap.AUTH_SIMPLE)
+# Close connection
+l.unbind_s()
+

+ 33 - 0
desktop/core/ext-py/python-ldap-2.4.6/Demo/ldapcontrols.py

@@ -0,0 +1,33 @@
+import ldap,ldapurl,pprint
+
+from ldap.controls import LDAPControl,BooleanControl
+
+l = ldap.initialize('ldap://localhost:1390',trace_level=2)
+
+print 60*'#'
+
+pprint.pprint(l.get_option(ldap.OPT_SERVER_CONTROLS))
+l.manage_dsa_it(1,1)
+pprint.pprint(l.get_option(ldap.OPT_SERVER_CONTROLS))
+print 60*'#'
+
+# Search with ManageDsaIT control (which has no value)
+pprint.pprint(l.search_ext_s(
+  'cn=Test-Referral,ou=Testing,dc=stroeder,dc=de',
+  ldap.SCOPE_BASE,
+  '(objectClass=*)',
+  ['*','+'],
+  serverctrls = [ LDAPControl('2.16.840.1.113730.3.4.2',1,None) ],
+))
+print 60*'#'
+
+# Search with Subentries control (which has boolean value)
+pprint.pprint(l.search_ext_s(
+  'dc=stroeder,dc=de',
+  ldap.SCOPE_SUBTREE,
+  '(objectClass=subentry)',
+  ['*','+'],
+  serverctrls = [ BooleanControl('1.3.6.1.4.1.4203.1.10.1',1,1) ],
+))
+
+print 60*'#'

+ 63 - 0
desktop/core/ext-py/python-ldap-2.4.6/Demo/matchedvalues.py

@@ -0,0 +1,63 @@
+#!/usr/bin/env python
+#
+# demo for matched values control (RFC 3876)
+#
+# suppose the uid=jsmith LDAP entry has two mail attributes:
+#
+# dn: uid=jsmith,ou=People,dc=example,dc=com
+# (...)
+# mail: jsmith@example.com
+# mail: jsmith@example.org
+#
+# Let's say you want to fetch only the example.org email. Without MV,
+# you would first fetch all mail attributes and then filter them further
+# on the client. With the MV control, the result can be given to the
+# client already filtered.
+#
+# Sample output:
+# $ ./matchedvalues.py
+# LDAP filter used: (&(objectClass=inetOrgPerson)(mail=*@example.org))
+# Requesting 'mail' attribute back
+#
+# No matched values control:
+# dn: uid=jsmith,ou=People,dc=example,dc=com
+# mail: jsmith@example.org
+# mail: john@example.com
+#
+# Matched values control: (mail=*@example.org)
+# dn: uid=jsmith,ou=People,dc=example,dc=com
+# mail: jsmith@example.org
+
+import ldap
+from ldap.controls import MatchedValuesControl
+
+def print_result(search_result):
+    for n in range(len(search_result)):
+        print "dn: %s" % search_result[n][0]
+        for attr in search_result[n][1].keys():
+            for i in range(len(search_result[n][1][attr])):
+                print "%s: %s" % (attr, search_result[n][1][attr][i])
+        print
+
+
+uri = "ldap://ldap.example.com"
+base = "dc=example,dc=com"
+scope = ldap.SCOPE_SUBTREE
+filter = "(&(objectClass=inetOrgPerson)(mail=*@example.org))"
+control_filter = "(mail=*@example.org)"
+
+ld = ldap.initialize(uri)
+
+mv = MatchedValuesControl(criticality=True, controlValue=control_filter)
+
+res = ld.search_ext_s(base, scope, filter, attrlist = ['mail'])
+print "LDAP filter used: %s" % filter
+print "Requesting 'mail' attribute back"
+print
+print "No matched values control:"
+print_result(res)
+
+res = ld.search_ext_s(base, scope, filter, attrlist = ['mail'], serverctrls = [mv])
+print "Matched values control: %s" % control_filter
+print_result(res)
+

+ 38 - 0
desktop/core/ext-py/python-ldap-2.4.6/Demo/ms_ad_bind.py

@@ -0,0 +1,38 @@
+# How to bind to MS AD with python-ldap and various methods
+
+import ldap,ldap.sasl
+
+ldap_uri = "ldap://dc1.example.com"
+dn = "CN=Anna Blume,CN=Users,DC=addomain,DC=example,DC=com"
+sAMAccountName = "ABlume"
+userPrincipalName = "ablume@addomain.example.com"
+password = 'testsecret'
+
+trace_level = 2
+
+l = ldap.initialize(ldap_uri,trace_level=trace_level)
+
+# Normal LDAPv3 compliant simple bind
+l.simple_bind_s(dn,password)
+
+# This is AD-specific and not LDAPv3 compliant
+l.simple_bind_s(userPrincipalName,password)
+
+# This is AD-specific and not LDAPv3 compliant
+l.simple_bind_s(userPrincipalName,password)
+
+# SASL bind with mech DIGEST-MD5 with sAMAccountName as SASL user name
+sasl_auth = ldap.sasl.sasl(
+  {
+    ldap.sasl.CB_AUTHNAME:sAMAccountName,
+    ldap.sasl.CB_PASS    :password,
+  },
+  'DIGEST-MD5'
+)
+l.sasl_interactive_bind_s("", sasl_auth)
+
+# SASL bind with mech GSSAPI
+# with the help of Kerberos V TGT obtained before with command
+# kinit ablume@ADDOMAIN.EXAMPLE.COM
+sasl_auth = ldap.sasl.sasl({},'GSSAPI')
+l.sasl_interactive_bind_s("", sasl_auth)

+ 29 - 0
desktop/core/ext-py/python-ldap-2.4.6/Demo/options.py

@@ -0,0 +1,29 @@
+
+import ldap
+
+host="localhost:1390"
+
+print "API info:",ldap.get_option(ldap.OPT_API_INFO)
+print "debug level:",ldap.get_option(ldap.OPT_DEBUG_LEVEL)
+#print "Setting debug level to 255..."
+#ldap.set_option(ldap.OPT_DEBUG_LEVEL,255)
+#print "debug level:",ldap.get_option(ldap.OPT_DEBUG_LEVEL)
+print "default size limit:",ldap.get_option(ldap.OPT_SIZELIMIT)
+print "Setting default size limit to 10..."
+ldap.set_option(ldap.OPT_SIZELIMIT,10)
+print "default size limit:",ldap.get_option(ldap.OPT_SIZELIMIT)
+print "Creating connection to",host,"..."
+l=ldap.init(host)
+print "size limit:",l.get_option(ldap.OPT_SIZELIMIT)
+print "Setting connection size limit to 20..."
+l.set_option(ldap.OPT_SIZELIMIT,20)
+print "size limit:",l.get_option(ldap.OPT_SIZELIMIT)
+#print "Setting time limit to 60 secs..."
+l.set_option(ldap.OPT_TIMELIMIT,60)
+#print "time limit:",l.get_option(ldap.OPT_TIMELIMIT)
+print "Binding..."
+l.simple_bind_s("","")
+
+
+
+

+ 64 - 0
desktop/core/ext-py/python-ldap-2.4.6/Demo/page_control.py

@@ -0,0 +1,64 @@
+url = "ldap://localhost:1390/"
+base = "dc=stroeder,dc=de"
+search_flt = r'(objectClass=*)'
+page_size = 10
+
+import ldap,pprint
+from ldap.controls import SimplePagedResultsControl
+
+searchreq_attrlist=['cn','entryDN','entryUUID','mail','objectClass']
+
+#ldap.set_option(ldap.OPT_DEBUG_LEVEL,255)
+ldap.set_option(ldap.OPT_REFERRALS, 0)
+l = ldap.initialize(url,trace_level=1)
+l.protocol_version = 3
+l.simple_bind_s("", "")
+
+req_ctrl = SimplePagedResultsControl(True,size=page_size,cookie='')
+
+known_ldap_resp_ctrls = {
+  SimplePagedResultsControl.controlType:SimplePagedResultsControl,
+}
+
+# Send search request
+msgid = l.search_ext(
+  base,
+  ldap.SCOPE_SUBTREE,
+  search_flt,
+  attrlist=searchreq_attrlist,
+  serverctrls=[req_ctrl]
+)
+
+pages = 0
+while True:
+    pages += 1
+    print "Getting page %d" % (pages)
+    rtype, rdata, rmsgid, serverctrls = l.result3(msgid,resp_ctrl_classes=known_ldap_resp_ctrls)
+    print '%d results' % len(rdata)
+    print 'serverctrls=',pprint.pprint(serverctrls)
+#    pprint.pprint(rdata)
+    pctrls = [
+      c
+      for c in serverctrls
+      if c.controlType == SimplePagedResultsControl.controlType
+    ]
+    if pctrls:
+        print 'pctrls[0].size',repr(pctrls[0].size)
+        print 'pctrls[0].cookie',repr(pctrls[0].cookie)
+        if pctrls[0].cookie:
+            # Copy cookie from response control to request control
+            req_ctrl.cookie = pctrls[0].cookie
+            msgid = l.search_ext(
+              base,
+              ldap.SCOPE_SUBTREE,
+              search_flt,
+              attrlist=searchreq_attrlist,
+              serverctrls=[req_ctrl]
+            )
+        else:
+            break
+    else:
+        print "Warning:  Server ignores RFC 2696 control."
+        break
+
+l.unbind_s()

+ 109 - 0
desktop/core/ext-py/python-ldap-2.4.6/Demo/paged_search_ext_s.py

@@ -0,0 +1,109 @@
+url = "ldap://localhost:1390/"
+base = "dc=stroeder,dc=de"
+search_flt = r'(objectClass=*)'
+
+searchreq_attrlist=['cn','entryDN','entryUUID','mail','objectClass']
+
+from ldap.ldapobject import ReconnectLDAPObject
+
+import ldap,pprint
+from ldap.controls import SimplePagedResultsControl
+
+
+class PagedResultsSearchObject:
+  page_size = 50
+
+  def paged_search_ext_s(self,base,scope,filterstr='(objectClass=*)',attrlist=None,attrsonly=0,serverctrls=None,clientctrls=None,timeout=-1,sizelimit=0):
+    """
+    Behaves exactly like LDAPObject.search_ext_s() but internally uses the
+    simple paged results control to retrieve search results in chunks.
+
+    This is non-sense for really large results sets which you would like
+    to process one-by-one
+    """
+
+    while True: # loop for reconnecting if necessary
+
+      req_ctrl = SimplePagedResultsControl(True,size=self.page_size,cookie='')
+
+      try:
+
+        # Send first search request
+        msgid = self.search_ext(
+          base,
+          scope,
+          filterstr=filterstr,
+          attrlist=attrlist,
+          attrsonly=attrsonly,
+          serverctrls=(serverctrls or [])+[req_ctrl],
+          clientctrls=clientctrls,
+          timeout=timeout,
+          sizelimit=sizelimit
+        )
+
+        result_pages = 0
+        all_results = []
+
+        while True:
+          rtype, rdata, rmsgid, rctrls = self.result3(msgid)
+          all_results.extend(rdata)
+          result_pages += 1
+          # Extract the simple paged results response control
+          pctrls = [
+            c
+            for c in rctrls
+            if c.controlType == SimplePagedResultsControl.controlType
+          ]
+          if pctrls:
+            if pctrls[0].cookie:
+                # Copy cookie from response control to request control
+                req_ctrl.cookie = pctrls[0].cookie
+                msgid = self.search_ext(
+                  base,
+                  scope,
+                  filterstr=filterstr,
+                  attrlist=attrlist,
+                  attrsonly=attrsonly,
+                  serverctrls=(serverctrls or [])+[req_ctrl],
+                  clientctrls=clientctrls,
+                  timeout=timeout,
+                  sizelimit=sizelimit
+                )
+            else:
+              break # no more pages available
+
+      except ldap.SERVER_DOWN,e:
+        try:
+          self.reconnect(self._uri)
+        except AttributeError:
+          raise e
+
+      else:
+        return result_pages,all_results
+
+
+
+
+class MyLDAPObject(ReconnectLDAPObject,PagedResultsSearchObject):
+  pass
+
+
+#ldap.set_option(ldap.OPT_DEBUG_LEVEL,255)
+ldap.set_option(ldap.OPT_REFERRALS, 0)
+l = MyLDAPObject(url,trace_level=2,retry_max=100,retry_delay=2)
+l.protocol_version = 3
+l.simple_bind_s("", "")
+l.page_size=10
+
+# Send search request
+result_pages,all_results = l.paged_search_ext_s(
+  base,
+  ldap.SCOPE_SUBTREE,
+  search_flt,
+  attrlist=searchreq_attrlist,
+  serverctrls=None
+)
+
+l.unbind_s()
+
+print 'Received %d results in %d pages.' % (len(all_results),result_pages)

+ 32 - 0
desktop/core/ext-py/python-ldap-2.4.6/Demo/passwd_ext_op.py

@@ -0,0 +1,32 @@
+"""
+Example showing the use of the password extended operation.
+"""
+
+import sys,ldap,ldapurl,getpass
+
+# Set debugging level
+ldap.set_option(ldap.OPT_DEBUG_LEVEL,255)
+ldapmodule_trace_level = 2
+ldapmodule_trace_file = sys.stderr
+
+lu = ldapurl.LDAPUrl(sys.argv[1])
+
+print 'Old password'
+oldpw = getpass.getpass()
+print 'New password'
+newpw = getpass.getpass()
+
+# Set path name of file containing all CA certificates
+# needed to validate server certificates
+ldap.set_option(ldap.OPT_X_TLS_CACERTFILE,'/etc/httpd/ssl.crt/myCA-cacerts.pem')
+
+# Create LDAPObject instance
+l = ldap.initialize(lu.initializeUrl(),trace_level=ldapmodule_trace_level,trace_file=ldapmodule_trace_file)
+
+l.protocol_version=ldap.VERSION3
+
+l.simple_bind_s(lu.dn,oldpw)
+
+l.passwd(lu.dn,oldpw,newpw)
+
+l.unbind_s()

+ 12 - 0
desktop/core/ext-py/python-ldap-2.4.6/Demo/pickle_ldapobject.py

@@ -0,0 +1,12 @@
+import os,ldap,pickle
+
+temp_file_name = os.path.join(os.environ.get('TMP','/tmp'),'pickle_ldap-%d' % (os.getpid()))
+
+l1 = ldap.ldapobject.ReconnectLDAPObject('ldap://localhost:1390',trace_level=1)
+l1.protocol_version = 3
+l1.search_s('',ldap.SCOPE_BASE,'(objectClass=*)')
+
+pickle.dump(l1,open(temp_file_name,'wb'))
+
+l2 = pickle.load(open(temp_file_name,'rb'))
+l2.search_s('',ldap.SCOPE_BASE,'(objectClass=*)')

+ 6 - 0
desktop/core/ext-py/python-ldap-2.4.6/Demo/pyasn1/README

@@ -0,0 +1,6 @@
+The sample modules/scripts herein require modules pyasn1 and pyasn1-modules.
+
+http://pyasn1.sourceforge.net/
+
+http://pypi.python.org/pypi/pyasn1
+http://pypi.python.org/pypi/pyasn1-modules

+ 55 - 0
desktop/core/ext-py/python-ldap-2.4.6/Demo/pyasn1/dds.py

@@ -0,0 +1,55 @@
+# -*- coding: utf-8 -*-
+"""
+Demo script for Dynamic Entries (see RFC 2589)
+
+This needs the following software:
+Python
+pyasn1
+pyasn1-modules
+python-ldap 2.4+
+"""
+
+from ldap.extop.dds import RefreshRequest,RefreshResponse
+
+import sys,ldap,ldapurl,getpass
+
+try:
+  ldap_url = ldapurl.LDAPUrl(sys.argv[1])
+  request_ttl = int(sys.argv[2])
+except IndexError,ValueError:
+  print 'Usage: dds.py <LDAP URL> <TTL>'
+  sys.exit(1)
+
+# Set debugging level
+#ldap.set_option(ldap.OPT_DEBUG_LEVEL,255)
+ldapmodule_trace_level = 2
+ldapmodule_trace_file = sys.stderr
+
+ldap_conn = ldap.ldapobject.LDAPObject(
+  ldap_url.initializeUrl(),
+  trace_level=ldapmodule_trace_level,
+  trace_file=ldapmodule_trace_file
+)
+
+if ldap_url.cred is None:
+  print 'Password for %s:' % (repr(ldap_url.who))
+  ldap_url.cred = getpass.getpass()
+
+try:
+  ldap_conn.simple_bind_s(ldap_url.who or '',ldap_url.cred or '')
+
+except ldap.INVALID_CREDENTIALS,e:
+  print 'Simple bind failed:',str(e)
+  sys.exit(1)
+
+else:
+  extreq = RefreshRequest(entryName=ldap_url.dn,requestTtl=request_ttl)
+  try:
+    extop_resp_obj = ldap_conn.extop_s(extreq,extop_resp_class=RefreshResponse)
+  except ldap.LDAPError,e:
+    print str(e)
+  else:
+    if extop_resp_obj.responseTtl!=request_ttl:
+      print 'Different response TTL:',extop_resp_obj.responseTtl
+    else:
+      print 'Response TTL:',extop_resp_obj.responseTtl

+ 50 - 0
desktop/core/ext-py/python-ldap-2.4.6/Demo/pyasn1/ppolicy.py

@@ -0,0 +1,50 @@
+# -*- coding: utf-8 -*-
+"""
+Demo script for Password Policy Controls
+(see http://tools.ietf.org/html/draft-behera-ldap-password-policy)
+
+This needs the following software:
+Python
+pyasn1
+pyasn1-modules
+python-ldap 2.4+
+"""
+
+import sys,ldap,ldapurl,getpass
+
+from ldap.controls.ppolicy import PasswordPolicyError,PasswordPolicyControl
+
+try:
+  ldap_url = ldapurl.LDAPUrl(sys.argv[1])
+except IndexError,ValueError:
+  print 'Usage: ppolicy.py <LDAP URL>'
+  sys.exit(1)
+
+# Set debugging level
+#ldap.set_option(ldap.OPT_DEBUG_LEVEL,255)
+ldapmodule_trace_level = 2
+ldapmodule_trace_file = sys.stderr
+
+ldap_conn = ldap.ldapobject.LDAPObject(
+  ldap_url.initializeUrl(),
+  trace_level=ldapmodule_trace_level,
+  trace_file=ldapmodule_trace_file
+)
+
+if ldap_url.cred is None:
+  print 'Password for %s:' % (repr(ldap_url.who))
+  ldap_url.cred = getpass.getpass()
+
+try:
+  msgid = ldap_conn.simple_bind(ldap_url.who,ldap_url.cred,serverctrls=[PasswordPolicyControl()])
+  res_type,res_data,res_msgid,res_ctrls = ldap_conn.result3(msgid)
+except ldap.INVALID_CREDENTIALS,e:
+  print 'Simple bind failed:',str(e)
+  sys.exit(1)
+else:
+  if res_ctrls[0].controlType==PasswordPolicyControl.controlType:
+    ppolicy_ctrl = res_ctrls[0]
+    print 'PasswordPolicyControl'
+    print 'error',repr(ppolicy_ctrl.error),(ppolicy_ctrl.error!=None)*repr(PasswordPolicyError(ppolicy_ctrl.error))
+    print 'timeBeforeExpiration',repr(ppolicy_ctrl.timeBeforeExpiration)
+    print 'graceAuthNsRemaining',repr(ppolicy_ctrl.graceAuthNsRemaining)

+ 79 - 0
desktop/core/ext-py/python-ldap-2.4.6/Demo/pyasn1/psearch.py

@@ -0,0 +1,79 @@
+# -*- coding: utf-8 -*-
+"""
+Demo script for Persistent Search Control
+(see http://tools.ietf.org/html/draft-ietf-ldapext-psearch)
+
+See http://www.python-ldap.org/ for project details.
+This needs the following software:
+Python
+pyasn1
+pyasn1-modules
+python-ldap 2.4+
+"""
+
+import sys,ldap,ldapurl,getpass
+
+from ldap.controls.psearch import PersistentSearchControl,EntryChangeNotificationControl,CHANGE_TYPES_STR
+
+try:
+  ldap_url = ldapurl.LDAPUrl(sys.argv[1])
+except IndexError:
+  print 'Usage: psearch.py <LDAP URL>'
+  sys.exit(1)
+
+# Set debugging level
+#ldap.set_option(ldap.OPT_DEBUG_LEVEL,255)
+ldapmodule_trace_level = 2
+ldapmodule_trace_file = sys.stderr
+
+ldap_conn = ldap.ldapobject.LDAPObject(
+  ldap_url.initializeUrl(),
+  trace_level=ldapmodule_trace_level,
+  trace_file=ldapmodule_trace_file
+)
+
+if ldap_url.cred is None:
+  print 'Password for %s:' % (repr(ldap_url.who))
+  ldap_url.cred = getpass.getpass()
+
+try:
+  ldap_conn.simple_bind_s(ldap_url.who,ldap_url.cred)
+
+except ldap.INVALID_CREDENTIALS,e:
+  print 'Simple bind failed:',str(e)
+  sys.exit(1)
+
+psc = PersistentSearchControl()
+
+msg_id = ldap_conn.search_ext(
+  ldap_url.dn,
+  ldap_url.scope,
+  ldap_url.filterstr,
+  attrlist = ldap_url.attrs or ['*','+'],
+  serverctrls=[psc],
+)
+
+while True:
+  try:
+    res_type,res_data,res_msgid,_,_,_ = ldap_conn.result4(
+      msg_id,
+      all=0,
+      timeout=10.0,
+      add_ctrls=1,
+      add_intermediates=1,
+      resp_ctrl_classes={EntryChangeNotificationControl.controlType:EntryChangeNotificationControl},
+    )
+  except ldap.TIMEOUT:
+    print 'Timeout waiting for results...'
+  else:
+    for dn,entry,srv_ctrls in res_data:
+      ecn_ctrls = [
+        c
+        for c in srv_ctrls
+        if c.controlType == EntryChangeNotificationControl.controlType
+      ]
+
+      if ecn_ctrls:
+        changeType,previousDN,changeNumber = ecn_ctrls[0].changeType,ecn_ctrls[0].previousDN,ecn_ctrls[0].changeNumber
+        change_type_desc = CHANGE_TYPES_STR[changeType]
+        print 'changeType: %s (%d), changeNumber: %s, previousDN: %s' % (change_type_desc,changeType,changeNumber,repr(previousDN))

+ 112 - 0
desktop/core/ext-py/python-ldap-2.4.6/Demo/pyasn1/readentrycontrol.py

@@ -0,0 +1,112 @@
+#!/usr/bin/env python
+"""
+This sample script demonstrates the use of the pre-read control (see RFC 4527).
+
+Originally contributed by Andreas Hasenack <ahasenack@terra.com.br>
+
+Requires module pyasn1 (see http://pyasn1.sourceforge.net/)
+"""
+
+import pprint,ldap,ldap.modlist
+
+from ldap.controls.readentry import PreReadControl,PostReadControl
+
+uri = "ldap://localhost:2071/"
+
+l = ldap.initialize(uri,trace_level=2)
+l.simple_bind_s('uid=diradm,ou=schulung,dc=stroeder,dc=local','testsecret')
+
+print """#---------------------------------------------------------------------------
+# Add new entry
+#---------------------------------------------------------------------------
+"""
+
+new_test_dn = "uid=ablume,ou=Users,ou=schulung,dc=stroeder,dc=local"
+new_test_dn2 = "uid=ablume2,ou=Users,ou=schulung,dc=stroeder,dc=local"
+new_test_entry = {
+  'objectClass':['account','posixAccount'],
+  'uid':['ablume'],
+  'cn':['Anna Blume'],
+  'uidNumber':['10000'],
+  'gidNumber':['10000'],
+  'homeDirectory':['/home/ablume'],
+}
+
+pr = PostReadControl(criticality=True,attrList=['entryUUID','entryCSN'])
+
+msg_id = l.add_ext(
+  new_test_dn,
+  ldap.modlist.addModlist(new_test_entry),
+  serverctrls = [pr]
+)
+_,_,_,resp_ctrls = l.result3(msg_id)
+print "resp_ctrls[0].dn:",resp_ctrls[0].dn
+print "resp_ctrls[0].entry:";pprint.pprint(resp_ctrls[0].entry)
+
+print """#---------------------------------------------------------------------------
+# Modify entry
+#---------------------------------------------------------------------------
+"""
+
+pr = PreReadControl(criticality=True,attrList=['uidNumber','gidNumber','entryCSN'])
+
+msg_id = l.modify_ext(
+  new_test_dn,
+  [(ldap.MOD_INCREMENT, "uidNumber", "1"),(ldap.MOD_INCREMENT, "gidNumber", "1")],
+  serverctrls = [pr]
+)
+_,_,_,resp_ctrls = l.result3(msg_id)
+print "resp_ctrls[0].dn:",resp_ctrls[0].dn
+print "resp_ctrls[0].entry:";pprint.pprint(resp_ctrls[0].entry)
+
+pr = PostReadControl(criticality=True,attrList=['uidNumber','gidNumber','entryCSN'])
+
+msg_id = l.modify_ext(
+  new_test_dn,
+  [(ldap.MOD_INCREMENT, "uidNumber", "1"),(ldap.MOD_INCREMENT, "gidNumber", "1")],
+  serverctrls = [pr]
+)
+_,_,_,resp_ctrls = l.result3(msg_id)
+print "resp_ctrls[0].dn:",resp_ctrls[0].dn
+print "resp_ctrls[0].entry:";pprint.pprint(resp_ctrls[0].entry)
+
+print """#---------------------------------------------------------------------------
+# Rename entry
+#---------------------------------------------------------------------------
+"""
+
+pr = PostReadControl(criticality=True,attrList=['uid'])
+msg_id = l.rename(
+  new_test_dn,
+  "uid=ablume2",
+  delold=1,
+  serverctrls = [pr]
+)
+_,_,_,resp_ctrls = l.result3(msg_id)
+print "resp_ctrls[0].dn:",resp_ctrls[0].dn
+print "resp_ctrls[0].entry:";pprint.pprint(resp_ctrls[0].entry)
+
+pr = PreReadControl(criticality=True,attrList=['uid'])
+msg_id = l.rename(
+  new_test_dn2,
+  "uid=ablume",
+  delold=1,
+  serverctrls = [pr]
+)
+_,_,_,resp_ctrls = l.result3(msg_id)
+print "resp_ctrls[0].dn:",resp_ctrls[0].dn
+print "resp_ctrls[0].entry:";pprint.pprint(resp_ctrls[0].entry)
+
+print """#---------------------------------------------------------------------------
+# Delete entry
+#---------------------------------------------------------------------------
+"""
+
+pr = PreReadControl(criticality=True,attrList=['*','+'])
+msg_id = l.delete_ext(
+  new_test_dn,
+  serverctrls = [pr]
+)
+_,_,_,resp_ctrls = l.result3(msg_id)
+print "resp_ctrls[0].dn:",resp_ctrls[0].dn
+print "resp_ctrls[0].entry:";pprint.pprint(resp_ctrls[0].entry)

+ 59 - 0
desktop/core/ext-py/python-ldap-2.4.6/Demo/pyasn1/sessiontrack.py

@@ -0,0 +1,59 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+"""
+demo_track_ldap_session.py
+
+Client-seitige Demo-Implementierung von Session Tracking Control
+
+http://tools.ietf.org/html/draft-wahl-ldap-session-03
+"""
+
+__version__ = '0.1'
+
+import sys,getpass,ldap,ldapurl
+
+from ldap.controls.sessiontrack import SessionTrackingControl,SESSION_TRACKING_FORMAT_OID_USERNAME
+
+try:
+  ldap_url = ldapurl.LDAPUrl(sys.argv[1])
+except IndexError,ValueError:
+  print 'Usage: %s <LDAP URL>' % (sys.argv[0])
+  sys.exit(1)
+
+# Set debugging level
+#ldap.set_option(ldap.OPT_DEBUG_LEVEL,255)
+ldapmodule_trace_level = 2
+ldapmodule_trace_file = sys.stderr
+
+ldap_conn = ldap.ldapobject.LDAPObject(
+  ldap_url.initializeUrl(),
+  trace_level=ldapmodule_trace_level,
+  trace_file=ldapmodule_trace_file
+)
+
+if ldap_url.who and ldap_url.cred is None:
+  print 'Password for %s:' % (repr(ldap_url.who))
+  ldap_url.cred = getpass.getpass()
+
+try:
+  ldap_conn.simple_bind_s(ldap_url.who or '',ldap_url.cred or '')
+
+except ldap.INVALID_CREDENTIALS,e:
+  print 'Simple bind failed:',str(e)
+  sys.exit(1)
+
+st_ctrl = SessionTrackingControl(
+  '192.0.2.1',
+  'app.example.com',
+  SESSION_TRACKING_FORMAT_OID_USERNAME,
+  'bloggs'
+)
+
+ldap_conn.search_ext_s(
+  ldap_url.dn or '',
+  ldap_url.scope or ldap.SCOPE_SUBTREE,
+  ldap_url.filterstr or '(objectClass=*)',
+  ldap_url.attrs or ['*'],
+  serverctrls=[st_ctrl]
+)
+

+ 93 - 0
desktop/core/ext-py/python-ldap-2.4.6/Demo/pyasn1/syncrepl.py

@@ -0,0 +1,93 @@
+#!/usr/bin/python
+
+import sys,anydbm,getpass
+import ldap,ldapurl,ldap.syncrepl
+
+
+class DNSync(ldap.ldapobject.LDAPObject,ldap.syncrepl.SyncreplConsumer):
+
+    def __init__(self, filename, *args, **kwargs):
+        ldap.ldapobject.LDAPObject.__init__(self, *args, **kwargs)
+        self.__db = anydbm.open(filename, 'c', 0640)
+        self.__presentUUIDs = {}
+
+    def syncrepl_set_cookie(self,cookie):
+        self.__db['cookie'] = cookie
+
+    def syncrepl_get_cookie(self):
+        if 'cookie' in self.__db:
+            return self.__db['cookie']
+
+    def syncrepl_delete(self, uuids):
+        for uuid in uuids:
+            dn = self.__db[uuid]
+            print "delete %s" % dn
+            del self.__db[uuid]
+
+    def syncrepl_present(self, uuids, refreshDeletes=False):
+        if uuids is None:
+            if refreshDeletes is False:
+                nonpresent = []
+                for uuid in self.__db.keys():
+                    if uuid == 'cookie': continue
+                    if uuid in self.__presentUUIDs: continue
+                    nonpresent.append(uuid)
+                self.syncrepl_delete(nonpresent)
+            self.__presentUUIDs = {}
+        else:
+            for uuid in uuids:
+                self.__presentUUIDs[uuid] = True
+
+    def syncrepl_entry(self, dn, attrs, uuid):
+        if uuid in self.__db:
+            odn = self.__db[uuid]
+            if odn != dn:
+                print "moddn %s -> %s" % ( odn, dn )
+            else:
+                print "modify %s" % self.__db[uuid]
+        else:
+            print "add %s" % dn
+        self.__db[uuid] = dn
+
+
+try:
+  ldap_url = ldapurl.LDAPUrl(sys.argv[1])
+  db_filename = sys.argv[2]
+except IndexError,ValueError:
+  print 'Usage: syncrepl.py <LDAP URL> <DB filename>'
+  sys.exit(1)
+
+# Set debugging level
+#ldap.set_option(ldap.OPT_DEBUG_LEVEL,255)
+ldapmodule_trace_level = 2
+ldapmodule_trace_file = sys.stderr
+
+ldap_sync_conn = DNSync(
+  db_filename,
+  ldap_url.initializeUrl(),
+  trace_level=ldapmodule_trace_level,
+  trace_file=ldapmodule_trace_file
+)
+
+if ldap_url.who and ldap_url.cred is None:
+  print 'Password for %s:' % (repr(ldap_url.who))
+  ldap_url.cred = getpass.getpass()
+
+try:
+  ldap_sync_conn.simple_bind_s(ldap_url.who or '',ldap_url.cred or '')
+
+except ldap.INVALID_CREDENTIALS,e:
+  print 'Simple bind failed:',str(e)
+  sys.exit(1)
+
+msgid = ldap_sync_conn.syncrepl_search(
+  ldap_url.dn,
+  ldap_url.scope,
+  mode='refreshAndPersist',
+  filterstr=ldap_url.filterstr
+)
+try:
+    while ldap_sync_conn.syncrepl_poll(all=1, msgid=msgid):
+        pass
+except KeyboardInterrupt:
+    pass

+ 24 - 0
desktop/core/ext-py/python-ldap-2.4.6/Demo/reconnect.py

@@ -0,0 +1,24 @@
+import sys,time,ldap,ldap.ldapobject,ldapurl
+
+from ldap.ldapobject import *
+
+ldap_url = ldapurl.LDAPUrl(sys.argv[1])
+ldap_url.applyDefaults({
+  'who':'',
+  'cred':'',
+  'filterstr':'(objectClass=*)',
+  'scope':ldap.SCOPE_BASE
+})
+
+ldap.trace_level=1
+
+l = ldap.ldapobject.ReconnectLDAPObject(
+  ldap_url.initializeUrl(),trace_level=ldap.trace_level
+)
+l.protocol_version = ldap.VERSION3
+
+l.simple_bind_s(ldap_url.who,ldap_url.cred)
+
+while 1:
+  l.search_s(ldap_url.dn,ldap_url.scope,ldap_url.filterstr,ldap_url.attrs)
+  sys.stdin.readline()

+ 52 - 0
desktop/core/ext-py/python-ldap-2.4.6/Demo/rename.py

@@ -0,0 +1,52 @@
+import ldap
+from getpass import getpass
+
+# Create LDAPObject instance
+l = ldap.initialize('ldap://localhost:1389',trace_level=1)
+
+print 'Password:'
+cred = getpass()
+
+try:
+
+  # Set LDAP protocol version used
+  l.set_option(ldap.OPT_PROTOCOL_VERSION,3)
+
+  # Try a bind to provoke failure if protocol version is not supported
+  l.bind_s('cn=root,dc=stroeder,dc=com',cred,ldap.AUTH_SIMPLE)
+
+  print 'Using rename_s():'
+
+  l.rename_s(
+    'uid=fred,ou=Unstructured testing tree,dc=stroeder,dc=com',
+    'cn=Fred Feuerstein',
+    'dc=stroeder,dc=com',
+    0
+  )
+
+  l.rename_s(
+    'cn=Fred Feuerstein,dc=stroeder,dc=com',
+    'uid=fred',
+    'ou=Unstructured testing tree,dc=stroeder,dc=com',
+    0
+  )
+
+  m = l.rename(
+    'uid=fred,ou=Unstructured testing tree,dc=stroeder,dc=com',
+    'cn=Fred Feuerstein',
+    'dc=stroeder,dc=com',
+    0
+  )
+  r = l.result(m,1)
+
+  m = l.rename(
+    'cn=Fred Feuerstein,dc=stroeder,dc=com',
+    'uid=fred',
+    'ou=Unstructured testing tree,dc=stroeder,dc=com',
+    0
+  )
+  r = l.result(m,1)
+
+finally:
+
+  l.unbind_s()

+ 26 - 0
desktop/core/ext-py/python-ldap-2.4.6/Demo/resiter.py

@@ -0,0 +1,26 @@
+"""
+Demo for using ldap.resiter.ResultProcessor
+written by Michael Stroeder <michael@stroeder.com>
+
+See http://python-ldap.sourceforge.net for details.
+
+\$Id: resiter.py,v 1.1 2005/11/07 11:24:25 stroeder Exp $
+
+Python compability note:
+Requires Python 2.3+
+"""
+
+import ldap,ldap.resiter
+
+class LDAPObject(ldap.ldapobject.LDAPObject,ldap.resiter.ResultProcessor):
+  pass
+
+l = LDAPObject('ldap://localhost:1390',trace_level=1)
+l.protocol_version = 3
+msgid = l.search('dc=stroeder,dc=de',ldap.SCOPE_SUBTREE,'(cn=m*)')
+
+result_iter = l.allresults(msgid)
+for result_type,result_list,result_msgid,result_serverctrls in result_iter:
+  print result_type,result_list,result_msgid,result_serverctrls
+
+l.unbind_s()

+ 84 - 0
desktop/core/ext-py/python-ldap-2.4.6/Demo/sasl_bind.py

@@ -0,0 +1,84 @@
+# For documentation, see comments in Module/LDAPObject.c and the
+# ldap.sasl module documentation.
+
+import ldap,ldap.sasl
+
+ldap.sasl._trace_level=0
+
+ldap.set_option(ldap.OPT_DEBUG_LEVEL,0)
+
+for ldap_uri,sasl_mech,sasl_cb_value_dict in [
+  (
+    "ldap://nb2.stroeder.local:1390/",
+    'CRAM-MD5',
+    {
+      ldap.sasl.CB_AUTHNAME    :'fred',
+      ldap.sasl.CB_PASS        :'secret',
+    }
+  ),
+  (
+    "ldap://nb2.stroeder.local:1390/",
+    'PLAIN',
+    {
+      ldap.sasl.CB_AUTHNAME    :'fred',
+      ldap.sasl.CB_PASS        :'secret',
+    }
+  ),
+  (
+    "ldap://nb2.stroeder.local:1390/",
+    'LOGIN',
+    {
+      ldap.sasl.CB_AUTHNAME    :'fred',
+      ldap.sasl.CB_PASS        :'secret',
+    }
+  ),
+  (
+    "ldapi://%2Ftmp%2Fopenldap-socket/",
+    'EXTERNAL',
+    { }
+  ),
+  (
+    "ldap://nb2.stroeder.local:1390/",
+    'GSSAPI',
+    { }
+  ),
+  (
+    "ldap://nb2.stroeder.local:1390/",
+    'NTLM',
+    {
+      ldap.sasl.CB_AUTHNAME    :'fred',
+      ldap.sasl.CB_PASS        :'secret',
+    }
+  ),
+  (
+    "ldap://nb2.stroeder.local:1390/",
+    'DIGEST-MD5',
+    {
+      ldap.sasl.CB_AUTHNAME    :'fred',
+      ldap.sasl.CB_PASS        :'secret',
+    }
+  ),
+]:
+  sasl_auth = ldap.sasl.sasl(sasl_cb_value_dict,sasl_mech)
+  print 20*'*',sasl_auth.mech,20*'*'
+  # Open the LDAP connection
+  l = ldap.initialize(ldap_uri,trace_level=0)
+  # Set protocol version to LDAPv3 to enable SASL bind!
+  l.protocol_version = 3
+  try:
+    l.sasl_interactive_bind_s("", sasl_auth)
+  except ldap.LDAPError,e:
+    print 'Error using SASL mechanism',sasl_auth.mech,str(e)
+  else:
+    print 'Sucessfully bound using SASL mechanism:',sasl_auth.mech
+    try:
+      print 'Result of Who Am I? ext. op:',repr(l.whoami_s())
+    except ldap.LDAPError,e:
+      print 'Error using SASL mechanism',sasl_auth.mech,str(e)
+    try:
+      print 'OPT_X_SASL_USERNAME',repr(l.get_option(ldap.OPT_X_SASL_USERNAME))
+    except AttributeError:
+      pass
+
+  l.unbind()
+  del l

+ 63 - 0
desktop/core/ext-py/python-ldap-2.4.6/Demo/schema.py

@@ -0,0 +1,63 @@
+import sys,ldap,ldap.schema
+
+schema_attrs = ldap.schema.SCHEMA_ATTRS
+
+ldap.set_option(ldap.OPT_DEBUG_LEVEL,0)
+
+ldap._trace_level = 0
+
+subschemasubentry_dn,schema = ldap.schema.urlfetch(sys.argv[-1])
+
+if subschemasubentry_dn is None:
+  print 'No sub schema sub entry found!'
+  sys.exit(1)
+
+if schema.non_unique_oids:
+  print '*** Schema errors ***'
+  print 'non-unique OIDs:\n','\r\n'.join(schema.non_unique_oids)
+
+print '*** Schema from',repr(subschemasubentry_dn)
+
+# Display schema
+for attr_type,schema_class in ldap.schema.SCHEMA_CLASS_MAPPING.items():
+  print '*'*20,attr_type,'*'*20
+  for element_id in schema.listall(schema_class):
+    se_orig = schema.get_obj(schema_class,element_id)
+    print attr_type,str(se_orig)
+print '*** Testing object class inetOrgPerson ***'
+
+drink = schema.get_obj(ldap.schema.AttributeType,'favouriteDrink')
+if not drink is None:
+  print '*** drink ***'
+  print 'drink.names',repr(drink.names)
+  print 'drink.collective',repr(drink.collective)
+
+inetOrgPerson = schema.get_obj(ldap.schema.ObjectClass,'inetOrgPerson')
+if not inetOrgPerson is None:
+  print inetOrgPerson.must,inetOrgPerson.may
+
+print '*** person,organizationalPerson,inetOrgPerson ***'
+try:
+  print schema.attribute_types(
+    ['person','organizationalPerson','inetOrgPerson']
+  )
+  print schema.attribute_types(
+    ['person','organizationalPerson','inetOrgPerson'],
+    attr_type_filter = [
+      ('no_user_mod',[0]),
+      ('usage',range(2)),
+    ]  
+  )
+except KeyError,e:
+  print '***KeyError',str(e)
+
+
+schema.ldap_entry()
+
+print str(schema.get_obj(ldap.schema.MatchingRule,'2.5.13.0'))
+print str(schema.get_obj(ldap.schema.MatchingRuleUse,'2.5.13.0'))
+
+print str(schema.get_obj(ldap.schema.AttributeType,'name'))
+print str(schema.get_inheritedobj(ldap.schema.AttributeType,'cn',['syntax','equality','substr','ordering']))
+
+must_attr,may_attr = schema.attribute_types(['person','organizationalPerson','inetOrgPerson'],raise_keyerror=0)

+ 100 - 0
desktop/core/ext-py/python-ldap-2.4.6/Demo/schema_tree.py

@@ -0,0 +1,100 @@
+"""
+Outputs the object class tree read from LDAPv3 schema
+of a given server
+
+Usage: schema_oc_tree.py [--html] [LDAP URL]
+"""
+
+import sys,getopt,ldap,ldap.schema
+
+
+ldap.trace_level = 1
+
+def PrintSchemaTree(schema,se_class,se_tree,se_oid,level):
+  """ASCII text output for console"""
+  se_obj = schema.get_obj(se_class,se_oid)
+  if se_obj!=None:
+    print '|    '*(level-1)+'+---'*(level>0), \
+          ', '.join(se_obj.names), \
+          '(%s)' % se_obj.oid
+  for sub_se_oid in se_tree[se_oid]:
+    print '|    '*(level+1)
+    PrintSchemaTree(schema,se_class,se_tree,sub_se_oid,level+1)
+
+
+def HTMLSchemaTree(schema,se_class,se_tree,se_oid,level):
+  """HTML output for browser"""
+  se_obj = schema.get_obj(se_class,se_oid)
+  if se_obj!=None:
+    print """
+    <dt><strong>%s (%s)</strong></dt>
+    <dd>
+      %s
+    """ % (', '.join(se_obj.names),se_obj.oid,se_obj.desc)
+  if se_tree[se_oid]:
+    print '<dl>'
+    for sub_se_oid in se_tree[se_oid]:
+      HTMLSchemaTree(schema,se_class,se_tree,sub_se_oid,level+1)
+    print '</dl>'
+  print '</dd>'
+
+
+ldap.set_option(ldap.OPT_DEBUG_LEVEL,0)
+
+ldap._trace_level = 0
+
+subschemasubentry_dn,schema = ldap.schema.urlfetch(sys.argv[-1],ldap.trace_level)
+
+if subschemasubentry_dn is None:
+  print 'No sub schema sub entry found!'
+  sys.exit(1)
+
+try:
+  options,args=getopt.getopt(sys.argv[1:],'',['html'])
+except getopt.error,e:
+  print 'Error: %s\nUsage: schema_oc_tree.py [--html] [LDAP URL]'
+
+html_output = options and options[0][0]=='--html'
+
+oc_tree = schema.tree(ldap.schema.ObjectClass)
+at_tree = schema.tree(ldap.schema.AttributeType)
+
+#for k,v in oc_tree.items():
+#  print k,'->',v
+#for k,v in at_tree.items():
+#  print k,'->',v
+
+if html_output:
+
+  print """<html>
+<head>
+  <title>Object class tree</title>
+</head>
+<body bgcolor="#ffffff">
+<h1>Object class tree</h1>
+<dl>
+"""
+  HTMLSchemaTree(schema,ldap.schema.ObjectClass,oc_tree,'2.5.6.0',0)
+  print """</dl>
+<h1>Attribute type tree</h1>
+<dl>
+"""
+  for a in schema.listall(ldap.schema.AttributeType):
+    if at_tree[a]:
+      HTMLSchemaTree(schema,ldap.schema.AttributeType,at_tree,a,0)
+      print
+
+  print """</dl>
+</body>
+</html>
+"""
+
+else:
+
+  print '*** Object class tree ***\n'
+  print
+  PrintSchemaTree(schema,ldap.schema.ObjectClass,oc_tree,'2.5.6.0',0)
+
+  print '\n*** Attribute types tree ***\n'
+  PrintSchemaTree(schema,ldap.schema.AttributeType,at_tree,'_',0)
+

+ 106 - 0
desktop/core/ext-py/python-ldap-2.4.6/Demo/simple.py

@@ -0,0 +1,106 @@
+import sys,getpass
+import ldap
+
+#l = ldap.open("localhost", 31001)
+l = ldap.open("marta.it.uq.edu.au")
+
+login_dn = "cn=root,ou=CSEE,o=UQ,c=AU"
+login_pw = getpass.getpass("Password for %s: " % login_dn)
+l.simple_bind_s(login_dn, login_pw)
+
+#
+# create a new sub organisation
+#
+
+try:
+    dn = "ou=CSEE,o=UQ,c=AU"
+    print "Adding", repr(dn)
+    l.add_s(dn,
+	 [
+	    ("objectclass",["organizationalUnit"]),
+	    ("ou", ["CSEE"]),
+	    ("description", [
+		    "Department of Computer Science and Electrical Engineering"]),
+	 ]
+       )
+
+except _ldap.LDAPError:
+    pass
+
+#
+# create an entry for me
+#
+
+dn = "cn=David Leonard,ou=CSEE,o=UQ,c=AU"
+print "Updating", repr(dn)
+
+try:
+	l.delete_s(dn)
+except:
+	pass
+
+l.add_s(dn,
+     [
+	("objectclass",			["organizationalPerson"]),
+	("sn",				["Leonard"]),
+	("cn",				["David Leonard"]),
+	("description",			["Ph.D. student"]),
+	("display-name",		["David Leonard"]),
+	#("commonname",			["David Leonard"]),
+	("mail",			["david.leonard@csee.uq.edu.au"]),
+	("othermailbox",		["d@openbsd.org"]),
+	("givenname",			["David"]),
+	("surname",			["Leonard"]),
+	("seeAlso",			["http://www.csee.uq.edu.au/~leonard/"]),
+	("url",				["http://www.csee.uq.edu.au/~leonard/"]),
+	#("homephone",			[]),
+	#("fax",			[]),
+	#("otherfacsimiletelephonenumber",[]),
+	#("officefax",			[]),
+	#("mobile",			[]),
+	#("otherpager",			[]),
+	#("officepager",		[]),
+	#("pager",			[]),
+	("info",			["info"]),
+	("title",			["Mr"]),
+	#("telephonenumber",		[]),
+	("l",				["Brisbane"]),
+	("st",				["Queensland"]),
+	("c",				["AU"]),
+	("co",				["co"]),
+	("o",				["UQ"]),
+	("ou",				["CSEE"]),
+	#("homepostaladdress",		[]),
+	#("postaladdress",		[]),
+	#("streetaddress",		[]),
+	#("street",			[]),
+	("department",			["CSEE"]),
+	("comment",			["comment"]),
+	#("postalcode",			[]),
+	("physicaldeliveryofficename",  ["Bldg 78, UQ, St Lucia"]),
+	("preferredDeliveryMethod",	["email"]),
+	("initials",			["DRL"]),
+	("conferenceinformation",	["MS-conferenceinformation"]),
+	#("usercertificate",		[]),
+	("labeleduri",			["labeleduri"]),
+	("manager",			["cn=Jaga Indulska"]),
+	("reports",			["reports"]),
+	("jpegPhoto",			[open("/www/leonard/leonard.jpg","r").read()]),
+	("uid",				["leonard"]),
+	("userPassword",		[""])
+
+    ])
+
+#
+# search beneath the CSEE/UQ/AU tree
+#
+
+res = l.search_s(
+	"ou=CSEE, o=UQ, c=AU", 
+	_ldap.SCOPE_SUBTREE, 
+	"objectclass=*",
+      )
+print res
+
+l.unbind()
+

+ 128 - 0
desktop/core/ext-py/python-ldap-2.4.6/Demo/simplebrowse.py

@@ -0,0 +1,128 @@
+#! python
+
+#
+# simple LDAP server browsing example
+#
+
+import ldap
+import string
+from traceback import print_exc
+
+url = "ldap://ldap.openldap.org/"
+dn = "dc=openldap,dc=org"
+
+print "Connecting to", url
+
+l = ldap.initialize(url)
+l.bind_s("", "", ldap.AUTH_SIMPLE);
+
+lastdn = dn
+dnlist = None
+
+while 1:
+
+    #-- read a command
+    try:
+        cmd = raw_input(dn + "> ")
+    except EOFError:
+	print
+	break
+
+    try:
+
+	if cmd == "?":
+		print  "cd <dn>	- change DN to <dn>"
+		print  "cd <n>	- change DN to number <n> of last 'ls'"
+		print  "cd -	- change to previous DN"
+		print  "cd ..	- change to one-level higher DN"
+		print  "cd 	- change to root DN"
+		print  "ls	- list children of crrent DN"
+		print  ".	- show attributes of current DN"
+		print  "/<expr>	- list descendents matching filter <expr>"
+		print  "?	- show this help"
+
+	elif cmd == "ls":
+		print "Children of", `dn`, ":"
+		dnlist = []
+		#
+		# List the children at one level down from the current dn
+		# We use the filter 'objectclass=*' to match everything.
+		# We're not interested in attributes at this stage, so
+		# we specify [] as the list of attribute names to retreive.
+		#
+		for name,attrs in l.search_s(dn, ldap.SCOPE_ONELEVEL, 
+		    "objectclass=*", []):
+			#-- shorten resulting dns for output brevity
+			if name.startswith(dn+", "):
+				shortname = "+ "+name[len(dn)+2:]
+			elif name.endswith(", "+dn):
+				shortname = name[:-len(dn)-2]+" +"
+			else:
+				shortname = name
+			print " %3d. %s" % (len(dnlist), shortname)
+			dnlist.append(name)
+
+	elif cmd == "cd":
+		dn = ""
+		dnlist = None
+
+	elif cmd.startswith("cd "):
+		arg = cmd[3:]
+		if arg == '-':
+			lastdn,dn = dn,lastdn
+		elif arg == '..':
+			dn = string.join(ldap.explode_dn(dn)[1:], ",")
+			dn = string.strip(dn)
+                else:
+		        try:
+			        i = int(arg)
+		        except:
+			        godn = arg
+                        else:
+			        if dnlist is None:
+				        print "do an ls first"
+                                else:
+			                godn = dnlist[i]
+		                lastdn = dn
+		                dn = godn
+
+	elif cmd == ".":
+		#
+		# Retrieve all the attributes for the current dn.
+		# We construct a search using SCOPE_BASE (ie just the
+		# given DN) and again filter with "objectclass=*".
+		# No attributes are listed, so the default is for
+		# the client to receive all attributes on the DN.
+		#
+		print "Attributes of", `dn`, ":"
+		for name,attrs in l.search_s(dn, ldap.SCOPE_BASE,
+		    "objectclass=*"):
+			print "  %-24s" % name
+			for k,vals in attrs.items():
+			    for v in vals:
+				if len(v) > 200: 
+					v = `v[:200]` + \
+						("... (%d bytes)" % len(v))
+				else:
+					v = `v`
+				print "      %-12s: %s" % (k, v)
+
+	elif cmd.startswith("/"):
+		#
+		# Search descendent objects to match a given filter.
+		# We use SCOPE_SUBTREE to indicate descendents, and
+		# again specify an empty attribute list to indicate
+		# that we're not interested in them.
+		#
+		expr = cmd[1:]
+		print "Descendents matching filter", `expr`, ":"
+		for name,attrs in l.search_s(dn, ldap.SCOPE_SUBTREE,
+		    expr, []):
+			print "  %24s", name
+
+	else:
+		print "unknown command - try '?' for help"
+
+    except:
+	print_exc()
+

+ 4 - 0
desktop/core/ext-py/python-ldap-2.4.6/Doc/.cvsignore

@@ -0,0 +1,4 @@
+python-ldap.pdf
+python-ldap.ps
+*.bck
+*~

+ 68 - 0
desktop/core/ext-py/python-ldap-2.4.6/Doc/Makefile

@@ -0,0 +1,68 @@
+# Makefile for Sphinx documentation
+#
+
+# You can set these variables from the command line.
+SPHINXOPTS    =
+SPHINXBUILD   = sphinx-build
+PAPER         =
+
+# Internal variables.
+PAPEROPT_a4     = -D latex_paper_size=a4
+PAPEROPT_letter = -D latex_paper_size=letter
+ALLSPHINXOPTS   = -d .build/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
+
+.PHONY: help clean html web htmlhelp latex changes linkcheck
+
+help:
+	@echo "Please use \`make <target>' where <target> is one of"
+	@echo "  html      to make standalone HTML files"
+	@echo "  web       to make files usable by Sphinx.web"
+	@echo "  htmlhelp  to make HTML files and a HTML help project"
+	@echo "  latex     to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
+	@echo "  changes   to make an overview over all changed/added/deprecated items"
+	@echo "  linkcheck to check all external links for integrity"
+
+clean:
+	-rm -rf .build/*
+
+html:
+	mkdir -p .build/html .build/doctrees
+	$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) .build/html
+	@echo
+	@echo "Build finished. The HTML pages are in .build/html."
+
+web:
+	mkdir -p .build/web .build/doctrees
+	$(SPHINXBUILD) -b web $(ALLSPHINXOPTS) .build/web
+	@echo
+	@echo "Build finished; now you can run"
+	@echo "  python -m sphinx.web .build/web"
+	@echo "to start the server."
+
+htmlhelp:
+	mkdir -p .build/htmlhelp .build/doctrees
+	$(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) .build/htmlhelp
+	@echo
+	@echo "Build finished; now you can run HTML Help Workshop with the" \
+	      ".hhp project file in .build/htmlhelp."
+
+latex:
+	mkdir -p .build/latex .build/doctrees
+	$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) .build/latex
+	@echo
+	@echo "Build finished; the LaTeX files are in .build/latex."
+	@echo "Run \`make all-pdf' or \`make all-ps' in that directory to" \
+	      "run these through (pdf)latex."
+
+changes:
+	mkdir -p .build/changes .build/doctrees
+	$(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) .build/changes
+	@echo
+	@echo "The overview file is in .build/changes."
+
+linkcheck:
+	mkdir -p .build/linkcheck .build/doctrees
+	$(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) .build/linkcheck
+	@echo
+	@echo "Link check complete; look for any errors in the above output " \
+	      "or in .build/linkcheck/output.txt."

+ 135 - 0
desktop/core/ext-py/python-ldap-2.4.6/Doc/conf.py

@@ -0,0 +1,135 @@
+# -*- coding: utf-8 -*-
+#
+# python-ldap documentation build configuration file, created by
+# sphinx-quickstart on Sat Mar 29 15:08:17 2008.
+#
+# This file is execfile()d with the current directory set to its containing dir.
+#
+# The contents of this file are pickled, so don't put values in the namespace
+# that aren't pickleable (module imports are okay, they're removed automatically).
+#
+# All configuration values have a default value; values that are commented out
+# serve to show the default value.
+#
+# $Id: conf.py,v 1.17 2011/10/14 11:48:31 stroeder Exp $
+
+import sys
+
+# If your extensions are in another directory, add it here.
+#sys.path.append('some/directory')
+
+# General configuration
+# ---------------------
+
+# Add any Sphinx extension module names here, as strings. They can be extensions
+# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
+extensions = ['sphinx.ext.autodoc']
+
+# Add any paths that contain templates here, relative to this directory.
+templates_path = ['.templates']
+
+# The suffix of source filenames.
+source_suffix = '.rst'
+
+# The master toctree document.
+master_doc = 'index'
+
+# General substitutions.
+project = 'python-ldap'
+copyright = '2008-2011, python-ldap project team'
+
+# The default replacements for |version| and |release|, also used in various
+# other places throughout the built documents.
+#
+# The short X.Y version.
+version = '2.4'
+# The full version, including alpha/beta/rc tags.
+release = '2.4.4.0'
+
+# There are two options for replacing |today|: either, you set today to some
+# non-false value, then it is used:
+#today = ''
+# Else, today_fmt is used as the format for a strftime call.
+today_fmt = '%B %d, %Y'
+
+# List of documents that shouldn't be included in the build.
+#unused_docs = []
+
+# If true, '()' will be appended to :func: etc. cross-reference text.
+#add_function_parentheses = True
+
+# If true, the current module name will be prepended to all description
+# unit titles (such as .. function::).
+#add_module_names = True
+
+# If true, sectionauthor and moduleauthor directives will be shown in the
+# output. They are ignored by default.
+#show_authors = False
+
+# The name of the Pygments (syntax highlighting) style to use.
+pygments_style = 'sphinx'
+
+
+# Options for HTML output
+# -----------------------
+
+# The style sheet to use for HTML and HTML Help pages. A file of that name
+# must exist either in Sphinx' static/ path, or in one of the custom paths
+# given in html_static_path.
+html_style = 'default.css'
+
+# Add any paths that contain custom static files (such as style sheets) here,
+# relative to this directory. They are copied after the builtin static files,
+# so a file named "default.css" will overwrite the builtin "default.css".
+html_static_path = ['.static']
+
+# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
+# using the given strftime format.
+html_last_updated_fmt = '%b %d, %Y'
+
+# If true, SmartyPants will be used to convert quotes and dashes to
+# typographically correct entities.
+#html_use_smartypants = True
+
+# Content template for the index page.
+#html_index = ''
+
+# Custom sidebar templates, maps document names to template names.
+#html_sidebars = {}
+
+# Additional templates that should be rendered to pages, maps page names to
+# template names.
+#html_additional_pages = {}
+
+# If false, no module index is generated.
+html_use_modindex = True
+
+# If true, the reST sources are included in the HTML build as _sources/<name>.
+#html_copy_source = True
+
+# Output file base name for HTML help builder.
+htmlhelp_basename = 'python-ldap-doc'
+
+
+# Options for LaTeX output
+# ------------------------
+
+# The paper size ('letter' or 'a4').
+#latex_paper_size = 'letter'
+
+# The font size ('10pt', '11pt' or '12pt').
+#latex_font_size = '10pt'
+
+# Grouping the document tree into LaTeX files. List of tuples
+# (source start file, target name, title, author, document class [howto/manual]).
+latex_documents = [('index', 'python-ldap.tex', 'python-ldap Documentation',
+                    'python-ldap project', 'manual')]
+
+# Additional stuff for the LaTeX preamble.
+#latex_preamble = ''
+
+# Documents to append as an appendix to all manuals.
+#latex_appendices = []
+
+# If false, no module index is generated.
+latex_use_modindex = True

+ 28 - 0
desktop/core/ext-py/python-ldap-2.4.6/Doc/dsml.rst

@@ -0,0 +1,28 @@
+.. % $Id: dsml.rst,v 1.5 2011/07/28 09:05:10 stroeder Exp $
+
+***************************************
+:mod:`dsml` DSMLv1 parser and generator
+***************************************
+
+.. :py:module:: dsml
+   :synopsis: Parses and generates DSMLv1 files
+.. moduleauthor:: python-ldap project (see http://www.python-ldap.org/)
+
+
+This module parses and generates LDAP data in the format DSMLv1. It is
+implemented in pure Python and does not rely on any non-standard modules.
+Therefore it can be used stand-alone without the rest of the python-ldap
+package.
+
+
+.. autoclass:: dsml.DSMLWriter
+   :members:
+
+.. autoclass:: dsml.DSMLParser
+   :members:
+
+
+.. _dsml-example:
+
+Example
+=======

+ 44 - 0
desktop/core/ext-py/python-ldap-2.4.6/Doc/index.rst

@@ -0,0 +1,44 @@
+##########################
+python-ldap Documentation
+##########################
+
+.. % $Id: index.rst,v 1.8 2011/10/26 19:42:45 stroeder Exp $
+
+.. topic:: Abstract
+
+   This document describes the package python-ldap with its various modules.
+
+   Depending on what you want to do this manual assumes basic to expert
+   knowledge about the Python language and the LDAP standard (LDAPv3).
+
+
+********
+Contents
+********
+
+.. toctree::
+   :maxdepth: 3
+
+   installing.rst
+   ldap.rst
+   ldap-async.rst
+   ldap-controls.rst
+   ldap-dn.rst
+   ldap-extop.rst
+   ldap-filter.rst
+   ldap-modlist.rst
+   ldap-resiter.rst
+   ldap-schema.rst
+   ldap-syncrepl.rst
+   ldif.rst
+   ldapurl.rst
+   dsml.rst
+
+
+******************
+Indices and tables
+******************
+
+* :ref:`genindex`
+* :ref:`modindex`
+* :ref:`search`

+ 111 - 0
desktop/core/ext-py/python-ldap-2.4.6/Doc/installing.rst

@@ -0,0 +1,111 @@
+.. % $Id: installing.rst,v 1.15 2011/07/24 19:00:53 stroeder Exp $
+
+***********************
+Building and installing
+***********************
+
+python-ldap is built and installed using the Python DistUtils installed
+along with your Python installation:
+
+::
+
+  python setup.py build
+  python setup.py install
+
+If you have more than one Python interpreter installed locally you should
+use the same one you plan to use python-ldap with.
+
+See further instructions for using DistUtils here: http://docs.python.org/install/index.html
+
+Prerequisites
+=============
+
+The following software packages are required to be installed
+on the local system when building python-ldap:
+
+- Python version 2.3 or later including its development files: http://www.python.org/
+- OpenLDAP client libs version 2.4.11 or later: http://www.openldap.org/
+  It is not possible and not supported to build with prior versions.
+- OpenSSL (optional): http://www.openssl.org/
+- cyrus-sasl (optional): http://www.cyrussasl.org
+- Kerberos libs, MIT or heimdal (optional)
+
+Furthermore some sub-modules of :py:mod:`ldap.controls` and :py:mod:`ldap.extop`
+require :py:mod:`pyasn1` and :py:mod:`pyasn1_modules` to be installed.
+
+  http://pyasn1.sf.net
+
+
+setup.cfg
+=========
+
+The file setup.cfg allows to set some build and installation
+parameters for reflecting the local installation of required
+software packages. Only section [_ldap] is described here.
+More information about other sections can be found in the
+documentation of Python's DistUtils.
+
+.. data:: library_dirs
+
+   Specifies in which directories to search for required libraries.
+
+.. data:: include_dirs
+
+   Specifies in which directories to search for include files of required libraries.
+
+.. data:: libs
+
+   A space-separated list of library names to link to (see :ref:`libs-used-label`).
+
+.. data:: extra_compile_args
+
+   Compiler options.
+
+.. data:: extra_objects
+
+.. _libs-used-label:
+
+Libs used
+---------
+
+.. data:: ldap
+.. data:: ldap_r
+
+   The LDAP protocol library of OpenLDAP. ldap_r is the reentrant version
+   and should be preferred.
+
+.. data:: lber
+
+   The BER encoder/decoder library of OpenLDAP.
+
+.. data:: sasl2
+
+   The Cyrus-SASL library if needed and present during build
+
+.. data:: ssl
+
+   The SSL/TLS library of OpenSSL if needed and present during build
+
+.. data:: crypto
+
+   The basic cryptographic library of OpenSSL if needed and present during build
+
+Example
+=============
+
+The following example is for a full-featured build (including SSL and SASL support)
+of python-ldap with OpenLDAP installed in a different prefix directory
+(here /opt/openldap-2.3) and SASL header files found in /usr/include/sasl.
+Debugging symbols are preserved with compile option -g.
+
+::
+
+  [_ldap]
+  library_dirs = /opt/openldap-2.3/lib
+  include_dirs = /opt/openldap-2.3/include /usr/include/sasl
+
+  extra_compile_args = -g
+  extra_objects =
+
+  libs = ldap_r lber sasl2 ssl crypto
+

+ 110 - 0
desktop/core/ext-py/python-ldap-2.4.6/Doc/ldap-async.rst

@@ -0,0 +1,110 @@
+.. % $Id: ldap-async.rst,v 1.4 2011/07/28 08:52:01 stroeder Exp $
+
+
+**************************************************************
+:py:mod:`ldap.async` Stream-processing of large search results
+**************************************************************
+
+.. py:module:: ldap.async
+   :synopsis: Framework for stream-processing of large search results.
+
+With newer Python versions one might want to consider using
+:py:mod:`ldap.resiter` instead.
+
+
+Classes
+=======
+
+.. autoclass:: ldap.async.AsyncSearchHandler
+   :members:
+
+.. autoclass:: ldap.async.List
+   :members:
+
+.. autoclass:: ldap.async.Dict
+   :members:
+
+.. autoclass:: ldap.async.IndexedDict
+   :members:
+
+.. autoclass:: ldap.async.LDIFWriter
+   :members:
+
+.. autoclass:: ldap.async.DSMLWriter
+   :members:
+
+.. _ldap.async-example:
+
+Examples
+========
+
+.. _ldap.async-example.List:
+
+Using ldap.async.List
+^^^^^^^^^^^^^^^^^^^^^
+
+This example demonstrates how to use class ldap.async.List for
+retrieving partial search results even though the exception
+:exc:`ldap.SIZELIMIT_EXCEEDED` was raised because a server side limit was hit. ::
+
+   import sys,ldap,ldap.async
+
+   s = ldap.async.List(
+     ldap.initialize('ldap://localhost'),
+   )
+
+   s.startSearch(
+     'dc=stroeder,dc=com',
+     ldap.SCOPE_SUBTREE,
+     '(objectClass=*)',
+   )
+
+   try:
+     partial = s.processResults()
+   except ldap.SIZELIMIT_EXCEEDED:
+     sys.stderr.write('Warning: Server-side size limit exceeded.\n')
+   else:
+     if partial:
+       sys.stderr.write('Warning: Only partial results received.\n')
+
+   sys.stdout.write(
+     '%d results received.\n' % (
+       len(s.allResults)
+     )
+   )
+
+.. _ldap.async-example.LDIFWriter:
+
+Using ldap.async.LDIFWriter
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+This example demonstrates how to use class ldap.async.LDIFWriter
+for writing search results as LDIF to stdout. ::
+
+   import sys,ldap,ldap.async
+
+   s = ldap.async.LDIFWriter(
+     ldap.initialize('ldap://localhost:1390'),
+     sys.stdout
+   )
+
+   s.startSearch(
+     'dc=stroeder,dc=com',
+     ldap.SCOPE_SUBTREE,
+     '(objectClass=*)',
+   )
+
+   try:
+     partial = s.processResults()
+   except ldap.SIZELIMIT_EXCEEDED:
+     sys.stderr.write('Warning: Server-side size limit exceeded.\n')
+   else:
+     if partial:
+       sys.stderr.write('Warning: Only partial results received.\n')
+
+   sys.stderr.write(
+     '%d results received.\n' % (
+       s.endResultBreak-s.beginResultsDropped
+     )
+   )
+

+ 204 - 0
desktop/core/ext-py/python-ldap-2.4.6/Doc/ldap-controls.rst

@@ -0,0 +1,204 @@
+.. % $Id: ldap-controls.rst,v 1.10 2011/07/23 08:15:38 stroeder Exp $
+
+
+*********************************************************************
+:py:mod:`ldap.controls` High-level access to LDAPv3 extended controls
+*********************************************************************
+
+.. py:module:: ldap.controls
+   :synopsis: High-level access to LDAPv3 extended controls.
+.. moduleauthor:: python-ldap project (see http://www.python-ldap.org/)
+
+
+Variables
+=========
+
+.. py:data:: KNOWN_RESPONSE_CONTROLS
+
+   Dictionary mapping the OIDs of known response controls to the accompanying
+   :py:class:`ResponseControl` classes. This is used
+   by :py:func:`DecodeControlTuples` to automatically decode control values.
+   Calling application can also register their custom :py:class:`ResponseControl`
+   classes in this dictionary possibly overriding pre-registered classes.
+
+
+Classes
+=======
+
+This module defines the following classes:
+
+
+.. autoclass:: ldap.controls.RequestControl
+   :members:
+
+.. autoclass:: ldap.controls.ResponseControl
+   :members:
+
+.. autoclass:: ldap.controls.LDAPControl
+   :members:
+
+
+Functions
+=========
+
+This module defines the following functions:
+
+
+.. autofunction:: ldap.controls.RequestControlTuples
+
+.. autofunction:: ldap.controls.DecodeControlTuples
+
+
+Sub-modules
+===========
+
+Various sub-modules implement specific LDAPv3 extended controls. The classes
+therein are derived from the base-classes :py:class:`ldap.controls.RequestControl`,
+:py:class:`ldap.controls.ResponseControl` or :py:class:`ldap.controls.LDAPControl`.
+
+Some of them require :py:mod:`pyasn1` and :py:mod:`pyasn1_modules` to be installed:
+
+Usually the names of the method arguments and the class attributes match
+the ASN.1 identifiers used in the specification. So looking at the referenced
+RFC or Internet-Draft is very helpful to understand the API.
+
+
+:py:mod:`ldap.controls.simple` Very simple controls
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+.. py:module:: ldap.controls.simple
+   :synopsis: simple request and response controls implemented in pure Python
+
+
+.. autoclass:: ldap.controls.simple.ValueLessRequestControl
+   :members:
+
+.. autoclass:: ldap.controls.simple.OctetStringInteger
+   :members:
+
+.. autoclass:: ldap.controls.simple.BooleanControl
+   :members:
+
+.. autoclass:: ldap.controls.simple.ManageDSAITControl
+   :members:
+
+   .. seealso::
+
+      :rfc:`3296` - Named Subordinate References in Lightweight Directory Access Protocol (LDAP) Directories
+
+.. autoclass:: ldap.controls.simple.RelaxRulesControl
+   :members:
+
+   .. seealso::
+
+      http://tools.ietf.org/draft/draft-zeilenga-ldap-relax/
+
+.. autoclass:: ldap.controls.simple.ProxyAuthzControl
+   :members:
+
+   .. seealso::
+
+      :rfc:`4370` - Lightweight Directory Access Protocol (LDAP): Proxied Authorization Control
+
+.. autoclass:: ldap.controls.simple.AuthorizationIdentityControl
+   :members:
+
+   .. seealso::
+
+      :rfc:`3829` - Lightweight Directory Access Protocol (LDAP): Authorization Identity Request and Response Controls
+
+.. autoclass:: ldap.controls.simple.GetEffectiveRightsControl
+   :members:
+
+
+
+:py:mod:`ldap.controls.libldap` Various controls implemented in OpenLDAP libs
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+
+.. py:module:: ldap.controls.libldap
+   :synopsis: request and response controls implemented by OpenLDAP libs
+
+This module wraps C functions in OpenLDAP client libs which implement various
+request and response controls into Python classes.
+
+
+.. autoclass:: ldap.controls.libldap.AssertionControl
+   :members:
+
+   .. seealso::
+
+      :rfc:`4528` - Lightweight Directory Access Protocol (LDAP) Assertion Control
+
+
+.. autoclass:: ldap.controls.libldap.MatchedValuesControl
+   :members:
+
+   .. seealso::
+
+      :rfc:`3876` - Returning Matched Values with the Lightweight Directory Access Protocol version 3 (LDAPv3)
+
+
+.. autoclass:: ldap.controls.libldap.SimplePagedResultsControl
+   :members:
+
+   .. seealso::
+
+      :rfc:`2696` - LDAP Control Extension for Simple Paged Results Manipulation
+
+
+:py:mod:`ldap.controls.psearch` LDAP Persistent Search
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+.. py:module:: ldap.controls.psearch
+   :synopsis: request and response controls for LDAP persistent search
+
+This module implements request and response controls for LDAP persistent
+search.
+
+.. seealso::
+
+   http://tools.ietf.org/html/draft-ietf-ldapext-psearch
+
+
+.. autoclass:: ldap.controls.psearch.PersistentSearchControl
+   :members:
+
+.. autoclass:: ldap.controls.psearch.EntryChangeNotificationControl
+   :members:
+
+
+:py:mod:`ldap.controls.sessiontrack` Session tracking control
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+.. py:module:: ldap.controls.sessiontrack
+   :synopsis: request control for session tracking
+
+.. seealso::
+
+   http://tools.ietf.org/html/draft-wahl-ldap-session
+
+
+.. autoclass:: ldap.controls.sessiontrack.SessionTrackingControl
+   :members:
+
+
+:py:mod:`ldap.controls.readentry` Read entry control
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+.. py:module:: ldap.controls.readentry
+   :synopsis: read entryrequest and response controls
+
+.. seealso::
+
+   :rfc:`4527` - Lightweight Directory Access Protocol (LDAP): Read Entry Controls
+
+
+.. autoclass:: ldap.controls.readentry.ReadEntryControl
+   :members:
+
+.. autoclass:: ldap.controls.readentry.PreReadControl
+   :members:
+
+.. autoclass:: ldap.controls.readentry.PostReadControl
+   :members:

+ 114 - 0
desktop/core/ext-py/python-ldap-2.4.6/Doc/ldap-dn.rst

@@ -0,0 +1,114 @@
+.. % $Id: ldap-dn.rst,v 1.6 2011/07/22 07:43:45 stroeder Exp $
+
+
+:py:mod:`ldap.dn` LDAP Distinguished Name handling
+====================================================
+
+.. py:module:: ldap.dn
+   :synopsis: LDAP Distinguished Name handling.
+.. moduleauthor:: python-ldap project (see http://www.python-ldap.org/)
+
+
+.. % Author of the module code;
+
+
+.. seealso::
+
+   For LDAPv3 DN syntax see:
+
+   :rfc:`4514` - Lightweight Directory Access Protocol (LDAP): String Representation of Distinguished Names
+
+.. seealso::
+
+   For deprecated LDAPv2 DN syntax (obsoleted by LDAPv3) see:
+
+   :rfc:`1779` - A String Representation of Distinguished Names
+
+The :mod:`ldap.dn` module defines the following functions:
+
+
+.. function:: escape_dn_chars(s)
+
+   This function escapes characters in string *s* which  are special in LDAP
+   distinguished names. You should use  this function when building LDAP DN strings
+   from arbitrary input.
+
+   .. % -> string
+
+
+.. function:: str2dn(s [, flags=0])
+
+   This function takes *s* and breaks it up into its component parts  down to AVA
+   level. The optional parameter *flags* describes the DN format of s  (see
+   :ref:`ldap-dn-flags`). Note that hex-encoded non-ASCII chars are decoded
+   to the raw bytes.
+
+   .. % -> list
+
+
+.. function:: dn2str(dn)
+
+   This function takes a decomposed DN in *dn* and returns  a single string. It's
+   the inverse to :func:`str2dn`.  Special characters are escaped with the help of
+   function :func:`escape_dn_chars`.
+
+   .. % -> string
+
+
+.. function:: explode_dn(dn [, notypes=0[, flags=0]])
+
+   This function takes *dn* and breaks it up into its component parts.   Each part
+   is known as an RDN (Relative Distinguished Name). The optional  *notypes*
+   parameter is used to specify that only the RDN values be   returned and not
+   their types. The optional parameter *flags*  describes the DN format of s (see
+   :ref:`ldap-dn-flags`).    This function is emulated by function
+   :func:`str2dn`  since the function ldap_explode_dn() in the C library is
+   deprecated.
+
+   .. % -> list
+
+
+.. function:: explode_rdn(rdn [, notypes=0[, flags=0]])
+
+   This function takes a (multi-valued) *rdn* and breaks it up  into a list of
+   characteristic attributes. The optional  *notypes* parameter is used to specify
+   that only the RDN values be   returned and not their types. The optional *flags*
+   parameter  describes the DN format of s (see :ref:`ldap-dn-flags`).    This
+   function is emulated by function :func:`str2dn`  since the function
+   ldap_explode_rdn() in the C library is deprecated.
+
+   .. % -> list
+
+
+.. _ldap-dn-example:
+
+Examples
+^^^^^^^^^
+
+Splitting a LDAPv3 DN to AVA level. Note that both examples have the same result
+but in the first example the non-ASCII chars are passed as is (byte buffer string)
+whereas in the second example the hex-encoded DN representation are passed to the function.
+
+>>> ldap.dn.str2dn('cn=Michael Str\xc3\xb6der,dc=stroeder,dc=com',flags=ldap.DN_FORMAT_LDAPV3)
+[[('cn', 'Michael Str\xc3\xb6der', 4)], [('dc', 'stroeder', 1)], [('dc', 'com', 1)]]
+>>> ldap.dn.str2dn('cn=Michael Str\C3\B6der,dc=stroeder,dc=com',flags=ldap.DN_FORMAT_LDAPV3)
+[[('cn', 'Michael Str\xc3\xb6der', 4)], [('dc', 'stroeder', 1)], [('dc', 'com', 1)]]
+
+
+Splitting a LDAPv2 DN into RDN parts:
+
+>>> ldap.dn.explode_dn('cn=Michael Stroeder;dc=stroeder;dc=com',flags=ldap.DN_FORMAT_LDAPV2)
+['cn=Michael Stroeder', 'dc=stroeder', 'dc=com']
+
+
+Splitting a multi-valued RDN:
+
+>>> ldap.dn.explode_rdn('cn=Michael Stroeder+mail=michael@stroeder.com',flags=ldap.DN_FORMAT_LDAPV2)
+['cn=Michael Stroeder', 'mail=michael@stroeder.com']
+
+Splitting a LDAPv3 DN with a multi-valued RDN into its AVA parts:
+
+
+>>> ldap.dn.str2dn('cn=Michael Stroeder+mail=michael@stroeder.com,dc=stroeder,dc=com')
+[[('cn', 'Michael Stroeder', 1), ('mail', 'michael@stroeder.com', 1)], [('dc', 'stroeder', 1)], [('dc', 'com', 1)]]
+

+ 43 - 0
desktop/core/ext-py/python-ldap-2.4.6/Doc/ldap-extop.rst

@@ -0,0 +1,43 @@
+.. % $Id: ldap-extop.rst,v 1.4 2011/07/22 17:28:46 stroeder Exp $
+
+********************************************************************
+:py:mod:`ldap.extop` High-level access to LDAPv3 extended operations
+********************************************************************
+
+.. py:module:: ldap.extop
+   :synopsis: High-level access to LDAPv3 extended operations.
+
+
+Classes
+=======
+
+This module defines the following classes:
+
+.. autoclass:: ldap.extop.ExtendedRequest
+   :members:
+
+
+.. autoclass:: ldap.extop.ExtendedResponse
+   :members:
+
+
+:py:mod:`ldap.extop.dds` Classes for Dynamic Entries extended operations
+========================================================================
+
+.. py:module:: ldap.extop.dds
+   :synopsis: Classes for Dynamic Entries extended operations
+
+This requires :py:mod:`pyasn1` and :py:mod:`pyasn1_modules` to be installed.
+
+.. seealso::
+
+   :rfc:`2589` - Lightweight Directory Access Protocol (v3): Extensions for Dynamic Directory Services
+
+
+.. autoclass:: ldap.extop.dds.RefreshRequest
+   :members:
+
+
+.. autoclass:: ldap.extop.dds.RefreshResponse
+   :members:
+

+ 41 - 0
desktop/core/ext-py/python-ldap-2.4.6/Doc/ldap-filter.rst

@@ -0,0 +1,41 @@
+.. % $Id: ldap-filter.rst,v 1.4 2011/07/21 20:33:26 stroeder Exp $
+
+
+:py:mod:`ldap.filter` LDAP filter handling
+============================================
+
+.. py:module:: ldap.filter
+   :synopsis: LDAP filter handling.
+.. moduleauthor:: python-ldap project (see http://www.python-ldap.org/)
+
+
+.. % Author of the module code;
+
+
+.. seealso::
+
+   :rfc:`4515` - Lightweight Directory Access Protocol (LDAP): String Representation of Search Filters.
+
+The :mod:`ldap.filter` module defines the following functions:
+
+
+.. function:: escape_filter_chars(assertion_value[, escape_mode=0])
+
+   This function escapes characters in *assertion_value* which  are special in LDAP
+   filters. You should use this function when  building LDAP filter strings from
+   arbitrary input.    *escape_mode* means:  If :const:`0` only special chars
+   mentioned in RFC 4515 are escaped.  If :const:`1` all NON-ASCII chars are
+   escaped.  If :const:`2` all chars are escaped.
+
+   .. % -> string
+
+
+.. function:: filter_format(filter_template, assertion_values)
+
+   This function applies :func:`escape_filter_chars` to each of the strings in
+   list *assertion_values*. After that *filter_template* containing  as many
+   :const:`%s` placeholders as count of assertion values is  used to build the
+   whole filter string.
+
+   .. % -> string
+

+ 53 - 0
desktop/core/ext-py/python-ldap-2.4.6/Doc/ldap-modlist.rst

@@ -0,0 +1,53 @@
+.. % $Id: ldap-modlist.rst,v 1.4 2011/07/22 17:39:44 stroeder Exp $
+
+
+:py:mod:`ldap.modlist` Generate modify lists
+==============================================
+
+.. py:module:: ldap.modlist
+
+
+The :mod:`ldap.modlist` module defines the following functions:
+
+
+.. function:: addModlist(entry [, ignore_attr_types=[]]) -> list
+
+   This function builds a list suitable for passing it directly as argument
+   *modlist* to method :py:meth:`ldap.ldapobject.LDAPObject.add` or
+   its synchronous counterpart :py:meth:`ldap.ldapobject.LDAPObject.add_s`.
+
+   *entry* is a dictionary like returned when receiving search results.
+
+   *ignore_attr_types* is a list of attribute type
+   names which shall be ignored completely. Attributes of these types will not appear
+   in the result at all.
+
+
+.. function:: modifyModlist( old_entry, new_entry [, ignore_attr_types=[] [, ignore_oldexistent=0 [, case_ignore_attr_types=None]]]) -> list
+
+   This function builds a list suitable for passing it directly as argument
+   *modlist* to method :py:meth:`ldap.ldapobject.LDAPObject.modify` or
+   its synchronous counterpart :py:meth:`ldap.ldapobject.LDAPObject.modify_s`. 
+   
+   Roughly when applying the resulting modify list to an entry
+   holding  the data *old_entry* it will be modified in such a way that the entry
+   holds *new_entry* after the modify operation. It is handy in situations when it
+   is impossible to track user changes to an entry's data or for synchronizing
+   operations.
+   
+   *old_entry* and *new_entry* are dictionaries like returned when
+   receiving search results.
+   
+   *ignore_attr_types* is a list of attribute type
+   names which shall be ignored completely. These attribute types will not appear
+   in the result at all.
+
+   If *ignore_oldexistent* is non-zero attribute type names which
+   are in *old_entry* but are not found in *new_entry* at all are not deleted.
+   This is handy for situations where your application sets attribute value to
+   an empty string for deleting an attribute. In most cases leave zero.
+
+   If *case_ignore_attr_types* is a list of attribute type names for which
+   the comparison will be conducted case-insensitive. It is useful in
+   situations where a LDAP server normalizes values and one wants to avoid
+   unnecessary changes (e.g. case of attribute type names in DNs).

+ 52 - 0
desktop/core/ext-py/python-ldap-2.4.6/Doc/ldap-resiter.rst

@@ -0,0 +1,52 @@
+.. % $Id: ldap-resiter.rst,v 1.5 2011/08/27 15:43:06 stroeder Exp $
+
+
+:py:mod:`ldap.resiter` Generator for stream-processing of large search results
+==============================================================================
+
+.. py:module:: ldap.resiter
+   :synopsis: Generator for stream-processing of large search results.
+.. moduleauthor:: python-ldap project (see http://www.python-ldap.org/)
+
+
+.. _ldap.resiter-classes:
+
+.. py:class:: ResultProcessor
+
+This is a mix-in class to be used with class :py:class:`ldap.LDAPObject` or
+derived classes which has these methods:
+
+  .. automethod:: ldap.resiter.ResultProcessor.allresults
+
+
+.. _ldap.resiter-example:
+
+
+Examples
+========
+
+.. _ldap.resiter.ResultProcessor-example:
+
+Using ldap.resiter.ResultProcessor
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+This example demonstrates how to use mix-in class ldap.resiter.ResultProcessor for
+retrieving results formerly requested with :py:meth:`ldap.LDAPObject.search()` and
+processing them in a for-loop.
+
+::
+
+  import sys,ldap,ldap.resiter
+
+  class MyLDAPObject(ldap.ldapobject.LDAPObject,ldap.resiter.ResultProcessor):
+    pass
+
+  l = MyLDAPObject('ldap://localhost')
+
+  # Asynchronous search method
+  msg_id = l.search('dc=stroeder,dc=com',ldap.SCOPE_SUBTREE,'(objectClass=*)')
+
+  for res_type,res_data,res_msgid,res_controls in l.allresults(msg_id):
+    for dn,entry in res_data:
+      # process dn and entry
+      print dn,entry['objectClass']

+ 87 - 0
desktop/core/ext-py/python-ldap-2.4.6/Doc/ldap-schema.rst

@@ -0,0 +1,87 @@
+.. % $Id: ldap-schema.rst,v 1.4 2011/07/22 17:46:07 stroeder Exp $
+
+
+********************************************
+:py:mod:`ldap.schema` Handling LDAPv3 schema
+********************************************
+
+.. py:module:: ldap.schema
+
+This module deals with schema information usually retrieved from
+a special subschema subentry provided by the server.
+It is closely modeled along the directory information model described
+in the following RFC with which you should make yourself familiar
+when trying to use this module:
+
+.. seealso::
+
+   :rfc:`4512` - Lightweight Directory Access Protocol (LDAP): Directory Information Models
+
+
+:py:mod:`ldap.schema.subentry` Processing LDAPv3 subschema subentry
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+.. py:module:: ldap.schema.subentry
+
+
+.. py:data:: NOT_HUMAN_READABLE_LDAP_SYNTAXES
+
+   Dictionary where the keys are the OIDs of LDAP syntaxes known to be
+   not human-readable when displayed to a console without conversion
+   and which cannot be decoded to a :py:data:`types.UnicodeType`.
+
+
+Functions
+=========
+
+.. autofunction:: ldap.schema.subentry.urlfetch
+
+Classes
+=======
+
+.. autoclass:: ldap.schema.subentry.SubSchema
+   :members:
+
+
+:py:mod:`ldap.schema.models` Schema elements
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+.. py:module:: ldap.schema.models
+
+
+.. autoclass:: ldap.schema.models.Entry
+   :members:
+
+.. autoclass:: ldap.schema.models.SchemaElement
+   :members:
+
+.. autoclass:: ldap.schema.models.AttributeType
+   :members:
+
+.. autoclass:: ldap.schema.models.ObjectClass
+   :members:
+
+.. autoclass:: ldap.schema.models.MatchingRule
+   :members:
+
+.. autoclass:: ldap.schema.models.MatchingRuleUse
+   :members:
+
+.. autoclass:: ldap.schema.models.DITContentRule
+   :members:
+
+.. autoclass:: ldap.schema.models.NameForm
+   :members:
+
+.. autoclass:: ldap.schema.models.DITStructureRule
+   :members:
+
+
+.. _ldap.schema-example:
+
+Examples for ldap.schema
+^^^^^^^^^^^^^^^^^^^^^^^^
+
+::
+
+   import ldap.schema

+ 25 - 0
desktop/core/ext-py/python-ldap-2.4.6/Doc/ldap-syncrepl.rst

@@ -0,0 +1,25 @@
+.. % $Id: ldap-syncrepl.rst,v 1.1 2011/10/26 19:41:27 stroeder Exp $
+
+********************************************************************
+:py:mod:`ldap.syncrepl` Implementation of a syncrepl consumer
+********************************************************************
+
+.. py:module:: ldap.syncrepl
+   :synopsis: Implementation of a syncrepl consumer
+
+.. seealso::
+
+   :rfc:`4533` - Lightweight Directory Access Protocol (v3): Content Synchronization Operation
+
+This requires :py:mod:`pyasn1` and :py:mod:`pyasn1_modules` to be installed.
+
+
+Classes
+=======
+
+This module defines the following classes:
+
+
+.. autoclass:: ldap.syncrepl.SyncreplConsumer
+   :members:
+

+ 1125 - 0
desktop/core/ext-py/python-ldap-2.4.6/Doc/ldap.rst

@@ -0,0 +1,1125 @@
+.. % $Id: ldap.rst,v 1.26 2011/09/14 18:21:06 stroeder Exp $
+
+********************************************
+:py:mod:`ldap` LDAP library interface module
+********************************************
+
+.. py:module:: ldap
+    :platform: Posix, Windows
+    :synopsis: Access to an underlying LDAP C library.
+.. moduleauthor:: python-ldap project (see http://www.python-ldap.org/)
+
+
+This module provides access to the LDAP  (Lightweight Directory Access Protocol)
+C API implemented  in OpenLDAP 2.3 or newer.  It is similar to the C API, with
+the notable differences  that lists are manipulated via Python  list operations
+and errors appear as exceptions.    For far more detailed information on the C
+interface,   please see the (expired) draft-ietf-ldapext-ldap-c-api-04.    This
+documentation is current for the Python LDAP module, version  |release|.  Source
+and binaries are available from http://www.python-ldap.org/.
+
+
+Functions
+=========
+
+This module defines the following functions:
+
+.. py:function:: initialize(uri [, trace_level=0 [, trace_file=sys.stdout [, trace_stack_limit=None]]]) -> LDAPObject object
+
+   Initializes a new connection object for accessing the given LDAP server,
+   and return an LDAP object (see :ref:`ldap-objects`) used to perform operations
+   on that server.  Parameter *uri* has to be a valid LDAP URL.
+
+   Note that the C wrapper function :py:func:_ldap.initialize() is called which calls
+   the OpenLDAP funtion ldap_initialize(). Calling this function just initializes
+   the LDAP connection struct in the C API - nothing else. The first call to
+   an operation method (bind, search etc.) then really opens the connection.
+   Before that nothing is sent on the wire.
+
+   The optional arguments are for generating debug log information:
+   *trace_level* specifies the amount of information being logged,
+   *trace_file* specifies a file-like object as target of the debug log and
+   *trace_stack_limit* specifies the stack limit of tracebacks in debug log.
+
+   Possible values for *trace_level* are
+   :py:const:`0` for no logging,
+   :py:const:`1` for only logging the method calls with arguments,
+   :py:const:`2` for logging the method calls with arguments and the complete results and
+   :py:const:`9` for also logging the traceback of method calls.
+
+   .. seealso::
+
+      :rfc:`4516` - Lightweight Directory Access Protocol (LDAP): Uniform Resource Locator
+
+.. py:function:: open(host [, port=PORT]) -> LDAPObject object
+
+   Opens a new connection with an LDAP server, and return an LDAP object  (see
+   :ref:`ldap-objects`) used to perform operations on that server.  *host* is a
+   string containing solely the host name. *port* is an integer specifying the
+   port where the LDAP server is  listening (default is 389).
+
+   Note: Using this function is deprecated.
+
+
+.. py:function:: get_option(option) -> int|string
+
+   This function returns the value of the global option specified by *option*.
+
+
+.. py:function:: set_option(option, invalue) -> None
+
+   This function sets the value of the global option specified by *option* to
+   *invalue*.
+
+
+.. _ldap-constants:
+
+Constants
+=========
+
+The module defines various constants. Note that some constants depend
+on the build options and which underlying libs were used or even on
+the version of the libs. So before using those constants the application has
+to explicitly check whether they are available.
+
+General
+-------
+
+.. py:data:: PORT
+
+   The assigned TCP port number (389) that LDAP servers listen on.
+
+.. py:data:: SASL_AVAIL
+
+   Integer where a non-zero value indicates that python-ldap was built with
+   support for SASL (Cyrus-SASL).
+
+.. py:data:: TLS_AVAIL
+
+   Integer where a non-zero value indicates that python-ldap was built with
+   support for SSL/TLS (OpenSSL or similar libs).
+
+
+.. _ldap-options:
+
+Options
+-------
+
+.. seealso::
+
+   :manpage:`ldap.conf{5}` and :manpage:`ldap_get_options{3}`
+
+
+For use with functions :py:func:set_option() and :py:func:get_option()
+and methods :py:method:LDAPObject.set_option() and :py:method:LDAPObject.get_option() the
+following option identifiers are defined as constants:
+
+.. py:data:: OPT_API_FEATURE_INFO
+
+.. py:data:: OPT_API_INFO
+
+.. py:data:: OPT_CLIENT_CONTROLS
+
+.. py:data:: OPT_DEBUG_LEVEL
+
+   Sets the debug level within the underlying LDAP C lib.
+
+.. py:data:: OPT_DEFBASE
+
+.. py:data:: OPT_DEREF
+
+   Specifies how alias derefencing is done within the underlying LDAP C lib.
+
+.. py:data:: OPT_ERROR_STRING
+
+.. py:data:: OPT_DIAGNOSTIC_MESSAGE
+
+.. py:data:: OPT_HOST_NAME
+
+.. py:data:: OPT_MATCHED_DN
+
+.. py:data:: OPT_NETWORK_TIMEOUT
+
+.. py:data:: OPT_PROTOCOL_VERSION
+
+   Sets the LDAP protocol version used for a connection. This is mapped to
+   object attribute `ldap.LDAPObject.protocol_version`
+
+.. py:data:: OPT_REFERRALS
+
+   int specifying whether referrals should be automatically chased within
+   the underlying LDAP C lib.
+
+.. py:data:: OPT_REFHOPLIMIT
+
+.. py:data:: OPT_RESTART
+
+.. py:data:: OPT_SERVER_CONTROLS
+
+.. py:data:: OPT_SIZELIMIT
+
+.. py:data:: OPT_SUCCESS
+
+.. py:data:: OPT_TIMELIMIT
+
+.. py:data:: OPT_TIMEOUT
+
+.. py:data:: OPT_URI
+
+.. _ldap-sasl-options:
+
+SASL options
+::::::::::::
+
+.. py:data:: OPT_X_SASL_AUTHCID
+
+.. py:data:: OPT_X_SASL_AUTHZID
+
+.. py:data:: OPT_X_SASL_MECH
+
+.. py:data:: OPT_X_SASL_NOCANON
+
+   If set to zero SASL host name canonicalization is disabled.
+
+.. py:data:: OPT_X_SASL_REALM
+
+.. py:data:: OPT_X_SASL_SECPROPS
+
+.. py:data:: OPT_X_SASL_SSF
+
+.. py:data:: OPT_X_SASL_SSF_EXTERNAL
+
+.. py:data:: OPT_X_SASL_SSF_MAX
+
+.. py:data:: OPT_X_SASL_SSF_MIN
+
+.. _ldap-tls-options:
+
+TLS options
+:::::::::::
+
+.. py:data:: OPT_X_TLS
+
+.. py:data:: OPT_X_TLS_ALLOW
+
+.. py:data:: OPT_X_TLS_CACERTDIR
+
+.. py:data:: OPT_X_TLS_CACERTFILE
+
+.. py:data:: OPT_X_TLS_CERTFILE
+
+.. py:data:: OPT_X_TLS_CIPHER_SUITE
+
+.. py:data:: OPT_X_TLS_CTX
+
+.. py:data:: OPT_X_TLS_DEMAND
+
+.. py:data:: OPT_X_TLS_HARD
+
+.. py:data:: OPT_X_TLS_KEYFILE
+
+.. py:data:: OPT_X_TLS_NEVER
+
+.. py:data:: OPT_X_TLS_RANDOM_FILE
+
+.. py:data:: OPT_X_TLS_REQUIRE_CERT
+
+.. py:data:: OPT_X_TLS_TRY
+
+.. _ldap-keepalive-options:
+
+Keepalive options
+:::::::::::::::::
+
+.. py:data:: OPT_X_KEEPALIVE_IDLE
+
+.. py:data:: OPT_X_KEEPALIVE_PROBES
+
+.. py:data:: OPT_X_KEEPALIVE_INTERVAL
+
+.. _ldap-dn-flags:
+
+DN format flags
+----------------
+
+This constants are used for DN-parsing functions found in
+sub-module :py:mod:`ldap.dn`.
+
+.. seealso::
+
+   :manpage:`ldap_str2dn{3}`
+
+
+.. py:data:: DN_FORMAT_LDAP
+
+.. py:data:: DN_FORMAT_LDAPV3
+
+.. py:data:: DN_FORMAT_LDAPV2
+
+.. py:data:: DN_FORMAT_DCE
+
+.. py:data:: DN_FORMAT_UFN
+
+.. py:data:: DN_FORMAT_AD_CANONICAL
+
+.. py:data:: DN_FORMAT_MASK
+
+.. py:data:: DN_PRETTY
+
+.. py:data:: DN_SKIP
+
+.. py:data:: DN_P_NOLEADTRAILSPACES
+
+.. py:data:: DN_P_NOSPACEAFTERRDN
+
+.. py:data:: DN_PEDANTIC
+
+
+
+.. _ldap-exceptions:
+
+Exceptions
+==========
+
+The module defines the following exceptions:
+
+.. py:exception:: LDAPError
+
+   This is the base class of all execeptions raised by the module :py:mod:`ldap`.
+   Unlike the C interface, errors are not returned as result codes, but
+   are instead turned into exceptions, raised as soon an the error condition
+   is detected.
+
+   The exceptions are accompanied by a dictionary possibly
+   containing an string value for the key :py:const:`desc`
+   (giving an English description of the error class)
+   and/or a string value for the key :py:const:`info`
+   (giving a string containing more information that the server may have sent).
+
+   A third possible field of this dictionary is :py:const:`matched` and
+   is set to a truncated form of the name provided or alias dereferenced
+   for the lowest entry (object or alias) that was matched.
+
+
+.. py:exception:: ADMINLIMIT_EXCEEDED
+
+.. py:exception:: AFFECTS_MULTIPLE_DSAS
+
+.. py:exception:: ALIAS_DEREF_PROBLEM
+
+   A problem was encountered when dereferencing an alias.
+   (Sets the :py:const:`matched` field.)
+
+.. py:exception:: ALIAS_PROBLEM
+
+   An alias in the directory points to a nonexistent entry.
+   (Sets the :py:const:`matched` field.)
+
+.. py:exception:: ALREADY_EXISTS
+
+   The entry already exists. E.g. the *dn* specified with :py:meth:`add()`
+   already exists in the DIT.
+
+.. py:exception:: AUTH_UNKNOWN
+
+   The authentication method specified to :py:meth:`bind()` is not known.
+
+.. py:exception:: BUSY
+
+   The DSA is busy.
+
+.. py:exception:: CLIENT_LOOP
+
+.. py:exception:: COMPARE_FALSE
+
+   A compare operation returned false.
+   (This exception should never be seen because :py:meth:`compare()` returns
+   a boolean result.)
+
+.. py:exception:: COMPARE_TRUE
+
+   A compare operation returned true.
+   (This exception should never be seen because :py:meth:`compare()` returns
+   a boolean result.)
+
+.. py:exception:: CONFIDENTIALITY_REQUIRED
+
+   Indicates that the session is not protected by a protocol such
+   as Transport Layer Security (TLS), which provides session
+   confidentiality.
+
+.. py:exception:: CONNECT_ERROR
+
+.. py:exception:: CONSTRAINT_VIOLATION
+
+   An attribute value specified or an operation started violates some
+   server-side constraint
+   (e.g., a postalAddress has too many lines or a line that is too long
+   or a password is expired).
+
+.. py:exception:: CONTROL_NOT_FOUND
+
+.. py:exception:: DECODING_ERROR
+
+   An error was encountered decoding a result from the LDAP server.
+
+.. py:exception:: ENCODING_ERROR
+
+   An error was encountered encoding parameters to send to the LDAP server.
+
+.. py:exception:: FILTER_ERROR
+
+   An invalid filter was supplied to :py:meth:`search()`
+   (e.g. unbalanced parentheses).
+
+.. py:exception:: INAPPROPRIATE_AUTH
+
+   Inappropriate authentication was specified (e.g. :py:const:`AUTH_SIMPLE`
+   was specified and the entry does not have a userPassword attribute).
+
+.. py:exception:: INAPPROPRIATE_MATCHING
+
+   Filter type not supported for the specified attribute.
+
+.. py:exception:: INSUFFICIENT_ACCESS
+
+   The user has insufficient access to perform the operation.
+
+.. py:exception:: INVALID_CREDENTIALS
+
+   Invalid credentials were presented during :py:meth:`bind()` or
+   :py:meth:`simple_bind()`.
+   (e.g., the wrong password).
+
+.. py:exception:: INVALID_DN_SYNTAX
+
+   A syntactically invalid DN was specified. (Sets the :py:const:`matched` field.)
+
+.. py:exception:: INVALID_SYNTAX
+
+   An attribute value specified by the client did not comply to the
+   syntax defined in the server-side schema.
+
+.. py:exception:: IS_LEAF
+
+   The object specified is a leaf of the diretcory tree.
+   Sets the :py:const:`matched` field of the exception dictionary value.
+
+.. py:exception:: LOCAL_ERROR
+
+   Some local error occurred. This is usually due to failed memory allocation.
+
+.. py:exception:: LOOP_DETECT
+
+   A loop was detected.
+
+.. py:exception:: MORE_RESULTS_TO_RETURN
+
+.. py:exception:: NAMING_VIOLATION
+
+   A naming violation occurred. This is raised e.g. if the LDAP server
+   has constraints about the tree naming.
+
+.. py:exception:: NO_OBJECT_CLASS_MODS
+
+   Modifying the objectClass attribute as requested is not allowed
+   (e.g. modifying structural object class of existing entry).
+
+.. py:exception:: NOT_ALLOWED_ON_NONLEAF
+
+   The operation is not allowed on a non-leaf object.
+
+.. py:exception:: NOT_ALLOWED_ON_RDN
+
+   The operation is not allowed on an RDN.
+
+.. py:exception:: NOT_SUPPORTED
+
+.. py:exception:: NO_MEMORY
+
+.. py:exception:: NO_OBJECT_CLASS_MODS
+
+   Object class modifications are not allowed.
+
+.. py:exception:: NO_RESULTS_RETURNED
+
+.. py:exception:: NO_SUCH_ATTRIBUTE
+
+   The attribute type specified does not exist in the entry.
+
+.. py:exception:: NO_SUCH_OBJECT
+
+   The specified object does not exist in the directory.
+   Sets the :py:const:`matched` field of the exception dictionary value.
+
+.. py:exception:: OBJECT_CLASS_VIOLATION
+
+   An object class violation occurred when the LDAP server checked
+   the data sent by the client against the server-side schema
+   (e.g. a "must" attribute was missing in the entry data).
+
+.. py:exception:: OPERATIONS_ERROR
+
+   An operations error occurred.
+
+.. py:exception:: OTHER
+
+   An unclassified error occurred.
+
+.. py:exception:: PARAM_ERROR
+
+   An ldap routine was called with a bad parameter.
+
+.. py:exception:: PARTIAL_RESULTS
+
+   Partial results only returned. This exception is raised if
+   a referral is received when using LDAPv2.
+   (This exception should never be seen with LDAPv3.)
+
+.. py:exception:: PROTOCOL_ERROR
+
+   A violation of the LDAP protocol was detected.
+
+.. py:exception:: RESULTS_TOO_LARGE
+
+   The result does not fit into a UDP packet. This happens only when using
+   UDP-based CLDAP (connection-less LDAP) which is not supported anyway.
+
+.. py:exception:: SASL_BIND_IN_PROGRESS
+
+.. py:exception:: SERVER_DOWN
+
+   The  LDAP  library  can't  contact the LDAP server.
+
+.. py:exception:: SIZELIMIT_EXCEEDED
+
+   An LDAP size limit was exceeded.
+   This could be due to a ``sizelimit`` configuration on the LDAP server.
+
+.. py:exception:: STRONG_AUTH_NOT_SUPPORTED
+
+   The LDAP server does not support strong authentication.
+
+.. py:exception:: STRONG_AUTH_REQUIRED
+
+   Strong authentication is required  for the operation.
+
+.. py:exception:: TIMELIMIT_EXCEEDED
+
+   An LDAP time limit was exceeded.
+
+.. py:exception:: TIMEOUT
+
+   A timelimit was exceeded while waiting for a result from the server.
+
+.. py:exception:: TYPE_OR_VALUE_EXISTS
+
+   An  attribute  type or attribute value specified already
+   exists in the entry.
+
+.. py:exception:: UNAVAILABLE
+
+   The DSA is unavailable.
+
+.. py:exception:: UNAVAILABLE_CRITICAL_EXTENSION
+
+   Indicates that the LDAP server was unable to satisfy a request
+   because one or more critical extensions were not available. Either
+   the server does not support the control or the control is not appropriate
+   for the operation type.
+
+.. py:exception:: UNDEFINED_TYPE
+
+   An attribute type used is not defined in the server-side schema.
+
+.. py:exception:: UNWILLING_TO_PERFORM
+
+   The  DSA is  unwilling to perform the operation.
+
+.. py:exception:: USER_CANCELLED
+
+   The operation was cancelled via the :py:meth:`abandon()` method.
+
+The above exceptions are raised when a result code from an underlying API
+call does not indicate success.
+
+
+.. _ldap-objects:
+
+LDAPObject classes
+==================
+
+.. py:class:: LDAPObject
+
+   Instances of :py:class:`LDAPObject` are returned by :py:func:`initialize()`
+   and :py:func:`open()` (deprecated). The connection is automatically unbound
+   and closed when the LDAP object is deleted. Internally :py:class:`LDAPObject`
+   is set to :py:class:`SimpleLDAPObject` by default.
+
+
+.. py:class:: SimpleLDAPObject(uri [, trace_level=0 [, trace_file=sys.stdout [, trace_stack_limit=5]]])
+
+   Instances of :py:class:`LDAPObject` are returned by :py:func:`initialize()`
+   and :py:func:`open()` (deprecated). The connection is automatically unbound
+   and closed  when the LDAP object is deleted.
+
+
+.. py:class:: ReconnectLDAPObject(uri [, trace_level=0 [, trace_file=sys.stdout [, trace_stack_limit=5] [, retry_max=1 [, retry_delay=60.0]]]])
+
+   This class is derived from :py:class:`SimpleLDAPObject` and used for automatic
+   reconnects when using the synchronous request methods (see below). This class
+   also implements the pickle protocol.
+
+   For automatic reconnects it has additional arguments:
+
+   *retry_max* specifies the number of reconnect attempts before
+   re-raising the :py:exc:`ldap.SERVER_DOWN` exception.
+
+   *retry_delay* specifies the time in seconds between reconnect attempts.
+
+
+Arguments for LDAPv3 controls
+-----------------------------
+
+The :py:mod:`ldap.controls` module can be used for constructing and
+decoding LDAPv3 controls. These arguments are available in the methods
+with names ending in :py:const:`_ext` or :py:const:`_ext_s`:
+
+*serverctrls*
+  is a list of :py:class:`ldap.controls.LDAPControl` instances sent to the server along
+  with the LDAP request (see module :py:mod:`ldap.controls`). These are
+  controls which alter the behaviour of the server when processing the
+  request if the control is supported by the server. The effect of controls
+  might differ depending on the type of LDAP request or controls might not
+  be applicable with certain LDAP requests at all.
+
+*clientctrls*
+  is a list of :py:class:`ldap.controls.LDAPControl` instances passed to the
+  client API and alter the behaviour of the client when processing the
+  request.
+
+
+Sending LDAP requests
+---------------------
+
+Most methods on LDAP objects initiate an asynchronous request to the
+LDAP server and return a message id that can be used later to retrieve
+the result with :py:meth:`result()`.
+
+Methods with names ending in :py:const:`_s` are the synchronous form
+and wait for and return with the server's result, or with
+:py:const:`None` if no data is expected.
+
+
+:class:`LDAPObject` instances have the following methods:
+
+.. py:method:: LDAPObject.abandon(msgid) -> None
+
+.. py:method:: LDAPObject.abandon_ext(msgid [, serverctrls=None [, clientctrls=None]]) -> None
+
+   Abandons an LDAP operation in progress without waiting for a LDAP response.
+   The *msgid* argument should be the message ID of an outstanding LDAP
+   operation as returned by the asynchronous methods :py:meth:`search()`, :py:meth:`modify()`, etc.
+   The caller can expect that the result of an abandoned operation will not be
+   returned from a future call to :py:meth:`result()`.
+
+   *serverctrls* and *clientctrls* like described above.
+
+
+.. py:method:: LDAPObject.add(dn, modlist) -> int
+
+.. py:method:: LDAPObject.add_s(dn, modlist) -> None
+
+.. py:method:: LDAPObject.add_ext(dn, modlist [, serverctrls=None [, clientctrls=None]]) -> int
+
+.. py:method:: LDAPObject.add_ext_s(dn, modlist [, serverctrls=None [, clientctrls=None]]) -> None
+
+   Performs an LDAP add operation. The *dn* argument is the distinguished
+   name (DN) of the entry to add, and *modlist* is a list of attributes to be
+   added. The modlist is similar the one passed to :py:meth:`modify()`, except that the
+   operation integer is omitted from the tuples in modlist. You might want to
+   look into sub-module \refmodule{ldap.modlist} for generating the modlist.
+
+   The asynchronous methods :py:meth:`add()` and :py:meth:`add_ext()`
+   return the message ID of the initiated request.
+
+   *serverctrls* and *clientctrls* like described above.
+
+.. py:method:: LDAPObject.bind(who, cred, method) -> int
+
+.. py:method:: LDAPObject.bind_s(who, cred, method) -> None
+
+.. py:method:: LDAPObject.simple_bind([who='' [, cred='']]) -> int
+
+.. py:method:: LDAPObject.simple_bind_s([who='' [, cred='']]) -> None
+
+   After an LDAP object is created, and before any other operations can be
+   attempted over the connection, a bind operation must be performed.
+
+   This method attempts to bind with the LDAP server using
+   either simple authentication, or Kerberos (if available).
+   The first and most general method, :py:meth:`bind()`,
+   takes a third parameter, *method* which can currently solely
+   be :py:const:`AUTH_SIMPLE`.
+
+
+.. py:method:: LDAPObject.sasl_interactive_bind_s(who, auth) -> None
+
+   This call is used to bind to the directory with a SASL bind request.
+
+
+.. py:method:: LDAPObject.cancel( cancelid, [, serverctrls=None [, clientctrls=None]]) -> None
+
+   Send cancels extended operation for an LDAP operation specified by *cancelid*.
+   The *cancelid* should be the message id of an outstanding LDAP operation as returned
+   by the asynchronous methods search(), modify() etc.  The caller
+   can expect that the result of an abandoned operation will not be
+   returned from a future call to :py:meth:`result()`.
+   In opposite to :py:meth:`abandon()` this extended operation gets an result from
+   the server and thus should be preferred if the server supports it.
+
+   *serverctrls* and *clientctrls* like described above.
+
+   :rfc:`3909` - Lightweight Directory Access Protocol (LDAP): Cancel Operation
+
+
+.. py:method:: LDAPObject.compare(dn, attr, value) -> int
+
+.. py:method:: LDAPObject.compare_s(dn, attr, value) -> tuple
+
+.. py:method:: LDAPObject.compare_ext(dn, attr, value [, serverctrls=None [, clientctrls=None]]) -> int
+
+.. py:method:: LDAPObject.compare_ext_s(dn, attr, value [, serverctrls=None [, clientctrls=None]]) -> tuple
+
+   Perform an LDAP comparison between the attribute named *attr* of
+   entry *dn*, and the value *value*. The synchronous forms
+   returns :py:const:`0` for false, or :py:const:`1` for true.
+   The asynchronous forms returns the message ID of the initiated request,
+   and the result of the asynchronous compare can be obtained using
+   :py:meth:`result()`.
+
+   Note that the asynchronous technique yields the answer
+   by raising the exception objects :py:exc:`ldap.COMPARE_TRUE` or
+   :py:exc:`ldap.COMPARE_FALSE`.
+
+   *serverctrls* and *clientctrls* like described above.
+
+   .. note::
+
+      A design fault in the LDAP API prevents *value*
+      from containing *NULL* characters.
+
+
+.. py:method:: LDAPObject.delete(dn) -> int
+
+.. py:method::  LDAPObject.delete_s(dn) -> None
+
+.. py:method:: LDAPObject.delete_ext(dn [, serverctrls=None [, clientctrls=None]]) -> int
+
+.. py:method:: LDAPObject.delete_ext_s(dn [, serverctrls=None [, clientctrls=None]]) -> None
+
+   Performs an LDAP delete operation on *dn*. The asynchronous form
+   returns the message id of the initiated request, and the result can be obtained
+   from a subsequent call to :py:meth:`result()`.
+
+   *serverctrls* and *clientctrls* like described above.
+
+
+.. py:method:: extop(extreq[,serverctrls=None[,clientctrls=None]]]) -> int
+
+.. py:method:: extop_s(extreq[,serverctrls=None[,clientctrls=None[,extop_resp_class=None]]]]) -> (respoid,respvalue)
+
+   Performs an LDAP extended operation. The asynchronous
+   form returns the message id of the initiated request, and the
+   result can be obtained from a subsequent call to :py:meth:`extop_result()`.
+
+   The *extreq* is an instance of class :py:class:`ldap.extop.ExtendedRequest`
+   containing the parameters for the extended operation request.
+   
+   If argument *extop_resp_class* is set to a sub-class of
+   :py:class:`ldap.extop.ExtendedResponse` this class is used to return an
+   object of this class instead of a raw BER value in respvalue.
+
+.. py:method:: extop_result(self,msgid=ldap.RES_ANY,all=1,timeout=None) -> (respoid,respvalue)
+
+   Wrapper method around :py:meth:`result4()` just for retrieving
+   the result of an extended operation sent before.
+
+
+.. py:method:: LDAPObject.modify(dn, modlist) -> int
+
+.. py:method:: LDAPObject.modify_s(dn, modlist) -> None
+
+.. py:method:: LDAPObject.modify_ext(dn, modlist [, serverctrls=None [, clientctrls=None]]) -> int
+
+.. py:method:: LDAPObject.modify_ext_s(dn, modlist [, serverctrls=None [, clientctrls=None]]) -> None
+
+   Performs an LDAP modify operation on an entry's attributes.
+   The *dn* argument is the distinguished name (DN) of the entry to modify,
+   and *modlist* is a list of modifications to make to that entry.
+
+   Each element in the list *modlist* should be a tuple of the form
+   *(mod_op,mod_type,mod_vals)*,
+   where *mod_op* indicates the operation (one of :py:const:`MOD_ADD`,
+   :py:const:`MOD_DELETE`, or :py:const:`MOD_REPLACE`),
+   *mod_type* is a string indicating the attribute type name, and
+   *mod_vals* is either a string value or a list of string values to add,
+   delete or replace respectively.  For the delete operation, *mod_vals*
+   may be :py:const:`None` indicating that all attributes are to be deleted.
+
+   *serverctrls* and *clientctrls* like described above.
+
+   The asynchronous methods :py:meth:`modify()` and :py:meth:`modify_ext()`
+   return the message ID of the initiated request.
+
+   You might want to look into sub-module :py:mod:`ldap.modlist` for
+   generating *modlist*.
+
+
+.. py:method:: LDAPObject.modrdn(dn, newrdn [, delold=1]) -> int
+
+
+.. py:method::  LDAPObject.modrdn_s(dn, newrdn [, delold=1]) -> None
+
+   Perform a ``modify RDN`` operation, (i.e. a renaming operation).
+   These routines take *dn* (the DN of the entry whose RDN is to be changed,
+   and *newrdn*, the new RDN to give to the entry. The optional parameter
+   *delold* is used to specify whether the old RDN should be kept as an
+   attribute of the entry or not.
+   The asynchronous version returns the initiated message id.
+
+   This operation is emulated by :py:meth:`rename()` and :py:meth:`rename_s()` methods
+   since the modrdn2* routines in the C library are deprecated.
+
+
+.. py:method:: LDAPObject.passwd(user, oldpw, newpw [, serverctrls=None [, clientctrls=None]]) -> int
+
+.. py:method:: LDAPObject.passwd_s(user, oldpw, newpw [, serverctrls=None [, clientctrls=None]]) -> None
+
+   Perform a ``LDAP Password Modify Extended Operation`` operation
+   on the entry specified by *user*.
+   The old password in *oldpw* is replaced with the new
+   password in *newpw* by a LDAP server supporting this operation.
+
+   If *oldpw* is not :py:const:`None` it has to match the old password
+   of the specified *user* which is sometimes used when a user changes
+   his own password.
+
+   *serverctrls* and *clientctrls* like described above.
+
+   The asynchronous version returns the initiated message id.
+
+   .. seealso::
+
+      :rfc:`3062` - LDAP Password Modify Extended Operation
+
+
+
+.. py:method:: LDAPObject.rename(dn, newrdn [, newsuperior=None [, delold=1 [, serverctrls=None [, clientctrls=None]]]]) -> int
+
+.. py:method:: LDAPObject.rename_s(dn, newrdn [, newsuperior=None [, delold=1 [, serverctrls=None [, clientctrls=None]]]]) -> None
+
+   Perform a ``Rename`` operation, (i.e. a renaming operation).
+   These routines take *dn* (the DN of the entry whose RDN is to be changed,
+   and *newrdn*, the new RDN to give to the entry.
+   The optional parameter *newsuperior* is used to specify
+   a new parent DN for moving an entry in the tree
+   (not all LDAP servers support this).
+   The optional parameter *delold* is used to specify
+   whether the old RDN should be kept as an attribute of the entry or not.
+
+   *serverctrls* and *clientctrls* like described above.
+
+.. py:method:: LDAPObject.result([msgid=RES_ANY [, all=1 [, timeout=None]]]) -> 2-tuple
+
+   This method is used to wait for and return the result of an operation
+   previously initiated by one of the LDAP *asynchronous* operations
+   (eg :py:meth:`search()`, :py:meth:`modify()`, etc.)
+
+   The *msgid* parameter is the integer identifier returned by that method.
+   The identifier is guaranteed to be unique across an LDAP session,
+   and tells the :py:meth:`result()` method to request the result of that
+   specific operation.
+
+   If a result is desired from any one of the in-progress operations,
+   *msgid* should be specified as the constant :py:const:`RES_ANY`
+   and the method :py:meth:`result2()` should be used instead.
+
+   The *all* parameter only has meaning for :py:meth:`search()` responses
+   and is used to select whether a single entry of the search
+   response should be returned, or to wait for all the results
+   of the search before returning.
+
+   A search response is made up of zero or more search entries
+   followed by a search result. If *all* is 0, search entries will
+   be returned one at a time as they come in, via separate calls
+   to :py:meth:`result()`. If all is 1, the search response will be returned
+   in its entirety, i.e. after all entries and the final search
+   result have been received.
+
+   For *all* set to 0, result tuples
+   trickle in (with the same message id), and with the result types
+   :py:const:`RES_SEARCH_ENTRY` and :py:const:`RES_SEARCH_REFERENCE`,
+   until the final result which has a result type of :py:const:`RES_SEARCH_RESULT`
+   and a (usually) empty data field.  When all is set to 1, only one result is returned,
+   with a result type of RES_SEARCH_RESULT, and all the result tuples
+   listed in the data field.
+
+   The *timeout* parameter is a limit on the number of seconds that the
+   method will wait for a response from the server.
+   If *timeout* is negative (which is the default),
+   the method will wait indefinitely for a response.
+   The timeout can be expressed as a floating-point value, and
+   a value of :py:const:`0` effects a poll.
+   If a timeout does occur, a :py:exc:`ldap.TIMEOUT` exception is raised,
+   unless polling, in which case ``(None, None)`` is returned.
+
+   The :py:meth:`result()` method returns a tuple of the form
+   ``(result-type, result-data)``.
+   The first element, ``result-type`` is a string, being one of
+   these module constants:
+   :py:const:`RES_BIND`, :py:const:`RES_SEARCH_ENTRY`,
+   :py:const:`RES_SEARCH_REFERENCE`, :py:const:`RES_SEARCH_RESULT`,
+   :py:const:`RES_MODIFY`, :py:const:`RES_ADD`, :py:const:`RES_DELETE`,
+   :py:const:`RES_MODRDN`, or :py:const:`RES_COMPARE`.
+
+   If *all* is :py:const:`0`, one response at a time is returned on
+   each call to :py:meth:`result()`, with termination indicated by
+   ``result-data`` being an empty list.
+
+   See :py:meth:`search()` for a description of the search result's
+   ``result-data``, otherwise the ``result-data`` is normally meaningless.
+
+
+
+.. py:method:: LDAPObject.result2([msgid=RES_ANY [, all=1 [, timeout=None]]]) -> 3-tuple
+
+   This method behaves almost exactly like :py:meth:`result()`. But
+   it returns a 3-tuple also containing the message id of the
+   outstanding LDAP operation a particular result message belongs
+   to. This is especially handy if one needs to dispatch results
+   obtained with ``msgid=``:py:const:`RES_ANY` to several consumer
+   threads which invoked a particular LDAP operation.
+
+
+.. py:method:: LDAPObject.result3([msgid=RES_ANY [, all=1 [, timeout=None]]]) -> 4-tuple
+
+   This method behaves almost exactly like :py:meth:`result2()`. But it
+   returns an extra item in the tuple, the decoded server controls.
+
+.. py:method:: LDAPObject.result4([msgid=RES_ANY [, all=1 [, timeout=None [, add_ctrls=0 [, add_intermediates=0 [, add_extop=0 [, resp_ctrl_classes=None]]]]]]]) -> 6-tuple
+
+   This method behaves almost exactly like :py:meth:`result3()`. But it
+   returns an extra items in the tuple, the decoded results of an extended response.
+
+   The additional arguments are:
+
+   *add_ctrls* (integer flag) specifies whether response controls are returned.
+   
+   add_intermediates (integer flag) specifies whether response controls of
+   intermediate search results are returned.
+
+   *add_extop* (integer flag) specifies whether the response of an
+   extended operation is returned. If using extended operations you should
+   consider using the method :py:meth:`extop_result()` or
+   :py:meth:`extop_s()` instead.
+
+   *resp_ctrl_classes* is a dictionary mapping the OID of a response controls to a
+   :py:class:`ldap.controls.ResponseControl` class of response controls known by the
+   application. So the response control value will be automatically decoded.
+   If :py:const:`None` the global dictionary :py:data:`ldap.controls.KNOWN_RESPONSE_CONTROLS`
+   is used instead.
+
+.. py:method:: LDAPObject.search(base, scope [,filterstr='(objectClass=*)' [, attrlist=None [, attrsonly=0]]]) ->int
+
+.. py:method:: LDAPObject.search_s(base, scope [,filterstr='(objectClass=*)' [, attrlist=None [, attrsonly=0]]]) ->list|None
+
+.. py:method:: LDAPObject.search_st(base, scope [,filterstr='(objectClass=*)' [, attrlist=None [, attrsonly=0 [, timeout=-1]]]]) -> list|None
+
+.. py:method:: LDAPObject.search_ext(base, scope [,filterstr='(objectClass=*)' [, attrlist=None [, attrsonly=0 [, serverctrls=None [, clientctrls=None [, timeout=-1 [, sizelimit=0]]]]]]]) -> int
+
+.. py:method:: LDAPObject.search_ext_s(base, scope [,filterstr='(objectClass=*)' [, attrlist=None [, attrsonly=0 [, serverctrls=None [, clientctrls=None [, timeout=-1 [, sizelimit=0]]]]]]]) -> list|None
+
+   Perform an LDAP search operation, with *base* as the DN of the entry
+   at which to start the search, *scope* being one of
+   :py:const:`SCOPE_BASE` (to search the object itself),
+   :py:const:`SCOPE_ONELEVEL` (to search the object's immediate children), or
+   :py:const:`SCOPE_SUBTREE` (to search the object and all its descendants).
+
+   The *filterstr* argument is a string representation of the filter to apply in
+   the search.
+
+   .. seealso::
+
+      :rfc:`4515` - Lightweight Directory Access Protocol (LDAP): String Representation of Search Filters.
+
+
+   Each result tuple is of the form ``(dn, attrs)``,
+   where *dn* is a string containing the DN (distinguished name) of the
+   entry, and *attrs* is a dictionary containing the attributes associated
+   with the entry. The keys of *attrs* are strings, and the associated
+   values are lists of strings.
+
+   The DN in *dn* is automatically extracted using the underlying libldap
+   function :cfunc:`ldap_get_dn()`, which may raise an exception if the
+   DN is malformed.
+
+   If *attrsonly* is non-zero, the values of *attrs* will be meaningless
+   (they are not transmitted in the result).
+
+   The retrieved attributes can be limited with the *attrlist* parameter.
+   If *attrlist* is :py:const:`None`, all the attributes of each entry are returned.
+
+   *serverctrls* and *clientctrls* like described above.
+
+   The synchronous form with timeout, :py:meth:`search_st()` or :py:meth:`search_ext_s()`,
+   will block for at most *timeout* seconds (or indefinitely if *timeout*
+   is negative). A :py:exc:`ldap.TIMEOUT` exception is raised if no result is received
+   within the specified time.
+
+   The amount of search results retrieved can be limited with the
+   *sizelimit* parameter when using :py:meth:`search_ext()`
+   or :py:meth:`search_ext_s()` (client-side search limit). If non-zero
+   not more than *sizelimit* results are returned by the server.
+
+
+
+.. py:method:: LDAPObject.start_tls_s() -> None
+
+   Negotiate TLS with server. The ``version`` attribute must have been
+   set to :py:const:`VERSION3` (which it is by default) before calling this method.
+   If TLS could not be started an exception will be raised.
+
+  .. seealso::
+
+    :rfc:`2830` - Lightweight Directory Access Protocol (v3): Extension for Transport Layer Security
+
+
+
+.. py:method:: LDAPObject.unbind() -> int
+
+.. py:method:: LDAPObject.unbind_s() -> None
+
+.. py:method:: LDAPObject.unbind_ext([, serverctrls=None [, clientctrls=None]]) -> int
+
+.. py:method:: LDAPObject.unbind_ext_s([, serverctrls=None [, clientctrls=None]]) -> None
+
+   This call is used to unbind from the directory, terminate the
+   current association, and free resources. Once called, the connection to the
+   LDAP server is closed and the LDAP object is marked invalid.
+   Further invocation of methods on the object will yield exceptions.
+
+   *serverctrls* and *clientctrls* like described above.
+
+   These methods are all synchronous in nature.
+
+
+.. py:method:: LDAPObject.whoami_s() -> string
+
+   This synchronous method implements the LDAP "Who Am I?"
+   extended operation.
+
+   It is useful for finding out to find out which identity
+   is assumed by the LDAP server after a SASL bind.
+
+   .. seealso::
+
+      :rfc:`4532` - Lightweight Directory Access Protocol (LDAP) "Who am I?" Operation
+
+
+Connection-specific LDAP options
+--------------------------------
+
+.. py:method:: LDAPObject.get_option(option) -> int|string
+
+   This method returns the value of the LDAPObject option
+   specified by *option*.
+
+
+.. py:method:: LDAPObject.set_option(option, invalue) -> None
+
+   This method sets the value of the LDAPObject option
+   specified by *option* to *invalue*.
+
+
+Object attributes
+-----------------
+
+If the underlying library provides enough information,
+each LDAP object will also have the following attributes.
+These attributes are mutable unless described as read-only.
+
+.. py:attribute:: LDAPObject.deref -> int
+
+   Controls whether aliases are automatically dereferenced.
+   This must be one of :py:const:`DEREF_NEVER`, :py:const:`DEREF_SEARCHING`,
+   :py:const:`DEREF_FINDING` or :py:const:`DEREF_ALWAYS`.
+   This option is mapped to option constant :py:const:`OPT_DEREF`
+   and used in the underlying OpenLDAP client lib.
+
+
+.. py:attribute:: LDAPObject.network_timeout -> int
+
+   Limit on waiting for a network response, in seconds.
+   Defaults to :py:const:`NO_LIMIT`.
+   This option is mapped to option constant :py:const:`OPT_NETWORK_TIMEOUT`
+   and used in the underlying OpenLDAP client lib.
+
+
+.. py:attribute:: LDAPObject.protocol_version -> int
+
+   Version of LDAP in use (either :py:const:`VERSION2` for LDAPv2
+   or :py:const:`VERSION3` for LDAPv3).
+   This option is mapped to option constant :py:const:`OPT_PROTOCOL_VERSION`
+   and used in the underlying OpenLDAP client lib.
+
+   .. note::
+
+      It is highly recommended to set the protocol version after establishing
+      a LDAP connection with :py:func:`ldap.initialize()` and before submitting
+      the first request.
+
+
+.. py:attribute:: LDAPObject.sizelimit -> int
+
+   Limit on size of message to receive from server.
+   Defaults to :py:const:`NO_LIMIT`.
+   This option is mapped to option constant :py:const:`OPT_SIZELIMIT`
+   and used in the underlying OpenLDAP client lib. Its use is deprecated
+   in favour of *sizelimit* parameter when using :py:meth:`search_ext()`.
+
+
+.. py:attribute:: LDAPObject.timelimit -> int
+
+   Limit on waiting for any response, in seconds.
+   Defaults to :py:const:`NO_LIMIT`.
+   This option is mapped to option constant :py:const:`OPT_TIMELIMIT`
+   and used in the underlying OpenLDAP client lib. Its use is deprecated
+   in favour of using :py:attr:`timeout`.
+
+
+.. py:attribute:: LDAPObject.timeout -> int
+
+   Limit on waiting for any response, in seconds.
+   Defaults to :py:const:`NO_LIMIT`.
+   This option is used in the wrapper module.
+
+
+.. _ldap-example:
+
+Example
+=======
+
+The following example demonstrates how to open a connection to an
+LDAP server using the :py:mod:`ldap` module and invoke a synchronous
+subtree search.
+
+>>> import ldap
+>>> l = ldap.initialize('ldap://localhost:1390')
+>>> l.search_s('ou=Testing,dc=stroeder,dc=de',ldap.SCOPE_SUBTREE,'(cn=fred*)',['cn','mail'])
+[('cn=Fred Feuerstein,ou=Testing,dc=stroeder,dc=de', {'cn': ['Fred Feuerstein']})]
+>>> r = l.search_s('ou=Testing,dc=stroeder,dc=de',ldap.SCOPE_SUBTREE,'(objectClass=*)',['cn','mail'])
+>>> for dn,entry in r:
+>>>   print 'Processing',repr(dn)
+>>>   handle_ldap_entry(entry)
+

+ 126 - 0
desktop/core/ext-py/python-ldap-2.4.6/Doc/ldapurl.rst

@@ -0,0 +1,126 @@
+.. % $Id: ldapurl.rst,v 1.9 2011/07/22 13:27:01 stroeder Exp $
+
+###################################
+:py:mod:`ldapurl` LDAP URL handling
+###################################
+
+.. py:module:: ldapurl
+   :synopsis: Parses and generates LDAP URLs
+.. moduleauthor:: python-ldap project (see http://www.python-ldap.org/)
+
+
+This module parses and generates LDAP URLs.    It is implemented in pure Python
+and does not rely on any  non-standard modules. Therefore it can be used stand-
+alone without  the rest of the python-ldap package.    Compability note: This
+module has been solely tested on Python 2.x and above.
+
+.. seealso::
+
+   :rfc:`4516` - The LDAP URL Format
+
+
+Constants
+=========
+
+The :mod:`ldapurl` module exports the following constants:
+
+.. py:data:: SEARCH_SCOPE
+
+   This dictionary maps a search scope string identifier  to the corresponding
+   integer value used with search operations  in :mod:`ldap`.
+
+
+.. py:data:: SEARCH_SCOPE_STR
+
+   This dictionary is the inverse to :const:`SEARCH_SCOPE`. It  maps a search scope
+   integer value to the corresponding string identifier  used in a LDAP URL string
+   representation.
+
+
+.. py:data:: LDAP_SCOPE_BASE
+
+
+.. py:data:: LDAP_SCOPE_ONELEVEL
+
+
+.. py:data:: LDAP_SCOPE_SUBTREE
+
+
+Functions
+=========
+
+.. autofunction:: ldapurl.isLDAPUrl
+
+
+.. autofunction:: ldapurl.ldapUrlEscape
+
+
+Classes
+=======
+
+.. _ldapurl-ldapurl:
+
+LDAP URLs
+^^^^^^^^^
+
+A :py:class:`LDAPUrl` object represents a complete LDAP URL.
+
+.. autoclass:: ldapurl.LDAPUrl 
+
+
+LDAP URL extensions
+^^^^^^^^^^^^^^^^^^^
+
+A :py:class:`LDAPUrlExtension` object represents a single LDAP URL extension
+whereas :py:class:`LDAPUrlExtensions` represents a list of LDAP URL extensions.
+
+
+.. _ldapurl-ldapurlextension:
+
+.. autoclass:: ldapurl.LDAPUrlExtension
+
+.. _ldapurl-ldapurlextensions:
+
+.. autoclass:: ldapurl.LDAPUrlExtensions
+
+
+.. _ldapurl-example:
+
+Example
+^^^^^^^
+
+Important security advice:
+For security reasons you shouldn't specify passwords in LDAP URLs
+unless you really know what you are doing.
+
+The following example demonstrates how to parse a LDAP URL
+with :mod:`ldapurl` module.
+
+
+>>> import ldapurl
+>>> ldap_url = ldapurl.LDAPUrl('ldap://localhost:1389/dc=stroeder,dc=com?cn,mail???bindname=cn=Michael%2cdc=stroeder%2cdc=com,X-BINDPW=secret')
+>>> # Using the parsed LDAP URL by reading the class attributes
+>>> ldap_url.dn
+'dc=stroeder,dc=com'
+>>> ldap_url.hostport
+'localhost:1389'
+>>> ldap_url.attrs
+['cn','mail']
+>>> ldap_url.filterstr
+'(objectclass=*)'
+>>> ldap_url.who
+'cn=Michael,dc=stroeder,dc=com'
+>>> ldap_url.cred
+'secret'
+>>> ldap_url.scope
+0
+
+
+The following example demonstrates how to generate a LDAP URL
+with \module{ldapurl} module.
+
+>>> import ldapurl
+>>> ldap_url = ldapurl.LDAPUrl(hostport='localhost:1389',dn='dc=stroeder,dc=com',attrs=['cn','mail'],who='cn=Michael,dc=stroeder,dc=com',cred='secret')
+>>> ldap_url.unparse()
+'ldap://localhost:1389/dc=stroeder,dc=com?cn,mail?base?(objectclass=*)?bindname=cn=Michael%2Cdc=stroeder%2Cdc=com,X-BINDPW=secret'
+

+ 83 - 0
desktop/core/ext-py/python-ldap-2.4.6/Doc/ldif.rst

@@ -0,0 +1,83 @@
+.. % $Id: ldif.rst,v 1.8 2011/09/14 18:29:18 stroeder Exp $
+
+#####################################
+:mod:`ldif` LDIF parser and generator
+#####################################
+
+.. py:module:: ldif
+   :synopsis: Parses and generates LDIF files
+.. moduleauthor:: python-ldap project (see http://www.python-ldap.org/)
+
+
+This module parses and generates LDAP data in the format LDIF.    It is
+implemented in pure Python and does not rely on any non-standard modules.
+Therefore it can be used stand-alone without the rest of the python-ldap
+package.
+
+.. seealso::
+
+   :rfc:`2849` - The LDAP Data Interchange Format (LDIF) - Technical Specification
+
+
+Functions
+^^^^^^^^^
+
+.. autofunction:: ldif.CreateLDIF
+
+.. autofunction:: ldif.ParseLDIF
+
+
+Classes
+^^^^^^^
+
+.. autoclass:: ldif.LDIFWriter
+
+.. autoclass:: ldif.LDIFParser
+
+.. autoclass:: LDIFRecordList
+
+.. autoclass:: LDIFCopy
+
+
+.. _ldif-example:
+
+Example
+^^^^^^^
+
+The following example demonstrates how to write LDIF output
+of an LDAP entry with :mod:`ldif` module.
+
+>>> import sys,ldif
+>>> entry={'objectClass':['top','person'],'cn':['Michael Stroeder'],'sn':['Stroeder']}
+>>> dn='cn=Michael Stroeder,ou=Test'
+>>> ldif_writer=ldif.LDIFWriter(sys.stdout)
+>>> ldif_writer.unparse(dn,entry)
+dn: cn=Michael Stroeder,ou=Test
+cn: Michael Stroeder
+objectClass: top
+objectClass: person
+sn: Stroeder
+
+
+The following example demonstrates how to parse an LDIF file
+with :mod:`ldif` module, skip some entries and write the result to stdout. ::
+
+   import sys
+   from ldif import LDIFParser,LDIFWriter
+
+   SKIP_DN = ["uid=foo,ou=People,dc=example,dc=com", 
+      "uid=bar,ou=People,dc=example,dc=com"]
+
+   class MyLDIF(LDIFParser):
+      def __init__(self,input,output):
+         LDIFParser.__init__(self,input)
+         self.writer = LDIFWriter(output)
+
+      def handle(self,dn,entry):
+         if dn in SKIP_DN:
+           return
+         self.writer.unparse(dn,entry)
+
+   parser = MyLDIF(open("input.ldif", 'rb'), sys.stdout)
+   parser.parse()
+

+ 28 - 0
desktop/core/ext-py/python-ldap-2.4.6/INSTALL

@@ -0,0 +1,28 @@
+------------------------------
+Installing python-ldap
+------------------------------
+
+Prerequisites:
+
+  Required:
+
+    - Python 2.3 or newer (see http://www.python.org)
+
+    - OpenLDAP 2.4.11+ client libs (see http://www.openldap.org)
+      It is not possible and not supported
+      by the python-ldap project to build with prior versions.
+
+  Optional dependencies of OpenLDAP libs:
+
+    - Cyrus SASL 2.1.x or newer (see http://asg.web.cmu.edu/sasl/sasl-library.html)
+
+    - OpenSSL 0.9.7 or newer (see http://www.openssl.org)
+    
+    - MIT Kerberos or heimdal libs
+
+Quick build instructions:
+    edit setup.cfg (see Build/ for platform-specific examples)
+    python setup.py build
+    python setup.py install
+
+$Id: INSTALL,v 1.10 2011/04/02 22:30:55 stroeder Exp $

+ 13 - 0
desktop/core/ext-py/python-ldap-2.4.6/LICENCE

@@ -0,0 +1,13 @@
+
+The python-ldap package is distributed under Python-style license.
+
+Standard disclaimer:
+   This software is made available by the author(s) to the public for free
+   and "as is".  All users of this free software are solely and entirely
+   responsible for their own choice and use of this software for their
+   own purposes.  By using this software, each user agrees that the
+   author(s) shall not be liable for damages of any kind in relation to
+   its use or performance. The author(s) do not warrant that this software
+   is fit for any purpose.
+
+$Id: LICENCE,v 1.1 2002/09/18 18:51:22 stroeder Exp $

+ 4 - 0
desktop/core/ext-py/python-ldap-2.4.6/Lib/.cvsignore

@@ -0,0 +1,4 @@
+*.pyc
+*.pyo
+*.bck
+*~

+ 297 - 0
desktop/core/ext-py/python-ldap-2.4.6/Lib/dsml.py

@@ -0,0 +1,297 @@
+"""
+dsml - generate and parse DSMLv1 data
+(see http://www.oasis-open.org/committees/dsml/)
+
+See http://www.python-ldap.org/ for details.
+
+$Id: dsml.py,v 1.23 2011/11/27 15:27:00 stroeder Exp $
+
+Python compability note:
+Tested with Python 2.0+.
+"""
+
+__version__ = '2.4.6'
+
+import string,base64
+
+
+special_entities = (
+  ('&','&amp;'),
+  ('<','&lt;'),
+  ('"','&quot;'),
+  ("'",'&apos;'),
+)
+
+
+def replace_char(s):
+  for char,entity in special_entities:
+    s = string.replace(s,char,entity)
+  return s
+
+
+class DSMLWriter:
+  """
+  Class for writing LDAP entry records to a DSMLv1 file.
+
+  Arguments:
+
+  f
+      File object for output.
+  base64_attrs
+      Attribute types to be base64-encoded.
+  dsml_comment
+      Text placed in comment lines behind <dsml:dsml>.
+  indent
+      String used for indentiation of next nested level.
+  """
+
+  def __init__(
+    self,f,base64_attrs=[],dsml_comment='',indent='    '
+  ):
+    self._output_file = f
+    self._base64_attrs = {}.fromkeys(map(string.lower,base64_attrs))
+    self._dsml_comment = dsml_comment
+    self._indent = indent
+
+  def _needs_base64_encoding(self,attr_type,attr_value):
+    if self._base64_attrs:
+      return self._base64_attrs.has_key(string.lower(attr_type))
+    else:
+      try:
+        unicode(attr_value,'utf-8')
+      except UnicodeError:
+        return 1
+      else:
+        return 0
+
+  def writeHeader(self):
+    """
+    Write the header
+    """
+    self._output_file.write('\n'.join([
+        '<?xml version="1.0" encoding="UTF-8"?>',
+        '<!DOCTYPE root PUBLIC "dsml.dtd" "http://www.dsml.org/1.0/dsml.dtd">',
+        '<dsml:dsml xmlns:dsml="http://www.dsml.org/DSML">',
+        '%s<dsml:directory-entries>\n' % (self._indent),
+      ])
+    )
+    if self._dsml_comment:
+      self._output_file.write('%s<!--\n' % (self._indent))
+      self._output_file.write('%s%s\n' % (self._indent,self._dsml_comment))
+      self._output_file.write('%s-->\n' % (self._indent))
+
+  def writeFooter(self):
+    """
+    Write the footer
+    """
+    self._output_file.write('%s</dsml:directory-entries>\n' % (self._indent))
+    self._output_file.write('</dsml:dsml>\n')
+
+  def unparse(self,dn,entry):
+    return self.writeRecord(dn,entry)
+
+  def writeRecord(self,dn,entry):
+    """
+    dn
+          string-representation of distinguished name
+    entry
+          dictionary holding the LDAP entry {attr:data}
+    """
+
+    # Write line dn: first
+    self._output_file.write(
+      '%s<dsml:entry dn="%s">\n' % (
+        self._indent*2,replace_char(dn)
+      )
+    )
+
+    objectclasses = entry.get('objectclass',entry.get('objectClass',[]))
+
+    self._output_file.write('%s<dsml:objectclass>\n' % (self._indent*3))
+    for oc in objectclasses:
+      self._output_file.write('%s<dsml:oc-value>%s</dsml:oc-value>\n' % (self._indent*4,oc))
+    self._output_file.write('%s</dsml:objectclass>\n' % (self._indent*3))
+
+    attr_types = entry.keys()[:]
+    try:
+      attr_types.remove('objectclass')
+      attr_types.remove('objectClass')
+    except ValueError:
+      pass
+    attr_types.sort()
+    for attr_type in attr_types:
+      self._output_file.write('%s<dsml:attr name="%s">\n' % (self._indent*3,attr_type))
+      for attr_value_item in entry[attr_type]:
+        needs_base64_encoding = self._needs_base64_encoding(
+          attr_type,attr_value_item
+        )
+        if needs_base64_encoding:
+          attr_value_item = base64.encodestring(attr_value_item)
+        else:
+          attr_value_item = replace_char(attr_value_item)
+        self._output_file.write('%s<dsml:value%s>\n' % (
+            self._indent*4,
+            ' encoding="base64"'*needs_base64_encoding
+          )
+        )
+        self._output_file.write('%s%s\n' % (
+            self._indent*5,
+            attr_value_item
+          )
+        )
+        self._output_file.write('%s</dsml:value>\n' % (
+            self._indent*4,
+          )
+        )
+      self._output_file.write('%s</dsml:attr>\n' % (self._indent*3))
+    self._output_file.write('%s</dsml:entry>\n' % (self._indent*2))
+    return
+
+
+try:
+
+  import xml.sax,xml.sax.handler
+
+except ImportError:
+  pass
+
+else:
+
+  class DSMLv1Handler(xml.sax.handler.ContentHandler):
+    """
+    Content handler class for DSMLv1
+    """
+
+    def __init__(self,parser_instance):
+      self._parser_instance = parser_instance
+      xml.sax.handler.ContentHandler.__init__(self)
+
+    def startDocument(self):
+      pass
+
+    def endDocument(self):
+      pass
+
+    def startElement(self,raw_name,attrs):
+      assert raw_name.startswith(''),'Illegal name'
+      name = raw_name[5:]
+      if name=='dsml':
+        pass
+      elif name=='directory-entries':
+        self._parsing_entries = 1
+      elif name=='entry':
+        self._dn = attrs['dn']
+        self._entry = {}
+      elif name=='attr':
+        self._attr_type = attrs['name'].encode('utf-8')
+        self._attr_values = []
+      elif name=='value':
+        self._attr_value = ''
+        self._base64_encoding = attrs.get('encoding','').lower()=='base64'
+      # Handle object class tags
+      elif name=='objectclass':
+        self._object_classes = []
+      elif name=='oc-value':
+        self._oc_value = ''
+      # Unhandled tags
+      else:
+        raise ValueError,'Unknown tag %s' % (raw_name)
+
+    def endElement(self,raw_name):
+      assert raw_name.startswith('dsml:'),'Illegal name'
+      name = raw_name[5:]
+      if name=='dsml':
+        pass
+      elif name=='directory-entries':
+        self._parsing_entries = 0
+      elif name=='entry':
+        self._parser_instance.handle(self._dn,self._entry)
+        del self._dn
+        del self._entry
+      elif name=='attr':
+        self._entry[self._attr_type] = self._attr_values
+        del self._attr_type
+        del self._attr_values
+      elif name=='value':
+        if self._base64_encoding:
+          attr_value = base64.decodestring(self._attr_value.strip())
+        else:
+          attr_value = self._attr_value.strip().encode('utf-8')
+        self._attr_values.append(attr_value)
+        del attr_value
+        del self._attr_value
+        del self._base64_encoding
+      # Handle object class tags
+      elif name=='objectclass':
+        self._entry['objectClass'] = self._object_classes
+        del self._object_classes
+      elif name=='oc-value':
+        self._object_classes.append(self._oc_value.strip().encode('utf-8'))
+        del self._oc_value
+      # Unhandled tags
+      else:
+        raise ValueError,'Unknown tag %s' % (raw_name)
+
+    def characters(self,ch):
+      if self.__dict__.has_key('_oc_value'):
+        self._oc_value = self._oc_value + ch
+      elif self.__dict__.has_key('_attr_value'):
+        self._attr_value = self._attr_value + ch
+      else:
+        pass
+
+
+  class DSMLParser:
+    """
+    Base class for a DSMLv1 parser. Applications should sub-class this
+    class and override method handle() to implement something meaningful.
+
+    Public class attributes:
+
+    records_read
+        Counter for records processed so far
+
+    Arguments:
+
+    input_file
+        File-object to read the DSMLv1 input from
+    ignored_attr_types
+        Attributes with these attribute type names will be ignored.
+    max_entries
+        If non-zero specifies the maximum number of entries to be
+        read from f.
+    line_sep
+        String used as line separator
+    """
+
+    def __init__(
+      self,
+      input_file,
+      ContentHandlerClass,
+      ignored_attr_types=None,
+      max_entries=0,
+    ):
+      self._input_file = input_file
+      self._max_entries = max_entries
+      self._ignored_attr_types = {}.fromkeys(map(string.lower,(ignored_attr_types or [])))
+      self._current_record = None,None
+      self.records_read = 0
+      self._parser = xml.sax.make_parser()
+      self._parser.setFeature(xml.sax.handler.feature_namespaces,0)
+      content_handler = ContentHandlerClass(self)
+      self._parser.setContentHandler(content_handler)
+
+    def handle(self,*args,**kwargs):
+      """
+      Process a single DSMLv1 entry record. This method should be
+      implemented by applications using DSMLParser.
+      """
+      import pprint
+      pprint.pprint(args)
+      pprint.pprint(kwargs)
+
+    def parse(self):
+      """
+      Continously read and parse DSML records
+      """
+      self._parser.parse(self._input_file)

+ 4 - 0
desktop/core/ext-py/python-ldap-2.4.6/Lib/ldap/.cvsignore

@@ -0,0 +1,4 @@
+*.pyc
+*.pyo
+*.bck
+*~

+ 88 - 0
desktop/core/ext-py/python-ldap-2.4.6/Lib/ldap/__init__.py

@@ -0,0 +1,88 @@
+"""
+ldap - base module
+
+See http://www.python-ldap.org/ for details.
+
+$Id: __init__.py,v 1.78 2011/11/27 15:27:00 stroeder Exp $
+"""
+
+# This is also the overall release version number
+
+__version__ = '2.4.6'
+
+import sys
+
+if __debug__:
+  # Tracing is only supported in debugging mode
+  import traceback
+  _trace_level = 0
+  _trace_file = sys.stderr
+  _trace_stack_limit = None
+
+from _ldap import *
+
+class DummyLock:
+  """Define dummy class with methods compatible to threading.Lock"""
+  def __init__(self):
+    pass
+  def acquire(self):
+    pass
+  def release(self):
+    pass
+
+try:
+  # Check if Python installation was build with thread support
+  import thread
+except ImportError:
+  LDAPLockBaseClass = DummyLock
+else:
+  import threading
+  LDAPLockBaseClass = threading.Lock
+
+
+class LDAPLock:
+  """
+  Mainly a wrapper class to log all locking events.
+  Note that this cumbersome approach with _lock attribute was taken
+  since threading.Lock is not suitable for sub-classing.
+  """
+  _min_trace_level = 3
+
+  def __init__(self,lock_class=None,desc=''):
+    """
+    lock_class
+        Class compatible to threading.Lock
+    desc
+        Description shown in debug log messages
+    """
+    self._desc = desc
+    self._lock = (lock_class or LDAPLockBaseClass)()
+
+  def acquire(self):
+    if __debug__:
+      global _trace_level
+      if _trace_level>=self._min_trace_level:
+        _trace_file.write('***%s %s.acquire()\n' % (self._desc,self.__class__.__name__))
+    return self._lock.acquire()
+
+  def release(self):
+    if __debug__:
+      global _trace_level
+      if _trace_level>=self._min_trace_level:
+        _trace_file.write('***%s %s.release()\n' % (self._desc,self.__class__.__name__))
+    return self._lock.release()
+
+
+# Create module-wide lock for serializing all calls into underlying LDAP lib
+_ldap_module_lock = LDAPLock(desc='Module wide')
+
+from functions import open,initialize,init,get_option,set_option
+
+from ldap.dn import explode_dn,explode_rdn,str2dn,dn2str
+del str2dn
+del dn2str
+
+# More constants
+
+# For compability of 2.3 and 2.4 OpenLDAP API
+OPT_DIAGNOSTIC_MESSAGE = OPT_ERROR_STRING

+ 306 - 0
desktop/core/ext-py/python-ldap-2.4.6/Lib/ldap/async.py

@@ -0,0 +1,306 @@
+"""
+ldap.async - handle async LDAP operations
+
+See http://www.python-ldap.org/ for details.
+
+\$Id: async.py,v 1.32 2011/07/28 08:51:38 stroeder Exp $
+
+Python compability note:
+Tested on Python 2.0+ but should run on Python 1.5.x.
+"""
+
+import ldap
+
+from ldap import __version__
+
+
+_searchResultTypes={
+  ldap.RES_SEARCH_ENTRY:None,
+  ldap.RES_SEARCH_RESULT:None,
+  ldap.RES_SEARCH_REFERENCE:None,
+}
+
+_entryResultTypes={
+  ldap.RES_SEARCH_ENTRY:None,
+  ldap.RES_SEARCH_RESULT:None,
+}
+
+
+class WrongResultType(Exception):
+
+  def __init__(self,receivedResultType,expectedResultTypes):
+    self.receivedResultType = receivedResultType
+    self.expectedResultTypes = expectedResultTypes
+    Exception.__init__(self)
+
+  def __str__(self):
+    return 'Received wrong result type %s (expected one of %s).' % (
+      self.receivedResultType,
+      ', '.join(self.expectedResultTypes),
+    )
+
+
+class AsyncSearchHandler:
+  """
+  Class for stream-processsing LDAP search results
+
+  Arguments:
+
+  l
+    LDAPObject instance
+  """
+
+  def __init__(self,l):
+    self._l = l
+    self._msgId = None
+
+  def startSearch(
+    self,
+    searchRoot,
+    searchScope,
+    filterStr,
+    attrList=None,
+    attrsOnly=0,
+    timeout=-1,
+    sizelimit=0,
+    serverctrls=None,
+    clientctrls=None
+  ):
+    """
+    searchRoot
+        See parameter base of method LDAPObject.search()
+    searchScope
+        See parameter scope of method LDAPObject.search()
+    filterStr
+        See parameter filter of method LDAPObject.search()
+    attrList=None
+        See parameter attrlist of method LDAPObject.search()
+    attrsOnly
+        See parameter attrsonly of method LDAPObject.search()
+    timeout
+        Maximum time the server shall use for search operation
+    sizelimit
+        Maximum number of entries a server should return
+        (request client-side limit)
+    serverctrls
+        list of server-side LDAP controls
+    clientctrls
+        list of client-side LDAP controls
+    """
+    self._msgId = self._l.search_ext(
+      searchRoot,searchScope,filterStr,
+      attrList,attrsOnly,serverctrls,clientctrls,timeout,sizelimit
+    )
+    return # startSearch()
+
+  def preProcessing(self):
+    """
+    Do anything you want after starting search but
+    before receiving and processing results
+    """
+
+  def postProcessing(self):
+    """
+    Do anything you want after receiving and processing results
+    """
+
+  def processResults(self,ignoreResultsNumber=0,processResultsCount=0,timeout=-1):
+    """
+    ignoreResultsNumber
+        Don't process the first ignoreResultsNumber results.
+    processResultsCount
+        If non-zero this parameters indicates the number of results
+        processed is limited to processResultsCount.
+    timeout
+        See parameter timeout of ldap.LDAPObject.result()
+    """
+    self.preProcessing()
+    result_counter = 0
+    end_result_counter = ignoreResultsNumber+processResultsCount
+    go_ahead = 1
+    partial = 0
+    self.beginResultsDropped = 0
+    self.endResultBreak = result_counter
+    try:
+      result_type,result_list = None,None
+      while go_ahead:
+        while result_type is None and not result_list:
+          result_type,result_list,result_msgid,result_serverctrls = self._l.result3(self._msgId,0,timeout)
+        if not result_list:
+          break
+        if not _searchResultTypes.has_key(result_type):
+          raise WrongResultType(result_type,_searchResultTypes.keys())
+        # Loop over list of search results
+        for result_item in result_list:
+          if result_counter<ignoreResultsNumber:
+            self.beginResultsDropped = self.beginResultsDropped+1
+          elif processResultsCount==0 or result_counter<end_result_counter:
+            self._processSingleResult(result_type,result_item)
+          else:
+            go_ahead = 0 # break-out from while go_ahead
+            partial = 1
+            break # break-out from this for-loop
+          result_counter = result_counter+1
+        result_type,result_list = None,None
+        self.endResultBreak = result_counter
+    finally:
+      if partial and self._msgId!=None:
+        self._l.abandon(self._msgId)
+    self.postProcessing()
+    return partial # processResults()
+
+  def _processSingleResult(self,resultType,resultItem):
+    """
+    Process single entry
+
+    resultType
+        result type
+    resultItem
+        Single item of a result list
+    """
+    pass
+
+
+class List(AsyncSearchHandler):
+  """
+  Class for collecting all search results.
+
+  This does not seem to make sense in the first place but think
+  of retrieving exactly a certain portion of the available search
+  results.
+  """
+
+  def __init__(self,l):
+    AsyncSearchHandler.__init__(self,l)
+    self.allResults = []
+
+  def _processSingleResult(self,resultType,resultItem):
+    self.allResults.append((resultType,resultItem))
+
+
+class Dict(AsyncSearchHandler):
+  """
+  Class for collecting all search results into a dictionary {dn:entry}
+  """
+
+  def __init__(self,l):
+    AsyncSearchHandler.__init__(self,l)
+    self.allEntries = {}
+
+  def _processSingleResult(self,resultType,resultItem):
+    if _entryResultTypes.has_key(resultType):
+      # Search continuations are ignored
+      dn,entry = resultItem
+      self.allEntries[dn] = entry
+
+
+class IndexedDict(Dict):
+  """
+  Class for collecting all search results into a dictionary {dn:entry}
+  and maintain case-sensitive equality indexes to entries
+  """
+
+  def __init__(self,l,indexed_attrs=None):
+    Dict.__init__(self,l)
+    self.indexed_attrs = indexed_attrs or tuple()
+    self.index = {}.fromkeys(self.indexed_attrs,{})
+
+  def _processSingleResult(self,resultType,resultItem):
+    if _entryResultTypes.has_key(resultType):
+      # Search continuations are ignored
+      dn,entry = resultItem
+      self.allEntries[dn] = entry
+      for a in self.indexed_attrs:
+        if entry.has_key(a):
+          for v in entry[a]:
+            try:
+              self.index[a][v].append(dn)
+            except KeyError:
+              self.index[a][v] = [ dn ]
+
+
+class FileWriter(AsyncSearchHandler):
+  """
+  Class for writing a stream of LDAP search results to a file object
+
+  Arguments:
+  l
+    LDAPObject instance
+  f
+    File object instance where the LDIF data is written to
+  """
+
+  def __init__(self,l,f,headerStr='',footerStr=''):
+    AsyncSearchHandler.__init__(self,l)
+    self._f = f
+    self.headerStr = headerStr
+    self.footerStr = footerStr
+
+  def preProcessing(self):
+    """
+    The headerStr is written to output after starting search but
+    before receiving and processing results.
+    """
+    self._f.write(self.headerStr)
+
+  def postProcessing(self):
+    """
+    The footerStr is written to output after receiving and
+    processing results.
+    """
+    self._f.write(self.footerStr)
+
+
+class LDIFWriter(FileWriter):
+  """
+  Class for writing a stream LDAP search results to a LDIF file
+
+  Arguments:
+
+  l
+    LDAPObject instance
+  writer_obj
+    Either a file-like object or a ldif.LDIFWriter instance used for output
+  """
+
+  def __init__(self,l,writer_obj,headerStr='',footerStr=''):
+    import ldif
+    if isinstance(writer_obj,ldif.LDIFWriter):
+      self._ldif_writer = writer_obj
+    else:
+      self._ldif_writer = ldif.LDIFWriter(writer_obj)
+    FileWriter.__init__(self,l,self._ldif_writer._output_file,headerStr,footerStr)
+
+  def _processSingleResult(self,resultType,resultItem):
+    if _entryResultTypes.has_key(resultType):
+      # Search continuations are ignored
+      dn,entry = resultItem
+      self._ldif_writer.unparse(dn,entry)
+
+
+class DSMLWriter(FileWriter):
+  """
+  Class for writing a stream LDAP search results to a DSML file
+
+  Arguments:
+
+  l
+    LDAPObject instance
+  writer_obj
+    Either a file-like object or a dsml.DSMLWriter instance used for output
+  """
+
+  def __init__(self,l,writer_obj,headerStr='',footerStr=''):
+    import dsml
+    if isinstance(writer_obj,dsml.DSMLWriter):
+      self._dsml_writer = writer_obj
+    else:
+      self._dsml_writer = dsml.DSMLWriter(writer_obj)
+    FileWriter.__init__(self,l,self._dsml_writer._output_file,headerStr,footerStr)
+
+  def _processSingleResult(self,resultType,resultItem):
+    if _entryResultTypes.has_key(resultType):
+      # Search continuations are ignored
+      dn,entry = resultItem
+      self._dsml_writer.unparse(dn,entry)
+

+ 125 - 0
desktop/core/ext-py/python-ldap-2.4.6/Lib/ldap/cidict.py

@@ -0,0 +1,125 @@
+"""
+This is a convenience wrapper for dictionaries
+returned from LDAP servers containing attribute
+names of variable case.
+
+See http://www.python-ldap.org/ for details.
+
+$Id: cidict.py,v 1.13 2009/04/17 14:34:34 stroeder Exp $
+"""
+
+__version__ = """$Revision: 1.13 $"""
+
+from UserDict import UserDict
+from string import lower
+
+class cidict(UserDict):
+  """
+  Case-insensitive but case-respecting dictionary.
+  """
+
+  def __init__(self,default=None):
+    self._keys = {}
+    UserDict.__init__(self,{})
+    self.update(default or {})
+
+  def __getitem__(self,key):
+    return self.data[lower(key)]
+
+  def __setitem__(self,key,value):
+    lower_key = lower(key)
+    self._keys[lower_key] = key
+    self.data[lower_key] = value
+
+  def __delitem__(self,key):
+    lower_key = lower(key)
+    del self._keys[lower_key]
+    del self.data[lower_key]
+
+  def update(self,dict):
+    for key in dict.keys():
+      self[key] = dict[key]
+
+  def has_key(self,key):
+    return UserDict.has_key(self,lower(key))
+
+  def __contains__(self,key):
+    return self.has_key(key)
+
+  def get(self,key,failobj=None):
+    try:
+      return self[key]
+    except KeyError:
+      return failobj
+
+  def keys(self):
+    return self._keys.values()
+
+  def items(self):
+    result = []
+    for k in self._keys.values():
+      result.append((k,self[k]))
+    return result
+
+
+def strlist_minus(a,b):
+  """
+  Return list of all items in a which are not in b (a - b).
+  a,b are supposed to be lists of case-insensitive strings.
+  """
+  temp = cidict()
+  for elt in b:
+    temp[elt] = elt
+  result = [
+    elt
+    for elt in a
+    if not temp.has_key(elt)
+  ]
+  return result
+
+
+def strlist_intersection(a,b):
+  """
+  Return intersection of two lists of case-insensitive strings a,b.
+  """
+  temp = cidict()
+  for elt in a:
+    temp[elt] = elt
+  result = [
+    temp[elt]
+    for elt in b
+    if temp.has_key(elt)
+  ]
+  return result
+
+
+def strlist_union(a,b):
+  """
+  Return union of two lists of case-insensitive strings a,b.
+  """
+  temp = cidict()
+  for elt in a:
+    temp[elt] = elt
+  for elt in b:
+    temp[elt] = elt
+  return temp.values()
+
+
+if __debug__ and __name__ == '__main__':
+  x = { 'AbCDeF' : 123 }
+  cix = cidict(x)
+  assert cix["ABCDEF"] == 123
+  assert cix.get("ABCDEF",None) == 123
+  assert cix.get("not existent",None) is None
+  cix["xYZ"] = 987
+  assert cix["XyZ"] == 987
+  assert cix.get("XyZ",None) == 987
+  cix_keys = cix.keys()
+  cix_keys.sort()
+  assert cix_keys==['AbCDeF','xYZ'],ValueError(repr(cix_keys))
+  cix_items = cix.items()
+  cix_items.sort()
+  assert cix_items==[('AbCDeF',123),('xYZ',987)],ValueError(repr(cix_items))
+  del cix["abcdEF"]
+  assert not cix._keys.has_key("abcdef")
+  assert not cix.has_key("AbCDef")

+ 148 - 0
desktop/core/ext-py/python-ldap-2.4.6/Lib/ldap/controls/__init__.py

@@ -0,0 +1,148 @@
+# -*- coding: utf-8 -*-
+"""
+controls.py - support classes for LDAP controls
+
+See http://www.python-ldap.org/ for details.
+
+$Id: __init__.py,v 1.6 2011/07/22 13:27:02 stroeder Exp $
+
+Description:
+The ldap.controls module provides LDAPControl classes.
+Each class provides support for a certain control.
+"""
+
+from ldap import __version__
+
+__all__ = [
+  # control OID to class registy
+  'KNOWN_CONTROLS',
+  # Classes
+  'AssertionControl',
+  'BooleanControl',
+  'LDAPControl',
+  'ManageDSAITControl',
+  'MatchedValuesControl',
+  'RelaxRulesControl',
+  'RequestControl',
+  'ResponseControl',
+  'SimplePagedResultsControl',
+  'ValueLessRequestControl',
+  # Functions
+  'RequestControlTuples',
+  'DecodeControlTuples',
+]
+
+KNOWN_RESPONSE_CONTROLS = {}
+
+import _ldap,ldap
+
+
+class RequestControl:
+  """
+  Base class for all request controls
+  
+  controlType
+      OID as string of the LDAPv3 extended request control
+  criticality
+      sets the criticality of the control (boolean)
+  encodedControlValue
+      control value of the LDAPv3 extended request control
+      (here it is the BER-encoded ASN.1 control value)
+  """
+
+  def __init__(self,controlType=None,criticality=False,encodedControlValue=None):
+    self.controlType = controlType
+    self.criticality = criticality
+    self.encodedControlValue = encodedControlValue
+
+  def encodeControlValue(self):
+    """
+    sets class attribute encodedControlValue to the BER-encoded ASN.1
+    control value composed by class attributes set before
+    """
+    return self.encodedControlValue
+
+
+class ResponseControl:
+  """
+  Base class for all response controls
+
+  controlType
+      OID as string of the LDAPv3 extended response control
+  criticality
+      sets the criticality of the received control (boolean)
+  """
+
+  def __init__(self,controlType=None,criticality=False):
+    self.controlType = controlType
+    self.criticality = criticality
+
+  def decodeControlValue(self,encodedControlValue):
+    """
+    decodes the BER-encoded ASN.1 control value and sets the appropriate
+    class attributes
+    """
+    self.encodedControlValue = encodedControlValue
+
+
+class LDAPControl(RequestControl,ResponseControl):
+  """
+  Base class for combined request/response controls mainly
+  for backward-compability to python-ldap 2.3.x
+  """
+
+  def __init__(self,controlType=None,criticality=False,controlValue=None,encodedControlValue=None):
+    self.controlType = controlType
+    self.criticality = criticality
+    self.controlValue = controlValue
+    self.encodedControlValue = encodedControlValue
+
+
+def RequestControlTuples(ldapControls):
+  """
+  Return list of readily encoded 3-tuples which can be directly
+  passed to C module _ldap
+
+  ldapControls
+      sequence-type of RequestControl objects
+  """
+  if ldapControls is None:
+    return None
+  else:
+    result = [
+      (c.controlType,c.criticality,c.encodeControlValue())
+      for c in ldapControls
+    ]
+    return result
+
+
+def DecodeControlTuples(ldapControlTuples,knownLDAPControls=None):
+  """
+  Returns list of readily decoded ResponseControl objects
+
+  ldapControlTuples
+      Sequence-type of 3-tuples returned by _ldap.result4() containing
+      the encoded ASN.1 control values of response controls.
+  knownLDAPControls
+      Dictionary mapping extended control's OID to ResponseControl class
+      of response controls known by the application. If None
+      ldap.controls.KNOWN_RESPONSE_CONTROLS is used here.
+  """
+  knownLDAPControls = knownLDAPControls or KNOWN_RESPONSE_CONTROLS
+  result = []
+  for controlType,criticality,encodedControlValue in ldapControlTuples or []:
+    try:
+      control = knownLDAPControls[controlType]()
+    except KeyError:
+      if criticality:
+        raise ldap.UNAVAILABLE_CRITICAL_EXTENSION('Received unexpected critical response control with controlType %s' % (repr(controlType)))
+    else:
+      control.controlType,control.criticality = controlType,criticality
+      control.decodeControlValue(encodedControlValue)
+      result.append(control)
+  return result
+
+
+# Import the standard sub-modules
+from ldap.controls.simple import *
+from ldap.controls.libldap import *

+ 77 - 0
desktop/core/ext-py/python-ldap-2.4.6/Lib/ldap/controls/libldap.py

@@ -0,0 +1,77 @@
+# -*- coding: utf-8 -*-
+"""
+controls.libldap - LDAP controls wrapper classes with en-/decoding done
+by OpenLDAP functions
+
+See http://www.python-ldap.org/ for details.
+
+$Id: libldap.py,v 1.2 2011/07/23 07:42:04 stroeder Exp $
+"""
+
+import _ldap,ldap
+from ldap.controls import RequestControl,LDAPControl,KNOWN_RESPONSE_CONTROLS
+
+
+class AssertionControl(RequestControl):
+  """
+  LDAP Assertion control, as defined in RFC 4528
+
+  filterstr
+    LDAP filter string specifying which assertions have to match
+    so that the server processes the operation
+  """
+  
+  controlType = ldap.CONTROL_ASSERT    
+  def __init__(self,criticality=True,filterstr='(objectClass=*)'):
+    self.criticality = criticality
+    self.filterstr = filterstr
+
+  def encodeControlValue(self):
+    return _ldap.encode_assertion_control(self.filterstr)
+
+KNOWN_RESPONSE_CONTROLS[ldap.CONTROL_ASSERT] = AssertionControl
+
+
+class MatchedValuesControl(RequestControl):
+  """
+  LDAP Matched Values control, as defined in RFC 3876
+
+  filterstr
+    LDAP filter string specifying which attribute values
+    should be returned
+  """
+  
+  controlType = ldap.CONTROL_VALUESRETURNFILTER
+  
+  def __init__(self,criticality=False,filterstr='(objectClass=*)'):
+    self.criticality = criticality
+    self.filterstr = filterstr
+
+  def encodeControlValue(self):
+    return _ldap.encode_valuesreturnfilter_control(self.filterstr)
+
+KNOWN_RESPONSE_CONTROLS[ldap.CONTROL_VALUESRETURNFILTER] = MatchedValuesControl
+
+
+class SimplePagedResultsControl(LDAPControl):
+  """
+  LDAP Control Extension for Simple Paged Results Manipulation
+
+  size
+    Page size requested (number of entries to be returned)
+  cookie
+    Cookie string received with last page
+  """
+  controlType = ldap.CONTROL_PAGEDRESULTS
+
+  def __init__(self,criticality=False,size=None,cookie=None):
+    self.criticality = criticality
+    self.size,self.cookie = size,cookie
+
+  def encodeControlValue(self):
+    return _ldap.encode_page_control(self.size,self.cookie)
+
+  def decodeControlValue(self,encodedControlValue):
+    self.size,self.cookie = _ldap.decode_page_control(encodedControlValue)
+
+KNOWN_RESPONSE_CONTROLS[ldap.CONTROL_PAGEDRESULTS] = SimplePagedResultsControl

+ 96 - 0
desktop/core/ext-py/python-ldap-2.4.6/Lib/ldap/controls/ppolicy.py

@@ -0,0 +1,96 @@
+# -*- coding: utf-8 -*-
+"""
+ldap.controls.ppolicy - classes for Password Policy controls
+(see http://tools.ietf.org/html/draft-behera-ldap-password-policy)
+
+See http://www.python-ldap.org/ for project details.
+
+$Id: ppolicy.py,v 1.3 2011/11/27 15:26:06 stroeder Exp $
+"""
+
+__all__ = [
+  'PasswordPolicyControl'
+]
+
+# Imports from python-ldap 2.4+
+import ldap.controls
+from ldap.controls import RequestControl,ResponseControl,ValueLessRequestControl,KNOWN_RESPONSE_CONTROLS
+
+# Imports from pyasn1
+from pyasn1.type import tag,namedtype,namedval,univ,constraint
+from pyasn1.codec.ber import encoder,decoder
+from pyasn1_modules.rfc2251 import LDAPDN
+
+
+class PasswordPolicyWarning(univ.Choice):
+  componentType = namedtype.NamedTypes(
+    namedtype.NamedType('timeBeforeExpiration',univ.Integer().subtype(
+      implicitTag=tag.Tag(tag.tagClassContext,tag.tagFormatSimple,0)
+    )),
+    namedtype.NamedType('graceAuthNsRemaining',univ.Integer().subtype(
+      implicitTag=tag.Tag(tag.tagClassContext,tag.tagFormatSimple,1)
+    )),
+  )
+
+
+class PasswordPolicyError(univ.Enumerated):
+  namedValues = namedval.NamedValues(
+    ('passwordExpired',0),
+    ('accountLocked',1),
+    ('changeAfterReset',2),
+    ('passwordModNotAllowed',3),
+    ('mustSupplyOldPassword',4),
+    ('insufficientPasswordQuality',5),
+    ('passwordTooShort',6),
+    ('passwordTooYoung',7),
+    ('passwordInHistory',8)
+  )
+  subtypeSpec = univ.Enumerated.subtypeSpec + constraint.SingleValueConstraint(0,1,2,3,4,5,6,7,8)
+
+
+class PasswordPolicyResponseValue(univ.Sequence):
+  componentType = namedtype.NamedTypes(
+    namedtype.OptionalNamedType(
+      'warning',
+      PasswordPolicyWarning().subtype(
+        implicitTag=tag.Tag(tag.tagClassContext,tag.tagFormatSimple,0)
+      ),
+    ),
+    namedtype.OptionalNamedType(
+      'error',PasswordPolicyError().subtype(
+        implicitTag=tag.Tag(tag.tagClassContext,tag.tagFormatSimple,1)
+      )
+    ),
+  )
+
+
+class PasswordPolicyControl(ValueLessRequestControl,ResponseControl):
+  controlType = '1.3.6.1.4.1.42.2.27.8.5.1'
+
+  def __init__(self,criticality=False):
+    self.criticality = criticality
+
+  def decodeControlValue(self,encodedControlValue):
+    ppolicyValue,_ = decoder.decode(encodedControlValue,asn1Spec=PasswordPolicyResponseValue())
+    warning = ppolicyValue.getComponentByName('warning')
+    if warning is None:
+      self.timeBeforeExpiration,self.graceAuthNsRemaining = None,None
+    else:
+      timeBeforeExpiration = warning.getComponentByName('timeBeforeExpiration')
+      if timeBeforeExpiration!=None:
+        self.timeBeforeExpiration = int(timeBeforeExpiration)
+      else:
+        self.timeBeforeExpiration = None
+      graceAuthNsRemaining = warning.getComponentByName('graceAuthNsRemaining')
+      if graceAuthNsRemaining!=None:
+        self.graceAuthNsRemaining = int(graceAuthNsRemaining)
+      else:
+        self.graceAuthNsRemaining = None
+    error = ppolicyValue.getComponentByName('error')
+    if error is None:
+      self.error = None
+    else:
+      self.error = int(error)
+      
+
+KNOWN_RESPONSE_CONTROLS[PasswordPolicyControl.controlType] = PasswordPolicyControl

+ 134 - 0
desktop/core/ext-py/python-ldap-2.4.6/Lib/ldap/controls/psearch.py

@@ -0,0 +1,134 @@
+# -*- coding: utf-8 -*-
+"""
+ldap.controls.psearch - classes for Persistent Search Control
+(see http://tools.ietf.org/html/draft-ietf-ldapext-psearch)
+
+See http://www.python-ldap.org/ for project details.
+
+$Id: psearch.py,v 1.4 2011/07/22 13:47:47 stroeder Exp $
+"""
+
+__all__ = [
+  'PersistentSearchControl',
+  'EntryChangeNotificationControl',
+  'CHANGE_TYPES_INT',
+  'CHANGE_TYPES_STR',
+]
+
+# Imports from python-ldap 2.4+
+import ldap.controls
+from ldap.controls import RequestControl,ResponseControl,KNOWN_RESPONSE_CONTROLS
+
+# Imports from pyasn1
+from pyasn1.type import namedtype,namedval,univ,constraint
+from pyasn1.codec.ber import encoder,decoder
+from pyasn1_modules.rfc2251 import LDAPDN
+
+#---------------------------------------------------------------------------
+# Constants and classes for Persistent Search Control
+#---------------------------------------------------------------------------
+
+CHANGE_TYPES_INT = {
+  'add':1,
+  'delete':2,
+  'modify':4,
+  'modDN':8,
+}
+CHANGE_TYPES_STR = dict([(v,k) for k,v in CHANGE_TYPES_INT.items()])
+
+
+class PersistentSearchControl(RequestControl):
+  """
+  Implements the request control for persistent search.
+
+  changeTypes
+    List of strings specifiying the types of changes returned by the server.
+    Setting to None requests all changes.
+  changesOnly
+    Boolean which indicates whether only changes are returned by the server.
+  returnECs
+    Boolean which indicates whether the server should return an
+    Entry Change Notication response control
+  """
+
+  class PersistentSearchControlValue(univ.Sequence):
+    componentType = namedtype.NamedTypes(
+      namedtype.NamedType('changeTypes',univ.Integer()),
+      namedtype.NamedType('changesOnly',univ.Boolean()),
+      namedtype.NamedType('returnECs',univ.Boolean()),
+    )
+
+  controlType = "2.16.840.1.113730.3.4.3"
+
+  def __init__(self,criticality=True,changeTypes=None,changesOnly=False,returnECs=True):
+    self.criticality,self.changesOnly,self.returnECs = \
+      criticality,changesOnly,returnECs
+    self.changeTypes = changeTypes or CHANGE_TYPES_INT.values()
+
+  def encodeControlValue(self):
+    if not type(self.changeTypes)==type(0):
+      # Assume a sequence type of integers to be OR-ed
+      changeTypes_int = 0
+      for ct in self.changeTypes:
+        changeTypes_int = changeTypes_int|CHANGE_TYPES_INT.get(ct,ct)
+      self.changeTypes = changeTypes_int
+    p = self.PersistentSearchControlValue()
+    p.setComponentByName('changeTypes',univ.Integer(self.changeTypes))
+    p.setComponentByName('changesOnly',univ.Boolean(self.changesOnly))
+    p.setComponentByName('returnECs',univ.Boolean(self.returnECs))
+    return encoder.encode(p)
+
+
+class ChangeType(univ.Enumerated):
+  namedValues = namedval.NamedValues(
+    ('add',1),
+    ('delete',2),
+    ('modify',4),
+    ('modDN',8),
+  )
+  subtypeSpec = univ.Enumerated.subtypeSpec + constraint.SingleValueConstraint(1,2,4,8)
+
+
+class EntryChangeNotificationValue(univ.Sequence):
+  componentType = namedtype.NamedTypes(
+    namedtype.NamedType('changeType',ChangeType()),
+    namedtype.OptionalNamedType('previousDN', LDAPDN()),
+    namedtype.OptionalNamedType('changeNumber',univ.Integer()),
+  )
+
+
+class EntryChangeNotificationControl(ResponseControl):
+  """
+  Implements the response control for persistent search.
+
+  Class attributes with values extracted from the response control:
+
+  changeType
+    String indicating the type of change causing this result to be
+    returned by the server
+  previousDN
+    Old DN of the entry in case of a modrdn change
+  changeNumber
+    A change serial number returned by the server (optional).
+  """
+
+  controlType = "2.16.840.1.113730.3.4.7"
+
+  def decodeControlValue(self,encodedControlValue):
+    ecncValue,_ = decoder.decode(encodedControlValue,asn1Spec=EntryChangeNotificationValue())
+    self.changeType = int(ecncValue.getComponentByName('changeType'))
+    if len(ecncValue)==3:
+      self.previousDN = str(ecncValue.getComponentByName('previousDN'))
+      self.changeNumber = int(ecncValue.getComponentByName('changeNumber'))
+    elif len(ecncValue)==2:
+      if self.changeType==8:
+        self.previousDN = str(ecncValue.getComponentByName('previousDN'))
+        self.changeNumber = None
+      else:
+        self.previousDN = None
+        self.changeNumber = int(ecncValue.getComponentByName('changeNumber'))
+    else:
+      self.previousDN,self.changeNumber = None,None
+    return (self.changeType,self.previousDN,self.changeNumber)
+
+KNOWN_RESPONSE_CONTROLS[EntryChangeNotificationControl.controlType] = EntryChangeNotificationControl

+ 90 - 0
desktop/core/ext-py/python-ldap-2.4.6/Lib/ldap/controls/readentry.py

@@ -0,0 +1,90 @@
+#!/usr/bin/env python
+"""
+ldap.controls.readentry - classes for the Read Entry controls
+(see RFC 4527)
+
+See http://www.python-ldap.org/ for project details.
+
+$Id: readentry.py,v 1.4 2011/07/28 08:57:12 stroeder Exp $
+"""
+
+import ldap
+
+from pyasn1.codec.ber import encoder,decoder
+from ldap.controls import LDAPControl,KNOWN_RESPONSE_CONTROLS
+
+from pyasn1_modules.rfc2251 import AttributeDescriptionList,SearchResultEntry
+
+
+class ReadEntryControl(LDAPControl):
+  """
+  Base class for read entry control described in RFC 4527
+
+  attrList
+      list of attribute type names requested
+
+  Class attributes with values extracted from the response control:
+
+  dn
+      string holding the distinguished name of the LDAP entry
+  entry
+      dictionary holding the LDAP entry
+  """
+
+  def __init__(self,criticality=False,attrList=None):
+    self.criticality,self.attrList,self.entry = criticality,attrList or [],None
+
+  def encodeControlValue(self):
+    attributeSelection = AttributeDescriptionList()
+    for i in range(len(self.attrList)):
+      attributeSelection.setComponentByPosition(i,self.attrList[i])
+    return encoder.encode(attributeSelection)
+
+  def decodeControlValue(self,encodedControlValue):
+    decodedEntry,_ = decoder.decode(encodedControlValue,asn1Spec=SearchResultEntry())
+    self.dn = str(decodedEntry[0])
+    self.entry = {}
+    for attr in decodedEntry[1]:
+      self.entry[str(attr[0])] = [ str(attr_value) for attr_value in attr[1] ]
+
+
+class PreReadControl(ReadEntryControl):
+  """
+  Class for pre-read control described in RFC 4527
+
+  attrList
+      list of attribute type names requested
+
+  Class attributes with values extracted from the response control:
+
+  dn
+      string holding the distinguished name of the LDAP entry
+      before the operation was done by the server
+  entry
+      dictionary holding the LDAP entry
+      before the operation was done by the server
+  """
+  controlType = ldap.CONTROL_PRE_READ
+
+KNOWN_RESPONSE_CONTROLS[PreReadControl.controlType] = PreReadControl
+
+
+class PostReadControl(ReadEntryControl):
+  """
+  Class for post-read control described in RFC 4527
+
+  attrList
+      list of attribute type names requested
+
+  Class attributes with values extracted from the response control:
+
+  dn
+      string holding the distinguished name of the LDAP entry
+      after the operation was done by the server
+  entry
+      dictionary holding the LDAP entry
+      after the operation was done by the server
+  """
+  controlType = ldap.CONTROL_POST_READ
+
+KNOWN_RESPONSE_CONTROLS[PostReadControl.controlType] = PostReadControl

+ 64 - 0
desktop/core/ext-py/python-ldap-2.4.6/Lib/ldap/controls/sessiontrack.py

@@ -0,0 +1,64 @@
+#!/usr/bin/env python
+"""
+ldap.controls.sessiontrack - class for session tracking control
+(see draft-wahl-ldap-session)
+
+See http://www.python-ldap.org/ for project details.
+
+$Id: sessiontrack.py,v 1.3 2011/07/23 08:03:53 stroeder Exp $
+"""
+
+from ldap.controls import RequestControl
+
+from pyasn1.type import namedtype,univ
+from pyasn1.codec.ber import encoder
+from pyasn1_modules.rfc2251 import LDAPString,LDAPOID
+
+
+# OID constants
+SESSION_TRACKING_CONTROL_OID = "1.3.6.1.4.1.21008.108.63.1"
+SESSION_TRACKING_FORMAT_OID_RADIUS_ACCT_SESSION_ID = SESSION_TRACKING_CONTROL_OID+".1"
+SESSION_TRACKING_FORMAT_OID_RADIUS_ACCT_MULTI_SESSION_ID = SESSION_TRACKING_CONTROL_OID+".2"
+SESSION_TRACKING_FORMAT_OID_USERNAME = SESSION_TRACKING_CONTROL_OID+".3"
+
+
+class SessionTrackingControl(RequestControl):
+  """
+  Class for Session Tracking Control
+
+  Because criticality MUST be false for this control it cannot be set
+  from the application.
+
+  sessionSourceIp
+    IP address of the request source as string
+  sessionSourceName
+    Name of the request source as string
+  formatOID
+    OID as string specifying the format
+  sessionTrackingIdentifier
+    String containing a specific tracking ID
+  """
+
+  class SessionIdentifierControlValue(univ.Sequence):
+    componentType = namedtype.NamedTypes(
+      namedtype.NamedType('sessionSourceIp',LDAPString()),
+      namedtype.NamedType('sessionSourceName',LDAPString()),
+      namedtype.NamedType('formatOID',LDAPOID()),
+      namedtype.NamedType('sessionTrackingIdentifier',LDAPString()),
+    )
+
+  controlType = SESSION_TRACKING_CONTROL_OID
+
+  def __init__(self,sessionSourceIp,sessionSourceName,formatOID,sessionTrackingIdentifier):
+    # criticality MUST be false for this control
+    self.criticality = False
+    self.sessionSourceIp,self.sessionSourceName,self.formatOID,self.sessionTrackingIdentifier = \
+      sessionSourceIp,sessionSourceName,formatOID,sessionTrackingIdentifier
+
+  def encodeControlValue(self):
+    s = self.SessionIdentifierControlValue()
+    s.setComponentByName('sessionSourceIp',LDAPString(self.sessionSourceIp))
+    s.setComponentByName('sessionSourceName',LDAPString(self.sessionSourceName))
+    s.setComponentByName('formatOID',LDAPOID(self.formatOID))
+    s.setComponentByName('sessionTrackingIdentifier',LDAPString(self.sessionTrackingIdentifier))
+    return encoder.encode(s)

+ 139 - 0
desktop/core/ext-py/python-ldap-2.4.6/Lib/ldap/controls/simple.py

@@ -0,0 +1,139 @@
+# -*- coding: utf-8 -*-
+"""
+ldap.controls.simple - classes for some very simple LDAP controls
+
+See http://www.python-ldap.org/ for details.
+
+$Id: simple.py,v 1.5 2011/07/23 08:03:53 stroeder Exp $
+"""
+
+import struct,ldap
+from ldap.controls import RequestControl,ResponseControl,LDAPControl,KNOWN_RESPONSE_CONTROLS
+
+
+class ValueLessRequestControl(RequestControl):
+  """
+  Base class for controls without a controlValue.
+  The presence of the control in a LDAPv3 request changes the server's
+  behaviour when processing the request simply based on the controlType.
+
+  controlType
+    OID of the request control
+  criticality
+    criticality request control
+  """
+
+  def __init__(self,controlType=None,criticality=False):
+    self.controlType = controlType
+    self.criticality = criticality
+
+  def encodeControlValue(self):
+    return None
+
+
+class OctetStringInteger(LDAPControl):
+  """
+  Base class with controlValue being unsigend integer values
+  
+  integerValue
+    Integer to be sent as OctetString
+  """
+
+  def __init__(self,controlType=None,criticality=False,integerValue=None):
+    self.controlType = controlType
+    self.criticality = criticality
+    self.integerValue = integerValue
+
+  def encodeControlValue(self):
+    return struct.pack('!Q',self.integerValue)
+
+  def decodeControlValue(self,encodedControlValue):
+    self.integerValue = self. struct.unpack('!Q',encodedControlValue)[0]
+    
+
+class BooleanControl(LDAPControl):
+  """
+  Base class for simple request controls with boolean control value.
+
+  Constructor argument and class attribute:
+
+  booleanValue
+    Boolean (True/False or 1/0) which is the boolean controlValue.
+  """
+  boolean2ber = { 1:'\x01\x01\xFF', 0:'\x01\x01\x00' }
+  ber2boolean = { '\x01\x01\xFF':1, '\x01\x01\x00':0 }
+
+  def __init__(self,controlType=None,criticality=False,booleanValue=False):
+    self.controlType = controlType
+    self.criticality = criticality
+    self.booleanValue = booleanValue
+
+  def encodeControlValue(self):
+    return self.boolean2ber[int(self.booleanValue)]
+
+  def decodeControlValue(self,encodedControlValue):
+    self.booleanValue = self.ber2boolean[encodedControlValue]
+
+
+class ManageDSAITControl(ValueLessRequestControl):
+  """
+  Manage DSA IT Control
+  """
+
+  def __init__(self,criticality=False):
+    ValueLessRequestControl.__init__(self,ldap.CONTROL_MANAGEDSAIT,criticality=False)
+
+KNOWN_RESPONSE_CONTROLS[ldap.CONTROL_MANAGEDSAIT] = ManageDSAITControl
+
+
+class RelaxRulesControl(ValueLessRequestControl):
+  """
+  Relax Rules Control
+  """
+
+  def __init__(self,criticality=False):
+    ValueLessRequestControl.__init__(self,ldap.CONTROL_RELAX,criticality=False)
+
+KNOWN_RESPONSE_CONTROLS[ldap.CONTROL_RELAX] = RelaxRulesControl
+
+
+class ProxyAuthzControl(RequestControl):
+  """
+  Proxy Authorization Control
+  
+  authzId
+    string containing the authorization ID indicating the identity
+    on behalf which the server should process the request
+  """
+
+  def __init__(self,criticality,authzId):
+    RequestControl.__init__(self,ldap.CONTROL_PROXY_AUTHZ,criticality,authzId)
+
+
+class AuthorizationIdentityControl(ValueLessRequestControl,ResponseControl):
+  """
+  Authorization Identity Request and Response Controls
+  
+  Class attributes:
+  
+  authzId
+    decoded authorization identity
+  """
+  controlType = '2.16.840.1.113730.3.4.16'
+
+  def __init__(self,criticality):
+    ValueLessRequestControl.__init__(self,self.controlType,criticality)
+
+  def decodeControlValue(self,encodedControlValue):
+    self.authzId = encodedControlValue
+
+KNOWN_RESPONSE_CONTROLS[AuthorizationIdentityControl.controlType] = AuthorizationIdentityControl
+
+
+class GetEffectiveRightsControl(RequestControl):
+  """
+  Get Effective Rights Control
+  """
+
+  def __init__(self,criticality,authzId=None):
+    RequestControl.__init__(self,'1.3.6.1.4.1.42.2.27.9.5.2',criticality,authzId)

+ 110 - 0
desktop/core/ext-py/python-ldap-2.4.6/Lib/ldap/dn.py

@@ -0,0 +1,110 @@
+"""
+dn.py - misc stuff for handling distinguished names (see RFC 4514)
+
+See http://www.python-ldap.org/ for details.
+
+\$Id: dn.py,v 1.11 2010/06/03 12:26:39 stroeder Exp $
+
+Compability:
+- Tested with Python 2.0+
+"""
+
+from ldap import __version__
+
+
+import _ldap
+
+import ldap.functions
+
+
+def escape_dn_chars(s):
+  """
+  Escape all DN special characters found in s
+  with a back-slash (see RFC 4514, section 2.4)
+  """
+  if s:
+    s = s.replace('\\','\\\\')
+    s = s.replace(',' ,'\\,')
+    s = s.replace('+' ,'\\+')
+    s = s.replace('"' ,'\\"')
+    s = s.replace('<' ,'\\<')
+    s = s.replace('>' ,'\\>')
+    s = s.replace(';' ,'\\;')
+    s = s.replace('=' ,'\\=')
+    s = s.replace('\000' ,'\\\000')    
+    if s[0]=='#' or s[0]==' ':
+      s = ''.join(('\\',s))
+    if s[-1]==' ':
+      s = ''.join((s[:-1],'\\ '))
+  return s
+
+
+def str2dn(dn,flags=0):
+  """
+  This function takes a DN as string as parameter and returns
+  a decomposed DN. It's the inverse to dn2str().
+  
+  flags describes the format of the dn
+
+  See also the OpenLDAP man-page ldap_str2dn(3)
+  """
+  if not dn:
+    return []
+  return ldap.functions._ldap_function_call(None,_ldap.str2dn,dn,flags)
+
+
+def dn2str(dn):
+  """
+  This function takes a decomposed DN as parameter and returns
+  a single string. It's the inverse to str2dn() but will always
+  return a DN in LDAPv3 format compliant to RFC 4514.
+  """
+  return ','.join([
+    '+'.join([
+      '='.join((atype,escape_dn_chars(avalue or '')))
+      for atype,avalue,dummy in rdn])
+    for rdn in dn
+  ])
+
+def explode_dn(dn,notypes=0,flags=0):
+  """
+  explode_dn(dn [, notypes=0]) -> list
+  
+  This function takes a DN and breaks it up into its component parts.
+  The notypes parameter is used to specify that only the component's
+  attribute values be returned and not the attribute types.
+  """
+  if not dn:
+    return []
+  dn_decomp = str2dn(dn,flags)
+  rdn_list = []
+  for rdn in dn_decomp:
+    if notypes:
+      rdn_list.append('+'.join([
+        escape_dn_chars(avalue or '')
+        for atype,avalue,dummy in rdn
+      ]))
+    else:
+      rdn_list.append('+'.join([
+        '='.join((atype,escape_dn_chars(avalue or '')))
+        for atype,avalue,dummy in rdn
+      ]))
+  return rdn_list
+
+
+def explode_rdn(rdn,notypes=0,flags=0):
+  """
+  explode_rdn(rdn [, notypes=0]) -> list
+  
+  This function takes a RDN and breaks it up into its component parts
+  if it is a multi-valued RDN.
+  The notypes parameter is used to specify that only the component's
+  attribute values be returned and not the attribute types.
+  """
+  if not rdn:
+    return []
+  rdn_decomp = str2dn(rdn,flags)[0]
+  if notypes:
+    return [avalue or '' for atype,avalue,dummy in rdn_decomp]
+  else:
+    return ['='.join((atype,escape_dn_chars(avalue or ''))) for atype,avalue,dummy in rdn_decomp]

+ 74 - 0
desktop/core/ext-py/python-ldap-2.4.6/Lib/ldap/extop/__init__.py

@@ -0,0 +1,74 @@
+"""
+controls.py - support classes for LDAPv3 extended operations
+
+See http://www.python-ldap.org/ for details.
+
+\$Id: __init__.py,v 1.4 2011/07/22 13:27:02 stroeder Exp $
+
+Description:
+The ldap.extop module provides base classes for LDAPv3 extended operations.
+Each class provides support for a certain extended operation request and
+response.
+"""
+
+from ldap import __version__
+
+
+class ExtendedRequest:
+  """
+  Generic base class for a LDAPv3 extended operation request
+
+  requestName
+      OID as string of the LDAPv3 extended operation request
+  requestValue
+      value of the LDAPv3 extended operation request
+      (here it is the BER-encoded ASN.1 request value)
+  """
+
+  def __init__(self,requestName,requestValue):
+    self.requestName = requestName
+    self.requestValue = requestValue
+
+  def __repr__(self):
+    return '%s(%s,%s)' % (self.__class__.__name__,self.requestName,self.requestValue)
+
+  def encodedRequestValue(self):
+    """
+    returns the BER-encoded ASN.1 request value composed by class attributes
+    set before
+    """
+    return self.requestValue
+
+
+class ExtendedResponse:
+  """
+  Generic base class for a LDAPv3 extended operation response
+
+  requestName
+      OID as string of the LDAPv3 extended operation response
+  encodedResponseValue
+      BER-encoded ASN.1 value of the LDAPv3 extended operation response
+  """
+
+  def __init__(self,responseName,encodedResponseValue):
+    self.responseName = responseName
+    self.responseValue = self.decodeResponseValue(encodedResponseValue)
+
+  def __repr__(self):
+    return '%s(%s,%s)' % (self.__class__.__name__,self.responseName,self.responseValue)
+
+  def decodeResponseValue(self,value):
+    """
+    decodes the BER-encoded ASN.1 extended operation response value and
+    sets the appropriate class attributes
+    """
+    return value
+
+
+# Optionally import sub-modules which need pyasn1 et al
+try:
+  import pyasn1,pyasn1_modules.rfc2251
+except ImportError:
+  pass
+else:
+  from ldap.extop.dds import *

Энэ ялгаанд хэт олон файл өөрчлөгдсөн тул зарим файлыг харуулаагүй болно