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
16 changes: 13 additions & 3 deletions dje/tests/test_two_factor.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,13 @@
from django.urls import reverse

import django_otp
from django_otp.forms import OTPTokenForm
from django_otp.oath import totp as oauth_totp
from django_otp.plugins.otp_totp.models import TOTPDevice

from dje.models import Dataspace
from dje.tests import create_user
from dje.two_factor import TwoFactorDisableForm
from dje.two_factor import TwoFactorVerifyForm


class TwoFactorAuthenticationTestCase(TestCase):
Expand Down Expand Up @@ -132,7 +133,7 @@ def test_two_factor_authentication_disable_2fa(self):
response = self.client.post(self.tfa_disable_url, data=data)
self.assertContains(response, "Please enter your OTP token.")

device_form_value = OTPTokenForm.device_choices(self.user)[0][0]
device_form_value = device.persistent_id
data = {"otp_token": "123456", "otp_device": device_form_value}
response = self.client.post(self.tfa_disable_url, data=data)
form_error = "Invalid token. Please make sure you have entered it correctly."
Expand Down Expand Up @@ -178,7 +179,7 @@ def test_two_factor_authentication_login_with_2fa(self):
self.assertContains(response, "Please enter your OTP token.")

valid_token = self._get_valid_token(device.bin_key)
device_form_value = OTPTokenForm.device_choices(self.user)[0][0]
device_form_value = device.persistent_id
data = {
"otp_token": valid_token,
"otp_device": device_form_value,
Expand All @@ -190,3 +191,12 @@ def test_two_factor_authentication_login_with_2fa(self):
self.assertIn("_auth_user_id", session_keys)
self.assertIn("_auth_user_hash", session_keys)
self.assertIn("otp_device_id", session_keys)

def test_two_factor_authentication_token_form_preselects_user_device(self):
device = self._create_device(self.user)
expected = f'<option value="{device.persistent_id}" selected>default</option>'

for form_class in [TwoFactorDisableForm, TwoFactorVerifyForm]:
form = form_class(user=self.user)
self.assertEqual(device.persistent_id, form.fields["otp_device"].initial)
self.assertInHTML(expected, str(form["otp_device"]))
18 changes: 16 additions & 2 deletions dje/two_factor.py
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,21 @@ def helper(self):
return helper


class TwoFactorDisableForm(OTPTokenForm):
class TwoFactorTokenForm(OTPTokenForm):
"""
Preselect the user device: the `otp_device` select is hidden and its first
choice is blank.
"""

def __init__(self, user, *args, **kwargs):
super().__init__(user, *args, **kwargs)

user_device = next(django_otp.devices_for_user(user), None)
if user_device:
self.fields["otp_device"].initial = user_device.persistent_id


class TwoFactorDisableForm(TwoFactorTokenForm):
@property
def helper(self):
helper = FormHelper()
Expand Down Expand Up @@ -168,7 +182,7 @@ def helper(self):
return helper


class TwoFactorVerifyForm(OTPTokenForm):
class TwoFactorVerifyForm(TwoFactorTokenForm):
"""Verify that the user provided `otp_token` is valid."""

@property
Expand Down
6 changes: 5 additions & 1 deletion notification/tests/test_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,11 @@ def test_webhook_subscription_deliver_inactive_returns_false(self, mock_post):
@patch("requests.post")
def test_webhook_subscription_deliver_request_exception_saves_error(self, mock_post):
mock_post.side_effect = req.exceptions.ConnectionError("Connection refused")
delivery = self.webhook.deliver(None, payload_override={"key": "val"})
with self.assertLogs("aboutcode.notifications", level="ERROR") as captured:
delivery = self.webhook.deliver(None, payload_override={"key": "val"})
self.assertEqual(
["Connection refused"], [record.getMessage() for record in captured.records]
)
self.assertIsNotNone(delivery)
self.assertFalse(delivery.delivered)
self.assertIn("Connection refused", delivery.delivery_error)
Expand Down
28 changes: 15 additions & 13 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -34,15 +34,15 @@ dependencies = [
"setuptools==84.0.0",
"packaging==26.3",
# Django
"django==6.1.1",
"django==6.1.2",
"asgiref==3.12.1",
"typing-extensions==4.16.0",
"sqlparse==0.6.0",
# Django apps
"django-crispy-forms==2.7",
"crispy-bootstrap5==2026.9",
"django-grappelli==5.0.0",
"django-filter==26.1",
"django-filter==26.2",
"django-registration==5.2.1",
"confusable_homoglyphs==3.3.1",
"django-guardian==3.5.0",
Expand All @@ -54,16 +54,18 @@ dependencies = [
"altcha==1.0.0",
"django-altcha==1.0.0",
# REST API
"djangorestframework==3.18.1",
"djangorestframework==3.18.3",
# API documentation
# Do not upgrade: 1.21.17+ requires swagger-spec-validator, which pulls jsonschema,
# referencing, rpds-py, jsonschema-specifications, and importlib-resources.
"drf-yasg==1.21.15",
"uritemplate==4.2.0",
"inflection==0.5.1",
"pytz==2026.4",
"pytz==2026.5",
# Track failed login attempts
"django-axes==8.3.1",
# Multi-factor authentication
"django-otp==1.7.3",
"django-otp==1.7.4",
"qrcode==8.2",
# Database
"psycopg==3.3.6",
Expand All @@ -72,20 +74,20 @@ dependencies = [
# Antivirus
"clamd==1.0.2",
# Testing
"model-bakery==1.24.1",
"model-bakery==1.24.2",
# Task queue
"rq==2.12.0",
"croniter==6.2.4",
"django-rq==4.2.0",
"fakeredis==2.38.0",
"fakeredis==2.39.0",
# Libs
"certifi==2026.7.22",
"urllib3==2.8.0",
"python-dateutil==2.9.0.post0",
"six==1.17.0",
"requests==2.34.2",
"idna==3.20",
"charset-normalizer==3.5.1",
"charset-normalizer==3.5.2",
"PyYAML==6.0.3",
"XlsxWriter==3.2.9",
# Markdown
Expand All @@ -111,7 +113,7 @@ dependencies = [
"attrs==26.1.0",
"click==8.5.0",
"Jinja2==3.1.6",
"MarkupSafe==3.0.3",
"markupsafe==3.0.4",
"saneyaml==0.6.1",
"openpyxl==3.1.5",
"et-xmlfile==2.0.0",
Expand All @@ -124,14 +126,14 @@ dependencies = [
"sortedcontainers==2.4.0",
"py-serializable==2.1.0",
# CSAF
"pydantic==2.13.5",
"pydantic-core==2.46.5",
"pydantic==2.14.1",
"pydantic-core==2.50.1",
"typing-inspection==0.4.4",
"annotated-types==0.8.0",
# OpenVEX
"msgspec==0.21.1",
"msgspec==0.22.0",
# OpenDocument Format
"odfdo==3.26.3",
"odfdo==3.27.1",
"lxml==6.1.3",
]

Expand Down
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file removed thirdparty/dist/fakeredis-2.38.0-py3-none-any.whl
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file removed thirdparty/dist/markupsafe-3.0.3.tar.gz
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file removed thirdparty/dist/model_bakery-1.24.1-py3-none-any.whl
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file removed thirdparty/dist/pydantic-2.13.5-py3-none-any.whl
Binary file not shown.
Binary file added thirdparty/dist/pydantic-2.14.1-py3-none-any.whl
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Loading
Loading