django-python3-ldap-0.9.8/0000755000076500000240000000000012632303477015363 5ustar davestaff00000000000000django-python3-ldap-0.9.8/django_python3_ldap/0000755000076500000240000000000012632303477021311 5ustar davestaff00000000000000django-python3-ldap-0.9.8/django_python3_ldap/__init__.py0000644000076500000240000000013012632303441023403 0ustar davestaff00000000000000""" Django LDAP user authentication backend for Python 3. """ __version__ = (0, 9, 8) django-python3-ldap-0.9.8/django_python3_ldap/auth.py0000644000076500000240000000076012455531455022631 0ustar davestaff00000000000000""" Django authentication backend. """ from django.contrib.auth.backends import ModelBackend from django_python3_ldap import ldap class LDAPBackend(ModelBackend): """ An authentication backend that delegates to an LDAP server. User models authenticated with LDAP are created on the fly, and syncronised with the LDAP credentials. """ supports_inactive_user = False def authenticate(self, *args, **kwargs): return ldap.authenticate(*args, **kwargs) django-python3-ldap-0.9.8/django_python3_ldap/conf.py0000644000076500000240000000551712620064667022622 0ustar davestaff00000000000000""" Settings used by django-python3. """ from django.conf import settings class LazySetting(object): """ A proxy to a named Django setting. """ def __init__(self, name, default=None): self.name = name self.default = default def __get__(self, obj, cls): if obj is None: return self return getattr(obj._settings, self.name, self.default) class LazySettings(object): """ A proxy to ldap-specific django settings. Settings are resolved at runtime, allowing tests to change settings at runtime. """ def __init__(self, settings): self._settings = settings LDAP_AUTH_URL = LazySetting( name = "LDAP_AUTH_URL", default = "ldap://localhost:389", ) LDAP_AUTH_USE_TLS = LazySetting( name = "LDAP_AUTH_USE_TLS", default = False, ) LDAP_AUTH_SEARCH_BASE = LazySetting( name = "LDAP_AUTH_SEARCH_BASE", default = "ou=people,dc=example,dc=com", ) LDAP_AUTH_OBJECT_CLASS = LazySetting( name = "LDAP_AUTH_OBJECT_CLASS", default = "inetOrgPerson", ) LDAP_AUTH_USER_FIELDS = LazySetting( name = "LDAP_AUTH_USER_FIELDS", default = { "username": "uid", "first_name": "givenName", "last_name": "sn", "email": "mail", }, ) LDAP_AUTH_USER_LOOKUP_FIELDS = LazySetting( name = "LDAP_AUTH_USER_LOOKUP_FIELDS", default = ( "username", ), ) LDAP_AUTH_CLEAN_USER_DATA = LazySetting( name = "LDAP_AUTH_CLEAN_USER_DATA", default = "django_python3_ldap.utils.clean_user_data", ) LDAP_AUTH_FORMAT_SEARCH_FILTERS = LazySetting( name = "LDAP_AUTH_FORMAT_SEARCH_FILTERS", default = "django_python3_ldap.utils.format_search_filters", ) LDAP_AUTH_SYNC_USER_RELATIONS = LazySetting( name = "LDAP_AUTH_SYNC_USER_RELATIONS", default = "django_python3_ldap.utils.sync_user_relations", ) LDAP_AUTH_FORMAT_USERNAME = LazySetting( name = "LDAP_AUTH_FORMAT_USERNAME", default = "django_python3_ldap.utils.format_username_openldap", ) LDAP_AUTH_ACTIVE_DIRECTORY_DOMAIN = LazySetting( name = "LDAP_AUTH_ACTIVE_DIRECTORY_DOMAIN", default = None, ) LDAP_AUTH_TEST_USER_USERNAME = LazySetting( name = "LDAP_AUTH_TEST_USER_USERNAME", default = "", ) LDAP_AUTH_TEST_USER_PASSWORD = LazySetting( name = "LDAP_AUTH_TEST_USER_PASSWORD", default = "", ) LDAP_AUTH_CONNECTION_USERNAME = LazySetting( name = "LDAP_AUTH_CONNECTION_USERNAME", default = None, ) LDAP_AUTH_CONNECTION_PASSWORD = LazySetting( name = "LDAP_AUTH_CONNECTION_PASSWORD", default = None, ) settings = LazySettings(settings) django-python3-ldap-0.9.8/django_python3_ldap/ldap.py0000644000076500000240000001111012632302240022560 0ustar davestaff00000000000000""" Low-level LDAP hooks. """ import ldap3 from contextlib import contextmanager from django.contrib.auth import get_user_model from django_python3_ldap.conf import settings from django_python3_ldap.utils import import_func, format_search_filter class Connection(object): """ A connection to an LDAP server. """ def __init__(self, connection): """ Creates the LDAP connection. No need to call this manually, the `connection()` context manager handles initialization. """ self._connection = connection def _get_or_create_user(self, user_data): """ Returns a Django user for the given LDAP user data. If the user does not exist, then it will be created. """ User = get_user_model() attributes = user_data["attributes"] # Create the user data. user_fields = { field_name: attributes.get(attribute_name, ("",))[0] for field_name, attribute_name in settings.LDAP_AUTH_USER_FIELDS.items() } user_fields = import_func(settings.LDAP_AUTH_CLEAN_USER_DATA)(user_fields) # Create the user lookup. user_lookup = { field_name: user_fields.pop(field_name, "") for field_name in settings.LDAP_AUTH_USER_LOOKUP_FIELDS } # Update or create the user. user, created = User.objects.update_or_create( defaults = user_fields, **user_lookup ) # Update relations import_func(settings.LDAP_AUTH_SYNC_USER_RELATIONS)(user, attributes) # All done! return user def iter_users(self): """ Returns an iterator of Django users that correspond to users in the LDAP database. """ paged_entries = self._connection.extend.standard.paged_search( search_base = settings.LDAP_AUTH_SEARCH_BASE, search_filter = format_search_filter({}), search_scope = ldap3.SEARCH_SCOPE_WHOLE_SUBTREE, attributes = ldap3.ALL_ATTRIBUTES, paged_size = 30, ) return ( self._get_or_create_user(entry) for entry in paged_entries if entry["type"] == "searchResEntry" ) def get_user(self, **kwargs): """ Returns the user with the given identifier. The user identifier should be keyword arguments matching the fields in settings.LDAP_AUTH_USER_LOOKUP_FIELDS. """ # Search the LDAP database. if self._connection.search( search_base = settings.LDAP_AUTH_SEARCH_BASE, search_filter = format_search_filter(kwargs), search_scope = ldap3.SEARCH_SCOPE_WHOLE_SUBTREE, attributes = ldap3.ALL_ATTRIBUTES, get_operational_attributes = True, size_limit = 1, ): return self._get_or_create_user(self._connection.response[0]) return None @contextmanager def connection(**kwargs): """ Creates and returns a connection to the LDAP server. The user identifier, if given, should be keyword arguments matching the fields in settings.LDAP_AUTH_USER_LOOKUP_FIELDS, plus a `password` argument. """ # Format the DN for the username. username = None password = None if kwargs: password = kwargs.pop("password") username = import_func(settings.LDAP_AUTH_FORMAT_USERNAME)(kwargs) # Make the connection. if username or password: if settings.LDAP_AUTH_USE_TLS: auto_bind = ldap3.AUTO_BIND_TLS_BEFORE_BIND else: auto_bind = ldap3.AUTO_BIND_NO_TLS else: auto_bind = ldap3.AUTO_BIND_NONE try: with ldap3.Connection(ldap3.Server(settings.LDAP_AUTH_URL), user=username, password=password, auto_bind=auto_bind) as c: yield Connection(c) except (ldap3.LDAPBindError, ldap3.LDAPSASLPrepError): yield None def authenticate(**kwargs): """ Authenticates with the LDAP server, and returns the corresponding Django user instance. The user identifier should be keyword arguments matching the fields in settings.LDAP_AUTH_USER_LOOKUP_FIELDS, plus a `password` argument. """ password = kwargs.pop("password") # Check that this is valid login data. if not password or frozenset(kwargs.keys()) != frozenset(settings.LDAP_AUTH_USER_LOOKUP_FIELDS): return None # Connect to LDAP. with connection(password=password, **kwargs) as c: if c is None: return None return c.get_user(**kwargs) django-python3-ldap-0.9.8/django_python3_ldap/management/0000755000076500000240000000000012632303477023425 5ustar davestaff00000000000000django-python3-ldap-0.9.8/django_python3_ldap/management/__init__.py0000644000076500000240000000000012455531455025526 0ustar davestaff00000000000000django-python3-ldap-0.9.8/django_python3_ldap/management/commands/0000755000076500000240000000000012632303477025226 5ustar davestaff00000000000000django-python3-ldap-0.9.8/django_python3_ldap/management/commands/__init__.py0000644000076500000240000000000012455531455027327 0ustar davestaff00000000000000django-python3-ldap-0.9.8/django_python3_ldap/management/commands/ldap_promote.py0000664000076500000240000000200112456001745030255 0ustar davestaff00000000000000from django.core.management.base import BaseCommand, CommandError from django.db import transaction from django.contrib.auth import get_user_model class Command(BaseCommand): help = "Promotes the named users to an admin superuser." args = "[username, ...]" @transaction.atomic() def handle(self, *usernames, **kwargs): verbosity = int(kwargs.get("verbosity", 1)) User = get_user_model() for username in usernames: try: user = User.objects.get(username=username) except User.DoesNotExist: raise CommandError("User with username {username} does not exist".format( username = username, )) else: user.is_staff = True user.is_superuser = True user.save() if verbosity >= 1: self.stdout.write("Promoted {user} to admin superuser".format( user = user, )) django-python3-ldap-0.9.8/django_python3_ldap/management/commands/ldap_sync_users.py0000644000076500000240000000140412620064667030776 0ustar davestaff00000000000000from django.core.management.base import NoArgsCommand from django.db import transaction from django_python3_ldap import ldap from django_python3_ldap.conf import settings class Command(NoArgsCommand): help = "Creates local user models for all users found in the remote LDAP authentication server." @transaction.atomic() def handle_noargs(self, **kwargs): verbosity = int(kwargs.get("verbosity", 1)) with ldap.connection(username=settings.LDAP_AUTH_CONNECTION_USERNAME, password=settings.LDAP_AUTH_CONNECTION_PASSWORD) as connection: for user in connection.iter_users(): if verbosity >= 1: self.stdout.write("Synced {user}".format( user = user, )) django-python3-ldap-0.9.8/django_python3_ldap/tests.py0000644000076500000240000001576012620064667023040 0ustar davestaff00000000000000# encoding=utf-8 from __future__ import unicode_literals from unittest import skipUnless from io import StringIO, BytesIO from django.test import TestCase from django.contrib.auth import authenticate from django.contrib.auth.models import User from django.conf import settings as django_settings from django.core.management import call_command, CommandError from django.utils import six from django_python3_ldap.conf import settings from django_python3_ldap.ldap import connection from django_python3_ldap.utils import clean_ldap_name, import_func @skipUnless(settings.LDAP_AUTH_TEST_USER_USERNAME, "No settings.LDAP_AUTH_TEST_USER_USERNAME supplied.") @skipUnless(settings.LDAP_AUTH_TEST_USER_PASSWORD, "No settings.LDAP_AUTH_TEST_USER_PASSWORD supplied.") @skipUnless(settings.LDAP_AUTH_USER_LOOKUP_FIELDS == ("username",), "Cannot test using custom lookup fields.") @skipUnless(django_settings.AUTH_USER_MODEL == "auth.User", "Cannot test using a custom user model.") class TestLdap(TestCase): def setUp(self): super(TestLdap, self).setUp() User.objects.all().delete() # Lazy settings tests. def testLazySettingsInstanceLookup(self): self.assertTrue(settings.LDAP_AUTH_TEST_USER_USERNAME) def testLazySettingsClassLookup(self): self.assertEqual(settings.__class__.LDAP_AUTH_TEST_USER_USERNAME.name, "LDAP_AUTH_TEST_USER_USERNAME") self.assertEqual(settings.__class__.LDAP_AUTH_TEST_USER_USERNAME.default, "") # Utils tests. def testCleanLdapName(self): self.assertEqual(clean_ldap_name("foo@bar.com"), r'foo@bar.com') self.assertEqual(clean_ldap_name("café"), r'caf\E9') # LDAP tests. def testGetUserKwargsSuccess(self): with connection() as c: user = c.get_user( username = settings.LDAP_AUTH_TEST_USER_USERNAME, ) self.assertIsInstance(user, User) self.assertEqual(user.username, settings.LDAP_AUTH_TEST_USER_USERNAME) def testGetUserKwargsIncorrectUsername(self): with connection() as c: user = c.get_user( username = "bad" + settings.LDAP_AUTH_TEST_USER_USERNAME, ) self.assertEqual(user, None) # Authentication tests. def testAuthenticateUserSuccess(self): user = authenticate( username = settings.LDAP_AUTH_TEST_USER_USERNAME, password = settings.LDAP_AUTH_TEST_USER_PASSWORD, ) self.assertIsInstance(user, User) self.assertEqual(user.username, settings.LDAP_AUTH_TEST_USER_USERNAME) def testAuthenticateUserBadUsername(self): user = authenticate( username = "bad" + settings.LDAP_AUTH_TEST_USER_USERNAME, password = settings.LDAP_AUTH_TEST_USER_PASSWORD, ) self.assertEqual(user, None) def testAuthenticateUserBadPassword(self): user = authenticate( username = settings.LDAP_AUTH_TEST_USER_USERNAME, password = "bad" + settings.LDAP_AUTH_TEST_USER_PASSWORD, ) self.assertEqual(user, None) def testRepeatedUserAuthenticationDoestRecreateUsers(self): user_1 = authenticate( username = settings.LDAP_AUTH_TEST_USER_USERNAME, password = settings.LDAP_AUTH_TEST_USER_PASSWORD, ) user_2 = authenticate( username = settings.LDAP_AUTH_TEST_USER_USERNAME, password = settings.LDAP_AUTH_TEST_USER_PASSWORD, ) # Ensure that the user isn't recreated on second access. self.assertEqual(user_1.pk, user_2.pk) def testAuthenticateWithTLS(self): with self.settings(LDAP_AUTH_USE_TLS=True): user = authenticate( username = settings.LDAP_AUTH_TEST_USER_USERNAME, password = settings.LDAP_AUTH_TEST_USER_PASSWORD, ) self.assertIsInstance(user, User) self.assertEqual(user.username, settings.LDAP_AUTH_TEST_USER_USERNAME) # User syncronisation. def testSyncUsersCreatesUsers(self): call_command("ldap_sync_users", verbosity=0) self.assertGreater(User.objects.count(), 0) def testSyncUsersCommandOutput(self): out = StringIO() if six.PY3 else BytesIO() call_command("ldap_sync_users", verbosity=1, stdout=out) rows = out.getvalue().split("\n")[:-1] self.assertEqual(len(rows), User.objects.count()) for row in rows: six.assertRegex(self, row, r'^Synced [^\s]+$') def testReSyncUsersDoesntRecreateUsers(self): call_command("ldap_sync_users", verbosity=0) user_count_1 = User.objects.count() call_command("ldap_sync_users", verbosity=0) user_count_2 = User.objects.count() self.assertEqual(user_count_1, user_count_2) # User promotion. def testPromoteUser(self): user = User.objects.create( username = "test", ) self.assertFalse(user.is_staff) self.assertFalse(user.is_superuser) # Promote the user. call_command("ldap_promote", "test", stdout=StringIO() if six.PY3 else BytesIO()) user = User.objects.get(username="test") self.assertTrue(user.is_staff) self.assertTrue(user.is_superuser) def testPromoteMissingUser(self): with self.assertRaises(CommandError, msg="User with username missing_user does not exist") as cm: call_command("ldap_promote", "missing_user", verbosity=0) def testSyncUserRelations(self): def check_sync_user_relation(user, data): # id have been created self.assertIsNotNone(user.id) # model is saved self.assertEqual(user.username, User.objects.get(pk=user.id).username) # save all groups self.assertIn('cn', data) groups = list() ldap_groups = list(data.get('memberOf', ())) ldap_groups.append('default_group') for group in ldap_groups: user.groups.create(name=group) with self.settings(LDAP_AUTH_SYNC_USER_RELATIONS=check_sync_user_relation): user = authenticate( username = settings.LDAP_AUTH_TEST_USER_USERNAME, password = settings.LDAP_AUTH_TEST_USER_PASSWORD, ) self.assertIsInstance(user, User) self.assertGreaterEqual(user.groups.count(), 1) self.assertEqual(user.groups.filter(name='default_group').count(), 1) def testImportFunc(self): self.assertIs(clean_ldap_name, import_func(clean_ldap_name)) self.assertIs(clean_ldap_name, import_func('django_python3_ldap.utils.clean_ldap_name')) self.assertTrue(callable(import_func('django.contrib.auth.get_user_model'))) self.assertRaises(AttributeError, import_func, 123) self.assertTrue(callable(import_func(settings.LDAP_AUTH_SYNC_USER_RELATIONS))) with self.settings(LDAP_AUTH_SYNC_USER_RELATIONS='django.contrib.auth.get_user_model'): self.assertTrue(callable(import_func(settings.LDAP_AUTH_SYNC_USER_RELATIONS))) django-python3-ldap-0.9.8/django_python3_ldap/utils.py0000644000076500000240000000622412625307152023023 0ustar davestaff00000000000000""" Some useful LDAP utilities. """ import re, binascii from django.contrib.auth.hashers import make_password from django.utils.encoding import force_text from django.utils.module_loading import import_string from django.utils import six from django_python3_ldap.conf import settings def import_func(func): if callable(func): return func elif isinstance(func, six.string_types): return import_string(func) raise AttributeError("Expected a function {0!r}".format(func)) def clean_ldap_name(name): """ Transforms the given name into a form that won't interfere with LDAP queries. """ return re.sub(r'[^a-zA-Z0-9 _\-.@]', lambda c: "\\" + force_text(binascii.hexlify(c.group(0).encode("latin-1", errors="ignore"))).upper(), force_text(name)) def convert_model_fields_to_ldap_fields(model_fields): """ Converts a set of model fields into a set of corresponding LDAP fields. """ return { settings.LDAP_AUTH_USER_FIELDS[field_name]: field_value for field_name, field_value in model_fields.items() } def format_search_filter(model_fields): """ Creates an LDAP search filter for the given set of model fields. """ ldap_fields = convert_model_fields_to_ldap_fields(model_fields); ldap_fields["objectClass"] = settings.LDAP_AUTH_OBJECT_CLASS search_filters = import_func(settings.LDAP_AUTH_FORMAT_SEARCH_FILTERS)(ldap_fields) return "(&{})".format("".join(search_filters)); def clean_user_data(model_fields): """ Transforms the user data loaded from LDAP into a form suitable for creating a user. """ # Create an unusable password for the user. model_fields["password"] = make_password(None) return model_fields def format_username_openldap(model_fields): """ Formats a user identifier into a username suitable for binding to an OpenLDAP server. """ return "{user_identifier},{search_base}".format( user_identifier = ",".join( "{attribute_name}={field_value}".format( attribute_name = clean_ldap_name(field_name), field_value = clean_ldap_name(field_value), ) for field_name, field_value in convert_model_fields_to_ldap_fields(model_fields).items() ), search_base = settings.LDAP_AUTH_SEARCH_BASE, ) def format_username_active_directory(model_fields): """ Formats a user identifier into a username suitable for binding to an Active Directory server. """ username = model_fields["username"] if settings.LDAP_AUTH_ACTIVE_DIRECTORY_DOMAIN: username = "{domain}\\{username}".format( domain = settings.LDAP_AUTH_ACTIVE_DIRECTORY_DOMAIN, username = username, ) return username def sync_user_relations(user, ldap_attributes): # do nothing by default pass def format_search_filters(ldap_fields): return [ "({attribute_name}={field_value})".format( attribute_name = clean_ldap_name(field_name), field_value = clean_ldap_name(field_value), ) for field_name, field_value in ldap_fields.items() ] django-python3-ldap-0.9.8/django_python3_ldap.egg-info/0000755000076500000240000000000012632303477023003 5ustar davestaff00000000000000django-python3-ldap-0.9.8/django_python3_ldap.egg-info/dependency_links.txt0000644000076500000240000000000112632303477027051 0ustar davestaff00000000000000 django-python3-ldap-0.9.8/django_python3_ldap.egg-info/PKG-INFO0000644000076500000240000000142412632303477024101 0ustar davestaff00000000000000Metadata-Version: 1.1 Name: django-python3-ldap Version: 0.9.8 Summary: Django LDAP user authentication backend for Python 3. Home-page: https://github.com/etianen/django-python3-ldap Author: Dave Hall Author-email: dave@etianen.com License: BSD Description: UNKNOWN Platform: UNKNOWN Classifier: Development Status :: 4 - Beta Classifier: Environment :: Web Environment Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: BSD License Classifier: Operating System :: OS Independent Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3.2 Classifier: Programming Language :: Python :: 3.3 Classifier: Programming Language :: Python :: 3.4 Classifier: Framework :: Django django-python3-ldap-0.9.8/django_python3_ldap.egg-info/requires.txt0000644000076500000240000000004512632303477025402 0ustar davestaff00000000000000django>=1.7 ldap3>=0.9.8.4,!=0.9.9.2 django-python3-ldap-0.9.8/django_python3_ldap.egg-info/SOURCES.txt0000644000076500000240000000117512632303477024673 0ustar davestaff00000000000000LICENSE MANIFEST.in README.rst setup.py django_python3_ldap/__init__.py django_python3_ldap/auth.py django_python3_ldap/conf.py django_python3_ldap/ldap.py django_python3_ldap/tests.py django_python3_ldap/utils.py django_python3_ldap.egg-info/PKG-INFO django_python3_ldap.egg-info/SOURCES.txt django_python3_ldap.egg-info/dependency_links.txt django_python3_ldap.egg-info/requires.txt django_python3_ldap.egg-info/top_level.txt django_python3_ldap/management/__init__.py django_python3_ldap/management/commands/__init__.py django_python3_ldap/management/commands/ldap_promote.py django_python3_ldap/management/commands/ldap_sync_users.pydjango-python3-ldap-0.9.8/django_python3_ldap.egg-info/top_level.txt0000644000076500000240000000002412632303477025531 0ustar davestaff00000000000000django_python3_ldap django-python3-ldap-0.9.8/LICENSE0000664000076500000240000000276412455527251016404 0ustar davestaff00000000000000Copyright (c) 2015, David Hall. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. 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. 3. Neither the name of David Hall nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. 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 OWNER 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-python3-ldap-0.9.8/MANIFEST.in0000664000076500000240000000004312455527227017124 0ustar davestaff00000000000000include LICENSE include README.rst django-python3-ldap-0.9.8/PKG-INFO0000644000076500000240000000142412632303477016461 0ustar davestaff00000000000000Metadata-Version: 1.1 Name: django-python3-ldap Version: 0.9.8 Summary: Django LDAP user authentication backend for Python 3. Home-page: https://github.com/etianen/django-python3-ldap Author: Dave Hall Author-email: dave@etianen.com License: BSD Description: UNKNOWN Platform: UNKNOWN Classifier: Development Status :: 4 - Beta Classifier: Environment :: Web Environment Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: BSD License Classifier: Operating System :: OS Independent Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3.2 Classifier: Programming Language :: Python :: 3.3 Classifier: Programming Language :: Python :: 3.4 Classifier: Framework :: Django django-python3-ldap-0.9.8/README.rst0000644000076500000240000001402412620064667017055 0ustar davestaff00000000000000django-python3-ldap =================== **django-python3-ldap** provides a Django LDAP user authentication backend for Python 3. Features -------- - Authenticate users with an LDAP server. - Sync LDAP users with a local Django database. - Supports custom Django user models. - Works in Python 3! Installation ------------ 1. Install using ``pip install django-python3-ldap``. 2. Add ``'django_python3_ldap'`` to your ``INSTALLED_APPS`` setting. 3. Set your ``AUTHENTICATION_BACKENDS`` setting to ``("django_python3_ldap.auth.LDAPBackend",)`` 4. Configure the settings for your LDAP server (see Available settings, below). 5. Optionally, run ``./manage.py ldap_sync_users`` to perform an initial sync of LDAP users. Available settings ------------------ .. code:: python # The URL of the LDAP server. LDAP_AUTH_URL = "ldap://localhost:389" # Initiate TLS on connection. LDAP_AUTH_USE_TLS = False # The LDAP search base for looking up users. LDAP_AUTH_SEARCH_BASE = "ou=people,dc=example,dc=com" # The LDAP class that represents a user. LDAP_AUTH_OBJECT_CLASS = "inetOrgPerson" # User model fields mapped to the LDAP # attributes that represent them. LDAP_AUTH_USER_FIELDS = { "username": "uid", "first_name": "givenName", "last_name": "sn", "email": "mail", } # A tuple of django model fields used to uniquely identify a user. LDAP_AUTH_USER_LOOKUP_FIELDS = ("username",) # Path to a callable that takes a dict of {model_field_name: value}, # returning a dict of clean model data. # Use this to customize how data loaded from LDAP is saved to the User model. LDAP_AUTH_CLEAN_USER_DATA = "django_python3_ldap.utils.clean_user_data" # Path to a callable that takes a user model and a dict of {ldap_field_name: [value]}, # and saves any additional user relationships based on the LDAP data. # Use this to customize how data loaded from LDAP is saved to User model relations. # For customizing non-related User model fields, use LDAP_AUTH_CLEAN_USER_DATA. LDAP_AUTH_SYNC_USER_RELATIONS = "django_python3_ldap.utils.sync_user_relations" # Path to a callable that takes a dict of {ldap_field_name: value}, # returning a list of [ldap_search_filter]. The search filters will then be AND'd # together when creating the final search filter. LDAP_AUTH_FORMAT_SEARCH_FILTERS = "django_python3_ldap.utils.format_search_filters" # Path to a callable that takes a dict of {model_field_name: value}, and returns # a string of the username to bind to the LDAP server. # Use this to support different types of LDAP server. LDAP_AUTH_FORMAT_USERNAME = "django_python3_ldap.utils.format_username_openldap" # Sets the login domain for Active Directory users. LDAP_AUTH_ACTIVE_DIRECTORY_DOMAIN = None # The LDAP username and password of a user for authenticating the `ldap_sync_users` # management command. Set to None if you allow anonymous queries. LDAP_AUTH_CONNECTION_USERNAME = None LDAP_AUTH_CONNECTION_PASSWORD = None Microsoft Active Directory support ---------------------------------- django-python3-ldap is configured by default to support login via OpenLDAP. To connect to a Microsoft Active Directory, add the following line to your settings file. .. code:: python LDAP_AUTH_FORMAT_USERNAME = "django_python3_ldap.utils.format_username_active_directory" If your Active Directory server requires a domain to be supplied with the username, then also specify: .. code:: python LDAP_AUTH_ACTIVE_DIRECTORY_DOMAIN = "your_domain" Custom user filters ------------------- By default, any users within ``LDAP_AUTH_SEARCH_BASE`` and of the correct ``LDAP_AUTH_OBJECT_CLASS`` will be considered a valid user. You can apply further filtering by setting a custom ``LDAP_AUTH_FORMAT_SEARCH_FILTERS`` callable. .. code:: python # settings.py LDAP_AUTH_FORMAT_SEARCH_FILTERS = "path.to.your.custom_format_search_filters" # pay/to/your.py from django_python3_ldap.utils import format_search_filters def custom_format_search_filters(ldap_fields): # Add in simple filters. ldap_fields["memberOf"] = "foo" # Call the base format callable. search_filters = format_search_filters(ldap_fields) # Advanced: apply custom LDAP filter logic. search_filters.append("(|(memberOf=groupA)(memberOf=GroupB))") # All done! return search_filters The returned list of search filters will be AND'd together to make the final search filter. How it works ------------ When a user attempts to authenticate, a connection is made to the LDAP server, and the application attempts to bind using the provided username and password. If the bind attempt is successful, the user details are loaded from the LDAP server and saved in a local Django ``User`` model. The local model is only created once, and the details will be kept updated with the LDAP record details on every login. To perform a full sync of all LDAP users to the local database, run ``./manage.py ldap_sync_users``. This is not required, as the authentication backend will create users on demand. Syncing users has the advantage of allowing you to assign permissions and groups to the existing users using the Django admin interface. Running ``ldap_sync_users`` as a background cron task is another optional way to keep all users in sync on a regular basis. Support and announcements ------------------------- Downloads and bug tracking can be found at the `main project website `_. More information ---------------- The django-python3-ldap project was developed by Dave Hall. You can get the code from the `django-python3-ldap project site `_. Dave Hall is a freelance web developer, based in Cambridge, UK. You can usually find him on the Internet in a number of different places: - `Website `_ - `Twitter `_ - `Google Profile `_ django-python3-ldap-0.9.8/setup.cfg0000644000076500000240000000007312632303477017204 0ustar davestaff00000000000000[egg_info] tag_date = 0 tag_svn_revision = 0 tag_build = django-python3-ldap-0.9.8/setup.py0000644000076500000240000000206612625307152017075 0ustar davestaff00000000000000from setuptools import setup, find_packages from django_python3_ldap import __version__ version_str = ".".join(str(n) for n in __version__) setup( name = "django-python3-ldap", version = version_str, license = "BSD", description = "Django LDAP user authentication backend for Python 3.", author = "Dave Hall", author_email = "dave@etianen.com", url = "https://github.com/etianen/django-python3-ldap", packages = find_packages(), install_requires = [ "django>=1.7", "ldap3>=0.9.8.4,!=0.9.9.2", ], classifiers = [ "Development Status :: 4 - Beta", "Environment :: Web Environment", "Intended Audience :: Developers", "License :: OSI Approved :: BSD License", "Operating System :: OS Independent", "Programming Language :: Python", "Programming Language :: Python :: 2.7", "Programming Language :: Python :: 3.2", "Programming Language :: Python :: 3.3", "Programming Language :: Python :: 3.4", "Framework :: Django", ], )