django-auth-ldap-1.2.7/0000755000076600000240000000000012602567034015435 5ustar psagersstaff00000000000000django-auth-ldap-1.2.7/CHANGES0000644000076600000240000000623712602566643016445 0ustar psagersstaff00000000000000v1.2.7 - 2015-09-29 ------------------- - Support Python 3 with `pyldap `_. v1.2.6 - 2015-03-29 ------------------- - Performance improvements to group mirroring (from `Denver Janke `_). - Add :data:`django_auth_ldap.backend.ldap_error` signal for custom handling of :exc:`~ldap.LDAPError` exceptions. - Add :data:`django_auth_ldap.backend.LDAPBackend.default_settings` for per-subclass default settings. v1.2.5 - 2015-01-30 ------------------- - Fix interaction between :setting:`AUTH_LDAP_AUTHORIZE_ALL_USERS` and :setting:`AUTH_LDAP_USER_SEARCH`. v1.2.4 - 2014-12-28 ------------------- - Add support for nisNetgroup groups (thanks to Christopher Bartz). v1.2.3 - 2014-11-18 ------------------- - Fix `#50`_: Improved escaping for filter strings. - Accept (and ignore) arbitrary keyword arguments to :meth:`~django_auth_ldap.backend.LDAPBackend.authenticate`. .. _#50: https://bitbucket.org/psagers/django-auth-ldap/issue/50/ v1.2.2 - 2014-09-22 ------------------- - Include test harness in source distribution. Some package maintainers find this helpful. v1.2.1 - 2014-08-24 ------------------- - More verbose log messages for authentication failures. 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 `_ 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 django-auth-ldap-1.2.7/django_auth_ldap/0000755000076600000240000000000012602567034020720 5ustar psagersstaff00000000000000django-auth-ldap-1.2.7/django_auth_ldap/__init__.py0000644000076600000240000000010112602566400023015 0ustar psagersstaff00000000000000version = (1, 2, 7) version_string = '.'.join(map(str, version)) django-auth-ldap-1.2.7/django_auth_ldap/backend.py0000644000076600000240000007572012516737512022700 0ustar psagersstaff00000000000000# Copyright (c) 2009, Peter Sagerson # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # - Redistributions of source code must retain the above copyright notice, this # list of conditions and the following disclaimer. # # - Redistributions in binary form must reproduce the above copyright notice, # this list of conditions and the following disclaimer in the documentation # and/or other materials provided with the distribution. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. """ LDAP authentication backend Complete documentation can be found in docs/howto/auth-ldap.txt (or the thing it compiles to). Use of this backend requires the python-ldap module. To support unit tests, we import ldap in a single centralized place (config._LDAPConfig) so that the test harness can insert a mock object. A few notes on naming conventions. If an identifier ends in _dn, it is a string representation of a distinguished name. If it ends in _info, it is a 2-tuple containing a DN and a dictionary of lists of attributes. ldap.search_s returns a list of such structures. An identifier that ends in _attrs is the dictionary of attributes from the _info structure. A connection is an LDAPObject that has been successfully bound with a DN and password. The identifier 'user' always refers to a User model object; LDAP user information will be user_dn or user_info. Additional classes can be found in the config module next to this one. """ import ldap import sys import traceback import pprint import copy from django.contrib.auth.models import User, Group, Permission import django.conf from django.core.cache import cache from django.core.exceptions import ImproperlyConfigured, ObjectDoesNotExist import django.dispatch 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 logger = _LDAPConfig.get_logger() # Exported signals # Allows clients to perform custom user/profile population. populate_user = django.dispatch.Signal(providing_args=["user", "ldap_user"]) populate_user_profile = django.dispatch.Signal(providing_args=["profile", "ldap_user"]) # Allows clients to inspect and perform special handling of LDAPError # exceptions. Exceptions raised by handlers will be propagated out. ldap_error = django.dispatch.Signal(providing_args=['context', 'exception']) class LDAPBackend(object): """ The main backend class. This implements the auth backend API, although it actually delegates most of its work to _LDAPUser, which is defined next. """ 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_' # Default settings to override the built-in defaults. default_settings = {} 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, self.default_settings) 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: options = getattr(django.conf.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, **kwargs): 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 = 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, 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): if perm[:perm.index('.')] == app_label: return True return False 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 if hasattr(user, 'ldap_user') and (user.ldap_user.dn is not None): return user.ldap_user.get_group_permissions() else: return set() # # Bonus API: populate the Django user from LDAP without authenticating. # def populate_user(self, username): ldap_user = _LDAPUser(self, username=username) user = ldap_user.populate_user() return user # # Hooks for subclasses # def get_or_create_user(self, username, ldap_user): """ This must return a (User, created) 2-tuple for the given LDAP user. username is the Django-friendly username of the user. ldap_user.dn is the user's DN and ldap_user.attrs contains all of their LDAP attributes. """ 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 def django_to_ldap_username(self, username): return username class _LDAPUser(object): """ Represents an LDAP user and ultimately fields all requests that the backend receives. This class exists for two reasons. First, it's convenient to have a separate object for each request so that we can use object attributes without running into threading problems. Second, these objects get attached to the User objects, which allows us to cache expensive LDAP information, especially around groups and permissions. self.backend is a reference back to the LDAPBackend instance, which we need to access the ldap module and any hooks that a subclass has overridden. """ class AuthenticationFailed(Exception): pass # 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 authenticated User object. If a user is given, the username will be ignored. """ self.backend = backend self._username = username 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._user = copy.deepcopy(self._user, memo) # This is all just cached immutable data. There's no point copying it. obj._username = self._username obj._user_dn = self._user_dn obj._user_attrs = self._user_attrs obj._groups = self._groups obj._group_permissions = self._group_permissions # The connection couldn't be copied even if we wanted to obj._connection = self._connection obj._connection_bound = self._connection_bound return obj def __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(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 as e: logger.debug(u"Authentication failed for %s: %s" % (self._username, e)) except ldap.LDAPError as e: results = ldap_error.send(self.backend.__class__, context='authenticate', exception=e) if len(results) == 0: logger.warning(u"Caught LDAPError while authenticating %s: %s", self._username, pprint.pformat(e)) except Exception: logger.exception(u"Caught Exception while authenticating %s", self._username) raise return user def get_group_permissions(self): """ If allowed by the configuration, this returns the set of permissions defined by the user's LDAP group memberships. """ if self._group_permissions is None: self._group_permissions = set() if self.settings.FIND_GROUP_PERMS: try: self._load_group_permissions() except ldap.LDAPError as e: results = ldap_error.send(self.backend.__class__, context='get_group_permissions', exception=e) if len(results) == 0: logger.warning("Caught LDAPError loading group permissions: %s", pprint.pformat(e)) return self._group_permissions def populate_user(self): """ Populates the Django user object using the default bind credentials. """ user = None try: # 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 ldap.LDAPError as e: results = ldap_error.send(self.backend.__class__, context='populate_user', exception=e) if len(results) == 0: logger.warning(u"Caught LDAPError while authenticating %s: %s", self._username, pprint.pformat(e)) except Exception as e: logger.error(u"Caught Exception while authenticating %s: %s", self._username, pprint.pformat(e)) logger.error(''.join(traceback.format_tb(sys.exc_info()[2]))) raise return user # # Public properties (callbacks). These are all lazy for performance reasons. # def _get_user_dn(self): if self._user_dn is None: self._load_user_dn() return self._user_dn dn = property(_get_user_dn) def _get_user_attrs(self): if self._user_attrs is None: self._load_user_attrs() return self._user_attrs attrs = property(_get_user_attrs) def _get_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) # # Authentication # def _authenticate_user_dn(self, password): """ Binds to the LDAP server with the user's DN and password. Raises AuthenticationFailed on failure. """ if self.dn is None: raise self.AuthenticationFailed("failed to map the username to a DN.") try: 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, ldap.SCOPE_BASE) results = search.execute(self.connection) if results is not None and len(results) > 0: self._user_attrs = results[0][1] def _load_user_dn(self): """ Populates self._user_dn with the distinguished name of our user. This will either construct the DN from a template in AUTH_LDAP_USER_DN_TEMPLATE or connect to the server and search for it. """ if self._using_simple_bind_mode(): self._construct_simple_user_dn() else: self._search_for_user_dn() def _using_simple_bind_mode(self): return (self.settings.USER_DN_TEMPLATE is not None) def _construct_simple_user_dn(self): 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): """ Searches the directory for a user matching AUTH_LDAP_USER_SEARCH. Populates self._user_dn and self._user_attrs. """ 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) = next(iter(results)) def _check_requirements(self): """ Checks all authentication requirements beyond credentials. Raises 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 = 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 # def _get_or_create_user(self, force_populate=False): """ Loads the User model object from the database or creates it if it doesn't exist. Also populates the fields, subject to AUTH_LDAP_ALWAYS_UPDATE_USER. """ save_user = False username = self.backend.ldap_to_django_username(self._username) self._user, created = self.backend.get_or_create_user(username, self) 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 should_populate: logger.debug("Populating Django user %s", username) self._populate_user() save_user = True 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() # 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 self._should_populate_profile(): self._populate_and_save_user_profile() def _populate_user(self): """ Populates our User object with information from the LDAP directory. """ self._populate_user_from_attributes() self._populate_user_from_group_memberships() def _populate_user_from_attributes(self): for field, attr in self.settings.USER_ATTR_MAP.items(): try: setattr(self._user, field, self.attrs[attr][0]) 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_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 _should_populate_profile(self): return ((django.VERSION < (1, 7)) and (getattr(django.conf.settings, 'AUTH_PROFILE_MODULE', None) is not None) and hasattr(self._user, 'get_profile')) def _populate_and_save_user_profile(self): """ Populates a User profile object with fields from the LDAP directory. """ try: profile = self._user.get_profile() save_profile = False 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 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): 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 user's membership. """ target_group_names = frozenset(self._get_groups().get_group_names()) current_group_names = frozenset(self._user.groups.values_list('name', flat=True).iterator()) if target_group_names != current_group_names: existing_groups = list(Group.objects.filter(name__in=target_group_names).iterator()) existing_group_names = frozenset(group.name for group in existing_groups) new_groups = [Group.objects.get_or_create(name=name)[0] for name in target_group_names if name not in existing_group_names] self._user.groups = existing_groups + new_groups # # Group information # def _load_group_permissions(self): """ Populates self._group_permissions based on LDAP group membership and Django group permissions. """ group_names = self._get_groups().get_group_names() 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): """ Returns an _LDAPUserGroups object, which can determine group membership. """ if self._groups is None: self._groups = _LDAPUserGroups(self) return self._groups # # LDAP connection # def _bind(self): """ Binds to the LDAP server with AUTH_LDAP_BIND_DN and AUTH_LDAP_BIND_PASSWORD. """ self._bind_as(self.settings.BIND_DN, self.settings.BIND_PASSWORD, sticky=True) 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 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(force_str(bind_dn), force_str(bind_password)) self._connection_bound = sticky def _get_connection(self): """ Returns our cached LDAPObject, which may or may not be bound. """ if self._connection is None: 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 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 = self.settings.GROUP_TYPE if self._group_type is None: raise ImproperlyConfigured("AUTH_LDAP_GROUP_TYPE must be an LDAPGroupType instance.") 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 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 = 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()) logger.debug("%s is%sa member of %s", self._ldap_user.dn, is_member and " " or " not ", group_dn) return is_member def get_group_dns(self): """ Returns a (cached) set of the distinguished names in self._group_infos. """ if self._group_dns is None: group_infos = self._get_group_infos() self._group_dns = set(group_info[0] for group_info in group_infos) return self._group_dns def _get_group_infos(self): """ Returns a (cached) list of group_info structures for the groups that our user is a member of. """ if self._group_infos is None: self._group_infos = self._group_type.user_groups(self._ldap_user, self._group_search) return self._group_infos def _load_cached_attr(self, attr_name): if 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 self.settings.CACHE_GROUPS: key = self._cache_key(attr_name) value = getattr(self, attr_name, None) cache.set(key, value, self.settings.GROUP_CACHE_TIMEOUT) def _cache_key(self, attr_name): """ 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): """ This is a simple class to take the place of the global settings object. An instance will contain all of our settings as attributes, with default values if they are not specified by the configuration. """ defaults = { '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, prefix='AUTH_LDAP_', defaults={}): """ Loads our settings from django.conf.settings, applying defaults for any that are omitted. """ defaults = dict(self.defaults, **defaults) for name, default in defaults.items(): value = getattr(django.conf.settings, prefix + name, default) setattr(self, name, value) django-auth-ldap-1.2.7/django_auth_ldap/config.py0000644000076600000240000005450412516737512022553 0ustar psagersstaff00000000000000# Copyright (c) 2009, Peter Sagerson # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # - Redistributions of source code must retain the above copyright notice, this # list of conditions and the following disclaimer. # # - Redistributions in binary form must reproduce the above copyright notice, # this list of conditions and the following disclaimer in the documentation # and/or other materials provided with the distribution. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. """ This module contains classes that will be needed for configuration of LDAP authentication. Unlike backend.py, this is safe to import into settings.py. Please see the docstring on the backend module for more information, including notes on naming conventions. """ 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): """ A private class that loads and caches some global objects. """ ldap = None logger = None _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 imported anywhere else so that the unit tests will pass in the absence of python-ldap. """ if cls.ldap is None: import ldap.filter # Support for python-ldap < 2.0.6 try: import ldap.dn except ImportError: from django_auth_ldap import dn ldap.dn = dn cls.ldap = ldap # 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) def get_logger(cls): """ Initializes and returns our logger instance. """ if cls.logger is None: class NullHandler(logging.Handler): def emit(self, record): pass cls.logger = logging.getLogger('django_auth_ldap') cls.logger.addHandler(NullHandler()) return cls.logger get_logger = classmethod(get_logger) # Our global logger logger = _LDAPConfig.get_logger() class LDAPSearch(object): """ Public class that holds a set of LDAP search parameters. Objects of this class should be considered immutable. Only the initialization method is documented for configuration purposes. Internal clients may use the other methods to refine and execute the search. """ def __init__(self, base_dn, scope, filterstr=u'(objectClass=*)'): """ These parameters are the same as the first three parameters to ldap.search_s. """ self.base_dn = base_dn self.scope = scope self.filterstr = filterstr self.ldap = _LDAPConfig.get_ldap() def search_with_additional_terms(self, term_dict, escape=True): """ Returns a new search object with additional search terms and-ed to the filter string. term_dict maps attribute names to assertion values. If you don't want the values escaped, pass escape=False. """ term_strings = [self.filterstr] for name, value in term_dict.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 string. The caller is responsible for passing in a properly escaped string. """ filterstr = u'(&%s%s)' % (self.filterstr, filterstr) return self.__class__(self.base_dn, self.scope, filterstr) def execute(self, connection, filterargs=(), escape=True): """ Executes the search on the given connection (an LDAPObject). filterargs is an object that will be used for expansion of the filter string. If escape is True, values in filterargs will be escaped. The python-ldap library returns utf8-encoded strings. For the sake of sanity, this method will decode all result strings and return them as Unicode. """ if escape: filterargs = self._escape_filterargs(filterargs) try: filterstr = self.filterstr % filterargs results = connection.search_s(force_str(self.base_dn), 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) def _begin(self, connection, filterargs=(), escape=True): """ Begins an asynchronous search and returns the message id to retrieve the results. filterargs is an object that will be used for expansion of the filter string. If escape is True, values in filterargs will be escaped. """ if escape: filterargs = self._escape_filterargs(filterargs) try: filterstr = self.filterstr % filterargs msgid = connection.search(force_str(self.base_dn), 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))) return msgid 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"result(%d) raised %s" % (msgid, pprint.pformat(e))) return self._process_results(results) def _escape_filterargs(self, filterargs): """ Escapes values in filterargs. filterargs is a value suitable for Django's string formatting operator (%), which means it's either a tuple or a dict. This return a new tuple or dict with all values escaped for use in filter strings. """ if isinstance(filterargs, tuple): filterargs = tuple(self.ldap.filter.escape_filter_chars(value) for value in filterargs) elif isinstance(filterargs, dict): filterargs = dict((key, self.ldap.filter.escape_filter_chars(value)) for key, value in filterargs.items()) else: raise TypeError("filterargs must be a tuple or dict.") return filterargs def _process_results(self, results): """ Returns a sanitized copy of raw LDAP results. This scrubs out 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): if msgid is not None: 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 dicts. This is helpful when interacting with the Unicode-unaware python-ldap. """ def __init__(self, encoding): self.encoding = encoding self.ldap = _LDAPConfig.get_ldap() def decode(self, value): try: if isinstance(value, bytes): value = value.decode(self.encoding) elif isinstance(value, list): value = self._decode_list(value) elif isinstance(value, tuple): value = tuple(self._decode_list(value)) elif isinstance(value, dict): value = self._decode_dict(value) except UnicodeDecodeError: pass return value def _decode_list(self, value): return [self.decode(v) for v in value] def _decode_dict(self, value): # 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): """ This is an abstract base class for classes that determine LDAP group membership. A group can mean many different things in LDAP, so we will need a concrete subclass for each grouping mechanism. Clients may subclass this if they have a group mechanism that is not handled by a built-in implementation. name_attr is the name of the LDAP attribute from which we will take the Django group name. Subclasses in this file must use self.ldap to access the python-ldap module. This will be a mock object during unit tests. """ def __init__(self, name_attr="cn"): self.name_attr = name_attr self.ldap = _LDAPConfig.get_ldap() def user_groups(self, ldap_user, group_search): """ Returns a list of group_info structures, each one a group to which ldap_user belongs. group_search is an LDAPSearch object that returns all of the groups that the user might belong to. Typical implementations will apply additional filters to group_search and return the results of the search. ldap_user represents the user and has the following three properties: dn: the distinguished name attrs: a dictionary of LDAP attributes (with lists of values) connection: an LDAPObject that has been bound with credentials This is the primitive method in the API and must be implemented. """ return [] def is_member(self, ldap_user, group_dn): """ This method is an optimization for determining group membership without loading all of the user's groups. Subclasses that are able to do this may return True or False. ldap_user is as above. group_dn is the distinguished name of the group in question. The base implementation returns None, which means we don't have enough information. The caller will have to call user_groups() instead and look for group_dn in the results. """ return None def group_name_from_info(self, group_info): """ Given the (DN, attrs) 2-tuple of an LDAP group, this returns the name of the Django group. This may return None to indicate that a particular LDAP group has no corresponding Django group. The base implementation returns the value of the cn attribute, or whichever attribute was given to __init__ in the name_attr parameter. """ try: name = group_info[1][self.name_attr][0] except (KeyError, IndexError): name = None return name class PosixGroupType(LDAPGroupType): """ An LDAPGroupType subclass that handles groups of class posixGroup. """ def user_groups(self, ldap_user, group_search): """ Searches for any group that is either the user's primary or contains the user as a member. """ groups = [] try: user_uid = ldap_user.attrs['uid'][0] 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): """ Returns True if the group is the user's primary group or if the user is listed in the group's memberUid attribute. """ try: user_uid = ldap_user.attrs['uid'][0] 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: 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 class MemberDNGroupType(LDAPGroupType): """ A group type that stores lists of members as distinguished names. """ def __init__(self, member_attr, name_attr='cn'): """ member_attr is the attribute on the group object that holds the list of member DNs. """ self.member_attr = member_attr super(MemberDNGroupType, self).__init__(name_attr) def user_groups(self, ldap_user, group_search): search = group_search.search_with_additional_terms({self.member_attr: ldap_user.dn}) groups = search.execute(ldap_user.connection) return groups def is_member(self, ldap_user, group_dn): 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 NISGroupType(LDAPGroupType): """ A group type that handles nisNetgroup. """ def user_groups(self, ldap_user, group_search): try: user_uid = ldap_user.attrs['uid'][0] filterstr = u'(|(nisNetgroupTriple=%s)(nisNetgroupTriple=%s))' % ( self.ldap.filter.escape_filter_chars('(,%s,)' % user_uid), self.ldap.filter.escape_filter_chars('(-,%s,-)' % user_uid) ) search = group_search.search_with_additional_term_string(filterstr) groups = search.execute(ldap_user.connection) except (KeyError, IndexError): pass return groups def is_member(self, ldap_user, group_dn): try: user_uid = ldap_user.attrs['uid'][0] result = ldap_user.connection.compare_s( force_str(group_dn), force_str('nisNetgroupTriple'), force_str('(,%s,)' % (user_uid)) ) if result == 0: result = ldap_user.connection.compare_s( force_str(group_dn), force_str('nisNetgroupTriple'), force_str('(-,%s,-)' % (user_uid)) ) except (ldap.UNDEFINED_TYPE, ldap.NO_SUCH_ATTRIBUTE, KeyError, IndexError): result = 0 return result class NestedMemberDNGroupType(LDAPGroupType): """ A group type that stores lists of members as distinguished names and supports nested groups. There is no shortcut for is_member in this case, so it's left unimplemented. """ def __init__(self, member_attr, name_attr='cn'): """ member_attr is the attribute on the group object that holds the list of member DNs. """ self.member_attr = member_attr super(NestedMemberDNGroupType, self).__init__(name_attr) def user_groups(self, ldap_user, group_search): """ This searches for all of a user's groups from the bottom up. In other words, it returns the groups that the user belongs to, the groups that those groups belong to, etc. Circular references will be detected and pruned. """ group_info_map = {} # Maps group_dn to group_info of groups we've found member_dn_set = set([ldap_user.dn]) # Member DNs to search with next handled_dn_set = set() # Member DNs that we've already searched with while len(member_dn_set) > 0: group_infos = self.find_groups_with_any_member(member_dn_set, group_search, ldap_user.connection) new_group_info_map = dict([(info[0], info) for info in group_infos]) group_info_map.update(new_group_info_map) handled_dn_set.update(member_dn_set) # Get ready for the next iteration. To avoid cycles, we make sure # never to search with the same member DN twice. member_dn_set = set(new_group_info_map.keys()) - handled_dn_set return group_info_map.values() def find_groups_with_any_member(self, member_dn_set, group_search, connection): terms = [ u"(%s=%s)" % (self.member_attr, self.ldap.filter.escape_filter_chars(dn)) for dn in member_dn_set ] filterstr = u"(|%s)" % "".join(terms) search = group_search.search_with_additional_term_string(filterstr) return search.execute(connection) class GroupOfNamesType(MemberDNGroupType): """ An LDAPGroupType subclass that handles groups of class groupOfNames. """ def __init__(self, name_attr='cn'): super(GroupOfNamesType, self).__init__('member', name_attr) class NestedGroupOfNamesType(NestedMemberDNGroupType): """ An LDAPGroupType subclass that handles groups of class groupOfNames with nested group references. """ def __init__(self, name_attr='cn'): super(NestedGroupOfNamesType, self).__init__('member', name_attr) class GroupOfUniqueNamesType(MemberDNGroupType): """ An LDAPGroupType subclass that handles groups of class groupOfUniqueNames. """ def __init__(self, name_attr='cn'): super(GroupOfUniqueNamesType, self).__init__('uniqueMember', name_attr) class NestedGroupOfUniqueNamesType(NestedMemberDNGroupType): """ An LDAPGroupType subclass that handles groups of class groupOfUniqueNames with nested group references. """ def __init__(self, name_attr='cn'): super(NestedGroupOfUniqueNamesType, self).__init__('uniqueMember', name_attr) class ActiveDirectoryGroupType(MemberDNGroupType): """ An LDAPGroupType subclass that handles Active Directory groups. """ def __init__(self, name_attr='cn'): super(ActiveDirectoryGroupType, self).__init__('member', name_attr) class NestedActiveDirectoryGroupType(NestedMemberDNGroupType): """ An LDAPGroupType subclass that handles Active Directory groups with nested group references. """ def __init__(self, name_attr='cn'): super(NestedActiveDirectoryGroupType, self).__init__('member', name_attr) 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) django-auth-ldap-1.2.7/django_auth_ldap/dn.py0000644000076600000240000000306712516737512021705 0ustar psagersstaff00000000000000# Copyright (c) 2009, Peter Sagerson # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # - Redistributions of source code must retain the above copyright notice, this # list of conditions and the following disclaimer. # # - Redistributions in binary form must reproduce the above copyright notice, # this list of conditions and the following disclaimer in the documentation # and/or other materials provided with the distribution. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. """ This is an ldap.dn replacement for old versions of python-ldap. It contains (often naive) implementations of the methods we care about. """ def escape_dn_chars(dn): "Old versions of python-ldap won't get DN escaping. Use with care." return dn django-auth-ldap-1.2.7/django_auth_ldap/models.py0000644000076600000240000000160712601276014022553 0ustar psagersstaff00000000000000from django.conf import settings 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(settings.AUTH_USER_MODEL) is_special = models.BooleanField(default=False) populated = models.BooleanField(default=False) django-auth-ldap-1.2.7/django_auth_ldap/tests.py0000644000076600000240000014067512601275420022443 0ustar psagersstaff00000000000000# 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 warnings import ldap try: import mockldap except ImportError: mockldap = None import django 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 try: from django.utils import unittest except ImportError: 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, NISGroupType 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) @unittest.skipIf(mockldap is None, "django_auth_ldap tests require the mockldap package.") class LDAPTest(TestCase): top = ("o=test", {"o": "test"}) people = ("ou=people,o=test", {"ou": "people"}) 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"] }) # nisGroup objects active_nis = ("cn=active_nis,ou=groups,o=test", { "cn": ["active_nis"], "objectClass": ["nisNetgroup"], "nisNetgroupTriple": ["(,alice,)"] }) staff_nis = ("cn=staff_nis,ou=groups,o=test", { "cn": ["staff_nis"], "objectClass": ["nisNetgroup"], "nisNetgroupTriple": ["(,alice,)"], }) superuser_nis = ("cn=superuser_nis,ou=groups,o=test", { "cn": ["superuser_nis"], "objectClass": ["nisNetgroup"], "nisNetgroupTriple": ["(,alice,)"], }) # Nested groups with a circular reference parent_gon = ("cn=parent_gon,ou=groups,o=test", { "cn": ["parent_gon"], "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, active_nis, staff_nis, superuser_nis, 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) warnings.filterwarnings('ignore', message='.*?AUTH_PROFILE_MODULE', category=DeprecationWarning, module='django_auth_ldap') @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 # # Tests # 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_default_settings(self): class MyBackend(backend.LDAPBackend): default_settings = dict( USER_DN_TEMPLATE='uid=%(user)s,ou=people,o=test' ) self.backend = MyBackend() user_count = User.objects.count() user = self.backend.authenticate(username='alice', password='password') self.assertTrue(not user.has_usable_password()) self.assertEqual(user.username, 'alice') self.assertEqual(User.objects.count(), user_count + 1) self.assertEqual( self.ldapobj.methods_called(), ['initialize', 'simple_bind_s'] ) def test_simple_bind_escaped(self): """ Bind with a username that requires escaping. """ self._init_settings( USER_DN_TEMPLATE='uid=%(user)s,ou=people,o=test' ) user = self.backend.authenticate(username='alice,1', password='password') self.assertEqual(user, None) self.assertEqual( self.ldapobj.methods_called(with_args=True), [('initialize', ('ldap://localhost',), {}), ('simple_bind_s', ('uid=alice\\,1,ou=people,o=test', 'password'), {})] ) def test_new_user_lowercase(self): self._init_settings( USER_DN_TEMPLATE='uid=%(user)s,ou=people,o=test' ) 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_escaped(self): """ Search for a username that requires escaping. """ self._init_settings( USER_SEARCH=LDAPSearch( "ou=people,o=test", ldap.SCOPE_SUBTREE, '(uid=%(user)s)' ) ) user = self.backend.authenticate(username='alice*', password='password') self.assertEqual(user, None) self.assertEqual( self.ldapobj.methods_called(with_args=True), [('initialize', ('ldap://localhost',), {}), ('simple_bind_s', ('', ''), {}), ('search_s', ('ou=people,o=test', ldap.SCOPE_SUBTREE, '(uid=alice\\2a)'), {})] ) def test_search_bind_no_user(self): self._init_settings( USER_SEARCH=LDAPSearch( "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) backend.populate_user.disconnect(handle_populate_user) @unittest.skipIf(django.VERSION >= (1, 7), "Skip profile tests in Django>=1.7") def test_signal_populate_user_profile(self): settings.AUTH_PROFILE_MODULE = 'django_auth_ldap.TestProfile' 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) backend.populate_user_profile.disconnect(handle_populate_user_profile) django.db.models.signals.post_save.disconnect(handle_user_saved, sender=User) def test_signal_ldap_error(self): self._init_settings( BIND_DN='uid=bob,ou=people,o=test', BIND_PASSWORD='bogus', USER_SEARCH=LDAPSearch( "ou=people,o=test", ldap.SCOPE_SUBTREE, '(uid=%(user)s)' ) ) def handle_ldap_error(sender, **kwargs): self.assertEqual(kwargs['context'], 'authenticate') raise kwargs['exception'] backend.ldap_error.connect(handle_ldap_error) with self.assertRaises(ldap.LDAPError): self.backend.authenticate(username='alice', password='password') backend.ldap_error.disconnect(handle_ldap_error) def test_no_update_existing(self): self._init_settings( USER_DN_TEMPLATE='uid=%(user)s,ou=people,o=test', 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_nis_membership(self): self._init_settings( USER_DN_TEMPLATE='uid=%(user)s,ou=people,o=test', GROUP_SEARCH=LDAPSearch('ou=groups,o=test', ldap.SCOPE_SUBTREE), GROUP_TYPE=NISGroupType(), USER_FLAGS_BY_GROUP={ 'is_active': "cn=active_nis,ou=groups,o=test", 'is_staff': "cn=staff_nis,ou=groups,o=test", 'is_superuser': "cn=superuser_nis,ou=groups,o=test" } ) alice = self.backend.authenticate(username='alice', password='password') bob = self.backend.authenticate(username='bob', password='password') self.assertTrue(alice.is_active) self.assertTrue(alice.is_staff) self.assertTrue(alice.is_superuser) self.assertTrue(not bob.is_active) self.assertTrue(not bob.is_staff) self.assertTrue(not bob.is_superuser) def test_nested_dn_group_membership(self): self._init_settings( USER_DN_TEMPLATE='uid=%(user)s,ou=people,o=test', 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) @unittest.skipIf(django.VERSION >= (1, 7), "Skip profile tests in Django>=1.7") 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_nis_group_permissions(self): self._init_settings( USER_DN_TEMPLATE='uid=%(user)s,ou=people,o=test', GROUP_SEARCH=LDAPSearch('ou=groups,o=test', ldap.SCOPE_SUBTREE, '(objectClass=nisNetgroup)'), GROUP_TYPE=NISGroupType(), FIND_GROUP_PERMS=True ) self._init_groups() alice = User.objects.create(username='alice') alice = self.backend.get_user(alice.pk) self.assertEqual(self.backend.get_group_permissions(alice), set(["auth.add_user", "auth.change_user"])) self.assertEqual(self.backend.get_all_permissions(alice), set(["auth.add_user", "auth.change_user"])) self.assertTrue(self.backend.has_perm(alice, "auth.add_user")) self.assertTrue(self.backend.has_module_perms(alice, "auth")) def test_foreign_user_permissions(self): self._init_settings( USER_DN_TEMPLATE='uid=%(user)s,ou=people,o=test', 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_authorize_external_unknown(self): self._init_settings( USER_SEARCH=LDAPSearch( "ou=people,o=test", ldap.SCOPE_SUBTREE, '(uid=%(user)s)' ), GROUP_SEARCH=LDAPSearch('ou=groups,o=test', ldap.SCOPE_SUBTREE), GROUP_TYPE=MemberDNGroupType(member_attr='member'), FIND_GROUP_PERMS=True, AUTHORIZE_ALL_USERS=True ) self._init_groups() alice = User.objects.create(username='not-in-ldap') self.assertEqual(self.backend.get_group_permissions(alice), set()) def test_create_without_auth(self): self._init_settings( USER_DN_TEMPLATE='uid=%(user)s,ou=people,o=test', ) 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")) # # Utilities # 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) active_nis = Group.objects.create(name='active_nis') active_nis.permissions.add(*permissions) django-auth-ldap-1.2.7/django_auth_ldap.egg-info/0000755000076600000240000000000012602567034022412 5ustar psagersstaff00000000000000django-auth-ldap-1.2.7/django_auth_ldap.egg-info/dependency_links.txt0000644000076600000240000000000112602567031026455 0ustar psagersstaff00000000000000 django-auth-ldap-1.2.7/django_auth_ldap.egg-info/PKG-INFO0000644000076600000240000001005012602567031023500 0ustar psagersstaff00000000000000Metadata-Version: 1.1 Name: django-auth-ldap Version: 1.2.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 version is supported on Python 2.6, 2.7, 3.3, and 3.4; and Django >= 1.3. Under Python 2, it requires `python-ldap `_ >= 2.0; under Python 3, it uses `pyldap `_. 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_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" } # 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 django-auth-ldap-1.2.7/django_auth_ldap.egg-info/requires.txt0000644000076600000240000000003112602567031025001 0ustar psagersstaff00000000000000django python-ldap >= 2.0django-auth-ldap-1.2.7/django_auth_ldap.egg-info/SOURCES.txt0000644000076600000240000000432212602567033024276 0ustar psagersstaff00000000000000CHANGES LICENSE MANIFEST.in README setup.py tox.ini django_auth_ldap/__init__.py django_auth_ldap/backend.py django_auth_ldap/config.py django_auth_ldap/dn.py django_auth_ldap/models.py django_auth_ldap/tests.py django_auth_ldap.egg-info/PKG-INFO django_auth_ldap.egg-info/SOURCES.txt django_auth_ldap.egg-info/dependency_links.txt django_auth_ldap.egg-info/requires.txt django_auth_ldap.egg-info/top_level.txt docs/Makefile docs/_templates/globaltoc.html docs/archive/versions/1.0.19/.buildinfo docs/archive/versions/1.0.19/genindex.html docs/archive/versions/1.0.19/index.html docs/archive/versions/1.0.19/objects.inv docs/archive/versions/1.0.19/py-modindex.html docs/archive/versions/1.0.19/search.html docs/archive/versions/1.0.19/searchindex.js docs/archive/versions/1.0.19/_sources/index.txt docs/archive/versions/1.0.19/_static/ajax-loader.gif docs/archive/versions/1.0.19/_static/basic.css docs/archive/versions/1.0.19/_static/comment-bright.png docs/archive/versions/1.0.19/_static/comment-close.png docs/archive/versions/1.0.19/_static/comment.png docs/archive/versions/1.0.19/_static/default.css docs/archive/versions/1.0.19/_static/doctools.js docs/archive/versions/1.0.19/_static/down-pressed.png docs/archive/versions/1.0.19/_static/down.png docs/archive/versions/1.0.19/_static/file.png docs/archive/versions/1.0.19/_static/jquery.js docs/archive/versions/1.0.19/_static/minus.png docs/archive/versions/1.0.19/_static/plus.png docs/archive/versions/1.0.19/_static/pygments.css docs/archive/versions/1.0.19/_static/searchtools.js docs/archive/versions/1.0.19/_static/sidebar.js docs/archive/versions/1.0.19/_static/underscore.js docs/archive/versions/1.0.19/_static/up-pressed.png docs/archive/versions/1.0.19/_static/up.png docs/archive/versions/1.0.19/_static/websupport.js docs/ext/daldocs.py docs/source/.spell.utf-8.add docs/source/.spell.utf-8.add.spl docs/source/authentication.rst docs/source/changes.rst docs/source/conf.py docs/source/example.rst docs/source/groups.rst docs/source/index.rst docs/source/install.rst docs/source/logging.rst docs/source/multiconfig.rst docs/source/performance.rst docs/source/permissions.rst docs/source/reference.rst docs/source/users.rst test/.coveragerc test/manage.py test/settings.py test/urls.pydjango-auth-ldap-1.2.7/django_auth_ldap.egg-info/top_level.txt0000644000076600000240000000002112602567031025132 0ustar psagersstaff00000000000000django_auth_ldap django-auth-ldap-1.2.7/docs/0000755000076600000240000000000012602567034016365 5ustar psagersstaff00000000000000django-auth-ldap-1.2.7/docs/_templates/0000755000076600000240000000000012602567034020522 5ustar psagersstaff00000000000000django-auth-ldap-1.2.7/docs/_templates/globaltoc.html0000644000076600000240000000051112242245151023344 0ustar psagersstaff00000000000000{# 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. #}

{{ _('Table Of Contents') }}

{{ toctree(maxdepth=2) }} django-auth-ldap-1.2.7/docs/archive/0000755000076600000240000000000012602567034020006 5ustar psagersstaff00000000000000django-auth-ldap-1.2.7/docs/archive/versions/0000755000076600000240000000000012602567034021656 5ustar psagersstaff00000000000000django-auth-ldap-1.2.7/docs/archive/versions/1.0.19/0000755000076600000240000000000012602567034022404 5ustar psagersstaff00000000000000django-auth-ldap-1.2.7/docs/archive/versions/1.0.19/.buildinfo0000644000076600000240000000034612242245103024351 0ustar psagersstaff00000000000000# 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 django-auth-ldap-1.2.7/docs/archive/versions/1.0.19/_sources/0000755000076600000240000000000012602567034024226 5ustar psagersstaff00000000000000django-auth-ldap-1.2.7/docs/archive/versions/1.0.19/_sources/index.txt0000644000076600000240000011223412242245103026067 0ustar psagersstaff00000000000000================================ Django authentication using LDAP ================================ This authentication backend enables a Django project to authenticate against any LDAP server. To use it, add :class:`django_auth_ldap.backend.LDAPBackend` to AUTHENTICATION_BACKENDS. It is not necessary to add `django_auth_ldap` to INSTALLED_APPLICATIONS unless you would like to run the unit tests. LDAP configuration can be as simple as a single distinguished name template, but there are many rich options for working with :class:`~django.contrib.auth.models.User` objects, groups, and permissions. This backend depends on the `python-ldap `_ module. .. note:: :class:`~django_auth_ldap.backend.LDAPBackend` does not inherit from :class:`~django.contrib.auth.backends.ModelBackend`. It is possible to use :class:`~django_auth_ldap.backend.LDAPBackend` exclusively by configuring it to draw group membership from the LDAP server. However, if you would like to assign permissions to individual users or add users to groups within Django, you'll need to have both backends installed: .. code-block:: python AUTHENTICATION_BACKENDS = ( 'django_auth_ldap.backend.LDAPBackend', 'django.contrib.auth.backends.ModelBackend', ) Configuring basic authentication ================================ If your LDAP server isn't running locally on the default port, you'll want to start by setting :ref:`AUTH_LDAP_SERVER_URI` to point to your server. .. code-block:: python AUTH_LDAP_SERVER_URI = "ldap://ldap.example.com" That done, the first step is to authenticate a username and password against the LDAP service. There are two ways to do this, called search/bind and simply bind. The first one involves connecting to the LDAP server either anonymously or with a fixed account and searching for the distinguished name of the authenticating user. Then we can attempt to bind again with the user's password. The second method is to derive the user's DN from his username and attempt to bind as the user directly. Because LDAP searches appear elsewhere in the configuration, the :class:`~django_auth_ldap.config.LDAPSearch` class is provided to encapsulate search information. In this case, the filter parameter should contain the placeholder ``%(user)s``. A simple configuration for the search/bind approach looks like this (some defaults included for completeness):: import ldap from django_auth_ldap.config import LDAPSearch AUTH_LDAP_BIND_DN = "" AUTH_LDAP_BIND_PASSWORD = "" AUTH_LDAP_USER_SEARCH = LDAPSearch("ou=users,dc=example,dc=com", ldap.SCOPE_SUBTREE, "(uid=%(user)s)") This will perform an anonymous bind, search under ``"ou=users,dc=example,dc=com"`` for an object with a uid matching the user's name, and try to bind using that DN and the user's password. The search must return exactly one result or authentication will fail. If you can't search anonymously, you can set :ref:`AUTH_LDAP_BIND_DN` to the distinguished name of an authorized user and :ref:`AUTH_LDAP_BIND_PASSWORD` to the password. To skip the search phase, set :ref:`AUTH_LDAP_USER_DN_TEMPLATE` to a template that will produce the authenticating user's DN directly. This template should have one placeholder, ``%(user)s``. If the previous example had used ``ldap.SCOPE_ONELEVEL``, the following would be a more straightforward (and efficient) equivalent:: AUTH_LDAP_USER_DN_TEMPLATE = "uid=%(user)s,ou=users,dc=example,dc=com" 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 connection to the LDAP server, you can either use an ``ldaps://`` URL or enable the StartTLS extension. The latter is generally the preferred mechanism. To enable StartTLS, set :ref:`AUTH_LDAP_START_TLS` to ``True``:: AUTH_LDAP_START_TLS = True Working with groups =================== Working with groups in LDAP can be a tricky business, mostly because there are so many different kinds. This module includes an extensible API for working with any kind of group and includes implementations for the most common ones. :class:`~django_auth_ldap.config.LDAPGroupType` is a base class whose concrete subclasses can determine group membership for particular grouping mechanisms. Three built-in subclasses cover most grouping mechanisms: * :class:`~django_auth_ldap.config.PosixGroupType` * :class:`~django_auth_ldap.config.MemberDNGroupType` * :class:`~django_auth_ldap.config.NestedMemberDNGroupType` posixGroup objects are somewhat specialized, so they get their own class. The other two cover mechanisms whereby a group object stores a list of its members as distinguished names. This includes groupOfNames, groupOfUniqueNames, and Active Directory groups, among others. The nested variant allows groups to contain other groups, to as many levels as you like. For convenience and readability, several trivial subclasses of the above are provided: * :class:`~django_auth_ldap.config.GroupOfNamesType` * :class:`~django_auth_ldap.config.NestedGroupOfNamesType` * :class:`~django_auth_ldap.config.GroupOfUniqueNamesType` * :class:`~django_auth_ldap.config.NestedGroupOfUniqueNamesType` * :class:`~django_auth_ldap.config.ActiveDirectoryGroupType` * :class:`~django_auth_ldap.config.NestedActiveDirectoryGroupType` To get started, you'll need to provide some basic information about your LDAP groups. :ref:`AUTH_LDAP_GROUP_SEARCH` is an :class:`~django_auth_ldap.config.LDAPSearch` object that identifies the set of relevant group objects. That is, all groups that users might belong to as well as any others that we might need to know about (in the case of nested groups, for example). :ref:`AUTH_LDAP_GROUP_TYPE` is an instance of the class corresponding to the type of group that will be returned by :ref:`AUTH_LDAP_GROUP_SEARCH`. All groups referenced elsewhere in the configuration must be of this type and part of the search results. .. code-block:: python import ldap from django_auth_ldap.config import LDAPSearch, GroupOfNamesType AUTH_LDAP_GROUP_SEARCH = LDAPSearch("ou=groups,dc=example,dc=com", ldap.SCOPE_SUBTREE, "(objectClass=groupOfNames)" ) AUTH_LDAP_GROUP_TYPE = GroupOfNamesType() The simplest use of groups is to limit the users who are allowed to log in. If :ref:`AUTH_LDAP_REQUIRE_GROUP` is set, then only users who are members of that group will successfully authenticate. :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. 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. 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 allowed in usernames, so :class:`~django_auth_ldap.backend.LDAPBackend` includes a pair of hooks, :meth:`~django_auth_ldap.backend.LDAPBackend.ldap_to_django_username` and :meth:`~django_auth_ldap.backend.LDAPBackend.django_to_ldap_username`, to translate between LDAP usernames and Django usernames. You'll need this, for example, if your LDAP names have periods in them. You can subclass :class:`~django_auth_ldap.backend.LDAPBackend` to implement these hooks; by default the username is not modified. :class:`~django.contrib.auth.models.User` objects that are authenticated by :class:`~django_auth_ldap.backend.LDAPBackend` will have an :attr:`~django.contrib.auth.models.User.ldap_username` attribute with the original (LDAP) username. :attr:`~django.contrib.auth.models.User.username` will, of course, be the Django username. LDAP directories tend to contain much more information about users that you may wish to propagate. A pair of settings, :ref:`AUTH_LDAP_USER_ATTR_MAP` and :ref:`AUTH_LDAP_PROFILE_ATTR_MAP`, serve to copy directory information into :class:`~django.contrib.auth.models.User` and profile objects. These are dictionaries that map user and profile model keys, respectively, to (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", "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 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 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 :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 unusable password set. This will only happen when the user is created, so if you set a valid password in Django, the user will be able to log in through :class:`~django.contrib.auth.backends.ModelBackend` (if configured) even if he is rejected by LDAP. This is not generally recommended, but could be useful as a fail-safe for selected users in case the LDAP server is unavailable. Permissions =========== Groups are useful for more than just populating the user's ``is_*`` fields. :class:`~django_auth_ldap.backend.LDAPBackend` would not be complete without some way to turn a user's LDAP group memberships into Django model permissions. In fact, there are two ways to do this. Ultimately, both mechanisms need some way to map LDAP groups to Django groups. Implementations of :class:`~django_auth_ldap.config.LDAPGroupType` will have an algorithm for deriving the Django group name from the LDAP group. Clients that need to modify this behavior can subclass the :class:`~django_auth_ldap.config.LDAPGroupType` class. All of the built-in implementations take a ``name_attr`` argument to ``__init__``, which specifies the LDAP attribute from which to take the Django group name. By default, the ``cn`` attribute is used. The least invasive way to map group permissions is to set :ref:`AUTH_LDAP_FIND_GROUP_PERMS` to ``True``. :class:`~django_auth_ldap.backend.LDAPBackend` will then find all of the LDAP groups that a user belongs to, map them to Django groups, and load the permissions for those groups. You will need to create the Django groups yourself, generally through the admin interface. To minimize traffic to the LDAP server, :class:`~django_auth_ldap.backend.LDAPBackend` can make use of Django's cache framework to keep a copy of a user's LDAP group memberships. To enable this feature, set :ref:`AUTH_LDAP_CACHE_GROUPS` to ``True``. You can also set :ref:`AUTH_LDAP_GROUP_CACHE_TIMEOUT` to override the timeout of cache entries (in seconds). .. code-block:: python AUTH_LDAP_CACHE_GROUPS = True AUTH_LDAP_GROUP_CACHE_TIMEOUT = 300 The second way to turn LDAP group memberships into permissions is to mirror the groups themselves. If :ref:`AUTH_LDAP_MIRROR_GROUPS` is ``True``, then every time a user logs in, :class:`~django_auth_ldap.backend.LDAPBackend` will update the database with the user's LDAP groups. Any group that doesn't exist will be created and the user's Django group membership will be updated to exactly match his LDAP group membership. Note that if the LDAP server has nested groups, the Django database will end up with a flattened representation. This approach has two main differences from :ref:`AUTH_LDAP_FIND_GROUP_PERMS`. First, :ref:`AUTH_LDAP_FIND_GROUP_PERMS` will query for LDAP group membership either for every request or according to the cache timeout. With group mirroring, membership will be updated when the user authenticates. This may not be appropriate for sites with long session timeouts. The second difference is that with :ref:`AUTH_LDAP_FIND_GROUP_PERMS`, there is no way for clients to determine a user's group memberships, only their permissions. If you want to make decisions based directly on group membership, you'll have to mirror the groups. :class:`~django_auth_ldap.backend.LDAPBackend` has one more feature pertaining to permissions, which is the ability to handle authorization for users that it did not authenticate. For example, you might be using Django's RemoteUserBackend to map externally authenticated users to Django users. By setting :ref:`AUTH_LDAP_AUTHORIZE_ALL_USERS`, :class:`~django_auth_ldap.backend.LDAPBackend` will map these users to LDAP users in the normal way in order to provide authorization information. Note that this does *not* work with :ref:`AUTH_LDAP_MIRROR_GROUPS`; group mirroring is a feature of authentication, not authorization. Logging ======= :class:`~django_auth_ldap.backend.LDAPBackend` uses the standard logging module to log debug and warning messages to the logger named ``'django_auth_ldap'``. If you need debug messages to help with configuration issues, you should add a handler to this logger. Note that this logger is initialized with a level of NOTSET, so you may need to change the level of the logger in order to get debug messages. .. code-block:: python import logging logger = logging.getLogger('django_auth_ldap') logger.addHandler(logging.StreamHandler()) logger.setLevel(logging.DEBUG) More options ============ Miscellaneous settings for :class:`~django_auth_ldap.backend.LDAPBackend`: * :ref:`AUTH_LDAP_GLOBAL_OPTIONS`: A dictionary of options to pass to python-ldap via ``ldap.set_option()``. * :ref:`AUTH_LDAP_CONNECTION_OPTIONS`: A dictionary of options to pass to each LDAPObject instance via ``LDAPObject.set_option()``. Performance =========== :class:`~django_auth_ldap.backend.LDAPBackend` is carefully designed not to require a connection to the LDAP service for every request. Of course, this depends heavily on how it is configured. If LDAP traffic or latency is a concern for your deployment, this section has a few tips on minimizing it, in decreasing order of impact. #. **Cache groups**. If :ref:`AUTH_LDAP_FIND_GROUP_PERMS` is ``True``, the default behavior is to reload a user's group memberships on every request. This is the safest behavior, as any membership change takes effect immediately, but it is expensive. If possible, set :ref:`AUTH_LDAP_CACHE_GROUPS` to ``True`` to remove most of this traffic. Alternatively, you might consider using :ref:`AUTH_LDAP_MIRROR_GROUPS` and relying on :class:`~django.contrib.auth.backends.ModelBackend` to supply group permissions. #. **Don't access user.ldap_user.***. These properties are only cached on a per-request basis. If you can propagate LDAP attributes to a :class:`~django.contrib.auth.models.User` or profile object, they will only be updated at login. ``user.ldap_user.attrs`` triggers an LDAP connection for every request in which it's accessed. If you're not using :ref:`AUTH_LDAP_USER_DN_TEMPLATE`, then accessing ``user.ldap_user.dn`` will also trigger an LDAP connection. #. **Use simpler group types**. Some grouping mechanisms are more expensive than others. This will often be outside your control, but it's important to note that the extra functionality of more complex group types like :class:`~django_auth_ldap.config.NestedGroupOfNamesType` is not free and will generally require a greater number and complexity of LDAP queries. #. **Use direct binding**. Binding with :ref:`AUTH_LDAP_USER_DN_TEMPLATE` is a little bit more efficient than relying on :ref:`AUTH_LDAP_USER_SEARCH`. Specifically, it saves two LDAP operations (one bind and one search) per login. Example configuration ===================== Here is a complete example configuration from :file:`settings.py` that exercises nearly all of the features. In this example, we're authenticating against a global pool of users in the directory, but we have a special area set aside for Django groups (ou=django,ou=groups,dc=example,dc=com). Remember that most of this is optional if you just need simple authentication. Some default settings and arguments are included for completeness. .. code-block:: python import ldap from django_auth_ldap.config import LDAPSearch, GroupOfNamesType # Baseline configuration. AUTH_LDAP_SERVER_URI = "ldap://ldap.example.com" AUTH_LDAP_BIND_DN = "cn=django-agent,dc=example,dc=com" AUTH_LDAP_BIND_PASSWORD = "phlebotinum" AUTH_LDAP_USER_SEARCH = LDAPSearch("ou=users,dc=example,dc=com", ldap.SCOPE_SUBTREE, "(uid=%(user)s)") # or perhaps: # AUTH_LDAP_USER_DN_TEMPLATE = "uid=%(user)s,ou=users,dc=example,dc=com" # Set up the basic group parameters. AUTH_LDAP_GROUP_SEARCH = LDAPSearch("ou=django,ou=groups,dc=example,dc=com", ldap.SCOPE_SUBTREE, "(objectClass=groupOfNames)" ) AUTH_LDAP_GROUP_TYPE = GroupOfNamesType(name_attr="cn") # 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', ) Reference ========= Settings -------- .. _AUTH_LDAP_ALWAYS_UPDATE_USER: AUTH_LDAP_ALWAYS_UPDATE_USER ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Default: ``True`` If ``True``, the fields of a :class:`~django.contrib.auth.models.User` object will be updated with the latest values from the LDAP directory every time the user logs in. Otherwise the :class:`~django.contrib.auth.models.User` object will only be populated when it is automatically created. .. _AUTH_LDAP_AUTHORIZE_ALL_USERS: AUTH_LDAP_AUTHORIZE_ALL_USERS ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Default: ``False`` If ``True``, :class:`~django_auth_ldap.backend.LDAPBackend` will be able furnish permissions for any Django user, regardless of which backend authenticated it. .. _AUTH_LDAP_BIND_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 ~~~~~~~~~~~~~~~~~ Default: ``''`` (Empty string) The distinguished name to use when binding to the LDAP server (with :ref:`AUTH_LDAP_BIND_PASSWORD`). Use the empty string (the default) for an anonymous bind. To authenticate a user, we will bind with that user's DN and password, but for all other LDAP operations, we will be bound as the DN in this setting. For example, if :ref:`AUTH_LDAP_USER_DN_TEMPLATE` is not set, we'll use this to search for the user. If :ref:`AUTH_LDAP_FIND_GROUP_PERMS` is ``True``, we'll also use it to determine group membership. .. _AUTH_LDAP_BIND_PASSWORD: AUTH_LDAP_BIND_PASSWORD ~~~~~~~~~~~~~~~~~~~~~~~ Default: ``''`` (Empty string) The password to use with :ref:`AUTH_LDAP_BIND_DN`. .. _AUTH_LDAP_CACHE_GROUPS: AUTH_LDAP_CACHE_GROUPS ~~~~~~~~~~~~~~~~~~~~~~ Default: ``False`` If ``True``, LDAP group membership will be cached using Django's cache framework. The cache timeout can be customized with :ref:`AUTH_LDAP_GROUP_CACHE_TIMEOUT`. .. _AUTH_LDAP_CONNECTION_OPTIONS: AUTH_LDAP_CONNECTION_OPTIONS ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Default: ``{}`` A dictionary of options to pass to each connection to the LDAP server via ``LDAPObject.set_option()``. Keys are ``ldap.OPT_*`` constants. .. _AUTH_LDAP_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. .. _AUTH_LDAP_FIND_GROUP_PERMS: AUTH_LDAP_FIND_GROUP_PERMS ~~~~~~~~~~~~~~~~~~~~~~~~~~ Default: ``False`` If ``True``, :class:`~django_auth_ldap.backend.LDAPBackend` will furnish group permissions based on the LDAP groups the authenticated user belongs to. :ref:`AUTH_LDAP_GROUP_SEARCH` and :ref:`AUTH_LDAP_GROUP_TYPE` must also be set. .. _AUTH_LDAP_GLOBAL_OPTIONS: AUTH_LDAP_GLOBAL_OPTIONS ~~~~~~~~~~~~~~~~~~~~~~~~ Default: ``{}`` A dictionary of options to pass to ``ldap.set_option()``. Keys are ``ldap.OPT_*`` constants. .. _AUTH_LDAP_GROUP_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 ~~~~~~~~~~~~~~~~~~~~~~ Default: ``None`` An :class:`~django_auth_ldap.config.LDAPSearch` object that finds all LDAP groups that users might belong to. If your configuration makes any references to LDAP groups, this and :ref:`AUTH_LDAP_GROUP_TYPE` must be set. .. _AUTH_LDAP_GROUP_TYPE: AUTH_LDAP_GROUP_TYPE ~~~~~~~~~~~~~~~~~~~~ Default: ``None`` An :class:`~django_auth_ldap.config.LDAPGroupType` instance describing the type of group returned by :ref:`AUTH_LDAP_GROUP_SEARCH`. .. _AUTH_LDAP_MIRROR_GROUPS: AUTH_LDAP_MIRROR_GROUPS ~~~~~~~~~~~~~~~~~~~~~~~ Default: ``False`` If ``True``, :class:`~django_auth_ldap.backend.LDAPBackend` will mirror a user's LDAP group membership in the Django database. Any time a user authenticates, we will create all of his LDAP groups as Django groups and update his Django group membership to exactly match his LDAP group membership. If the LDAP server has nested groups, the Django database will end up with a flattened representation. .. _AUTH_LDAP_PROFILE_ATTR_MAP: AUTH_LDAP_PROFILE_ATTR_MAP ~~~~~~~~~~~~~~~~~~~~~~~~~~ Default: ``{}`` A mapping from user profile field names to LDAP attribute names. A user's profile will be populated from his LDAP attributes at login. .. _AUTH_LDAP_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 ~~~~~~~~~~~~~~~~~~~~~~~ Default: ``None`` The distinguished name of a group; authentication will fail for any user that does not belong to this group. .. _AUTH_LDAP_SERVER_URI: AUTH_LDAP_SERVER_URI ~~~~~~~~~~~~~~~~~~~~ Default: ``ldap://localhost`` The URI of the LDAP server. This can be any URI that is supported by your underlying LDAP libraries. .. _AUTH_LDAP_START_TLS: AUTH_LDAP_START_TLS ~~~~~~~~~~~~~~~~~~~ Default: ``False`` If ``True``, each connection to the LDAP server will call start_tls to enable TLS encryption over the standard LDAP port. There are a number of configuration options that can be given to :ref:`AUTH_LDAP_GLOBAL_OPTIONS` that affect the TLS connection. For example, ``ldap.OPT_X_TLS_REQUIRE_CERT`` can be set to ``ldap.OPT_X_TLS_NEVER`` to disable certificate verification, perhaps to allow self-signed certificates. .. _AUTH_LDAP_USER_ATTR_MAP: AUTH_LDAP_USER_ATTR_MAP ~~~~~~~~~~~~~~~~~~~~~~~ Default: ``{}`` A mapping from :class:`~django.contrib.auth.models.User` field names to LDAP attribute names. A users's :class:`~django.contrib.auth.models.User` object will be populated from his LDAP attributes at login. .. _AUTH_LDAP_USER_DN_TEMPLATE: AUTH_LDAP_USER_DN_TEMPLATE ~~~~~~~~~~~~~~~~~~~~~~~~~~ Default: ``None`` A string template that describes any user's distinguished name based on the username. This must contain the placeholder ``%(user)s``. .. _AUTH_LDAP_USER_FLAGS_BY_GROUP: AUTH_LDAP_USER_FLAGS_BY_GROUP ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Default: ``{}`` A mapping from boolean :class:`~django.contrib.auth.models.User` field names to distinguished names of LDAP groups. The corresponding field is set to ``True`` or ``False`` according to whether the user is a member of the group. .. _AUTH_LDAP_USER_SEARCH: AUTH_LDAP_USER_SEARCH ~~~~~~~~~~~~~~~~~~~~~ Default: ``None`` An :class:`~django_auth_ldap.config.LDAPSearch` object that will locate a user in the directory. The filter parameter should contain the placeholder ``%(user)s`` for the username. It must return exactly one result for authentication to succeed. 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:: LDAPGroupType The base class for objects that will determine group membership for various LDAP grouping mechanisms. Implementations are provided for common group types or you can write your own. See the source code for subclassing notes. .. method:: __init__(name_attr='cn') By default, LDAP groups will be mapped to Django groups by taking the first value of the cn attribute. You can specify a different attribute with ``name_attr``. .. class:: PosixGroupType A concrete subclass of :class:`~django_auth_ldap.config.LDAPGroupType` that handles the ``posixGroup`` object class. This checks for both primary group and group membership. .. method:: __init__(name_attr='cn') .. class:: MemberDNGroupType A concrete subclass of :class:`~django_auth_ldap.config.LDAPGroupType` that handles grouping mechanisms wherein the group object contains a list of its member DNs. .. method:: __init__(member_attr, name_attr='cn') * ``member_attr``: The attribute on the group object that contains a list of member DNs. 'member' and 'uniqueMember' are common examples. .. class:: NestedMemberDNGroupType Similar to :class:`~django_auth_ldap.config.MemberDNGroupType`, except this allows groups to contain other groups as members. Group hierarchies will be traversed to determine membership. .. method:: __init__(member_attr, name_attr='cn') As above. .. class:: GroupOfNamesType A concrete subclass of :class:`~django_auth_ldap.config.MemberDNGroupType` that handles the ``groupOfNames`` object class. Equivalent to ``MemberDNGroupType('member')``. .. method:: __init__(name_attr='cn') .. class:: NestedGroupOfNamesType A concrete subclass of :class:`~django_auth_ldap.config.NestedMemberDNGroupType` that handles the ``groupOfNames`` object class. Equivalent to ``NestedMemberDNGroupType('member')``. .. method:: __init__(name_attr='cn') .. class:: GroupOfUniqueNamesType A concrete subclass of :class:`~django_auth_ldap.config.MemberDNGroupType` that handles the ``groupOfUniqueNames`` object class. Equivalent to ``MemberDNGroupType('uniqueMember')``. .. method:: __init__(name_attr='cn') .. class:: NestedGroupOfUniqueNamesType A concrete subclass of :class:`~django_auth_ldap.config.NestedMemberDNGroupType` that handles the ``groupOfUniqueNames`` object class. Equivalent to ``NestedMemberDNGroupType('uniqueMember')``. .. method:: __init__(name_attr='cn') .. class:: ActiveDirectoryGroupType A concrete subclass of :class:`~django_auth_ldap.config.MemberDNGroupType` that handles Active Directory groups. Equivalent to ``MemberDNGroupType('member')``. .. method:: __init__(name_attr='cn') .. class:: NestedActiveDirectoryGroupType A concrete subclass of :class:`~django_auth_ldap.config.NestedMemberDNGroupType` that handles Active Directory groups. Equivalent to ``NestedMemberDNGroupType('member')``. .. method:: __init__(name_attr='cn') Backend ------- .. module:: django_auth_ldap.backend .. 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. .. method:: populate_user(username) Populates the Django user for the given LDAP username. This connects to the LDAP directory with the default credentials and attempts to populate the indicated Django user as if they had just logged in. :ref:`AUTH_LDAP_ALWAYS_UPDATE_USER` is ignored (assumed ``True``). .. method:: get_or_create_user(self, username, ldap_user) Given a username and an LDAP user object, this must return the associated Django User object. The ``username`` argument has already been passed through :meth:`~django_auth_ldap.backend.LDAPBackend.ldap_to_django_username`. You can get information about the LDAP user via ``ldap_user.dn`` and ``ldap_user.attrs``. The return value must be the same as ``User.objects.get_or_create()``: a (User, created) two-tuple. The default implementation 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. .. method:: django_to_ldap_username(username) The inverse of :meth:`~django_auth_ldap.backend.LDAPBackend.ldap_to_django_username`. If this is not symmetrical to :meth:`~django_auth_ldap.backend.LDAPBackend.ldap_to_django_username`, the behavior is undefined. License ======= Copyright (c) 2009, Peter Sagerson All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: - Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. - Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. django-auth-ldap-1.2.7/docs/archive/versions/1.0.19/_static/0000755000076600000240000000000012602567034024032 5ustar psagersstaff00000000000000django-auth-ldap-1.2.7/docs/archive/versions/1.0.19/_static/ajax-loader.gif0000644000076600000240000000124112242245103026674 0ustar psagersstaff00000000000000GIF89aU|NU|l!Created with ajaxload.info! ! NETSCAPE2.0,30Ikc:Nf E1º.`q-[9ݦ9 JkH! ,4N!  DqBQT`1 `LE[|ua C%$*! ,62#+AȐ̔V/cNIBap ̳ƨ+Y2d! ,3b%+2V_ ! 1DaFbR]=08,Ȥr9L! ,2r'+JdL &v`\bThYB)@<&,ȤR! ,3 9tڞ0!.BW1  sa50 m)J! ,2 ٜU]qp`a4AF0` @1Α! ,20IeBԜ) q10ʰPaVڥ ub[;django-auth-ldap-1.2.7/docs/archive/versions/1.0.19/_static/basic.css0000644000076600000240000002041712242245103025617 0ustar psagersstaff00000000000000/* * 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; } }django-auth-ldap-1.2.7/docs/archive/versions/1.0.19/_static/comment-bright.png0000644000076600000240000000665412242245103027460 0ustar psagersstaff00000000000000PNG  IHDRa OiCCPPhotoshop ICC profilexڝSgTS=BKKoR RB&*! J!QEEȠQ, !{kּ> H3Q5 B.@ $pd!s#~<<+"x M0B\t8K@zB@F&S`cbP-`'{[! eDh;VEX0fK9-0IWfH  0Q){`##xFW<+*x<$9E[-qWW.(I+6aa@.y24x6_-"bbϫp@t~,/;m%h^ uf@Wp~<5j>{-]cK'Xto(hw?G%fIq^D$.Tʳ?D*A, `6B$BB dr`)B(Ͱ*`/@4Qhp.U=pa( Aa!ڈbX#!H$ ɈQ"K5H1RT UH=r9\F;2G1Q= C7F dt1r=6Ыhڏ>C03l0.B8, c˱" VcϱwE 6wB aAHXLXNH $4 7 Q'"K&b21XH,#/{C7$C2'ITFnR#,4H#dk9, +ȅ3![ b@qS(RjJ4e2AURݨT5ZBRQ4u9̓IKhhitݕNWGw Ljg(gwLӋT071oUX**| J&*/Tު UUT^S}FU3S ԖUPSSg;goT?~YYLOCQ_ cx,!k u5&|v*=9C3J3WRf?qtN (~))4L1e\kXHQG6EYAJ'\'GgSSݧ M=:.kDwn^Loy}/TmG X $ <5qo</QC]@Caaᄑ.ȽJtq]zۯ6iܟ4)Y3sCQ? 0k߬~OCOg#/c/Wװwa>>r><72Y_7ȷOo_C#dz%gA[z|!?:eAAA!h쐭!ΑiP~aa~ 'W?pX15wCsDDDޛg1O9-J5*>.j<74?.fYXXIlK9.*6nl {/]py.,:@LN8A*%w% yg"/6шC\*NH*Mz쑼5y$3,幄'L Lݛ:v m2=:1qB!Mggfvˬen/kY- BTZ(*geWf͉9+̳ې7ᒶKW-X潬j9(xoʿܔĹdff-[n ڴ VE/(ۻCɾUUMfeI?m]Nmq#׹=TR+Gw- 6 U#pDy  :v{vg/jBFS[b[O>zG499?rCd&ˮ/~јѡ򗓿m|x31^VwwO| (hSЧc3-bKGD pHYs  tIME 6 B\<IDAT8˅Kh]es1mA`jh[-E(FEaA!bIȐ*BX"؁4)NURZ!Mhjssm؋^-\gg ]o|Ҭ[346>zd ]#8Oݺt{5uIXN!I=@Vf=v1}e>;fvnvxaHrʪJF`D¹WZ]S%S)WAb |0K=So7D~\~q-˟\aMZ,S'*} F`Nnz674U H3Q5 B.@ $pd!s#~<<+"x M0B\t8K@zB@F&S`cbP-`'{[! eDh;VEX0fK9-0IWfH  0Q){`##xFW<+*x<$9E[-qWW.(I+6aa@.y24x6_-"bbϫp@t~,/;m%h^ uf@Wp~<5j>{-]cK'Xto(hw?G%fIq^D$.Tʳ?D*A, `6B$BB dr`)B(Ͱ*`/@4Qhp.U=pa( Aa!ڈbX#!H$ ɈQ"K5H1RT UH=r9\F;2G1Q= C7F dt1r=6Ыhڏ>C03l0.B8, c˱" VcϱwE 6wB aAHXLXNH $4 7 Q'"K&b21XH,#/{C7$C2'ITFnR#,4H#dk9, +ȅ3![ b@qS(RjJ4e2AURݨT5ZBRQ4u9̓IKhhitݕNWGw Ljg(gwLӋT071oUX**| J&*/Tު UUT^S}FU3S ԖUPSSg;goT?~YYLOCQ_ cx,!k u5&|v*=9C3J3WRf?qtN (~))4L1e\kXHQG6EYAJ'\'GgSSݧ M=:.kDwn^Loy}/TmG X $ <5qo</QC]@Caaᄑ.ȽJtq]zۯ6iܟ4)Y3sCQ? 0k߬~OCOg#/c/Wװwa>>r><72Y_7ȷOo_C#dz%gA[z|!?:eAAA!h쐭!ΑiP~aa~ 'W?pX15wCsDDDޛg1O9-J5*>.j<74?.fYXXIlK9.*6nl {/]py.,:@LN8A*%w% yg"/6шC\*NH*Mz쑼5y$3,幄'L Lݛ:v m2=:1qB!Mggfvˬen/kY- BTZ(*geWf͉9+̳ې7ᒶKW-X潬j9(xoʿܔĹdff-[n ڴ VE/(ۻCɾUUMfeI?m]Nmq#׹=TR+Gw- 6 U#pDy  :v{vg/jBFS[b[O>zG499?rCd&ˮ/~јѡ򗓿m|x31^VwwO| (hSЧc3-bKGD pHYs  tIME!,IDAT8e_Hu?}s3y˕U2MvQ֊FE.łĊbE$DDZF5b@Q":2{n.s<_ y?mwV@tR`}Z _# _=_@ w^R%6gC-έ(K>| ${} H3Q5 B.@ $pd!s#~<<+"x M0B\t8K@zB@F&S`cbP-`'{[! eDh;VEX0fK9-0IWfH  0Q){`##xFW<+*x<$9E[-qWW.(I+6aa@.y24x6_-"bbϫp@t~,/;m%h^ uf@Wp~<5j>{-]cK'Xto(hw?G%fIq^D$.Tʳ?D*A, `6B$BB dr`)B(Ͱ*`/@4Qhp.U=pa( Aa!ڈbX#!H$ ɈQ"K5H1RT UH=r9\F;2G1Q= C7F dt1r=6Ыhڏ>C03l0.B8, c˱" VcϱwE 6wB aAHXLXNH $4 7 Q'"K&b21XH,#/{C7$C2'ITFnR#,4H#dk9, +ȅ3![ b@qS(RjJ4e2AURݨT5ZBRQ4u9̓IKhhitݕNWGw Ljg(gwLӋT071oUX**| J&*/Tު UUT^S}FU3S ԖUPSSg;goT?~YYLOCQ_ cx,!k u5&|v*=9C3J3WRf?qtN (~))4L1e\kXHQG6EYAJ'\'GgSSݧ M=:.kDwn^Loy}/TmG X $ <5qo</QC]@Caaᄑ.ȽJtq]zۯ6iܟ4)Y3sCQ? 0k߬~OCOg#/c/Wװwa>>r><72Y_7ȷOo_C#dz%gA[z|!?:eAAA!h쐭!ΑiP~aa~ 'W?pX15wCsDDDޛg1O9-J5*>.j<74?.fYXXIlK9.*6nl {/]py.,:@LN8A*%w% yg"/6шC\*NH*Mz쑼5y$3,幄'L Lݛ:v m2=:1qB!Mggfvˬen/kY- BTZ(*geWf͉9+̳ې7ᒶKW-X潬j9(xoʿܔĹdff-[n ڴ VE/(ۻCɾUUMfeI?m]Nmq#׹=TR+Gw- 6 U#pDy  :v{vg/jBFS[b[O>zG499?rCd&ˮ/~јѡ򗓿m|x31^VwwO| (hSЧc3-bKGD pHYs  tIME 1;VIDAT8ukU?sg4h`G1 RQܸp%Bn"bЍXJ .4V iZ##T;m!4bP~7r>ιbwc;m;oӍAΆ ζZ^/|s{;yR=9(rtVoG1w#_ө{*E&!(LVuoᲵ‘D PG4 :&~*ݳreu: S-,U^E&JY[P!RB ŖޞʖR@_ȐdBfNvHf"2T]R j'B1ddAak/DIJD D2H&L`&L $Ex,6|~_\P $MH`I=@Z||ttvgcЕWTZ'3rje"ܵx9W> mb|byfFRx{w%DZC$wdցHmWnta(M<~;9]C/_;Տ#}o`zSڷ_>:;x컓?yݩ|}~wam-/7=0S5RP"*֯ IENDB`django-auth-ldap-1.2.7/docs/archive/versions/1.0.19/_static/default.css0000644000076600000240000001000712242245103026154 0ustar psagersstaff00000000000000/* * 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; }django-auth-ldap-1.2.7/docs/archive/versions/1.0.19/_static/doctools.js0000644000076600000240000001527012242245103026211 0ustar psagersstaff00000000000000/* * 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() { $('\u00B6'). attr('href', '#' + this.id). attr('title', _('Permalink to this headline')). appendTo(this); }); $('dt[id]').each(function() { $('\u00B6'). 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); $('') .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(); }); django-auth-ldap-1.2.7/docs/archive/versions/1.0.19/_static/down-pressed.png0000644000076600000240000000056012242245103027141 0ustar psagersstaff00000000000000PNG  IHDRasRGBbKGDC pHYs B(xtIME -vF#IDAT8!OAJ, ++@I vbÿ@W7F HN#48646TMvv޼7Dsax1U q;< E-f)j%po4xF78G>)- EYm4%7YTk-Qa"NWAo-yeq,) Ypt\hqmszG]Nar߶s^l vh\2%0EeRvIENDB`django-auth-ldap-1.2.7/docs/archive/versions/1.0.19/_static/down.png0000644000076600000240000000055312242245103025500 0ustar psagersstaff00000000000000PNG  IHDRasRGBbKGDC pHYs B(xtIME"U{IDAT8ҡNCAJ, ++@4>/U^,~T&3M^^^PM6ٹs*RJa)eG*W<"F Fg78G>q OIp:sAj5GنyD^+yU:p_%G@D|aOs(yM,"msx:.b@D|`Vٟ۲иeKſ/G!IENDB`django-auth-ldap-1.2.7/docs/archive/versions/1.0.19/_static/file.png0000644000076600000240000000061012242245103025442 0ustar psagersstaff00000000000000PNG  IHDRabKGD pHYs  tIME  )TIDAT8˭J@Ir('[ "&xYZ X0!i|_@tD] #xjv YNaEi(əy@D&`6PZk$)5%"z.NA#Aba`Vs_3c,2mj [klvy|!Iմy;v "߮a?A7`c^nk?Bg}TЙD# "RD1yER*6MJ3K_Ut8F~IENDB`django-auth-ldap-1.2.7/docs/archive/versions/1.0.19/_static/minus.png0000644000076600000240000000030712242245103025661 0ustar psagersstaff00000000000000PNG  IHDR &q pHYs  tIME <8tEXtComment̖RIDATcz(BpipPc |IENDB`django-auth-ldap-1.2.7/docs/archive/versions/1.0.19/_static/pygments.css0000644000076600000240000000753412242245103026411 0ustar psagersstaff00000000000000.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 */django-auth-ldap-1.2.7/docs/archive/versions/1.0.19/_static/searchtools.js0000644000076600000240000003725312242245103026716 0ustar psagersstaff00000000000000/* * 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 = $('
').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 = $('

' + _('Searching') + '

').appendTo(this.out); this.dots = $('').appendTo(this.title); this.status = $('

').appendTo(this.out); this.output = $('