Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 20 additions & 1 deletion .github/workflows/python-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -100,6 +101,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:
Expand All @@ -110,4 +124,9 @@ jobs:
env:
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
SONAR_HOST_URL: ${{ secrets.SONAR_HOST_URL }}
url: ${{ secrets.SONAR_HOST_URL }}/dashboard?id=django-windowsauthtoken

- 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 }}
3 changes: 3 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -62,3 +62,6 @@ source = ["django_windowsauthtoken"]

[tool.pytest.ini_options]
addopts = "--cov --cov-report=term-missing"

[tool.mypy]
strict = true
1 change: 1 addition & 0 deletions sonar-project.properties
Original file line number Diff line number Diff line change
Expand Up @@ -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
18 changes: 10 additions & 8 deletions src/django_windowsauthtoken/middleware.py
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -16,12 +18,12 @@
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
win32api = None
win32security = None
pywintypes = None # type: ignore[assignment]
win32api = None # type: ignore[assignment]
win32security = None # type: ignore[assignment]


class WindowsAuthTokenMiddleware:
Expand All @@ -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:
Expand Down Expand Up @@ -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)
6 changes: 3 additions & 3 deletions src/django_windowsauthtoken/views.py
Original file line number Diff line number Diff line change
@@ -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.
"""
Expand All @@ -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
Expand Down
2 changes: 1 addition & 1 deletion tests/test_middleware.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Loading