Coverage for src/django_otp_webauthn/settings.py: 82%
44 statements
« prev ^ index » next coverage.py v7.5.4, created at 2024-06-23 20:15 +0000
« prev ^ index » next coverage.py v7.5.4, created at 2024-06-23 20:15 +0000
1# Settings pattern adapted from
2# https://overtag.dk/v2/blog/a-settings-pattern-for-reusable-django-apps/
3from dataclasses import dataclass
4from typing import Callable, Union
6from django.conf import settings as django_settings
7from django.core.exceptions import ImproperlyConfigured
8from django.http import HttpRequest
9from django.utils.module_loading import import_string
11settings_prefix = "OTP_WEBAUTHN"
14@dataclass(frozen=True)
15class AppSettings:
16 """Access this instance as ``django_otp_webauthn.settings.app_settings``."""
18 OTP_WEBAUTHN_EXCEPTION_LOGGER_NAME = "django_otp_webauthn"
19 """The logger name to use for exceptions. Leave blank to disable logging."""
21 OTP_WEBAUTHN_CREDENTIAL_MODEL = "django_otp_webauthn.WebAuthnCredential"
22 """Format: 'app_label.model_name'. The model to use for webauthn
23 credential."""
25 OTP_WEBAUTHN_ATTESTATION_MODEL = "django_otp_webauthn.WebAuthnAttestation"
26 """Format: 'app_label.model_name'. The model to use for webauthn
27 attestation."""
29 OTP_WEBAUTHN_HELPER_CLASS = "django_otp_webauthn.helpers.WebAuthnHelper"
30 """The class to use for webauthn operations. This should be a class that subclasses
31 ``django_otp_webauthn.helpers.WebAuthnHelper``."""
33 OTP_WEBAUTHN_ALLOW_PASSWORDLESS_LOGIN = True
34 """If true, the default views will allow users to login with just a webauthn
35 credential. No username or password required. The user will be marked as
36 having passed MFA authentication."""
38 OTP_WEBAUTHN_RP_ID: str = ""
39 """The relying party ID for webauthn ceremonies. This should be the main
40 domain of the web application, like: 'example.com'.
42 **Important:** registered WebAuthn credentials will be scoped to this
43 domain and its subdomains. Changing it will require users to re-register
44 their credentials. Migration is NOT possible.
45 """
47 OTP_WEBAUTHN_RP_ID_CALLABLE: Callable[[HttpRequest], str] = ""
48 """Advanced usage. Import path to a callable that returns the relying party ID for webauthn
49 ceremonies. The callable should accept a single ``HttpRequest`` argument
51 For example: 'my_project.utils.get_rp_id'
53 This takes precedence over the ``OTP_WEBAUTHN_RP_ID`` setting.
54 """
56 OTP_WEBAUTHN_RP_NAME: str = ""
57 """The relying party name for webauthn ceremonies. Some clients display this
58 value to users. It should be a human-readable name for the relying party.
59 For example, 'Acme Corp.'.
60 """
62 OTP_WEBAUTHN_RP_NAME_CALLABLE: Callable[[HttpRequest], str] = ""
63 """Advanced usage. Import path to a callable that returns the relying party
64 name for webauthn ceremonies. The callable should accept a single
65 ``HttpRequest`` argument.
67 For example: 'my_project.utils.get_rp_name'
69 This takes precedence over the ``OTP_WEBAUTHN_RP_NAME`` setting.
70 """
72 OTP_WEBAUTHN_ALLOWED_ORIGINS = []
73 """A list of allowed origins for webauthn authentication. An origin should be
74 in the format 'https://example.com'.
76 - Origins must be the same as the relying party ID domain, or a subdomain of the relying party ID.
77 - Origins must be secure (https://).
78 """
80 OTP_WEBAUTHN_SUPPORTED_COSE_ALGORITHMS = "all"
81 """A list of COSE algorithms supported by the server. Must be an integer
82 value from https://www.iana.org/assignments/cose/cose.xhtml#algorithms. If
83 set to the string 'all', the default algorithms from py_webauthn will be
84 used."""
86 OTP_WEBAUTHN_TIMEOUT_SECONDS = 60 * 5 # 5 minutes
87 """The timeout in seconds to request for client-side browser webauthn operations. Default is 5 minutes to
88 balance security and usability needs.
90 Take care to keep this value reasonable. You ought to follow WCAG 2.2
91 accessibility guidelines regarding timeouts. See https://www.w3.org/TR/WCAG22/#enough-time.
92 """
94 def __getattribute__(self, __name: str):
95 # Check if a Django project settings should override the app default.
96 # In order to avoid returning any random properties of the django settings, we inspect the prefix firstly.
97 if __name.startswith(settings_prefix) and hasattr(django_settings, __name): 97 ↛ 98line 97 didn't jump to line 98 because the condition on line 97 was never true
98 return getattr(django_settings, __name)
100 return super().__getattribute__(__name)
102 def _get_callable_setting(self, key: str) -> Union[Callable, None]:
103 """Imports and returns a callable setting."""
105 value = self.__getattribute__(key)
107 func = import_string(value)
108 if not callable(func):
109 raise ImproperlyConfigured(f"{key} must be a callable. Got {repr(func)}.")
111 return func
114app_settings = AppSettings()