From 84fbf217124976a717f616e96e134b5b6a469f60 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Freitag?= Date: Wed, 17 Jul 2024 14:31:00 +0200 Subject: [PATCH 01/11] Use ruff instead of black, flake8 and isort Simpler, fully configured from pyproject.toml, and faster. --- .github/workflows/check.yml | 16 ++-------------- pyproject.toml | 11 +++++++++-- setup.cfg | 2 -- tox.ini | 20 ++++---------------- 4 files changed, 15 insertions(+), 34 deletions(-) delete mode 100644 setup.cfg diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index bb58e5bc..fe565fbe 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -6,20 +6,8 @@ on: jobs: build: - name: ${{ matrix.tox-environment }} + name: check runs-on: ubuntu-latest - - strategy: - fail-fast: false - matrix: - tox-environment: - - black - - flake8 - - isort - - env: - TOXENV: ${{ matrix.tox-environment }} - steps: - uses: actions/checkout@v4 @@ -30,4 +18,4 @@ jobs: run: python -m pip install tox - name: Run - run: tox + run: tox -e ruff diff --git a/pyproject.toml b/pyproject.toml index b2db850a..f05ca348 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -46,8 +46,15 @@ Source = "https://github.com/django-auth-ldap/django-auth-ldap" Tracker = "https://github.com/django-auth-ldap/django-auth-ldap/issues" Changelog = "https://github.com/django-auth-ldap/django-auth-ldap/releases/" -[tool.isort] -profile = "black" +[tool.ruff.lint] +# See prefixes in https://beta.ruff.rs/docs/rules/ +select = [ + "F", # pyflakes + "E", # pycodestyle errors + "W", # pycodestyle warnings + "I", # isort + "C4", # flake8-comprehension +] [build-system] requires = [ diff --git a/setup.cfg b/setup.cfg deleted file mode 100644 index 2bcd70e3..00000000 --- a/setup.cfg +++ /dev/null @@ -1,2 +0,0 @@ -[flake8] -max-line-length = 88 diff --git a/tox.ini b/tox.ini index 5a191e25..6dfa3ae5 100644 --- a/tox.ini +++ b/tox.ini @@ -1,8 +1,6 @@ [tox] envlist = - black - flake8 - isort + ruff docs django32 django42 @@ -18,19 +16,9 @@ deps = django50: Django>=5.0,<5.1 djangomain: https://github.com/django/django/archive/main.tar.gz -[testenv:black] -deps = black -commands = black --check --diff . -skip_install = true - -[testenv:flake8] -deps = flake8 -commands = flake8 -skip_install = true - -[testenv:isort] -deps = isort>=5.0.1 -commands = isort --check --diff . +[testenv:ruff] +deps = ruff +commands = ruff check . skip_install = true [testenv:docs] From 9304ceb37b45dba5c2f4a2dd03d9bb9fbfc0e00b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Freitag?= Date: Wed, 17 Jul 2024 14:40:27 +0200 Subject: [PATCH 02/11] Drop support for django 3.2 It has reached its end of life. --- .github/workflows/test.yml | 7 ------- pyproject.toml | 3 +-- tox.ini | 2 -- 3 files changed, 1 insertion(+), 11 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 7917c9ed..8b0e8bcf 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -21,13 +21,6 @@ jobs: - django42 - django50 include: - # Django 3.2 - - python-version: 3.8 - tox-environment: django32 - - python-version: 3.9 - tox-environment: django32 - - python-version: '3.10' - tox-environment: django32 # Django 5.0 - python-version: '3.10' tox-environment: django50 diff --git a/pyproject.toml b/pyproject.toml index f05ca348..0cec5d06 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -14,7 +14,6 @@ classifiers = [ "Development Status :: 5 - Production/Stable", "Environment :: Web Environment", "Framework :: Django", - "Framework :: Django :: 3.2", "Framework :: Django :: 4.2", "Framework :: Django :: 5.0", "Intended Audience :: Developers", @@ -35,7 +34,7 @@ classifiers = [ dynamic = ["version"] dependencies = [ - "Django>=3.2", + "Django>=4.2", "python-ldap>=3.1", ] diff --git a/tox.ini b/tox.ini index 6dfa3ae5..ec92ccb6 100644 --- a/tox.ini +++ b/tox.ini @@ -2,7 +2,6 @@ envlist = ruff docs - django32 django42 django50 djangomain @@ -11,7 +10,6 @@ isolated_build = true [testenv] commands = {envpython} -Wa -b -m django test --settings tests.settings deps = - django32: Django>=3.2,<4.0 django42: Django>=4.2,<4.3 django50: Django>=5.0,<5.1 djangomain: https://github.com/django/django/archive/main.tar.gz From 800818779beea7d4b1c721cc3e8c6fc52f7ba2d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Freitag?= Date: Wed, 17 Jul 2024 14:46:22 +0200 Subject: [PATCH 03/11] Simplify test matrix --- .github/workflows/test.yml | 16 +--------------- 1 file changed, 1 insertion(+), 15 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 8b0e8bcf..b768a7fc 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -20,21 +20,7 @@ jobs: tox-environment: - django42 - django50 - include: - # Django 5.0 - - python-version: '3.10' - tox-environment: django50 - - python-version: '3.11' - tox-environment: django50 - - python-version: '3.12' - tox-environment: django50 - # Django main - - python-version: '3.10' - tox-environment: djangomain - - python-version: '3.11' - tox-environment: djangomain - - python-version: '3.12' - tox-environment: djangomain + - djangomain env: TOXENV: ${{ matrix.tox-environment }} From fb89efc0e85af1a7bf2dd16fbb72d4e050944134 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Freitag?= Date: Wed, 17 Jul 2024 14:59:45 +0200 Subject: [PATCH 04/11] Add support for Django 5.1 --- .github/workflows/test.yml | 1 + pyproject.toml | 1 + tox.ini | 2 ++ 3 files changed, 4 insertions(+) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index b768a7fc..570df829 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -20,6 +20,7 @@ jobs: tox-environment: - django42 - django50 + - django51 - djangomain env: diff --git a/pyproject.toml b/pyproject.toml index 0cec5d06..92da5356 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -16,6 +16,7 @@ classifiers = [ "Framework :: Django", "Framework :: Django :: 4.2", "Framework :: Django :: 5.0", + "Framework :: Django :: 5.1", "Intended Audience :: Developers", "Intended Audience :: System Administrators", "License :: OSI Approved :: BSD License", diff --git a/tox.ini b/tox.ini index ec92ccb6..916fb805 100644 --- a/tox.ini +++ b/tox.ini @@ -4,6 +4,7 @@ envlist = docs django42 django50 + django51 djangomain isolated_build = true @@ -12,6 +13,7 @@ commands = {envpython} -Wa -b -m django test --settings tests.settings deps = django42: Django>=4.2,<4.3 django50: Django>=5.0,<5.1 + django51: Django>=5.1b1,<5.2 djangomain: https://github.com/django/django/archive/main.tar.gz [testenv:ruff] From 21c229685b068f37286f8b20d3dbe7256bfa5c7d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Freitag?= Date: Thu, 18 Jul 2024 20:48:59 +0200 Subject: [PATCH 05/11] Restore testing for Python 3.8 and 3.9 --- .github/workflows/test.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 570df829..6df29de7 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -22,6 +22,11 @@ jobs: - django50 - django51 - djangomain + include: + - python-version: 3.8 + tox-environment: django42 + - python-version: 3.9 + tox-environment: django42 env: TOXENV: ${{ matrix.tox-environment }} From 92a1439e6fc0b069fed54f0c12a0c19e7aaf7506 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Freitag?= Date: Thu, 18 Jul 2024 20:41:32 +0200 Subject: [PATCH 06/11] Add support for Python 3.13 --- .github/workflows/test.yml | 1 + pyproject.toml | 1 + 2 files changed, 2 insertions(+) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 6df29de7..ad3d8d05 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -17,6 +17,7 @@ jobs: - '3.10' - '3.11' - '3.12' + - '3.13-dev' tox-environment: - django42 - django50 diff --git a/pyproject.toml b/pyproject.toml index 92da5356..3b4a52b4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -28,6 +28,7 @@ classifiers = [ "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", "Topic :: Internet :: WWW/HTTP", "Topic :: Software Development :: Libraries :: Python Modules", "Topic :: System :: Systems Administration :: Authentication/Directory :: LDAP", From 1850b1900e2d6a754da89dcef8aa0fde6b5b1a72 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Freitag?= Date: Fri, 19 Jul 2024 18:44:24 +0200 Subject: [PATCH 07/11] Use tox-gh to simplify test matrix on GitHub --- .github/workflows/test.yml | 17 +++-------------- tox.ini | 9 +++++++++ 2 files changed, 12 insertions(+), 14 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index ad3d8d05..9b841300 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -14,23 +14,12 @@ jobs: matrix: # https://docs.djangoproject.com/en/dev/faq/install/#what-python-version-can-i-use-with-django python-version: + - '3.8' + - '3.9' - '3.10' - '3.11' - '3.12' - '3.13-dev' - tox-environment: - - django42 - - django50 - - django51 - - djangomain - include: - - python-version: 3.8 - tox-environment: django42 - - python-version: 3.9 - tox-environment: django42 - - env: - TOXENV: ${{ matrix.tox-environment }} steps: - name: Install LDAP libs @@ -50,7 +39,7 @@ jobs: python-version: ${{ matrix.python-version }} - name: Install dependencies - run: python -m pip install tox + run: python -m pip install tox-gh - name: Run tests run: tox diff --git a/tox.ini b/tox.ini index 916fb805..444c614d 100644 --- a/tox.ini +++ b/tox.ini @@ -8,6 +8,15 @@ envlist = djangomain isolated_build = true +[gh] +python = + 3.8 = django42 + 3.9 = django42 + 3.10 = django{42,50,51,main} + 3.11 = django{42,50,51,main} + 3.12 = django{42,50,51,main} + 3.13 = django{42,50,51,main} + [testenv] commands = {envpython} -Wa -b -m django test --settings tests.settings deps = From 7508234670da2a2ffd0314a211d1126c491bbe8a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Freitag?= Date: Fri, 19 Jul 2024 21:24:26 +0200 Subject: [PATCH 08/11] Add setuptools requirement to build docs with Python 3.12 ``` Running Sphinx v7.4.6 Configuration error: There is a programmable error in your configuration file: Traceback (most recent call last): File "/home/freitafr/dev/django-auth-ldap/venv/lib/python3.12/site-packages/sphinx/config.py", line 529, in eval_config_file exec(code, namespace) # NoQA: S102 ^^^^^^^^^^^^^^^^^^^^^ File "/home/freitafr/dev/django-auth-ldap/docs/conf.py", line 17, in from pkg_resources import DistributionNotFound, get_distribution ModuleNotFoundError: No module named 'pkg_resources' make: *** [Makefile:20: html] Error 2 ``` --- tox.ini | 1 + 1 file changed, 1 insertion(+) diff --git a/tox.ini b/tox.ini index 444c614d..87ffa51a 100644 --- a/tox.ini +++ b/tox.ini @@ -34,6 +34,7 @@ skip_install = true isolated_build = true deps = readme_renderer + setuptools>=65.0 sphinx commands = make -C docs html From 2c5d07b9262ab750a70dc0bed2eb7fc41dcafc61 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Freitag?= Date: Sat, 20 Jul 2024 11:35:13 +0200 Subject: [PATCH 09/11] Specify python-version for each GitHub action Otherwise, the system version is used, which issues a warning, is not under control and cannot use the pip cache. --- .github/workflows/check.yml | 2 ++ .github/workflows/docs.yml | 2 ++ .github/workflows/release.yml | 2 +- 3 files changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index fe565fbe..bcabe5eb 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -13,6 +13,8 @@ jobs: - name: Set up Python uses: actions/setup-python@v5 + with: + python-version: 3.x - name: Install dependencies run: python -m pip install tox diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 10ad872e..3f4878ea 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -26,6 +26,8 @@ jobs: - name: Set up Python uses: actions/setup-python@v5 + with: + python-version: 3.x - name: Install dependencies run: python -m pip install tox diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index f18e3725..0fae9255 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -13,7 +13,7 @@ jobs: - name: Set up Python uses: actions/setup-python@v5 with: - python-version: "3.x" + python-version: 3.x - name: Install dependencies run: pip install --user build setuptools twine wheel From 796129bc27a6a1076711bf6d2ab01b71181bf330 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Freitag?= Date: Sat, 20 Jul 2024 11:36:08 +0200 Subject: [PATCH 10/11] Cache pip packages across GitHub actions Be easier on the network and slightly faster. --- .github/workflows/check.yml | 1 + .github/workflows/docs.yml | 1 + .github/workflows/release.yml | 1 + .github/workflows/test.yml | 1 + 4 files changed, 4 insertions(+) diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index bcabe5eb..80dca6f4 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -15,6 +15,7 @@ jobs: uses: actions/setup-python@v5 with: python-version: 3.x + cache: pip - name: Install dependencies run: python -m pip install tox diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 3f4878ea..d47a1308 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -28,6 +28,7 @@ jobs: uses: actions/setup-python@v5 with: python-version: 3.x + cache: pip - name: Install dependencies run: python -m pip install tox diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 0fae9255..2cbc194f 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -14,6 +14,7 @@ jobs: uses: actions/setup-python@v5 with: python-version: 3.x + cache: pip - name: Install dependencies run: pip install --user build setuptools twine wheel diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 9b841300..6c71ffd6 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -37,6 +37,7 @@ jobs: uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} + cache: pip - name: Install dependencies run: python -m pip install tox-gh From 3685761acbdf5488cf51136be6b4c8d526b67c40 Mon Sep 17 00:00:00 2001 From: Marti Raudsepp Date: Tue, 30 Jul 2024 13:05:35 +0300 Subject: [PATCH 11/11] Propagate LDAP errors instead of silently ignoring, send more ldap_error signals * LDAPError exceptions are now propagated from the `LDAPSearch.execute()` method, which is used internally in many code paths. It's now possible to distinguish errors from empty results. * In particular `NestedMemberDNGroupType` no longer returns partial results when faced with errors, but also propagate the results. * When `MIRROR_GROUPS` or `MIRROR_GROUPS_EXCEPT` is enabled, then an error during group mirroring will fail authentication. Previously it could mirror a partial set of groups or remove all groups. * The `ldap_error` Django signal is now sent for more situations -- previously it only reported errors from authentication, but nothing else. --- django_auth_ldap/backend.py | 107 ++++++++++++++++++++++------------ django_auth_ldap/config.py | 24 +++----- docs/reference.rst | 13 +++-- tests/tests.py | 111 +++++++++++++++++++++++++++++++++++- 4 files changed, 194 insertions(+), 61 deletions(-) diff --git a/django_auth_ldap/backend.py b/django_auth_ldap/backend.py index 17c5e0c9..c63b6cec 100644 --- a/django_auth_ldap/backend.py +++ b/django_auth_ldap/backend.py @@ -78,6 +78,31 @@ ldap_error = django.dispatch.Signal() +_error_context_descriptions = { + "authenticate": "while authenticating", + "populate_user": "populating user info", + "get_group_permissions": "loading group permissions", + "search_for_user_dn": "looking up user", + "mirror_groups": "updating mirrored groups", +} + + +def _report_error(sender, context, user, request, exception): + description = _error_context_descriptions.get(context, "from unknown context") + logger.warning( + "Caught LDAPError %s: %s", + description, + pprint.pformat(exception) + ) + ldap_error.send( + sender, + context=context, + user=user, + request=request, + exception=exception, + ) + + class LDAPBackend: """ The main backend class. This implements the auth backend API, although it @@ -354,19 +379,13 @@ def authenticate(self, password): except self.AuthenticationFailed as e: logger.debug("Authentication failed for %s: %s", self._username, e) except ldap.LDAPError as e: - results = ldap_error.send( + _report_error( type(self.backend), - context="authenticate", - user=self._user, - request=self._request, - exception=e, + "authenticate", + self._user, + self._request, + e ) - if len(results) == 0: - logger.warning( - "Caught LDAPError while authenticating %s: %s", - self._username, - pprint.pformat(e), - ) except Exception as e: logger.warning("%s while authenticating %s", e, self._username) raise @@ -386,18 +405,13 @@ def get_group_permissions(self): if self.dn is not None: self._load_group_permissions() except ldap.LDAPError as e: - results = ldap_error.send( + _report_error( type(self.backend), - context="get_group_permissions", - user=self._user, - request=self._request, - exception=e, + "get_group_permissions", + self._user, + self._request, + e ) - if len(results) == 0: - logger.warning( - "Caught LDAPError loading group permissions: %s", - pprint.pformat(e), - ) return self._group_permissions @@ -414,20 +428,17 @@ def populate_user(self): self._get_or_create_user(force_populate=True) user = self._user + except self.AuthenticationFailed as e: + # Mirroring groups can raise AuthenticationFailed + logger.debug("Failed to populate user %s: %s", self._username, e) except ldap.LDAPError as e: - results = ldap_error.send( + _report_error( type(self.backend), - context="populate_user", - user=self._user, - request=self._request, - exception=e, + "populate_user", + self._user, + self._request, + e ) - if len(results) == 0: - logger.warning( - "Caught LDAPError while authenticating %s: %s", - self._username, - pprint.pformat(e), - ) except Exception as e: logger.warning("%s while authenticating %s", e, self._username) raise @@ -537,11 +548,21 @@ def _search_for_user(): "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: - (user_dn, self._user_attrs) = next(iter(results)) + user_dn = None + + try: + results = search.execute(self.connection, {"user": self._username}) + except ldap.LDAPError as e: + _report_error( + type(self.backend), + "search_for_user_dn", + self._user, + self._request, + e + ) else: - user_dn = None + if results is not None and len(results) == 1: + (user_dn, self._user_attrs) = next(iter(results)) return user_dn @@ -756,7 +777,19 @@ 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()) + try: + target_group_names = frozenset(self._get_groups().get_group_names()) + except ldap.LDAPError as e: + _report_error( + type(self.backend), + context="mirror_groups", + user=self._user, + request=self._request, + exception=e, + ) + # Prevent user from logging in since their groups are out of sync + raise self.AuthenticationFailed("Error mirroring user groups") + current_group_names = frozenset( self._user.groups.values_list("name", flat=True).iterator() ) diff --git a/django_auth_ldap/config.py b/django_auth_ldap/config.py index 6dcdefa8..64f57eb6 100644 --- a/django_auth_ldap/config.py +++ b/django_auth_ldap/config.py @@ -197,23 +197,13 @@ def execute(self, connection, filterargs=(), escape=True): if escape: filterargs = self._escape_filterargs(filterargs) - try: - filterstr = self.filterstr % filterargs - logger.debug( - "Invoking search_s('%s', %s, '%s')", self.base_dn, self.scope, filterstr - ) - results = connection.search_s( - self.base_dn, self.scope, filterstr, self.attrlist - ) - except ldap.LDAPError as e: - results = [] - logger.error( - "search_s('%s', %s, '%s') raised %s", - self.base_dn, - self.scope, - filterstr, - pprint.pformat(e), - ) + filterstr = self.filterstr % filterargs + logger.debug( + "Invoking search_s('%s', %s, '%s')", self.base_dn, self.scope, filterstr + ) + results = connection.search_s( + self.base_dn, self.scope, filterstr, self.attrlist + ) return self._process_results(results) diff --git a/docs/reference.rst b/docs/reference.rst index a989a152..2332a4bd 100644 --- a/docs/reference.rst +++ b/docs/reference.rst @@ -577,21 +577,24 @@ Backend .. data:: ldap_error - This is a Django signal that is sent when we receive an :exc:`ldap.LDAPError` exception. The signal has four keyword arguments: - ``context``: one of ``'authenticate'``, ``'get_group_permissions'``, or - ``'populate_user'``, indicating which API was being called when the - exception was caught. - - ``user``: the Django user being processed (if available). + ``'populate_user'``, ``'search_for_user_dn'`` or ``'mirror_groups'``, + indicating which API was being called when the exception was caught. + - ``user``: the Django user being processed (if available) or ``None``. - ``request``: the Django request object associated with the - authentication attempt (if available). + authentication attempt (if available) or ``None``. - ``exception``: the :exc:`~ldap.LDAPError` object itself. The sender is the :class:`~django_auth_ldap.backend.LDAPBackend` class (or subclass). + By default, LDAP errors are be handled by ``django_auth_ldap`` by failing + the authentication. If instead you wish to propagate the error to up + application code, then raise an exception from the signal handler. + .. class:: LDAPBackend :class:`~django_auth_ldap.backend.LDAPBackend` has one method that may be diff --git a/tests/tests.py b/tests/tests.py index 79646833..41cb3735 100644 --- a/tests/tests.py +++ b/tests/tests.py @@ -29,6 +29,7 @@ import pickle from copy import deepcopy from unittest import mock +from unittest.mock import ANY import ldap import slapdtest @@ -602,6 +603,31 @@ def test_populate_user_with_missing_attribute(self): ], ) + def test_populate_user_ldap_error(self): + self._init_settings( + USER_DN_TEMPLATE="uid=%(user)s,ou=people,o=test", + USER_ATTR_MAP={"first_name": "givenName", "last_name": "sn"}, + SERVER_URI="", # This will cause a network error + ) + + with self.assertLogs("django_auth_ldap", level=logging.DEBUG) as logs: + with catch_signal(ldap_error) as handler: + LDAPBackend().populate_user('alice') + + handler.assert_called_once_with( + signal=ldap_error, + sender=LDAPBackend, + context="populate_user", + user=None, + request=None, + exception=ANY, + ) + self.assertEqual( + logs.output[-1], + "WARNING:django_auth_ldap:Caught LDAPError populating user info: " + "LDAPError(0, 'Error')" + ) + @mock.patch.object(LDAPSearch, "execute", return_value=None) def test_populate_user_with_bad_search(self, mock_execute): self._init_settings( @@ -720,11 +746,46 @@ def handle_ldap_error(sender, **kwargs): request = RequestFactory().get("/") with self.assertRaises(ldap.LDAPError): authenticate(request=request, username="alice", password="password") - handler.assert_called_once() + assert handler.mock_calls[0].kwargs['context'] == 'search_for_user_dn' + assert handler.mock_calls[1].kwargs['context'] == 'authenticate' + assert handler.call_count == 2 _args, kwargs = handler.call_args self.assertEqual(kwargs["context"], "authenticate") self.assertEqual(kwargs["request"], request) + def test_search_for_user_dn_error(self): + self._init_settings( + USER_DN_TEMPLATE=None, + USER_SEARCH=LDAPSearch("ou=people,o=test", ldap.SCOPE_SUBTREE, "(uid=*)"), + USER_ATTR_MAP={"first_name": "givenName", "last_name": "sn"}, + SERVER_URI="", # This will cause a network error + ) + + request = RequestFactory().get("/") + + with self.assertLogs("django_auth_ldap", level=logging.DEBUG) as logs: + with catch_signal(ldap_error) as handler: + authenticate(request=request, username="alice", password="password") + + handler.assert_called_once_with( + signal=ldap_error, + sender=LDAPBackend, + context="search_for_user_dn", + user=None, + request=request, + exception=ANY, + ) + self.assertEqual( + logs.output[-2], + "WARNING:django_auth_ldap:Caught LDAPError looking up user: " + "LDAPError(0, 'Error')" + ) + self.assertEqual( + logs.output[-1], + "DEBUG:django_auth_ldap:Authentication failed for alice: failed " + "to map the username to a DN.", + ) + def test_populate_signal_ldap_error(self): self._init_settings( BIND_DN="uid=bob,ou=people,o=test", @@ -1421,6 +1482,52 @@ def test_group_mirroring_blacklist_noop(self): set(alice.groups.values_list("name", flat=True)), {"mirror1", "mirror3"} ) + def test_group_mirroring_error(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, + ) + + grp = Group.objects.create(name="test_group") + alice = User.objects.create(username="alice") + alice.groups.add(grp) + + with self.assertLogs("django_auth_ldap", level=logging.DEBUG) as logs: + with catch_signal(ldap_error) as handler: + with mock.patch( + "django_auth_ldap.backend._LDAPUserGroups.get_group_names", + side_effect=ldap.LDAPError(0, "Error") + ): + user = authenticate(username="alice", password="password") + + self.assertIsNone(user) + + # When there's an error populating groups, preserve old user groups. + self.assertEqual(set(alice.groups.all()), {grp}) + + handler.assert_called_once_with( + signal=ldap_error, + sender=LDAPBackend, + context="mirror_groups", + user=alice, + request=None, + exception=ANY, + ) + self.assertEqual( + logs.output[-2], + "WARNING:django_auth_ldap:Caught LDAPError updating mirrored groups: " + "LDAPError(0, 'Error')" + ) + self.assertEqual( + logs.output[-1], + "DEBUG:django_auth_ldap:Authentication failed for alice: Error " + "mirroring user groups" + ) + def test_authorize_external_users(self): self._init_settings( USER_DN_TEMPLATE="uid=%(user)s,ou=people,o=test", @@ -1542,7 +1649,7 @@ def test_start_tls(self, mock): self.assertEqual(log2, "DEBUG:django_auth_ldap:Initiating TLS") self.assertTrue( log3.startswith( - "WARNING:django_auth_ldap:Caught LDAPError while authenticating alice: " + "WARNING:django_auth_ldap:Caught LDAPError while authenticating: " ) ) pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy