Ver Fonte

HUE-2012 [core] upgrade django auth ldap

Abraham Elmahrek há 11 anos atrás
pai
commit
e4ff34855d
62 ficheiros alterados com 7970 adições e 1456 exclusões
  1. 0 24
      desktop/core/ext-py/django-auth-ldap-1.0.7/PKG-INFO
  2. 0 2
      desktop/core/ext-py/django-auth-ldap-1.0.7/django_auth_ldap/models.py
  3. 0 1037
      desktop/core/ext-py/django-auth-ldap-1.0.7/django_auth_ldap/tests.py
  4. 70 0
      desktop/core/ext-py/django-auth-ldap-1.2.0/CHANGES
  5. 23 0
      desktop/core/ext-py/django-auth-ldap-1.2.0/LICENSE
  6. 5 0
      desktop/core/ext-py/django-auth-ldap-1.2.0/MANIFEST.in
  7. 97 0
      desktop/core/ext-py/django-auth-ldap-1.2.0/PKG-INFO
  8. 70 0
      desktop/core/ext-py/django-auth-ldap-1.2.0/README
  9. 2 0
      desktop/core/ext-py/django-auth-ldap-1.2.0/django_auth_ldap/__init__.py
  10. 388 240
      desktop/core/ext-py/django-auth-ldap-1.2.0/django_auth_ldap/backend.py
  11. 206 80
      desktop/core/ext-py/django-auth-ldap-1.2.0/django_auth_ldap/config.py
  12. 5 4
      desktop/core/ext-py/django-auth-ldap-1.2.0/django_auth_ldap/dn.py
  13. 31 0
      desktop/core/ext-py/django-auth-ldap-1.2.0/django_auth_ldap/models.py
  14. 1137 0
      desktop/core/ext-py/django-auth-ldap-1.2.0/django_auth_ldap/tests.py
  15. 26 25
      desktop/core/ext-py/django-auth-ldap-1.2.0/docs/Makefile
  16. 11 0
      desktop/core/ext-py/django-auth-ldap-1.2.0/docs/_templates/globaltoc.html
  17. 4 0
      desktop/core/ext-py/django-auth-ldap-1.2.0/docs/archive/versions/1.0.19/.buildinfo
  18. 153 28
      desktop/core/ext-py/django-auth-ldap-1.2.0/docs/archive/versions/1.0.19/_sources/index.txt
  19. BIN
      desktop/core/ext-py/django-auth-ldap-1.2.0/docs/archive/versions/1.0.19/_static/ajax-loader.gif
  20. 540 0
      desktop/core/ext-py/django-auth-ldap-1.2.0/docs/archive/versions/1.0.19/_static/basic.css
  21. BIN
      desktop/core/ext-py/django-auth-ldap-1.2.0/docs/archive/versions/1.0.19/_static/comment-bright.png
  22. BIN
      desktop/core/ext-py/django-auth-ldap-1.2.0/docs/archive/versions/1.0.19/_static/comment-close.png
  23. BIN
      desktop/core/ext-py/django-auth-ldap-1.2.0/docs/archive/versions/1.0.19/_static/comment.png
  24. 260 0
      desktop/core/ext-py/django-auth-ldap-1.2.0/docs/archive/versions/1.0.19/_static/default.css
  25. 247 0
      desktop/core/ext-py/django-auth-ldap-1.2.0/docs/archive/versions/1.0.19/_static/doctools.js
  26. BIN
      desktop/core/ext-py/django-auth-ldap-1.2.0/docs/archive/versions/1.0.19/_static/down-pressed.png
  27. BIN
      desktop/core/ext-py/django-auth-ldap-1.2.0/docs/archive/versions/1.0.19/_static/down.png
  28. BIN
      desktop/core/ext-py/django-auth-ldap-1.2.0/docs/archive/versions/1.0.19/_static/file.png
  29. 154 0
      desktop/core/ext-py/django-auth-ldap-1.2.0/docs/archive/versions/1.0.19/_static/jquery.js
  30. BIN
      desktop/core/ext-py/django-auth-ldap-1.2.0/docs/archive/versions/1.0.19/_static/minus.png
  31. BIN
      desktop/core/ext-py/django-auth-ldap-1.2.0/docs/archive/versions/1.0.19/_static/plus.png
  32. 62 0
      desktop/core/ext-py/django-auth-ldap-1.2.0/docs/archive/versions/1.0.19/_static/pygments.css
  33. 560 0
      desktop/core/ext-py/django-auth-ldap-1.2.0/docs/archive/versions/1.0.19/_static/searchtools.js
  34. 151 0
      desktop/core/ext-py/django-auth-ldap-1.2.0/docs/archive/versions/1.0.19/_static/sidebar.js
  35. 23 0
      desktop/core/ext-py/django-auth-ldap-1.2.0/docs/archive/versions/1.0.19/_static/underscore.js
  36. BIN
      desktop/core/ext-py/django-auth-ldap-1.2.0/docs/archive/versions/1.0.19/_static/up-pressed.png
  37. BIN
      desktop/core/ext-py/django-auth-ldap-1.2.0/docs/archive/versions/1.0.19/_static/up.png
  38. 808 0
      desktop/core/ext-py/django-auth-ldap-1.2.0/docs/archive/versions/1.0.19/_static/websupport.js
  39. 314 0
      desktop/core/ext-py/django-auth-ldap-1.2.0/docs/archive/versions/1.0.19/genindex.html
  40. 1030 0
      desktop/core/ext-py/django-auth-ldap-1.2.0/docs/archive/versions/1.0.19/index.html
  41. BIN
      desktop/core/ext-py/django-auth-ldap-1.2.0/docs/archive/versions/1.0.19/objects.inv
  42. 121 0
      desktop/core/ext-py/django-auth-ldap-1.2.0/docs/archive/versions/1.0.19/py-modindex.html
  43. 105 0
      desktop/core/ext-py/django-auth-ldap-1.2.0/docs/archive/versions/1.0.19/search.html
  44. 0 0
      desktop/core/ext-py/django-auth-ldap-1.2.0/docs/archive/versions/1.0.19/searchindex.js
  45. 11 0
      desktop/core/ext-py/django-auth-ldap-1.2.0/docs/ext/daldocs.py
  46. 82 0
      desktop/core/ext-py/django-auth-ldap-1.2.0/docs/source/.spell.utf-8.add
  47. BIN
      desktop/core/ext-py/django-auth-ldap-1.2.0/docs/source/.spell.utf-8.add.spl
  48. 135 0
      desktop/core/ext-py/django-auth-ldap-1.2.0/docs/source/authentication.rst
  49. 4 0
      desktop/core/ext-py/django-auth-ldap-1.2.0/docs/source/changes.rst
  50. 24 10
      desktop/core/ext-py/django-auth-ldap-1.2.0/docs/source/conf.py
  51. 74 0
      desktop/core/ext-py/django-auth-ldap-1.2.0/docs/source/example.rst
  52. 74 0
      desktop/core/ext-py/django-auth-ldap-1.2.0/docs/source/groups.rst
  53. 46 0
      desktop/core/ext-py/django-auth-ldap-1.2.0/docs/source/index.rst
  54. 27 0
      desktop/core/ext-py/django-auth-ldap-1.2.0/docs/source/install.rst
  55. 17 0
      desktop/core/ext-py/django-auth-ldap-1.2.0/docs/source/logging.rst
  56. 51 0
      desktop/core/ext-py/django-auth-ldap-1.2.0/docs/source/multiconfig.rst
  57. 34 0
      desktop/core/ext-py/django-auth-ldap-1.2.0/docs/source/performance.rst
  58. 76 0
      desktop/core/ext-py/django-auth-ldap-1.2.0/docs/source/permissions.rst
  59. 555 0
      desktop/core/ext-py/django-auth-ldap-1.2.0/docs/source/reference.rst
  60. 133 0
      desktop/core/ext-py/django-auth-ldap-1.2.0/docs/source/users.rst
  61. 5 0
      desktop/core/ext-py/django-auth-ldap-1.2.0/setup.cfg
  62. 19 6
      desktop/core/ext-py/django-auth-ldap-1.2.0/setup.py

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

@@ -1,24 +0,0 @@
-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

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

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

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

@@ -1,1037 +0,0 @@
-# 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)

+ 70 - 0
desktop/core/ext-py/django-auth-ldap-1.2.0/CHANGES

@@ -0,0 +1,70 @@
+v1.2 - 2014-04-10
+-----------------
+
+- django-auth-ldap now provides experimental Python 3 support. Python 2.5 was
+  dropped.
+
+  To sum up, django-auth-ldap works with Python 2.6, 2.7, 3.3 and 3.4.
+
+  Since python-ldap isn't making progress toward Python 3, if you're using
+  Python 3, you need to install a fork::
+
+      $ pip install git+https://github.com/rbarrois/python-ldap.git@py3
+
+  Thanks to `Aymeric Augustin <https://myks.org/en/>`_ for making this happen.
+
+
+v1.1.8 - 2014-02-01
+-------------------
+
+* Fix `#43`_: Update :class:`~django_auth_ldap.config.LDAPSearchUnion` to work for
+  group searches in addition to user searches.
+
+* Tox no longer supports Python 2.5, so our tests now run on 2.6 and 2.7 only.
+
+.. _#43: https://bitbucket.org/psagers/django-auth-ldap/issue/43/
+
+
+v1.1.7 - 2013-11-19
+-------------------
+
+* Bug fix: :setting:`AUTH_LDAP_GLOBAL_OPTIONS` could be ignored in some cases
+  (such as :func:`~django_auth_ldap.backend.LDAPBackend.populate_user`).
+
+
+v1.1.5 - 2013-10-25
+-------------------
+
+* Fix `#41`_: Support POSIX group permissions with no gidNumber attribute.
+
+* Support multiple group DNs for \*_FLAGS_BY_GROUP.
+
+
+.. _#41: https://bitbucket.org/psagers/django-auth-ldap/issue/41/
+
+
+v1.1.4 - 2013-03-09
+-------------------
+
+* Add support for Django 1.5's custom user models.
+
+
+v1.1.3 - 2013-01-05
+-------------------
+
+* Fix `#33`_: Reject empty passwords by default.
+
+  Unless :setting:`AUTH_LDAP_PERMIT_EMPTY_PASSWORD` is set to True,
+  LDAPBackend.authenticate() will immediately return None if the password is
+  empty. This is technically backwards-incompatible, but it's a more secure
+  default for those LDAP servers that are configured such that binds without
+  passwords always succeed.
+
+* Fix `#39`_: Add support for pickling LDAP-authenticated users.
+
+
+.. _#33: https://bitbucket.org/psagers/django-auth-ldap/issue/33/
+.. _#39: https://bitbucket.org/psagers/django-auth-ldap/issue/39/
+
+
+.. vim: ft=rst nospell tw=80

+ 23 - 0
desktop/core/ext-py/django-auth-ldap-1.2.0/LICENSE

@@ -0,0 +1,23 @@
+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.

+ 5 - 0
desktop/core/ext-py/django-auth-ldap-1.2.0/MANIFEST.in

@@ -0,0 +1,5 @@
+include README LICENSE CHANGES
+
+recursive-include docs *
+prune docs/build
+global-exclude *.pyc .DS_Store .workon

+ 97 - 0
desktop/core/ext-py/django-auth-ldap-1.2.0/PKG-INFO

@@ -0,0 +1,97 @@
+Metadata-Version: 1.1
+Name: django-auth-ldap
+Version: 1.2.0
+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 version is tested on Python 2.6 to 2.7, Django >= 1.3, and python-ldap
+        2.4.13.
+        
+        Full documentation can be found at http://pythonhosted.org/django-auth-ldap/;
+        following is an example configuration, just to whet your appetite::
+        
+            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()
+        
+            # Simple group restrictions
+            AUTH_LDAP_REQUIRE_GROUP = "cn=enabled,ou=django,ou=groups,dc=example,dc=com"
+            AUTH_LDAP_DENY_GROUP = "cn=disabled,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"
+            }
+        
+            AUTH_LDAP_PROFILE_FLAGS_BY_GROUP = {
+                "is_awesome": "cn=awesome,ou=django,ou=groups,dc=example,dc=com",
+            }
+        
+            # 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',
+            )
+        
+Keywords: django,ldap,authentication,auth
+Platform: UNKNOWN
+Classifier: Development Status :: 5 - Production/Stable
+Classifier: Environment :: Web Environment
+Classifier: Programming Language :: Python
+Classifier: Programming Language :: Python :: 2
+Classifier: Programming Language :: Python :: 2.6
+Classifier: Programming Language :: Python :: 2.7
+Classifier: Programming Language :: Python :: 3
+Classifier: Programming Language :: Python :: 3.3
+Classifier: Programming Language :: Python :: 3.4
+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

+ 70 - 0
desktop/core/ext-py/django-auth-ldap-1.2.0/README

@@ -0,0 +1,70 @@
+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 version is tested on Python 2.6 to 2.7, Django >= 1.3, and python-ldap
+2.4.13.
+
+Full documentation can be found at http://pythonhosted.org/django-auth-ldap/;
+following is an example configuration, just to whet your appetite::
+
+    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()
+
+    # Simple group restrictions
+    AUTH_LDAP_REQUIRE_GROUP = "cn=enabled,ou=django,ou=groups,dc=example,dc=com"
+    AUTH_LDAP_DENY_GROUP = "cn=disabled,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"
+    }
+
+    AUTH_LDAP_PROFILE_FLAGS_BY_GROUP = {
+        "is_awesome": "cn=awesome,ou=django,ou=groups,dc=example,dc=com",
+    }
+
+    # 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',
+    )

+ 2 - 0
desktop/core/ext-py/django-auth-ldap-1.2.0/django_auth_ldap/__init__.py

@@ -0,0 +1,2 @@
+version = (1, 2, 0)
+version_string = '.'.join(map(str, version))

+ 388 - 240
desktop/core/ext-py/django-auth-ldap-1.0.7/django_auth_ldap/backend.py → desktop/core/ext-py/django-auth-ldap-1.2.0/django_auth_ldap/backend.py

@@ -1,16 +1,16 @@
 # 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
@@ -45,76 +45,133 @@ 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 ldap
 import sys
 import traceback
 import pprint
 import copy
 
-import django.db
-from django.contrib.auth.models import User, Group, SiteProfileNotAvailable
+from django.contrib.auth.models import User, Group, Permission
 from django.core.cache import cache
 from django.core.exceptions import ImproperlyConfigured, ObjectDoesNotExist
+import django.dispatch
+try:
+    from django.utils.encoding import force_str
+except ImportError:  # Django < 1.5
+    from django.utils.encoding import smart_str as force_str
+
+# Django 1.7 Removed custom profiles
+try:
+    from django.contrib.auth.models import SiteProfileNotAvailable
+except ImportError:
+    SiteProfileNotAvailable = Exception
+
+# Support Django 1.5's custom user models
+try:
+    from django.contrib.auth import get_user_model
+    get_user_username = lambda u: u.get_username()
+except ImportError:
+    get_user_model = lambda: User                                        # noqa
+    get_user_username = lambda u: u.username
+
+# Small compatibility hack
+try:
+    basestring
+except NameError:
+    basestring = str
 
-from django_auth_ldap.config import _LDAPConfig, LDAPSearch, LDAPGroupType
+from django_auth_ldap.config import _LDAPConfig, LDAPSearch
 
 
 logger = _LDAPConfig.get_logger()
 
 
+# Signals for populating user objects.
+populate_user = django.dispatch.Signal(providing_args=["user", "ldap_user"])
+populate_user_profile = django.dispatch.Signal(providing_args=["profile", "ldap_user"])
+
+
 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)
+    supports_anonymous_user = False
+    supports_object_permissions = True
+    supports_inactive_user = False
+
+    _settings = None
+    _ldap = None  # The cached ldap module (or mock object)
+
+    # This is prepended to our internal setting names to produce the names we
+    # expect in Django's settings file. Subclasses can change this in order to
+    # support multiple collections of settings.
+    settings_prefix = 'AUTH_LDAP_'
+
+    def __getstate__(self):
+        """
+        Exclude certain cached properties from pickling.
+        """
+        return dict((k, v) for (k, v) in self.__dict__.items()
+                    if k not in ['_settings', '_ldap'])
 
+    def _get_settings(self):
+        if self._settings is None:
+            self._settings = LDAPSettings(self.settings_prefix)
+
+        return self._settings
+
+    def _set_settings(self, settings):
+        self._settings = settings
+
+    settings = property(_get_settings, _set_settings)
+
+    def _get_ldap(self):
+        if self._ldap is None:
+            from django.conf import settings
+
+            options = getattr(settings, 'AUTH_LDAP_GLOBAL_OPTIONS', None)
+
+            self._ldap = _LDAPConfig.get_ldap(options)
+
+        return self._ldap
+    ldap = property(_get_ldap)
+
+    def get_user_model(self):
+        """
+        By default, this will return the model class configured by
+        AUTH_USER_MODEL. Subclasses may wish to override it and return a proxy
+        model.
+        """
+        return get_user_model()
 
     #
     # The Django auth backend API
     #
 
     def authenticate(self, username, password):
-        ldap_user = _LDAPUser(self, username=username)
+        if len(password) == 0 and not self.settings.PERMIT_EMPTY_PASSWORD:
+            logger.debug('Rejecting empty password for %s' % username)
+            return None
+
+        ldap_user = _LDAPUser(self, username=username.strip())
         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:
+            user = self.get_user_model().objects.get(pk=user_id)
+            _LDAPUser(self, user=user)  # This sets user.ldap_user
+        except ObjectDoesNotExist:
             pass
-        
+
         return user
-    
-    def has_perm(self, user, perm):
-        return perm in self.get_all_permissions(user)
+
+    def has_perm(self, user, perm, obj=None):
+        return perm in self.get_all_permissions(user, obj)
 
     def has_module_perms(self, user, app_label):
         for perm in self.get_all_permissions(user):
@@ -123,13 +180,13 @@ class LDAPBackend(object):
 
         return False
 
-    def get_all_permissions(self, user):
-        return self.get_group_permissions(user)
+    def get_all_permissions(self, user, obj=None):
+        return self.get_group_permissions(user, obj)
+
+    def get_group_permissions(self, user, obj=None):
+        if not hasattr(user, 'ldap_user') and self.settings.AUTHORIZE_ALL_USERS:
+            _LDAPUser(self, user=user)  # This sets user.ldap_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:
@@ -142,9 +199,9 @@ class LDAPBackend(object):
     def populate_user(self, username):
         ldap_user = _LDAPUser(self, username=username)
         user = ldap_user.populate_user()
-        
+
         return user
-    
+
     #
     # Hooks for subclasses
     #
@@ -155,7 +212,15 @@ class LDAPBackend(object):
         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)
+        model = self.get_user_model()
+        username_field = getattr(model, 'USERNAME_FIELD', 'username')
+
+        kwargs = {
+            username_field + '__iexact': username,
+            'defaults': {username_field: username.lower()}
+        }
+
+        return model.objects.get_or_create(**kwargs)
 
     def ldap_to_django_username(self, username):
         return username
@@ -172,17 +237,26 @@ class _LDAPUser(object):
     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
-    
+
+    # Defaults
+    _user = None
+    _user_dn = None
+    _user_attrs = None
+    _groups = None
+    _group_permissions = None
+    _connection = None
+    _connection_bound = False
+
     #
     # Initialization
     #
-    
+
     def __init__(self, backend, username=None, user=None):
         """
         A new LDAPUser must be initialized with either a username or an
@@ -190,26 +264,17 @@ class _LDAPUser(object):
         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.
@@ -218,46 +283,61 @@ class _LDAPUser(object):
         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 __getstate__(self):
+        """
+        Most of our properties are cached from the LDAP server. We only want to
+        pickle a few crucial things.
+        """
+        return dict((k, v) for (k, v) in self.__dict__.items()
+                    if k in ['backend', '_username', '_user'])
+
     def _set_authenticated_user(self, user):
         self._user = user
-        self._username = self.backend.django_to_ldap_username(user.username)
+        self._username = self.backend.django_to_ldap_username(get_user_username(user))
 
         user.ldap_user = self
         user.ldap_username = self._username
-    
+
+    def _get_ldap(self):
+        return self.backend.ldap
+    ldap = property(_get_ldap)
+
+    def _get_settings(self):
+        return self.backend.settings
+    settings = property(_get_settings)
+
     #
     # Entry points
     #
-    
+
     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:
+        except self.AuthenticationFailed as e:
             logger.debug(u"Authentication failed for %s" % self._username)
-        except self.ldap.LDAPError, e:
+        except ldap.LDAPError as 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])))
+                           self._username, pprint.pformat(e))
+        except Exception:
+            logger.exception(u"Caught Exception while authenticating %s",
+                             self._username)
             raise
 
         return user
@@ -270,13 +350,13 @@ class _LDAPUser(object):
         if self._group_permissions is None:
             self._group_permissions = set()
 
-            if ldap_settings.AUTH_LDAP_FIND_GROUP_PERMS:
+            if self.settings.FIND_GROUP_PERMS:
                 try:
                     self._load_group_permissions()
-                except self.ldap.LDAPError, e:
+                except ldap.LDAPError as e:
                     logger.warning("Caught LDAPError loading group permissions: %s",
-                        pprint.pformat(e))
-        
+                                   pprint.pformat(e))
+
         return self._group_permissions
 
     def populate_user(self):
@@ -284,20 +364,23 @@ class _LDAPUser(object):
         Populates the Django user object using the default bind credentials.
         """
         user = None
-        
+
         try:
-            self._get_or_create_user(force_populate=True)
-            
+            # self.attrs will only be non-None if we were able to load this user
+            # from the LDAP directory, so this filters out nonexistent users.
+            if self.attrs is not None:
+                self._get_or_create_user(force_populate=True)
+
             user = self._user
-        except self.ldap.LDAPError, e:
+        except ldap.LDAPError as e:
             logger.warning(u"Caught LDAPError while authenticating %s: %s",
-                self._username, pprint.pformat(e))
-        except Exception, e:
+                           self._username, pprint.pformat(e))
+        except Exception as e:
             logger.error(u"Caught Exception while authenticating %s: %s",
-                self._username, pprint.pformat(e))
+                         self._username, pprint.pformat(e))
             logger.error(''.join(traceback.format_tb(sys.exc_info()[2])))
             raise
-        
+
         return user
 
     #
@@ -314,14 +397,22 @@ class _LDAPUser(object):
     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_group_dns(self):
+        return self._get_groups().get_group_dns()
+    group_dns = property(_get_group_dns)
+
+    def _get_group_names(self):
+        return self._get_groups().get_group_names()
+    group_names = property(_get_group_names)
+
     def _get_bound_connection(self):
         if not self._connection_bound:
             self._bind()
-        
+
         return self._get_connection()
     connection = property(_get_bound_connection)
 
@@ -338,18 +429,20 @@ class _LDAPUser(object):
             raise self.AuthenticationFailed("Failed to map the username to a DN.")
 
         try:
-            self._bind_as(self.dn, password)
-        except self.ldap.INVALID_CREDENTIALS:
+            sticky = self.settings.BIND_AS_AUTHENTICATING_USER
+
+            self._bind_as(self.dn, password, sticky=sticky)
+        except 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)
+            search = LDAPSearch(self.dn, 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
@@ -362,12 +455,12 @@ class _LDAPUser(object):
             self._search_for_user_dn()
 
     def _using_simple_bind_mode(self):
-        return (ldap_settings.AUTH_LDAP_USER_DN_TEMPLATE is not None)
+        return (self.settings.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)
-        
+        template = self.settings.USER_DN_TEMPLATE
+        username = ldap.dn.escape_dn_chars(self._username)
+
         self._user_dn = template % {'user': username}
 
     def _search_for_user_dn(self):
@@ -375,13 +468,13 @@ class _LDAPUser(object):
         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
+        search = self.settings.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]
+            (self._user_dn, self._user_attrs) = next(iter(results))
 
     def _check_requirements(self):
         """
@@ -389,19 +482,36 @@ class _LDAPUser(object):
         AuthenticationFailed on failure.
         """
         self._check_required_group()
-    
+        self._check_denied_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
-        
+        required_group_dn = self.settings.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")
 
+        return True
+
+    def _check_denied_group(self):
+        """
+        Returns True if the negative group requirement (AUTH_LDAP_DENY_GROUP)
+        is met. Always returns True if AUTH_LDAP_DENY_GROUP is None.
+        """
+        denied_group_dn = self.settings.DENY_GROUP
+
+        if denied_group_dn is not None:
+            is_member = self._get_groups().is_member_of(denied_group_dn)
+            if is_member:
+                raise self.AuthenticationFailed("User is a member of AUTH_LDAP_DENY_GROUP")
+
+        return True
+
     #
     # User management
     #
@@ -413,30 +523,43 @@ class _LDAPUser(object):
         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)
+        self._user, created = self.backend.get_or_create_user(username, self)
+        self._user.ldap_user = self
+        self._user.ldap_username = self._username
+
+        should_populate = force_populate or self.settings.ALWAYS_UPDATE_USER or created
 
         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):
+        if should_populate:
             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:
+        if self.settings.MIRROR_GROUPS:
             self._mirror_groups()
 
+        # Give the client a chance to finish populating the user just before
+        # saving.
+        if should_populate:
+            signal_responses = populate_user.send(self.backend.__class__, user=self._user, ldap_user=self)
+            if len(signal_responses) > 0:
+                save_user = True
+
         if save_user:
             self._user.save()
 
-        self._user.ldap_user = self
-        self._user.ldap_username = self._username
+        # We populate the profile after the user model is saved to give the
+        # client a chance to create the profile. Custom user models in Django
+        # 1.5 probably won't have a get_profile method.
+        if should_populate and hasattr(self._user, 'get_profile'):
+            self._populate_and_save_user_profile()
 
     def _populate_user(self):
         """
@@ -444,17 +567,19 @@ class _LDAPUser(object):
         """
         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():
+        for field, attr in self.settings.USER_ATTR_MAP.items():
             try:
                 setattr(self._user, field, self.attrs[attr][0])
-            except (KeyError, IndexError):
-                pass
-    
+            except Exception:
+                logger.warning("%s does not have a value for the attribute %s", self.dn, attr)
+
     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)
+        for field, group_dns in self.settings.USER_FLAGS_BY_GROUP.items():
+            if isinstance(group_dns, basestring):
+                group_dns = [group_dns]
+            value = any(self._get_groups().is_member_of(dn) for dn in group_dns)
             setattr(self._user, field, value)
 
     def _populate_and_save_user_profile(self):
@@ -463,19 +588,55 @@ class _LDAPUser(object):
         """
         try:
             profile = self._user.get_profile()
+            save_profile = False
 
-            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
+            logger.debug("Populating Django user profile for %s", get_user_username(self._user))
+
+            save_profile = self._populate_profile_from_attributes(profile) or save_profile
+            save_profile = self._populate_profile_from_group_memberships(profile) or save_profile
 
-            if len(ldap_settings.AUTH_LDAP_PROFILE_ATTR_MAP) > 0:
+            signal_responses = populate_user_profile.send(self.backend.__class__, profile=profile, ldap_user=self)
+            if len(signal_responses) > 0:
+                save_profile = True
+
+            if save_profile:
                 profile.save()
         except (SiteProfileNotAvailable, ObjectDoesNotExist):
-            pass
-    
+            logger.debug("Django user %s does not have a profile to populate", get_user_username(self._user))
+
+    def _populate_profile_from_attributes(self, profile):
+        """
+        Populate the given profile object from AUTH_LDAP_PROFILE_ATTR_MAP.
+        Returns True if the profile was modified.
+        """
+        save_profile = False
+
+        for field, attr in self.settings.PROFILE_ATTR_MAP.items():
+            try:
+                # user_attrs is a hash of lists of attribute values
+                setattr(profile, field, self.attrs[attr][0])
+                save_profile = True
+            except Exception:
+                logger.warning("%s does not have a value for the attribute %s", self.dn, attr)
+
+        return save_profile
+
+    def _populate_profile_from_group_memberships(self, profile):
+        """
+        Populate the given profile object from AUTH_LDAP_PROFILE_FLAGS_BY_GROUP.
+        Returns True if the profile was modified.
+        """
+        save_profile = False
+
+        for field, group_dns in self.settings.PROFILE_FLAGS_BY_GROUP.items():
+            if isinstance(group_dns, basestring):
+                group_dns = [group_dns]
+            value = any(self._get_groups().is_member_of(dn) for dn in group_dns)
+            setattr(profile, field, value)
+            save_profile = True
+
+        return save_profile
+
     def _mirror_groups(self):
         """
         Mirrors the user's LDAP groups in the Django database and updates the
@@ -483,52 +644,26 @@ class _LDAPUser(object):
         """
         group_names = self._get_groups().get_group_names()
         groups = [Group.objects.get_or_create(name=group_name)[0] for group_name
-            in group_names]
-        
+                  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()])
+
+        perms = Permission.objects.filter(group__name__in=group_names)
+        perms = perms.values_list('content_type__app_label', 'codename')
+        perms = perms.order_by()
+
+        self._group_permissions = set(["%s.%s" % (ct, name) for ct, name in perms])
 
     def _get_groups(self):
         """
@@ -537,7 +672,7 @@ class _LDAPUser(object):
         """
         if self._groups is None:
             self._groups = _LDAPUserGroups(self)
-        
+
         return self._groups
 
     #
@@ -549,115 +684,122 @@ class _LDAPUser(object):
         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
+        self._bind_as(self.settings.BIND_DN, self.settings.BIND_PASSWORD,
+                      sticky=True)
 
-    def _bind_as(self, bind_dn, bind_password):
+    def _bind_as(self, bind_dn, bind_password, sticky=False):
         """
         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.
+        If sticky is True, then we will consider the connection to be bound for
+        the life of this object. If False, then the caller only wishes to test
+        the credentials, after which the connection will be considered unbound.
         """
-        self._get_connection().simple_bind_s(bind_dn.encode('utf-8'),
-            bind_password.encode('utf-8'))
+        self._get_connection().simple_bind_s(force_str(bind_dn),
+                                             force_str(bind_password))
 
-        self._connection_bound = False
+        self._connection_bound = sticky
 
     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():
+            uri = self.settings.SERVER_URI
+            if callable(uri):
+                uri = uri()
+
+            self._connection = self.backend.ldap.initialize(uri)
+
+            for opt, value in self.settings.CONNECTION_OPTIONS.items():
                 self._connection.set_option(opt, value)
 
-            if ldap_settings.AUTH_LDAP_START_TLS:
+            if self.settings.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.settings = ldap_user.settings
         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
+        self._group_type = self.settings.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
+
+        self._group_search = self.settings.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
+        Returns the set 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._group_names = set(
+                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
-        
+
+        # Normalize the DN
+        group_dn = group_dn.lower()
+
         # 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())
-        
+            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):
+
+    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])
-        
+            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
@@ -665,24 +807,31 @@ class _LDAPUserGroups(object):
         """
         if self._group_infos is None:
             self._group_infos = self._group_type.user_groups(self._ldap_user,
-                self._group_search)
-        
+                                                             self._group_search)
+
         return self._group_infos
 
     def _load_cached_attr(self, attr_name):
-        if ldap_settings.AUTH_LDAP_CACHE_GROUPS:
+        if self.settings.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:
+        if self.settings.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)
-    
+            cache.set(key, value, self.settings.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)
+        """
+        Memcache keys can't have spaces in them, so we'll remove them from the
+        DN for maximum compatibility.
+        """
+        dn = self._ldap_user.dn.replace(' ', '%20')
+        key = u'auth_ldap.%s.%s.%s' % (self.__class__.__name__, attr_name, dn)
+
+        return key
 
 
 class LDAPSettings(object):
@@ -692,39 +841,38 @@ class LDAPSettings(object):
     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,
+        'ALWAYS_UPDATE_USER': True,
+        'AUTHORIZE_ALL_USERS': False,
+        'BIND_AS_AUTHENTICATING_USER': False,
+        'BIND_DN': '',
+        'BIND_PASSWORD': '',
+        'CACHE_GROUPS': False,
+        'CONNECTION_OPTIONS': {},
+        'DENY_GROUP': None,
+        'FIND_GROUP_PERMS': False,
+        'GROUP_CACHE_TIMEOUT': None,
+        'GROUP_SEARCH': None,
+        'GROUP_TYPE': None,
+        'MIRROR_GROUPS': False,
+        'PERMIT_EMPTY_PASSWORD': False,
+        'PROFILE_ATTR_MAP': {},
+        'PROFILE_FLAGS_BY_GROUP': {},
+        'REQUIRE_GROUP': None,
+        'SERVER_URI': 'ldap://localhost',
+        'START_TLS': False,
+        'USER_ATTR_MAP': {},
+        'USER_DN_TEMPLATE': None,
+        'USER_FLAGS_BY_GROUP': {},
+        'USER_SEARCH': None,
     }
-    
-    def __init__(self):
+
+    def __init__(self, prefix='AUTH_LDAP_'):
         """
         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()
+        for name, default in self.defaults.items():
+            value = getattr(settings, prefix + name, default)
+            setattr(self, name, value)

+ 206 - 80
desktop/core/ext-py/django-auth-ldap-1.0.7/django_auth_ldap/config.py → desktop/core/ext-py/django-auth-ldap-1.2.0/django_auth_ldap/config.py

@@ -1,16 +1,16 @@
 # 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
@@ -30,14 +30,15 @@ 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 ldap
 import logging
 import pprint
 
+try:
+    from django.utils.encoding import force_str
+except ImportError:  # Django < 1.5
+    from django.utils.encoding import smart_str as force_str
+
 
 class _LDAPConfig(object):
     """
@@ -45,8 +46,10 @@ class _LDAPConfig(object):
     """
     ldap = None
     logger = None
-    
-    def get_ldap(cls):
+
+    _ldap_configured = False
+
+    def get_ldap(cls, global_options=None):
         """
         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
@@ -54,7 +57,6 @@ class _LDAPConfig(object):
         of python-ldap.
         """
         if cls.ldap is None:
-            import ldap
             import ldap.filter
 
             # Support for python-ldap < 2.0.6
@@ -63,9 +65,16 @@ class _LDAPConfig(object):
             except ImportError:
                 from django_auth_ldap import dn
                 ldap.dn = dn
-            
+
             cls.ldap = ldap
-        
+
+        # Apply global LDAP options once
+        if (not cls._ldap_configured) and (global_options is not None):
+            for opt, value in global_options.items():
+                cls.ldap.set_option(opt, value)
+
+            cls._ldap_configured = True
+
         return cls.ldap
     get_ldap = classmethod(get_ldap)
 
@@ -105,7 +114,7 @@ class LDAPSearch(object):
         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
@@ -113,16 +122,16 @@ class LDAPSearch(object):
         you don't want the values escaped, pass escape=False.
         """
         term_strings = [self.filterstr]
-        
-        for name, value in term_dict.iteritems():
+
+        for name, value in term_dict.items():
             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
@@ -130,40 +139,111 @@ class LDAPSearch(object):
         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'))
+            results = connection.search_s(force_str(self.base_dn),
+                                          self.scope,
+                                          force_str(filterstr))
+        except ldap.LDAPError as e:
+            results = []
+            logger.error(u"search_s('%s', %d, '%s') raised %s" %
+                         (self.base_dn, self.scope, filterstr, pprint.pformat(e)))
+
+        return self._process_results(results)
 
-            # 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)
+    def _begin(self, connection, filterargs=()):
+        """
+        Begins an asynchronous search and returns the message id to retrieve
+        the results.
+        """
+        try:
+            filterstr = self.filterstr % filterargs
+            msgid = connection.search(force_str(self.base_dn),
+                                      self.scope, force_str(filterstr))
+        except ldap.LDAPError as e:
+            msgid = None
+            logger.error(u"search('%s', %d, '%s') raised %s" %
+                         (self.base_dn, self.scope, filterstr, pprint.pformat(e)))
 
-            results = _DeepStringCoder('utf-8').decode(results)
+        return msgid
 
-            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:
+    def _results(self, connection, msgid):
+        """
+        Returns the result of a previous asynchronous query.
+        """
+        try:
+            kind, results = connection.result(msgid)
+            if kind != ldap.RES_SEARCH_RESULT:
+                results = []
+        except ldap.LDAPError as e:
             results = []
-            logger.error(u"search_s('%s', %d, '%s') raised %s" %
-                (self.base_dn, self.scope, filterstr, pprint.pformat(e)))
-        
+            logger.error(u"result(%d) raised %s" % (msgid, pprint.pformat(e)))
+
+        return self._process_results(results)
+
+    def _process_results(self, results):
+        """
+        Returns a sanitized copy of raw LDAP results. This scrubs out
+        references, decodes utf8, normalizes DNs, etc.
+        """
+        results = [r for r in results if r[0] is not None]
+        results = _DeepStringCoder('utf-8').decode(results)
+
+        # The normal form of a DN is lower case.
+        results = [(r[0].lower(), r[1]) for r in 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, self.filterstr, len(result_dns),
+                      "; ".join(result_dns)))
+
         return results
 
 
+class LDAPSearchUnion(object):
+    """
+    A compound search object that returns the union of the results. Instantiate
+    it with one or more LDAPSearch objects.
+    """
+    def __init__(self, *args):
+        self.searches = args
+        self.ldap = _LDAPConfig.get_ldap()
+
+    def search_with_additional_terms(self, term_dict, escape=True):
+        searches = [s.search_with_additional_terms(term_dict, escape)
+                    for s in self.searches]
+
+        return self.__class__(*searches)
+
+    def search_with_additional_term_string(self, filterstr):
+        searches = [s.search_with_additional_term_string(filterstr)
+                    for s in self.searches]
+
+        return self.__class__(*searches)
+
+    def execute(self, connection, filterargs=()):
+        msgids = [search._begin(connection, filterargs) for search in self.searches]
+        results = {}
+
+        for search, msgid in zip(self.searches, msgids):
+            result = search._results(connection, msgid)
+            results.update(dict(result))
+
+        return results.items()
+
+
 class _DeepStringCoder(object):
     """
     Encodes and decodes strings in a nested structure of lists, tuples, and
@@ -172,10 +252,11 @@ class _DeepStringCoder(object):
     """
     def __init__(self, encoding):
         self.encoding = encoding
-    
+        self.ldap = _LDAPConfig.get_ldap()
+
     def decode(self, value):
         try:
-            if isinstance(value, str):
+            if isinstance(value, bytes):
                 value = value.decode(self.encoding)
             elif isinstance(value, list):
                 value = self._decode_list(value)
@@ -185,14 +266,22 @@ class _DeepStringCoder(object):
                 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()])
+        # Attribute dictionaries should be case-insensitive. python-ldap
+        # defines this, although for some reason, it doesn't appear to use it
+        # for search results.
+        decoded = self.ldap.cidict.cidict()
+
+        for k, v in value.items():
+            decoded[self.decode(k)] = self.decode(v)
+
+        return decoded
 
 
 class LDAPGroupType(object):
@@ -202,10 +291,10 @@ class LDAPGroupType(object):
     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.
     """
@@ -221,22 +310,22 @@ class LDAPGroupType(object):
         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.
@@ -248,7 +337,7 @@ class LDAPGroupType(object):
         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.
@@ -257,7 +346,7 @@ class LDAPGroupType(object):
             name = group_info[1][self.name_attr][0]
         except (KeyError, IndexError):
             name = None
-        
+
         return name
 
 
@@ -271,21 +360,26 @@ class PosixGroupType(LDAPGroupType):
         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)
-            )
-            
+
+            if 'gidNumber' in ldap_user.attrs:
+                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)
+                )
+            else:
+                filterstr = u'(memberUid=%s)' % (
+                    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):
@@ -295,14 +389,21 @@ class PosixGroupType(LDAPGroupType):
         """
         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'))
+            try:
+                is_member = ldap_user.connection.compare_s(force_str(group_dn), 'memberUid', force_str(user_uid))
+            except (ldap.UNDEFINED_TYPE, ldap.NO_SUCH_ATTRIBUTE):
+                is_member = False
+
             if not is_member:
-                is_member = ldap_user.connection.compare_s(group_dn.encode('utf-8'), 'gidNumber', user_gid.encode('utf-8'))
+                try:
+                    user_gid = ldap_user.attrs['gidNumber'][0]
+                    is_member = ldap_user.connection.compare_s(force_str(group_dn), 'gidNumber', force_str(user_gid))
+                except (ldap.UNDEFINED_TYPE, ldap.NO_SUCH_ATTRIBUTE):
+                    is_member = False
         except (KeyError, IndexError):
             is_member = False
-        
+
         return is_member
 
 
@@ -316,19 +417,26 @@ class MemberDNGroupType(LDAPGroupType):
         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})
+        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'))
+        try:
+            result = ldap_user.connection.compare_s(
+                force_str(group_dn),
+                force_str(self.member_attr),
+                force_str(ldap_user.dn)
+            )
+        except (ldap.UNDEFINED_TYPE, ldap.NO_SUCH_ATTRIBUTE):
+            result = 0
+
+        return result
 
 
 class NestedMemberDNGroupType(LDAPGroupType):
@@ -345,7 +453,7 @@ class NestedMemberDNGroupType(LDAPGroupType):
         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
@@ -353,13 +461,14 @@ class NestedMemberDNGroupType(LDAPGroupType):
         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
-        
+        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)
+                                                           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)
@@ -367,18 +476,18 @@ class NestedMemberDNGroupType(LDAPGroupType):
             # 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)
 
 
@@ -431,3 +540,20 @@ class NestedActiveDirectoryGroupType(NestedMemberDNGroupType):
     """
     def __init__(self, name_attr='cn'):
         super(NestedActiveDirectoryGroupType, self).__init__('member', name_attr)
+
+
+class OrganizationalRoleGroupType(MemberDNGroupType):
+    """
+    An LDAPGroupType subclass that handles groups of class organizationalRole.
+    """
+    def __init__(self, name_attr='cn'):
+        super(OrganizationalRoleGroupType, self).__init__('roleOccupant', name_attr)
+
+
+class NestedOrganizationalRoleGroupType(NestedMemberDNGroupType):
+    """
+    An LDAPGroupType subclass that handles groups of class OrganizationalRoleGroupType
+    with nested group references.
+    """
+    def __init__(self, name_attr='cn'):
+        super(NestedOrganizationalRoleGroupType, self).__init__('roleOccupant', name_attr)

+ 5 - 4
desktop/core/ext-py/django-auth-ldap-1.0.7/django_auth_ldap/dn.py → desktop/core/ext-py/django-auth-ldap-1.2.0/django_auth_ldap/dn.py

@@ -1,16 +1,16 @@
 # 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
@@ -27,6 +27,7 @@ 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

+ 31 - 0
desktop/core/ext-py/django-auth-ldap-1.2.0/django_auth_ldap/models.py

@@ -0,0 +1,31 @@
+from django.db import models
+
+
+# Support for testing Django 1.5's custom user models.
+try:
+    from django.contrib.auth.models import AbstractBaseUser
+except ImportError:
+    from django.contrib.auth.models import User
+
+    TestUser = User
+else:
+    class TestUser(AbstractBaseUser):
+        identifier = models.CharField(max_length=40, unique=True, db_index=True)
+
+        USERNAME_FIELD = 'identifier'
+
+        def get_full_name(self):
+            return self.identifier
+
+        def get_short_name(self):
+            return self.identifier
+
+
+class TestProfile(models.Model):
+    """
+    A user profile model for use by unit tests. This has nothing to do with the
+    authentication backend itself.
+    """
+    user = models.OneToOneField('auth.User')
+    is_special = models.BooleanField(default=False)
+    populated = models.BooleanField(default=False)

+ 1137 - 0
desktop/core/ext-py/django-auth-ldap-1.2.0/django_auth_ldap/tests.py

@@ -0,0 +1,1137 @@
+# 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.
+
+
+from copy import deepcopy
+import logging
+import pickle
+
+import ldap
+try:
+    import mockldap
+except ImportError:
+    mockldap = None
+
+from django.conf import settings
+import django.db.models.signals
+from django.contrib.auth.models import User, Permission, Group
+from django.test import TestCase
+try:
+    from django.utils.encoding import force_str
+except ImportError:  # Django < 1.5
+    from django.utils.encoding import smart_str as force_str
+from django.utils import unittest
+try:
+    from django.test.utils import override_settings
+except ImportError:
+    override_settings = lambda *args, **kwargs: (lambda v: v)
+
+from django_auth_ldap.models import TestUser, TestProfile
+from django_auth_ldap import backend
+from django_auth_ldap.config import LDAPSearch, LDAPSearchUnion
+from django_auth_ldap.config import PosixGroupType, MemberDNGroupType, NestedMemberDNGroupType
+from django_auth_ldap.config import GroupOfNamesType
+
+
+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.items():
+            value = kwargs.get(name, default)
+            setattr(self, name, value)
+
+
+class LDAPTest(TestCase):
+    top = ("o=test", {"o": "test"})
+    people = ("ou=people,o=test", {"ou": "people"})
+    groups = ("ou=groups,o=test", {"ou": "groups"})
+    moregroups = ("ou=moregroups,o=test", {"ou": "moregroups"})
+
+    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 = (force_str(u"uid=dreßler,ou=people,o=test"), {
+        "uid": [force_str(u"dreßler")],
+        "objectClass": ["person", "organizationalPerson", "inetOrgPerson", "posixAccount"],
+        "userPassword": ["password"],
+        "uidNumber": ["1002"],
+        "gidNumber": ["50"],
+        "givenName": ["Wolfgang"],
+        "sn": [force_str(u"Dreßler")]
+    })
+    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"],
+        "memberUid": [],
+    })
+    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"],
+    })
+
+    # groupOfNames groups
+    empty_gon = ("cn=empty_gon,ou=groups,o=test", {
+        "cn": ["empty_gon"],
+        "objectClass": ["groupOfNames"],
+        "member": []
+    })
+    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"]
+    })
+    other_gon = ("cn=other_gon,ou=moregroups,o=test", {
+        "cn": ["other_gon"],
+        "objectClass": ["groupOfNames"],
+        "member": ["uid=bob,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"]
+    })
+
+    directory = dict([top, people, groups, moregroups, alice, bob, dressler,
+                      nobody, active_px, staff_px, superuser_px, empty_gon,
+                      active_gon, staff_gon, superuser_gon, other_gon,
+                      parent_gon, nested_gon, circular_gon])
+
+    @classmethod
+    def configure_logger(cls):
+        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)
+
+    @classmethod
+    def setUpClass(cls):
+        cls.configure_logger()
+        cls.mockldap = mockldap.MockLdap(cls.directory)
+
+    @classmethod
+    def tearDownClass(cls):
+        del cls.mockldap
+
+    def setUp(self):
+        self.mockldap.start()
+        self.ldapobj = self.mockldap['ldap://localhost']
+
+        self.backend = backend.LDAPBackend()
+        self.backend.ldap  # Force global configuration
+
+    def tearDown(self):
+        self.mockldap.stop()
+        del self.ldapobj
+
+    def test_options(self):
+        self._init_settings(
+            USER_DN_TEMPLATE='uid=%(user)s,ou=people,o=test',
+            CONNECTION_OPTIONS={'opt1': 'value1'}
+        )
+        self.backend.authenticate(username='alice', password='password')
+
+        self.assertEqual(self.ldapobj.get_option('opt1'), 'value1')
+
+    def test_callable_server_uri(self):
+        self._init_settings(
+            SERVER_URI=lambda: 'ldap://ldap.example.com',
+            USER_DN_TEMPLATE='uid=%(user)s,ou=people,o=test'
+        )
+
+        self.backend.authenticate(username='alice', password='password')
+
+        ldapobj = self.mockldap['ldap://ldap.example.com']
+        self.assertEqual(
+            ldapobj.methods_called(with_args=True),
+            [('initialize', ('ldap://ldap.example.com',), {}),
+             ('simple_bind_s', ('uid=alice,ou=people,o=test', 'password'), {})]
+        )
+
+    def test_simple_bind(self):
+        self._init_settings(
+            USER_DN_TEMPLATE='uid=%(user)s,ou=people,o=test'
+        )
+        user_count = User.objects.count()
+
+        user = self.backend.authenticate(username='alice', password='password')
+
+        self.assertTrue(not user.has_usable_password())
+        self.assertEqual(user.username, 'alice')
+        self.assertEqual(User.objects.count(), user_count + 1)
+        self.assertEqual(
+            self.ldapobj.methods_called(),
+            ['initialize', 'simple_bind_s']
+        )
+
+    def test_new_user_lowercase(self):
+        self._init_settings(
+            USER_DN_TEMPLATE='uid=%(user)s,ou=people,o=test'
+        )
+        user_count = User.objects.count()
+
+        user = self.backend.authenticate(username='Alice', password='password')
+
+        self.assertTrue(not user.has_usable_password())
+        self.assertEqual(user.username, 'alice')
+        self.assertEqual(User.objects.count(), user_count + 1)
+        self.assertEqual(
+            self.ldapobj.methods_called(),
+            ['initialize', 'simple_bind_s']
+        )
+
+    def test_deepcopy(self):
+        self._init_settings(
+            USER_DN_TEMPLATE='uid=%(user)s,ou=people,o=test'
+        )
+
+        user = self.backend.authenticate(username='Alice', password='password')
+        user = deepcopy(user)
+
+    @override_settings(AUTH_USER_MODEL='django_auth_ldap.TestUser')
+    def test_auth_custom_user(self):
+        self._init_settings(
+            USER_DN_TEMPLATE='uid=%(user)s,ou=people,o=test',
+        )
+
+        user = self.backend.authenticate(username='Alice', password='password')
+
+        self.assertTrue(isinstance(user, TestUser))
+
+    @override_settings(AUTH_USER_MODEL='django_auth_ldap.TestUser')
+    def test_get_custom_user(self):
+        self._init_settings(
+            USER_DN_TEMPLATE='uid=%(user)s,ou=people,o=test',
+        )
+
+        user = self.backend.authenticate(username='Alice', password='password')
+        user = self.backend.get_user(user.id)
+
+        self.assertTrue(isinstance(user, TestUser))
+
+    def test_new_user_whitespace(self):
+        self._init_settings(
+            USER_DN_TEMPLATE='uid=%(user)s,ou=people,o=test'
+        )
+        user_count = User.objects.count()
+
+        user = self.backend.authenticate(username=' alice', password='password')
+        user = self.backend.authenticate(username='alice ', password='password')
+
+        self.assertTrue(not user.has_usable_password())
+        self.assertEqual(user.username, 'alice')
+        self.assertEqual(User.objects.count(), user_count + 1)
+
+    def test_simple_bind_bad_user(self):
+        self._init_settings(
+            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.assertTrue(user is None)
+        self.assertEqual(User.objects.count(), user_count)
+        self.assertEqual(
+            self.ldapobj.methods_called(),
+            ['initialize', 'simple_bind_s']
+        )
+
+    def test_simple_bind_bad_password(self):
+        self._init_settings(
+            USER_DN_TEMPLATE='uid=%(user)s,ou=people,o=test'
+        )
+        user_count = User.objects.count()
+
+        user = self.backend.authenticate(username='alice', password='bogus')
+
+        self.assertTrue(user is None)
+        self.assertEqual(User.objects.count(), user_count)
+        self.assertEqual(
+            self.ldapobj.methods_called(),
+            ['initialize', 'simple_bind_s']
+        )
+
+    def test_existing_user(self):
+        self._init_settings(
+            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.assertTrue(user is not None)
+        self.assertEqual(User.objects.count(), user_count)
+
+    def test_existing_user_insensitive(self):
+        self._init_settings(
+            USER_SEARCH=LDAPSearch(
+                "ou=people,o=test", ldap.SCOPE_SUBTREE, '(uid=%(user)s)'
+            )
+        )
+        # mockldap doesn't handle case-insensitive matching properly.
+        self.ldapobj.search_s.seed('ou=people,o=test', ldap.SCOPE_SUBTREE,
+                                   '(uid=Alice)')([self.alice])
+        User.objects.create(username='alice')
+
+        user = self.backend.authenticate(username='Alice', password='password')
+
+        self.assertTrue(user is not None)
+        self.assertEqual(user.username, 'alice')
+        self.assertEqual(User.objects.count(), 1)
+
+    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.backend = MyBackend()
+        self._init_settings(
+            USER_DN_TEMPLATE='uid=%(user)s,ou=people,o=test'
+        )
+        user_count = User.objects.count()
+
+        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(
+            USER_SEARCH=LDAPSearch(
+                "ou=people,o=test", ldap.SCOPE_SUBTREE, '(uid=%(user)s)'
+            )
+        )
+        user_count = User.objects.count()
+
+        user = self.backend.authenticate(username='alice', password='password')
+
+        self.assertTrue(user is not None)
+        self.assertEqual(User.objects.count(), user_count + 1)
+        self.assertEqual(
+            self.ldapobj.methods_called(),
+            ['initialize', 'simple_bind_s', 'search_s', 'simple_bind_s']
+        )
+
+    def test_search_bind_no_user(self):
+        self._init_settings(
+            USER_SEARCH=LDAPSearch(
+                "ou=people,o=test", ldap.SCOPE_SUBTREE, '(cn=%(user)s)'
+            )
+        )
+
+        user = self.backend.authenticate(username='alice', password='password')
+
+        self.assertTrue(user is None)
+        self.assertEqual(
+            self.ldapobj.methods_called(),
+            ['initialize', 'simple_bind_s', 'search_s']
+        )
+
+    def test_search_bind_multiple_users(self):
+        self._init_settings(
+            USER_SEARCH=LDAPSearch(
+                "ou=people,o=test", ldap.SCOPE_SUBTREE, '(uid=*)'
+            )
+        )
+
+        user = self.backend.authenticate(username='alice', password='password')
+
+        self.assertTrue(user is None)
+        self.assertEqual(
+            self.ldapobj.methods_called(),
+            ['initialize', 'simple_bind_s', 'search_s']
+        )
+
+    def test_search_bind_bad_password(self):
+        self._init_settings(
+            USER_SEARCH=LDAPSearch(
+                "ou=people,o=test", ldap.SCOPE_SUBTREE, '(uid=%(user)s)'
+            )
+        )
+
+        user = self.backend.authenticate(username='alice', password='bogus')
+
+        self.assertTrue(user is None)
+        self.assertEqual(
+            self.ldapobj.methods_called(),
+            ['initialize', 'simple_bind_s', 'search_s', 'simple_bind_s']
+        )
+
+    def test_search_bind_with_credentials(self):
+        self._init_settings(
+            BIND_DN='uid=bob,ou=people,o=test',
+            BIND_PASSWORD='password',
+            USER_SEARCH=LDAPSearch(
+                "ou=people,o=test", ldap.SCOPE_SUBTREE, '(uid=%(user)s)'
+            )
+        )
+
+        user = self.backend.authenticate(username='alice', password='password')
+
+        self.assertTrue(user is not None)
+        self.assertTrue(user.ldap_user is not None)
+        self.assertEqual(user.ldap_user.dn, self.alice[0])
+        self.assertEqual(user.ldap_user.attrs, ldap.cidict.cidict(self.alice[1]))
+        self.assertEqual(
+            self.ldapobj.methods_called(),
+            ['initialize', 'simple_bind_s', 'search_s', 'simple_bind_s']
+        )
+
+    def test_search_bind_with_bad_credentials(self):
+        self._init_settings(
+            BIND_DN='uid=bob,ou=people,o=test',
+            BIND_PASSWORD='bogus',
+            USER_SEARCH=LDAPSearch(
+                "ou=people,o=test", ldap.SCOPE_SUBTREE, '(uid=%(user)s)'
+            )
+        )
+
+        user = self.backend.authenticate(username='alice', password='password')
+
+        self.assertTrue(user is None)
+        self.assertEqual(
+            self.ldapobj.methods_called(),
+            ['initialize', 'simple_bind_s']
+        )
+
+    def test_unicode_user(self):
+        self._init_settings(
+            USER_DN_TEMPLATE='uid=%(user)s,ou=people,o=test',
+            USER_ATTR_MAP={'first_name': 'givenName', 'last_name': 'sn'}
+        )
+
+        user = self.backend.authenticate(username=u'dreßler', password='password')
+        self.assertTrue(user is not None)
+        self.assertEqual(user.username, u'dreßler')
+        self.assertEqual(user.last_name, u'Dreßler')
+
+    def test_cidict(self):
+        self._init_settings(
+            USER_DN_TEMPLATE='uid=%(user)s,ou=people,o=test',
+        )
+
+        user = self.backend.authenticate(username="alice", password="password")
+
+        self.assertTrue(isinstance(user.ldap_user.attrs, ldap.cidict.cidict))
+
+    def test_populate_user(self):
+        self._init_settings(
+            USER_DN_TEMPLATE='uid=%(user)s,ou=people,o=test',
+            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')
+
+        # init, bind as user, bind anonymous, lookup user attrs
+        self.assertEqual(
+            self.ldapobj.methods_called(),
+            ['initialize', 'simple_bind_s', 'simple_bind_s', 'search_s']
+        )
+
+    def test_bind_as_user(self):
+        self._init_settings(
+            USER_DN_TEMPLATE='uid=%(user)s,ou=people,o=test',
+            USER_ATTR_MAP={'first_name': 'givenName', 'last_name': 'sn'},
+            BIND_AS_AUTHENTICATING_USER=True,
+        )
+
+        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')
+
+        # init, bind as user, lookup user attrs
+        self.assertEqual(
+            self.ldapobj.methods_called(),
+            ['initialize', 'simple_bind_s', 'search_s']
+        )
+
+    def test_signal_populate_user(self):
+        self._init_settings(
+            USER_DN_TEMPLATE='uid=%(user)s,ou=people,o=test'
+        )
+
+        def handle_populate_user(sender, **kwargs):
+            self.assertTrue('user' in kwargs and 'ldap_user' in kwargs)
+            kwargs['user'].populate_user_handled = True
+        backend.populate_user.connect(handle_populate_user)
+
+        user = self.backend.authenticate(username='alice', password='password')
+
+        self.assertTrue(user.populate_user_handled)
+
+    def test_signal_populate_user_profile(self):
+        settings.AUTH_PROFILE_MODULE = 'django_auth_ldap.TestProfile'
+
+        self._init_settings(
+            USER_DN_TEMPLATE='uid=%(user)s,ou=people,o=test'
+        )
+
+        def handle_user_saved(sender, **kwargs):
+            if kwargs['created']:
+                TestProfile.objects.create(user=kwargs['instance'])
+
+        def handle_populate_user_profile(sender, **kwargs):
+            self.assertTrue('profile' in kwargs and 'ldap_user' in kwargs)
+            kwargs['profile'].populated = True
+
+        django.db.models.signals.post_save.connect(handle_user_saved, sender=User)
+        backend.populate_user_profile.connect(handle_populate_user_profile)
+
+        user = self.backend.authenticate(username='alice', password='password')
+
+        self.assertTrue(user.get_profile().populated)
+
+    def test_no_update_existing(self):
+        self._init_settings(
+            USER_DN_TEMPLATE='uid=%(user)s,ou=people,o=test',
+            USER_ATTR_MAP={'first_name': 'givenName', 'last_name': 'sn'},
+            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(
+            USER_DN_TEMPLATE='uid=%(user)s,ou=people,o=test',
+            GROUP_SEARCH=LDAPSearch('ou=groups,o=test', ldap.SCOPE_SUBTREE, '(objectClass=groupOfNames)'),
+            GROUP_TYPE=MemberDNGroupType(member_attr='member'),
+            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.assertTrue(alice is not None)
+        self.assertTrue(bob is None)
+        self.assertEqual(
+            self.ldapobj.methods_called(),
+            ['initialize', 'simple_bind_s', 'simple_bind_s', 'compare_s',
+             'initialize', 'simple_bind_s', 'simple_bind_s', 'compare_s']
+        )
+
+    def test_group_union(self):
+        self._init_settings(
+            USER_DN_TEMPLATE='uid=%(user)s,ou=people,o=test',
+            GROUP_SEARCH=LDAPSearchUnion(
+                LDAPSearch('ou=groups,o=test', ldap.SCOPE_SUBTREE, '(objectClass=groupOfNames)'),
+                LDAPSearch('ou=moregroups,o=test', ldap.SCOPE_SUBTREE, '(objectClass=groupOfNames)')
+            ),
+            GROUP_TYPE=MemberDNGroupType(member_attr='member'),
+            REQUIRE_GROUP="cn=other_gon,ou=moregroups,o=test"
+        )
+
+        alice = self.backend.authenticate(username='alice', password='password')
+        bob = self.backend.authenticate(username='bob', password='password')
+
+        self.assertTrue(alice is None)
+        self.assertTrue(bob is not None)
+        self.assertEqual(bob.ldap_user.group_names, set(['other_gon']))
+
+    def test_nested_group_union(self):
+        self._init_settings(
+            USER_DN_TEMPLATE='uid=%(user)s,ou=people,o=test',
+            GROUP_SEARCH=LDAPSearchUnion(
+                LDAPSearch('ou=groups,o=test', ldap.SCOPE_SUBTREE, '(objectClass=groupOfNames)'),
+                LDAPSearch('ou=moregroups,o=test', ldap.SCOPE_SUBTREE, '(objectClass=groupOfNames)')
+            ),
+            GROUP_TYPE=NestedMemberDNGroupType(member_attr='member'),
+            REQUIRE_GROUP="cn=other_gon,ou=moregroups,o=test"
+        )
+
+        alice = self.backend.authenticate(username='alice', password='password')
+        bob = self.backend.authenticate(username='bob', password='password')
+
+        self.assertTrue(alice is None)
+        self.assertTrue(bob is not None)
+        self.assertEqual(bob.ldap_user.group_names, set(['other_gon']))
+
+    def test_denied_group(self):
+        self._init_settings(
+            USER_DN_TEMPLATE='uid=%(user)s,ou=people,o=test',
+            GROUP_SEARCH=LDAPSearch('ou=groups,o=test', ldap.SCOPE_SUBTREE),
+            GROUP_TYPE=MemberDNGroupType(member_attr='member'),
+            DENY_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.assertTrue(alice is None)
+        self.assertTrue(bob is not None)
+        self.assertEqual(
+            self.ldapobj.methods_called(),
+            ['initialize', 'simple_bind_s', 'simple_bind_s', 'compare_s',
+             'initialize', 'simple_bind_s', 'simple_bind_s', 'compare_s']
+        )
+
+    def test_group_dns(self):
+        self._init_settings(
+            USER_DN_TEMPLATE='uid=%(user)s,ou=people,o=test',
+            GROUP_SEARCH=LDAPSearch('ou=groups,o=test', ldap.SCOPE_SUBTREE),
+            GROUP_TYPE=MemberDNGroupType(member_attr='member'),
+        )
+        alice = self.backend.authenticate(username='alice', password='password')
+
+        self.assertEqual(alice.ldap_user.group_dns, set((g[0].lower() for g in [self.active_gon, self.staff_gon, self.superuser_gon, self.nested_gon])))
+
+    def test_group_names(self):
+        self._init_settings(
+            USER_DN_TEMPLATE='uid=%(user)s,ou=people,o=test',
+            GROUP_SEARCH=LDAPSearch('ou=groups,o=test', ldap.SCOPE_SUBTREE),
+            GROUP_TYPE=MemberDNGroupType(member_attr='member'),
+        )
+        alice = self.backend.authenticate(username='alice', password='password')
+
+        self.assertEqual(alice.ldap_user.group_names, set(['active_gon', 'staff_gon', 'superuser_gon', 'nested_gon']))
+
+    def test_dn_group_membership(self):
+        self._init_settings(
+            USER_DN_TEMPLATE='uid=%(user)s,ou=people,o=test',
+            GROUP_SEARCH=LDAPSearch('ou=groups,o=test', ldap.SCOPE_SUBTREE),
+            GROUP_TYPE=MemberDNGroupType(member_attr='member'),
+            USER_FLAGS_BY_GROUP={
+                'is_active': "cn=active_gon,ou=groups,o=test",
+                'is_staff': ["cn=empty_gon,ou=groups,o=test",
+                             "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.assertTrue(alice.is_active)
+        self.assertTrue(alice.is_staff)
+        self.assertTrue(alice.is_superuser)
+        self.assertTrue(not bob.is_active)
+        self.assertTrue(not bob.is_staff)
+        self.assertTrue(not bob.is_superuser)
+
+    def test_posix_membership(self):
+        self._init_settings(
+            USER_DN_TEMPLATE='uid=%(user)s,ou=people,o=test',
+            GROUP_SEARCH=LDAPSearch('ou=groups,o=test', ldap.SCOPE_SUBTREE),
+            GROUP_TYPE=PosixGroupType(),
+            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.assertTrue(alice.is_active)
+        self.assertTrue(alice.is_staff)
+        self.assertTrue(alice.is_superuser)
+        self.assertTrue(not bob.is_active)
+        self.assertTrue(not bob.is_staff)
+        self.assertTrue(not bob.is_superuser)
+
+    def test_nested_dn_group_membership(self):
+        self._init_settings(
+            USER_DN_TEMPLATE='uid=%(user)s,ou=people,o=test',
+            GROUP_SEARCH=LDAPSearch('ou=groups,o=test', ldap.SCOPE_SUBTREE),
+            GROUP_TYPE=NestedMemberDNGroupType(member_attr='member'),
+            USER_FLAGS_BY_GROUP={
+                'is_active': "cn=parent_gon,ou=groups,o=test",
+                'is_staff': "cn=parent_gon,ou=groups,o=test",
+            }
+        )
+        alice = self.backend.authenticate(username='alice', password='password')
+        bob = self.backend.authenticate(username='bob', password='password')
+
+        self.assertTrue(alice.is_active)
+        self.assertTrue(alice.is_staff)
+        self.assertTrue(not bob.is_active)
+        self.assertTrue(not bob.is_staff)
+
+    def test_posix_missing_attributes(self):
+        self._init_settings(
+            USER_DN_TEMPLATE='uid=%(user)s,ou=people,o=test',
+            GROUP_SEARCH=LDAPSearch('ou=groups,o=test', ldap.SCOPE_SUBTREE),
+            GROUP_TYPE=PosixGroupType(),
+            USER_FLAGS_BY_GROUP={
+                'is_active': "cn=active_px,ou=groups,o=test"
+            }
+        )
+
+        nobody = self.backend.authenticate(username='nobody', password='password')
+
+        self.assertTrue(not nobody.is_active)
+
+    def test_profile_flags(self):
+        settings.AUTH_PROFILE_MODULE = 'django_auth_ldap.TestProfile'
+
+        self._init_settings(
+            USER_DN_TEMPLATE='uid=%(user)s,ou=people,o=test',
+            GROUP_SEARCH=LDAPSearch('ou=groups,o=test', ldap.SCOPE_SUBTREE),
+            GROUP_TYPE=MemberDNGroupType(member_attr='member'),
+            PROFILE_FLAGS_BY_GROUP={
+                'is_special': ["cn=superuser_gon,ou=groups,o=test"]
+            }
+        )
+
+        def handle_user_saved(sender, **kwargs):
+            if kwargs['created']:
+                TestProfile.objects.create(user=kwargs['instance'])
+
+        django.db.models.signals.post_save.connect(handle_user_saved, sender=User)
+
+        alice = self.backend.authenticate(username='alice', password='password')
+        bob = self.backend.authenticate(username='bob', password='password')
+
+        self.assertTrue(alice.get_profile().is_special)
+        self.assertTrue(not bob.get_profile().is_special)
+
+    def test_dn_group_permissions(self):
+        self._init_settings(
+            USER_DN_TEMPLATE='uid=%(user)s,ou=people,o=test',
+            GROUP_SEARCH=LDAPSearch('ou=groups,o=test', ldap.SCOPE_SUBTREE),
+            GROUP_TYPE=MemberDNGroupType(member_attr='member'),
+            FIND_GROUP_PERMS=True
+        )
+        self._init_groups()
+
+        alice = User.objects.create(username='alice')
+        alice = self.backend.get_user(alice.pk)
+
+        self.assertEqual(self.backend.get_group_permissions(alice), set(["auth.add_user", "auth.change_user"]))
+        self.assertEqual(self.backend.get_all_permissions(alice), set(["auth.add_user", "auth.change_user"]))
+        self.assertTrue(self.backend.has_perm(alice, "auth.add_user"))
+        self.assertTrue(self.backend.has_module_perms(alice, "auth"))
+
+    def test_empty_group_permissions(self):
+        self._init_settings(
+            USER_DN_TEMPLATE='uid=%(user)s,ou=people,o=test',
+            GROUP_SEARCH=LDAPSearch('ou=groups,o=test', ldap.SCOPE_SUBTREE),
+            GROUP_TYPE=MemberDNGroupType(member_attr='member'),
+            FIND_GROUP_PERMS=True
+        )
+        self._init_groups()
+
+        bob = User.objects.create(username='bob')
+        bob = self.backend.get_user(bob.pk)
+
+        self.assertEqual(self.backend.get_group_permissions(bob), set())
+        self.assertEqual(self.backend.get_all_permissions(bob), set())
+        self.assertTrue(not self.backend.has_perm(bob, "auth.add_user"))
+        self.assertTrue(not self.backend.has_module_perms(bob, "auth"))
+
+    def test_posix_group_permissions(self):
+        self._init_settings(
+            USER_DN_TEMPLATE='uid=%(user)s,ou=people,o=test',
+            GROUP_SEARCH=LDAPSearch('ou=groups,o=test', ldap.SCOPE_SUBTREE,
+                                    '(objectClass=posixGroup)'),
+            GROUP_TYPE=PosixGroupType(),
+            FIND_GROUP_PERMS=True
+        )
+        self._init_groups()
+
+        alice = User.objects.create(username='alice')
+        alice = self.backend.get_user(alice.pk)
+
+        self.assertEqual(self.backend.get_group_permissions(alice), set(["auth.add_user", "auth.change_user"]))
+        self.assertEqual(self.backend.get_all_permissions(alice), set(["auth.add_user", "auth.change_user"]))
+        self.assertTrue(self.backend.has_perm(alice, "auth.add_user"))
+        self.assertTrue(self.backend.has_module_perms(alice, "auth"))
+
+    def test_posix_group_permissions_no_gid(self):
+        self._init_settings(
+            USER_DN_TEMPLATE='uid=%(user)s,ou=people,o=test',
+            GROUP_SEARCH=LDAPSearch('ou=groups,o=test', ldap.SCOPE_SUBTREE,
+                                    '(objectClass=posixGroup)'),
+            GROUP_TYPE=PosixGroupType(),
+            FIND_GROUP_PERMS=True
+        )
+        self._init_groups()
+        self.ldapobj.modify_s(self.alice[0], [(ldap.MOD_DELETE, 'gidNumber', None)])
+        self.ldapobj.modify_s(self.active_px[0], [(ldap.MOD_ADD, 'memberUid', ['alice'])])
+
+        alice = User.objects.create(username='alice')
+        alice = self.backend.get_user(alice.pk)
+
+        self.assertEqual(self.backend.get_group_permissions(alice), set(["auth.add_user", "auth.change_user"]))
+        self.assertEqual(self.backend.get_all_permissions(alice), set(["auth.add_user", "auth.change_user"]))
+        self.assertTrue(self.backend.has_perm(alice, "auth.add_user"))
+        self.assertTrue(self.backend.has_module_perms(alice, "auth"))
+
+    def test_foreign_user_permissions(self):
+        self._init_settings(
+            USER_DN_TEMPLATE='uid=%(user)s,ou=people,o=test',
+            GROUP_SEARCH=LDAPSearch('ou=groups,o=test', ldap.SCOPE_SUBTREE),
+            GROUP_TYPE=MemberDNGroupType(member_attr='member'),
+            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(
+            USER_DN_TEMPLATE='uid=%(user)s,ou=people,o=test',
+            GROUP_SEARCH=LDAPSearch('ou=groups,o=test', ldap.SCOPE_SUBTREE),
+            GROUP_TYPE=MemberDNGroupType(member_attr='member'),
+            FIND_GROUP_PERMS=True,
+            CACHE_GROUPS=True
+        )
+        self._init_groups()
+
+        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.ldapobj.methods_called(),
+            ['initialize', 'simple_bind_s', 'search_s',
+             'initialize', 'simple_bind_s', 'search_s']
+        )
+
+    def test_group_mirroring(self):
+        self._init_settings(
+            USER_DN_TEMPLATE='uid=%(user)s,ou=people,o=test',
+            GROUP_SEARCH=LDAPSearch('ou=groups,o=test', ldap.SCOPE_SUBTREE,
+                                    '(objectClass=posixGroup)'),
+            GROUP_TYPE=PosixGroupType(),
+            MIRROR_GROUPS=True,
+        )
+
+        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(
+            USER_DN_TEMPLATE='uid=%(user)s,ou=people,o=test',
+            GROUP_SEARCH=LDAPSearch('ou=groups,o=test', ldap.SCOPE_SUBTREE,
+                                    '(objectClass=groupOfNames)'),
+            GROUP_TYPE=NestedMemberDNGroupType(member_attr='member'),
+            MIRROR_GROUPS=True,
+        )
+
+        alice = self.backend.authenticate(username='alice', password='password')
+
+        self.assertEqual(
+            set(Group.objects.all().values_list('name', flat=True)),
+            set(['active_gon', 'staff_gon', 'superuser_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(
+            USER_DN_TEMPLATE='uid=%(user)s,ou=people,o=test',
+            GROUP_SEARCH=LDAPSearch('ou=groups,o=test', ldap.SCOPE_SUBTREE),
+            GROUP_TYPE=MemberDNGroupType(member_attr='member'),
+            FIND_GROUP_PERMS=True,
+            AUTHORIZE_ALL_USERS=True
+        )
+        self._init_groups()
+
+        alice = User.objects.create(username='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(
+            USER_DN_TEMPLATE='uid=%(user)s,ou=people,o=test',
+        )
+
+        alice = self.backend.populate_user('alice')
+        bob = self.backend.populate_user('bob')
+
+        self.assertTrue(alice is not None)
+        self.assertEqual(alice.first_name, u"")
+        self.assertEqual(alice.last_name, u"")
+        self.assertTrue(alice.is_active)
+        self.assertTrue(not alice.is_staff)
+        self.assertTrue(not alice.is_superuser)
+        self.assertTrue(bob is not None)
+        self.assertEqual(bob.first_name, u"")
+        self.assertEqual(bob.last_name, u"")
+        self.assertTrue(bob.is_active)
+        self.assertTrue(not bob.is_staff)
+        self.assertTrue(not bob.is_superuser)
+
+    def test_populate_without_auth(self):
+        self._init_settings(
+            USER_DN_TEMPLATE='uid=%(user)s,ou=people,o=test',
+            ALWAYS_UPDATE_USER=False,
+            USER_ATTR_MAP={'first_name': 'givenName', 'last_name': 'sn'},
+            GROUP_SEARCH=LDAPSearch('ou=groups,o=test', ldap.SCOPE_SUBTREE),
+            GROUP_TYPE=GroupOfNamesType(),
+            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.assertTrue(alice is not None)
+        self.assertEqual(alice.first_name, u"Alice")
+        self.assertEqual(alice.last_name, u"Adams")
+        self.assertTrue(alice.is_active)
+        self.assertTrue(alice.is_staff)
+        self.assertTrue(alice.is_superuser)
+        self.assertTrue(bob is not None)
+        self.assertEqual(bob.first_name, u"Robert")
+        self.assertEqual(bob.last_name, u"Barker")
+        self.assertTrue(not bob.is_active)
+        self.assertTrue(not bob.is_staff)
+        self.assertTrue(not bob.is_superuser)
+
+    def test_populate_bogus_user(self):
+        self._init_settings(
+            USER_DN_TEMPLATE='uid=%(user)s,ou=people,o=test',
+        )
+
+        bogus = self.backend.populate_user('bogus')
+
+        self.assertEqual(bogus, None)
+
+    def test_start_tls_missing(self):
+        self._init_settings(
+            USER_DN_TEMPLATE='uid=%(user)s,ou=people,o=test',
+            START_TLS=False,
+        )
+
+        self.assertTrue(not self.ldapobj.tls_enabled)
+        self.backend.authenticate(username='alice', password='password')
+        self.assertTrue(not self.ldapobj.tls_enabled)
+
+    def test_start_tls(self):
+        self._init_settings(
+            USER_DN_TEMPLATE='uid=%(user)s,ou=people,o=test',
+            START_TLS=True,
+        )
+
+        self.assertTrue(not self.ldapobj.tls_enabled)
+        self.backend.authenticate(username='alice', password='password')
+        self.assertTrue(self.ldapobj.tls_enabled)
+
+    def test_null_search_results(self):
+        """
+        Make sure we're not phased by referrals.
+        """
+        self._init_settings(
+            USER_SEARCH=LDAPSearch(
+                "ou=people,o=test", ldap.SCOPE_SUBTREE, '(uid=%(user)s)'
+            )
+        )
+        self.backend.authenticate(username='alice', password='password')
+
+    def test_union_search(self):
+        self._init_settings(
+            USER_SEARCH=LDAPSearchUnion(
+                LDAPSearch("ou=groups,o=test", ldap.SCOPE_SUBTREE, '(uid=%(user)s)'),
+                LDAPSearch("ou=people,o=test", ldap.SCOPE_SUBTREE, '(uid=%(user)s)'),
+            )
+        )
+        alice = self.backend.authenticate(username='alice', password='password')
+
+        self.assertTrue(alice is not None)
+
+        self.assertEqual(
+            self.ldapobj.methods_called(),
+            ['initialize', 'simple_bind_s', 'search', 'search', 'result',
+             'result', 'simple_bind_s']
+        )
+
+    def test_deny_empty_password(self):
+        self._init_settings(
+            USER_DN_TEMPLATE='uid=%(user)s,ou=people,o=test',
+        )
+
+        alice = self.backend.authenticate(username=u'alice', password=u'')
+
+        self.assertEqual(alice, None)
+        self.assertEqual(self.ldapobj.methods_called(), [])
+
+    def test_permit_empty_password(self):
+        self._init_settings(
+            USER_DN_TEMPLATE='uid=%(user)s,ou=people,o=test',
+            PERMIT_EMPTY_PASSWORD=True,
+        )
+
+        alice = self.backend.authenticate(username=u'alice', password=u'')
+
+        self.assertEqual(alice, None)
+        self.assertEqual(
+            self.ldapobj.methods_called(),
+            ['initialize', 'simple_bind_s']
+        )
+
+    def test_pickle(self):
+        self._init_settings(
+            USER_DN_TEMPLATE='uid=%(user)s,ou=people,o=test',
+            GROUP_SEARCH=LDAPSearch('ou=groups,o=test', ldap.SCOPE_SUBTREE),
+            GROUP_TYPE=MemberDNGroupType(member_attr='member'),
+            FIND_GROUP_PERMS=True
+        )
+        self._init_groups()
+
+        alice0 = self.backend.authenticate(username=u'alice', password=u'password')
+
+        pickled = pickle.dumps(alice0, pickle.HIGHEST_PROTOCOL)
+        alice = pickle.loads(pickled)
+        alice.ldap_user.backend.settings = alice0.ldap_user.backend.settings
+
+        self.assertTrue(alice is not None)
+        self.assertEqual(self.backend.get_group_permissions(alice), set(["auth.add_user", "auth.change_user"]))
+        self.assertEqual(self.backend.get_all_permissions(alice), set(["auth.add_user", "auth.change_user"]))
+        self.assertTrue(self.backend.has_perm(alice, "auth.add_user"))
+        self.assertTrue(self.backend.has_module_perms(alice, "auth"))
+
+    def _init_settings(self, **kwargs):
+        self.backend.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)
+
+
+# Python 2.5-compatible class decoration
+LDAPTest = unittest.skipIf(mockldap is None, "django_auth_ldap tests require the mockldap package.")(LDAPTest)

+ 26 - 25
desktop/core/ext-py/django-auth-ldap-1.0.7/docs/Makefile → desktop/core/ext-py/django-auth-ldap-1.2.0/docs/Makefile

@@ -5,11 +5,12 @@
 SPHINXOPTS	  =
 SPHINXBUILD	  = sphinx-build
 PAPER		  =
+BUILDDIR          = build
 
 # Internal variables.
-PAPEROPT_a4		= -D latex_paper_size=a4
+PAPEROPT_a4	= -D latex_paper_size=a4
 PAPEROPT_letter = -D latex_paper_size=letter
-ALLSPHINXOPTS	= -d _build/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
+ALLSPHINXOPTS	= -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source
 
 .PHONY: help clean html dirhtml pickle json htmlhelp qthelp latex changes linkcheck doctest
 
@@ -27,66 +28,66 @@ help:
 	@echo "	 doctest   to run all doctests embedded in the documentation (if enabled)"
 
 clean:
-	-rm -rf _build
+	-rm -rf $(BUILDDIR)
 
 html:
-	$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) _build/html
+	$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
 	@echo
-	@echo "Build finished. The HTML pages are in _build/html."
+	@echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
 
 dirhtml:
-	$(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) _build/dirhtml
+	$(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
 	@echo
-	@echo "Build finished. The HTML pages are in _build/dirhtml."
+	@echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
 
 pickle:
-	$(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) _build/pickle
+	$(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
 	@echo
 	@echo "Build finished; now you can process the pickle files."
 
 json:
-	$(SPHINXBUILD) -b json $(ALLSPHINXOPTS) _build/json
+	$(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
 	@echo
 	@echo "Build finished; now you can process the JSON files."
 
 htmlhelp:
-	$(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) _build/htmlhelp
+	$(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
 	@echo
 	@echo "Build finished; now you can run HTML Help Workshop with the" \
-		  ".hhp project file in _build/htmlhelp."
+		  ".hhp project file in $(BUILDDIR)/htmlhelp."
 
 qthelp:
-	$(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) _build/qthelp
+	$(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/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"
+		  ".qhcp project file in $(BUILDDIR)/qthelp, like this:"
+	@echo "# qcollectiongenerator $(BUILDDIR)/qthelp/django-auth-ldap.qhcp"
 	@echo "To view the help file:"
-	@echo "# assistant -collectionFile _build/qthelp/django-auth-ldap.qhc"
+	@echo "# assistant -collectionFile $(BUILDDIR)/qthelp/django-auth-ldap.qhc"
 
 latex:
-	$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) _build/latex
+	$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
 	@echo
-	@echo "Build finished; the LaTeX files are in _build/latex."
+	@echo "Build finished; the LaTeX files are in $(BUILDDIR)/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
+	$(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
 	@echo
-	@echo "The overview file is in _build/changes."
+	@echo "The overview file is in $(BUILDDIR)/changes."
 
 linkcheck:
-	$(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) _build/linkcheck
+	$(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
 	@echo
 	@echo "Link check complete; look for any errors in the above output " \
-		  "or in _build/linkcheck/output.txt."
+		  "or in $(BUILDDIR)/linkcheck/output.txt."
 
 doctest:
-	$(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) _build/doctest
+	$(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
 	@echo "Testing of doctests in the sources finished, look at the " \
-		  "results in _build/doctest/output.txt."
+		  "results in $(BUILDDIR)/doctest/output.txt."
 
 zip:
-	rm _build/html.zip || true
-	cd _build/html && zip -R ../html.zip '*' -x .buildinfo -x '_sources/*'
+	rm $(BUILDDIR)/html.zip || true
+	cd $(BUILDDIR)/html && zip -R ../html.zip '*' -x .buildinfo

+ 11 - 0
desktop/core/ext-py/django-auth-ldap-1.2.0/docs/_templates/globaltoc.html

@@ -0,0 +1,11 @@
+{#
+    basic/globaltoc.html
+    ~~~~~~~~~~~~~~~~~~~~
+
+    Sphinx sidebar template: global table of contents.
+
+    :copyright: Copyright 2007-2011 by the Sphinx team, see AUTHORS.
+    :license: BSD, see LICENSE for details.
+#}
+<h3><a href="{{ pathto(master_doc) }}">{{ _('Table Of Contents') }}</a></h3>
+{{ toctree(maxdepth=2) }}

+ 4 - 0
desktop/core/ext-py/django-auth-ldap-1.2.0/docs/archive/versions/1.0.19/.buildinfo

@@ -0,0 +1,4 @@
+# Sphinx build info version 1
+# This file hashes the configuration used when building these files. When it is not found, a full rebuild will be done.
+config: beaa0dacdfd5534fbb362de5fa44bb4c
+tags: fbb0d17656682115ca4d033fb2f83ba1

+ 153 - 28
desktop/core/ext-py/django-auth-ldap-1.0.7/docs/index.rst → desktop/core/ext-py/django-auth-ldap-1.2.0/docs/archive/versions/1.0.19/_sources/index.txt

@@ -75,6 +75,20 @@ efficient) equivalent::
 
     AUTH_LDAP_USER_DN_TEMPLATE = "uid=%(user)s,ou=users,dc=example,dc=com"
 
+LDAP is fairly flexible when it comes to matching DNs.
+:class:`~django_auth_ldap.backend.LDAPBackend` make an effort to accommodate
+this by forcing usernames to lower case when creating Django users and trimming
+whitespace when authenticating.
+
+By default, all LDAP operations are performed with the :ref:`AUTH_LDAP_BIND_DN`
+and :ref:`AUTH_LDAP_BIND_PASSWORD` credentials, not with the user's. Otherwise,
+the LDAP connection would be bound as the authenticating user during login
+requests and as the default credentials during other requests, so you would see
+inconsistent LDAP attributes depending on the nature of the Django view. If
+you're willing to accept the inconsistency in order to retrieve attributes
+while bound as the authenticating user. see
+:ref:`AUTH_LDAP_BIND_AS_AUTHENTICATING_USER`.
+
 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
@@ -127,7 +141,7 @@ configuration must be of this type and part of the search results.
 
     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)"
     )
@@ -135,9 +149,13 @@ configuration must be of this type and part of the search results.
 
 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::
+group will successfully authenticate. :ref:`AUTH_LDAP_DENY_GROUP` is the
+reverse: if given, members of this group will be rejected.
+
+.. code-block:: python
 
     AUTH_LDAP_REQUIRE_GROUP = "cn=enabled,ou=groups,dc=example,dc=com"
+    AUTH_LDAP_DENY_GROUP = "cn=disabled,ou=groups,dc=example,dc=com"
 
 More advanced uses of groups are covered in the next two sections.
 
@@ -148,7 +166,11 @@ 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.
+object to represent him in the database. Because the LDAP search is
+case-insenstive, the default implementation also searches for existing Django
+users with an iexact query and new users are created with lowercase usernames.
+See :meth:`~django_auth_ldap.backend.LDAPBackend.get_or_create_user` if you'd
+like to override this behavior.
 
 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
@@ -170,8 +192,8 @@ 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::
+dictionaries that map user and profile model keys, respectively, to
+(case-insensitive) LDAP attribute names::
 
     AUTH_LDAP_USER_ATTR_MAP = {"first_name": "givenName", "last_name": "sn"}
     AUTH_LDAP_PROFILE_ATTR_MAP = {"home_directory": "homeDirectory"}
@@ -185,27 +207,47 @@ group membership::
         "is_superuser": "cn=superuser,ou=groups,dc=example,dc=com"
     }
 
+    AUTH_LDAP_PROFILE_FLAGS_BY_GROUP = {
+        "is_awesome": "cn=awesome,ou=django,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.
+should feel free to create yourself.
+:meth:`~django_auth_ldap.backend.LDAPBackend.populate_user` returns the new
+:class:`~django.contrib.auth.models.User` or `None` if the user could not be
+found in LDAP.
 
 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:
+``user.ldap_user`` is an object with four public properties. The group
+properties are, of course, only valid if groups are configured.
 
     * ``dn``: The user's distinguished name.
     * ``attrs``: The user's LDAP attributes as a dictionary of lists of string
-      values.
+      values. The dictionaries are modified to use case-insensitive keys.
+    * ``group_dns``: The set of groups that this user belongs to, as DNs.
+    * ``group_names``: The set of groups that this user belongs to, as simple
+      names. These are the names that will be used if
+      :ref:`AUTH_LDAP_MIRROR_GROUPS` is used.
 
 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.
 
+If you would like to perform any additional population of user or profile
+objects, django_auth_ldap exposes two custom signals to help:
+:data:`~django_auth_ldap.backend.populate_user` and
+:data:`~django_auth_ldap.backend.populate_user_profile`. These are sent after
+the backend has finished populating the respective objects and before they are
+saved to the database. You can use this to propagate additional information from
+the LDAP directory to the user and profile objects any way you like.
+
 .. note::
 
     Users created by :class:`~django_auth_ldap.backend.LDAPBackend` will have an
@@ -378,8 +420,9 @@ and arguments are included for completeness.
     )
     AUTH_LDAP_GROUP_TYPE = GroupOfNamesType(name_attr="cn")
 
-    # Only users in this group can log in.
+    # Simple group restrictions
     AUTH_LDAP_REQUIRE_GROUP = "cn=enabled,ou=django,ou=groups,dc=example,dc=com"
+    AUTH_LDAP_DENY_GROUP = "cn=disabled,ou=django,ou=groups,dc=example,dc=com"
 
     # Populate the Django user from the LDAP directory.
     AUTH_LDAP_USER_ATTR_MAP = {
@@ -397,10 +440,14 @@ and arguments are included for completeness.
         "is_staff": "cn=staff,ou=django,ou=groups,dc=example,dc=com",
         "is_superuser": "cn=superuser,ou=django,ou=groups,dc=example,dc=com"
     }
-    
+
+    AUTH_LDAP_PROFILE_FLAGS_BY_GROUP = {
+        "is_awesome": "cn=awesome,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
 
@@ -447,6 +494,24 @@ 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_AS_AUTHENTICATING_USER:
+
+AUTH_LDAP_BIND_AS_AUTHENTICATING_USER
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Default: ``False``
+
+If ``True``, authentication will leave the LDAP connection bound as the
+authenticating user, rather than forcing it to re-bind with the default
+credentials after authentication succeeds. This may be desirable if you do not
+have global credentials that are able to access the user's attributes.
+django-auth-ldap never stores the user's password, so this only applies to
+requests where the user is authenticated. Thus, the downside to this setting is
+that LDAP results may vary based on whether the user was authenticated earlier
+in the Django view, which could be surprising to code not directly concerned
+with authentication.
+
+
 .. _AUTH_LDAP_BIND_DN:
 
 AUTH_LDAP_BIND_DN
@@ -460,7 +525,7 @@ 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.
+we'll also use it to determine group membership.
 
 
 .. _AUTH_LDAP_BIND_PASSWORD:
@@ -496,15 +561,15 @@ 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_DENY_GROUP:
 
-AUTH_LDAP_GROUP_CACHE_TIMEOUT
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+AUTH_LDAP_DENY_GROUP
+~~~~~~~~~~~~~~~~~~~~~~~
 
 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.
+The distinguished name of a group; authentication will fail for any user
+that belongs to this group.
 
 
 .. _AUTH_LDAP_FIND_GROUP_PERMS:
@@ -530,6 +595,17 @@ A dictionary of options to pass to ``ldap.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_GROUP_SEARCH:
 
 AUTH_LDAP_GROUP_SEARCH
@@ -578,6 +654,18 @@ 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_PROFILE_FLAGS_BY_GROUP:
+
+AUTH_LDAP_PROFILE_FLAGS_BY_GROUP
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Default: ``{}``
+
+A mapping from boolean profile field names to distinguished names of LDAP
+groups. The corresponding field in a user's profile is set to ``True`` or
+``False`` according to whether the user is a member of the group.
+
+
 .. _AUTH_LDAP_REQUIRE_GROUP:
 
 AUTH_LDAP_REQUIRE_GROUP
@@ -585,8 +673,8 @@ AUTH_LDAP_REQUIRE_GROUP
 
 Default: ``None``
 
-The distinguished name of a group that a user must belong to in order to
-successfully authenticate.
+The distinguished name of a group; authentication will fail for any user that
+does not belong to this group.
 
 
 .. _AUTH_LDAP_SERVER_URI:
@@ -663,6 +751,20 @@ in the directory. The filter parameter should contain the placeholder
 authentication to succeed.
 
 
+Module Properties
+-----------------
+
+.. module:: django_auth_ldap
+
+.. data:: version
+
+    The library's current version number as a 3-tuple.
+
+.. data:: version_string
+
+    The library's current version number as a string.
+
+
 Configuration
 -------------
 
@@ -683,9 +785,9 @@ Configuration
     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``.
@@ -704,9 +806,9 @@ Configuration
     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.
 
@@ -718,7 +820,7 @@ Configuration
     traversed to determine membership.
 
     .. method:: __init__(member_attr, name_attr='cn')
-    
+
         As above.
 
 
@@ -784,6 +886,28 @@ Backend
 
 .. module:: django_auth_ldap.backend
 
+.. data:: populate_user
+
+    This is a Django signal that is sent when clients should perform additional
+    customization of a :class:`~django.contrib.auth.models.User` object. It is
+    sent after a user has been authenticated and the backend has finished
+    populating it, and just before it is saved. The client may take this
+    opportunity to populate additional model fields, perhaps based on
+    ``ldap_user.attrs``. This signal has two keyword arguments: ``user`` is the
+    :class:`~django.contrib.auth.models.User` object and ``ldap_user`` is the
+    same as ``user.ldap_user``. The sender is the
+    :class:`~django_auth_ldap.backend.LDAPBackend` class.
+
+.. data:: populate_user_profile
+
+    Like :data:`~django_auth_ldap.backend.populate_user`, but sent for the user
+    profile object. This will only be sent if the user has an existing profile.
+    As with :data:`~django_auth_ldap.backend.populate_user`, it is sent after the
+    backend has finished setting properties and before the object is saved. This
+    signal has two keyword arguments: ``profile`` is the user profile object and
+    ``ldap_user`` is the same as ``user.ldap_user``. The sender is the
+    :class:`~django_auth_ldap.backend.LDAPBackend` class.
+
 .. class:: LDAPBackend
 
     :class:`~django_auth_ldap.backend.LDAPBackend` has one method that may be
@@ -806,12 +930,13 @@ Backend
         ``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.
+        The default implementation calls ``User.objects.get_or_create()``, using
+        a case-insensitive query and creating new users with lowercase
+        usernames. 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.

BIN
desktop/core/ext-py/django-auth-ldap-1.2.0/docs/archive/versions/1.0.19/_static/ajax-loader.gif


+ 540 - 0
desktop/core/ext-py/django-auth-ldap-1.2.0/docs/archive/versions/1.0.19/_static/basic.css

@@ -0,0 +1,540 @@
+/*
+ * basic.css
+ * ~~~~~~~~~
+ *
+ * Sphinx stylesheet -- basic theme.
+ *
+ * :copyright: Copyright 2007-2011 by the Sphinx team, see AUTHORS.
+ * :license: BSD, see LICENSE for details.
+ *
+ */
+
+/* -- main layout ----------------------------------------------------------- */
+
+div.clearer {
+    clear: both;
+}
+
+/* -- relbar ---------------------------------------------------------------- */
+
+div.related {
+    width: 100%;
+    font-size: 90%;
+}
+
+div.related h3 {
+    display: none;
+}
+
+div.related ul {
+    margin: 0;
+    padding: 0 0 0 10px;
+    list-style: none;
+}
+
+div.related li {
+    display: inline;
+}
+
+div.related li.right {
+    float: right;
+    margin-right: 5px;
+}
+
+/* -- sidebar --------------------------------------------------------------- */
+
+div.sphinxsidebarwrapper {
+    padding: 10px 5px 0 10px;
+}
+
+div.sphinxsidebar {
+    float: left;
+    width: 230px;
+    margin-left: -100%;
+    font-size: 90%;
+}
+
+div.sphinxsidebar ul {
+    list-style: none;
+}
+
+div.sphinxsidebar ul ul,
+div.sphinxsidebar ul.want-points {
+    margin-left: 20px;
+    list-style: square;
+}
+
+div.sphinxsidebar ul ul {
+    margin-top: 0;
+    margin-bottom: 0;
+}
+
+div.sphinxsidebar form {
+    margin-top: 10px;
+}
+
+div.sphinxsidebar input {
+    border: 1px solid #98dbcc;
+    font-family: sans-serif;
+    font-size: 1em;
+}
+
+div.sphinxsidebar #searchbox input[type="text"] {
+    width: 170px;
+}
+
+div.sphinxsidebar #searchbox input[type="submit"] {
+    width: 30px;
+}
+
+img {
+    border: 0;
+}
+
+/* -- search page ----------------------------------------------------------- */
+
+ul.search {
+    margin: 10px 0 0 20px;
+    padding: 0;
+}
+
+ul.search li {
+    padding: 5px 0 5px 20px;
+    background-image: url(file.png);
+    background-repeat: no-repeat;
+    background-position: 0 7px;
+}
+
+ul.search li a {
+    font-weight: bold;
+}
+
+ul.search li div.context {
+    color: #888;
+    margin: 2px 0 0 30px;
+    text-align: left;
+}
+
+ul.keywordmatches li.goodmatch a {
+    font-weight: bold;
+}
+
+/* -- index page ------------------------------------------------------------ */
+
+table.contentstable {
+    width: 90%;
+}
+
+table.contentstable p.biglink {
+    line-height: 150%;
+}
+
+a.biglink {
+    font-size: 1.3em;
+}
+
+span.linkdescr {
+    font-style: italic;
+    padding-top: 5px;
+    font-size: 90%;
+}
+
+/* -- general index --------------------------------------------------------- */
+
+table.indextable {
+    width: 100%;
+}
+
+table.indextable td {
+    text-align: left;
+    vertical-align: top;
+}
+
+table.indextable dl, table.indextable dd {
+    margin-top: 0;
+    margin-bottom: 0;
+}
+
+table.indextable tr.pcap {
+    height: 10px;
+}
+
+table.indextable tr.cap {
+    margin-top: 10px;
+    background-color: #f2f2f2;
+}
+
+img.toggler {
+    margin-right: 3px;
+    margin-top: 3px;
+    cursor: pointer;
+}
+
+div.modindex-jumpbox {
+    border-top: 1px solid #ddd;
+    border-bottom: 1px solid #ddd;
+    margin: 1em 0 1em 0;
+    padding: 0.4em;
+}
+
+div.genindex-jumpbox {
+    border-top: 1px solid #ddd;
+    border-bottom: 1px solid #ddd;
+    margin: 1em 0 1em 0;
+    padding: 0.4em;
+}
+
+/* -- general body styles --------------------------------------------------- */
+
+a.headerlink {
+    visibility: hidden;
+}
+
+h1:hover > a.headerlink,
+h2:hover > a.headerlink,
+h3:hover > a.headerlink,
+h4:hover > a.headerlink,
+h5:hover > a.headerlink,
+h6:hover > a.headerlink,
+dt:hover > a.headerlink {
+    visibility: visible;
+}
+
+div.body p.caption {
+    text-align: inherit;
+}
+
+div.body td {
+    text-align: left;
+}
+
+.field-list ul {
+    padding-left: 1em;
+}
+
+.first {
+    margin-top: 0 !important;
+}
+
+p.rubric {
+    margin-top: 30px;
+    font-weight: bold;
+}
+
+img.align-left, .figure.align-left, object.align-left {
+    clear: left;
+    float: left;
+    margin-right: 1em;
+}
+
+img.align-right, .figure.align-right, object.align-right {
+    clear: right;
+    float: right;
+    margin-left: 1em;
+}
+
+img.align-center, .figure.align-center, object.align-center {
+  display: block;
+  margin-left: auto;
+  margin-right: auto;
+}
+
+.align-left {
+    text-align: left;
+}
+
+.align-center {
+    text-align: center;
+}
+
+.align-right {
+    text-align: right;
+}
+
+/* -- sidebars -------------------------------------------------------------- */
+
+div.sidebar {
+    margin: 0 0 0.5em 1em;
+    border: 1px solid #ddb;
+    padding: 7px 7px 0 7px;
+    background-color: #ffe;
+    width: 40%;
+    float: right;
+}
+
+p.sidebar-title {
+    font-weight: bold;
+}
+
+/* -- topics ---------------------------------------------------------------- */
+
+div.topic {
+    border: 1px solid #ccc;
+    padding: 7px 7px 0 7px;
+    margin: 10px 0 10px 0;
+}
+
+p.topic-title {
+    font-size: 1.1em;
+    font-weight: bold;
+    margin-top: 10px;
+}
+
+/* -- admonitions ----------------------------------------------------------- */
+
+div.admonition {
+    margin-top: 10px;
+    margin-bottom: 10px;
+    padding: 7px;
+}
+
+div.admonition dt {
+    font-weight: bold;
+}
+
+div.admonition dl {
+    margin-bottom: 0;
+}
+
+p.admonition-title {
+    margin: 0px 10px 5px 0px;
+    font-weight: bold;
+}
+
+div.body p.centered {
+    text-align: center;
+    margin-top: 25px;
+}
+
+/* -- tables ---------------------------------------------------------------- */
+
+table.docutils {
+    border: 0;
+    border-collapse: collapse;
+}
+
+table.docutils td, table.docutils th {
+    padding: 1px 8px 1px 5px;
+    border-top: 0;
+    border-left: 0;
+    border-right: 0;
+    border-bottom: 1px solid #aaa;
+}
+
+table.field-list td, table.field-list th {
+    border: 0 !important;
+}
+
+table.footnote td, table.footnote th {
+    border: 0 !important;
+}
+
+th {
+    text-align: left;
+    padding-right: 5px;
+}
+
+table.citation {
+    border-left: solid 1px gray;
+    margin-left: 1px;
+}
+
+table.citation td {
+    border-bottom: none;
+}
+
+/* -- other body styles ----------------------------------------------------- */
+
+ol.arabic {
+    list-style: decimal;
+}
+
+ol.loweralpha {
+    list-style: lower-alpha;
+}
+
+ol.upperalpha {
+    list-style: upper-alpha;
+}
+
+ol.lowerroman {
+    list-style: lower-roman;
+}
+
+ol.upperroman {
+    list-style: upper-roman;
+}
+
+dl {
+    margin-bottom: 15px;
+}
+
+dd p {
+    margin-top: 0px;
+}
+
+dd ul, dd table {
+    margin-bottom: 10px;
+}
+
+dd {
+    margin-top: 3px;
+    margin-bottom: 10px;
+    margin-left: 30px;
+}
+
+dt:target, .highlighted {
+    background-color: #fbe54e;
+}
+
+dl.glossary dt {
+    font-weight: bold;
+    font-size: 1.1em;
+}
+
+.field-list ul {
+    margin: 0;
+    padding-left: 1em;
+}
+
+.field-list p {
+    margin: 0;
+}
+
+.refcount {
+    color: #060;
+}
+
+.optional {
+    font-size: 1.3em;
+}
+
+.versionmodified {
+    font-style: italic;
+}
+
+.system-message {
+    background-color: #fda;
+    padding: 5px;
+    border: 3px solid red;
+}
+
+.footnote:target  {
+    background-color: #ffa;
+}
+
+.line-block {
+    display: block;
+    margin-top: 1em;
+    margin-bottom: 1em;
+}
+
+.line-block .line-block {
+    margin-top: 0;
+    margin-bottom: 0;
+    margin-left: 1.5em;
+}
+
+.guilabel, .menuselection {
+    font-family: sans-serif;
+}
+
+.accelerator {
+    text-decoration: underline;
+}
+
+.classifier {
+    font-style: oblique;
+}
+
+abbr, acronym {
+    border-bottom: dotted 1px;
+    cursor: help;
+}
+
+/* -- code displays --------------------------------------------------------- */
+
+pre {
+    overflow: auto;
+    overflow-y: hidden;  /* fixes display issues on Chrome browsers */
+}
+
+td.linenos pre {
+    padding: 5px 0px;
+    border: 0;
+    background-color: transparent;
+    color: #aaa;
+}
+
+table.highlighttable {
+    margin-left: 0.5em;
+}
+
+table.highlighttable td {
+    padding: 0 0.5em 0 0.5em;
+}
+
+tt.descname {
+    background-color: transparent;
+    font-weight: bold;
+    font-size: 1.2em;
+}
+
+tt.descclassname {
+    background-color: transparent;
+}
+
+tt.xref, a tt {
+    background-color: transparent;
+    font-weight: bold;
+}
+
+h1 tt, h2 tt, h3 tt, h4 tt, h5 tt, h6 tt {
+    background-color: transparent;
+}
+
+.viewcode-link {
+    float: right;
+}
+
+.viewcode-back {
+    float: right;
+    font-family: sans-serif;
+}
+
+div.viewcode-block:target {
+    margin: -1px -10px;
+    padding: 0 10px;
+}
+
+/* -- math display ---------------------------------------------------------- */
+
+img.math {
+    vertical-align: middle;
+}
+
+div.body div.math p {
+    text-align: center;
+}
+
+span.eqno {
+    float: right;
+}
+
+/* -- printout stylesheet --------------------------------------------------- */
+
+@media print {
+    div.document,
+    div.documentwrapper,
+    div.bodywrapper {
+        margin: 0 !important;
+        width: 100%;
+    }
+
+    div.sphinxsidebar,
+    div.related,
+    div.footer,
+    #top-link {
+        display: none;
+    }
+}

BIN
desktop/core/ext-py/django-auth-ldap-1.2.0/docs/archive/versions/1.0.19/_static/comment-bright.png


BIN
desktop/core/ext-py/django-auth-ldap-1.2.0/docs/archive/versions/1.0.19/_static/comment-close.png


BIN
desktop/core/ext-py/django-auth-ldap-1.2.0/docs/archive/versions/1.0.19/_static/comment.png


+ 260 - 0
desktop/core/ext-py/django-auth-ldap-1.2.0/docs/archive/versions/1.0.19/_static/default.css

@@ -0,0 +1,260 @@
+/*
+ * default.css_t
+ * ~~~~~~~~~~~~~
+ *
+ * Sphinx stylesheet -- default theme.
+ *
+ * :copyright: Copyright 2007-2011 by the Sphinx team, see AUTHORS.
+ * :license: BSD, see LICENSE for details.
+ *
+ */
+
+@import url("basic.css");
+
+/* -- page layout ----------------------------------------------------------- */
+
+body {
+    font-family: sans-serif;
+    font-size: 100%;
+    background-color: #11303d;
+    color: #000;
+    margin: 0;
+    padding: 0;
+}
+
+div.document {
+    background-color: #1c4e63;
+}
+
+div.documentwrapper {
+    float: left;
+    width: 100%;
+}
+
+div.bodywrapper {
+    margin: 0 0 0 230px;
+}
+
+div.body {
+    background-color: #ffffff;
+    color: #000000;
+    padding: 0 20px 30px 20px;
+}
+div.bodywrapper {
+    margin: 0 230px 0 0;
+}
+
+div.footer {
+    color: #ffffff;
+    width: 100%;
+    padding: 9px 0 9px 0;
+    text-align: center;
+    font-size: 75%;
+}
+
+div.footer a {
+    color: #ffffff;
+    text-decoration: underline;
+}
+
+div.related {
+    background-color: #133f52;
+    line-height: 30px;
+    color: #ffffff;
+}
+
+div.related a {
+    color: #ffffff;
+}
+
+div.sphinxsidebar {
+    float: right;
+}
+
+div.sphinxsidebar h3 {
+    font-family: 'Trebuchet MS', sans-serif;
+    color: #ffffff;
+    font-size: 1.4em;
+    font-weight: normal;
+    margin: 0;
+    padding: 0;
+}
+
+div.sphinxsidebar h3 a {
+    color: #ffffff;
+}
+
+div.sphinxsidebar h4 {
+    font-family: 'Trebuchet MS', sans-serif;
+    color: #ffffff;
+    font-size: 1.3em;
+    font-weight: normal;
+    margin: 5px 0 0 0;
+    padding: 0;
+}
+
+div.sphinxsidebar p {
+    color: #ffffff;
+}
+
+div.sphinxsidebar p.topless {
+    margin: 5px 10px 10px 10px;
+}
+
+div.sphinxsidebar ul {
+    margin: 10px;
+    padding: 0;
+    color: #ffffff;
+}
+
+div.sphinxsidebar a {
+    color: #98dbcc;
+}
+
+div.sphinxsidebar input {
+    border: 1px solid #98dbcc;
+    font-family: sans-serif;
+    font-size: 1em;
+}
+
+
+
+/* -- hyperlink styles ------------------------------------------------------ */
+
+a {
+    color: #355f7c;
+    text-decoration: none;
+}
+
+a:visited {
+    color: #355f7c;
+    text-decoration: none;
+}
+
+a:hover {
+    text-decoration: underline;
+}
+
+
+
+/* -- body styles ----------------------------------------------------------- */
+
+div.body h1,
+div.body h2,
+div.body h3,
+div.body h4,
+div.body h5,
+div.body h6 {
+    font-family: 'Trebuchet MS', sans-serif;
+    background-color: #f2f2f2;
+    font-weight: normal;
+    color: #20435c;
+    border-bottom: 1px solid #ccc;
+    margin: 20px -20px 10px -20px;
+    padding: 3px 0 3px 10px;
+}
+
+div.body h1 { margin-top: 0; font-size: 200%; }
+div.body h2 { font-size: 160%; }
+div.body h3 { font-size: 140%; }
+div.body h4 { font-size: 120%; }
+div.body h5 { font-size: 110%; }
+div.body h6 { font-size: 100%; }
+
+a.headerlink {
+    color: #c60f0f;
+    font-size: 0.8em;
+    padding: 0 4px 0 4px;
+    text-decoration: none;
+}
+
+a.headerlink:hover {
+    background-color: #c60f0f;
+    color: white;
+}
+
+div.body p, div.body dd, div.body li {
+    text-align: justify;
+    line-height: 130%;
+}
+
+div.admonition p.admonition-title + p {
+    display: inline;
+}
+
+div.admonition p {
+    margin-bottom: 5px;
+}
+
+div.admonition pre {
+    margin-bottom: 5px;
+}
+
+div.admonition ul, div.admonition ol {
+    margin-bottom: 5px;
+}
+
+div.note {
+    background-color: #eee;
+    border: 1px solid #ccc;
+}
+
+div.seealso {
+    background-color: #ffc;
+    border: 1px solid #ff6;
+}
+
+div.topic {
+    background-color: #eee;
+}
+
+div.warning {
+    background-color: #ffe4e4;
+    border: 1px solid #f66;
+}
+
+p.admonition-title {
+    display: inline;
+}
+
+p.admonition-title:after {
+    content: ":";
+}
+
+pre {
+    padding: 5px;
+    background-color: #eeffcc;
+    color: #333333;
+    line-height: 120%;
+    border: 1px solid #ac9;
+    border-left: none;
+    border-right: none;
+}
+
+tt {
+    background-color: #ecf0f3;
+    padding: 0 1px 0 1px;
+    font-size: 0.95em;
+}
+
+th {
+    background-color: #ede;
+}
+
+.warning tt {
+    background: #efc2c2;
+}
+
+.note tt {
+    background: #d6d6d6;
+}
+
+.viewcode-back {
+    font-family: sans-serif;
+}
+
+div.viewcode-block:target {
+    background-color: #f4debf;
+    border-top: 1px solid #ac9;
+    border-bottom: 1px solid #ac9;
+}

+ 247 - 0
desktop/core/ext-py/django-auth-ldap-1.2.0/docs/archive/versions/1.0.19/_static/doctools.js

@@ -0,0 +1,247 @@
+/*
+ * doctools.js
+ * ~~~~~~~~~~~
+ *
+ * Sphinx JavaScript utilities for all documentation.
+ *
+ * :copyright: Copyright 2007-2011 by the Sphinx team, see AUTHORS.
+ * :license: BSD, see LICENSE for details.
+ *
+ */
+
+/**
+ * select a different prefix for underscore
+ */
+$u = _.noConflict();
+
+/**
+ * make the code below compatible with browsers without
+ * an installed firebug like debugger
+if (!window.console || !console.firebug) {
+  var names = ["log", "debug", "info", "warn", "error", "assert", "dir",
+    "dirxml", "group", "groupEnd", "time", "timeEnd", "count", "trace",
+    "profile", "profileEnd"];
+  window.console = {};
+  for (var i = 0; i < names.length; ++i)
+    window.console[names[i]] = function() {};
+}
+ */
+
+/**
+ * small helper function to urldecode strings
+ */
+jQuery.urldecode = function(x) {
+  return decodeURIComponent(x).replace(/\+/g, ' ');
+}
+
+/**
+ * small helper function to urlencode strings
+ */
+jQuery.urlencode = encodeURIComponent;
+
+/**
+ * This function returns the parsed url parameters of the
+ * current request. Multiple values per key are supported,
+ * it will always return arrays of strings for the value parts.
+ */
+jQuery.getQueryParameters = function(s) {
+  if (typeof s == 'undefined')
+    s = document.location.search;
+  var parts = s.substr(s.indexOf('?') + 1).split('&');
+  var result = {};
+  for (var i = 0; i < parts.length; i++) {
+    var tmp = parts[i].split('=', 2);
+    var key = jQuery.urldecode(tmp[0]);
+    var value = jQuery.urldecode(tmp[1]);
+    if (key in result)
+      result[key].push(value);
+    else
+      result[key] = [value];
+  }
+  return result;
+};
+
+/**
+ * small function to check if an array contains
+ * a given item.
+ */
+jQuery.contains = function(arr, item) {
+  for (var i = 0; i < arr.length; i++) {
+    if (arr[i] == item)
+      return true;
+  }
+  return false;
+};
+
+/**
+ * highlight a given string on a jquery object by wrapping it in
+ * span elements with the given class name.
+ */
+jQuery.fn.highlightText = function(text, className) {
+  function highlight(node) {
+    if (node.nodeType == 3) {
+      var val = node.nodeValue;
+      var pos = val.toLowerCase().indexOf(text);
+      if (pos >= 0 && !jQuery(node.parentNode).hasClass(className)) {
+        var span = document.createElement("span");
+        span.className = className;
+        span.appendChild(document.createTextNode(val.substr(pos, text.length)));
+        node.parentNode.insertBefore(span, node.parentNode.insertBefore(
+          document.createTextNode(val.substr(pos + text.length)),
+          node.nextSibling));
+        node.nodeValue = val.substr(0, pos);
+      }
+    }
+    else if (!jQuery(node).is("button, select, textarea")) {
+      jQuery.each(node.childNodes, function() {
+        highlight(this);
+      });
+    }
+  }
+  return this.each(function() {
+    highlight(this);
+  });
+};
+
+/**
+ * Small JavaScript module for the documentation.
+ */
+var Documentation = {
+
+  init : function() {
+    this.fixFirefoxAnchorBug();
+    this.highlightSearchWords();
+    this.initIndexTable();
+  },
+
+  /**
+   * i18n support
+   */
+  TRANSLATIONS : {},
+  PLURAL_EXPR : function(n) { return n == 1 ? 0 : 1; },
+  LOCALE : 'unknown',
+
+  // gettext and ngettext don't access this so that the functions
+  // can safely bound to a different name (_ = Documentation.gettext)
+  gettext : function(string) {
+    var translated = Documentation.TRANSLATIONS[string];
+    if (typeof translated == 'undefined')
+      return string;
+    return (typeof translated == 'string') ? translated : translated[0];
+  },
+
+  ngettext : function(singular, plural, n) {
+    var translated = Documentation.TRANSLATIONS[singular];
+    if (typeof translated == 'undefined')
+      return (n == 1) ? singular : plural;
+    return translated[Documentation.PLURALEXPR(n)];
+  },
+
+  addTranslations : function(catalog) {
+    for (var key in catalog.messages)
+      this.TRANSLATIONS[key] = catalog.messages[key];
+    this.PLURAL_EXPR = new Function('n', 'return +(' + catalog.plural_expr + ')');
+    this.LOCALE = catalog.locale;
+  },
+
+  /**
+   * add context elements like header anchor links
+   */
+  addContextElements : function() {
+    $('div[id] > :header:first').each(function() {
+      $('<a class="headerlink">\u00B6</a>').
+      attr('href', '#' + this.id).
+      attr('title', _('Permalink to this headline')).
+      appendTo(this);
+    });
+    $('dt[id]').each(function() {
+      $('<a class="headerlink">\u00B6</a>').
+      attr('href', '#' + this.id).
+      attr('title', _('Permalink to this definition')).
+      appendTo(this);
+    });
+  },
+
+  /**
+   * workaround a firefox stupidity
+   */
+  fixFirefoxAnchorBug : function() {
+    if (document.location.hash && $.browser.mozilla)
+      window.setTimeout(function() {
+        document.location.href += '';
+      }, 10);
+  },
+
+  /**
+   * highlight the search words provided in the url in the text
+   */
+  highlightSearchWords : function() {
+    var params = $.getQueryParameters();
+    var terms = (params.highlight) ? params.highlight[0].split(/\s+/) : [];
+    if (terms.length) {
+      var body = $('div.body');
+      window.setTimeout(function() {
+        $.each(terms, function() {
+          body.highlightText(this.toLowerCase(), 'highlighted');
+        });
+      }, 10);
+      $('<p class="highlight-link"><a href="javascript:Documentation.' +
+        'hideSearchWords()">' + _('Hide Search Matches') + '</a></p>')
+          .appendTo($('#searchbox'));
+    }
+  },
+
+  /**
+   * init the domain index toggle buttons
+   */
+  initIndexTable : function() {
+    var togglers = $('img.toggler').click(function() {
+      var src = $(this).attr('src');
+      var idnum = $(this).attr('id').substr(7);
+      $('tr.cg-' + idnum).toggle();
+      if (src.substr(-9) == 'minus.png')
+        $(this).attr('src', src.substr(0, src.length-9) + 'plus.png');
+      else
+        $(this).attr('src', src.substr(0, src.length-8) + 'minus.png');
+    }).css('display', '');
+    if (DOCUMENTATION_OPTIONS.COLLAPSE_INDEX) {
+        togglers.click();
+    }
+  },
+
+  /**
+   * helper function to hide the search marks again
+   */
+  hideSearchWords : function() {
+    $('#searchbox .highlight-link').fadeOut(300);
+    $('span.highlighted').removeClass('highlighted');
+  },
+
+  /**
+   * make the url absolute
+   */
+  makeURL : function(relativeURL) {
+    return DOCUMENTATION_OPTIONS.URL_ROOT + '/' + relativeURL;
+  },
+
+  /**
+   * get the current relative url
+   */
+  getCurrentURL : function() {
+    var path = document.location.pathname;
+    var parts = path.split(/\//);
+    $.each(DOCUMENTATION_OPTIONS.URL_ROOT.split(/\//), function() {
+      if (this == '..')
+        parts.pop();
+    });
+    var url = parts.join('/');
+    return path.substring(url.lastIndexOf('/') + 1, path.length - 1);
+  }
+};
+
+// quick alias for translations
+_ = Documentation.gettext;
+
+$(document).ready(function() {
+  Documentation.init();
+});

BIN
desktop/core/ext-py/django-auth-ldap-1.2.0/docs/archive/versions/1.0.19/_static/down-pressed.png


BIN
desktop/core/ext-py/django-auth-ldap-1.2.0/docs/archive/versions/1.0.19/_static/down.png


BIN
desktop/core/ext-py/django-auth-ldap-1.2.0/docs/archive/versions/1.0.19/_static/file.png


+ 154 - 0
desktop/core/ext-py/django-auth-ldap-1.2.0/docs/archive/versions/1.0.19/_static/jquery.js

@@ -0,0 +1,154 @@
+/*!
+ * jQuery JavaScript Library v1.4.2
+ * http://jquery.com/
+ *
+ * Copyright 2010, John Resig
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
+ *
+ * Includes Sizzle.js
+ * http://sizzlejs.com/
+ * Copyright 2010, The Dojo Foundation
+ * Released under the MIT, BSD, and GPL Licenses.
+ *
+ * Date: Sat Feb 13 22:33:48 2010 -0500
+ */
+(function(A,w){function ma(){if(!c.isReady){try{s.documentElement.doScroll("left")}catch(a){setTimeout(ma,1);return}c.ready()}}function Qa(a,b){b.src?c.ajax({url:b.src,async:false,dataType:"script"}):c.globalEval(b.text||b.textContent||b.innerHTML||"");b.parentNode&&b.parentNode.removeChild(b)}function X(a,b,d,f,e,j){var i=a.length;if(typeof b==="object"){for(var o in b)X(a,o,b[o],f,e,d);return a}if(d!==w){f=!j&&f&&c.isFunction(d);for(o=0;o<i;o++)e(a[o],b,f?d.call(a[o],o,e(a[o],b)):d,j);return a}return i?
+e(a[0],b):w}function J(){return(new Date).getTime()}function Y(){return false}function Z(){return true}function na(a,b,d){d[0].type=a;return c.event.handle.apply(b,d)}function oa(a){var b,d=[],f=[],e=arguments,j,i,o,k,n,r;i=c.data(this,"events");if(!(a.liveFired===this||!i||!i.live||a.button&&a.type==="click")){a.liveFired=this;var u=i.live.slice(0);for(k=0;k<u.length;k++){i=u[k];i.origType.replace(O,"")===a.type?f.push(i.selector):u.splice(k--,1)}j=c(a.target).closest(f,a.currentTarget);n=0;for(r=
+j.length;n<r;n++)for(k=0;k<u.length;k++){i=u[k];if(j[n].selector===i.selector){o=j[n].elem;f=null;if(i.preType==="mouseenter"||i.preType==="mouseleave")f=c(a.relatedTarget).closest(i.selector)[0];if(!f||f!==o)d.push({elem:o,handleObj:i})}}n=0;for(r=d.length;n<r;n++){j=d[n];a.currentTarget=j.elem;a.data=j.handleObj.data;a.handleObj=j.handleObj;if(j.handleObj.origHandler.apply(j.elem,e)===false){b=false;break}}return b}}function pa(a,b){return"live."+(a&&a!=="*"?a+".":"")+b.replace(/\./g,"`").replace(/ /g,
+"&")}function qa(a){return!a||!a.parentNode||a.parentNode.nodeType===11}function ra(a,b){var d=0;b.each(function(){if(this.nodeName===(a[d]&&a[d].nodeName)){var f=c.data(a[d++]),e=c.data(this,f);if(f=f&&f.events){delete e.handle;e.events={};for(var j in f)for(var i in f[j])c.event.add(this,j,f[j][i],f[j][i].data)}}})}function sa(a,b,d){var f,e,j;b=b&&b[0]?b[0].ownerDocument||b[0]:s;if(a.length===1&&typeof a[0]==="string"&&a[0].length<512&&b===s&&!ta.test(a[0])&&(c.support.checkClone||!ua.test(a[0]))){e=
+true;if(j=c.fragments[a[0]])if(j!==1)f=j}if(!f){f=b.createDocumentFragment();c.clean(a,b,f,d)}if(e)c.fragments[a[0]]=j?f:1;return{fragment:f,cacheable:e}}function K(a,b){var d={};c.each(va.concat.apply([],va.slice(0,b)),function(){d[this]=a});return d}function wa(a){return"scrollTo"in a&&a.document?a:a.nodeType===9?a.defaultView||a.parentWindow:false}var c=function(a,b){return new c.fn.init(a,b)},Ra=A.jQuery,Sa=A.$,s=A.document,T,Ta=/^[^<]*(<[\w\W]+>)[^>]*$|^#([\w-]+)$/,Ua=/^.[^:#\[\.,]*$/,Va=/\S/,
+Wa=/^(\s|\u00A0)+|(\s|\u00A0)+$/g,Xa=/^<(\w+)\s*\/?>(?:<\/\1>)?$/,P=navigator.userAgent,xa=false,Q=[],L,$=Object.prototype.toString,aa=Object.prototype.hasOwnProperty,ba=Array.prototype.push,R=Array.prototype.slice,ya=Array.prototype.indexOf;c.fn=c.prototype={init:function(a,b){var d,f;if(!a)return this;if(a.nodeType){this.context=this[0]=a;this.length=1;return this}if(a==="body"&&!b){this.context=s;this[0]=s.body;this.selector="body";this.length=1;return this}if(typeof a==="string")if((d=Ta.exec(a))&&
+(d[1]||!b))if(d[1]){f=b?b.ownerDocument||b:s;if(a=Xa.exec(a))if(c.isPlainObject(b)){a=[s.createElement(a[1])];c.fn.attr.call(a,b,true)}else a=[f.createElement(a[1])];else{a=sa([d[1]],[f]);a=(a.cacheable?a.fragment.cloneNode(true):a.fragment).childNodes}return c.merge(this,a)}else{if(b=s.getElementById(d[2])){if(b.id!==d[2])return T.find(a);this.length=1;this[0]=b}this.context=s;this.selector=a;return this}else if(!b&&/^\w+$/.test(a)){this.selector=a;this.context=s;a=s.getElementsByTagName(a);return c.merge(this,
+a)}else return!b||b.jquery?(b||T).find(a):c(b).find(a);else if(c.isFunction(a))return T.ready(a);if(a.selector!==w){this.selector=a.selector;this.context=a.context}return c.makeArray(a,this)},selector:"",jquery:"1.4.2",length:0,size:function(){return this.length},toArray:function(){return R.call(this,0)},get:function(a){return a==null?this.toArray():a<0?this.slice(a)[0]:this[a]},pushStack:function(a,b,d){var f=c();c.isArray(a)?ba.apply(f,a):c.merge(f,a);f.prevObject=this;f.context=this.context;if(b===
+"find")f.selector=this.selector+(this.selector?" ":"")+d;else if(b)f.selector=this.selector+"."+b+"("+d+")";return f},each:function(a,b){return c.each(this,a,b)},ready:function(a){c.bindReady();if(c.isReady)a.call(s,c);else Q&&Q.push(a);return this},eq:function(a){return a===-1?this.slice(a):this.slice(a,+a+1)},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},slice:function(){return this.pushStack(R.apply(this,arguments),"slice",R.call(arguments).join(","))},map:function(a){return this.pushStack(c.map(this,
+function(b,d){return a.call(b,d,b)}))},end:function(){return this.prevObject||c(null)},push:ba,sort:[].sort,splice:[].splice};c.fn.init.prototype=c.fn;c.extend=c.fn.extend=function(){var a=arguments[0]||{},b=1,d=arguments.length,f=false,e,j,i,o;if(typeof a==="boolean"){f=a;a=arguments[1]||{};b=2}if(typeof a!=="object"&&!c.isFunction(a))a={};if(d===b){a=this;--b}for(;b<d;b++)if((e=arguments[b])!=null)for(j in e){i=a[j];o=e[j];if(a!==o)if(f&&o&&(c.isPlainObject(o)||c.isArray(o))){i=i&&(c.isPlainObject(i)||
+c.isArray(i))?i:c.isArray(o)?[]:{};a[j]=c.extend(f,i,o)}else if(o!==w)a[j]=o}return a};c.extend({noConflict:function(a){A.$=Sa;if(a)A.jQuery=Ra;return c},isReady:false,ready:function(){if(!c.isReady){if(!s.body)return setTimeout(c.ready,13);c.isReady=true;if(Q){for(var a,b=0;a=Q[b++];)a.call(s,c);Q=null}c.fn.triggerHandler&&c(s).triggerHandler("ready")}},bindReady:function(){if(!xa){xa=true;if(s.readyState==="complete")return c.ready();if(s.addEventListener){s.addEventListener("DOMContentLoaded",
+L,false);A.addEventListener("load",c.ready,false)}else if(s.attachEvent){s.attachEvent("onreadystatechange",L);A.attachEvent("onload",c.ready);var a=false;try{a=A.frameElement==null}catch(b){}s.documentElement.doScroll&&a&&ma()}}},isFunction:function(a){return $.call(a)==="[object Function]"},isArray:function(a){return $.call(a)==="[object Array]"},isPlainObject:function(a){if(!a||$.call(a)!=="[object Object]"||a.nodeType||a.setInterval)return false;if(a.constructor&&!aa.call(a,"constructor")&&!aa.call(a.constructor.prototype,
+"isPrototypeOf"))return false;var b;for(b in a);return b===w||aa.call(a,b)},isEmptyObject:function(a){for(var b in a)return false;return true},error:function(a){throw a;},parseJSON:function(a){if(typeof a!=="string"||!a)return null;a=c.trim(a);if(/^[\],:{}\s]*$/.test(a.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g,"@").replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,"]").replace(/(?:^|:|,)(?:\s*\[)+/g,"")))return A.JSON&&A.JSON.parse?A.JSON.parse(a):(new Function("return "+
+a))();else c.error("Invalid JSON: "+a)},noop:function(){},globalEval:function(a){if(a&&Va.test(a)){var b=s.getElementsByTagName("head")[0]||s.documentElement,d=s.createElement("script");d.type="text/javascript";if(c.support.scriptEval)d.appendChild(s.createTextNode(a));else d.text=a;b.insertBefore(d,b.firstChild);b.removeChild(d)}},nodeName:function(a,b){return a.nodeName&&a.nodeName.toUpperCase()===b.toUpperCase()},each:function(a,b,d){var f,e=0,j=a.length,i=j===w||c.isFunction(a);if(d)if(i)for(f in a){if(b.apply(a[f],
+d)===false)break}else for(;e<j;){if(b.apply(a[e++],d)===false)break}else if(i)for(f in a){if(b.call(a[f],f,a[f])===false)break}else for(d=a[0];e<j&&b.call(d,e,d)!==false;d=a[++e]);return a},trim:function(a){return(a||"").replace(Wa,"")},makeArray:function(a,b){b=b||[];if(a!=null)a.length==null||typeof a==="string"||c.isFunction(a)||typeof a!=="function"&&a.setInterval?ba.call(b,a):c.merge(b,a);return b},inArray:function(a,b){if(b.indexOf)return b.indexOf(a);for(var d=0,f=b.length;d<f;d++)if(b[d]===
+a)return d;return-1},merge:function(a,b){var d=a.length,f=0;if(typeof b.length==="number")for(var e=b.length;f<e;f++)a[d++]=b[f];else for(;b[f]!==w;)a[d++]=b[f++];a.length=d;return a},grep:function(a,b,d){for(var f=[],e=0,j=a.length;e<j;e++)!d!==!b(a[e],e)&&f.push(a[e]);return f},map:function(a,b,d){for(var f=[],e,j=0,i=a.length;j<i;j++){e=b(a[j],j,d);if(e!=null)f[f.length]=e}return f.concat.apply([],f)},guid:1,proxy:function(a,b,d){if(arguments.length===2)if(typeof b==="string"){d=a;a=d[b];b=w}else if(b&&
+!c.isFunction(b)){d=b;b=w}if(!b&&a)b=function(){return a.apply(d||this,arguments)};if(a)b.guid=a.guid=a.guid||b.guid||c.guid++;return b},uaMatch:function(a){a=a.toLowerCase();a=/(webkit)[ \/]([\w.]+)/.exec(a)||/(opera)(?:.*version)?[ \/]([\w.]+)/.exec(a)||/(msie) ([\w.]+)/.exec(a)||!/compatible/.test(a)&&/(mozilla)(?:.*? rv:([\w.]+))?/.exec(a)||[];return{browser:a[1]||"",version:a[2]||"0"}},browser:{}});P=c.uaMatch(P);if(P.browser){c.browser[P.browser]=true;c.browser.version=P.version}if(c.browser.webkit)c.browser.safari=
+true;if(ya)c.inArray=function(a,b){return ya.call(b,a)};T=c(s);if(s.addEventListener)L=function(){s.removeEventListener("DOMContentLoaded",L,false);c.ready()};else if(s.attachEvent)L=function(){if(s.readyState==="complete"){s.detachEvent("onreadystatechange",L);c.ready()}};(function(){c.support={};var a=s.documentElement,b=s.createElement("script"),d=s.createElement("div"),f="script"+J();d.style.display="none";d.innerHTML="   <link/><table></table><a href='/a' style='color:red;float:left;opacity:.55;'>a</a><input type='checkbox'/>";
+var e=d.getElementsByTagName("*"),j=d.getElementsByTagName("a")[0];if(!(!e||!e.length||!j)){c.support={leadingWhitespace:d.firstChild.nodeType===3,tbody:!d.getElementsByTagName("tbody").length,htmlSerialize:!!d.getElementsByTagName("link").length,style:/red/.test(j.getAttribute("style")),hrefNormalized:j.getAttribute("href")==="/a",opacity:/^0.55$/.test(j.style.opacity),cssFloat:!!j.style.cssFloat,checkOn:d.getElementsByTagName("input")[0].value==="on",optSelected:s.createElement("select").appendChild(s.createElement("option")).selected,
+parentNode:d.removeChild(d.appendChild(s.createElement("div"))).parentNode===null,deleteExpando:true,checkClone:false,scriptEval:false,noCloneEvent:true,boxModel:null};b.type="text/javascript";try{b.appendChild(s.createTextNode("window."+f+"=1;"))}catch(i){}a.insertBefore(b,a.firstChild);if(A[f]){c.support.scriptEval=true;delete A[f]}try{delete b.test}catch(o){c.support.deleteExpando=false}a.removeChild(b);if(d.attachEvent&&d.fireEvent){d.attachEvent("onclick",function k(){c.support.noCloneEvent=
+false;d.detachEvent("onclick",k)});d.cloneNode(true).fireEvent("onclick")}d=s.createElement("div");d.innerHTML="<input type='radio' name='radiotest' checked='checked'/>";a=s.createDocumentFragment();a.appendChild(d.firstChild);c.support.checkClone=a.cloneNode(true).cloneNode(true).lastChild.checked;c(function(){var k=s.createElement("div");k.style.width=k.style.paddingLeft="1px";s.body.appendChild(k);c.boxModel=c.support.boxModel=k.offsetWidth===2;s.body.removeChild(k).style.display="none"});a=function(k){var n=
+s.createElement("div");k="on"+k;var r=k in n;if(!r){n.setAttribute(k,"return;");r=typeof n[k]==="function"}return r};c.support.submitBubbles=a("submit");c.support.changeBubbles=a("change");a=b=d=e=j=null}})();c.props={"for":"htmlFor","class":"className",readonly:"readOnly",maxlength:"maxLength",cellspacing:"cellSpacing",rowspan:"rowSpan",colspan:"colSpan",tabindex:"tabIndex",usemap:"useMap",frameborder:"frameBorder"};var G="jQuery"+J(),Ya=0,za={};c.extend({cache:{},expando:G,noData:{embed:true,object:true,
+applet:true},data:function(a,b,d){if(!(a.nodeName&&c.noData[a.nodeName.toLowerCase()])){a=a==A?za:a;var f=a[G],e=c.cache;if(!f&&typeof b==="string"&&d===w)return null;f||(f=++Ya);if(typeof b==="object"){a[G]=f;e[f]=c.extend(true,{},b)}else if(!e[f]){a[G]=f;e[f]={}}a=e[f];if(d!==w)a[b]=d;return typeof b==="string"?a[b]:a}},removeData:function(a,b){if(!(a.nodeName&&c.noData[a.nodeName.toLowerCase()])){a=a==A?za:a;var d=a[G],f=c.cache,e=f[d];if(b){if(e){delete e[b];c.isEmptyObject(e)&&c.removeData(a)}}else{if(c.support.deleteExpando)delete a[c.expando];
+else a.removeAttribute&&a.removeAttribute(c.expando);delete f[d]}}}});c.fn.extend({data:function(a,b){if(typeof a==="undefined"&&this.length)return c.data(this[0]);else if(typeof a==="object")return this.each(function(){c.data(this,a)});var d=a.split(".");d[1]=d[1]?"."+d[1]:"";if(b===w){var f=this.triggerHandler("getData"+d[1]+"!",[d[0]]);if(f===w&&this.length)f=c.data(this[0],a);return f===w&&d[1]?this.data(d[0]):f}else return this.trigger("setData"+d[1]+"!",[d[0],b]).each(function(){c.data(this,
+a,b)})},removeData:function(a){return this.each(function(){c.removeData(this,a)})}});c.extend({queue:function(a,b,d){if(a){b=(b||"fx")+"queue";var f=c.data(a,b);if(!d)return f||[];if(!f||c.isArray(d))f=c.data(a,b,c.makeArray(d));else f.push(d);return f}},dequeue:function(a,b){b=b||"fx";var d=c.queue(a,b),f=d.shift();if(f==="inprogress")f=d.shift();if(f){b==="fx"&&d.unshift("inprogress");f.call(a,function(){c.dequeue(a,b)})}}});c.fn.extend({queue:function(a,b){if(typeof a!=="string"){b=a;a="fx"}if(b===
+w)return c.queue(this[0],a);return this.each(function(){var d=c.queue(this,a,b);a==="fx"&&d[0]!=="inprogress"&&c.dequeue(this,a)})},dequeue:function(a){return this.each(function(){c.dequeue(this,a)})},delay:function(a,b){a=c.fx?c.fx.speeds[a]||a:a;b=b||"fx";return this.queue(b,function(){var d=this;setTimeout(function(){c.dequeue(d,b)},a)})},clearQueue:function(a){return this.queue(a||"fx",[])}});var Aa=/[\n\t]/g,ca=/\s+/,Za=/\r/g,$a=/href|src|style/,ab=/(button|input)/i,bb=/(button|input|object|select|textarea)/i,
+cb=/^(a|area)$/i,Ba=/radio|checkbox/;c.fn.extend({attr:function(a,b){return X(this,a,b,true,c.attr)},removeAttr:function(a){return this.each(function(){c.attr(this,a,"");this.nodeType===1&&this.removeAttribute(a)})},addClass:function(a){if(c.isFunction(a))return this.each(function(n){var r=c(this);r.addClass(a.call(this,n,r.attr("class")))});if(a&&typeof a==="string")for(var b=(a||"").split(ca),d=0,f=this.length;d<f;d++){var e=this[d];if(e.nodeType===1)if(e.className){for(var j=" "+e.className+" ",
+i=e.className,o=0,k=b.length;o<k;o++)if(j.indexOf(" "+b[o]+" ")<0)i+=" "+b[o];e.className=c.trim(i)}else e.className=a}return this},removeClass:function(a){if(c.isFunction(a))return this.each(function(k){var n=c(this);n.removeClass(a.call(this,k,n.attr("class")))});if(a&&typeof a==="string"||a===w)for(var b=(a||"").split(ca),d=0,f=this.length;d<f;d++){var e=this[d];if(e.nodeType===1&&e.className)if(a){for(var j=(" "+e.className+" ").replace(Aa," "),i=0,o=b.length;i<o;i++)j=j.replace(" "+b[i]+" ",
+" ");e.className=c.trim(j)}else e.className=""}return this},toggleClass:function(a,b){var d=typeof a,f=typeof b==="boolean";if(c.isFunction(a))return this.each(function(e){var j=c(this);j.toggleClass(a.call(this,e,j.attr("class"),b),b)});return this.each(function(){if(d==="string")for(var e,j=0,i=c(this),o=b,k=a.split(ca);e=k[j++];){o=f?o:!i.hasClass(e);i[o?"addClass":"removeClass"](e)}else if(d==="undefined"||d==="boolean"){this.className&&c.data(this,"__className__",this.className);this.className=
+this.className||a===false?"":c.data(this,"__className__")||""}})},hasClass:function(a){a=" "+a+" ";for(var b=0,d=this.length;b<d;b++)if((" "+this[b].className+" ").replace(Aa," ").indexOf(a)>-1)return true;return false},val:function(a){if(a===w){var b=this[0];if(b){if(c.nodeName(b,"option"))return(b.attributes.value||{}).specified?b.value:b.text;if(c.nodeName(b,"select")){var d=b.selectedIndex,f=[],e=b.options;b=b.type==="select-one";if(d<0)return null;var j=b?d:0;for(d=b?d+1:e.length;j<d;j++){var i=
+e[j];if(i.selected){a=c(i).val();if(b)return a;f.push(a)}}return f}if(Ba.test(b.type)&&!c.support.checkOn)return b.getAttribute("value")===null?"on":b.value;return(b.value||"").replace(Za,"")}return w}var o=c.isFunction(a);return this.each(function(k){var n=c(this),r=a;if(this.nodeType===1){if(o)r=a.call(this,k,n.val());if(typeof r==="number")r+="";if(c.isArray(r)&&Ba.test(this.type))this.checked=c.inArray(n.val(),r)>=0;else if(c.nodeName(this,"select")){var u=c.makeArray(r);c("option",this).each(function(){this.selected=
+c.inArray(c(this).val(),u)>=0});if(!u.length)this.selectedIndex=-1}else this.value=r}})}});c.extend({attrFn:{val:true,css:true,html:true,text:true,data:true,width:true,height:true,offset:true},attr:function(a,b,d,f){if(!a||a.nodeType===3||a.nodeType===8)return w;if(f&&b in c.attrFn)return c(a)[b](d);f=a.nodeType!==1||!c.isXMLDoc(a);var e=d!==w;b=f&&c.props[b]||b;if(a.nodeType===1){var j=$a.test(b);if(b in a&&f&&!j){if(e){b==="type"&&ab.test(a.nodeName)&&a.parentNode&&c.error("type property can't be changed");
+a[b]=d}if(c.nodeName(a,"form")&&a.getAttributeNode(b))return a.getAttributeNode(b).nodeValue;if(b==="tabIndex")return(b=a.getAttributeNode("tabIndex"))&&b.specified?b.value:bb.test(a.nodeName)||cb.test(a.nodeName)&&a.href?0:w;return a[b]}if(!c.support.style&&f&&b==="style"){if(e)a.style.cssText=""+d;return a.style.cssText}e&&a.setAttribute(b,""+d);a=!c.support.hrefNormalized&&f&&j?a.getAttribute(b,2):a.getAttribute(b);return a===null?w:a}return c.style(a,b,d)}});var O=/\.(.*)$/,db=function(a){return a.replace(/[^\w\s\.\|`]/g,
+function(b){return"\\"+b})};c.event={add:function(a,b,d,f){if(!(a.nodeType===3||a.nodeType===8)){if(a.setInterval&&a!==A&&!a.frameElement)a=A;var e,j;if(d.handler){e=d;d=e.handler}if(!d.guid)d.guid=c.guid++;if(j=c.data(a)){var i=j.events=j.events||{},o=j.handle;if(!o)j.handle=o=function(){return typeof c!=="undefined"&&!c.event.triggered?c.event.handle.apply(o.elem,arguments):w};o.elem=a;b=b.split(" ");for(var k,n=0,r;k=b[n++];){j=e?c.extend({},e):{handler:d,data:f};if(k.indexOf(".")>-1){r=k.split(".");
+k=r.shift();j.namespace=r.slice(0).sort().join(".")}else{r=[];j.namespace=""}j.type=k;j.guid=d.guid;var u=i[k],z=c.event.special[k]||{};if(!u){u=i[k]=[];if(!z.setup||z.setup.call(a,f,r,o)===false)if(a.addEventListener)a.addEventListener(k,o,false);else a.attachEvent&&a.attachEvent("on"+k,o)}if(z.add){z.add.call(a,j);if(!j.handler.guid)j.handler.guid=d.guid}u.push(j);c.event.global[k]=true}a=null}}},global:{},remove:function(a,b,d,f){if(!(a.nodeType===3||a.nodeType===8)){var e,j=0,i,o,k,n,r,u,z=c.data(a),
+C=z&&z.events;if(z&&C){if(b&&b.type){d=b.handler;b=b.type}if(!b||typeof b==="string"&&b.charAt(0)==="."){b=b||"";for(e in C)c.event.remove(a,e+b)}else{for(b=b.split(" ");e=b[j++];){n=e;i=e.indexOf(".")<0;o=[];if(!i){o=e.split(".");e=o.shift();k=new RegExp("(^|\\.)"+c.map(o.slice(0).sort(),db).join("\\.(?:.*\\.)?")+"(\\.|$)")}if(r=C[e])if(d){n=c.event.special[e]||{};for(B=f||0;B<r.length;B++){u=r[B];if(d.guid===u.guid){if(i||k.test(u.namespace)){f==null&&r.splice(B--,1);n.remove&&n.remove.call(a,u)}if(f!=
+null)break}}if(r.length===0||f!=null&&r.length===1){if(!n.teardown||n.teardown.call(a,o)===false)Ca(a,e,z.handle);delete C[e]}}else for(var B=0;B<r.length;B++){u=r[B];if(i||k.test(u.namespace)){c.event.remove(a,n,u.handler,B);r.splice(B--,1)}}}if(c.isEmptyObject(C)){if(b=z.handle)b.elem=null;delete z.events;delete z.handle;c.isEmptyObject(z)&&c.removeData(a)}}}}},trigger:function(a,b,d,f){var e=a.type||a;if(!f){a=typeof a==="object"?a[G]?a:c.extend(c.Event(e),a):c.Event(e);if(e.indexOf("!")>=0){a.type=
+e=e.slice(0,-1);a.exclusive=true}if(!d){a.stopPropagation();c.event.global[e]&&c.each(c.cache,function(){this.events&&this.events[e]&&c.event.trigger(a,b,this.handle.elem)})}if(!d||d.nodeType===3||d.nodeType===8)return w;a.result=w;a.target=d;b=c.makeArray(b);b.unshift(a)}a.currentTarget=d;(f=c.data(d,"handle"))&&f.apply(d,b);f=d.parentNode||d.ownerDocument;try{if(!(d&&d.nodeName&&c.noData[d.nodeName.toLowerCase()]))if(d["on"+e]&&d["on"+e].apply(d,b)===false)a.result=false}catch(j){}if(!a.isPropagationStopped()&&
+f)c.event.trigger(a,b,f,true);else if(!a.isDefaultPrevented()){f=a.target;var i,o=c.nodeName(f,"a")&&e==="click",k=c.event.special[e]||{};if((!k._default||k._default.call(d,a)===false)&&!o&&!(f&&f.nodeName&&c.noData[f.nodeName.toLowerCase()])){try{if(f[e]){if(i=f["on"+e])f["on"+e]=null;c.event.triggered=true;f[e]()}}catch(n){}if(i)f["on"+e]=i;c.event.triggered=false}}},handle:function(a){var b,d,f,e;a=arguments[0]=c.event.fix(a||A.event);a.currentTarget=this;b=a.type.indexOf(".")<0&&!a.exclusive;
+if(!b){d=a.type.split(".");a.type=d.shift();f=new RegExp("(^|\\.)"+d.slice(0).sort().join("\\.(?:.*\\.)?")+"(\\.|$)")}e=c.data(this,"events");d=e[a.type];if(e&&d){d=d.slice(0);e=0;for(var j=d.length;e<j;e++){var i=d[e];if(b||f.test(i.namespace)){a.handler=i.handler;a.data=i.data;a.handleObj=i;i=i.handler.apply(this,arguments);if(i!==w){a.result=i;if(i===false){a.preventDefault();a.stopPropagation()}}if(a.isImmediatePropagationStopped())break}}}return a.result},props:"altKey attrChange attrName bubbles button cancelable charCode clientX clientY ctrlKey currentTarget data detail eventPhase fromElement handler keyCode layerX layerY metaKey newValue offsetX offsetY originalTarget pageX pageY prevValue relatedNode relatedTarget screenX screenY shiftKey srcElement target toElement view wheelDelta which".split(" "),
+fix:function(a){if(a[G])return a;var b=a;a=c.Event(b);for(var d=this.props.length,f;d;){f=this.props[--d];a[f]=b[f]}if(!a.target)a.target=a.srcElement||s;if(a.target.nodeType===3)a.target=a.target.parentNode;if(!a.relatedTarget&&a.fromElement)a.relatedTarget=a.fromElement===a.target?a.toElement:a.fromElement;if(a.pageX==null&&a.clientX!=null){b=s.documentElement;d=s.body;a.pageX=a.clientX+(b&&b.scrollLeft||d&&d.scrollLeft||0)-(b&&b.clientLeft||d&&d.clientLeft||0);a.pageY=a.clientY+(b&&b.scrollTop||
+d&&d.scrollTop||0)-(b&&b.clientTop||d&&d.clientTop||0)}if(!a.which&&(a.charCode||a.charCode===0?a.charCode:a.keyCode))a.which=a.charCode||a.keyCode;if(!a.metaKey&&a.ctrlKey)a.metaKey=a.ctrlKey;if(!a.which&&a.button!==w)a.which=a.button&1?1:a.button&2?3:a.button&4?2:0;return a},guid:1E8,proxy:c.proxy,special:{ready:{setup:c.bindReady,teardown:c.noop},live:{add:function(a){c.event.add(this,a.origType,c.extend({},a,{handler:oa}))},remove:function(a){var b=true,d=a.origType.replace(O,"");c.each(c.data(this,
+"events").live||[],function(){if(d===this.origType.replace(O,""))return b=false});b&&c.event.remove(this,a.origType,oa)}},beforeunload:{setup:function(a,b,d){if(this.setInterval)this.onbeforeunload=d;return false},teardown:function(a,b){if(this.onbeforeunload===b)this.onbeforeunload=null}}}};var Ca=s.removeEventListener?function(a,b,d){a.removeEventListener(b,d,false)}:function(a,b,d){a.detachEvent("on"+b,d)};c.Event=function(a){if(!this.preventDefault)return new c.Event(a);if(a&&a.type){this.originalEvent=
+a;this.type=a.type}else this.type=a;this.timeStamp=J();this[G]=true};c.Event.prototype={preventDefault:function(){this.isDefaultPrevented=Z;var a=this.originalEvent;if(a){a.preventDefault&&a.preventDefault();a.returnValue=false}},stopPropagation:function(){this.isPropagationStopped=Z;var a=this.originalEvent;if(a){a.stopPropagation&&a.stopPropagation();a.cancelBubble=true}},stopImmediatePropagation:function(){this.isImmediatePropagationStopped=Z;this.stopPropagation()},isDefaultPrevented:Y,isPropagationStopped:Y,
+isImmediatePropagationStopped:Y};var Da=function(a){var b=a.relatedTarget;try{for(;b&&b!==this;)b=b.parentNode;if(b!==this){a.type=a.data;c.event.handle.apply(this,arguments)}}catch(d){}},Ea=function(a){a.type=a.data;c.event.handle.apply(this,arguments)};c.each({mouseenter:"mouseover",mouseleave:"mouseout"},function(a,b){c.event.special[a]={setup:function(d){c.event.add(this,b,d&&d.selector?Ea:Da,a)},teardown:function(d){c.event.remove(this,b,d&&d.selector?Ea:Da)}}});if(!c.support.submitBubbles)c.event.special.submit=
+{setup:function(){if(this.nodeName.toLowerCase()!=="form"){c.event.add(this,"click.specialSubmit",function(a){var b=a.target,d=b.type;if((d==="submit"||d==="image")&&c(b).closest("form").length)return na("submit",this,arguments)});c.event.add(this,"keypress.specialSubmit",function(a){var b=a.target,d=b.type;if((d==="text"||d==="password")&&c(b).closest("form").length&&a.keyCode===13)return na("submit",this,arguments)})}else return false},teardown:function(){c.event.remove(this,".specialSubmit")}};
+if(!c.support.changeBubbles){var da=/textarea|input|select/i,ea,Fa=function(a){var b=a.type,d=a.value;if(b==="radio"||b==="checkbox")d=a.checked;else if(b==="select-multiple")d=a.selectedIndex>-1?c.map(a.options,function(f){return f.selected}).join("-"):"";else if(a.nodeName.toLowerCase()==="select")d=a.selectedIndex;return d},fa=function(a,b){var d=a.target,f,e;if(!(!da.test(d.nodeName)||d.readOnly)){f=c.data(d,"_change_data");e=Fa(d);if(a.type!=="focusout"||d.type!=="radio")c.data(d,"_change_data",
+e);if(!(f===w||e===f))if(f!=null||e){a.type="change";return c.event.trigger(a,b,d)}}};c.event.special.change={filters:{focusout:fa,click:function(a){var b=a.target,d=b.type;if(d==="radio"||d==="checkbox"||b.nodeName.toLowerCase()==="select")return fa.call(this,a)},keydown:function(a){var b=a.target,d=b.type;if(a.keyCode===13&&b.nodeName.toLowerCase()!=="textarea"||a.keyCode===32&&(d==="checkbox"||d==="radio")||d==="select-multiple")return fa.call(this,a)},beforeactivate:function(a){a=a.target;c.data(a,
+"_change_data",Fa(a))}},setup:function(){if(this.type==="file")return false;for(var a in ea)c.event.add(this,a+".specialChange",ea[a]);return da.test(this.nodeName)},teardown:function(){c.event.remove(this,".specialChange");return da.test(this.nodeName)}};ea=c.event.special.change.filters}s.addEventListener&&c.each({focus:"focusin",blur:"focusout"},function(a,b){function d(f){f=c.event.fix(f);f.type=b;return c.event.handle.call(this,f)}c.event.special[b]={setup:function(){this.addEventListener(a,
+d,true)},teardown:function(){this.removeEventListener(a,d,true)}}});c.each(["bind","one"],function(a,b){c.fn[b]=function(d,f,e){if(typeof d==="object"){for(var j in d)this[b](j,f,d[j],e);return this}if(c.isFunction(f)){e=f;f=w}var i=b==="one"?c.proxy(e,function(k){c(this).unbind(k,i);return e.apply(this,arguments)}):e;if(d==="unload"&&b!=="one")this.one(d,f,e);else{j=0;for(var o=this.length;j<o;j++)c.event.add(this[j],d,i,f)}return this}});c.fn.extend({unbind:function(a,b){if(typeof a==="object"&&
+!a.preventDefault)for(var d in a)this.unbind(d,a[d]);else{d=0;for(var f=this.length;d<f;d++)c.event.remove(this[d],a,b)}return this},delegate:function(a,b,d,f){return this.live(b,d,f,a)},undelegate:function(a,b,d){return arguments.length===0?this.unbind("live"):this.die(b,null,d,a)},trigger:function(a,b){return this.each(function(){c.event.trigger(a,b,this)})},triggerHandler:function(a,b){if(this[0]){a=c.Event(a);a.preventDefault();a.stopPropagation();c.event.trigger(a,b,this[0]);return a.result}},
+toggle:function(a){for(var b=arguments,d=1;d<b.length;)c.proxy(a,b[d++]);return this.click(c.proxy(a,function(f){var e=(c.data(this,"lastToggle"+a.guid)||0)%d;c.data(this,"lastToggle"+a.guid,e+1);f.preventDefault();return b[e].apply(this,arguments)||false}))},hover:function(a,b){return this.mouseenter(a).mouseleave(b||a)}});var Ga={focus:"focusin",blur:"focusout",mouseenter:"mouseover",mouseleave:"mouseout"};c.each(["live","die"],function(a,b){c.fn[b]=function(d,f,e,j){var i,o=0,k,n,r=j||this.selector,
+u=j?this:c(this.context);if(c.isFunction(f)){e=f;f=w}for(d=(d||"").split(" ");(i=d[o++])!=null;){j=O.exec(i);k="";if(j){k=j[0];i=i.replace(O,"")}if(i==="hover")d.push("mouseenter"+k,"mouseleave"+k);else{n=i;if(i==="focus"||i==="blur"){d.push(Ga[i]+k);i+=k}else i=(Ga[i]||i)+k;b==="live"?u.each(function(){c.event.add(this,pa(i,r),{data:f,selector:r,handler:e,origType:i,origHandler:e,preType:n})}):u.unbind(pa(i,r),e)}}return this}});c.each("blur focus focusin focusout load resize scroll unload click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup error".split(" "),
+function(a,b){c.fn[b]=function(d){return d?this.bind(b,d):this.trigger(b)};if(c.attrFn)c.attrFn[b]=true});A.attachEvent&&!A.addEventListener&&A.attachEvent("onunload",function(){for(var a in c.cache)if(c.cache[a].handle)try{c.event.remove(c.cache[a].handle.elem)}catch(b){}});(function(){function a(g){for(var h="",l,m=0;g[m];m++){l=g[m];if(l.nodeType===3||l.nodeType===4)h+=l.nodeValue;else if(l.nodeType!==8)h+=a(l.childNodes)}return h}function b(g,h,l,m,q,p){q=0;for(var v=m.length;q<v;q++){var t=m[q];
+if(t){t=t[g];for(var y=false;t;){if(t.sizcache===l){y=m[t.sizset];break}if(t.nodeType===1&&!p){t.sizcache=l;t.sizset=q}if(t.nodeName.toLowerCase()===h){y=t;break}t=t[g]}m[q]=y}}}function d(g,h,l,m,q,p){q=0;for(var v=m.length;q<v;q++){var t=m[q];if(t){t=t[g];for(var y=false;t;){if(t.sizcache===l){y=m[t.sizset];break}if(t.nodeType===1){if(!p){t.sizcache=l;t.sizset=q}if(typeof h!=="string"){if(t===h){y=true;break}}else if(k.filter(h,[t]).length>0){y=t;break}}t=t[g]}m[q]=y}}}var f=/((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^[\]]*\]|['"][^'"]*['"]|[^[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g,
+e=0,j=Object.prototype.toString,i=false,o=true;[0,0].sort(function(){o=false;return 0});var k=function(g,h,l,m){l=l||[];var q=h=h||s;if(h.nodeType!==1&&h.nodeType!==9)return[];if(!g||typeof g!=="string")return l;for(var p=[],v,t,y,S,H=true,M=x(h),I=g;(f.exec(""),v=f.exec(I))!==null;){I=v[3];p.push(v[1]);if(v[2]){S=v[3];break}}if(p.length>1&&r.exec(g))if(p.length===2&&n.relative[p[0]])t=ga(p[0]+p[1],h);else for(t=n.relative[p[0]]?[h]:k(p.shift(),h);p.length;){g=p.shift();if(n.relative[g])g+=p.shift();
+t=ga(g,t)}else{if(!m&&p.length>1&&h.nodeType===9&&!M&&n.match.ID.test(p[0])&&!n.match.ID.test(p[p.length-1])){v=k.find(p.shift(),h,M);h=v.expr?k.filter(v.expr,v.set)[0]:v.set[0]}if(h){v=m?{expr:p.pop(),set:z(m)}:k.find(p.pop(),p.length===1&&(p[0]==="~"||p[0]==="+")&&h.parentNode?h.parentNode:h,M);t=v.expr?k.filter(v.expr,v.set):v.set;if(p.length>0)y=z(t);else H=false;for(;p.length;){var D=p.pop();v=D;if(n.relative[D])v=p.pop();else D="";if(v==null)v=h;n.relative[D](y,v,M)}}else y=[]}y||(y=t);y||k.error(D||
+g);if(j.call(y)==="[object Array]")if(H)if(h&&h.nodeType===1)for(g=0;y[g]!=null;g++){if(y[g]&&(y[g]===true||y[g].nodeType===1&&E(h,y[g])))l.push(t[g])}else for(g=0;y[g]!=null;g++)y[g]&&y[g].nodeType===1&&l.push(t[g]);else l.push.apply(l,y);else z(y,l);if(S){k(S,q,l,m);k.uniqueSort(l)}return l};k.uniqueSort=function(g){if(B){i=o;g.sort(B);if(i)for(var h=1;h<g.length;h++)g[h]===g[h-1]&&g.splice(h--,1)}return g};k.matches=function(g,h){return k(g,null,null,h)};k.find=function(g,h,l){var m,q;if(!g)return[];
+for(var p=0,v=n.order.length;p<v;p++){var t=n.order[p];if(q=n.leftMatch[t].exec(g)){var y=q[1];q.splice(1,1);if(y.substr(y.length-1)!=="\\"){q[1]=(q[1]||"").replace(/\\/g,"");m=n.find[t](q,h,l);if(m!=null){g=g.replace(n.match[t],"");break}}}}m||(m=h.getElementsByTagName("*"));return{set:m,expr:g}};k.filter=function(g,h,l,m){for(var q=g,p=[],v=h,t,y,S=h&&h[0]&&x(h[0]);g&&h.length;){for(var H in n.filter)if((t=n.leftMatch[H].exec(g))!=null&&t[2]){var M=n.filter[H],I,D;D=t[1];y=false;t.splice(1,1);if(D.substr(D.length-
+1)!=="\\"){if(v===p)p=[];if(n.preFilter[H])if(t=n.preFilter[H](t,v,l,p,m,S)){if(t===true)continue}else y=I=true;if(t)for(var U=0;(D=v[U])!=null;U++)if(D){I=M(D,t,U,v);var Ha=m^!!I;if(l&&I!=null)if(Ha)y=true;else v[U]=false;else if(Ha){p.push(D);y=true}}if(I!==w){l||(v=p);g=g.replace(n.match[H],"");if(!y)return[];break}}}if(g===q)if(y==null)k.error(g);else break;q=g}return v};k.error=function(g){throw"Syntax error, unrecognized expression: "+g;};var n=k.selectors={order:["ID","NAME","TAG"],match:{ID:/#((?:[\w\u00c0-\uFFFF-]|\\.)+)/,
+CLASS:/\.((?:[\w\u00c0-\uFFFF-]|\\.)+)/,NAME:/\[name=['"]*((?:[\w\u00c0-\uFFFF-]|\\.)+)['"]*\]/,ATTR:/\[\s*((?:[\w\u00c0-\uFFFF-]|\\.)+)\s*(?:(\S?=)\s*(['"]*)(.*?)\3|)\s*\]/,TAG:/^((?:[\w\u00c0-\uFFFF\*-]|\\.)+)/,CHILD:/:(only|nth|last|first)-child(?:\((even|odd|[\dn+-]*)\))?/,POS:/:(nth|eq|gt|lt|first|last|even|odd)(?:\((\d*)\))?(?=[^-]|$)/,PSEUDO:/:((?:[\w\u00c0-\uFFFF-]|\\.)+)(?:\((['"]?)((?:\([^\)]+\)|[^\(\)]*)+)\2\))?/},leftMatch:{},attrMap:{"class":"className","for":"htmlFor"},attrHandle:{href:function(g){return g.getAttribute("href")}},
+relative:{"+":function(g,h){var l=typeof h==="string",m=l&&!/\W/.test(h);l=l&&!m;if(m)h=h.toLowerCase();m=0;for(var q=g.length,p;m<q;m++)if(p=g[m]){for(;(p=p.previousSibling)&&p.nodeType!==1;);g[m]=l||p&&p.nodeName.toLowerCase()===h?p||false:p===h}l&&k.filter(h,g,true)},">":function(g,h){var l=typeof h==="string";if(l&&!/\W/.test(h)){h=h.toLowerCase();for(var m=0,q=g.length;m<q;m++){var p=g[m];if(p){l=p.parentNode;g[m]=l.nodeName.toLowerCase()===h?l:false}}}else{m=0;for(q=g.length;m<q;m++)if(p=g[m])g[m]=
+l?p.parentNode:p.parentNode===h;l&&k.filter(h,g,true)}},"":function(g,h,l){var m=e++,q=d;if(typeof h==="string"&&!/\W/.test(h)){var p=h=h.toLowerCase();q=b}q("parentNode",h,m,g,p,l)},"~":function(g,h,l){var m=e++,q=d;if(typeof h==="string"&&!/\W/.test(h)){var p=h=h.toLowerCase();q=b}q("previousSibling",h,m,g,p,l)}},find:{ID:function(g,h,l){if(typeof h.getElementById!=="undefined"&&!l)return(g=h.getElementById(g[1]))?[g]:[]},NAME:function(g,h){if(typeof h.getElementsByName!=="undefined"){var l=[];
+h=h.getElementsByName(g[1]);for(var m=0,q=h.length;m<q;m++)h[m].getAttribute("name")===g[1]&&l.push(h[m]);return l.length===0?null:l}},TAG:function(g,h){return h.getElementsByTagName(g[1])}},preFilter:{CLASS:function(g,h,l,m,q,p){g=" "+g[1].replace(/\\/g,"")+" ";if(p)return g;p=0;for(var v;(v=h[p])!=null;p++)if(v)if(q^(v.className&&(" "+v.className+" ").replace(/[\t\n]/g," ").indexOf(g)>=0))l||m.push(v);else if(l)h[p]=false;return false},ID:function(g){return g[1].replace(/\\/g,"")},TAG:function(g){return g[1].toLowerCase()},
+CHILD:function(g){if(g[1]==="nth"){var h=/(-?)(\d*)n((?:\+|-)?\d*)/.exec(g[2]==="even"&&"2n"||g[2]==="odd"&&"2n+1"||!/\D/.test(g[2])&&"0n+"+g[2]||g[2]);g[2]=h[1]+(h[2]||1)-0;g[3]=h[3]-0}g[0]=e++;return g},ATTR:function(g,h,l,m,q,p){h=g[1].replace(/\\/g,"");if(!p&&n.attrMap[h])g[1]=n.attrMap[h];if(g[2]==="~=")g[4]=" "+g[4]+" ";return g},PSEUDO:function(g,h,l,m,q){if(g[1]==="not")if((f.exec(g[3])||"").length>1||/^\w/.test(g[3]))g[3]=k(g[3],null,null,h);else{g=k.filter(g[3],h,l,true^q);l||m.push.apply(m,
+g);return false}else if(n.match.POS.test(g[0])||n.match.CHILD.test(g[0]))return true;return g},POS:function(g){g.unshift(true);return g}},filters:{enabled:function(g){return g.disabled===false&&g.type!=="hidden"},disabled:function(g){return g.disabled===true},checked:function(g){return g.checked===true},selected:function(g){return g.selected===true},parent:function(g){return!!g.firstChild},empty:function(g){return!g.firstChild},has:function(g,h,l){return!!k(l[3],g).length},header:function(g){return/h\d/i.test(g.nodeName)},
+text:function(g){return"text"===g.type},radio:function(g){return"radio"===g.type},checkbox:function(g){return"checkbox"===g.type},file:function(g){return"file"===g.type},password:function(g){return"password"===g.type},submit:function(g){return"submit"===g.type},image:function(g){return"image"===g.type},reset:function(g){return"reset"===g.type},button:function(g){return"button"===g.type||g.nodeName.toLowerCase()==="button"},input:function(g){return/input|select|textarea|button/i.test(g.nodeName)}},
+setFilters:{first:function(g,h){return h===0},last:function(g,h,l,m){return h===m.length-1},even:function(g,h){return h%2===0},odd:function(g,h){return h%2===1},lt:function(g,h,l){return h<l[3]-0},gt:function(g,h,l){return h>l[3]-0},nth:function(g,h,l){return l[3]-0===h},eq:function(g,h,l){return l[3]-0===h}},filter:{PSEUDO:function(g,h,l,m){var q=h[1],p=n.filters[q];if(p)return p(g,l,h,m);else if(q==="contains")return(g.textContent||g.innerText||a([g])||"").indexOf(h[3])>=0;else if(q==="not"){h=
+h[3];l=0;for(m=h.length;l<m;l++)if(h[l]===g)return false;return true}else k.error("Syntax error, unrecognized expression: "+q)},CHILD:function(g,h){var l=h[1],m=g;switch(l){case "only":case "first":for(;m=m.previousSibling;)if(m.nodeType===1)return false;if(l==="first")return true;m=g;case "last":for(;m=m.nextSibling;)if(m.nodeType===1)return false;return true;case "nth":l=h[2];var q=h[3];if(l===1&&q===0)return true;h=h[0];var p=g.parentNode;if(p&&(p.sizcache!==h||!g.nodeIndex)){var v=0;for(m=p.firstChild;m;m=
+m.nextSibling)if(m.nodeType===1)m.nodeIndex=++v;p.sizcache=h}g=g.nodeIndex-q;return l===0?g===0:g%l===0&&g/l>=0}},ID:function(g,h){return g.nodeType===1&&g.getAttribute("id")===h},TAG:function(g,h){return h==="*"&&g.nodeType===1||g.nodeName.toLowerCase()===h},CLASS:function(g,h){return(" "+(g.className||g.getAttribute("class"))+" ").indexOf(h)>-1},ATTR:function(g,h){var l=h[1];g=n.attrHandle[l]?n.attrHandle[l](g):g[l]!=null?g[l]:g.getAttribute(l);l=g+"";var m=h[2];h=h[4];return g==null?m==="!=":m===
+"="?l===h:m==="*="?l.indexOf(h)>=0:m==="~="?(" "+l+" ").indexOf(h)>=0:!h?l&&g!==false:m==="!="?l!==h:m==="^="?l.indexOf(h)===0:m==="$="?l.substr(l.length-h.length)===h:m==="|="?l===h||l.substr(0,h.length+1)===h+"-":false},POS:function(g,h,l,m){var q=n.setFilters[h[2]];if(q)return q(g,l,h,m)}}},r=n.match.POS;for(var u in n.match){n.match[u]=new RegExp(n.match[u].source+/(?![^\[]*\])(?![^\(]*\))/.source);n.leftMatch[u]=new RegExp(/(^(?:.|\r|\n)*?)/.source+n.match[u].source.replace(/\\(\d+)/g,function(g,
+h){return"\\"+(h-0+1)}))}var z=function(g,h){g=Array.prototype.slice.call(g,0);if(h){h.push.apply(h,g);return h}return g};try{Array.prototype.slice.call(s.documentElement.childNodes,0)}catch(C){z=function(g,h){h=h||[];if(j.call(g)==="[object Array]")Array.prototype.push.apply(h,g);else if(typeof g.length==="number")for(var l=0,m=g.length;l<m;l++)h.push(g[l]);else for(l=0;g[l];l++)h.push(g[l]);return h}}var B;if(s.documentElement.compareDocumentPosition)B=function(g,h){if(!g.compareDocumentPosition||
+!h.compareDocumentPosition){if(g==h)i=true;return g.compareDocumentPosition?-1:1}g=g.compareDocumentPosition(h)&4?-1:g===h?0:1;if(g===0)i=true;return g};else if("sourceIndex"in s.documentElement)B=function(g,h){if(!g.sourceIndex||!h.sourceIndex){if(g==h)i=true;return g.sourceIndex?-1:1}g=g.sourceIndex-h.sourceIndex;if(g===0)i=true;return g};else if(s.createRange)B=function(g,h){if(!g.ownerDocument||!h.ownerDocument){if(g==h)i=true;return g.ownerDocument?-1:1}var l=g.ownerDocument.createRange(),m=
+h.ownerDocument.createRange();l.setStart(g,0);l.setEnd(g,0);m.setStart(h,0);m.setEnd(h,0);g=l.compareBoundaryPoints(Range.START_TO_END,m);if(g===0)i=true;return g};(function(){var g=s.createElement("div"),h="script"+(new Date).getTime();g.innerHTML="<a name='"+h+"'/>";var l=s.documentElement;l.insertBefore(g,l.firstChild);if(s.getElementById(h)){n.find.ID=function(m,q,p){if(typeof q.getElementById!=="undefined"&&!p)return(q=q.getElementById(m[1]))?q.id===m[1]||typeof q.getAttributeNode!=="undefined"&&
+q.getAttributeNode("id").nodeValue===m[1]?[q]:w:[]};n.filter.ID=function(m,q){var p=typeof m.getAttributeNode!=="undefined"&&m.getAttributeNode("id");return m.nodeType===1&&p&&p.nodeValue===q}}l.removeChild(g);l=g=null})();(function(){var g=s.createElement("div");g.appendChild(s.createComment(""));if(g.getElementsByTagName("*").length>0)n.find.TAG=function(h,l){l=l.getElementsByTagName(h[1]);if(h[1]==="*"){h=[];for(var m=0;l[m];m++)l[m].nodeType===1&&h.push(l[m]);l=h}return l};g.innerHTML="<a href='#'></a>";
+if(g.firstChild&&typeof g.firstChild.getAttribute!=="undefined"&&g.firstChild.getAttribute("href")!=="#")n.attrHandle.href=function(h){return h.getAttribute("href",2)};g=null})();s.querySelectorAll&&function(){var g=k,h=s.createElement("div");h.innerHTML="<p class='TEST'></p>";if(!(h.querySelectorAll&&h.querySelectorAll(".TEST").length===0)){k=function(m,q,p,v){q=q||s;if(!v&&q.nodeType===9&&!x(q))try{return z(q.querySelectorAll(m),p)}catch(t){}return g(m,q,p,v)};for(var l in g)k[l]=g[l];h=null}}();
+(function(){var g=s.createElement("div");g.innerHTML="<div class='test e'></div><div class='test'></div>";if(!(!g.getElementsByClassName||g.getElementsByClassName("e").length===0)){g.lastChild.className="e";if(g.getElementsByClassName("e").length!==1){n.order.splice(1,0,"CLASS");n.find.CLASS=function(h,l,m){if(typeof l.getElementsByClassName!=="undefined"&&!m)return l.getElementsByClassName(h[1])};g=null}}})();var E=s.compareDocumentPosition?function(g,h){return!!(g.compareDocumentPosition(h)&16)}:
+function(g,h){return g!==h&&(g.contains?g.contains(h):true)},x=function(g){return(g=(g?g.ownerDocument||g:0).documentElement)?g.nodeName!=="HTML":false},ga=function(g,h){var l=[],m="",q;for(h=h.nodeType?[h]:h;q=n.match.PSEUDO.exec(g);){m+=q[0];g=g.replace(n.match.PSEUDO,"")}g=n.relative[g]?g+"*":g;q=0;for(var p=h.length;q<p;q++)k(g,h[q],l);return k.filter(m,l)};c.find=k;c.expr=k.selectors;c.expr[":"]=c.expr.filters;c.unique=k.uniqueSort;c.text=a;c.isXMLDoc=x;c.contains=E})();var eb=/Until$/,fb=/^(?:parents|prevUntil|prevAll)/,
+gb=/,/;R=Array.prototype.slice;var Ia=function(a,b,d){if(c.isFunction(b))return c.grep(a,function(e,j){return!!b.call(e,j,e)===d});else if(b.nodeType)return c.grep(a,function(e){return e===b===d});else if(typeof b==="string"){var f=c.grep(a,function(e){return e.nodeType===1});if(Ua.test(b))return c.filter(b,f,!d);else b=c.filter(b,f)}return c.grep(a,function(e){return c.inArray(e,b)>=0===d})};c.fn.extend({find:function(a){for(var b=this.pushStack("","find",a),d=0,f=0,e=this.length;f<e;f++){d=b.length;
+c.find(a,this[f],b);if(f>0)for(var j=d;j<b.length;j++)for(var i=0;i<d;i++)if(b[i]===b[j]){b.splice(j--,1);break}}return b},has:function(a){var b=c(a);return this.filter(function(){for(var d=0,f=b.length;d<f;d++)if(c.contains(this,b[d]))return true})},not:function(a){return this.pushStack(Ia(this,a,false),"not",a)},filter:function(a){return this.pushStack(Ia(this,a,true),"filter",a)},is:function(a){return!!a&&c.filter(a,this).length>0},closest:function(a,b){if(c.isArray(a)){var d=[],f=this[0],e,j=
+{},i;if(f&&a.length){e=0;for(var o=a.length;e<o;e++){i=a[e];j[i]||(j[i]=c.expr.match.POS.test(i)?c(i,b||this.context):i)}for(;f&&f.ownerDocument&&f!==b;){for(i in j){e=j[i];if(e.jquery?e.index(f)>-1:c(f).is(e)){d.push({selector:i,elem:f});delete j[i]}}f=f.parentNode}}return d}var k=c.expr.match.POS.test(a)?c(a,b||this.context):null;return this.map(function(n,r){for(;r&&r.ownerDocument&&r!==b;){if(k?k.index(r)>-1:c(r).is(a))return r;r=r.parentNode}return null})},index:function(a){if(!a||typeof a===
+"string")return c.inArray(this[0],a?c(a):this.parent().children());return c.inArray(a.jquery?a[0]:a,this)},add:function(a,b){a=typeof a==="string"?c(a,b||this.context):c.makeArray(a);b=c.merge(this.get(),a);return this.pushStack(qa(a[0])||qa(b[0])?b:c.unique(b))},andSelf:function(){return this.add(this.prevObject)}});c.each({parent:function(a){return(a=a.parentNode)&&a.nodeType!==11?a:null},parents:function(a){return c.dir(a,"parentNode")},parentsUntil:function(a,b,d){return c.dir(a,"parentNode",
+d)},next:function(a){return c.nth(a,2,"nextSibling")},prev:function(a){return c.nth(a,2,"previousSibling")},nextAll:function(a){return c.dir(a,"nextSibling")},prevAll:function(a){return c.dir(a,"previousSibling")},nextUntil:function(a,b,d){return c.dir(a,"nextSibling",d)},prevUntil:function(a,b,d){return c.dir(a,"previousSibling",d)},siblings:function(a){return c.sibling(a.parentNode.firstChild,a)},children:function(a){return c.sibling(a.firstChild)},contents:function(a){return c.nodeName(a,"iframe")?
+a.contentDocument||a.contentWindow.document:c.makeArray(a.childNodes)}},function(a,b){c.fn[a]=function(d,f){var e=c.map(this,b,d);eb.test(a)||(f=d);if(f&&typeof f==="string")e=c.filter(f,e);e=this.length>1?c.unique(e):e;if((this.length>1||gb.test(f))&&fb.test(a))e=e.reverse();return this.pushStack(e,a,R.call(arguments).join(","))}});c.extend({filter:function(a,b,d){if(d)a=":not("+a+")";return c.find.matches(a,b)},dir:function(a,b,d){var f=[];for(a=a[b];a&&a.nodeType!==9&&(d===w||a.nodeType!==1||!c(a).is(d));){a.nodeType===
+1&&f.push(a);a=a[b]}return f},nth:function(a,b,d){b=b||1;for(var f=0;a;a=a[d])if(a.nodeType===1&&++f===b)break;return a},sibling:function(a,b){for(var d=[];a;a=a.nextSibling)a.nodeType===1&&a!==b&&d.push(a);return d}});var Ja=/ jQuery\d+="(?:\d+|null)"/g,V=/^\s+/,Ka=/(<([\w:]+)[^>]*?)\/>/g,hb=/^(?:area|br|col|embed|hr|img|input|link|meta|param)$/i,La=/<([\w:]+)/,ib=/<tbody/i,jb=/<|&#?\w+;/,ta=/<script|<object|<embed|<option|<style/i,ua=/checked\s*(?:[^=]|=\s*.checked.)/i,Ma=function(a,b,d){return hb.test(d)?
+a:b+"></"+d+">"},F={option:[1,"<select multiple='multiple'>","</select>"],legend:[1,"<fieldset>","</fieldset>"],thead:[1,"<table>","</table>"],tr:[2,"<table><tbody>","</tbody></table>"],td:[3,"<table><tbody><tr>","</tr></tbody></table>"],col:[2,"<table><tbody></tbody><colgroup>","</colgroup></table>"],area:[1,"<map>","</map>"],_default:[0,"",""]};F.optgroup=F.option;F.tbody=F.tfoot=F.colgroup=F.caption=F.thead;F.th=F.td;if(!c.support.htmlSerialize)F._default=[1,"div<div>","</div>"];c.fn.extend({text:function(a){if(c.isFunction(a))return this.each(function(b){var d=
+c(this);d.text(a.call(this,b,d.text()))});if(typeof a!=="object"&&a!==w)return this.empty().append((this[0]&&this[0].ownerDocument||s).createTextNode(a));return c.text(this)},wrapAll:function(a){if(c.isFunction(a))return this.each(function(d){c(this).wrapAll(a.call(this,d))});if(this[0]){var b=c(a,this[0].ownerDocument).eq(0).clone(true);this[0].parentNode&&b.insertBefore(this[0]);b.map(function(){for(var d=this;d.firstChild&&d.firstChild.nodeType===1;)d=d.firstChild;return d}).append(this)}return this},
+wrapInner:function(a){if(c.isFunction(a))return this.each(function(b){c(this).wrapInner(a.call(this,b))});return this.each(function(){var b=c(this),d=b.contents();d.length?d.wrapAll(a):b.append(a)})},wrap:function(a){return this.each(function(){c(this).wrapAll(a)})},unwrap:function(){return this.parent().each(function(){c.nodeName(this,"body")||c(this).replaceWith(this.childNodes)}).end()},append:function(){return this.domManip(arguments,true,function(a){this.nodeType===1&&this.appendChild(a)})},
+prepend:function(){return this.domManip(arguments,true,function(a){this.nodeType===1&&this.insertBefore(a,this.firstChild)})},before:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments,false,function(b){this.parentNode.insertBefore(b,this)});else if(arguments.length){var a=c(arguments[0]);a.push.apply(a,this.toArray());return this.pushStack(a,"before",arguments)}},after:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments,false,function(b){this.parentNode.insertBefore(b,
+this.nextSibling)});else if(arguments.length){var a=this.pushStack(this,"after",arguments);a.push.apply(a,c(arguments[0]).toArray());return a}},remove:function(a,b){for(var d=0,f;(f=this[d])!=null;d++)if(!a||c.filter(a,[f]).length){if(!b&&f.nodeType===1){c.cleanData(f.getElementsByTagName("*"));c.cleanData([f])}f.parentNode&&f.parentNode.removeChild(f)}return this},empty:function(){for(var a=0,b;(b=this[a])!=null;a++)for(b.nodeType===1&&c.cleanData(b.getElementsByTagName("*"));b.firstChild;)b.removeChild(b.firstChild);
+return this},clone:function(a){var b=this.map(function(){if(!c.support.noCloneEvent&&!c.isXMLDoc(this)){var d=this.outerHTML,f=this.ownerDocument;if(!d){d=f.createElement("div");d.appendChild(this.cloneNode(true));d=d.innerHTML}return c.clean([d.replace(Ja,"").replace(/=([^="'>\s]+\/)>/g,'="$1">').replace(V,"")],f)[0]}else return this.cloneNode(true)});if(a===true){ra(this,b);ra(this.find("*"),b.find("*"))}return b},html:function(a){if(a===w)return this[0]&&this[0].nodeType===1?this[0].innerHTML.replace(Ja,
+""):null;else if(typeof a==="string"&&!ta.test(a)&&(c.support.leadingWhitespace||!V.test(a))&&!F[(La.exec(a)||["",""])[1].toLowerCase()]){a=a.replace(Ka,Ma);try{for(var b=0,d=this.length;b<d;b++)if(this[b].nodeType===1){c.cleanData(this[b].getElementsByTagName("*"));this[b].innerHTML=a}}catch(f){this.empty().append(a)}}else c.isFunction(a)?this.each(function(e){var j=c(this),i=j.html();j.empty().append(function(){return a.call(this,e,i)})}):this.empty().append(a);return this},replaceWith:function(a){if(this[0]&&
+this[0].parentNode){if(c.isFunction(a))return this.each(function(b){var d=c(this),f=d.html();d.replaceWith(a.call(this,b,f))});if(typeof a!=="string")a=c(a).detach();return this.each(function(){var b=this.nextSibling,d=this.parentNode;c(this).remove();b?c(b).before(a):c(d).append(a)})}else return this.pushStack(c(c.isFunction(a)?a():a),"replaceWith",a)},detach:function(a){return this.remove(a,true)},domManip:function(a,b,d){function f(u){return c.nodeName(u,"table")?u.getElementsByTagName("tbody")[0]||
+u.appendChild(u.ownerDocument.createElement("tbody")):u}var e,j,i=a[0],o=[],k;if(!c.support.checkClone&&arguments.length===3&&typeof i==="string"&&ua.test(i))return this.each(function(){c(this).domManip(a,b,d,true)});if(c.isFunction(i))return this.each(function(u){var z=c(this);a[0]=i.call(this,u,b?z.html():w);z.domManip(a,b,d)});if(this[0]){e=i&&i.parentNode;e=c.support.parentNode&&e&&e.nodeType===11&&e.childNodes.length===this.length?{fragment:e}:sa(a,this,o);k=e.fragment;if(j=k.childNodes.length===
+1?(k=k.firstChild):k.firstChild){b=b&&c.nodeName(j,"tr");for(var n=0,r=this.length;n<r;n++)d.call(b?f(this[n],j):this[n],n>0||e.cacheable||this.length>1?k.cloneNode(true):k)}o.length&&c.each(o,Qa)}return this}});c.fragments={};c.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(a,b){c.fn[a]=function(d){var f=[];d=c(d);var e=this.length===1&&this[0].parentNode;if(e&&e.nodeType===11&&e.childNodes.length===1&&d.length===1){d[b](this[0]);
+return this}else{e=0;for(var j=d.length;e<j;e++){var i=(e>0?this.clone(true):this).get();c.fn[b].apply(c(d[e]),i);f=f.concat(i)}return this.pushStack(f,a,d.selector)}}});c.extend({clean:function(a,b,d,f){b=b||s;if(typeof b.createElement==="undefined")b=b.ownerDocument||b[0]&&b[0].ownerDocument||s;for(var e=[],j=0,i;(i=a[j])!=null;j++){if(typeof i==="number")i+="";if(i){if(typeof i==="string"&&!jb.test(i))i=b.createTextNode(i);else if(typeof i==="string"){i=i.replace(Ka,Ma);var o=(La.exec(i)||["",
+""])[1].toLowerCase(),k=F[o]||F._default,n=k[0],r=b.createElement("div");for(r.innerHTML=k[1]+i+k[2];n--;)r=r.lastChild;if(!c.support.tbody){n=ib.test(i);o=o==="table"&&!n?r.firstChild&&r.firstChild.childNodes:k[1]==="<table>"&&!n?r.childNodes:[];for(k=o.length-1;k>=0;--k)c.nodeName(o[k],"tbody")&&!o[k].childNodes.length&&o[k].parentNode.removeChild(o[k])}!c.support.leadingWhitespace&&V.test(i)&&r.insertBefore(b.createTextNode(V.exec(i)[0]),r.firstChild);i=r.childNodes}if(i.nodeType)e.push(i);else e=
+c.merge(e,i)}}if(d)for(j=0;e[j];j++)if(f&&c.nodeName(e[j],"script")&&(!e[j].type||e[j].type.toLowerCase()==="text/javascript"))f.push(e[j].parentNode?e[j].parentNode.removeChild(e[j]):e[j]);else{e[j].nodeType===1&&e.splice.apply(e,[j+1,0].concat(c.makeArray(e[j].getElementsByTagName("script"))));d.appendChild(e[j])}return e},cleanData:function(a){for(var b,d,f=c.cache,e=c.event.special,j=c.support.deleteExpando,i=0,o;(o=a[i])!=null;i++)if(d=o[c.expando]){b=f[d];if(b.events)for(var k in b.events)e[k]?
+c.event.remove(o,k):Ca(o,k,b.handle);if(j)delete o[c.expando];else o.removeAttribute&&o.removeAttribute(c.expando);delete f[d]}}});var kb=/z-?index|font-?weight|opacity|zoom|line-?height/i,Na=/alpha\([^)]*\)/,Oa=/opacity=([^)]*)/,ha=/float/i,ia=/-([a-z])/ig,lb=/([A-Z])/g,mb=/^-?\d+(?:px)?$/i,nb=/^-?\d/,ob={position:"absolute",visibility:"hidden",display:"block"},pb=["Left","Right"],qb=["Top","Bottom"],rb=s.defaultView&&s.defaultView.getComputedStyle,Pa=c.support.cssFloat?"cssFloat":"styleFloat",ja=
+function(a,b){return b.toUpperCase()};c.fn.css=function(a,b){return X(this,a,b,true,function(d,f,e){if(e===w)return c.curCSS(d,f);if(typeof e==="number"&&!kb.test(f))e+="px";c.style(d,f,e)})};c.extend({style:function(a,b,d){if(!a||a.nodeType===3||a.nodeType===8)return w;if((b==="width"||b==="height")&&parseFloat(d)<0)d=w;var f=a.style||a,e=d!==w;if(!c.support.opacity&&b==="opacity"){if(e){f.zoom=1;b=parseInt(d,10)+""==="NaN"?"":"alpha(opacity="+d*100+")";a=f.filter||c.curCSS(a,"filter")||"";f.filter=
+Na.test(a)?a.replace(Na,b):b}return f.filter&&f.filter.indexOf("opacity=")>=0?parseFloat(Oa.exec(f.filter)[1])/100+"":""}if(ha.test(b))b=Pa;b=b.replace(ia,ja);if(e)f[b]=d;return f[b]},css:function(a,b,d,f){if(b==="width"||b==="height"){var e,j=b==="width"?pb:qb;function i(){e=b==="width"?a.offsetWidth:a.offsetHeight;f!=="border"&&c.each(j,function(){f||(e-=parseFloat(c.curCSS(a,"padding"+this,true))||0);if(f==="margin")e+=parseFloat(c.curCSS(a,"margin"+this,true))||0;else e-=parseFloat(c.curCSS(a,
+"border"+this+"Width",true))||0})}a.offsetWidth!==0?i():c.swap(a,ob,i);return Math.max(0,Math.round(e))}return c.curCSS(a,b,d)},curCSS:function(a,b,d){var f,e=a.style;if(!c.support.opacity&&b==="opacity"&&a.currentStyle){f=Oa.test(a.currentStyle.filter||"")?parseFloat(RegExp.$1)/100+"":"";return f===""?"1":f}if(ha.test(b))b=Pa;if(!d&&e&&e[b])f=e[b];else if(rb){if(ha.test(b))b="float";b=b.replace(lb,"-$1").toLowerCase();e=a.ownerDocument.defaultView;if(!e)return null;if(a=e.getComputedStyle(a,null))f=
+a.getPropertyValue(b);if(b==="opacity"&&f==="")f="1"}else if(a.currentStyle){d=b.replace(ia,ja);f=a.currentStyle[b]||a.currentStyle[d];if(!mb.test(f)&&nb.test(f)){b=e.left;var j=a.runtimeStyle.left;a.runtimeStyle.left=a.currentStyle.left;e.left=d==="fontSize"?"1em":f||0;f=e.pixelLeft+"px";e.left=b;a.runtimeStyle.left=j}}return f},swap:function(a,b,d){var f={};for(var e in b){f[e]=a.style[e];a.style[e]=b[e]}d.call(a);for(e in b)a.style[e]=f[e]}});if(c.expr&&c.expr.filters){c.expr.filters.hidden=function(a){var b=
+a.offsetWidth,d=a.offsetHeight,f=a.nodeName.toLowerCase()==="tr";return b===0&&d===0&&!f?true:b>0&&d>0&&!f?false:c.curCSS(a,"display")==="none"};c.expr.filters.visible=function(a){return!c.expr.filters.hidden(a)}}var sb=J(),tb=/<script(.|\s)*?\/script>/gi,ub=/select|textarea/i,vb=/color|date|datetime|email|hidden|month|number|password|range|search|tel|text|time|url|week/i,N=/=\?(&|$)/,ka=/\?/,wb=/(\?|&)_=.*?(&|$)/,xb=/^(\w+:)?\/\/([^\/?#]+)/,yb=/%20/g,zb=c.fn.load;c.fn.extend({load:function(a,b,d){if(typeof a!==
+"string")return zb.call(this,a);else if(!this.length)return this;var f=a.indexOf(" ");if(f>=0){var e=a.slice(f,a.length);a=a.slice(0,f)}f="GET";if(b)if(c.isFunction(b)){d=b;b=null}else if(typeof b==="object"){b=c.param(b,c.ajaxSettings.traditional);f="POST"}var j=this;c.ajax({url:a,type:f,dataType:"html",data:b,complete:function(i,o){if(o==="success"||o==="notmodified")j.html(e?c("<div />").append(i.responseText.replace(tb,"")).find(e):i.responseText);d&&j.each(d,[i.responseText,o,i])}});return this},
+serialize:function(){return c.param(this.serializeArray())},serializeArray:function(){return this.map(function(){return this.elements?c.makeArray(this.elements):this}).filter(function(){return this.name&&!this.disabled&&(this.checked||ub.test(this.nodeName)||vb.test(this.type))}).map(function(a,b){a=c(this).val();return a==null?null:c.isArray(a)?c.map(a,function(d){return{name:b.name,value:d}}):{name:b.name,value:a}}).get()}});c.each("ajaxStart ajaxStop ajaxComplete ajaxError ajaxSuccess ajaxSend".split(" "),
+function(a,b){c.fn[b]=function(d){return this.bind(b,d)}});c.extend({get:function(a,b,d,f){if(c.isFunction(b)){f=f||d;d=b;b=null}return c.ajax({type:"GET",url:a,data:b,success:d,dataType:f})},getScript:function(a,b){return c.get(a,null,b,"script")},getJSON:function(a,b,d){return c.get(a,b,d,"json")},post:function(a,b,d,f){if(c.isFunction(b)){f=f||d;d=b;b={}}return c.ajax({type:"POST",url:a,data:b,success:d,dataType:f})},ajaxSetup:function(a){c.extend(c.ajaxSettings,a)},ajaxSettings:{url:location.href,
+global:true,type:"GET",contentType:"application/x-www-form-urlencoded",processData:true,async:true,xhr:A.XMLHttpRequest&&(A.location.protocol!=="file:"||!A.ActiveXObject)?function(){return new A.XMLHttpRequest}:function(){try{return new A.ActiveXObject("Microsoft.XMLHTTP")}catch(a){}},accepts:{xml:"application/xml, text/xml",html:"text/html",script:"text/javascript, application/javascript",json:"application/json, text/javascript",text:"text/plain",_default:"*/*"}},lastModified:{},etag:{},ajax:function(a){function b(){e.success&&
+e.success.call(k,o,i,x);e.global&&f("ajaxSuccess",[x,e])}function d(){e.complete&&e.complete.call(k,x,i);e.global&&f("ajaxComplete",[x,e]);e.global&&!--c.active&&c.event.trigger("ajaxStop")}function f(q,p){(e.context?c(e.context):c.event).trigger(q,p)}var e=c.extend(true,{},c.ajaxSettings,a),j,i,o,k=a&&a.context||e,n=e.type.toUpperCase();if(e.data&&e.processData&&typeof e.data!=="string")e.data=c.param(e.data,e.traditional);if(e.dataType==="jsonp"){if(n==="GET")N.test(e.url)||(e.url+=(ka.test(e.url)?
+"&":"?")+(e.jsonp||"callback")+"=?");else if(!e.data||!N.test(e.data))e.data=(e.data?e.data+"&":"")+(e.jsonp||"callback")+"=?";e.dataType="json"}if(e.dataType==="json"&&(e.data&&N.test(e.data)||N.test(e.url))){j=e.jsonpCallback||"jsonp"+sb++;if(e.data)e.data=(e.data+"").replace(N,"="+j+"$1");e.url=e.url.replace(N,"="+j+"$1");e.dataType="script";A[j]=A[j]||function(q){o=q;b();d();A[j]=w;try{delete A[j]}catch(p){}z&&z.removeChild(C)}}if(e.dataType==="script"&&e.cache===null)e.cache=false;if(e.cache===
+false&&n==="GET"){var r=J(),u=e.url.replace(wb,"$1_="+r+"$2");e.url=u+(u===e.url?(ka.test(e.url)?"&":"?")+"_="+r:"")}if(e.data&&n==="GET")e.url+=(ka.test(e.url)?"&":"?")+e.data;e.global&&!c.active++&&c.event.trigger("ajaxStart");r=(r=xb.exec(e.url))&&(r[1]&&r[1]!==location.protocol||r[2]!==location.host);if(e.dataType==="script"&&n==="GET"&&r){var z=s.getElementsByTagName("head")[0]||s.documentElement,C=s.createElement("script");C.src=e.url;if(e.scriptCharset)C.charset=e.scriptCharset;if(!j){var B=
+false;C.onload=C.onreadystatechange=function(){if(!B&&(!this.readyState||this.readyState==="loaded"||this.readyState==="complete")){B=true;b();d();C.onload=C.onreadystatechange=null;z&&C.parentNode&&z.removeChild(C)}}}z.insertBefore(C,z.firstChild);return w}var E=false,x=e.xhr();if(x){e.username?x.open(n,e.url,e.async,e.username,e.password):x.open(n,e.url,e.async);try{if(e.data||a&&a.contentType)x.setRequestHeader("Content-Type",e.contentType);if(e.ifModified){c.lastModified[e.url]&&x.setRequestHeader("If-Modified-Since",
+c.lastModified[e.url]);c.etag[e.url]&&x.setRequestHeader("If-None-Match",c.etag[e.url])}r||x.setRequestHeader("X-Requested-With","XMLHttpRequest");x.setRequestHeader("Accept",e.dataType&&e.accepts[e.dataType]?e.accepts[e.dataType]+", */*":e.accepts._default)}catch(ga){}if(e.beforeSend&&e.beforeSend.call(k,x,e)===false){e.global&&!--c.active&&c.event.trigger("ajaxStop");x.abort();return false}e.global&&f("ajaxSend",[x,e]);var g=x.onreadystatechange=function(q){if(!x||x.readyState===0||q==="abort"){E||
+d();E=true;if(x)x.onreadystatechange=c.noop}else if(!E&&x&&(x.readyState===4||q==="timeout")){E=true;x.onreadystatechange=c.noop;i=q==="timeout"?"timeout":!c.httpSuccess(x)?"error":e.ifModified&&c.httpNotModified(x,e.url)?"notmodified":"success";var p;if(i==="success")try{o=c.httpData(x,e.dataType,e)}catch(v){i="parsererror";p=v}if(i==="success"||i==="notmodified")j||b();else c.handleError(e,x,i,p);d();q==="timeout"&&x.abort();if(e.async)x=null}};try{var h=x.abort;x.abort=function(){x&&h.call(x);
+g("abort")}}catch(l){}e.async&&e.timeout>0&&setTimeout(function(){x&&!E&&g("timeout")},e.timeout);try{x.send(n==="POST"||n==="PUT"||n==="DELETE"?e.data:null)}catch(m){c.handleError(e,x,null,m);d()}e.async||g();return x}},handleError:function(a,b,d,f){if(a.error)a.error.call(a.context||a,b,d,f);if(a.global)(a.context?c(a.context):c.event).trigger("ajaxError",[b,a,f])},active:0,httpSuccess:function(a){try{return!a.status&&location.protocol==="file:"||a.status>=200&&a.status<300||a.status===304||a.status===
+1223||a.status===0}catch(b){}return false},httpNotModified:function(a,b){var d=a.getResponseHeader("Last-Modified"),f=a.getResponseHeader("Etag");if(d)c.lastModified[b]=d;if(f)c.etag[b]=f;return a.status===304||a.status===0},httpData:function(a,b,d){var f=a.getResponseHeader("content-type")||"",e=b==="xml"||!b&&f.indexOf("xml")>=0;a=e?a.responseXML:a.responseText;e&&a.documentElement.nodeName==="parsererror"&&c.error("parsererror");if(d&&d.dataFilter)a=d.dataFilter(a,b);if(typeof a==="string")if(b===
+"json"||!b&&f.indexOf("json")>=0)a=c.parseJSON(a);else if(b==="script"||!b&&f.indexOf("javascript")>=0)c.globalEval(a);return a},param:function(a,b){function d(i,o){if(c.isArray(o))c.each(o,function(k,n){b||/\[\]$/.test(i)?f(i,n):d(i+"["+(typeof n==="object"||c.isArray(n)?k:"")+"]",n)});else!b&&o!=null&&typeof o==="object"?c.each(o,function(k,n){d(i+"["+k+"]",n)}):f(i,o)}function f(i,o){o=c.isFunction(o)?o():o;e[e.length]=encodeURIComponent(i)+"="+encodeURIComponent(o)}var e=[];if(b===w)b=c.ajaxSettings.traditional;
+if(c.isArray(a)||a.jquery)c.each(a,function(){f(this.name,this.value)});else for(var j in a)d(j,a[j]);return e.join("&").replace(yb,"+")}});var la={},Ab=/toggle|show|hide/,Bb=/^([+-]=)?([\d+-.]+)(.*)$/,W,va=[["height","marginTop","marginBottom","paddingTop","paddingBottom"],["width","marginLeft","marginRight","paddingLeft","paddingRight"],["opacity"]];c.fn.extend({show:function(a,b){if(a||a===0)return this.animate(K("show",3),a,b);else{a=0;for(b=this.length;a<b;a++){var d=c.data(this[a],"olddisplay");
+this[a].style.display=d||"";if(c.css(this[a],"display")==="none"){d=this[a].nodeName;var f;if(la[d])f=la[d];else{var e=c("<"+d+" />").appendTo("body");f=e.css("display");if(f==="none")f="block";e.remove();la[d]=f}c.data(this[a],"olddisplay",f)}}a=0;for(b=this.length;a<b;a++)this[a].style.display=c.data(this[a],"olddisplay")||"";return this}},hide:function(a,b){if(a||a===0)return this.animate(K("hide",3),a,b);else{a=0;for(b=this.length;a<b;a++){var d=c.data(this[a],"olddisplay");!d&&d!=="none"&&c.data(this[a],
+"olddisplay",c.css(this[a],"display"))}a=0;for(b=this.length;a<b;a++)this[a].style.display="none";return this}},_toggle:c.fn.toggle,toggle:function(a,b){var d=typeof a==="boolean";if(c.isFunction(a)&&c.isFunction(b))this._toggle.apply(this,arguments);else a==null||d?this.each(function(){var f=d?a:c(this).is(":hidden");c(this)[f?"show":"hide"]()}):this.animate(K("toggle",3),a,b);return this},fadeTo:function(a,b,d){return this.filter(":hidden").css("opacity",0).show().end().animate({opacity:b},a,d)},
+animate:function(a,b,d,f){var e=c.speed(b,d,f);if(c.isEmptyObject(a))return this.each(e.complete);return this[e.queue===false?"each":"queue"](function(){var j=c.extend({},e),i,o=this.nodeType===1&&c(this).is(":hidden"),k=this;for(i in a){var n=i.replace(ia,ja);if(i!==n){a[n]=a[i];delete a[i];i=n}if(a[i]==="hide"&&o||a[i]==="show"&&!o)return j.complete.call(this);if((i==="height"||i==="width")&&this.style){j.display=c.css(this,"display");j.overflow=this.style.overflow}if(c.isArray(a[i])){(j.specialEasing=
+j.specialEasing||{})[i]=a[i][1];a[i]=a[i][0]}}if(j.overflow!=null)this.style.overflow="hidden";j.curAnim=c.extend({},a);c.each(a,function(r,u){var z=new c.fx(k,j,r);if(Ab.test(u))z[u==="toggle"?o?"show":"hide":u](a);else{var C=Bb.exec(u),B=z.cur(true)||0;if(C){u=parseFloat(C[2]);var E=C[3]||"px";if(E!=="px"){k.style[r]=(u||1)+E;B=(u||1)/z.cur(true)*B;k.style[r]=B+E}if(C[1])u=(C[1]==="-="?-1:1)*u+B;z.custom(B,u,E)}else z.custom(B,u,"")}});return true})},stop:function(a,b){var d=c.timers;a&&this.queue([]);
+this.each(function(){for(var f=d.length-1;f>=0;f--)if(d[f].elem===this){b&&d[f](true);d.splice(f,1)}});b||this.dequeue();return this}});c.each({slideDown:K("show",1),slideUp:K("hide",1),slideToggle:K("toggle",1),fadeIn:{opacity:"show"},fadeOut:{opacity:"hide"}},function(a,b){c.fn[a]=function(d,f){return this.animate(b,d,f)}});c.extend({speed:function(a,b,d){var f=a&&typeof a==="object"?a:{complete:d||!d&&b||c.isFunction(a)&&a,duration:a,easing:d&&b||b&&!c.isFunction(b)&&b};f.duration=c.fx.off?0:typeof f.duration===
+"number"?f.duration:c.fx.speeds[f.duration]||c.fx.speeds._default;f.old=f.complete;f.complete=function(){f.queue!==false&&c(this).dequeue();c.isFunction(f.old)&&f.old.call(this)};return f},easing:{linear:function(a,b,d,f){return d+f*a},swing:function(a,b,d,f){return(-Math.cos(a*Math.PI)/2+0.5)*f+d}},timers:[],fx:function(a,b,d){this.options=b;this.elem=a;this.prop=d;if(!b.orig)b.orig={}}});c.fx.prototype={update:function(){this.options.step&&this.options.step.call(this.elem,this.now,this);(c.fx.step[this.prop]||
+c.fx.step._default)(this);if((this.prop==="height"||this.prop==="width")&&this.elem.style)this.elem.style.display="block"},cur:function(a){if(this.elem[this.prop]!=null&&(!this.elem.style||this.elem.style[this.prop]==null))return this.elem[this.prop];return(a=parseFloat(c.css(this.elem,this.prop,a)))&&a>-10000?a:parseFloat(c.curCSS(this.elem,this.prop))||0},custom:function(a,b,d){function f(j){return e.step(j)}this.startTime=J();this.start=a;this.end=b;this.unit=d||this.unit||"px";this.now=this.start;
+this.pos=this.state=0;var e=this;f.elem=this.elem;if(f()&&c.timers.push(f)&&!W)W=setInterval(c.fx.tick,13)},show:function(){this.options.orig[this.prop]=c.style(this.elem,this.prop);this.options.show=true;this.custom(this.prop==="width"||this.prop==="height"?1:0,this.cur());c(this.elem).show()},hide:function(){this.options.orig[this.prop]=c.style(this.elem,this.prop);this.options.hide=true;this.custom(this.cur(),0)},step:function(a){var b=J(),d=true;if(a||b>=this.options.duration+this.startTime){this.now=
+this.end;this.pos=this.state=1;this.update();this.options.curAnim[this.prop]=true;for(var f in this.options.curAnim)if(this.options.curAnim[f]!==true)d=false;if(d){if(this.options.display!=null){this.elem.style.overflow=this.options.overflow;a=c.data(this.elem,"olddisplay");this.elem.style.display=a?a:this.options.display;if(c.css(this.elem,"display")==="none")this.elem.style.display="block"}this.options.hide&&c(this.elem).hide();if(this.options.hide||this.options.show)for(var e in this.options.curAnim)c.style(this.elem,
+e,this.options.orig[e]);this.options.complete.call(this.elem)}return false}else{e=b-this.startTime;this.state=e/this.options.duration;a=this.options.easing||(c.easing.swing?"swing":"linear");this.pos=c.easing[this.options.specialEasing&&this.options.specialEasing[this.prop]||a](this.state,e,0,1,this.options.duration);this.now=this.start+(this.end-this.start)*this.pos;this.update()}return true}};c.extend(c.fx,{tick:function(){for(var a=c.timers,b=0;b<a.length;b++)a[b]()||a.splice(b--,1);a.length||
+c.fx.stop()},stop:function(){clearInterval(W);W=null},speeds:{slow:600,fast:200,_default:400},step:{opacity:function(a){c.style(a.elem,"opacity",a.now)},_default:function(a){if(a.elem.style&&a.elem.style[a.prop]!=null)a.elem.style[a.prop]=(a.prop==="width"||a.prop==="height"?Math.max(0,a.now):a.now)+a.unit;else a.elem[a.prop]=a.now}}});if(c.expr&&c.expr.filters)c.expr.filters.animated=function(a){return c.grep(c.timers,function(b){return a===b.elem}).length};c.fn.offset="getBoundingClientRect"in s.documentElement?
+function(a){var b=this[0];if(a)return this.each(function(e){c.offset.setOffset(this,a,e)});if(!b||!b.ownerDocument)return null;if(b===b.ownerDocument.body)return c.offset.bodyOffset(b);var d=b.getBoundingClientRect(),f=b.ownerDocument;b=f.body;f=f.documentElement;return{top:d.top+(self.pageYOffset||c.support.boxModel&&f.scrollTop||b.scrollTop)-(f.clientTop||b.clientTop||0),left:d.left+(self.pageXOffset||c.support.boxModel&&f.scrollLeft||b.scrollLeft)-(f.clientLeft||b.clientLeft||0)}}:function(a){var b=
+this[0];if(a)return this.each(function(r){c.offset.setOffset(this,a,r)});if(!b||!b.ownerDocument)return null;if(b===b.ownerDocument.body)return c.offset.bodyOffset(b);c.offset.initialize();var d=b.offsetParent,f=b,e=b.ownerDocument,j,i=e.documentElement,o=e.body;f=(e=e.defaultView)?e.getComputedStyle(b,null):b.currentStyle;for(var k=b.offsetTop,n=b.offsetLeft;(b=b.parentNode)&&b!==o&&b!==i;){if(c.offset.supportsFixedPosition&&f.position==="fixed")break;j=e?e.getComputedStyle(b,null):b.currentStyle;
+k-=b.scrollTop;n-=b.scrollLeft;if(b===d){k+=b.offsetTop;n+=b.offsetLeft;if(c.offset.doesNotAddBorder&&!(c.offset.doesAddBorderForTableAndCells&&/^t(able|d|h)$/i.test(b.nodeName))){k+=parseFloat(j.borderTopWidth)||0;n+=parseFloat(j.borderLeftWidth)||0}f=d;d=b.offsetParent}if(c.offset.subtractsBorderForOverflowNotVisible&&j.overflow!=="visible"){k+=parseFloat(j.borderTopWidth)||0;n+=parseFloat(j.borderLeftWidth)||0}f=j}if(f.position==="relative"||f.position==="static"){k+=o.offsetTop;n+=o.offsetLeft}if(c.offset.supportsFixedPosition&&
+f.position==="fixed"){k+=Math.max(i.scrollTop,o.scrollTop);n+=Math.max(i.scrollLeft,o.scrollLeft)}return{top:k,left:n}};c.offset={initialize:function(){var a=s.body,b=s.createElement("div"),d,f,e,j=parseFloat(c.curCSS(a,"marginTop",true))||0;c.extend(b.style,{position:"absolute",top:0,left:0,margin:0,border:0,width:"1px",height:"1px",visibility:"hidden"});b.innerHTML="<div style='position:absolute;top:0;left:0;margin:0;border:5px solid #000;padding:0;width:1px;height:1px;'><div></div></div><table style='position:absolute;top:0;left:0;margin:0;border:5px solid #000;padding:0;width:1px;height:1px;' cellpadding='0' cellspacing='0'><tr><td></td></tr></table>";
+a.insertBefore(b,a.firstChild);d=b.firstChild;f=d.firstChild;e=d.nextSibling.firstChild.firstChild;this.doesNotAddBorder=f.offsetTop!==5;this.doesAddBorderForTableAndCells=e.offsetTop===5;f.style.position="fixed";f.style.top="20px";this.supportsFixedPosition=f.offsetTop===20||f.offsetTop===15;f.style.position=f.style.top="";d.style.overflow="hidden";d.style.position="relative";this.subtractsBorderForOverflowNotVisible=f.offsetTop===-5;this.doesNotIncludeMarginInBodyOffset=a.offsetTop!==j;a.removeChild(b);
+c.offset.initialize=c.noop},bodyOffset:function(a){var b=a.offsetTop,d=a.offsetLeft;c.offset.initialize();if(c.offset.doesNotIncludeMarginInBodyOffset){b+=parseFloat(c.curCSS(a,"marginTop",true))||0;d+=parseFloat(c.curCSS(a,"marginLeft",true))||0}return{top:b,left:d}},setOffset:function(a,b,d){if(/static/.test(c.curCSS(a,"position")))a.style.position="relative";var f=c(a),e=f.offset(),j=parseInt(c.curCSS(a,"top",true),10)||0,i=parseInt(c.curCSS(a,"left",true),10)||0;if(c.isFunction(b))b=b.call(a,
+d,e);d={top:b.top-e.top+j,left:b.left-e.left+i};"using"in b?b.using.call(a,d):f.css(d)}};c.fn.extend({position:function(){if(!this[0])return null;var a=this[0],b=this.offsetParent(),d=this.offset(),f=/^body|html$/i.test(b[0].nodeName)?{top:0,left:0}:b.offset();d.top-=parseFloat(c.curCSS(a,"marginTop",true))||0;d.left-=parseFloat(c.curCSS(a,"marginLeft",true))||0;f.top+=parseFloat(c.curCSS(b[0],"borderTopWidth",true))||0;f.left+=parseFloat(c.curCSS(b[0],"borderLeftWidth",true))||0;return{top:d.top-
+f.top,left:d.left-f.left}},offsetParent:function(){return this.map(function(){for(var a=this.offsetParent||s.body;a&&!/^body|html$/i.test(a.nodeName)&&c.css(a,"position")==="static";)a=a.offsetParent;return a})}});c.each(["Left","Top"],function(a,b){var d="scroll"+b;c.fn[d]=function(f){var e=this[0],j;if(!e)return null;if(f!==w)return this.each(function(){if(j=wa(this))j.scrollTo(!a?f:c(j).scrollLeft(),a?f:c(j).scrollTop());else this[d]=f});else return(j=wa(e))?"pageXOffset"in j?j[a?"pageYOffset":
+"pageXOffset"]:c.support.boxModel&&j.document.documentElement[d]||j.document.body[d]:e[d]}});c.each(["Height","Width"],function(a,b){var d=b.toLowerCase();c.fn["inner"+b]=function(){return this[0]?c.css(this[0],d,false,"padding"):null};c.fn["outer"+b]=function(f){return this[0]?c.css(this[0],d,false,f?"margin":"border"):null};c.fn[d]=function(f){var e=this[0];if(!e)return f==null?null:this;if(c.isFunction(f))return this.each(function(j){var i=c(this);i[d](f.call(this,j,i[d]()))});return"scrollTo"in
+e&&e.document?e.document.compatMode==="CSS1Compat"&&e.document.documentElement["client"+b]||e.document.body["client"+b]:e.nodeType===9?Math.max(e.documentElement["client"+b],e.body["scroll"+b],e.documentElement["scroll"+b],e.body["offset"+b],e.documentElement["offset"+b]):f===w?c.css(e,d):this.css(d,typeof f==="string"?f:f+"px")}});A.jQuery=A.$=c})(window);

BIN
desktop/core/ext-py/django-auth-ldap-1.2.0/docs/archive/versions/1.0.19/_static/minus.png


BIN
desktop/core/ext-py/django-auth-ldap-1.2.0/docs/archive/versions/1.0.19/_static/plus.png


+ 62 - 0
desktop/core/ext-py/django-auth-ldap-1.2.0/docs/archive/versions/1.0.19/_static/pygments.css

@@ -0,0 +1,62 @@
+.highlight .hll { background-color: #ffffcc }
+.highlight  { background: #eeffcc; }
+.highlight .c { color: #408090; font-style: italic } /* Comment */
+.highlight .err { border: 1px solid #FF0000 } /* Error */
+.highlight .k { color: #007020; font-weight: bold } /* Keyword */
+.highlight .o { color: #666666 } /* Operator */
+.highlight .cm { color: #408090; font-style: italic } /* Comment.Multiline */
+.highlight .cp { color: #007020 } /* Comment.Preproc */
+.highlight .c1 { color: #408090; font-style: italic } /* Comment.Single */
+.highlight .cs { color: #408090; background-color: #fff0f0 } /* Comment.Special */
+.highlight .gd { color: #A00000 } /* Generic.Deleted */
+.highlight .ge { font-style: italic } /* Generic.Emph */
+.highlight .gr { color: #FF0000 } /* Generic.Error */
+.highlight .gh { color: #000080; font-weight: bold } /* Generic.Heading */
+.highlight .gi { color: #00A000 } /* Generic.Inserted */
+.highlight .go { color: #333333 } /* Generic.Output */
+.highlight .gp { color: #c65d09; font-weight: bold } /* Generic.Prompt */
+.highlight .gs { font-weight: bold } /* Generic.Strong */
+.highlight .gu { color: #800080; font-weight: bold } /* Generic.Subheading */
+.highlight .gt { color: #0044DD } /* Generic.Traceback */
+.highlight .kc { color: #007020; font-weight: bold } /* Keyword.Constant */
+.highlight .kd { color: #007020; font-weight: bold } /* Keyword.Declaration */
+.highlight .kn { color: #007020; font-weight: bold } /* Keyword.Namespace */
+.highlight .kp { color: #007020 } /* Keyword.Pseudo */
+.highlight .kr { color: #007020; font-weight: bold } /* Keyword.Reserved */
+.highlight .kt { color: #902000 } /* Keyword.Type */
+.highlight .m { color: #208050 } /* Literal.Number */
+.highlight .s { color: #4070a0 } /* Literal.String */
+.highlight .na { color: #4070a0 } /* Name.Attribute */
+.highlight .nb { color: #007020 } /* Name.Builtin */
+.highlight .nc { color: #0e84b5; font-weight: bold } /* Name.Class */
+.highlight .no { color: #60add5 } /* Name.Constant */
+.highlight .nd { color: #555555; font-weight: bold } /* Name.Decorator */
+.highlight .ni { color: #d55537; font-weight: bold } /* Name.Entity */
+.highlight .ne { color: #007020 } /* Name.Exception */
+.highlight .nf { color: #06287e } /* Name.Function */
+.highlight .nl { color: #002070; font-weight: bold } /* Name.Label */
+.highlight .nn { color: #0e84b5; font-weight: bold } /* Name.Namespace */
+.highlight .nt { color: #062873; font-weight: bold } /* Name.Tag */
+.highlight .nv { color: #bb60d5 } /* Name.Variable */
+.highlight .ow { color: #007020; font-weight: bold } /* Operator.Word */
+.highlight .w { color: #bbbbbb } /* Text.Whitespace */
+.highlight .mf { color: #208050 } /* Literal.Number.Float */
+.highlight .mh { color: #208050 } /* Literal.Number.Hex */
+.highlight .mi { color: #208050 } /* Literal.Number.Integer */
+.highlight .mo { color: #208050 } /* Literal.Number.Oct */
+.highlight .sb { color: #4070a0 } /* Literal.String.Backtick */
+.highlight .sc { color: #4070a0 } /* Literal.String.Char */
+.highlight .sd { color: #4070a0; font-style: italic } /* Literal.String.Doc */
+.highlight .s2 { color: #4070a0 } /* Literal.String.Double */
+.highlight .se { color: #4070a0; font-weight: bold } /* Literal.String.Escape */
+.highlight .sh { color: #4070a0 } /* Literal.String.Heredoc */
+.highlight .si { color: #70a0d0; font-style: italic } /* Literal.String.Interpol */
+.highlight .sx { color: #c65d09 } /* Literal.String.Other */
+.highlight .sr { color: #235388 } /* Literal.String.Regex */
+.highlight .s1 { color: #4070a0 } /* Literal.String.Single */
+.highlight .ss { color: #517918 } /* Literal.String.Symbol */
+.highlight .bp { color: #007020 } /* Name.Builtin.Pseudo */
+.highlight .vc { color: #bb60d5 } /* Name.Variable.Class */
+.highlight .vg { color: #bb60d5 } /* Name.Variable.Global */
+.highlight .vi { color: #bb60d5 } /* Name.Variable.Instance */
+.highlight .il { color: #208050 } /* Literal.Number.Integer.Long */

+ 560 - 0
desktop/core/ext-py/django-auth-ldap-1.2.0/docs/archive/versions/1.0.19/_static/searchtools.js

@@ -0,0 +1,560 @@
+/*
+ * searchtools.js_t
+ * ~~~~~~~~~~~~~~~~
+ *
+ * Sphinx JavaScript utilties for the full-text search.
+ *
+ * :copyright: Copyright 2007-2011 by the Sphinx team, see AUTHORS.
+ * :license: BSD, see LICENSE for details.
+ *
+ */
+
+/**
+ * helper function to return a node containing the
+ * search summary for a given text. keywords is a list
+ * of stemmed words, hlwords is the list of normal, unstemmed
+ * words. the first one is used to find the occurance, the
+ * latter for highlighting it.
+ */
+
+jQuery.makeSearchSummary = function(text, keywords, hlwords) {
+  var textLower = text.toLowerCase();
+  var start = 0;
+  $.each(keywords, function() {
+    var i = textLower.indexOf(this.toLowerCase());
+    if (i > -1)
+      start = i;
+  });
+  start = Math.max(start - 120, 0);
+  var excerpt = ((start > 0) ? '...' : '') +
+  $.trim(text.substr(start, 240)) +
+  ((start + 240 - text.length) ? '...' : '');
+  var rv = $('<div class="context"></div>').text(excerpt);
+  $.each(hlwords, function() {
+    rv = rv.highlightText(this, 'highlighted');
+  });
+  return rv;
+}
+
+
+/**
+ * Porter Stemmer
+ */
+var Stemmer = function() {
+
+  var step2list = {
+    ational: 'ate',
+    tional: 'tion',
+    enci: 'ence',
+    anci: 'ance',
+    izer: 'ize',
+    bli: 'ble',
+    alli: 'al',
+    entli: 'ent',
+    eli: 'e',
+    ousli: 'ous',
+    ization: 'ize',
+    ation: 'ate',
+    ator: 'ate',
+    alism: 'al',
+    iveness: 'ive',
+    fulness: 'ful',
+    ousness: 'ous',
+    aliti: 'al',
+    iviti: 'ive',
+    biliti: 'ble',
+    logi: 'log'
+  };
+
+  var step3list = {
+    icate: 'ic',
+    ative: '',
+    alize: 'al',
+    iciti: 'ic',
+    ical: 'ic',
+    ful: '',
+    ness: ''
+  };
+
+  var c = "[^aeiou]";          // consonant
+  var v = "[aeiouy]";          // vowel
+  var C = c + "[^aeiouy]*";    // consonant sequence
+  var V = v + "[aeiou]*";      // vowel sequence
+
+  var mgr0 = "^(" + C + ")?" + V + C;                      // [C]VC... is m>0
+  var meq1 = "^(" + C + ")?" + V + C + "(" + V + ")?$";    // [C]VC[V] is m=1
+  var mgr1 = "^(" + C + ")?" + V + C + V + C;              // [C]VCVC... is m>1
+  var s_v   = "^(" + C + ")?" + v;                         // vowel in stem
+
+  this.stemWord = function (w) {
+    var stem;
+    var suffix;
+    var firstch;
+    var origword = w;
+
+    if (w.length < 3)
+      return w;
+
+    var re;
+    var re2;
+    var re3;
+    var re4;
+
+    firstch = w.substr(0,1);
+    if (firstch == "y")
+      w = firstch.toUpperCase() + w.substr(1);
+
+    // Step 1a
+    re = /^(.+?)(ss|i)es$/;
+    re2 = /^(.+?)([^s])s$/;
+
+    if (re.test(w))
+      w = w.replace(re,"$1$2");
+    else if (re2.test(w))
+      w = w.replace(re2,"$1$2");
+
+    // Step 1b
+    re = /^(.+?)eed$/;
+    re2 = /^(.+?)(ed|ing)$/;
+    if (re.test(w)) {
+      var fp = re.exec(w);
+      re = new RegExp(mgr0);
+      if (re.test(fp[1])) {
+        re = /.$/;
+        w = w.replace(re,"");
+      }
+    }
+    else if (re2.test(w)) {
+      var fp = re2.exec(w);
+      stem = fp[1];
+      re2 = new RegExp(s_v);
+      if (re2.test(stem)) {
+        w = stem;
+        re2 = /(at|bl|iz)$/;
+        re3 = new RegExp("([^aeiouylsz])\\1$");
+        re4 = new RegExp("^" + C + v + "[^aeiouwxy]$");
+        if (re2.test(w))
+          w = w + "e";
+        else if (re3.test(w)) {
+          re = /.$/;
+          w = w.replace(re,"");
+        }
+        else if (re4.test(w))
+          w = w + "e";
+      }
+    }
+
+    // Step 1c
+    re = /^(.+?)y$/;
+    if (re.test(w)) {
+      var fp = re.exec(w);
+      stem = fp[1];
+      re = new RegExp(s_v);
+      if (re.test(stem))
+        w = stem + "i";
+    }
+
+    // Step 2
+    re = /^(.+?)(ational|tional|enci|anci|izer|bli|alli|entli|eli|ousli|ization|ation|ator|alism|iveness|fulness|ousness|aliti|iviti|biliti|logi)$/;
+    if (re.test(w)) {
+      var fp = re.exec(w);
+      stem = fp[1];
+      suffix = fp[2];
+      re = new RegExp(mgr0);
+      if (re.test(stem))
+        w = stem + step2list[suffix];
+    }
+
+    // Step 3
+    re = /^(.+?)(icate|ative|alize|iciti|ical|ful|ness)$/;
+    if (re.test(w)) {
+      var fp = re.exec(w);
+      stem = fp[1];
+      suffix = fp[2];
+      re = new RegExp(mgr0);
+      if (re.test(stem))
+        w = stem + step3list[suffix];
+    }
+
+    // Step 4
+    re = /^(.+?)(al|ance|ence|er|ic|able|ible|ant|ement|ment|ent|ou|ism|ate|iti|ous|ive|ize)$/;
+    re2 = /^(.+?)(s|t)(ion)$/;
+    if (re.test(w)) {
+      var fp = re.exec(w);
+      stem = fp[1];
+      re = new RegExp(mgr1);
+      if (re.test(stem))
+        w = stem;
+    }
+    else if (re2.test(w)) {
+      var fp = re2.exec(w);
+      stem = fp[1] + fp[2];
+      re2 = new RegExp(mgr1);
+      if (re2.test(stem))
+        w = stem;
+    }
+
+    // Step 5
+    re = /^(.+?)e$/;
+    if (re.test(w)) {
+      var fp = re.exec(w);
+      stem = fp[1];
+      re = new RegExp(mgr1);
+      re2 = new RegExp(meq1);
+      re3 = new RegExp("^" + C + v + "[^aeiouwxy]$");
+      if (re.test(stem) || (re2.test(stem) && !(re3.test(stem))))
+        w = stem;
+    }
+    re = /ll$/;
+    re2 = new RegExp(mgr1);
+    if (re.test(w) && re2.test(w)) {
+      re = /.$/;
+      w = w.replace(re,"");
+    }
+
+    // and turn initial Y back to y
+    if (firstch == "y")
+      w = firstch.toLowerCase() + w.substr(1);
+    return w;
+  }
+}
+
+
+/**
+ * Search Module
+ */
+var Search = {
+
+  _index : null,
+  _queued_query : null,
+  _pulse_status : -1,
+
+  init : function() {
+      var params = $.getQueryParameters();
+      if (params.q) {
+          var query = params.q[0];
+          $('input[name="q"]')[0].value = query;
+          this.performSearch(query);
+      }
+  },
+
+  loadIndex : function(url) {
+    $.ajax({type: "GET", url: url, data: null, success: null,
+            dataType: "script", cache: true});
+  },
+
+  setIndex : function(index) {
+    var q;
+    this._index = index;
+    if ((q = this._queued_query) !== null) {
+      this._queued_query = null;
+      Search.query(q);
+    }
+  },
+
+  hasIndex : function() {
+      return this._index !== null;
+  },
+
+  deferQuery : function(query) {
+      this._queued_query = query;
+  },
+
+  stopPulse : function() {
+      this._pulse_status = 0;
+  },
+
+  startPulse : function() {
+    if (this._pulse_status >= 0)
+        return;
+    function pulse() {
+      Search._pulse_status = (Search._pulse_status + 1) % 4;
+      var dotString = '';
+      for (var i = 0; i < Search._pulse_status; i++)
+        dotString += '.';
+      Search.dots.text(dotString);
+      if (Search._pulse_status > -1)
+        window.setTimeout(pulse, 500);
+    };
+    pulse();
+  },
+
+  /**
+   * perform a search for something
+   */
+  performSearch : function(query) {
+    // create the required interface elements
+    this.out = $('#search-results');
+    this.title = $('<h2>' + _('Searching') + '</h2>').appendTo(this.out);
+    this.dots = $('<span></span>').appendTo(this.title);
+    this.status = $('<p style="display: none"></p>').appendTo(this.out);
+    this.output = $('<ul class="search"/>').appendTo(this.out);
+
+    $('#search-progress').text(_('Preparing search...'));
+    this.startPulse();
+
+    // index already loaded, the browser was quick!
+    if (this.hasIndex())
+      this.query(query);
+    else
+      this.deferQuery(query);
+  },
+
+  query : function(query) {
+    var stopwords = ["and","then","into","it","as","are","in","if","for","no","there","their","was","is","be","to","that","but","they","not","such","with","by","a","on","these","of","will","this","near","the","or","at"];
+
+    // Stem the searchterms and add them to the correct list
+    var stemmer = new Stemmer();
+    var searchterms = [];
+    var excluded = [];
+    var hlterms = [];
+    var tmp = query.split(/\s+/);
+    var objectterms = [];
+    for (var i = 0; i < tmp.length; i++) {
+      if (tmp[i] != "") {
+          objectterms.push(tmp[i].toLowerCase());
+      }
+
+      if ($u.indexOf(stopwords, tmp[i]) != -1 || tmp[i].match(/^\d+$/) ||
+          tmp[i] == "") {
+        // skip this "word"
+        continue;
+      }
+      // stem the word
+      var word = stemmer.stemWord(tmp[i]).toLowerCase();
+      // select the correct list
+      if (word[0] == '-') {
+        var toAppend = excluded;
+        word = word.substr(1);
+      }
+      else {
+        var toAppend = searchterms;
+        hlterms.push(tmp[i].toLowerCase());
+      }
+      // only add if not already in the list
+      if (!$.contains(toAppend, word))
+        toAppend.push(word);
+    };
+    var highlightstring = '?highlight=' + $.urlencode(hlterms.join(" "));
+
+    // console.debug('SEARCH: searching for:');
+    // console.info('required: ', searchterms);
+    // console.info('excluded: ', excluded);
+
+    // prepare search
+    var filenames = this._index.filenames;
+    var titles = this._index.titles;
+    var terms = this._index.terms;
+    var fileMap = {};
+    var files = null;
+    // different result priorities
+    var importantResults = [];
+    var objectResults = [];
+    var regularResults = [];
+    var unimportantResults = [];
+    $('#search-progress').empty();
+
+    // lookup as object
+    for (var i = 0; i < objectterms.length; i++) {
+      var others = [].concat(objectterms.slice(0,i),
+                             objectterms.slice(i+1, objectterms.length))
+      var results = this.performObjectSearch(objectterms[i], others);
+      // Assume first word is most likely to be the object,
+      // other words more likely to be in description.
+      // Therefore put matches for earlier words first.
+      // (Results are eventually used in reverse order).
+      objectResults = results[0].concat(objectResults);
+      importantResults = results[1].concat(importantResults);
+      unimportantResults = results[2].concat(unimportantResults);
+    }
+
+    // perform the search on the required terms
+    for (var i = 0; i < searchterms.length; i++) {
+      var word = searchterms[i];
+      // no match but word was a required one
+      if ((files = terms[word]) == null)
+        break;
+      if (files.length == undefined) {
+        files = [files];
+      }
+      // create the mapping
+      for (var j = 0; j < files.length; j++) {
+        var file = files[j];
+        if (file in fileMap)
+          fileMap[file].push(word);
+        else
+          fileMap[file] = [word];
+      }
+    }
+
+    // now check if the files don't contain excluded terms
+    for (var file in fileMap) {
+      var valid = true;
+
+      // check if all requirements are matched
+      if (fileMap[file].length != searchterms.length)
+        continue;
+
+      // ensure that none of the excluded terms is in the
+      // search result.
+      for (var i = 0; i < excluded.length; i++) {
+        if (terms[excluded[i]] == file ||
+            $.contains(terms[excluded[i]] || [], file)) {
+          valid = false;
+          break;
+        }
+      }
+
+      // if we have still a valid result we can add it
+      // to the result list
+      if (valid)
+        regularResults.push([filenames[file], titles[file], '', null]);
+    }
+
+    // delete unused variables in order to not waste
+    // memory until list is retrieved completely
+    delete filenames, titles, terms;
+
+    // now sort the regular results descending by title
+    regularResults.sort(function(a, b) {
+      var left = a[1].toLowerCase();
+      var right = b[1].toLowerCase();
+      return (left > right) ? -1 : ((left < right) ? 1 : 0);
+    });
+
+    // combine all results
+    var results = unimportantResults.concat(regularResults)
+      .concat(objectResults).concat(importantResults);
+
+    // print the results
+    var resultCount = results.length;
+    function displayNextItem() {
+      // results left, load the summary and display it
+      if (results.length) {
+        var item = results.pop();
+        var listItem = $('<li style="display:none"></li>');
+        if (DOCUMENTATION_OPTIONS.FILE_SUFFIX == '') {
+          // dirhtml builder
+          var dirname = item[0] + '/';
+          if (dirname.match(/\/index\/$/)) {
+            dirname = dirname.substring(0, dirname.length-6);
+          } else if (dirname == 'index/') {
+            dirname = '';
+          }
+          listItem.append($('<a/>').attr('href',
+            DOCUMENTATION_OPTIONS.URL_ROOT + dirname +
+            highlightstring + item[2]).html(item[1]));
+        } else {
+          // normal html builders
+          listItem.append($('<a/>').attr('href',
+            item[0] + DOCUMENTATION_OPTIONS.FILE_SUFFIX +
+            highlightstring + item[2]).html(item[1]));
+        }
+        if (item[3]) {
+          listItem.append($('<span> (' + item[3] + ')</span>'));
+          Search.output.append(listItem);
+          listItem.slideDown(5, function() {
+            displayNextItem();
+          });
+        } else if (DOCUMENTATION_OPTIONS.HAS_SOURCE) {
+          $.get(DOCUMENTATION_OPTIONS.URL_ROOT + '_sources/' +
+                item[0] + '.txt', function(data) {
+            if (data != '') {
+              listItem.append($.makeSearchSummary(data, searchterms, hlterms));
+              Search.output.append(listItem);
+            }
+            listItem.slideDown(5, function() {
+              displayNextItem();
+            });
+          }, "text");
+        } else {
+          // no source available, just display title
+          Search.output.append(listItem);
+          listItem.slideDown(5, function() {
+            displayNextItem();
+          });
+        }
+      }
+      // search finished, update title and status message
+      else {
+        Search.stopPulse();
+        Search.title.text(_('Search Results'));
+        if (!resultCount)
+          Search.status.text(_('Your search did not match any documents. Please make sure that all words are spelled correctly and that you\'ve selected enough categories.'));
+        else
+            Search.status.text(_('Search finished, found %s page(s) matching the search query.').replace('%s', resultCount));
+        Search.status.fadeIn(500);
+      }
+    }
+    displayNextItem();
+  },
+
+  performObjectSearch : function(object, otherterms) {
+    var filenames = this._index.filenames;
+    var objects = this._index.objects;
+    var objnames = this._index.objnames;
+    var titles = this._index.titles;
+
+    var importantResults = [];
+    var objectResults = [];
+    var unimportantResults = [];
+
+    for (var prefix in objects) {
+      for (var name in objects[prefix]) {
+        var fullname = (prefix ? prefix + '.' : '') + name;
+        if (fullname.toLowerCase().indexOf(object) > -1) {
+          var match = objects[prefix][name];
+          var objname = objnames[match[1]][2];
+          var title = titles[match[0]];
+          // If more than one term searched for, we require other words to be
+          // found in the name/title/description
+          if (otherterms.length > 0) {
+            var haystack = (prefix + ' ' + name + ' ' +
+                            objname + ' ' + title).toLowerCase();
+            var allfound = true;
+            for (var i = 0; i < otherterms.length; i++) {
+              if (haystack.indexOf(otherterms[i]) == -1) {
+                allfound = false;
+                break;
+              }
+            }
+            if (!allfound) {
+              continue;
+            }
+          }
+          var descr = objname + _(', in ') + title;
+          anchor = match[3];
+          if (anchor == '')
+            anchor = fullname;
+          else if (anchor == '-')
+            anchor = objnames[match[1]][1] + '-' + fullname;
+          result = [filenames[match[0]], fullname, '#'+anchor, descr];
+          switch (match[2]) {
+          case 1: objectResults.push(result); break;
+          case 0: importantResults.push(result); break;
+          case 2: unimportantResults.push(result); break;
+          }
+        }
+      }
+    }
+
+    // sort results descending
+    objectResults.sort(function(a, b) {
+      return (a[1] > b[1]) ? -1 : ((a[1] < b[1]) ? 1 : 0);
+    });
+
+    importantResults.sort(function(a, b) {
+      return (a[1] > b[1]) ? -1 : ((a[1] < b[1]) ? 1 : 0);
+    });
+
+    unimportantResults.sort(function(a, b) {
+      return (a[1] > b[1]) ? -1 : ((a[1] < b[1]) ? 1 : 0);
+    });
+
+    return [importantResults, objectResults, unimportantResults]
+  }
+}
+
+$(document).ready(function() {
+  Search.init();
+});

+ 151 - 0
desktop/core/ext-py/django-auth-ldap-1.2.0/docs/archive/versions/1.0.19/_static/sidebar.js

@@ -0,0 +1,151 @@
+/*
+ * sidebar.js
+ * ~~~~~~~~~~
+ *
+ * This script makes the Sphinx sidebar collapsible.
+ *
+ * .sphinxsidebar contains .sphinxsidebarwrapper.  This script adds
+ * in .sphixsidebar, after .sphinxsidebarwrapper, the #sidebarbutton
+ * used to collapse and expand the sidebar.
+ *
+ * When the sidebar is collapsed the .sphinxsidebarwrapper is hidden
+ * and the width of the sidebar and the margin-left of the document
+ * are decreased. When the sidebar is expanded the opposite happens.
+ * This script saves a per-browser/per-session cookie used to
+ * remember the position of the sidebar among the pages.
+ * Once the browser is closed the cookie is deleted and the position
+ * reset to the default (expanded).
+ *
+ * :copyright: Copyright 2007-2011 by the Sphinx team, see AUTHORS.
+ * :license: BSD, see LICENSE for details.
+ *
+ */
+
+$(function() {
+  // global elements used by the functions.
+  // the 'sidebarbutton' element is defined as global after its
+  // creation, in the add_sidebar_button function
+  var bodywrapper = $('.bodywrapper');
+  var sidebar = $('.sphinxsidebar');
+  var sidebarwrapper = $('.sphinxsidebarwrapper');
+
+  // for some reason, the document has no sidebar; do not run into errors
+  if (!sidebar.length) return;
+
+  // original margin-left of the bodywrapper and width of the sidebar
+  // with the sidebar expanded
+  var bw_margin_expanded = bodywrapper.css('margin-left');
+  var ssb_width_expanded = sidebar.width();
+
+  // margin-left of the bodywrapper and width of the sidebar
+  // with the sidebar collapsed
+  var bw_margin_collapsed = '.8em';
+  var ssb_width_collapsed = '.8em';
+
+  // colors used by the current theme
+  var dark_color = $('.related').css('background-color');
+  var light_color = $('.document').css('background-color');
+
+  function sidebar_is_collapsed() {
+    return sidebarwrapper.is(':not(:visible)');
+  }
+
+  function toggle_sidebar() {
+    if (sidebar_is_collapsed())
+      expand_sidebar();
+    else
+      collapse_sidebar();
+  }
+
+  function collapse_sidebar() {
+    sidebarwrapper.hide();
+    sidebar.css('width', ssb_width_collapsed);
+    bodywrapper.css('margin-left', bw_margin_collapsed);
+    sidebarbutton.css({
+        'margin-left': '0',
+        'height': bodywrapper.height()
+    });
+    sidebarbutton.find('span').text('»');
+    sidebarbutton.attr('title', _('Expand sidebar'));
+    document.cookie = 'sidebar=collapsed';
+  }
+
+  function expand_sidebar() {
+    bodywrapper.css('margin-left', bw_margin_expanded);
+    sidebar.css('width', ssb_width_expanded);
+    sidebarwrapper.show();
+    sidebarbutton.css({
+        'margin-left': ssb_width_expanded-12,
+        'height': bodywrapper.height()
+    });
+    sidebarbutton.find('span').text('«');
+    sidebarbutton.attr('title', _('Collapse sidebar'));
+    document.cookie = 'sidebar=expanded';
+  }
+
+  function add_sidebar_button() {
+    sidebarwrapper.css({
+        'float': 'left',
+        'margin-right': '0',
+        'width': ssb_width_expanded - 28
+    });
+    // create the button
+    sidebar.append(
+        '<div id="sidebarbutton"><span>&laquo;</span></div>'
+    );
+    var sidebarbutton = $('#sidebarbutton');
+    light_color = sidebarbutton.css('background-color');
+    // find the height of the viewport to center the '<<' in the page
+    var viewport_height;
+    if (window.innerHeight)
+ 	  viewport_height = window.innerHeight;
+    else
+	  viewport_height = $(window).height();
+    sidebarbutton.find('span').css({
+        'display': 'block',
+        'margin-top': (viewport_height - sidebar.position().top - 20) / 2
+    });
+
+    sidebarbutton.click(toggle_sidebar);
+    sidebarbutton.attr('title', _('Collapse sidebar'));
+    sidebarbutton.css({
+        'color': '#FFFFFF',
+        'border-left': '1px solid ' + dark_color,
+        'font-size': '1.2em',
+        'cursor': 'pointer',
+        'height': bodywrapper.height(),
+        'padding-top': '1px',
+        'margin-left': ssb_width_expanded - 12
+    });
+
+    sidebarbutton.hover(
+      function () {
+          $(this).css('background-color', dark_color);
+      },
+      function () {
+          $(this).css('background-color', light_color);
+      }
+    );
+  }
+
+  function set_position_from_cookie() {
+    if (!document.cookie)
+      return;
+    var items = document.cookie.split(';');
+    for(var k=0; k<items.length; k++) {
+      var key_val = items[k].split('=');
+      var key = key_val[0];
+      if (key == 'sidebar') {
+        var value = key_val[1];
+        if ((value == 'collapsed') && (!sidebar_is_collapsed()))
+          collapse_sidebar();
+        else if ((value == 'expanded') && (sidebar_is_collapsed()))
+          expand_sidebar();
+      }
+    }
+  }
+
+  add_sidebar_button();
+  var sidebarbutton = $('#sidebarbutton');
+  set_position_from_cookie();
+});

+ 23 - 0
desktop/core/ext-py/django-auth-ldap-1.2.0/docs/archive/versions/1.0.19/_static/underscore.js

@@ -0,0 +1,23 @@
+// Underscore.js 0.5.5
+// (c) 2009 Jeremy Ashkenas, DocumentCloud Inc.
+// Underscore is freely distributable under the terms of the MIT license.
+// Portions of Underscore are inspired by or borrowed from Prototype.js,
+// Oliver Steele's Functional, and John Resig's Micro-Templating.
+// For all details and documentation:
+// http://documentcloud.github.com/underscore/
+(function(){var j=this,n=j._,i=function(a){this._wrapped=a},m=typeof StopIteration!=="undefined"?StopIteration:"__break__",b=j._=function(a){return new i(a)};if(typeof exports!=="undefined")exports._=b;var k=Array.prototype.slice,o=Array.prototype.unshift,p=Object.prototype.toString,q=Object.prototype.hasOwnProperty,r=Object.prototype.propertyIsEnumerable;b.VERSION="0.5.5";b.each=function(a,c,d){try{if(a.forEach)a.forEach(c,d);else if(b.isArray(a)||b.isArguments(a))for(var e=0,f=a.length;e<f;e++)c.call(d,
+a[e],e,a);else{var g=b.keys(a);f=g.length;for(e=0;e<f;e++)c.call(d,a[g[e]],g[e],a)}}catch(h){if(h!=m)throw h;}return a};b.map=function(a,c,d){if(a&&b.isFunction(a.map))return a.map(c,d);var e=[];b.each(a,function(f,g,h){e.push(c.call(d,f,g,h))});return e};b.reduce=function(a,c,d,e){if(a&&b.isFunction(a.reduce))return a.reduce(b.bind(d,e),c);b.each(a,function(f,g,h){c=d.call(e,c,f,g,h)});return c};b.reduceRight=function(a,c,d,e){if(a&&b.isFunction(a.reduceRight))return a.reduceRight(b.bind(d,e),c);
+var f=b.clone(b.toArray(a)).reverse();b.each(f,function(g,h){c=d.call(e,c,g,h,a)});return c};b.detect=function(a,c,d){var e;b.each(a,function(f,g,h){if(c.call(d,f,g,h)){e=f;b.breakLoop()}});return e};b.select=function(a,c,d){if(a&&b.isFunction(a.filter))return a.filter(c,d);var e=[];b.each(a,function(f,g,h){c.call(d,f,g,h)&&e.push(f)});return e};b.reject=function(a,c,d){var e=[];b.each(a,function(f,g,h){!c.call(d,f,g,h)&&e.push(f)});return e};b.all=function(a,c,d){c=c||b.identity;if(a&&b.isFunction(a.every))return a.every(c,
+d);var e=true;b.each(a,function(f,g,h){(e=e&&c.call(d,f,g,h))||b.breakLoop()});return e};b.any=function(a,c,d){c=c||b.identity;if(a&&b.isFunction(a.some))return a.some(c,d);var e=false;b.each(a,function(f,g,h){if(e=c.call(d,f,g,h))b.breakLoop()});return e};b.include=function(a,c){if(b.isArray(a))return b.indexOf(a,c)!=-1;var d=false;b.each(a,function(e){if(d=e===c)b.breakLoop()});return d};b.invoke=function(a,c){var d=b.rest(arguments,2);return b.map(a,function(e){return(c?e[c]:e).apply(e,d)})};b.pluck=
+function(a,c){return b.map(a,function(d){return d[c]})};b.max=function(a,c,d){if(!c&&b.isArray(a))return Math.max.apply(Math,a);var e={computed:-Infinity};b.each(a,function(f,g,h){g=c?c.call(d,f,g,h):f;g>=e.computed&&(e={value:f,computed:g})});return e.value};b.min=function(a,c,d){if(!c&&b.isArray(a))return Math.min.apply(Math,a);var e={computed:Infinity};b.each(a,function(f,g,h){g=c?c.call(d,f,g,h):f;g<e.computed&&(e={value:f,computed:g})});return e.value};b.sortBy=function(a,c,d){return b.pluck(b.map(a,
+function(e,f,g){return{value:e,criteria:c.call(d,e,f,g)}}).sort(function(e,f){e=e.criteria;f=f.criteria;return e<f?-1:e>f?1:0}),"value")};b.sortedIndex=function(a,c,d){d=d||b.identity;for(var e=0,f=a.length;e<f;){var g=e+f>>1;d(a[g])<d(c)?(e=g+1):(f=g)}return e};b.toArray=function(a){if(!a)return[];if(a.toArray)return a.toArray();if(b.isArray(a))return a;if(b.isArguments(a))return k.call(a);return b.values(a)};b.size=function(a){return b.toArray(a).length};b.first=function(a,c,d){return c&&!d?k.call(a,
+0,c):a[0]};b.rest=function(a,c,d){return k.call(a,b.isUndefined(c)||d?1:c)};b.last=function(a){return a[a.length-1]};b.compact=function(a){return b.select(a,function(c){return!!c})};b.flatten=function(a){return b.reduce(a,[],function(c,d){if(b.isArray(d))return c.concat(b.flatten(d));c.push(d);return c})};b.without=function(a){var c=b.rest(arguments);return b.select(a,function(d){return!b.include(c,d)})};b.uniq=function(a,c){return b.reduce(a,[],function(d,e,f){if(0==f||(c===true?b.last(d)!=e:!b.include(d,
+e)))d.push(e);return d})};b.intersect=function(a){var c=b.rest(arguments);return b.select(b.uniq(a),function(d){return b.all(c,function(e){return b.indexOf(e,d)>=0})})};b.zip=function(){for(var a=b.toArray(arguments),c=b.max(b.pluck(a,"length")),d=new Array(c),e=0;e<c;e++)d[e]=b.pluck(a,String(e));return d};b.indexOf=function(a,c){if(a.indexOf)return a.indexOf(c);for(var d=0,e=a.length;d<e;d++)if(a[d]===c)return d;return-1};b.lastIndexOf=function(a,c){if(a.lastIndexOf)return a.lastIndexOf(c);for(var d=
+a.length;d--;)if(a[d]===c)return d;return-1};b.range=function(a,c,d){var e=b.toArray(arguments),f=e.length<=1;a=f?0:e[0];c=f?e[0]:e[1];d=e[2]||1;e=Math.ceil((c-a)/d);if(e<=0)return[];e=new Array(e);f=a;for(var g=0;1;f+=d){if((d>0?f-c:c-f)>=0)return e;e[g++]=f}};b.bind=function(a,c){var d=b.rest(arguments,2);return function(){return a.apply(c||j,d.concat(b.toArray(arguments)))}};b.bindAll=function(a){var c=b.rest(arguments);if(c.length==0)c=b.functions(a);b.each(c,function(d){a[d]=b.bind(a[d],a)});
+return a};b.delay=function(a,c){var d=b.rest(arguments,2);return setTimeout(function(){return a.apply(a,d)},c)};b.defer=function(a){return b.delay.apply(b,[a,1].concat(b.rest(arguments)))};b.wrap=function(a,c){return function(){var d=[a].concat(b.toArray(arguments));return c.apply(c,d)}};b.compose=function(){var a=b.toArray(arguments);return function(){for(var c=b.toArray(arguments),d=a.length-1;d>=0;d--)c=[a[d].apply(this,c)];return c[0]}};b.keys=function(a){if(b.isArray(a))return b.range(0,a.length);
+var c=[];for(var d in a)q.call(a,d)&&c.push(d);return c};b.values=function(a){return b.map(a,b.identity)};b.functions=function(a){return b.select(b.keys(a),function(c){return b.isFunction(a[c])}).sort()};b.extend=function(a,c){for(var d in c)a[d]=c[d];return a};b.clone=function(a){if(b.isArray(a))return a.slice(0);return b.extend({},a)};b.tap=function(a,c){c(a);return a};b.isEqual=function(a,c){if(a===c)return true;var d=typeof a;if(d!=typeof c)return false;if(a==c)return true;if(!a&&c||a&&!c)return false;
+if(a.isEqual)return a.isEqual(c);if(b.isDate(a)&&b.isDate(c))return a.getTime()===c.getTime();if(b.isNaN(a)&&b.isNaN(c))return true;if(b.isRegExp(a)&&b.isRegExp(c))return a.source===c.source&&a.global===c.global&&a.ignoreCase===c.ignoreCase&&a.multiline===c.multiline;if(d!=="object")return false;if(a.length&&a.length!==c.length)return false;d=b.keys(a);var e=b.keys(c);if(d.length!=e.length)return false;for(var f in a)if(!b.isEqual(a[f],c[f]))return false;return true};b.isEmpty=function(a){return b.keys(a).length==
+0};b.isElement=function(a){return!!(a&&a.nodeType==1)};b.isArray=function(a){return!!(a&&a.concat&&a.unshift)};b.isArguments=function(a){return a&&b.isNumber(a.length)&&!b.isArray(a)&&!r.call(a,"length")};b.isFunction=function(a){return!!(a&&a.constructor&&a.call&&a.apply)};b.isString=function(a){return!!(a===""||a&&a.charCodeAt&&a.substr)};b.isNumber=function(a){return p.call(a)==="[object Number]"};b.isDate=function(a){return!!(a&&a.getTimezoneOffset&&a.setUTCFullYear)};b.isRegExp=function(a){return!!(a&&
+a.test&&a.exec&&(a.ignoreCase||a.ignoreCase===false))};b.isNaN=function(a){return b.isNumber(a)&&isNaN(a)};b.isNull=function(a){return a===null};b.isUndefined=function(a){return typeof a=="undefined"};b.noConflict=function(){j._=n;return this};b.identity=function(a){return a};b.breakLoop=function(){throw m;};var s=0;b.uniqueId=function(a){var c=s++;return a?a+c:c};b.template=function(a,c){a=new Function("obj","var p=[],print=function(){p.push.apply(p,arguments);};with(obj){p.push('"+a.replace(/[\r\t\n]/g,
+" ").replace(/'(?=[^%]*%>)/g,"\t").split("'").join("\\'").split("\t").join("'").replace(/<%=(.+?)%>/g,"',$1,'").split("<%").join("');").split("%>").join("p.push('")+"');}return p.join('');");return c?a(c):a};b.forEach=b.each;b.foldl=b.inject=b.reduce;b.foldr=b.reduceRight;b.filter=b.select;b.every=b.all;b.some=b.any;b.head=b.first;b.tail=b.rest;b.methods=b.functions;var l=function(a,c){return c?b(a).chain():a};b.each(b.functions(b),function(a){var c=b[a];i.prototype[a]=function(){var d=b.toArray(arguments);
+o.call(d,this._wrapped);return l(c.apply(b,d),this._chain)}});b.each(["pop","push","reverse","shift","sort","splice","unshift"],function(a){var c=Array.prototype[a];i.prototype[a]=function(){c.apply(this._wrapped,arguments);return l(this._wrapped,this._chain)}});b.each(["concat","join","slice"],function(a){var c=Array.prototype[a];i.prototype[a]=function(){return l(c.apply(this._wrapped,arguments),this._chain)}});i.prototype.chain=function(){this._chain=true;return this};i.prototype.value=function(){return this._wrapped}})();

BIN
desktop/core/ext-py/django-auth-ldap-1.2.0/docs/archive/versions/1.0.19/_static/up-pressed.png


BIN
desktop/core/ext-py/django-auth-ldap-1.2.0/docs/archive/versions/1.0.19/_static/up.png


+ 808 - 0
desktop/core/ext-py/django-auth-ldap-1.2.0/docs/archive/versions/1.0.19/_static/websupport.js

@@ -0,0 +1,808 @@
+/*
+ * websupport.js
+ * ~~~~~~~~~~~~~
+ *
+ * sphinx.websupport utilties for all documentation.
+ *
+ * :copyright: Copyright 2007-2011 by the Sphinx team, see AUTHORS.
+ * :license: BSD, see LICENSE for details.
+ *
+ */
+
+(function($) {
+  $.fn.autogrow = function() {
+    return this.each(function() {
+    var textarea = this;
+
+    $.fn.autogrow.resize(textarea);
+
+    $(textarea)
+      .focus(function() {
+        textarea.interval = setInterval(function() {
+          $.fn.autogrow.resize(textarea);
+        }, 500);
+      })
+      .blur(function() {
+        clearInterval(textarea.interval);
+      });
+    });
+  };
+
+  $.fn.autogrow.resize = function(textarea) {
+    var lineHeight = parseInt($(textarea).css('line-height'), 10);
+    var lines = textarea.value.split('\n');
+    var columns = textarea.cols;
+    var lineCount = 0;
+    $.each(lines, function() {
+      lineCount += Math.ceil(this.length / columns) || 1;
+    });
+    var height = lineHeight * (lineCount + 1);
+    $(textarea).css('height', height);
+  };
+})(jQuery);
+
+(function($) {
+  var comp, by;
+
+  function init() {
+    initEvents();
+    initComparator();
+  }
+
+  function initEvents() {
+    $('a.comment-close').live("click", function(event) {
+      event.preventDefault();
+      hide($(this).attr('id').substring(2));
+    });
+    $('a.vote').live("click", function(event) {
+      event.preventDefault();
+      handleVote($(this));
+    });
+    $('a.reply').live("click", function(event) {
+      event.preventDefault();
+      openReply($(this).attr('id').substring(2));
+    });
+    $('a.close-reply').live("click", function(event) {
+      event.preventDefault();
+      closeReply($(this).attr('id').substring(2));
+    });
+    $('a.sort-option').live("click", function(event) {
+      event.preventDefault();
+      handleReSort($(this));
+    });
+    $('a.show-proposal').live("click", function(event) {
+      event.preventDefault();
+      showProposal($(this).attr('id').substring(2));
+    });
+    $('a.hide-proposal').live("click", function(event) {
+      event.preventDefault();
+      hideProposal($(this).attr('id').substring(2));
+    });
+    $('a.show-propose-change').live("click", function(event) {
+      event.preventDefault();
+      showProposeChange($(this).attr('id').substring(2));
+    });
+    $('a.hide-propose-change').live("click", function(event) {
+      event.preventDefault();
+      hideProposeChange($(this).attr('id').substring(2));
+    });
+    $('a.accept-comment').live("click", function(event) {
+      event.preventDefault();
+      acceptComment($(this).attr('id').substring(2));
+    });
+    $('a.delete-comment').live("click", function(event) {
+      event.preventDefault();
+      deleteComment($(this).attr('id').substring(2));
+    });
+    $('a.comment-markup').live("click", function(event) {
+      event.preventDefault();
+      toggleCommentMarkupBox($(this).attr('id').substring(2));
+    });
+  }
+
+  /**
+   * Set comp, which is a comparator function used for sorting and
+   * inserting comments into the list.
+   */
+  function setComparator() {
+    // If the first three letters are "asc", sort in ascending order
+    // and remove the prefix.
+    if (by.substring(0,3) == 'asc') {
+      var i = by.substring(3);
+      comp = function(a, b) { return a[i] - b[i]; };
+    } else {
+      // Otherwise sort in descending order.
+      comp = function(a, b) { return b[by] - a[by]; };
+    }
+
+    // Reset link styles and format the selected sort option.
+    $('a.sel').attr('href', '#').removeClass('sel');
+    $('a.by' + by).removeAttr('href').addClass('sel');
+  }
+
+  /**
+   * Create a comp function. If the user has preferences stored in
+   * the sortBy cookie, use those, otherwise use the default.
+   */
+  function initComparator() {
+    by = 'rating'; // Default to sort by rating.
+    // If the sortBy cookie is set, use that instead.
+    if (document.cookie.length > 0) {
+      var start = document.cookie.indexOf('sortBy=');
+      if (start != -1) {
+        start = start + 7;
+        var end = document.cookie.indexOf(";", start);
+        if (end == -1) {
+          end = document.cookie.length;
+          by = unescape(document.cookie.substring(start, end));
+        }
+      }
+    }
+    setComparator();
+  }
+
+  /**
+   * Show a comment div.
+   */
+  function show(id) {
+    $('#ao' + id).hide();
+    $('#ah' + id).show();
+    var context = $.extend({id: id}, opts);
+    var popup = $(renderTemplate(popupTemplate, context)).hide();
+    popup.find('textarea[name="proposal"]').hide();
+    popup.find('a.by' + by).addClass('sel');
+    var form = popup.find('#cf' + id);
+    form.submit(function(event) {
+      event.preventDefault();
+      addComment(form);
+    });
+    $('#s' + id).after(popup);
+    popup.slideDown('fast', function() {
+      getComments(id);
+    });
+  }
+
+  /**
+   * Hide a comment div.
+   */
+  function hide(id) {
+    $('#ah' + id).hide();
+    $('#ao' + id).show();
+    var div = $('#sc' + id);
+    div.slideUp('fast', function() {
+      div.remove();
+    });
+  }
+
+  /**
+   * Perform an ajax request to get comments for a node
+   * and insert the comments into the comments tree.
+   */
+  function getComments(id) {
+    $.ajax({
+     type: 'GET',
+     url: opts.getCommentsURL,
+     data: {node: id},
+     success: function(data, textStatus, request) {
+       var ul = $('#cl' + id);
+       var speed = 100;
+       $('#cf' + id)
+         .find('textarea[name="proposal"]')
+         .data('source', data.source);
+
+       if (data.comments.length === 0) {
+         ul.html('<li>No comments yet.</li>');
+         ul.data('empty', true);
+       } else {
+         // If there are comments, sort them and put them in the list.
+         var comments = sortComments(data.comments);
+         speed = data.comments.length * 100;
+         appendComments(comments, ul);
+         ul.data('empty', false);
+       }
+       $('#cn' + id).slideUp(speed + 200);
+       ul.slideDown(speed);
+     },
+     error: function(request, textStatus, error) {
+       showError('Oops, there was a problem retrieving the comments.');
+     },
+     dataType: 'json'
+    });
+  }
+
+  /**
+   * Add a comment via ajax and insert the comment into the comment tree.
+   */
+  function addComment(form) {
+    var node_id = form.find('input[name="node"]').val();
+    var parent_id = form.find('input[name="parent"]').val();
+    var text = form.find('textarea[name="comment"]').val();
+    var proposal = form.find('textarea[name="proposal"]').val();
+
+    if (text == '') {
+      showError('Please enter a comment.');
+      return;
+    }
+
+    // Disable the form that is being submitted.
+    form.find('textarea,input').attr('disabled', 'disabled');
+
+    // Send the comment to the server.
+    $.ajax({
+      type: "POST",
+      url: opts.addCommentURL,
+      dataType: 'json',
+      data: {
+        node: node_id,
+        parent: parent_id,
+        text: text,
+        proposal: proposal
+      },
+      success: function(data, textStatus, error) {
+        // Reset the form.
+        if (node_id) {
+          hideProposeChange(node_id);
+        }
+        form.find('textarea')
+          .val('')
+          .add(form.find('input'))
+          .removeAttr('disabled');
+	var ul = $('#cl' + (node_id || parent_id));
+        if (ul.data('empty')) {
+          $(ul).empty();
+          ul.data('empty', false);
+        }
+        insertComment(data.comment);
+        var ao = $('#ao' + node_id);
+        ao.find('img').attr({'src': opts.commentBrightImage});
+        if (node_id) {
+          // if this was a "root" comment, remove the commenting box
+          // (the user can get it back by reopening the comment popup)
+          $('#ca' + node_id).slideUp();
+        }
+      },
+      error: function(request, textStatus, error) {
+        form.find('textarea,input').removeAttr('disabled');
+        showError('Oops, there was a problem adding the comment.');
+      }
+    });
+  }
+
+  /**
+   * Recursively append comments to the main comment list and children
+   * lists, creating the comment tree.
+   */
+  function appendComments(comments, ul) {
+    $.each(comments, function() {
+      var div = createCommentDiv(this);
+      ul.append($(document.createElement('li')).html(div));
+      appendComments(this.children, div.find('ul.comment-children'));
+      // To avoid stagnating data, don't store the comments children in data.
+      this.children = null;
+      div.data('comment', this);
+    });
+  }
+
+  /**
+   * After adding a new comment, it must be inserted in the correct
+   * location in the comment tree.
+   */
+  function insertComment(comment) {
+    var div = createCommentDiv(comment);
+
+    // To avoid stagnating data, don't store the comments children in data.
+    comment.children = null;
+    div.data('comment', comment);
+
+    var ul = $('#cl' + (comment.node || comment.parent));
+    var siblings = getChildren(ul);
+
+    var li = $(document.createElement('li'));
+    li.hide();
+
+    // Determine where in the parents children list to insert this comment.
+    for(i=0; i < siblings.length; i++) {
+      if (comp(comment, siblings[i]) <= 0) {
+        $('#cd' + siblings[i].id)
+          .parent()
+          .before(li.html(div));
+        li.slideDown('fast');
+        return;
+      }
+    }
+
+    // If we get here, this comment rates lower than all the others,
+    // or it is the only comment in the list.
+    ul.append(li.html(div));
+    li.slideDown('fast');
+  }
+
+  function acceptComment(id) {
+    $.ajax({
+      type: 'POST',
+      url: opts.acceptCommentURL,
+      data: {id: id},
+      success: function(data, textStatus, request) {
+        $('#cm' + id).fadeOut('fast');
+        $('#cd' + id).removeClass('moderate');
+      },
+      error: function(request, textStatus, error) {
+        showError('Oops, there was a problem accepting the comment.');
+      }
+    });
+  }
+
+  function deleteComment(id) {
+    $.ajax({
+      type: 'POST',
+      url: opts.deleteCommentURL,
+      data: {id: id},
+      success: function(data, textStatus, request) {
+        var div = $('#cd' + id);
+        if (data == 'delete') {
+          // Moderator mode: remove the comment and all children immediately
+          div.slideUp('fast', function() {
+            div.remove();
+          });
+          return;
+        }
+        // User mode: only mark the comment as deleted
+        div
+          .find('span.user-id:first')
+          .text('[deleted]').end()
+          .find('div.comment-text:first')
+          .text('[deleted]').end()
+          .find('#cm' + id + ', #dc' + id + ', #ac' + id + ', #rc' + id +
+                ', #sp' + id + ', #hp' + id + ', #cr' + id + ', #rl' + id)
+          .remove();
+        var comment = div.data('comment');
+        comment.username = '[deleted]';
+        comment.text = '[deleted]';
+        div.data('comment', comment);
+      },
+      error: function(request, textStatus, error) {
+        showError('Oops, there was a problem deleting the comment.');
+      }
+    });
+  }
+
+  function showProposal(id) {
+    $('#sp' + id).hide();
+    $('#hp' + id).show();
+    $('#pr' + id).slideDown('fast');
+  }
+
+  function hideProposal(id) {
+    $('#hp' + id).hide();
+    $('#sp' + id).show();
+    $('#pr' + id).slideUp('fast');
+  }
+
+  function showProposeChange(id) {
+    $('#pc' + id).hide();
+    $('#hc' + id).show();
+    var textarea = $('#pt' + id);
+    textarea.val(textarea.data('source'));
+    $.fn.autogrow.resize(textarea[0]);
+    textarea.slideDown('fast');
+  }
+
+  function hideProposeChange(id) {
+    $('#hc' + id).hide();
+    $('#pc' + id).show();
+    var textarea = $('#pt' + id);
+    textarea.val('').removeAttr('disabled');
+    textarea.slideUp('fast');
+  }
+
+  function toggleCommentMarkupBox(id) {
+    $('#mb' + id).toggle();
+  }
+
+  /** Handle when the user clicks on a sort by link. */
+  function handleReSort(link) {
+    var classes = link.attr('class').split(/\s+/);
+    for (var i=0; i<classes.length; i++) {
+      if (classes[i] != 'sort-option') {
+	by = classes[i].substring(2);
+      }
+    }
+    setComparator();
+    // Save/update the sortBy cookie.
+    var expiration = new Date();
+    expiration.setDate(expiration.getDate() + 365);
+    document.cookie= 'sortBy=' + escape(by) +
+                     ';expires=' + expiration.toUTCString();
+    $('ul.comment-ul').each(function(index, ul) {
+      var comments = getChildren($(ul), true);
+      comments = sortComments(comments);
+      appendComments(comments, $(ul).empty());
+    });
+  }
+
+  /**
+   * Function to process a vote when a user clicks an arrow.
+   */
+  function handleVote(link) {
+    if (!opts.voting) {
+      showError("You'll need to login to vote.");
+      return;
+    }
+
+    var id = link.attr('id');
+    if (!id) {
+      // Didn't click on one of the voting arrows.
+      return;
+    }
+    // If it is an unvote, the new vote value is 0,
+    // Otherwise it's 1 for an upvote, or -1 for a downvote.
+    var value = 0;
+    if (id.charAt(1) != 'u') {
+      value = id.charAt(0) == 'u' ? 1 : -1;
+    }
+    // The data to be sent to the server.
+    var d = {
+      comment_id: id.substring(2),
+      value: value
+    };
+
+    // Swap the vote and unvote links.
+    link.hide();
+    $('#' + id.charAt(0) + (id.charAt(1) == 'u' ? 'v' : 'u') + d.comment_id)
+      .show();
+
+    // The div the comment is displayed in.
+    var div = $('div#cd' + d.comment_id);
+    var data = div.data('comment');
+
+    // If this is not an unvote, and the other vote arrow has
+    // already been pressed, unpress it.
+    if ((d.value !== 0) && (data.vote === d.value * -1)) {
+      $('#' + (d.value == 1 ? 'd' : 'u') + 'u' + d.comment_id).hide();
+      $('#' + (d.value == 1 ? 'd' : 'u') + 'v' + d.comment_id).show();
+    }
+
+    // Update the comments rating in the local data.
+    data.rating += (data.vote === 0) ? d.value : (d.value - data.vote);
+    data.vote = d.value;
+    div.data('comment', data);
+
+    // Change the rating text.
+    div.find('.rating:first')
+      .text(data.rating + ' point' + (data.rating == 1 ? '' : 's'));
+
+    // Send the vote information to the server.
+    $.ajax({
+      type: "POST",
+      url: opts.processVoteURL,
+      data: d,
+      error: function(request, textStatus, error) {
+        showError('Oops, there was a problem casting that vote.');
+      }
+    });
+  }
+
+  /**
+   * Open a reply form used to reply to an existing comment.
+   */
+  function openReply(id) {
+    // Swap out the reply link for the hide link
+    $('#rl' + id).hide();
+    $('#cr' + id).show();
+
+    // Add the reply li to the children ul.
+    var div = $(renderTemplate(replyTemplate, {id: id})).hide();
+    $('#cl' + id)
+      .prepend(div)
+      // Setup the submit handler for the reply form.
+      .find('#rf' + id)
+      .submit(function(event) {
+        event.preventDefault();
+        addComment($('#rf' + id));
+        closeReply(id);
+      })
+      .find('input[type=button]')
+      .click(function() {
+        closeReply(id);
+      });
+    div.slideDown('fast', function() {
+      $('#rf' + id).find('textarea').focus();
+    });
+  }
+
+  /**
+   * Close the reply form opened with openReply.
+   */
+  function closeReply(id) {
+    // Remove the reply div from the DOM.
+    $('#rd' + id).slideUp('fast', function() {
+      $(this).remove();
+    });
+
+    // Swap out the hide link for the reply link
+    $('#cr' + id).hide();
+    $('#rl' + id).show();
+  }
+
+  /**
+   * Recursively sort a tree of comments using the comp comparator.
+   */
+  function sortComments(comments) {
+    comments.sort(comp);
+    $.each(comments, function() {
+      this.children = sortComments(this.children);
+    });
+    return comments;
+  }
+
+  /**
+   * Get the children comments from a ul. If recursive is true,
+   * recursively include childrens' children.
+   */
+  function getChildren(ul, recursive) {
+    var children = [];
+    ul.children().children("[id^='cd']")
+      .each(function() {
+        var comment = $(this).data('comment');
+        if (recursive)
+          comment.children = getChildren($(this).find('#cl' + comment.id), true);
+        children.push(comment);
+      });
+    return children;
+  }
+
+  /** Create a div to display a comment in. */
+  function createCommentDiv(comment) {
+    if (!comment.displayed && !opts.moderator) {
+      return $('<div class="moderate">Thank you!  Your comment will show up '
+               + 'once it is has been approved by a moderator.</div>');
+    }
+    // Prettify the comment rating.
+    comment.pretty_rating = comment.rating + ' point' +
+      (comment.rating == 1 ? '' : 's');
+    // Make a class (for displaying not yet moderated comments differently)
+    comment.css_class = comment.displayed ? '' : ' moderate';
+    // Create a div for this comment.
+    var context = $.extend({}, opts, comment);
+    var div = $(renderTemplate(commentTemplate, context));
+
+    // If the user has voted on this comment, highlight the correct arrow.
+    if (comment.vote) {
+      var direction = (comment.vote == 1) ? 'u' : 'd';
+      div.find('#' + direction + 'v' + comment.id).hide();
+      div.find('#' + direction + 'u' + comment.id).show();
+    }
+
+    if (opts.moderator || comment.text != '[deleted]') {
+      div.find('a.reply').show();
+      if (comment.proposal_diff)
+        div.find('#sp' + comment.id).show();
+      if (opts.moderator && !comment.displayed)
+        div.find('#cm' + comment.id).show();
+      if (opts.moderator || (opts.username == comment.username))
+        div.find('#dc' + comment.id).show();
+    }
+    return div;
+  }
+
+  /**
+   * A simple template renderer. Placeholders such as <%id%> are replaced
+   * by context['id'] with items being escaped. Placeholders such as <#id#>
+   * are not escaped.
+   */
+  function renderTemplate(template, context) {
+    var esc = $(document.createElement('div'));
+
+    function handle(ph, escape) {
+      var cur = context;
+      $.each(ph.split('.'), function() {
+        cur = cur[this];
+      });
+      return escape ? esc.text(cur || "").html() : cur;
+    }
+
+    return template.replace(/<([%#])([\w\.]*)\1>/g, function() {
+      return handle(arguments[2], arguments[1] == '%' ? true : false);
+    });
+  }
+
+  /** Flash an error message briefly. */
+  function showError(message) {
+    $(document.createElement('div')).attr({'class': 'popup-error'})
+      .append($(document.createElement('div'))
+               .attr({'class': 'error-message'}).text(message))
+      .appendTo('body')
+      .fadeIn("slow")
+      .delay(2000)
+      .fadeOut("slow");
+  }
+
+  /** Add a link the user uses to open the comments popup. */
+  $.fn.comment = function() {
+    return this.each(function() {
+      var id = $(this).attr('id').substring(1);
+      var count = COMMENT_METADATA[id];
+      var title = count + ' comment' + (count == 1 ? '' : 's');
+      var image = count > 0 ? opts.commentBrightImage : opts.commentImage;
+      var addcls = count == 0 ? ' nocomment' : '';
+      $(this)
+        .append(
+          $(document.createElement('a')).attr({
+            href: '#',
+            'class': 'sphinx-comment-open' + addcls,
+            id: 'ao' + id
+          })
+            .append($(document.createElement('img')).attr({
+              src: image,
+              alt: 'comment',
+              title: title
+            }))
+            .click(function(event) {
+              event.preventDefault();
+              show($(this).attr('id').substring(2));
+            })
+        )
+        .append(
+          $(document.createElement('a')).attr({
+            href: '#',
+            'class': 'sphinx-comment-close hidden',
+            id: 'ah' + id
+          })
+            .append($(document.createElement('img')).attr({
+              src: opts.closeCommentImage,
+              alt: 'close',
+              title: 'close'
+            }))
+            .click(function(event) {
+              event.preventDefault();
+              hide($(this).attr('id').substring(2));
+            })
+        );
+    });
+  };
+
+  var opts = {
+    processVoteURL: '/_process_vote',
+    addCommentURL: '/_add_comment',
+    getCommentsURL: '/_get_comments',
+    acceptCommentURL: '/_accept_comment',
+    deleteCommentURL: '/_delete_comment',
+    commentImage: '/static/_static/comment.png',
+    closeCommentImage: '/static/_static/comment-close.png',
+    loadingImage: '/static/_static/ajax-loader.gif',
+    commentBrightImage: '/static/_static/comment-bright.png',
+    upArrow: '/static/_static/up.png',
+    downArrow: '/static/_static/down.png',
+    upArrowPressed: '/static/_static/up-pressed.png',
+    downArrowPressed: '/static/_static/down-pressed.png',
+    voting: false,
+    moderator: false
+  };
+
+  if (typeof COMMENT_OPTIONS != "undefined") {
+    opts = jQuery.extend(opts, COMMENT_OPTIONS);
+  }
+
+  var popupTemplate = '\
+    <div class="sphinx-comments" id="sc<%id%>">\
+      <p class="sort-options">\
+        Sort by:\
+        <a href="#" class="sort-option byrating">best rated</a>\
+        <a href="#" class="sort-option byascage">newest</a>\
+        <a href="#" class="sort-option byage">oldest</a>\
+      </p>\
+      <div class="comment-header">Comments</div>\
+      <div class="comment-loading" id="cn<%id%>">\
+        loading comments... <img src="<%loadingImage%>" alt="" /></div>\
+      <ul id="cl<%id%>" class="comment-ul"></ul>\
+      <div id="ca<%id%>">\
+      <p class="add-a-comment">Add a comment\
+        (<a href="#" class="comment-markup" id="ab<%id%>">markup</a>):</p>\
+      <div class="comment-markup-box" id="mb<%id%>">\
+        reStructured text markup: <i>*emph*</i>, <b>**strong**</b>, \
+        <tt>``code``</tt>, \
+        code blocks: <tt>::</tt> and an indented block after blank line</div>\
+      <form method="post" id="cf<%id%>" class="comment-form" action="">\
+        <textarea name="comment" cols="80"></textarea>\
+        <p class="propose-button">\
+          <a href="#" id="pc<%id%>" class="show-propose-change">\
+            Propose a change &#9657;\
+          </a>\
+          <a href="#" id="hc<%id%>" class="hide-propose-change">\
+            Propose a change &#9663;\
+          </a>\
+        </p>\
+        <textarea name="proposal" id="pt<%id%>" cols="80"\
+                  spellcheck="false"></textarea>\
+        <input type="submit" value="Add comment" />\
+        <input type="hidden" name="node" value="<%id%>" />\
+        <input type="hidden" name="parent" value="" />\
+      </form>\
+      </div>\
+    </div>';
+
+  var commentTemplate = '\
+    <div id="cd<%id%>" class="sphinx-comment<%css_class%>">\
+      <div class="vote">\
+        <div class="arrow">\
+          <a href="#" id="uv<%id%>" class="vote" title="vote up">\
+            <img src="<%upArrow%>" />\
+          </a>\
+          <a href="#" id="uu<%id%>" class="un vote" title="vote up">\
+            <img src="<%upArrowPressed%>" />\
+          </a>\
+        </div>\
+        <div class="arrow">\
+          <a href="#" id="dv<%id%>" class="vote" title="vote down">\
+            <img src="<%downArrow%>" id="da<%id%>" />\
+          </a>\
+          <a href="#" id="du<%id%>" class="un vote" title="vote down">\
+            <img src="<%downArrowPressed%>" />\
+          </a>\
+        </div>\
+      </div>\
+      <div class="comment-content">\
+        <p class="tagline comment">\
+          <span class="user-id"><%username%></span>\
+          <span class="rating"><%pretty_rating%></span>\
+          <span class="delta"><%time.delta%></span>\
+        </p>\
+        <div class="comment-text comment"><#text#></div>\
+        <p class="comment-opts comment">\
+          <a href="#" class="reply hidden" id="rl<%id%>">reply &#9657;</a>\
+          <a href="#" class="close-reply" id="cr<%id%>">reply &#9663;</a>\
+          <a href="#" id="sp<%id%>" class="show-proposal">proposal &#9657;</a>\
+          <a href="#" id="hp<%id%>" class="hide-proposal">proposal &#9663;</a>\
+          <a href="#" id="dc<%id%>" class="delete-comment hidden">delete</a>\
+          <span id="cm<%id%>" class="moderation hidden">\
+            <a href="#" id="ac<%id%>" class="accept-comment">accept</a>\
+          </span>\
+        </p>\
+        <pre class="proposal" id="pr<%id%>">\
+<#proposal_diff#>\
+        </pre>\
+          <ul class="comment-children" id="cl<%id%>"></ul>\
+        </div>\
+        <div class="clearleft"></div>\
+      </div>\
+    </div>';
+
+  var replyTemplate = '\
+    <li>\
+      <div class="reply-div" id="rd<%id%>">\
+        <form id="rf<%id%>">\
+          <textarea name="comment" cols="80"></textarea>\
+          <input type="submit" value="Add reply" />\
+          <input type="button" value="Cancel" />\
+          <input type="hidden" name="parent" value="<%id%>" />\
+          <input type="hidden" name="node" value="" />\
+        </form>\
+      </div>\
+    </li>';
+
+  $(document).ready(function() {
+    init();
+  });
+})(jQuery);
+
+$(document).ready(function() {
+  // add comment anchors for all paragraphs that are commentable
+  $('.sphinx-has-comment').comment();
+
+  // highlight search words in search results
+  $("div.context").each(function() {
+    var params = $.getQueryParameters();
+    var terms = (params.q) ? params.q[0].split(/\s+/) : [];
+    var result = $(this);
+    $.each(terms, function() {
+      result.highlightText(this.toLowerCase(), 'highlighted');
+    });
+  });
+
+  // directly open comment window if requested
+  var anchor = document.location.hash;
+  if (anchor.substring(0, 9) == '#comment-') {
+    $('#ao' + anchor.substring(9)).click();
+    document.location.hash = '#s' + anchor.substring(9);
+  }
+});

+ 314 - 0
desktop/core/ext-py/django-auth-ldap-1.2.0/docs/archive/versions/1.0.19/genindex.html

@@ -0,0 +1,314 @@
+
+
+
+
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+  "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+
+
+<html xmlns="http://www.w3.org/1999/xhtml">
+  <head>
+    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
+    
+    <title>Index &mdash; django-auth-ldap 1.0.19 documentation</title>
+    
+    <link rel="stylesheet" href="_static/default.css" type="text/css" />
+    <link rel="stylesheet" href="_static/pygments.css" type="text/css" />
+    
+    <script type="text/javascript">
+      var DOCUMENTATION_OPTIONS = {
+        URL_ROOT:    '',
+        VERSION:     '1.0.19',
+        COLLAPSE_INDEX: false,
+        FILE_SUFFIX: '.html',
+        HAS_SOURCE:  true
+      };
+    </script>
+    <script type="text/javascript" src="_static/jquery.js"></script>
+    <script type="text/javascript" src="_static/underscore.js"></script>
+    <script type="text/javascript" src="_static/doctools.js"></script>
+    <link rel="top" title="django-auth-ldap 1.0.19 documentation" href="index.html" /> 
+  </head>
+  <body>
+    <div class="related">
+      <h3>Navigation</h3>
+      <ul>
+        <li class="right" style="margin-right: 10px">
+          <a href="#" title="General Index"
+             accesskey="I">index</a></li>
+        <li class="right" >
+          <a href="py-modindex.html" title="Python Module Index"
+             >modules</a> |</li>
+        <li><a href="index.html">django-auth-ldap 1.0.19 documentation</a> &raquo;</li> 
+      </ul>
+    </div>  
+
+    <div class="document">
+      <div class="documentwrapper">
+        <div class="bodywrapper">
+          <div class="body">
+            
+
+<h1 id="index">Index</h1>
+
+<div class="genindex-jumpbox">
+ <a href="#_"><strong>_</strong></a>
+ | <a href="#A"><strong>A</strong></a>
+ | <a href="#D"><strong>D</strong></a>
+ | <a href="#G"><strong>G</strong></a>
+ | <a href="#L"><strong>L</strong></a>
+ | <a href="#M"><strong>M</strong></a>
+ | <a href="#N"><strong>N</strong></a>
+ | <a href="#P"><strong>P</strong></a>
+ | <a href="#V"><strong>V</strong></a>
+ 
+</div>
+<h2 id="_">_</h2>
+<table style="width: 100%" class="indextable genindextable"><tr>
+  <td style="width: 33%" valign="top"><dl>
+      
+  <dt><a href="index.html#django_auth_ldap.config.ActiveDirectoryGroupType.__init__">__init__() (django_auth_ldap.config.ActiveDirectoryGroupType method)</a>
+  </dt>
+
+      <dd><dl>
+        
+  <dt><a href="index.html#django_auth_ldap.config.GroupOfNamesType.__init__">(django_auth_ldap.config.GroupOfNamesType method)</a>
+  </dt>
+
+        
+  <dt><a href="index.html#django_auth_ldap.config.GroupOfUniqueNamesType.__init__">(django_auth_ldap.config.GroupOfUniqueNamesType method)</a>
+  </dt>
+
+        
+  <dt><a href="index.html#django_auth_ldap.config.LDAPGroupType.__init__">(django_auth_ldap.config.LDAPGroupType method)</a>
+  </dt>
+
+        
+  <dt><a href="index.html#django_auth_ldap.config.LDAPSearch.__init__">(django_auth_ldap.config.LDAPSearch method)</a>
+  </dt>
+
+        
+  <dt><a href="index.html#django_auth_ldap.config.MemberDNGroupType.__init__">(django_auth_ldap.config.MemberDNGroupType method)</a>
+  </dt>
+
+        
+  <dt><a href="index.html#django_auth_ldap.config.NestedActiveDirectoryGroupType.__init__">(django_auth_ldap.config.NestedActiveDirectoryGroupType method)</a>
+  </dt>
+
+        
+  <dt><a href="index.html#django_auth_ldap.config.NestedGroupOfNamesType.__init__">(django_auth_ldap.config.NestedGroupOfNamesType method)</a>
+  </dt>
+
+        
+  <dt><a href="index.html#django_auth_ldap.config.NestedGroupOfUniqueNamesType.__init__">(django_auth_ldap.config.NestedGroupOfUniqueNamesType method)</a>
+  </dt>
+
+        
+  <dt><a href="index.html#django_auth_ldap.config.NestedMemberDNGroupType.__init__">(django_auth_ldap.config.NestedMemberDNGroupType method)</a>
+  </dt>
+
+        
+  <dt><a href="index.html#django_auth_ldap.config.PosixGroupType.__init__">(django_auth_ldap.config.PosixGroupType method)</a>
+  </dt>
+
+      </dl></dd>
+  </dl></td>
+</tr></table>
+
+<h2 id="A">A</h2>
+<table style="width: 100%" class="indextable genindextable"><tr>
+  <td style="width: 33%" valign="top"><dl>
+      
+  <dt><a href="index.html#django_auth_ldap.config.ActiveDirectoryGroupType">ActiveDirectoryGroupType (class in django_auth_ldap.config)</a>
+  </dt>
+
+  </dl></td>
+</tr></table>
+
+<h2 id="D">D</h2>
+<table style="width: 100%" class="indextable genindextable"><tr>
+  <td style="width: 33%" valign="top"><dl>
+      
+  <dt><a href="index.html#module-django_auth_ldap">django_auth_ldap (module)</a>
+  </dt>
+
+      
+  <dt><a href="index.html#module-django_auth_ldap.backend">django_auth_ldap.backend (module)</a>
+  </dt>
+
+  </dl></td>
+  <td style="width: 33%" valign="top"><dl>
+      
+  <dt><a href="index.html#module-django_auth_ldap.config">django_auth_ldap.config (module)</a>
+  </dt>
+
+      
+  <dt><a href="index.html#django_auth_ldap.backend.LDAPBackend.django_to_ldap_username">django_to_ldap_username() (django_auth_ldap.backend.LDAPBackend method)</a>
+  </dt>
+
+  </dl></td>
+</tr></table>
+
+<h2 id="G">G</h2>
+<table style="width: 100%" class="indextable genindextable"><tr>
+  <td style="width: 33%" valign="top"><dl>
+      
+  <dt><a href="index.html#django_auth_ldap.backend.LDAPBackend.get_or_create_user">get_or_create_user() (django_auth_ldap.backend.LDAPBackend method)</a>
+  </dt>
+
+      
+  <dt><a href="index.html#django_auth_ldap.config.GroupOfNamesType">GroupOfNamesType (class in django_auth_ldap.config)</a>
+  </dt>
+
+  </dl></td>
+  <td style="width: 33%" valign="top"><dl>
+      
+  <dt><a href="index.html#django_auth_ldap.config.GroupOfUniqueNamesType">GroupOfUniqueNamesType (class in django_auth_ldap.config)</a>
+  </dt>
+
+  </dl></td>
+</tr></table>
+
+<h2 id="L">L</h2>
+<table style="width: 100%" class="indextable genindextable"><tr>
+  <td style="width: 33%" valign="top"><dl>
+      
+  <dt><a href="index.html#django_auth_ldap.backend.LDAPBackend.ldap_to_django_username">ldap_to_django_username() (django_auth_ldap.backend.LDAPBackend method)</a>
+  </dt>
+
+      
+  <dt><a href="index.html#django_auth_ldap.backend.LDAPBackend">LDAPBackend (class in django_auth_ldap.backend)</a>
+  </dt>
+
+  </dl></td>
+  <td style="width: 33%" valign="top"><dl>
+      
+  <dt><a href="index.html#django_auth_ldap.config.LDAPGroupType">LDAPGroupType (class in django_auth_ldap.config)</a>
+  </dt>
+
+      
+  <dt><a href="index.html#django_auth_ldap.config.LDAPSearch">LDAPSearch (class in django_auth_ldap.config)</a>
+  </dt>
+
+  </dl></td>
+</tr></table>
+
+<h2 id="M">M</h2>
+<table style="width: 100%" class="indextable genindextable"><tr>
+  <td style="width: 33%" valign="top"><dl>
+      
+  <dt><a href="index.html#django_auth_ldap.config.MemberDNGroupType">MemberDNGroupType (class in django_auth_ldap.config)</a>
+  </dt>
+
+  </dl></td>
+</tr></table>
+
+<h2 id="N">N</h2>
+<table style="width: 100%" class="indextable genindextable"><tr>
+  <td style="width: 33%" valign="top"><dl>
+      
+  <dt><a href="index.html#django_auth_ldap.config.NestedActiveDirectoryGroupType">NestedActiveDirectoryGroupType (class in django_auth_ldap.config)</a>
+  </dt>
+
+      
+  <dt><a href="index.html#django_auth_ldap.config.NestedGroupOfNamesType">NestedGroupOfNamesType (class in django_auth_ldap.config)</a>
+  </dt>
+
+  </dl></td>
+  <td style="width: 33%" valign="top"><dl>
+      
+  <dt><a href="index.html#django_auth_ldap.config.NestedGroupOfUniqueNamesType">NestedGroupOfUniqueNamesType (class in django_auth_ldap.config)</a>
+  </dt>
+
+      
+  <dt><a href="index.html#django_auth_ldap.config.NestedMemberDNGroupType">NestedMemberDNGroupType (class in django_auth_ldap.config)</a>
+  </dt>
+
+  </dl></td>
+</tr></table>
+
+<h2 id="P">P</h2>
+<table style="width: 100%" class="indextable genindextable"><tr>
+  <td style="width: 33%" valign="top"><dl>
+      
+  <dt><a href="index.html#django_auth_ldap.backend.populate_user">populate_user (in module django_auth_ldap.backend)</a>
+  </dt>
+
+      
+  <dt><a href="index.html#django_auth_ldap.backend.LDAPBackend.populate_user">populate_user() (django_auth_ldap.backend.LDAPBackend method)</a>
+  </dt>
+
+  </dl></td>
+  <td style="width: 33%" valign="top"><dl>
+      
+  <dt><a href="index.html#django_auth_ldap.backend.populate_user_profile">populate_user_profile (in module django_auth_ldap.backend)</a>
+  </dt>
+
+      
+  <dt><a href="index.html#django_auth_ldap.config.PosixGroupType">PosixGroupType (class in django_auth_ldap.config)</a>
+  </dt>
+
+  </dl></td>
+</tr></table>
+
+<h2 id="V">V</h2>
+<table style="width: 100%" class="indextable genindextable"><tr>
+  <td style="width: 33%" valign="top"><dl>
+      
+  <dt><a href="index.html#django_auth_ldap.version">version (in module django_auth_ldap)</a>
+  </dt>
+
+  </dl></td>
+  <td style="width: 33%" valign="top"><dl>
+      
+  <dt><a href="index.html#django_auth_ldap.version_string">version_string (in module django_auth_ldap)</a>
+  </dt>
+
+  </dl></td>
+</tr></table>
+
+
+
+          </div>
+        </div>
+      </div>
+      <div class="sphinxsidebar">
+        <div class="sphinxsidebarwrapper">
+
+   
+
+<div id="searchbox" style="display: none">
+  <h3>Quick search</h3>
+    <form class="search" action="search.html" method="get">
+      <input type="text" name="q" />
+      <input type="submit" value="Go" />
+      <input type="hidden" name="check_keywords" value="yes" />
+      <input type="hidden" name="area" value="default" />
+    </form>
+    <p class="searchtip" style="font-size: 90%">
+    Enter search terms or a module, class or function name.
+    </p>
+</div>
+<script type="text/javascript">$('#searchbox').show(0);</script>
+        </div>
+      </div>
+      <div class="clearer"></div>
+    </div>
+    <div class="related">
+      <h3>Navigation</h3>
+      <ul>
+        <li class="right" style="margin-right: 10px">
+          <a href="#" title="General Index"
+             >index</a></li>
+        <li class="right" >
+          <a href="py-modindex.html" title="Python Module Index"
+             >modules</a> |</li>
+        <li><a href="index.html">django-auth-ldap 1.0.19 documentation</a> &raquo;</li> 
+      </ul>
+    </div>
+    <div class="footer">
+        &copy; Copyright 2009, Peter Sagerson.
+      Created using <a href="http://sphinx.pocoo.org/">Sphinx</a> 1.1.3.
+    </div>
+  </body>
+</html>

+ 1030 - 0
desktop/core/ext-py/django-auth-ldap-1.2.0/docs/archive/versions/1.0.19/index.html

@@ -0,0 +1,1030 @@
+
+
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+  "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+
+
+<html xmlns="http://www.w3.org/1999/xhtml">
+  <head>
+    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
+    
+    <title>Django authentication using LDAP &mdash; django-auth-ldap 1.0.19 documentation</title>
+    
+    <link rel="stylesheet" href="_static/default.css" type="text/css" />
+    <link rel="stylesheet" href="_static/pygments.css" type="text/css" />
+    
+    <script type="text/javascript">
+      var DOCUMENTATION_OPTIONS = {
+        URL_ROOT:    '',
+        VERSION:     '1.0.19',
+        COLLAPSE_INDEX: false,
+        FILE_SUFFIX: '.html',
+        HAS_SOURCE:  true
+      };
+    </script>
+    <script type="text/javascript" src="_static/jquery.js"></script>
+    <script type="text/javascript" src="_static/underscore.js"></script>
+    <script type="text/javascript" src="_static/doctools.js"></script>
+    <link rel="top" title="django-auth-ldap 1.0.19 documentation" href="#" /> 
+  </head>
+  <body>
+    <div class="related">
+      <h3>Navigation</h3>
+      <ul>
+        <li class="right" style="margin-right: 10px">
+          <a href="genindex.html" title="General Index"
+             accesskey="I">index</a></li>
+        <li class="right" >
+          <a href="py-modindex.html" title="Python Module Index"
+             >modules</a> |</li>
+        <li><a href="#">django-auth-ldap 1.0.19 documentation</a> &raquo;</li> 
+      </ul>
+    </div>  
+
+    <div class="document">
+      <div class="documentwrapper">
+        <div class="bodywrapper">
+          <div class="body">
+            
+  <div class="section" id="django-authentication-using-ldap">
+<h1>Django authentication using LDAP<a class="headerlink" href="#django-authentication-using-ldap" title="Permalink to this headline">¶</a></h1>
+<p>This authentication backend enables a Django project to authenticate against any
+LDAP server. To use it, add <a class="reference internal" href="#django_auth_ldap.backend.LDAPBackend" title="django_auth_ldap.backend.LDAPBackend"><tt class="xref py py-class docutils literal"><span class="pre">django_auth_ldap.backend.LDAPBackend</span></tt></a> to
+AUTHENTICATION_BACKENDS. It is not necessary to add <cite>django_auth_ldap</cite> 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
+<tt class="xref py py-class docutils literal"><span class="pre">User</span></tt> objects, groups, and permissions. This
+backend depends on the <a class="reference external" href="http://www.python-ldap.org/">python-ldap</a> module.</p>
+<div class="admonition note">
+<p class="first admonition-title">Note</p>
+<p><a class="reference internal" href="#django_auth_ldap.backend.LDAPBackend" title="django_auth_ldap.backend.LDAPBackend"><tt class="xref py py-class docutils literal"><span class="pre">LDAPBackend</span></tt></a> does not inherit from
+<tt class="xref py py-class docutils literal"><span class="pre">ModelBackend</span></tt>. It is possible to use
+<a class="reference internal" href="#django_auth_ldap.backend.LDAPBackend" title="django_auth_ldap.backend.LDAPBackend"><tt class="xref py py-class docutils literal"><span class="pre">LDAPBackend</span></tt></a> 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&#8217;ll need to have both backends installed:</p>
+<div class="last highlight-python"><div class="highlight"><pre><span class="n">AUTHENTICATION_BACKENDS</span> <span class="o">=</span> <span class="p">(</span>
+    <span class="s">&#39;django_auth_ldap.backend.LDAPBackend&#39;</span><span class="p">,</span>
+    <span class="s">&#39;django.contrib.auth.backends.ModelBackend&#39;</span><span class="p">,</span>
+<span class="p">)</span>
+</pre></div>
+</div>
+</div>
+<div class="section" id="configuring-basic-authentication">
+<h2>Configuring basic authentication<a class="headerlink" href="#configuring-basic-authentication" title="Permalink to this headline">¶</a></h2>
+<p>If your LDAP server isn&#8217;t running locally on the default port, you&#8217;ll want to
+start by setting <a class="reference internal" href="#auth-ldap-server-uri"><em>AUTH_LDAP_SERVER_URI</em></a> to point to your server.</p>
+<div class="highlight-python"><div class="highlight"><pre><span class="n">AUTH_LDAP_SERVER_URI</span> <span class="o">=</span> <span class="s">&quot;ldap://ldap.example.com&quot;</span>
+</pre></div>
+</div>
+<p>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&#8217;s password. The second
+method is to derive the user&#8217;s DN from his username and attempt to bind as the
+user directly.</p>
+<p>Because LDAP searches appear elsewhere in the configuration, the
+<a class="reference internal" href="#django_auth_ldap.config.LDAPSearch" title="django_auth_ldap.config.LDAPSearch"><tt class="xref py py-class docutils literal"><span class="pre">LDAPSearch</span></tt></a> class is provided to encapsulate
+search information. In this case, the filter parameter should contain the
+placeholder <tt class="docutils literal"><span class="pre">%(user)s</span></tt>. A simple configuration for the search/bind approach
+looks like this (some defaults included for completeness):</p>
+<div class="highlight-python"><div class="highlight"><pre><span class="kn">import</span> <span class="nn">ldap</span>
+<span class="kn">from</span> <span class="nn">django_auth_ldap.config</span> <span class="kn">import</span> <span class="n">LDAPSearch</span>
+
+<span class="n">AUTH_LDAP_BIND_DN</span> <span class="o">=</span> <span class="s">&quot;&quot;</span>
+<span class="n">AUTH_LDAP_BIND_PASSWORD</span> <span class="o">=</span> <span class="s">&quot;&quot;</span>
+<span class="n">AUTH_LDAP_USER_SEARCH</span> <span class="o">=</span> <span class="n">LDAPSearch</span><span class="p">(</span><span class="s">&quot;ou=users,dc=example,dc=com&quot;</span><span class="p">,</span>
+    <span class="n">ldap</span><span class="o">.</span><span class="n">SCOPE_SUBTREE</span><span class="p">,</span> <span class="s">&quot;(uid=</span><span class="si">%(user)s</span><span class="s">)&quot;</span><span class="p">)</span>
+</pre></div>
+</div>
+<p>This will perform an anonymous bind, search under
+<tt class="docutils literal"><span class="pre">&quot;ou=users,dc=example,dc=com&quot;</span></tt> for an object with a uid matching the user&#8217;s
+name, and try to bind using that DN and the user&#8217;s password. The search must
+return exactly one result or authentication will fail. If you can&#8217;t search
+anonymously, you can set <a class="reference internal" href="#auth-ldap-bind-dn"><em>AUTH_LDAP_BIND_DN</em></a> to the distinguished name of
+an authorized user and <a class="reference internal" href="#auth-ldap-bind-password"><em>AUTH_LDAP_BIND_PASSWORD</em></a> to the password.</p>
+<p>To skip the search phase, set <a class="reference internal" href="#auth-ldap-user-dn-template"><em>AUTH_LDAP_USER_DN_TEMPLATE</em></a> to a template
+that will produce the authenticating user&#8217;s DN directly. This template should
+have one placeholder, <tt class="docutils literal"><span class="pre">%(user)s</span></tt>. If the previous example had used
+<tt class="docutils literal"><span class="pre">ldap.SCOPE_ONELEVEL</span></tt>, the following would be a more straightforward (and
+efficient) equivalent:</p>
+<div class="highlight-python"><div class="highlight"><pre><span class="n">AUTH_LDAP_USER_DN_TEMPLATE</span> <span class="o">=</span> <span class="s">&quot;uid=</span><span class="si">%(user)s</span><span class="s">,ou=users,dc=example,dc=com&quot;</span>
+</pre></div>
+</div>
+<p>LDAP is fairly flexible when it comes to matching DNs.
+<a class="reference internal" href="#django_auth_ldap.backend.LDAPBackend" title="django_auth_ldap.backend.LDAPBackend"><tt class="xref py py-class docutils literal"><span class="pre">LDAPBackend</span></tt></a> make an effort to accommodate
+this by forcing usernames to lower case when creating Django users and trimming
+whitespace when authenticating.</p>
+<p>By default, all LDAP operations are performed with the <a class="reference internal" href="#auth-ldap-bind-dn"><em>AUTH_LDAP_BIND_DN</em></a>
+and <a class="reference internal" href="#auth-ldap-bind-password"><em>AUTH_LDAP_BIND_PASSWORD</em></a> credentials, not with the user&#8217;s. Otherwise,
+the LDAP connection would be bound as the authenticating user during login
+requests and as the default credentials during other requests, so you would see
+inconsistent LDAP attributes depending on the nature of the Django view. If
+you&#8217;re willing to accept the inconsistency in order to retrieve attributes
+while bound as the authenticating user. see
+<a class="reference internal" href="#auth-ldap-bind-as-authenticating-user"><em>AUTH_LDAP_BIND_AS_AUTHENTICATING_USER</em></a>.</p>
+<p>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 <tt class="docutils literal"><span class="pre">ldaps://</span></tt> URL or enable
+the StartTLS extension. The latter is generally the preferred mechanism. To
+enable StartTLS, set <a class="reference internal" href="#auth-ldap-start-tls"><em>AUTH_LDAP_START_TLS</em></a> to <tt class="docutils literal"><span class="pre">True</span></tt>:</p>
+<div class="highlight-python"><div class="highlight"><pre><span class="n">AUTH_LDAP_START_TLS</span> <span class="o">=</span> <span class="bp">True</span>
+</pre></div>
+</div>
+</div>
+<div class="section" id="working-with-groups">
+<h2>Working with groups<a class="headerlink" href="#working-with-groups" title="Permalink to this headline">¶</a></h2>
+<p>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.
+<a class="reference internal" href="#django_auth_ldap.config.LDAPGroupType" title="django_auth_ldap.config.LDAPGroupType"><tt class="xref py py-class docutils literal"><span class="pre">LDAPGroupType</span></tt></a> is a base class whose concrete
+subclasses can determine group membership for particular grouping mechanisms.
+Three built-in subclasses cover most grouping mechanisms:</p>
+<blockquote>
+<div><ul class="simple">
+<li><a class="reference internal" href="#django_auth_ldap.config.PosixGroupType" title="django_auth_ldap.config.PosixGroupType"><tt class="xref py py-class docutils literal"><span class="pre">PosixGroupType</span></tt></a></li>
+<li><a class="reference internal" href="#django_auth_ldap.config.MemberDNGroupType" title="django_auth_ldap.config.MemberDNGroupType"><tt class="xref py py-class docutils literal"><span class="pre">MemberDNGroupType</span></tt></a></li>
+<li><a class="reference internal" href="#django_auth_ldap.config.NestedMemberDNGroupType" title="django_auth_ldap.config.NestedMemberDNGroupType"><tt class="xref py py-class docutils literal"><span class="pre">NestedMemberDNGroupType</span></tt></a></li>
+</ul>
+</div></blockquote>
+<p>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:</p>
+<blockquote>
+<div><ul class="simple">
+<li><a class="reference internal" href="#django_auth_ldap.config.GroupOfNamesType" title="django_auth_ldap.config.GroupOfNamesType"><tt class="xref py py-class docutils literal"><span class="pre">GroupOfNamesType</span></tt></a></li>
+<li><a class="reference internal" href="#django_auth_ldap.config.NestedGroupOfNamesType" title="django_auth_ldap.config.NestedGroupOfNamesType"><tt class="xref py py-class docutils literal"><span class="pre">NestedGroupOfNamesType</span></tt></a></li>
+<li><a class="reference internal" href="#django_auth_ldap.config.GroupOfUniqueNamesType" title="django_auth_ldap.config.GroupOfUniqueNamesType"><tt class="xref py py-class docutils literal"><span class="pre">GroupOfUniqueNamesType</span></tt></a></li>
+<li><a class="reference internal" href="#django_auth_ldap.config.NestedGroupOfUniqueNamesType" title="django_auth_ldap.config.NestedGroupOfUniqueNamesType"><tt class="xref py py-class docutils literal"><span class="pre">NestedGroupOfUniqueNamesType</span></tt></a></li>
+<li><a class="reference internal" href="#django_auth_ldap.config.ActiveDirectoryGroupType" title="django_auth_ldap.config.ActiveDirectoryGroupType"><tt class="xref py py-class docutils literal"><span class="pre">ActiveDirectoryGroupType</span></tt></a></li>
+<li><a class="reference internal" href="#django_auth_ldap.config.NestedActiveDirectoryGroupType" title="django_auth_ldap.config.NestedActiveDirectoryGroupType"><tt class="xref py py-class docutils literal"><span class="pre">NestedActiveDirectoryGroupType</span></tt></a></li>
+</ul>
+</div></blockquote>
+<p>To get started, you&#8217;ll need to provide some basic information about your LDAP
+groups. <a class="reference internal" href="#auth-ldap-group-search"><em>AUTH_LDAP_GROUP_SEARCH</em></a> is an
+<a class="reference internal" href="#django_auth_ldap.config.LDAPSearch" title="django_auth_ldap.config.LDAPSearch"><tt class="xref py py-class docutils literal"><span class="pre">LDAPSearch</span></tt></a> 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). <a class="reference internal" href="#auth-ldap-group-type"><em>AUTH_LDAP_GROUP_TYPE</em></a> is an instance of the class
+corresponding to the type of group that will be returned by
+<a class="reference internal" href="#auth-ldap-group-search"><em>AUTH_LDAP_GROUP_SEARCH</em></a>. All groups referenced elsewhere in the
+configuration must be of this type and part of the search results.</p>
+<div class="highlight-python"><div class="highlight"><pre><span class="kn">import</span> <span class="nn">ldap</span>
+<span class="kn">from</span> <span class="nn">django_auth_ldap.config</span> <span class="kn">import</span> <span class="n">LDAPSearch</span><span class="p">,</span> <span class="n">GroupOfNamesType</span>
+
+<span class="n">AUTH_LDAP_GROUP_SEARCH</span> <span class="o">=</span> <span class="n">LDAPSearch</span><span class="p">(</span><span class="s">&quot;ou=groups,dc=example,dc=com&quot;</span><span class="p">,</span>
+    <span class="n">ldap</span><span class="o">.</span><span class="n">SCOPE_SUBTREE</span><span class="p">,</span> <span class="s">&quot;(objectClass=groupOfNames)&quot;</span>
+<span class="p">)</span>
+<span class="n">AUTH_LDAP_GROUP_TYPE</span> <span class="o">=</span> <span class="n">GroupOfNamesType</span><span class="p">()</span>
+</pre></div>
+</div>
+<p>The simplest use of groups is to limit the users who are allowed to log in. If
+<a class="reference internal" href="#auth-ldap-require-group"><em>AUTH_LDAP_REQUIRE_GROUP</em></a> is set, then only users who are members of that
+group will successfully authenticate. <a class="reference internal" href="#auth-ldap-deny-group"><em>AUTH_LDAP_DENY_GROUP</em></a> is the
+reverse: if given, members of this group will be rejected.</p>
+<div class="highlight-python"><div class="highlight"><pre><span class="n">AUTH_LDAP_REQUIRE_GROUP</span> <span class="o">=</span> <span class="s">&quot;cn=enabled,ou=groups,dc=example,dc=com&quot;</span>
+<span class="n">AUTH_LDAP_DENY_GROUP</span> <span class="o">=</span> <span class="s">&quot;cn=disabled,ou=groups,dc=example,dc=com&quot;</span>
+</pre></div>
+</div>
+<p>More advanced uses of groups are covered in the next two sections.</p>
+</div>
+<div class="section" id="user-objects">
+<h2>User objects<a class="headerlink" href="#user-objects" title="Permalink to this headline">¶</a></h2>
+<p>Authenticating against an external source is swell, but Django&#8217;s auth module is
+tightly bound to the <tt class="xref py py-class docutils literal"><span class="pre">django.contrib.auth.models.User</span></tt> model. Thus, when
+a user logs in, we have to create a <tt class="xref py py-class docutils literal"><span class="pre">User</span></tt>
+object to represent him in the database. Because the LDAP search is
+case-insenstive, the default implementation also searches for existing Django
+users with an iexact query and new users are created with lowercase usernames.
+See <a class="reference internal" href="#django_auth_ldap.backend.LDAPBackend.get_or_create_user" title="django_auth_ldap.backend.LDAPBackend.get_or_create_user"><tt class="xref py py-meth docutils literal"><span class="pre">get_or_create_user()</span></tt></a> if you&#8217;d
+like to override this behavior.</p>
+<p>The only required field for a user is the username, which we obviously have. The
+<tt class="xref py py-class docutils literal"><span class="pre">User</span></tt> model is picky about the characters
+allowed in usernames, so <a class="reference internal" href="#django_auth_ldap.backend.LDAPBackend" title="django_auth_ldap.backend.LDAPBackend"><tt class="xref py py-class docutils literal"><span class="pre">LDAPBackend</span></tt></a> includes
+a pair of hooks,
+<a class="reference internal" href="#django_auth_ldap.backend.LDAPBackend.ldap_to_django_username" title="django_auth_ldap.backend.LDAPBackend.ldap_to_django_username"><tt class="xref py py-meth docutils literal"><span class="pre">ldap_to_django_username()</span></tt></a> and
+<a class="reference internal" href="#django_auth_ldap.backend.LDAPBackend.django_to_ldap_username" title="django_auth_ldap.backend.LDAPBackend.django_to_ldap_username"><tt class="xref py py-meth docutils literal"><span class="pre">django_to_ldap_username()</span></tt></a>, to
+translate between LDAP usernames and Django usernames. You&#8217;ll need this, for
+example, if your LDAP names have periods in them. You can subclass
+<a class="reference internal" href="#django_auth_ldap.backend.LDAPBackend" title="django_auth_ldap.backend.LDAPBackend"><tt class="xref py py-class docutils literal"><span class="pre">LDAPBackend</span></tt></a> to implement these hooks; by
+default the username is not modified. <tt class="xref py py-class docutils literal"><span class="pre">User</span></tt>
+objects that are authenticated by <a class="reference internal" href="#django_auth_ldap.backend.LDAPBackend" title="django_auth_ldap.backend.LDAPBackend"><tt class="xref py py-class docutils literal"><span class="pre">LDAPBackend</span></tt></a>
+will have an <tt class="xref py py-attr docutils literal"><span class="pre">ldap_username</span></tt> attribute
+with the original (LDAP) username.
+<tt class="xref py py-attr docutils literal"><span class="pre">username</span></tt> will, of course, be the Django
+username.</p>
+<p>LDAP directories tend to contain much more information about users that you may
+wish to propagate. A pair of settings, <a class="reference internal" href="#auth-ldap-user-attr-map"><em>AUTH_LDAP_USER_ATTR_MAP</em></a> and
+<a class="reference internal" href="#auth-ldap-profile-attr-map"><em>AUTH_LDAP_PROFILE_ATTR_MAP</em></a>, serve to copy directory information into
+<tt class="xref py py-class docutils literal"><span class="pre">User</span></tt> and profile objects. These are
+dictionaries that map user and profile model keys, respectively, to
+(case-insensitive) LDAP attribute names:</p>
+<div class="highlight-python"><div class="highlight"><pre><span class="n">AUTH_LDAP_USER_ATTR_MAP</span> <span class="o">=</span> <span class="p">{</span><span class="s">&quot;first_name&quot;</span><span class="p">:</span> <span class="s">&quot;givenName&quot;</span><span class="p">,</span> <span class="s">&quot;last_name&quot;</span><span class="p">:</span> <span class="s">&quot;sn&quot;</span><span class="p">}</span>
+<span class="n">AUTH_LDAP_PROFILE_ATTR_MAP</span> <span class="o">=</span> <span class="p">{</span><span class="s">&quot;home_directory&quot;</span><span class="p">:</span> <span class="s">&quot;homeDirectory&quot;</span><span class="p">}</span>
+</pre></div>
+</div>
+<p>Only string fields can be mapped to attributes. Boolean fields can be defined by
+group membership:</p>
+<div class="highlight-python"><div class="highlight"><pre><span class="n">AUTH_LDAP_USER_FLAGS_BY_GROUP</span> <span class="o">=</span> <span class="p">{</span>
+    <span class="s">&quot;is_active&quot;</span><span class="p">:</span> <span class="s">&quot;cn=active,ou=groups,dc=example,dc=com&quot;</span><span class="p">,</span>
+    <span class="s">&quot;is_staff&quot;</span><span class="p">:</span> <span class="s">&quot;cn=staff,ou=groups,dc=example,dc=com&quot;</span><span class="p">,</span>
+    <span class="s">&quot;is_superuser&quot;</span><span class="p">:</span> <span class="s">&quot;cn=superuser,ou=groups,dc=example,dc=com&quot;</span>
+<span class="p">}</span>
+
+<span class="n">AUTH_LDAP_PROFILE_FLAGS_BY_GROUP</span> <span class="o">=</span> <span class="p">{</span>
+    <span class="s">&quot;is_awesome&quot;</span><span class="p">:</span> <span class="s">&quot;cn=awesome,ou=django,ou=groups,dc=example,dc=com&quot;</span>
+<span class="p">}</span>
+</pre></div>
+</div>
+<p>By default, all mapped user fields will be updated each time the user logs in.
+To disable this, set <a class="reference internal" href="#auth-ldap-always-update-user"><em>AUTH_LDAP_ALWAYS_UPDATE_USER</em></a> to <tt class="docutils literal"><span class="pre">False</span></tt>. 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 <a class="reference internal" href="#django_auth_ldap.backend.LDAPBackend.populate_user" title="django_auth_ldap.backend.LDAPBackend.populate_user"><tt class="xref py py-meth docutils literal"><span class="pre">django_auth_ldap.backend.LDAPBackend.populate_user()</span></tt></a>. You&#8217;ll
+need an instance of <a class="reference internal" href="#django_auth_ldap.backend.LDAPBackend" title="django_auth_ldap.backend.LDAPBackend"><tt class="xref py py-class docutils literal"><span class="pre">LDAPBackend</span></tt></a>, which you
+should feel free to create yourself.
+<a class="reference internal" href="#django_auth_ldap.backend.LDAPBackend.populate_user" title="django_auth_ldap.backend.LDAPBackend.populate_user"><tt class="xref py py-meth docutils literal"><span class="pre">populate_user()</span></tt></a> returns the new
+<tt class="xref py py-class docutils literal"><span class="pre">User</span></tt> or <cite>None</cite> if the user could not be
+found in LDAP.</p>
+<p>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&#8217;s raw LDAP attributes.
+<tt class="docutils literal"><span class="pre">user.ldap_user</span></tt> is an object with four public properties. The group
+properties are, of course, only valid if groups are configured.</p>
+<blockquote>
+<div><ul class="simple">
+<li><tt class="docutils literal"><span class="pre">dn</span></tt>: The user&#8217;s distinguished name.</li>
+<li><tt class="docutils literal"><span class="pre">attrs</span></tt>: The user&#8217;s LDAP attributes as a dictionary of lists of string
+values. The dictionaries are modified to use case-insensitive keys.</li>
+<li><tt class="docutils literal"><span class="pre">group_dns</span></tt>: The set of groups that this user belongs to, as DNs.</li>
+<li><tt class="docutils literal"><span class="pre">group_names</span></tt>: The set of groups that this user belongs to, as simple
+names. These are the names that will be used if
+<a class="reference internal" href="#auth-ldap-mirror-groups"><em>AUTH_LDAP_MIRROR_GROUPS</em></a> is used.</li>
+</ul>
+</div></blockquote>
+<p>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&#8217;s objectSid.</p>
+<p>If you would like to perform any additional population of user or profile
+objects, django_auth_ldap exposes two custom signals to help:
+<a class="reference internal" href="#django_auth_ldap.backend.populate_user" title="django_auth_ldap.backend.populate_user"><tt class="xref py py-data docutils literal"><span class="pre">populate_user</span></tt></a> and
+<a class="reference internal" href="#django_auth_ldap.backend.populate_user_profile" title="django_auth_ldap.backend.populate_user_profile"><tt class="xref py py-data docutils literal"><span class="pre">populate_user_profile</span></tt></a>. These are sent after
+the backend has finished populating the respective objects and before they are
+saved to the database. You can use this to propagate additional information from
+the LDAP directory to the user and profile objects any way you like.</p>
+<div class="admonition note">
+<p class="first admonition-title">Note</p>
+<p class="last">Users created by <a class="reference internal" href="#django_auth_ldap.backend.LDAPBackend" title="django_auth_ldap.backend.LDAPBackend"><tt class="xref py py-class docutils literal"><span class="pre">LDAPBackend</span></tt></a> 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
+<tt class="xref py py-class docutils literal"><span class="pre">ModelBackend</span></tt> (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.</p>
+</div>
+</div>
+<div class="section" id="permissions">
+<h2>Permissions<a class="headerlink" href="#permissions" title="Permalink to this headline">¶</a></h2>
+<p>Groups are useful for more than just populating the user&#8217;s <tt class="docutils literal"><span class="pre">is_*</span></tt> fields.
+<a class="reference internal" href="#django_auth_ldap.backend.LDAPBackend" title="django_auth_ldap.backend.LDAPBackend"><tt class="xref py py-class docutils literal"><span class="pre">LDAPBackend</span></tt></a> would not be complete without
+some way to turn a user&#8217;s LDAP group memberships into Django model permissions.
+In fact, there are two ways to do this.</p>
+<p>Ultimately, both mechanisms need some way to map LDAP groups to Django groups.
+Implementations of <a class="reference internal" href="#django_auth_ldap.config.LDAPGroupType" title="django_auth_ldap.config.LDAPGroupType"><tt class="xref py py-class docutils literal"><span class="pre">LDAPGroupType</span></tt></a> will have an
+algorithm for deriving the Django group name from the LDAP group. Clients that
+need to modify this behavior can subclass the
+<a class="reference internal" href="#django_auth_ldap.config.LDAPGroupType" title="django_auth_ldap.config.LDAPGroupType"><tt class="xref py py-class docutils literal"><span class="pre">LDAPGroupType</span></tt></a> class. All of the built-in
+implementations take a <tt class="docutils literal"><span class="pre">name_attr</span></tt> argument to <tt class="docutils literal"><span class="pre">__init__</span></tt>, which
+specifies the LDAP attribute from which to take the Django group name. By
+default, the <tt class="docutils literal"><span class="pre">cn</span></tt> attribute is used.</p>
+<p>The least invasive way to map group permissions is to set
+<a class="reference internal" href="#auth-ldap-find-group-perms"><em>AUTH_LDAP_FIND_GROUP_PERMS</em></a> to <tt class="docutils literal"><span class="pre">True</span></tt>.
+<a class="reference internal" href="#django_auth_ldap.backend.LDAPBackend" title="django_auth_ldap.backend.LDAPBackend"><tt class="xref py py-class docutils literal"><span class="pre">LDAPBackend</span></tt></a> 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.</p>
+<p>To minimize traffic to the LDAP server,
+<a class="reference internal" href="#django_auth_ldap.backend.LDAPBackend" title="django_auth_ldap.backend.LDAPBackend"><tt class="xref py py-class docutils literal"><span class="pre">LDAPBackend</span></tt></a> can make use of Django&#8217;s cache
+framework to keep a copy of a user&#8217;s LDAP group memberships. To enable this
+feature, set <a class="reference internal" href="#auth-ldap-cache-groups"><em>AUTH_LDAP_CACHE_GROUPS</em></a> to <tt class="docutils literal"><span class="pre">True</span></tt>. You can also set
+<a class="reference internal" href="#auth-ldap-group-cache-timeout"><em>AUTH_LDAP_GROUP_CACHE_TIMEOUT</em></a> to override the timeout of cache entries
+(in seconds).</p>
+<div class="highlight-python"><div class="highlight"><pre><span class="n">AUTH_LDAP_CACHE_GROUPS</span> <span class="o">=</span> <span class="bp">True</span>
+<span class="n">AUTH_LDAP_GROUP_CACHE_TIMEOUT</span> <span class="o">=</span> <span class="mi">300</span>
+</pre></div>
+</div>
+<p>The second way to turn LDAP group memberships into permissions is to mirror the
+groups themselves. If <a class="reference internal" href="#auth-ldap-mirror-groups"><em>AUTH_LDAP_MIRROR_GROUPS</em></a> is <tt class="docutils literal"><span class="pre">True</span></tt>, then every
+time a user logs in, <a class="reference internal" href="#django_auth_ldap.backend.LDAPBackend" title="django_auth_ldap.backend.LDAPBackend"><tt class="xref py py-class docutils literal"><span class="pre">LDAPBackend</span></tt></a> will update
+the database with the user&#8217;s LDAP groups. Any group that doesn&#8217;t exist will be
+created and the user&#8217;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.</p>
+<p>This approach has two main differences from <a class="reference internal" href="#auth-ldap-find-group-perms"><em>AUTH_LDAP_FIND_GROUP_PERMS</em></a>.
+First, <a class="reference internal" href="#auth-ldap-find-group-perms"><em>AUTH_LDAP_FIND_GROUP_PERMS</em></a> 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 <a class="reference internal" href="#auth-ldap-find-group-perms"><em>AUTH_LDAP_FIND_GROUP_PERMS</em></a>, there is no way for clients to
+determine a user&#8217;s group memberships, only their permissions. If you want to
+make decisions based directly on group membership, you&#8217;ll have to mirror the
+groups.</p>
+<p><a class="reference internal" href="#django_auth_ldap.backend.LDAPBackend" title="django_auth_ldap.backend.LDAPBackend"><tt class="xref py py-class docutils literal"><span class="pre">LDAPBackend</span></tt></a> 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&#8217;s RemoteUserBackend
+to map externally authenticated users to Django users. By setting
+<a class="reference internal" href="#auth-ldap-authorize-all-users"><em>AUTH_LDAP_AUTHORIZE_ALL_USERS</em></a>,
+<a class="reference internal" href="#django_auth_ldap.backend.LDAPBackend" title="django_auth_ldap.backend.LDAPBackend"><tt class="xref py py-class docutils literal"><span class="pre">LDAPBackend</span></tt></a> will map these users to LDAP
+users in the normal way in order to provide authorization information. Note that
+this does <em>not</em> work with <a class="reference internal" href="#auth-ldap-mirror-groups"><em>AUTH_LDAP_MIRROR_GROUPS</em></a>; group mirroring is a
+feature of authentication, not authorization.</p>
+</div>
+<div class="section" id="logging">
+<h2>Logging<a class="headerlink" href="#logging" title="Permalink to this headline">¶</a></h2>
+<p><a class="reference internal" href="#django_auth_ldap.backend.LDAPBackend" title="django_auth_ldap.backend.LDAPBackend"><tt class="xref py py-class docutils literal"><span class="pre">LDAPBackend</span></tt></a> uses the standard logging module
+to log debug and warning messages to the logger named <tt class="docutils literal"><span class="pre">'django_auth_ldap'</span></tt>. 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.</p>
+<div class="highlight-python"><div class="highlight"><pre><span class="kn">import</span> <span class="nn">logging</span>
+
+<span class="n">logger</span> <span class="o">=</span> <span class="n">logging</span><span class="o">.</span><span class="n">getLogger</span><span class="p">(</span><span class="s">&#39;django_auth_ldap&#39;</span><span class="p">)</span>
+<span class="n">logger</span><span class="o">.</span><span class="n">addHandler</span><span class="p">(</span><span class="n">logging</span><span class="o">.</span><span class="n">StreamHandler</span><span class="p">())</span>
+<span class="n">logger</span><span class="o">.</span><span class="n">setLevel</span><span class="p">(</span><span class="n">logging</span><span class="o">.</span><span class="n">DEBUG</span><span class="p">)</span>
+</pre></div>
+</div>
+</div>
+<div class="section" id="more-options">
+<h2>More options<a class="headerlink" href="#more-options" title="Permalink to this headline">¶</a></h2>
+<p>Miscellaneous settings for <a class="reference internal" href="#django_auth_ldap.backend.LDAPBackend" title="django_auth_ldap.backend.LDAPBackend"><tt class="xref py py-class docutils literal"><span class="pre">LDAPBackend</span></tt></a>:</p>
+<blockquote>
+<div><ul class="simple">
+<li><a class="reference internal" href="#auth-ldap-global-options"><em>AUTH_LDAP_GLOBAL_OPTIONS</em></a>: A dictionary of options to pass to
+python-ldap via <tt class="docutils literal"><span class="pre">ldap.set_option()</span></tt>.</li>
+<li><a class="reference internal" href="#auth-ldap-connection-options"><em>AUTH_LDAP_CONNECTION_OPTIONS</em></a>: A dictionary of options to pass to
+each LDAPObject instance via <tt class="docutils literal"><span class="pre">LDAPObject.set_option()</span></tt>.</li>
+</ul>
+</div></blockquote>
+</div>
+<div class="section" id="performance">
+<h2>Performance<a class="headerlink" href="#performance" title="Permalink to this headline">¶</a></h2>
+<p><a class="reference internal" href="#django_auth_ldap.backend.LDAPBackend" title="django_auth_ldap.backend.LDAPBackend"><tt class="xref py py-class docutils literal"><span class="pre">LDAPBackend</span></tt></a> 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.</p>
+<blockquote>
+<div><ol class="arabic simple">
+<li><strong>Cache groups</strong>. If <a class="reference internal" href="#auth-ldap-find-group-perms"><em>AUTH_LDAP_FIND_GROUP_PERMS</em></a> is <tt class="docutils literal"><span class="pre">True</span></tt>, the
+default behavior is to reload a user&#8217;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
+<a class="reference internal" href="#auth-ldap-cache-groups"><em>AUTH_LDAP_CACHE_GROUPS</em></a> to <tt class="docutils literal"><span class="pre">True</span></tt> to remove most of this traffic.
+Alternatively, you might consider using <a class="reference internal" href="#auth-ldap-mirror-groups"><em>AUTH_LDAP_MIRROR_GROUPS</em></a>
+and relying on <tt class="xref py py-class docutils literal"><span class="pre">ModelBackend</span></tt> to
+supply group permissions.</li>
+<li><strong>Don&#8217;t access user.ldap_user.*</strong>. These properties are only cached
+on a per-request basis. If you can propagate LDAP attributes to a
+<tt class="xref py py-class docutils literal"><span class="pre">User</span></tt> or profile object, they will
+only be updated at login. <tt class="docutils literal"><span class="pre">user.ldap_user.attrs</span></tt> triggers an LDAP
+connection for every request in which it&#8217;s accessed. If you&#8217;re not using
+<a class="reference internal" href="#auth-ldap-user-dn-template"><em>AUTH_LDAP_USER_DN_TEMPLATE</em></a>, then accessing <tt class="docutils literal"><span class="pre">user.ldap_user.dn</span></tt>
+will also trigger an LDAP connection.</li>
+<li><strong>Use simpler group types</strong>. Some grouping mechanisms are more expensive
+than others. This will often be outside your control, but it&#8217;s important
+to note that the extra functionality of more complex group types like
+<a class="reference internal" href="#django_auth_ldap.config.NestedGroupOfNamesType" title="django_auth_ldap.config.NestedGroupOfNamesType"><tt class="xref py py-class docutils literal"><span class="pre">NestedGroupOfNamesType</span></tt></a> is not free and
+will generally require a greater number and complexity of LDAP queries.</li>
+<li><strong>Use direct binding</strong>. Binding with
+<a class="reference internal" href="#auth-ldap-user-dn-template"><em>AUTH_LDAP_USER_DN_TEMPLATE</em></a> is a little bit more efficient than
+relying on <a class="reference internal" href="#auth-ldap-user-search"><em>AUTH_LDAP_USER_SEARCH</em></a>. Specifically, it saves two LDAP
+operations (one bind and one search) per login.</li>
+</ol>
+</div></blockquote>
+</div>
+<div class="section" id="example-configuration">
+<h2>Example configuration<a class="headerlink" href="#example-configuration" title="Permalink to this headline">¶</a></h2>
+<p>Here is a complete example configuration from <tt class="file docutils literal"><span class="pre">settings.py</span></tt> that exercises
+nearly all of the features. In this example, we&#8217;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.</p>
+<div class="highlight-python"><div class="highlight"><pre><span class="kn">import</span> <span class="nn">ldap</span>
+<span class="kn">from</span> <span class="nn">django_auth_ldap.config</span> <span class="kn">import</span> <span class="n">LDAPSearch</span><span class="p">,</span> <span class="n">GroupOfNamesType</span>
+
+
+<span class="c"># Baseline configuration.</span>
+<span class="n">AUTH_LDAP_SERVER_URI</span> <span class="o">=</span> <span class="s">&quot;ldap://ldap.example.com&quot;</span>
+
+<span class="n">AUTH_LDAP_BIND_DN</span> <span class="o">=</span> <span class="s">&quot;cn=django-agent,dc=example,dc=com&quot;</span>
+<span class="n">AUTH_LDAP_BIND_PASSWORD</span> <span class="o">=</span> <span class="s">&quot;phlebotinum&quot;</span>
+<span class="n">AUTH_LDAP_USER_SEARCH</span> <span class="o">=</span> <span class="n">LDAPSearch</span><span class="p">(</span><span class="s">&quot;ou=users,dc=example,dc=com&quot;</span><span class="p">,</span>
+    <span class="n">ldap</span><span class="o">.</span><span class="n">SCOPE_SUBTREE</span><span class="p">,</span> <span class="s">&quot;(uid=</span><span class="si">%(user)s</span><span class="s">)&quot;</span><span class="p">)</span>
+<span class="c"># or perhaps:</span>
+<span class="c"># AUTH_LDAP_USER_DN_TEMPLATE = &quot;uid=%(user)s,ou=users,dc=example,dc=com&quot;</span>
+
+<span class="c"># Set up the basic group parameters.</span>
+<span class="n">AUTH_LDAP_GROUP_SEARCH</span> <span class="o">=</span> <span class="n">LDAPSearch</span><span class="p">(</span><span class="s">&quot;ou=django,ou=groups,dc=example,dc=com&quot;</span><span class="p">,</span>
+    <span class="n">ldap</span><span class="o">.</span><span class="n">SCOPE_SUBTREE</span><span class="p">,</span> <span class="s">&quot;(objectClass=groupOfNames)&quot;</span>
+<span class="p">)</span>
+<span class="n">AUTH_LDAP_GROUP_TYPE</span> <span class="o">=</span> <span class="n">GroupOfNamesType</span><span class="p">(</span><span class="n">name_attr</span><span class="o">=</span><span class="s">&quot;cn&quot;</span><span class="p">)</span>
+
+<span class="c"># Simple group restrictions</span>
+<span class="n">AUTH_LDAP_REQUIRE_GROUP</span> <span class="o">=</span> <span class="s">&quot;cn=enabled,ou=django,ou=groups,dc=example,dc=com&quot;</span>
+<span class="n">AUTH_LDAP_DENY_GROUP</span> <span class="o">=</span> <span class="s">&quot;cn=disabled,ou=django,ou=groups,dc=example,dc=com&quot;</span>
+
+<span class="c"># Populate the Django user from the LDAP directory.</span>
+<span class="n">AUTH_LDAP_USER_ATTR_MAP</span> <span class="o">=</span> <span class="p">{</span>
+    <span class="s">&quot;first_name&quot;</span><span class="p">:</span> <span class="s">&quot;givenName&quot;</span><span class="p">,</span>
+    <span class="s">&quot;last_name&quot;</span><span class="p">:</span> <span class="s">&quot;sn&quot;</span><span class="p">,</span>
+    <span class="s">&quot;email&quot;</span><span class="p">:</span> <span class="s">&quot;mail&quot;</span>
+<span class="p">}</span>
+
+<span class="n">AUTH_LDAP_PROFILE_ATTR_MAP</span> <span class="o">=</span> <span class="p">{</span>
+    <span class="s">&quot;employee_number&quot;</span><span class="p">:</span> <span class="s">&quot;employeeNumber&quot;</span>
+<span class="p">}</span>
+
+<span class="n">AUTH_LDAP_USER_FLAGS_BY_GROUP</span> <span class="o">=</span> <span class="p">{</span>
+    <span class="s">&quot;is_active&quot;</span><span class="p">:</span> <span class="s">&quot;cn=active,ou=django,ou=groups,dc=example,dc=com&quot;</span><span class="p">,</span>
+    <span class="s">&quot;is_staff&quot;</span><span class="p">:</span> <span class="s">&quot;cn=staff,ou=django,ou=groups,dc=example,dc=com&quot;</span><span class="p">,</span>
+    <span class="s">&quot;is_superuser&quot;</span><span class="p">:</span> <span class="s">&quot;cn=superuser,ou=django,ou=groups,dc=example,dc=com&quot;</span>
+<span class="p">}</span>
+
+<span class="n">AUTH_LDAP_PROFILE_FLAGS_BY_GROUP</span> <span class="o">=</span> <span class="p">{</span>
+    <span class="s">&quot;is_awesome&quot;</span><span class="p">:</span> <span class="s">&quot;cn=awesome,ou=django,ou=groups,dc=example,dc=com&quot;</span><span class="p">,</span>
+<span class="p">}</span>
+
+<span class="c"># This is the default, but I like to be explicit.</span>
+<span class="n">AUTH_LDAP_ALWAYS_UPDATE_USER</span> <span class="o">=</span> <span class="bp">True</span>
+
+<span class="c"># Use LDAP group membership to calculate group permissions.</span>
+<span class="n">AUTH_LDAP_FIND_GROUP_PERMS</span> <span class="o">=</span> <span class="bp">True</span>
+
+<span class="c"># Cache group memberships for an hour to minimize LDAP traffic</span>
+<span class="n">AUTH_LDAP_CACHE_GROUPS</span> <span class="o">=</span> <span class="bp">True</span>
+<span class="n">AUTH_LDAP_GROUP_CACHE_TIMEOUT</span> <span class="o">=</span> <span class="mi">3600</span>
+
+
+<span class="c"># Keep ModelBackend around for per-user permissions and maybe a local</span>
+<span class="c"># superuser.</span>
+<span class="n">AUTHENTICATION_BACKENDS</span> <span class="o">=</span> <span class="p">(</span>
+    <span class="s">&#39;django_auth_ldap.backend.LDAPBackend&#39;</span><span class="p">,</span>
+    <span class="s">&#39;django.contrib.auth.backends.ModelBackend&#39;</span><span class="p">,</span>
+<span class="p">)</span>
+</pre></div>
+</div>
+</div>
+<div class="section" id="reference">
+<h2>Reference<a class="headerlink" href="#reference" title="Permalink to this headline">¶</a></h2>
+<div class="section" id="settings">
+<h3>Settings<a class="headerlink" href="#settings" title="Permalink to this headline">¶</a></h3>
+<div class="section" id="auth-ldap-always-update-user">
+<span id="id1"></span><h4>AUTH_LDAP_ALWAYS_UPDATE_USER<a class="headerlink" href="#auth-ldap-always-update-user" title="Permalink to this headline">¶</a></h4>
+<p>Default: <tt class="docutils literal"><span class="pre">True</span></tt></p>
+<p>If <tt class="docutils literal"><span class="pre">True</span></tt>, the fields of a <tt class="xref py py-class docutils literal"><span class="pre">User</span></tt> object
+will be updated with the latest values from the LDAP directory every time the
+user logs in. Otherwise the <tt class="xref py py-class docutils literal"><span class="pre">User</span></tt> object
+will only be populated when it is automatically created.</p>
+</div>
+<div class="section" id="auth-ldap-authorize-all-users">
+<span id="id2"></span><h4>AUTH_LDAP_AUTHORIZE_ALL_USERS<a class="headerlink" href="#auth-ldap-authorize-all-users" title="Permalink to this headline">¶</a></h4>
+<p>Default: <tt class="docutils literal"><span class="pre">False</span></tt></p>
+<p>If <tt class="docutils literal"><span class="pre">True</span></tt>, <a class="reference internal" href="#django_auth_ldap.backend.LDAPBackend" title="django_auth_ldap.backend.LDAPBackend"><tt class="xref py py-class docutils literal"><span class="pre">LDAPBackend</span></tt></a> will be able furnish
+permissions for any Django user, regardless of which backend authenticated it.</p>
+</div>
+<div class="section" id="auth-ldap-bind-as-authenticating-user">
+<span id="id3"></span><h4>AUTH_LDAP_BIND_AS_AUTHENTICATING_USER<a class="headerlink" href="#auth-ldap-bind-as-authenticating-user" title="Permalink to this headline">¶</a></h4>
+<p>Default: <tt class="docutils literal"><span class="pre">False</span></tt></p>
+<p>If <tt class="docutils literal"><span class="pre">True</span></tt>, authentication will leave the LDAP connection bound as the
+authenticating user, rather than forcing it to re-bind with the default
+credentials after authentication succeeds. This may be desirable if you do not
+have global credentials that are able to access the user&#8217;s attributes.
+django-auth-ldap never stores the user&#8217;s password, so this only applies to
+requests where the user is authenticated. Thus, the downside to this setting is
+that LDAP results may vary based on whether the user was authenticated earlier
+in the Django view, which could be surprising to code not directly concerned
+with authentication.</p>
+</div>
+<div class="section" id="auth-ldap-bind-dn">
+<span id="id4"></span><h4>AUTH_LDAP_BIND_DN<a class="headerlink" href="#auth-ldap-bind-dn" title="Permalink to this headline">¶</a></h4>
+<p>Default: <tt class="docutils literal"><span class="pre">''</span></tt> (Empty string)</p>
+<p>The distinguished name to use when binding to the LDAP server (with
+<a class="reference internal" href="#auth-ldap-bind-password"><em>AUTH_LDAP_BIND_PASSWORD</em></a>). Use the empty string (the default) for an
+anonymous bind. To authenticate a user, we will bind with that user&#8217;s DN and
+password, but for all other LDAP operations, we will be bound as the DN in this
+setting. For example, if <a class="reference internal" href="#auth-ldap-user-dn-template"><em>AUTH_LDAP_USER_DN_TEMPLATE</em></a> is not set, we&#8217;ll use
+this to search for the user. If <a class="reference internal" href="#auth-ldap-find-group-perms"><em>AUTH_LDAP_FIND_GROUP_PERMS</em></a> is <tt class="docutils literal"><span class="pre">True</span></tt>,
+we&#8217;ll also use it to determine group membership.</p>
+</div>
+<div class="section" id="auth-ldap-bind-password">
+<span id="id5"></span><h4>AUTH_LDAP_BIND_PASSWORD<a class="headerlink" href="#auth-ldap-bind-password" title="Permalink to this headline">¶</a></h4>
+<p>Default: <tt class="docutils literal"><span class="pre">''</span></tt> (Empty string)</p>
+<p>The password to use with <a class="reference internal" href="#auth-ldap-bind-dn"><em>AUTH_LDAP_BIND_DN</em></a>.</p>
+</div>
+<div class="section" id="auth-ldap-cache-groups">
+<span id="id6"></span><h4>AUTH_LDAP_CACHE_GROUPS<a class="headerlink" href="#auth-ldap-cache-groups" title="Permalink to this headline">¶</a></h4>
+<p>Default: <tt class="docutils literal"><span class="pre">False</span></tt></p>
+<p>If <tt class="docutils literal"><span class="pre">True</span></tt>, LDAP group membership will be cached using Django&#8217;s cache
+framework. The cache timeout can be customized with
+<a class="reference internal" href="#auth-ldap-group-cache-timeout"><em>AUTH_LDAP_GROUP_CACHE_TIMEOUT</em></a>.</p>
+</div>
+<div class="section" id="auth-ldap-connection-options">
+<span id="id7"></span><h4>AUTH_LDAP_CONNECTION_OPTIONS<a class="headerlink" href="#auth-ldap-connection-options" title="Permalink to this headline">¶</a></h4>
+<p>Default: <tt class="docutils literal"><span class="pre">{}</span></tt></p>
+<p>A dictionary of options to pass to each connection to the LDAP server via
+<tt class="docutils literal"><span class="pre">LDAPObject.set_option()</span></tt>. Keys are <tt class="docutils literal"><span class="pre">ldap.OPT_*</span></tt> constants.</p>
+</div>
+<div class="section" id="auth-ldap-deny-group">
+<span id="id8"></span><h4>AUTH_LDAP_DENY_GROUP<a class="headerlink" href="#auth-ldap-deny-group" title="Permalink to this headline">¶</a></h4>
+<p>Default: <tt class="docutils literal"><span class="pre">None</span></tt></p>
+<p>The distinguished name of a group; authentication will fail for any user
+that belongs to this group.</p>
+</div>
+<div class="section" id="auth-ldap-find-group-perms">
+<span id="id9"></span><h4>AUTH_LDAP_FIND_GROUP_PERMS<a class="headerlink" href="#auth-ldap-find-group-perms" title="Permalink to this headline">¶</a></h4>
+<p>Default: <tt class="docutils literal"><span class="pre">False</span></tt></p>
+<p>If <tt class="docutils literal"><span class="pre">True</span></tt>, <a class="reference internal" href="#django_auth_ldap.backend.LDAPBackend" title="django_auth_ldap.backend.LDAPBackend"><tt class="xref py py-class docutils literal"><span class="pre">LDAPBackend</span></tt></a> will furnish group
+permissions based on the LDAP groups the authenticated user belongs to.
+<a class="reference internal" href="#auth-ldap-group-search"><em>AUTH_LDAP_GROUP_SEARCH</em></a> and <a class="reference internal" href="#auth-ldap-group-type"><em>AUTH_LDAP_GROUP_TYPE</em></a> must also be set.</p>
+</div>
+<div class="section" id="auth-ldap-global-options">
+<span id="id10"></span><h4>AUTH_LDAP_GLOBAL_OPTIONS<a class="headerlink" href="#auth-ldap-global-options" title="Permalink to this headline">¶</a></h4>
+<p>Default: <tt class="docutils literal"><span class="pre">{}</span></tt></p>
+<p>A dictionary of options to pass to <tt class="docutils literal"><span class="pre">ldap.set_option()</span></tt>. Keys are
+<tt class="docutils literal"><span class="pre">ldap.OPT_*</span></tt> constants.</p>
+</div>
+<div class="section" id="auth-ldap-group-cache-timeout">
+<span id="id11"></span><h4>AUTH_LDAP_GROUP_CACHE_TIMEOUT<a class="headerlink" href="#auth-ldap-group-cache-timeout" title="Permalink to this headline">¶</a></h4>
+<p>Default: <tt class="docutils literal"><span class="pre">None</span></tt></p>
+<p>If <a class="reference internal" href="#auth-ldap-cache-groups"><em>AUTH_LDAP_CACHE_GROUPS</em></a> is <tt class="docutils literal"><span class="pre">True</span></tt>, this is the cache timeout for
+group memberships. If <tt class="docutils literal"><span class="pre">None</span></tt>, the global cache timeout will be used.</p>
+</div>
+<div class="section" id="auth-ldap-group-search">
+<span id="id12"></span><h4>AUTH_LDAP_GROUP_SEARCH<a class="headerlink" href="#auth-ldap-group-search" title="Permalink to this headline">¶</a></h4>
+<p>Default: <tt class="docutils literal"><span class="pre">None</span></tt></p>
+<p>An <a class="reference internal" href="#django_auth_ldap.config.LDAPSearch" title="django_auth_ldap.config.LDAPSearch"><tt class="xref py py-class docutils literal"><span class="pre">LDAPSearch</span></tt></a> object that finds all LDAP
+groups that users might belong to. If your configuration makes any references to
+LDAP groups, this and <a class="reference internal" href="#auth-ldap-group-type"><em>AUTH_LDAP_GROUP_TYPE</em></a> must be set.</p>
+</div>
+<div class="section" id="auth-ldap-group-type">
+<span id="id13"></span><h4>AUTH_LDAP_GROUP_TYPE<a class="headerlink" href="#auth-ldap-group-type" title="Permalink to this headline">¶</a></h4>
+<p>Default: <tt class="docutils literal"><span class="pre">None</span></tt></p>
+<p>An <a class="reference internal" href="#django_auth_ldap.config.LDAPGroupType" title="django_auth_ldap.config.LDAPGroupType"><tt class="xref py py-class docutils literal"><span class="pre">LDAPGroupType</span></tt></a> instance describing the type
+of group returned by <a class="reference internal" href="#auth-ldap-group-search"><em>AUTH_LDAP_GROUP_SEARCH</em></a>.</p>
+</div>
+<div class="section" id="auth-ldap-mirror-groups">
+<span id="id14"></span><h4>AUTH_LDAP_MIRROR_GROUPS<a class="headerlink" href="#auth-ldap-mirror-groups" title="Permalink to this headline">¶</a></h4>
+<p>Default: <tt class="docutils literal"><span class="pre">False</span></tt></p>
+<p>If <tt class="docutils literal"><span class="pre">True</span></tt>, <a class="reference internal" href="#django_auth_ldap.backend.LDAPBackend" title="django_auth_ldap.backend.LDAPBackend"><tt class="xref py py-class docutils literal"><span class="pre">LDAPBackend</span></tt></a> will mirror a user&#8217;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.</p>
+</div>
+<div class="section" id="auth-ldap-profile-attr-map">
+<span id="id15"></span><h4>AUTH_LDAP_PROFILE_ATTR_MAP<a class="headerlink" href="#auth-ldap-profile-attr-map" title="Permalink to this headline">¶</a></h4>
+<p>Default: <tt class="docutils literal"><span class="pre">{}</span></tt></p>
+<p>A mapping from user profile field names to LDAP attribute names. A user&#8217;s
+profile will be populated from his LDAP attributes at login.</p>
+</div>
+<div class="section" id="auth-ldap-profile-flags-by-group">
+<span id="id16"></span><h4>AUTH_LDAP_PROFILE_FLAGS_BY_GROUP<a class="headerlink" href="#auth-ldap-profile-flags-by-group" title="Permalink to this headline">¶</a></h4>
+<p>Default: <tt class="docutils literal"><span class="pre">{}</span></tt></p>
+<p>A mapping from boolean profile field names to distinguished names of LDAP
+groups. The corresponding field in a user&#8217;s profile is set to <tt class="docutils literal"><span class="pre">True</span></tt> or
+<tt class="docutils literal"><span class="pre">False</span></tt> according to whether the user is a member of the group.</p>
+</div>
+<div class="section" id="auth-ldap-require-group">
+<span id="id17"></span><h4>AUTH_LDAP_REQUIRE_GROUP<a class="headerlink" href="#auth-ldap-require-group" title="Permalink to this headline">¶</a></h4>
+<p>Default: <tt class="docutils literal"><span class="pre">None</span></tt></p>
+<p>The distinguished name of a group; authentication will fail for any user that
+does not belong to this group.</p>
+</div>
+<div class="section" id="auth-ldap-server-uri">
+<span id="id18"></span><h4>AUTH_LDAP_SERVER_URI<a class="headerlink" href="#auth-ldap-server-uri" title="Permalink to this headline">¶</a></h4>
+<p>Default: <tt class="docutils literal"><span class="pre">ldap://localhost</span></tt></p>
+<p>The URI of the LDAP server. This can be any URI that is supported by your
+underlying LDAP libraries.</p>
+</div>
+<div class="section" id="auth-ldap-start-tls">
+<span id="id19"></span><h4>AUTH_LDAP_START_TLS<a class="headerlink" href="#auth-ldap-start-tls" title="Permalink to this headline">¶</a></h4>
+<p>Default: <tt class="docutils literal"><span class="pre">False</span></tt></p>
+<p>If <tt class="docutils literal"><span class="pre">True</span></tt>, 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 <a class="reference internal" href="#auth-ldap-global-options"><em>AUTH_LDAP_GLOBAL_OPTIONS</em></a> that affect the
+TLS connection. For example, <tt class="docutils literal"><span class="pre">ldap.OPT_X_TLS_REQUIRE_CERT</span></tt> can be set to
+<tt class="docutils literal"><span class="pre">ldap.OPT_X_TLS_NEVER</span></tt> to disable certificate verification, perhaps to allow
+self-signed certificates.</p>
+</div>
+<div class="section" id="auth-ldap-user-attr-map">
+<span id="id20"></span><h4>AUTH_LDAP_USER_ATTR_MAP<a class="headerlink" href="#auth-ldap-user-attr-map" title="Permalink to this headline">¶</a></h4>
+<p>Default: <tt class="docutils literal"><span class="pre">{}</span></tt></p>
+<p>A mapping from <tt class="xref py py-class docutils literal"><span class="pre">User</span></tt> field names to LDAP
+attribute names. A users&#8217;s <tt class="xref py py-class docutils literal"><span class="pre">User</span></tt> object will
+be populated from his LDAP attributes at login.</p>
+</div>
+<div class="section" id="auth-ldap-user-dn-template">
+<span id="id21"></span><h4>AUTH_LDAP_USER_DN_TEMPLATE<a class="headerlink" href="#auth-ldap-user-dn-template" title="Permalink to this headline">¶</a></h4>
+<p>Default: <tt class="docutils literal"><span class="pre">None</span></tt></p>
+<p>A string template that describes any user&#8217;s distinguished name based on the
+username. This must contain the placeholder <tt class="docutils literal"><span class="pre">%(user)s</span></tt>.</p>
+</div>
+<div class="section" id="auth-ldap-user-flags-by-group">
+<span id="id22"></span><h4>AUTH_LDAP_USER_FLAGS_BY_GROUP<a class="headerlink" href="#auth-ldap-user-flags-by-group" title="Permalink to this headline">¶</a></h4>
+<p>Default: <tt class="docutils literal"><span class="pre">{}</span></tt></p>
+<p>A mapping from boolean <tt class="xref py py-class docutils literal"><span class="pre">User</span></tt> field names to
+distinguished names of LDAP groups. The corresponding field is set to <tt class="docutils literal"><span class="pre">True</span></tt>
+or <tt class="docutils literal"><span class="pre">False</span></tt> according to whether the user is a member of the group.</p>
+</div>
+<div class="section" id="auth-ldap-user-search">
+<span id="id23"></span><h4>AUTH_LDAP_USER_SEARCH<a class="headerlink" href="#auth-ldap-user-search" title="Permalink to this headline">¶</a></h4>
+<p>Default: <tt class="docutils literal"><span class="pre">None</span></tt></p>
+<p>An <a class="reference internal" href="#django_auth_ldap.config.LDAPSearch" title="django_auth_ldap.config.LDAPSearch"><tt class="xref py py-class docutils literal"><span class="pre">LDAPSearch</span></tt></a> object that will locate a user
+in the directory. The filter parameter should contain the placeholder
+<tt class="docutils literal"><span class="pre">%(user)s</span></tt> for the username. It must return exactly one result for
+authentication to succeed.</p>
+</div>
+</div>
+<div class="section" id="module-django_auth_ldap">
+<span id="module-properties"></span><h3>Module Properties<a class="headerlink" href="#module-django_auth_ldap" title="Permalink to this headline">¶</a></h3>
+<dl class="data">
+<dt id="django_auth_ldap.version">
+<tt class="descclassname">django_auth_ldap.</tt><tt class="descname">version</tt><a class="headerlink" href="#django_auth_ldap.version" title="Permalink to this definition">¶</a></dt>
+<dd><p>The library&#8217;s current version number as a 3-tuple.</p>
+</dd></dl>
+
+<dl class="data">
+<dt id="django_auth_ldap.version_string">
+<tt class="descclassname">django_auth_ldap.</tt><tt class="descname">version_string</tt><a class="headerlink" href="#django_auth_ldap.version_string" title="Permalink to this definition">¶</a></dt>
+<dd><p>The library&#8217;s current version number as a string.</p>
+</dd></dl>
+
+</div>
+<div class="section" id="module-django_auth_ldap.config">
+<span id="configuration"></span><h3>Configuration<a class="headerlink" href="#module-django_auth_ldap.config" title="Permalink to this headline">¶</a></h3>
+<dl class="class">
+<dt id="django_auth_ldap.config.LDAPSearch">
+<em class="property">class </em><tt class="descclassname">django_auth_ldap.config.</tt><tt class="descname">LDAPSearch</tt><a class="headerlink" href="#django_auth_ldap.config.LDAPSearch" title="Permalink to this definition">¶</a></dt>
+<dd><dl class="method">
+<dt id="django_auth_ldap.config.LDAPSearch.__init__">
+<tt class="descname">__init__</tt><big>(</big><em>base_dn</em>, <em>scope</em>, <em>filterstr='(objectClass=*)'</em><big>)</big><a class="headerlink" href="#django_auth_ldap.config.LDAPSearch.__init__" title="Permalink to this definition">¶</a></dt>
+<dd><ul class="simple">
+<li><tt class="docutils literal"><span class="pre">base_dn</span></tt>: The distinguished name of the search base.</li>
+<li><tt class="docutils literal"><span class="pre">scope</span></tt>: One of <tt class="docutils literal"><span class="pre">ldap.SCOPE_*</span></tt>.</li>
+<li><tt class="docutils literal"><span class="pre">filterstr</span></tt>: An optional filter string (e.g. &#8216;(objectClass=person)&#8217;).
+In order to be valid, <tt class="docutils literal"><span class="pre">filterstr</span></tt> must be enclosed in parentheses.</li>
+</ul>
+</dd></dl>
+
+</dd></dl>
+
+<dl class="class">
+<dt id="django_auth_ldap.config.LDAPGroupType">
+<em class="property">class </em><tt class="descclassname">django_auth_ldap.config.</tt><tt class="descname">LDAPGroupType</tt><a class="headerlink" href="#django_auth_ldap.config.LDAPGroupType" title="Permalink to this definition">¶</a></dt>
+<dd><p>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.</p>
+<dl class="method">
+<dt id="django_auth_ldap.config.LDAPGroupType.__init__">
+<tt class="descname">__init__</tt><big>(</big><em>name_attr='cn'</em><big>)</big><a class="headerlink" href="#django_auth_ldap.config.LDAPGroupType.__init__" title="Permalink to this definition">¶</a></dt>
+<dd><p>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 <tt class="docutils literal"><span class="pre">name_attr</span></tt>.</p>
+</dd></dl>
+
+</dd></dl>
+
+<dl class="class">
+<dt id="django_auth_ldap.config.PosixGroupType">
+<em class="property">class </em><tt class="descclassname">django_auth_ldap.config.</tt><tt class="descname">PosixGroupType</tt><a class="headerlink" href="#django_auth_ldap.config.PosixGroupType" title="Permalink to this definition">¶</a></dt>
+<dd><p>A concrete subclass of <a class="reference internal" href="#django_auth_ldap.config.LDAPGroupType" title="django_auth_ldap.config.LDAPGroupType"><tt class="xref py py-class docutils literal"><span class="pre">LDAPGroupType</span></tt></a> that
+handles the <tt class="docutils literal"><span class="pre">posixGroup</span></tt> object class. This checks for both primary group
+and group membership.</p>
+<dl class="method">
+<dt id="django_auth_ldap.config.PosixGroupType.__init__">
+<tt class="descname">__init__</tt><big>(</big><em>name_attr='cn'</em><big>)</big><a class="headerlink" href="#django_auth_ldap.config.PosixGroupType.__init__" title="Permalink to this definition">¶</a></dt>
+<dd></dd></dl>
+
+</dd></dl>
+
+<dl class="class">
+<dt id="django_auth_ldap.config.MemberDNGroupType">
+<em class="property">class </em><tt class="descclassname">django_auth_ldap.config.</tt><tt class="descname">MemberDNGroupType</tt><a class="headerlink" href="#django_auth_ldap.config.MemberDNGroupType" title="Permalink to this definition">¶</a></dt>
+<dd><p>A concrete subclass of
+<a class="reference internal" href="#django_auth_ldap.config.LDAPGroupType" title="django_auth_ldap.config.LDAPGroupType"><tt class="xref py py-class docutils literal"><span class="pre">LDAPGroupType</span></tt></a> that handles grouping
+mechanisms wherein the group object contains a list of its member DNs.</p>
+<dl class="method">
+<dt id="django_auth_ldap.config.MemberDNGroupType.__init__">
+<tt class="descname">__init__</tt><big>(</big><em>member_attr</em>, <em>name_attr='cn'</em><big>)</big><a class="headerlink" href="#django_auth_ldap.config.MemberDNGroupType.__init__" title="Permalink to this definition">¶</a></dt>
+<dd><ul class="simple">
+<li><tt class="docutils literal"><span class="pre">member_attr</span></tt>: The attribute on the group object that contains a
+list of member DNs. &#8216;member&#8217; and &#8216;uniqueMember&#8217; are common examples.</li>
+</ul>
+</dd></dl>
+
+</dd></dl>
+
+<dl class="class">
+<dt id="django_auth_ldap.config.NestedMemberDNGroupType">
+<em class="property">class </em><tt class="descclassname">django_auth_ldap.config.</tt><tt class="descname">NestedMemberDNGroupType</tt><a class="headerlink" href="#django_auth_ldap.config.NestedMemberDNGroupType" title="Permalink to this definition">¶</a></dt>
+<dd><p>Similar to <a class="reference internal" href="#django_auth_ldap.config.MemberDNGroupType" title="django_auth_ldap.config.MemberDNGroupType"><tt class="xref py py-class docutils literal"><span class="pre">MemberDNGroupType</span></tt></a>, except this
+allows groups to contain other groups as members. Group hierarchies will be
+traversed to determine membership.</p>
+<dl class="method">
+<dt id="django_auth_ldap.config.NestedMemberDNGroupType.__init__">
+<tt class="descname">__init__</tt><big>(</big><em>member_attr</em>, <em>name_attr='cn'</em><big>)</big><a class="headerlink" href="#django_auth_ldap.config.NestedMemberDNGroupType.__init__" title="Permalink to this definition">¶</a></dt>
+<dd><p>As above.</p>
+</dd></dl>
+
+</dd></dl>
+
+<dl class="class">
+<dt id="django_auth_ldap.config.GroupOfNamesType">
+<em class="property">class </em><tt class="descclassname">django_auth_ldap.config.</tt><tt class="descname">GroupOfNamesType</tt><a class="headerlink" href="#django_auth_ldap.config.GroupOfNamesType" title="Permalink to this definition">¶</a></dt>
+<dd><p>A concrete subclass of <a class="reference internal" href="#django_auth_ldap.config.MemberDNGroupType" title="django_auth_ldap.config.MemberDNGroupType"><tt class="xref py py-class docutils literal"><span class="pre">MemberDNGroupType</span></tt></a>
+that handles the <tt class="docutils literal"><span class="pre">groupOfNames</span></tt> object class. Equivalent to
+<tt class="docutils literal"><span class="pre">MemberDNGroupType('member')</span></tt>.</p>
+<dl class="method">
+<dt id="django_auth_ldap.config.GroupOfNamesType.__init__">
+<tt class="descname">__init__</tt><big>(</big><em>name_attr='cn'</em><big>)</big><a class="headerlink" href="#django_auth_ldap.config.GroupOfNamesType.__init__" title="Permalink to this definition">¶</a></dt>
+<dd></dd></dl>
+
+</dd></dl>
+
+<dl class="class">
+<dt id="django_auth_ldap.config.NestedGroupOfNamesType">
+<em class="property">class </em><tt class="descclassname">django_auth_ldap.config.</tt><tt class="descname">NestedGroupOfNamesType</tt><a class="headerlink" href="#django_auth_ldap.config.NestedGroupOfNamesType" title="Permalink to this definition">¶</a></dt>
+<dd><p>A concrete subclass of
+<a class="reference internal" href="#django_auth_ldap.config.NestedMemberDNGroupType" title="django_auth_ldap.config.NestedMemberDNGroupType"><tt class="xref py py-class docutils literal"><span class="pre">NestedMemberDNGroupType</span></tt></a> that handles the
+<tt class="docutils literal"><span class="pre">groupOfNames</span></tt> object class. Equivalent to
+<tt class="docutils literal"><span class="pre">NestedMemberDNGroupType('member')</span></tt>.</p>
+<dl class="method">
+<dt id="django_auth_ldap.config.NestedGroupOfNamesType.__init__">
+<tt class="descname">__init__</tt><big>(</big><em>name_attr='cn'</em><big>)</big><a class="headerlink" href="#django_auth_ldap.config.NestedGroupOfNamesType.__init__" title="Permalink to this definition">¶</a></dt>
+<dd></dd></dl>
+
+</dd></dl>
+
+<dl class="class">
+<dt id="django_auth_ldap.config.GroupOfUniqueNamesType">
+<em class="property">class </em><tt class="descclassname">django_auth_ldap.config.</tt><tt class="descname">GroupOfUniqueNamesType</tt><a class="headerlink" href="#django_auth_ldap.config.GroupOfUniqueNamesType" title="Permalink to this definition">¶</a></dt>
+<dd><p>A concrete subclass of <a class="reference internal" href="#django_auth_ldap.config.MemberDNGroupType" title="django_auth_ldap.config.MemberDNGroupType"><tt class="xref py py-class docutils literal"><span class="pre">MemberDNGroupType</span></tt></a>
+that handles the <tt class="docutils literal"><span class="pre">groupOfUniqueNames</span></tt> object class. Equivalent to
+<tt class="docutils literal"><span class="pre">MemberDNGroupType('uniqueMember')</span></tt>.</p>
+<dl class="method">
+<dt id="django_auth_ldap.config.GroupOfUniqueNamesType.__init__">
+<tt class="descname">__init__</tt><big>(</big><em>name_attr='cn'</em><big>)</big><a class="headerlink" href="#django_auth_ldap.config.GroupOfUniqueNamesType.__init__" title="Permalink to this definition">¶</a></dt>
+<dd></dd></dl>
+
+</dd></dl>
+
+<dl class="class">
+<dt id="django_auth_ldap.config.NestedGroupOfUniqueNamesType">
+<em class="property">class </em><tt class="descclassname">django_auth_ldap.config.</tt><tt class="descname">NestedGroupOfUniqueNamesType</tt><a class="headerlink" href="#django_auth_ldap.config.NestedGroupOfUniqueNamesType" title="Permalink to this definition">¶</a></dt>
+<dd><p>A concrete subclass of
+<a class="reference internal" href="#django_auth_ldap.config.NestedMemberDNGroupType" title="django_auth_ldap.config.NestedMemberDNGroupType"><tt class="xref py py-class docutils literal"><span class="pre">NestedMemberDNGroupType</span></tt></a> that handles the
+<tt class="docutils literal"><span class="pre">groupOfUniqueNames</span></tt> object class. Equivalent to
+<tt class="docutils literal"><span class="pre">NestedMemberDNGroupType('uniqueMember')</span></tt>.</p>
+<dl class="method">
+<dt id="django_auth_ldap.config.NestedGroupOfUniqueNamesType.__init__">
+<tt class="descname">__init__</tt><big>(</big><em>name_attr='cn'</em><big>)</big><a class="headerlink" href="#django_auth_ldap.config.NestedGroupOfUniqueNamesType.__init__" title="Permalink to this definition">¶</a></dt>
+<dd></dd></dl>
+
+</dd></dl>
+
+<dl class="class">
+<dt id="django_auth_ldap.config.ActiveDirectoryGroupType">
+<em class="property">class </em><tt class="descclassname">django_auth_ldap.config.</tt><tt class="descname">ActiveDirectoryGroupType</tt><a class="headerlink" href="#django_auth_ldap.config.ActiveDirectoryGroupType" title="Permalink to this definition">¶</a></dt>
+<dd><p>A concrete subclass of <a class="reference internal" href="#django_auth_ldap.config.MemberDNGroupType" title="django_auth_ldap.config.MemberDNGroupType"><tt class="xref py py-class docutils literal"><span class="pre">MemberDNGroupType</span></tt></a>
+that handles Active Directory groups. Equivalent to
+<tt class="docutils literal"><span class="pre">MemberDNGroupType('member')</span></tt>.</p>
+<dl class="method">
+<dt id="django_auth_ldap.config.ActiveDirectoryGroupType.__init__">
+<tt class="descname">__init__</tt><big>(</big><em>name_attr='cn'</em><big>)</big><a class="headerlink" href="#django_auth_ldap.config.ActiveDirectoryGroupType.__init__" title="Permalink to this definition">¶</a></dt>
+<dd></dd></dl>
+
+</dd></dl>
+
+<dl class="class">
+<dt id="django_auth_ldap.config.NestedActiveDirectoryGroupType">
+<em class="property">class </em><tt class="descclassname">django_auth_ldap.config.</tt><tt class="descname">NestedActiveDirectoryGroupType</tt><a class="headerlink" href="#django_auth_ldap.config.NestedActiveDirectoryGroupType" title="Permalink to this definition">¶</a></dt>
+<dd><p>A concrete subclass of
+<a class="reference internal" href="#django_auth_ldap.config.NestedMemberDNGroupType" title="django_auth_ldap.config.NestedMemberDNGroupType"><tt class="xref py py-class docutils literal"><span class="pre">NestedMemberDNGroupType</span></tt></a> that handles
+Active Directory groups. Equivalent to
+<tt class="docutils literal"><span class="pre">NestedMemberDNGroupType('member')</span></tt>.</p>
+<dl class="method">
+<dt id="django_auth_ldap.config.NestedActiveDirectoryGroupType.__init__">
+<tt class="descname">__init__</tt><big>(</big><em>name_attr='cn'</em><big>)</big><a class="headerlink" href="#django_auth_ldap.config.NestedActiveDirectoryGroupType.__init__" title="Permalink to this definition">¶</a></dt>
+<dd></dd></dl>
+
+</dd></dl>
+
+</div>
+<div class="section" id="module-django_auth_ldap.backend">
+<span id="backend"></span><h3>Backend<a class="headerlink" href="#module-django_auth_ldap.backend" title="Permalink to this headline">¶</a></h3>
+<dl class="data">
+<dt id="django_auth_ldap.backend.populate_user">
+<tt class="descclassname">django_auth_ldap.backend.</tt><tt class="descname">populate_user</tt><a class="headerlink" href="#django_auth_ldap.backend.populate_user" title="Permalink to this definition">¶</a></dt>
+<dd><p>This is a Django signal that is sent when clients should perform additional
+customization of a <tt class="xref py py-class docutils literal"><span class="pre">User</span></tt> object. It is
+sent after a user has been authenticated and the backend has finished
+populating it, and just before it is saved. The client may take this
+opportunity to populate additional model fields, perhaps based on
+<tt class="docutils literal"><span class="pre">ldap_user.attrs</span></tt>. This signal has two keyword arguments: <tt class="docutils literal"><span class="pre">user</span></tt> is the
+<tt class="xref py py-class docutils literal"><span class="pre">User</span></tt> object and <tt class="docutils literal"><span class="pre">ldap_user</span></tt> is the
+same as <tt class="docutils literal"><span class="pre">user.ldap_user</span></tt>. The sender is the
+<a class="reference internal" href="#django_auth_ldap.backend.LDAPBackend" title="django_auth_ldap.backend.LDAPBackend"><tt class="xref py py-class docutils literal"><span class="pre">LDAPBackend</span></tt></a> class.</p>
+</dd></dl>
+
+<dl class="data">
+<dt id="django_auth_ldap.backend.populate_user_profile">
+<tt class="descclassname">django_auth_ldap.backend.</tt><tt class="descname">populate_user_profile</tt><a class="headerlink" href="#django_auth_ldap.backend.populate_user_profile" title="Permalink to this definition">¶</a></dt>
+<dd><p>Like <a class="reference internal" href="#django_auth_ldap.backend.populate_user" title="django_auth_ldap.backend.populate_user"><tt class="xref py py-data docutils literal"><span class="pre">populate_user</span></tt></a>, but sent for the user
+profile object. This will only be sent if the user has an existing profile.
+As with <a class="reference internal" href="#django_auth_ldap.backend.populate_user" title="django_auth_ldap.backend.populate_user"><tt class="xref py py-data docutils literal"><span class="pre">populate_user</span></tt></a>, it is sent after the
+backend has finished setting properties and before the object is saved. This
+signal has two keyword arguments: <tt class="docutils literal"><span class="pre">profile</span></tt> is the user profile object and
+<tt class="docutils literal"><span class="pre">ldap_user</span></tt> is the same as <tt class="docutils literal"><span class="pre">user.ldap_user</span></tt>. The sender is the
+<a class="reference internal" href="#django_auth_ldap.backend.LDAPBackend" title="django_auth_ldap.backend.LDAPBackend"><tt class="xref py py-class docutils literal"><span class="pre">LDAPBackend</span></tt></a> class.</p>
+</dd></dl>
+
+<dl class="class">
+<dt id="django_auth_ldap.backend.LDAPBackend">
+<em class="property">class </em><tt class="descclassname">django_auth_ldap.backend.</tt><tt class="descname">LDAPBackend</tt><a class="headerlink" href="#django_auth_ldap.backend.LDAPBackend" title="Permalink to this definition">¶</a></dt>
+<dd><p><a class="reference internal" href="#django_auth_ldap.backend.LDAPBackend" title="django_auth_ldap.backend.LDAPBackend"><tt class="xref py py-class docutils literal"><span class="pre">LDAPBackend</span></tt></a> has one method that may be
+called directly and several that may be overridden in subclasses.</p>
+<dl class="method">
+<dt id="django_auth_ldap.backend.LDAPBackend.populate_user">
+<tt class="descname">populate_user</tt><big>(</big><em>username</em><big>)</big><a class="headerlink" href="#django_auth_ldap.backend.LDAPBackend.populate_user" title="Permalink to this definition">¶</a></dt>
+<dd><p>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.
+<a class="reference internal" href="#auth-ldap-always-update-user"><em>AUTH_LDAP_ALWAYS_UPDATE_USER</em></a> is ignored (assumed <tt class="docutils literal"><span class="pre">True</span></tt>).</p>
+</dd></dl>
+
+<dl class="method">
+<dt id="django_auth_ldap.backend.LDAPBackend.get_or_create_user">
+<tt class="descname">get_or_create_user</tt><big>(</big><em>self</em>, <em>username</em>, <em>ldap_user</em><big>)</big><a class="headerlink" href="#django_auth_ldap.backend.LDAPBackend.get_or_create_user" title="Permalink to this definition">¶</a></dt>
+<dd><p>Given a username and an LDAP user object, this must return the
+associated Django User object. The <tt class="docutils literal"><span class="pre">username</span></tt> argument has already
+been passed through
+<a class="reference internal" href="#django_auth_ldap.backend.LDAPBackend.ldap_to_django_username" title="django_auth_ldap.backend.LDAPBackend.ldap_to_django_username"><tt class="xref py py-meth docutils literal"><span class="pre">ldap_to_django_username()</span></tt></a>.
+You can get information about the LDAP user via <tt class="docutils literal"><span class="pre">ldap_user.dn</span></tt> and
+<tt class="docutils literal"><span class="pre">ldap_user.attrs</span></tt>. The return value must be the same as
+<tt class="docutils literal"><span class="pre">User.objects.get_or_create()</span></tt>: a (User, created) two-tuple.</p>
+<p>The default implementation calls <tt class="docutils literal"><span class="pre">User.objects.get_or_create()</span></tt>, using
+a case-insensitive query and creating new users with lowercase
+usernames. Subclasses are welcome to associate LDAP users to Django
+users any way they like.</p>
+</dd></dl>
+
+<dl class="method">
+<dt id="django_auth_ldap.backend.LDAPBackend.ldap_to_django_username">
+<tt class="descname">ldap_to_django_username</tt><big>(</big><em>username</em><big>)</big><a class="headerlink" href="#django_auth_ldap.backend.LDAPBackend.ldap_to_django_username" title="Permalink to this definition">¶</a></dt>
+<dd><p>Returns a valid Django username based on the given LDAP username (which
+is what the user enters). By default, <tt class="docutils literal"><span class="pre">username</span></tt> is returned
+unchanged. This can be overriden by subclasses.</p>
+</dd></dl>
+
+<dl class="method">
+<dt id="django_auth_ldap.backend.LDAPBackend.django_to_ldap_username">
+<tt class="descname">django_to_ldap_username</tt><big>(</big><em>username</em><big>)</big><a class="headerlink" href="#django_auth_ldap.backend.LDAPBackend.django_to_ldap_username" title="Permalink to this definition">¶</a></dt>
+<dd><p>The inverse of
+<a class="reference internal" href="#django_auth_ldap.backend.LDAPBackend.ldap_to_django_username" title="django_auth_ldap.backend.LDAPBackend.ldap_to_django_username"><tt class="xref py py-meth docutils literal"><span class="pre">ldap_to_django_username()</span></tt></a>.
+If this is not symmetrical to
+<a class="reference internal" href="#django_auth_ldap.backend.LDAPBackend.ldap_to_django_username" title="django_auth_ldap.backend.LDAPBackend.ldap_to_django_username"><tt class="xref py py-meth docutils literal"><span class="pre">ldap_to_django_username()</span></tt></a>,
+the behavior is undefined.</p>
+</dd></dl>
+
+</dd></dl>
+
+</div>
+</div>
+<div class="section" id="license">
+<h2>License<a class="headerlink" href="#license" title="Permalink to this headline">¶</a></h2>
+<p>Copyright (c) 2009, Peter Sagerson
+All rights reserved.</p>
+<p>Redistribution and use in source and binary forms, with or without modification,
+are permitted provided that the following conditions are met:</p>
+<ul class="simple">
+<li>Redistributions of source code must retain the above copyright notice, this
+list of conditions and the following disclaimer.</li>
+<li>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.</li>
+</ul>
+<p>THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS &#8220;AS IS&#8221; 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.</p>
+</div>
+</div>
+
+
+          </div>
+        </div>
+      </div>
+      <div class="sphinxsidebar">
+        <div class="sphinxsidebarwrapper">
+  <h3><a href="#">Table Of Contents</a></h3>
+  <ul>
+<li><a class="reference internal" href="#">Django authentication using LDAP</a><ul>
+<li><a class="reference internal" href="#configuring-basic-authentication">Configuring basic authentication</a></li>
+<li><a class="reference internal" href="#working-with-groups">Working with groups</a></li>
+<li><a class="reference internal" href="#user-objects">User objects</a></li>
+<li><a class="reference internal" href="#permissions">Permissions</a></li>
+<li><a class="reference internal" href="#logging">Logging</a></li>
+<li><a class="reference internal" href="#more-options">More options</a></li>
+<li><a class="reference internal" href="#performance">Performance</a></li>
+<li><a class="reference internal" href="#example-configuration">Example configuration</a></li>
+<li><a class="reference internal" href="#reference">Reference</a><ul>
+<li><a class="reference internal" href="#settings">Settings</a><ul>
+<li><a class="reference internal" href="#auth-ldap-always-update-user">AUTH_LDAP_ALWAYS_UPDATE_USER</a></li>
+<li><a class="reference internal" href="#auth-ldap-authorize-all-users">AUTH_LDAP_AUTHORIZE_ALL_USERS</a></li>
+<li><a class="reference internal" href="#auth-ldap-bind-as-authenticating-user">AUTH_LDAP_BIND_AS_AUTHENTICATING_USER</a></li>
+<li><a class="reference internal" href="#auth-ldap-bind-dn">AUTH_LDAP_BIND_DN</a></li>
+<li><a class="reference internal" href="#auth-ldap-bind-password">AUTH_LDAP_BIND_PASSWORD</a></li>
+<li><a class="reference internal" href="#auth-ldap-cache-groups">AUTH_LDAP_CACHE_GROUPS</a></li>
+<li><a class="reference internal" href="#auth-ldap-connection-options">AUTH_LDAP_CONNECTION_OPTIONS</a></li>
+<li><a class="reference internal" href="#auth-ldap-deny-group">AUTH_LDAP_DENY_GROUP</a></li>
+<li><a class="reference internal" href="#auth-ldap-find-group-perms">AUTH_LDAP_FIND_GROUP_PERMS</a></li>
+<li><a class="reference internal" href="#auth-ldap-global-options">AUTH_LDAP_GLOBAL_OPTIONS</a></li>
+<li><a class="reference internal" href="#auth-ldap-group-cache-timeout">AUTH_LDAP_GROUP_CACHE_TIMEOUT</a></li>
+<li><a class="reference internal" href="#auth-ldap-group-search">AUTH_LDAP_GROUP_SEARCH</a></li>
+<li><a class="reference internal" href="#auth-ldap-group-type">AUTH_LDAP_GROUP_TYPE</a></li>
+<li><a class="reference internal" href="#auth-ldap-mirror-groups">AUTH_LDAP_MIRROR_GROUPS</a></li>
+<li><a class="reference internal" href="#auth-ldap-profile-attr-map">AUTH_LDAP_PROFILE_ATTR_MAP</a></li>
+<li><a class="reference internal" href="#auth-ldap-profile-flags-by-group">AUTH_LDAP_PROFILE_FLAGS_BY_GROUP</a></li>
+<li><a class="reference internal" href="#auth-ldap-require-group">AUTH_LDAP_REQUIRE_GROUP</a></li>
+<li><a class="reference internal" href="#auth-ldap-server-uri">AUTH_LDAP_SERVER_URI</a></li>
+<li><a class="reference internal" href="#auth-ldap-start-tls">AUTH_LDAP_START_TLS</a></li>
+<li><a class="reference internal" href="#auth-ldap-user-attr-map">AUTH_LDAP_USER_ATTR_MAP</a></li>
+<li><a class="reference internal" href="#auth-ldap-user-dn-template">AUTH_LDAP_USER_DN_TEMPLATE</a></li>
+<li><a class="reference internal" href="#auth-ldap-user-flags-by-group">AUTH_LDAP_USER_FLAGS_BY_GROUP</a></li>
+<li><a class="reference internal" href="#auth-ldap-user-search">AUTH_LDAP_USER_SEARCH</a></li>
+</ul>
+</li>
+<li><a class="reference internal" href="#module-django_auth_ldap">Module Properties</a></li>
+<li><a class="reference internal" href="#module-django_auth_ldap.config">Configuration</a></li>
+<li><a class="reference internal" href="#module-django_auth_ldap.backend">Backend</a></li>
+</ul>
+</li>
+<li><a class="reference internal" href="#license">License</a></li>
+</ul>
+</li>
+</ul>
+
+  <h3>This Page</h3>
+  <ul class="this-page-menu">
+    <li><a href="_sources/index.txt"
+           rel="nofollow">Show Source</a></li>
+  </ul>
+<div id="searchbox" style="display: none">
+  <h3>Quick search</h3>
+    <form class="search" action="search.html" method="get">
+      <input type="text" name="q" />
+      <input type="submit" value="Go" />
+      <input type="hidden" name="check_keywords" value="yes" />
+      <input type="hidden" name="area" value="default" />
+    </form>
+    <p class="searchtip" style="font-size: 90%">
+    Enter search terms or a module, class or function name.
+    </p>
+</div>
+<script type="text/javascript">$('#searchbox').show(0);</script>
+        </div>
+      </div>
+      <div class="clearer"></div>
+    </div>
+    <div class="related">
+      <h3>Navigation</h3>
+      <ul>
+        <li class="right" style="margin-right: 10px">
+          <a href="genindex.html" title="General Index"
+             >index</a></li>
+        <li class="right" >
+          <a href="py-modindex.html" title="Python Module Index"
+             >modules</a> |</li>
+        <li><a href="#">django-auth-ldap 1.0.19 documentation</a> &raquo;</li> 
+      </ul>
+    </div>
+    <div class="footer">
+        &copy; Copyright 2009, Peter Sagerson.
+      Created using <a href="http://sphinx.pocoo.org/">Sphinx</a> 1.1.3.
+    </div>
+  </body>
+</html>

BIN
desktop/core/ext-py/django-auth-ldap-1.2.0/docs/archive/versions/1.0.19/objects.inv


+ 121 - 0
desktop/core/ext-py/django-auth-ldap-1.2.0/docs/archive/versions/1.0.19/py-modindex.html

@@ -0,0 +1,121 @@
+
+
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+  "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+
+
+<html xmlns="http://www.w3.org/1999/xhtml">
+  <head>
+    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
+    
+    <title>Python Module Index &mdash; django-auth-ldap 1.0.19 documentation</title>
+    
+    <link rel="stylesheet" href="_static/default.css" type="text/css" />
+    <link rel="stylesheet" href="_static/pygments.css" type="text/css" />
+    
+    <script type="text/javascript">
+      var DOCUMENTATION_OPTIONS = {
+        URL_ROOT:    '',
+        VERSION:     '1.0.19',
+        COLLAPSE_INDEX: false,
+        FILE_SUFFIX: '.html',
+        HAS_SOURCE:  true
+      };
+    </script>
+    <script type="text/javascript" src="_static/jquery.js"></script>
+    <script type="text/javascript" src="_static/underscore.js"></script>
+    <script type="text/javascript" src="_static/doctools.js"></script>
+    <link rel="top" title="django-auth-ldap 1.0.19 documentation" href="index.html" />
+ 
+
+
+  </head>
+  <body>
+    <div class="related">
+      <h3>Navigation</h3>
+      <ul>
+        <li class="right" style="margin-right: 10px">
+          <a href="genindex.html" title="General Index"
+             accesskey="I">index</a></li>
+        <li class="right" >
+          <a href="#" title="Python Module Index"
+             >modules</a> |</li>
+        <li><a href="index.html">django-auth-ldap 1.0.19 documentation</a> &raquo;</li> 
+      </ul>
+    </div>  
+
+    <div class="document">
+      <div class="documentwrapper">
+        <div class="bodywrapper">
+          <div class="body">
+            
+
+   <h1>Python Module Index</h1>
+
+   <div class="modindex-jumpbox">
+   <a href="#cap-d"><strong>d</strong></a>
+   </div>
+
+   <table class="indextable modindextable" cellspacing="0" cellpadding="2">
+     <tr class="pcap"><td></td><td>&nbsp;</td><td></td></tr>
+     <tr class="cap" id="cap-d"><td></td><td>
+       <strong>d</strong></td><td></td></tr>
+     <tr>
+       <td><img src="_static/minus.png" class="toggler"
+              id="toggle-1" style="display: none" alt="-" /></td>
+       <td>
+       <a href="index.html#module-django_auth_ldap"><tt class="xref">django_auth_ldap</tt></a></td><td>
+       <em></em></td></tr>
+     <tr class="cg-1">
+       <td></td>
+       <td>&nbsp;&nbsp;&nbsp;
+       <a href="index.html#module-django_auth_ldap.backend"><tt class="xref">django_auth_ldap.backend</tt></a></td><td>
+       <em></em></td></tr>
+     <tr class="cg-1">
+       <td></td>
+       <td>&nbsp;&nbsp;&nbsp;
+       <a href="index.html#module-django_auth_ldap.config"><tt class="xref">django_auth_ldap.config</tt></a></td><td>
+       <em></em></td></tr>
+   </table>
+
+
+          </div>
+        </div>
+      </div>
+      <div class="sphinxsidebar">
+        <div class="sphinxsidebarwrapper">
+<div id="searchbox" style="display: none">
+  <h3>Quick search</h3>
+    <form class="search" action="search.html" method="get">
+      <input type="text" name="q" />
+      <input type="submit" value="Go" />
+      <input type="hidden" name="check_keywords" value="yes" />
+      <input type="hidden" name="area" value="default" />
+    </form>
+    <p class="searchtip" style="font-size: 90%">
+    Enter search terms or a module, class or function name.
+    </p>
+</div>
+<script type="text/javascript">$('#searchbox').show(0);</script>
+        </div>
+      </div>
+      <div class="clearer"></div>
+    </div>
+    <div class="related">
+      <h3>Navigation</h3>
+      <ul>
+        <li class="right" style="margin-right: 10px">
+          <a href="genindex.html" title="General Index"
+             >index</a></li>
+        <li class="right" >
+          <a href="#" title="Python Module Index"
+             >modules</a> |</li>
+        <li><a href="index.html">django-auth-ldap 1.0.19 documentation</a> &raquo;</li> 
+      </ul>
+    </div>
+    <div class="footer">
+        &copy; Copyright 2009, Peter Sagerson.
+      Created using <a href="http://sphinx.pocoo.org/">Sphinx</a> 1.1.3.
+    </div>
+  </body>
+</html>

+ 105 - 0
desktop/core/ext-py/django-auth-ldap-1.2.0/docs/archive/versions/1.0.19/search.html

@@ -0,0 +1,105 @@
+
+
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+  "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+
+
+<html xmlns="http://www.w3.org/1999/xhtml">
+  <head>
+    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
+    
+    <title>Search &mdash; django-auth-ldap 1.0.19 documentation</title>
+    
+    <link rel="stylesheet" href="_static/default.css" type="text/css" />
+    <link rel="stylesheet" href="_static/pygments.css" type="text/css" />
+    
+    <script type="text/javascript">
+      var DOCUMENTATION_OPTIONS = {
+        URL_ROOT:    '',
+        VERSION:     '1.0.19',
+        COLLAPSE_INDEX: false,
+        FILE_SUFFIX: '.html',
+        HAS_SOURCE:  true
+      };
+    </script>
+    <script type="text/javascript" src="_static/jquery.js"></script>
+    <script type="text/javascript" src="_static/underscore.js"></script>
+    <script type="text/javascript" src="_static/doctools.js"></script>
+    <script type="text/javascript" src="_static/searchtools.js"></script>
+    <link rel="top" title="django-auth-ldap 1.0.19 documentation" href="index.html" />
+  <script type="text/javascript">
+    jQuery(function() { Search.loadIndex("searchindex.js"); });
+  </script>
+   
+
+  </head>
+  <body>
+    <div class="related">
+      <h3>Navigation</h3>
+      <ul>
+        <li class="right" style="margin-right: 10px">
+          <a href="genindex.html" title="General Index"
+             accesskey="I">index</a></li>
+        <li class="right" >
+          <a href="py-modindex.html" title="Python Module Index"
+             >modules</a> |</li>
+        <li><a href="index.html">django-auth-ldap 1.0.19 documentation</a> &raquo;</li> 
+      </ul>
+    </div>  
+
+    <div class="document">
+      <div class="documentwrapper">
+        <div class="bodywrapper">
+          <div class="body">
+            
+  <h1 id="search-documentation">Search</h1>
+  <div id="fallback" class="admonition warning">
+  <script type="text/javascript">$('#fallback').hide();</script>
+  <p>
+    Please activate JavaScript to enable the search
+    functionality.
+  </p>
+  </div>
+  <p>
+    From here you can search these documents. Enter your search
+    words into the box below and click "search". Note that the search
+    function will automatically search for all of the words. Pages
+    containing fewer words won't appear in the result list.
+  </p>
+  <form action="" method="get">
+    <input type="text" name="q" value="" />
+    <input type="submit" value="search" />
+    <span id="search-progress" style="padding-left: 10px"></span>
+  </form>
+  
+  <div id="search-results">
+  
+  </div>
+
+          </div>
+        </div>
+      </div>
+      <div class="sphinxsidebar">
+        <div class="sphinxsidebarwrapper">
+        </div>
+      </div>
+      <div class="clearer"></div>
+    </div>
+    <div class="related">
+      <h3>Navigation</h3>
+      <ul>
+        <li class="right" style="margin-right: 10px">
+          <a href="genindex.html" title="General Index"
+             >index</a></li>
+        <li class="right" >
+          <a href="py-modindex.html" title="Python Module Index"
+             >modules</a> |</li>
+        <li><a href="index.html">django-auth-ldap 1.0.19 documentation</a> &raquo;</li> 
+      </ul>
+    </div>
+    <div class="footer">
+        &copy; Copyright 2009, Peter Sagerson.
+      Created using <a href="http://sphinx.pocoo.org/">Sphinx</a> 1.1.3.
+    </div>
+  </body>
+</html>

Diff do ficheiro suprimidas por serem muito extensas
+ 0 - 0
desktop/core/ext-py/django-auth-ldap-1.2.0/docs/archive/versions/1.0.19/searchindex.js


+ 11 - 0
desktop/core/ext-py/django-auth-ldap-1.2.0/docs/ext/daldocs.py

@@ -0,0 +1,11 @@
+"""
+Extra stuff for the django-auth-ldap Sphinx docs.
+"""
+
+
+def setup(app):
+    app.add_crossref_type(
+        directivename="setting",
+        rolename="setting",
+        indextemplate="pair: %s; setting",
+    )

+ 82 - 0
desktop/core/ext-py/django-auth-ldap-1.2.0/docs/source/.spell.utf-8.add

@@ -0,0 +1,82 @@
+LDAP
+AUTH
+ldap
+django
+auth
+config
+LDAPSearch
+DN
+username
+LDAPSearchUnion
+ou
+dc
+otherusers
+ONELEVEL
+backend
+LDAPBackend
+usernames
+uid
+localhost
+StartTLS
+ldaps
+DNs
+s
+contrib
+BACKENDS
+APPS
+www
+org
+backends
+ModelBackend
+LDAPGroupType
+PosixGroupType
+MemberDNGroupType
+NestedMemberDNGroupType
+groupOfNames
+groupOfUniqueNames
+GroupOfNamesType
+NestedGroupOfNamesType
+GroupOfUniqueNamesType
+NestedGroupOfUniqueNamesType
+ActiveDirectoryGroupType
+NestedActiveDirectoryGroupType
+objectClass
+cn
+API
+posixGroup
+Django's
+iexact
+meth
+attr
+dn
+attrs
+dns
+utf8
+objectSid
+alice
+Django
+RemoteUserBackend
+init
+admin
+NOTSET
+getLogger
+addHandler
+StreamHandler
+setLevel
+py
+phlebotinum
+givenName
+sn
+employeeNumber
+LDAPObject
+tls
+users's
+filterstr
+subclassing
+uniqueMember
+Configs
+mypackage
+LDAPBackend1
+LDAPBackend2
+ldap1
+ldap2

BIN
desktop/core/ext-py/django-auth-ldap-1.2.0/docs/source/.spell.utf-8.add.spl


+ 135 - 0
desktop/core/ext-py/django-auth-ldap-1.2.0/docs/source/authentication.rst

@@ -0,0 +1,135 @@
+Authentication
+==============
+
+Server Config
+-------------
+
+If your LDAP server isn't running locally on the default port, you'll want to
+start by setting :setting:`AUTH_LDAP_SERVER_URI` to point to your server. The
+value of this setting can be anything that your LDAP library supports. For
+instance, openldap may allow you to give a comma- or space-separated list of
+URIs to try in sequence.
+
+.. code-block:: python
+
+    AUTH_LDAP_SERVER_URI = "ldap://ldap.example.com"
+
+If your server location is even more dynamic than this, you may provide a
+function (or any callable object) that returns the URI. You should assume that
+this will be called on every request, so if it's an expensive operation, some
+caching is in order.
+
+.. code-block:: python
+
+    from my_module import find_my_ldap_server
+
+    AUTH_LDAP_SERVER_URI = find_my_ldap_server
+
+If you need to configure any python-ldap options, you can set
+:setting:`AUTH_LDAP_GLOBAL_OPTIONS` and/or
+:setting:`AUTH_LDAP_CONNECTION_OPTIONS`. For example, disabling referrals is not
+uncommon::
+
+    import ldap
+
+    AUTH_LDAP_CONNECTION_OPTIONS = {
+        ldap.OPT_REFERRALS: 0
+    }
+
+
+Search/Bind
+-----------
+
+Now that you can talk to your LDAP server, the next step is to authenticate a
+username and password. There are two ways to do this, called search/bind and
+direct 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 :setting:`AUTH_LDAP_BIND_DN` to the distinguished name
+of an authorized user and :setting:`AUTH_LDAP_BIND_PASSWORD` to the password.
+
+Search Unions
+^^^^^^^^^^^^^
+
+.. versionadded:: 1.1
+
+If you need to search in more than one place for a user, you can use
+:class:`~django_auth_ldap.config.LDAPSearchUnion`. This takes multiple
+LDAPSearch objects and returns the union of the results. The precedence of the
+underlying searches is unspecified.
+
+.. code-block:: python
+
+    import ldap
+    from django_auth_ldap.config import LDAPSearch, LDAPSearchUnion
+
+    AUTH_LDAP_USER_SEARCH = LDAPSearchUnion(
+        LDAPSearch("ou=users,dc=example,dc=com", ldap.SCOPE_SUBTREE, "(uid=%(user)s)"),
+        LDAPSearch("ou=otherusers,dc=example,dc=com", ldap.SCOPE_SUBTREE, "(uid=%(user)s)"),
+    )
+
+
+Direct Bind
+-----------
+
+To skip the search phase, set :setting:`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 first 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"
+
+
+Notes
+-----
+
+LDAP is fairly flexible when it comes to matching DNs.
+:class:`~django_auth_ldap.backend.LDAPBackend` makes an effort to accommodate
+this by forcing usernames to lower case when creating Django users and trimming
+whitespace when authenticating.
+
+Some LDAP servers are configured to allow users to bind without a password. As a
+precaution against false positives,
+:class:`~django_auth_ldap.backend.LDAPBackend` will summarily reject any
+authentication attempt with an empty password. You can disable this behavior by
+setting :setting:`AUTH_LDAP_PERMIT_EMPTY_PASSWORD` to True.
+
+By default, all LDAP operations are performed with the
+:setting:`AUTH_LDAP_BIND_DN` and :setting:`AUTH_LDAP_BIND_PASSWORD` credentials,
+not with the user's. Otherwise, the LDAP connection would be bound as the
+authenticating user during login requests and as the default credentials during
+other requests, so you might see inconsistent LDAP attributes depending on the
+nature of the Django view. If you're willing to accept the inconsistency in
+order to retrieve attributes while bound as the authenticating user, see
+:setting:`AUTH_LDAP_BIND_AS_AUTHENTICATING_USER`.
+
+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 :setting:`AUTH_LDAP_START_TLS` to ``True``::
+
+    AUTH_LDAP_START_TLS = True

+ 4 - 0
desktop/core/ext-py/django-auth-ldap-1.2.0/docs/source/changes.rst

@@ -0,0 +1,4 @@
+Change Log
+==========
+
+.. include:: ../../CHANGES

+ 24 - 10
desktop/core/ext-py/django-auth-ldap-1.0.7/docs/conf.py → desktop/core/ext-py/django-auth-ldap-1.2.0/docs/source/conf.py

@@ -11,21 +11,33 @@
 # All configuration values have a default; values that are commented out
 # serve to show the default.
 
-import sys, os
+import sys
+import os.path
 
 # 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('.'))
+sys.path.insert(0, os.path.abspath('../ext'))
 
 # -- 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 = []
+extensions = [
+    'sphinx.ext.intersphinx',
+
+    'daldocs',
+]
+
+intersphinx_mapping = {
+    'python': ('http://docs.python.org/', None),
+    'django': ('https://docs.djangoproject.com/en/dev/',
+               'https://docs.djangoproject.com/en/dev/_objects/'),
+    'pythonldap': ('http://python-ldap.org/doc/html/', None),
+}
 
 # Add any paths that contain templates here, relative to this directory.
-templates_path = ['_templates']
+templates_path = ['../_templates']
 
 # The suffix of source filenames.
 source_suffix = '.rst'
@@ -45,9 +57,9 @@ copyright = u'2009, Peter Sagerson'
 # built documents.
 #
 # The short X.Y version.
-version = '1.0'
+version = '1.1'
 # The full version, including alpha/beta/rc tags.
-release = '1.0.7'
+release = '1.2.0'
 
 # The language for content autogenerated by Sphinx. Refer to documentation
 # for a list of supported languages.
@@ -64,7 +76,7 @@ release = '1.0.7'
 
 # List of directories, relative to source directory, that shouldn't be searched
 # for source files.
-exclude_trees = ['_build']
+exclude_trees = ['build']
 
 # The reST default role (used for this markup: `text`) to use for all documents.
 #default_role = None
@@ -97,7 +109,7 @@ html_theme = 'default'
 # further.  For a list of options available for each theme, see the
 # documentation.
 html_theme_options = {
-    "rightsidebar": True,
+    #"rightsidebar": True,
 }
 
 # Add any paths that contain custom themes here, relative to this directory.
@@ -122,7 +134,7 @@ html_theme_options = {
 # 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']
+html_static_path = ['../archive']
 
 # If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
 # using the given strftime format.
@@ -133,7 +145,9 @@ html_theme_options = {
 #html_use_smartypants = True
 
 # Custom sidebar templates, maps document names to template names.
-#html_sidebars = {}
+html_sidebars = {
+    '*': ['globaltoc.html', 'relations.html', 'sourcelink.html', 'searchbox.html']
+}
 
 # Additional templates that should be rendered to pages, maps page names to
 # template names.

+ 74 - 0
desktop/core/ext-py/django-auth-ldap-1.2.0/docs/source/example.rst

@@ -0,0 +1,74 @@
+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")
+
+    # Simple group restrictions
+    AUTH_LDAP_REQUIRE_GROUP = "cn=enabled,ou=django,ou=groups,dc=example,dc=com"
+    AUTH_LDAP_DENY_GROUP = "cn=disabled,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"
+    }
+
+    AUTH_LDAP_PROFILE_FLAGS_BY_GROUP = {
+        "is_awesome": "cn=awesome,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',
+    )

+ 74 - 0
desktop/core/ext-py/django-auth-ldap-1.2.0/docs/source/groups.rst

@@ -0,0 +1,74 @@
+Working With Groups
+===================
+
+Types of 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`
+    * :class:`~django_auth_ldap.config.OrganizationalRoleGroupType`
+    * :class:`~django_auth_ldap.config.NestedOrganizationalRoleGroupType`
+
+
+Finding Groups
+--------------
+
+To get started, you'll need to provide some basic information about your LDAP
+groups. :setting:`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). :setting:`AUTH_LDAP_GROUP_TYPE` is an instance of the class
+corresponding to the type of group that will be returned by
+:setting:`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()
+
+
+Limiting Access
+---------------
+
+The simplest use of groups is to limit the users who are allowed to log in. If
+:setting:`AUTH_LDAP_REQUIRE_GROUP` is set, then only users who are members of
+that group will successfully authenticate. :setting:`AUTH_LDAP_DENY_GROUP` is
+the reverse: if given, members of this group will be rejected.
+
+.. code-block:: python
+
+    AUTH_LDAP_REQUIRE_GROUP = "cn=enabled,ou=groups,dc=example,dc=com"
+    AUTH_LDAP_DENY_GROUP = "cn=disabled,ou=groups,dc=example,dc=com"
+
+When groups are configured, you can always get the list of a user's groups from
+``user.ldap_user.group_dns`` or ``user.ldap_user.group_names``. More advanced
+uses of groups are covered in the next two sections.

+ 46 - 0
desktop/core/ext-py/django-auth-ldap-1.2.0/docs/source/index.rst

@@ -0,0 +1,46 @@
+================================
+Django Authentication Using LDAP
+================================
+
+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 version is officially supported on Python >= 2.6 and < 3, Django >= 1.3,
+and python-ldap >= 2.0. It is known to work on earlier versions (especially of
+Django) and backwards-compatibility is not broken needlessly, however users of
+older dependencies are urged to test their deployments carefully and be wary of
+updates.
+
+Support for Python >= 3.3 is considered experimental at this time. Since
+python-ldap doesn't support Python 3 yet, you have to install a fork::
+
+    $ pip install git+https://github.com/rbarrois/python-ldap.git@py3
+
+.. toctree::
+    :maxdepth: 2
+
+    install
+    authentication
+    groups
+    users
+    permissions
+    multiconfig
+    logging
+    performance
+    example
+    reference
+    changes
+
+
+Older Versions
+==============
+
+- `django-auth-ldap 1.0.19 <_static/versions/1.0.19/index.html>`_
+
+
+License
+=======
+
+.. include:: ../../LICENSE

+ 27 - 0
desktop/core/ext-py/django-auth-ldap-1.2.0/docs/source/install.rst

@@ -0,0 +1,27 @@
+Installation
+============
+
+This authentication backend enables a Django project to authenticate against any
+LDAP server. To use it, add :class:`django_auth_ldap.backend.LDAPBackend` to
+:django:setting:`AUTHENTICATION_BACKENDS`. Adding `django_auth_ldap` to
+:django:setting:`INSTALLED_APPS` is not recommended 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',
+        )

+ 17 - 0
desktop/core/ext-py/django-auth-ldap-1.2.0/docs/source/logging.rst

@@ -0,0 +1,17 @@
+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)

+ 51 - 0
desktop/core/ext-py/django-auth-ldap-1.2.0/docs/source/multiconfig.rst

@@ -0,0 +1,51 @@
+Multiple LDAP Configs
+=====================
+
+.. versionadded:: 1.1
+
+You've probably noticed that all of the settings for this backend have the
+prefix AUTH_LDAP\_. This is the default, but it can be customized by subclasses
+of :class:`~django_auth_ldap.backend.LDAPBackend`. The main reason you would
+want to do this is to create two backend subclasses that reference different
+collections of settings and thus operate independently. For example, you might
+have two separate LDAP servers that you want to authenticate against. A short
+example should demonstrate this:
+
+.. code-block:: python
+
+    # mypackage.ldap
+
+    from django_auth_ldap.backend import LDAPBackend
+
+    class LDAPBackend1(LDAPBackend):
+        settings_prefix = "AUTH_LDAP_1_"
+
+    class LDAPBackend2(LDAPBackend):
+        settings_prefix = "AUTH_LDAP_2_" 
+
+
+.. code-block:: python
+
+    # settings.py
+
+    AUTH_LDAP_1_SERVER_URI = "ldap://ldap1.example.com"
+    AUTH_LDAP_1_USER_DN_TEMPLATE = "uid=%(user)s,ou=users,dc=example,dc=com"
+
+    AUTH_LDAP_2_SERVER_URI = "ldap://ldap2.example.com"
+    AUTH_LDAP_2_USER_DN_TEMPLATE = "uid=%(user)s,ou=users,dc=example,dc=com"
+
+    AUTHENTICATION_BACKENDS = (
+        "mypackage.ldap.LDAPBackend1",
+        "mypackage.ldap.LDAPBackend2",
+    )
+
+All of the usual rules apply: Django will attempt to authenticate a user with
+each backend in turn until one of them succeeds. When a particular backend
+successfully authenticates a user, that user will be linked to the backend for
+the duration of their session.
+
+.. note::
+
+    Due to its global nature, :setting:`AUTH_LDAP_GLOBAL_OPTIONS` ignores the
+    settings prefix. Regardless of how many backends are installed, this setting
+    is referenced once by its default name at the time we load the ldap module.

+ 34 - 0
desktop/core/ext-py/django-auth-ldap-1.2.0/docs/source/performance.rst

@@ -0,0 +1,34 @@
+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 :setting:`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
+       :setting:`AUTH_LDAP_CACHE_GROUPS` to ``True`` to remove most of this
+       traffic.  Alternatively, you might consider using
+       :setting:`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
+       :setting:`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
+       :setting:`AUTH_LDAP_USER_DN_TEMPLATE` is a little bit more efficient than
+       relying on :setting:`AUTH_LDAP_USER_SEARCH`. Specifically, it saves two
+       LDAP operations (one bind and one search) per login.

+ 76 - 0
desktop/core/ext-py/django-auth-ldap-1.2.0/docs/source/permissions.rst

@@ -0,0 +1,76 @@
+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.
+
+
+Using Groups Directly
+---------------------
+
+The least invasive way to map group permissions is to set
+:setting:`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 and
+associate permissions 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 :setting:`AUTH_LDAP_CACHE_GROUPS` to ``True``. You can also set
+:setting:`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
+
+
+Group Mirroring
+---------------
+
+The second way to turn LDAP group memberships into permissions is to mirror the
+groups themselves. If :setting:`AUTH_LDAP_MIRROR_GROUPS` is ``True``, then every
+time a user logs in, :class:`~django_auth_ldap.backend.LDAPBackend` will update
+the database with the user's LDAP groups. Any group that doesn't exist will be
+created and the user's Django group membership will be updated to exactly match
+his LDAP group membership. Note that if the LDAP server has nested groups, the
+Django database will end up with a flattened representation. For group mirroring
+to have any effect, you of course need
+:class:`~django.contrib.auth.backends.ModelBackend` installed as an
+authentication backend.
+
+The main difference between this approach and
+:setting:`AUTH_LDAP_FIND_GROUP_PERMS` is that
+:setting:`AUTH_LDAP_FIND_GROUP_PERMS` will query for LDAP group membership
+either for every request or according to the cache timeout. With group
+mirroring, membership will be updated when the user authenticates. This may not
+be appropriate for sites with long session timeouts.
+
+
+Non-LDAP Users
+--------------
+
+: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
+:class:`~django.contrib.auth.backends.RemoteUserBackend`
+to map externally authenticated users to Django users. By setting
+:setting:`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 :setting:`AUTH_LDAP_MIRROR_GROUPS`; group mirroring is
+a feature of authentication, not authorization.

+ 555 - 0
desktop/core/ext-py/django-auth-ldap-1.2.0/docs/source/reference.rst

@@ -0,0 +1,555 @@
+Reference
+=========
+
+Settings
+--------
+
+.. setting:: 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.
+
+
+.. setting:: 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.
+
+
+.. setting:: AUTH_LDAP_BIND_AS_AUTHENTICATING_USER
+
+AUTH_LDAP_BIND_AS_AUTHENTICATING_USER
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Default: ``False``
+
+If ``True``, authentication will leave the LDAP connection bound as the
+authenticating user, rather than forcing it to re-bind with the default
+credentials after authentication succeeds. This may be desirable if you do not
+have global credentials that are able to access the user's attributes.
+django-auth-ldap never stores the user's password, so this only applies to
+requests where the user is authenticated. Thus, the downside to this setting is
+that LDAP results may vary based on whether the user was authenticated earlier
+in the Django view, which could be surprising to code not directly concerned
+with authentication.
+
+
+.. setting:: AUTH_LDAP_BIND_DN
+
+AUTH_LDAP_BIND_DN
+~~~~~~~~~~~~~~~~~
+
+Default: ``''`` (Empty string)
+
+The distinguished name to use when binding to the LDAP server (with
+:setting:`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 :setting:`AUTH_LDAP_USER_DN_TEMPLATE` is not set, we'll
+use this to search for the user. If :setting:`AUTH_LDAP_FIND_GROUP_PERMS` is
+``True``, we'll also use it to determine group membership.
+
+
+.. setting:: AUTH_LDAP_BIND_PASSWORD
+
+AUTH_LDAP_BIND_PASSWORD
+~~~~~~~~~~~~~~~~~~~~~~~
+
+Default: ``''`` (Empty string)
+
+The password to use with :setting:`AUTH_LDAP_BIND_DN`.
+
+
+.. setting:: 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
+:setting:`AUTH_LDAP_GROUP_CACHE_TIMEOUT`.
+
+
+.. setting:: 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_*
+<http://python-ldap.org/doc/html/ldap.html#options>`_ constants.
+
+
+.. setting:: AUTH_LDAP_DENY_GROUP
+
+AUTH_LDAP_DENY_GROUP
+~~~~~~~~~~~~~~~~~~~~~~~
+
+Default: ``None``
+
+The distinguished name of a group; authentication will fail for any user
+that belongs to this group.
+
+
+.. setting:: 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.
+:setting:`AUTH_LDAP_GROUP_SEARCH` and :setting:`AUTH_LDAP_GROUP_TYPE` must also be
+set.
+
+
+.. setting:: AUTH_LDAP_GLOBAL_OPTIONS
+
+AUTH_LDAP_GLOBAL_OPTIONS
+~~~~~~~~~~~~~~~~~~~~~~~~
+
+Default: ``{}``
+
+A dictionary of options to pass to ``ldap.set_option()``. Keys are
+`ldap.OPT_* <http://python-ldap.org/doc/html/ldap.html#options>`_ constants.
+
+.. note::
+
+    Due to its global nature, this setting ignores the :doc:`settings prefix
+    <multiconfig>`. Regardless of how many backends are installed, this setting
+    is referenced once by its default name at the time we load the ldap module.
+
+
+.. setting:: AUTH_LDAP_GROUP_CACHE_TIMEOUT
+
+AUTH_LDAP_GROUP_CACHE_TIMEOUT
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Default: ``None``
+
+If :setting:`AUTH_LDAP_CACHE_GROUPS` is ``True``, this is the cache timeout for
+group memberships. If ``None``, the global cache timeout will be used.
+
+
+.. setting:: 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 :setting:`AUTH_LDAP_GROUP_TYPE` must be set.
+
+
+.. setting:: 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 :setting:`AUTH_LDAP_GROUP_SEARCH`.
+
+
+.. setting:: 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.
+
+
+.. setting:: AUTH_LDAP_PERMIT_EMPTY_PASSWORD
+
+AUTH_LDAP_PERMIT_EMPTY_PASSWORD
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Default: ``False``
+
+If ``False`` (the default), authentication with an empty password will fail
+immediately, without any LDAP communication. This is a secure default, as some
+LDAP servers are configured to allow binds to succeed with no password, perhaps
+at a reduced level of access. If you need to make use of this LDAP feature, you
+can change this setting to ``True``.
+
+
+.. setting:: 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.
+
+
+.. setting:: AUTH_LDAP_PROFILE_FLAGS_BY_GROUP
+
+AUTH_LDAP_PROFILE_FLAGS_BY_GROUP
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Default: ``{}``
+
+A mapping from boolean profile field names to distinguished names of LDAP
+groups. The corresponding field in a user's profile is set to ``True`` or
+``False`` according to whether the user is a member of the group.
+
+
+.. setting:: AUTH_LDAP_REQUIRE_GROUP
+
+AUTH_LDAP_REQUIRE_GROUP
+~~~~~~~~~~~~~~~~~~~~~~~
+
+Default: ``None``
+
+The distinguished name of a group; authentication will fail for any user that
+does not belong to this group.
+
+
+.. setting:: 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.
+
+
+.. setting:: AUTH_LDAP_START_TLS
+
+AUTH_LDAP_START_TLS
+~~~~~~~~~~~~~~~~~~~
+
+Default: ``False``
+
+If ``True``, each connection to the LDAP server will call :meth:`~ldap.LDAPObject.start_tls_s` to enable
+TLS encryption over the standard LDAP port. There are a number of configuration
+options that can be given to :setting:`AUTH_LDAP_GLOBAL_OPTIONS` that affect the
+TLS connection. For example, :data:`ldap.OPT_X_TLS_REQUIRE_CERT` can be set to
+:data:`ldap.OPT_X_TLS_NEVER` to disable certificate verification, perhaps to
+allow self-signed certificates.
+
+
+.. setting:: 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.
+
+
+.. setting:: 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``.
+
+
+.. setting:: 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.
+
+
+.. setting:: 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.
+
+
+Module Properties
+-----------------
+
+.. module:: django_auth_ldap
+
+.. data:: version
+
+    The library's current version number as a 3-tuple.
+
+.. data:: version_string
+
+    The library's current version number as a string.
+
+
+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:: LDAPSearchUnion
+
+    .. versionadded:: 1.1
+
+    .. method:: __init__(\*searches)
+
+        * ``searches``: Zero or more LDAPSearch objects. The result of the
+          overall search is the union (by DN) of the results of the underlying
+          searches. The precedence of the underlying results and the ordering of
+          the final results are both undefined.
+
+.. 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')
+
+
+.. class:: OrganizationalRoleGroupType
+
+    A concrete subclass of :class:`~django_auth_ldap.config.MemberDNGroupType`
+    that handles the ``organizationalRole`` object class. Equivalent to
+    ``MemberDNGroupType('roleOccupant')``.
+
+    .. method:: __init__(name_attr='cn')
+
+
+.. class:: NestedOrganizationalRoleGroupType
+
+    A concrete subclass of
+    :class:`~django_auth_ldap.config.NestedMemberDNGroupType` that handles the
+    ``organizationalRole`` object class. Equivalent to
+    ``NestedMemberDNGroupType('roleOccupant')``.
+
+    .. method:: __init__(name_attr='cn')
+
+
+Backend
+-------
+
+.. module:: django_auth_ldap.backend
+
+.. data:: populate_user
+
+    This is a Django signal that is sent when clients should perform additional
+    customization of a :class:`~django.contrib.auth.models.User` object. It is
+    sent after a user has been authenticated and the backend has finished
+    populating it, and just before it is saved. The client may take this
+    opportunity to populate additional model fields, perhaps based on
+    ``ldap_user.attrs``. This signal has two keyword arguments: ``user`` is the
+    :class:`~django.contrib.auth.models.User` object and ``ldap_user`` is the
+    same as ``user.ldap_user``. The sender is the
+    :class:`~django_auth_ldap.backend.LDAPBackend` class.
+
+.. data:: populate_user_profile
+
+    Like :data:`~django_auth_ldap.backend.populate_user`, but sent for the user
+    profile object. This will only be sent if the user has an existing profile.
+    As with :data:`~django_auth_ldap.backend.populate_user`, it is sent after the
+    backend has finished setting properties and before the object is saved. This
+    signal has two keyword arguments: ``profile`` is the user profile object and
+    ``ldap_user`` is the same as ``user.ldap_user``. The sender is the
+    :class:`~django_auth_ldap.backend.LDAPBackend` class.
+
+.. class:: LDAPBackend
+
+    :class:`~django_auth_ldap.backend.LDAPBackend` has one method that may be
+    called directly and several that may be overridden in subclasses.
+
+    .. data:: settings_prefix
+
+        A prefix for all of our Django settings. By default, this is
+        ``"AUTH_LDAP_"``, but subclasses can override this. When different
+        subclasses use different prefixes, they can both be installed and
+        operate independently.
+
+    .. 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.
+        :setting:`AUTH_LDAP_ALWAYS_UPDATE_USER` is ignored (assumed ``True``).
+
+    .. method:: get_user_model(self)
+
+        Returns the user model that
+        :meth:`~django_auth_ldap.backend.LDAPBackend.get_or_create_user` will
+        instantiate. In Django 1.5, custom user models will be respected; in
+        earlier versions, the model defaults to
+        :class:`django.contrib.auth.models.User`. Subclasses would most likely
+        override this in order to substitute a :ref:`proxy model
+        <proxy-models>`.
+
+    .. method:: get_or_create_user(self, username, ldap_user)
+
+        Given a username and an LDAP user object, this must return a valid
+        Django user model instance. 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
+        :meth:`~django.db.models.query.QuerySet.get_or_create`: an (instance,
+        created) two-tuple.
+
+        The default implementation calls ``<model>.objects.get_or_create()``,
+        using a case-insensitive query and creating new users with lowercase
+        usernames. The user model is obtained from
+        :meth:`~django_auth_ldap.backend.LDAPBackend.get_user_model`. A subclass
+        may override this to associate LDAP users to Django users any way it
+        likes.
+
+    .. 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 overridden 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.

+ 133 - 0
desktop/core/ext-py/django-auth-ldap-1.2.0/docs/source/users.rst

@@ -0,0 +1,133 @@
+User objects
+============
+
+Authenticating against an external source is swell, but Django's auth module is
+tightly bound to a user model. When a user logs in, we have to create a model
+object to represent them in the database. Because the LDAP search is
+case-insensitive, the default implementation also searches for existing Django
+users with an iexact query and new users are created with lowercase usernames.
+See :meth:`~django_auth_ldap.backend.LDAPBackend.get_or_create_user` if you'd
+like to override this behavior. See
+:meth:`~django_auth_ldap.backend.LDAPBackend.get_user_model` if you'd like to
+substitute a proxy model.
+
+.. note::
+
+    Prior to Django 1.5, user objects were always instances of
+    :class:`~django.contrib.auth.models.User`. Current versions of Django
+    support custom user models via the :setting:`AUTH_USER_MODEL` setting. As of
+    version 1.1.4, django-auth-ldap will respect custom user models.
+
+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:`ldap_username` attribute with the original (LDAP) username.
+:attr:`~django.contrib.auth.models.User.username` (or
+:meth:`~django.contrib.auth.models.AbstractBaseUser.get_username`) will, of
+course, be the Django username.
+
+.. 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
+    they are 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.
+
+
+User Attributes
+---------------
+
+LDAP directories tend to contain much more information about users that you may
+wish to propagate. A pair of settings, :setting:`AUTH_LDAP_USER_ATTR_MAP` and
+:setting:`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
+(case-insensitive) 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",
+                     "cn=admin,ou=groups,dc=example,dc=com"],
+        "is_superuser": "cn=superuser,ou=groups,dc=example,dc=com"
+    }
+
+    AUTH_LDAP_PROFILE_FLAGS_BY_GROUP = {
+        "is_awesome": ["cn=awesome,ou=groups,dc=example,dc=com"]
+    }
+
+If a list of groups is given, the flag will be set if the user is a member of
+any group.
+
+
+Updating Users
+--------------
+
+By default, all mapped user fields will be updated each time the user logs in.
+To disable this, set :setting:`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 should feel free to create yourself.
+:meth:`~django_auth_ldap.backend.LDAPBackend.populate_user` returns the
+:class:`~django.contrib.auth.models.User` or `None` if the user could not be
+found in LDAP.
+
+.. code-block:: python
+
+    from django_auth_ldap.backend import LDAPBackend
+
+    user = LDAPBackend().populate_user('alice')
+    if user is None:
+        raise Exception('No user named alice')
+
+
+Direct Attribute Access
+-----------------------
+
+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 four public properties. The group
+properties are, of course, only valid if groups are configured.
+
+    * ``dn``: The user's distinguished name.
+    * ``attrs``: The user's LDAP attributes as a dictionary of lists of string
+      values. The dictionaries are modified to use case-insensitive keys.
+    * ``group_dns``: The set of groups that this user belongs to, as DNs.
+    * ``group_names``: The set of groups that this user belongs to, as simple
+      names. These are the names that will be used if
+      :setting:`AUTH_LDAP_MIRROR_GROUPS` is used.
+
+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.
+
+
+Custom Field Population
+-----------------------
+
+If you would like to perform any additional population of user or profile
+objects, :mod:`django_auth_ldap.backend` exposes two custom signals to help:
+:data:`~django_auth_ldap.backend.populate_user` and
+:data:`~django_auth_ldap.backend.populate_user_profile`. These are sent after
+the backend has finished populating the respective objects and before they are
+saved to the database. You can use this to propagate additional information from
+the LDAP directory to the user and profile objects any way you like.

+ 5 - 0
desktop/core/ext-py/django-auth-ldap-1.2.0/setup.cfg

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

+ 19 - 6
desktop/core/ext-py/django-auth-ldap-1.0.7/setup.py → desktop/core/ext-py/django-auth-ldap-1.2.0/setup.py

@@ -4,21 +4,24 @@ from setuptools import setup
 
 setup(
     name="django-auth-ldap",
-    version="1.0.7",
+    version="1.2.0",
     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/.
-    """,
+    long_description=open('README').read(),
     url="http://bitbucket.org/psagers/django-auth-ldap/",
     author="Peter Sagerson",
-    author_email="psagers_pypi@ignorare.net",
+    author_email="psagers.pypi@ignorare.net",
     license="BSD",
     packages=["django_auth_ldap"],
     classifiers=[
         "Development Status :: 5 - Production/Stable",
         "Environment :: Web Environment",
         "Programming Language :: Python",
+        "Programming Language :: Python :: 2",
+        "Programming Language :: Python :: 2.6",
+        "Programming Language :: Python :: 2.7",
+        "Programming Language :: Python :: 3",
+        "Programming Language :: Python :: 3.3",
+        "Programming Language :: Python :: 3.4",
         "Framework :: Django",
         "Intended Audience :: Developers",
         "Intended Audience :: System Administrators",
@@ -28,4 +31,14 @@ This package requires at least Python 2.3, Django 1.0, and python-ldap 2.0. Docu
         "Topic :: Software Development :: Libraries :: Python Modules",
     ],
     keywords=["django", "ldap", "authentication", "auth"],
+    install_requires=[
+        "django",
+        "python-ldap >= 2.0",
+    ],
+    setup_requires=[
+        "setuptools >= 0.6c11",
+    ],
+    tests_require=[
+        "mockldap >= 0.2",
+    ]
 )

Alguns ficheiros não foram mostrados porque muitos ficheiros mudaram neste diff