[Python-modules-commits] [django-python3-ldap] 01/03: importing django-python3-ldap_0.9.8.orig.tar.gz
Michael Fladischer
fladi at moszumanska.debian.org
Tue Jan 5 12:39:37 UTC 2016
This is an automated email from the git hooks/post-receive script.
fladi pushed a commit to branch master
in repository django-python3-ldap.
commit e366734e58f3dffa63ea86e08b93dd464b1d59d1
Author: Michael Fladischer <FladischerMichael at fladi.at>
Date: Mon Jan 4 19:10:50 2016 +0100
importing django-python3-ldap_0.9.8.orig.tar.gz
---
LICENSE | 27 ++++
MANIFEST.in | 2 +
PKG-INFO | 21 +++
README.rst | 168 +++++++++++++++++++
django_python3_ldap.egg-info/PKG-INFO | 21 +++
django_python3_ldap.egg-info/SOURCES.txt | 19 +++
django_python3_ldap.egg-info/dependency_links.txt | 1 +
django_python3_ldap.egg-info/requires.txt | 2 +
django_python3_ldap.egg-info/top_level.txt | 1 +
django_python3_ldap/__init__.py | 6 +
django_python3_ldap/auth.py | 23 +++
django_python3_ldap/conf.py | 119 ++++++++++++++
django_python3_ldap/ldap.py | 145 +++++++++++++++++
django_python3_ldap/management/__init__.py | 0
.../management/commands/__init__.py | 0
.../management/commands/ldap_promote.py | 30 ++++
.../management/commands/ldap_sync_users.py | 20 +++
django_python3_ldap/tests.py | 178 +++++++++++++++++++++
django_python3_ldap/utils.py | 109 +++++++++++++
setup.cfg | 5 +
setup.py | 35 ++++
21 files changed, 932 insertions(+)
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..711d010
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,27 @@
+Copyright (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.
diff --git a/MANIFEST.in b/MANIFEST.in
new file mode 100644
index 0000000..9d5d250
--- /dev/null
+++ b/MANIFEST.in
@@ -0,0 +1,2 @@
+include LICENSE
+include README.rst
diff --git a/PKG-INFO b/PKG-INFO
new file mode 100644
index 0000000..785fdd4
--- /dev/null
+++ b/PKG-INFO
@@ -0,0 +1,21 @@
+Metadata-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 at 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
diff --git a/README.rst b/README.rst
new file mode 100644
index 0000000..69df588
--- /dev/null
+++ b/README.rst
@@ -0,0 +1,168 @@
+django-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 <http://github.com/etianen/django-python3-ldap>`_.
+
+
+More information
+----------------
+
+The django-python3-ldap project was developed by Dave Hall. You can get the code
+from the `django-python3-ldap project site <http://github.com/etianen/django-python3-ldap>`_.
+
+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 <http://www.etianen.com/>`_
+- `Twitter <http://twitter.com/etianen>`_
+- `Google Profile <http://www.google.com/profiles/david.etianen>`_
diff --git a/django_python3_ldap.egg-info/PKG-INFO b/django_python3_ldap.egg-info/PKG-INFO
new file mode 100644
index 0000000..785fdd4
--- /dev/null
+++ b/django_python3_ldap.egg-info/PKG-INFO
@@ -0,0 +1,21 @@
+Metadata-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 at 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
diff --git a/django_python3_ldap.egg-info/SOURCES.txt b/django_python3_ldap.egg-info/SOURCES.txt
new file mode 100644
index 0000000..ea2ae84
--- /dev/null
+++ b/django_python3_ldap.egg-info/SOURCES.txt
@@ -0,0 +1,19 @@
+LICENSE
+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.py
\ No newline at end of file
diff --git a/django_python3_ldap.egg-info/dependency_links.txt b/django_python3_ldap.egg-info/dependency_links.txt
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/django_python3_ldap.egg-info/dependency_links.txt
@@ -0,0 +1 @@
+
diff --git a/django_python3_ldap.egg-info/requires.txt b/django_python3_ldap.egg-info/requires.txt
new file mode 100644
index 0000000..b1c41e9
--- /dev/null
+++ b/django_python3_ldap.egg-info/requires.txt
@@ -0,0 +1,2 @@
+django>=1.7
+ldap3>=0.9.8.4,!=0.9.9.2
diff --git a/django_python3_ldap.egg-info/top_level.txt b/django_python3_ldap.egg-info/top_level.txt
new file mode 100644
index 0000000..ceea820
--- /dev/null
+++ b/django_python3_ldap.egg-info/top_level.txt
@@ -0,0 +1 @@
+django_python3_ldap
diff --git a/django_python3_ldap/__init__.py b/django_python3_ldap/__init__.py
new file mode 100644
index 0000000..cb8dc7f
--- /dev/null
+++ b/django_python3_ldap/__init__.py
@@ -0,0 +1,6 @@
+"""
+Django LDAP user authentication backend for Python 3.
+"""
+
+
+__version__ = (0, 9, 8)
diff --git a/django_python3_ldap/auth.py b/django_python3_ldap/auth.py
new file mode 100644
index 0000000..cba93fd
--- /dev/null
+++ b/django_python3_ldap/auth.py
@@ -0,0 +1,23 @@
+"""
+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)
diff --git a/django_python3_ldap/conf.py b/django_python3_ldap/conf.py
new file mode 100644
index 0000000..e1e3469
--- /dev/null
+++ b/django_python3_ldap/conf.py
@@ -0,0 +1,119 @@
+"""
+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)
diff --git a/django_python3_ldap/ldap.py b/django_python3_ldap/ldap.py
new file mode 100644
index 0000000..8841a2e
--- /dev/null
+++ b/django_python3_ldap/ldap.py
@@ -0,0 +1,145 @@
+"""
+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
+
+
+ at 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)
diff --git a/django_python3_ldap/management/__init__.py b/django_python3_ldap/management/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/django_python3_ldap/management/commands/__init__.py b/django_python3_ldap/management/commands/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/django_python3_ldap/management/commands/ldap_promote.py b/django_python3_ldap/management/commands/ldap_promote.py
new file mode 100644
index 0000000..17cbd64
--- /dev/null
+++ b/django_python3_ldap/management/commands/ldap_promote.py
@@ -0,0 +1,30 @@
+from 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,
+ ))
diff --git a/django_python3_ldap/management/commands/ldap_sync_users.py b/django_python3_ldap/management/commands/ldap_sync_users.py
new file mode 100644
index 0000000..a51cf3a
--- /dev/null
+++ b/django_python3_ldap/management/commands/ldap_sync_users.py
@@ -0,0 +1,20 @@
+from 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,
+ ))
diff --git a/django_python3_ldap/tests.py b/django_python3_ldap/tests.py
new file mode 100644
index 0000000..124e00c
--- /dev/null
+++ b/django_python3_ldap/tests.py
@@ -0,0 +1,178 @@
+# 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
+
+
+ at skipUnless(settings.LDAP_AUTH_TEST_USER_USERNAME, "No settings.LDAP_AUTH_TEST_USER_USERNAME supplied.")
+ at skipUnless(settings.LDAP_AUTH_TEST_USER_PASSWORD, "No settings.LDAP_AUTH_TEST_USER_PASSWORD supplied.")
+ at skipUnless(settings.LDAP_AUTH_USER_LOOKUP_FIELDS == ("username",), "Cannot test using custom lookup fields.")
+ at 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 at bar.com"), r'foo at 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)))
diff --git a/django_python3_ldap/utils.py b/django_python3_ldap/utils.py
new file mode 100644
index 0000000..24029b6
--- /dev/null
+++ b/django_python3_ldap/utils.py
@@ -0,0 +1,109 @@
+"""
+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,
+ )
... 82 lines suppressed ...
--
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/python-modules/packages/django-python3-ldap.git
More information about the Python-modules-commits
mailing list