From 534b6696d53020557bc9d2242186ed8206549648 Mon Sep 17 00:00:00 2001 From: Tom Hendrikx Date: Sun, 31 Aug 2025 14:36:21 +0200 Subject: [PATCH 1/9] Generate mypy reports for sonarqube --- .github/workflows/python-test.yml | 13 +++++++++++++ sonar-project.properties | 1 + 2 files changed, 14 insertions(+) diff --git a/.github/workflows/python-test.yml b/.github/workflows/python-test.yml index 8579a2d..460c6d1 100644 --- a/.github/workflows/python-test.yml +++ b/.github/workflows/python-test.yml @@ -100,6 +100,19 @@ jobs: # No shallow clone for a better analysis fetch-depth: 0 + - name: Set up Python 3.13 + uses: actions/setup-python@v3 + with: + python-version: 3.13 + - name: Install Mypy + run: | + python -m pip install mypy django-stubs types-pywin32 + + - name: Run MyPy + run: | + mypy --strict --junit-xml mypy.xml src + continue-on-error: true + - name: Download coverage report uses: actions/download-artifact@v4 with: diff --git a/sonar-project.properties b/sonar-project.properties index bba5c25..947b2fe 100644 --- a/sonar-project.properties +++ b/sonar-project.properties @@ -5,3 +5,4 @@ sonar.sources = src sonar.tests = tests sonar.python.coverage.reportPaths=coverage.xml sonar.python.xunit.reportPath=pytest.xml +sonar.python.mypy.reportPaths=mypy.xml From e48b34607e9ad0d0c89643736d9d3acc7cadf75d Mon Sep 17 00:00:00 2001 From: Tom Hendrikx Date: Sun, 31 Aug 2025 14:43:16 +0200 Subject: [PATCH 2/9] Remove unused url envvar --- .github/workflows/python-test.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/python-test.yml b/.github/workflows/python-test.yml index 460c6d1..53aa2cc 100644 --- a/.github/workflows/python-test.yml +++ b/.github/workflows/python-test.yml @@ -123,4 +123,3 @@ jobs: env: SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} SONAR_HOST_URL: ${{ secrets.SONAR_HOST_URL }} - url: ${{ secrets.SONAR_HOST_URL }}/dashboard?id=django-windowsauthtoken From f4ae18f6c4ef7513bdef47785021ea2ec1f3f142 Mon Sep 17 00:00:00 2001 From: Tom Hendrikx Date: Sun, 31 Aug 2025 14:53:32 +0200 Subject: [PATCH 3/9] Silence type errors for broken import handling Trying to resolve this by making a complex type will just make more mess downstream like: ``` error: Item "None" of "object | None" has no attribute "GetTokenInformation" [union-attr] ``` --- src/django_windowsauthtoken/middleware.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/django_windowsauthtoken/middleware.py b/src/django_windowsauthtoken/middleware.py index 99d272f..5eb80aa 100644 --- a/src/django_windowsauthtoken/middleware.py +++ b/src/django_windowsauthtoken/middleware.py @@ -19,9 +19,9 @@ except ImportError: if _IGNORE_PYWIN32_ERRORS: # pragma: no cover logger.warning("pywin32 is not installed, but errors are being ignored.") - pywintypes = None - win32api = None - win32security = None + pywintypes = None # type: ignore[assignment] + win32api = None # type: ignore[assignment] + win32security = None # type: ignore[assignment] class WindowsAuthTokenMiddleware: From 7e40eab92c6c714d04706d08dec5f7b2f42b6a25 Mon Sep 17 00:00:00 2001 From: Tom Hendrikx Date: Sun, 31 Aug 2025 14:58:44 +0200 Subject: [PATCH 4/9] Fix mypy issues in views --- src/django_windowsauthtoken/views.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/django_windowsauthtoken/views.py b/src/django_windowsauthtoken/views.py index 6320418..4682444 100644 --- a/src/django_windowsauthtoken/views.py +++ b/src/django_windowsauthtoken/views.py @@ -1,10 +1,10 @@ from django.conf import settings -from django.http import JsonResponse +from django.http import HttpRequest, JsonResponse from django.views.decorators.http import require_GET @require_GET -def debug_view(request): +def debug_view(request: HttpRequest) -> JsonResponse: """ A simple debug view that returns the current user's username and domain. """ @@ -22,7 +22,7 @@ def debug_view(request): # State of the user object "user.is_authenticated": request.user.is_authenticated, "user.is_anonymous": request.user.is_anonymous, - "user.username": request.user.username if request.user.is_authenticated else "N/A", + "user.username": request.user.get_username() if request.user.is_authenticated else "N/A", # Full request representation for deeper debugging if needed, mainly ASGI/WSGI differences "request": str(request), # Full META dump for deeper debugging if needed From 370c3b5b933d85513e4e02df582781254bc6bd5c Mon Sep 17 00:00:00 2001 From: Tom Hendrikx Date: Sun, 31 Aug 2025 15:07:19 +0200 Subject: [PATCH 5/9] Resolve mypy issues in middleware --- src/django_windowsauthtoken/middleware.py | 8 +++++--- tests/test_middleware.py | 2 +- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/django_windowsauthtoken/middleware.py b/src/django_windowsauthtoken/middleware.py index 5eb80aa..8781437 100644 --- a/src/django_windowsauthtoken/middleware.py +++ b/src/django_windowsauthtoken/middleware.py @@ -1,8 +1,10 @@ import logging import os +from typing import Callable from django.conf import settings from django.core.exceptions import ImproperlyConfigured +from django.http import HttpRequest, HttpResponse from django.utils.module_loading import import_string from .formatters import DEFAULT_FORMATTER, FormattingError @@ -34,14 +36,14 @@ class WindowsAuthTokenMiddleware: header_name = "X-IIS-WindowsAuthToken" """The HTTP header name where the Windows Authentication Token is expected.""" - def __init__(self, get_response): + def __init__(self, get_response: Callable[[HttpRequest], HttpResponse]) -> None: self.get_response = get_response self.username_formatter: str = getattr(settings, "WINDOWSAUTHTOKEN_USERNAME_FORMATTER", DEFAULT_FORMATTER) if not any([win32security, pywintypes, win32api]) and not _IGNORE_PYWIN32_ERRORS: raise ImproperlyConfigured("pywin32 is required for Windows Authentication Token middleware.'") - def __call__(self, request): + def __call__(self, request: HttpRequest) -> HttpResponse: auth_token = request.headers.get(self.header_name, "") if auth_token: try: @@ -135,5 +137,5 @@ def format_username(self, user: str, domain: str) -> str: Raises: FormattingError: If the formatter raises an error. """ - formatter = import_string(self.username_formatter) + formatter: Callable[[str, str], str] = import_string(self.username_formatter) return formatter(user, domain) diff --git a/tests/test_middleware.py b/tests/test_middleware.py index 47eec4b..5ddf0f8 100644 --- a/tests/test_middleware.py +++ b/tests/test_middleware.py @@ -270,4 +270,4 @@ async def test_combine_with_remote_user_middleware_async(mocker, settings, async assert await User.objects.acount() == 1, "User should be created by RemoteUserMiddleware" user = await User.objects.afirst() assert user == response.asgi_request.user - assert user.username == r"TESTDOMAIN\testuser" + assert user.get_username() == r"TESTDOMAIN\testuser" From 3045ceabb6c54f76e33d16ec7c2943dc9f40a24d Mon Sep 17 00:00:00 2001 From: Tom Hendrikx Date: Sun, 31 Aug 2025 15:07:35 +0200 Subject: [PATCH 6/9] Enable mypy strict checks --- pyproject.toml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index 06dc38d..4e78c97 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -62,3 +62,6 @@ source = ["django_windowsauthtoken"] [tool.pytest.ini_options] addopts = "--cov --cov-report=term-missing" + +[tool.mypy] +strict = true From 976b618a8d9227bb9a3a3f1304f5cc93f098fb7f Mon Sep 17 00:00:00 2001 From: Tom Hendrikx Date: Sun, 31 Aug 2025 15:29:03 +0200 Subject: [PATCH 7/9] Change coverage markers for pywin32 imports You can never get 100% coverage, depending on the platform you're running the coverage analysis on, so let's ignore both. --- src/django_windowsauthtoken/middleware.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/django_windowsauthtoken/middleware.py b/src/django_windowsauthtoken/middleware.py index 8781437..82e9404 100644 --- a/src/django_windowsauthtoken/middleware.py +++ b/src/django_windowsauthtoken/middleware.py @@ -18,8 +18,8 @@ import pywintypes import win32api import win32security -except ImportError: - if _IGNORE_PYWIN32_ERRORS: # pragma: no cover +except ImportError: # pragma: no cover + if _IGNORE_PYWIN32_ERRORS: logger.warning("pywin32 is not installed, but errors are being ignored.") pywintypes = None # type: ignore[assignment] win32api = None # type: ignore[assignment] From 67f13c2cb46917e07fc03bce59513fa663778ea3 Mon Sep 17 00:00:00 2001 From: Tom Hendrikx Date: Sun, 31 Aug 2025 15:37:50 +0200 Subject: [PATCH 8/9] Check the sonarqube quality gate and optionally fail the check --- .github/workflows/python-test.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/workflows/python-test.yml b/.github/workflows/python-test.yml index 53aa2cc..defd352 100644 --- a/.github/workflows/python-test.yml +++ b/.github/workflows/python-test.yml @@ -123,3 +123,9 @@ jobs: env: SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} SONAR_HOST_URL: ${{ secrets.SONAR_HOST_URL }} + + - name: SonarQube Quality Gate check + uses: sonarsource/sonarqube-quality-gate-action@v1.2.0 + env: + SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} + SONAR_HOST_URL: ${{ secrets.SONAR_HOST_URL }} From 4da4dbebcdcfaa87c4c57b0bc6cd6a4f412b0def Mon Sep 17 00:00:00 2001 From: Tom Hendrikx Date: Sun, 31 Aug 2025 15:40:06 +0200 Subject: [PATCH 9/9] Always get pytest reports as artifact --- .github/workflows/python-test.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/python-test.yml b/.github/workflows/python-test.yml index defd352..bbd5b0f 100644 --- a/.github/workflows/python-test.yml +++ b/.github/workflows/python-test.yml @@ -79,6 +79,7 @@ jobs: - name: Test with pytest run: | uv run pytest --cov-report xml:coverage.xml --junitxml=pytest.xml --cov-fail-under=80 + continue-on-error: true # Always continue to upload reports - name: Upload coverage and test reports # Upload coverage report only once to avoid redundancy