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

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 

5 

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 

10 

11settings_prefix = "OTP_WEBAUTHN" 

12 

13 

14@dataclass(frozen=True) 

15class AppSettings: 

16 """Access this instance as ``django_otp_webauthn.settings.app_settings``.""" 

17 

18 OTP_WEBAUTHN_EXCEPTION_LOGGER_NAME = "django_otp_webauthn" 

19 """The logger name to use for exceptions. Leave blank to disable logging.""" 

20 

21 OTP_WEBAUTHN_CREDENTIAL_MODEL = "django_otp_webauthn.WebAuthnCredential" 

22 """Format: 'app_label.model_name'. The model to use for webauthn 

23 credential.""" 

24 

25 OTP_WEBAUTHN_ATTESTATION_MODEL = "django_otp_webauthn.WebAuthnAttestation" 

26 """Format: 'app_label.model_name'. The model to use for webauthn 

27 attestation.""" 

28 

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``.""" 

32 

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.""" 

37 

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'. 

41 

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 """ 

46 

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 

50 

51 For example: 'my_project.utils.get_rp_id' 

52 

53 This takes precedence over the ``OTP_WEBAUTHN_RP_ID`` setting. 

54 """ 

55 

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 """ 

61 

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. 

66 

67 For example: 'my_project.utils.get_rp_name' 

68 

69 This takes precedence over the ``OTP_WEBAUTHN_RP_NAME`` setting. 

70 """ 

71 

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'. 

75 

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 """ 

79 

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.""" 

85 

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. 

89 

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 """ 

93 

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) 

99 

100 return super().__getattribute__(__name) 

101 

102 def _get_callable_setting(self, key: str) -> Union[Callable, None]: 

103 """Imports and returns a callable setting.""" 

104 

105 value = self.__getattribute__(key) 

106 

107 func = import_string(value) 

108 if not callable(func): 

109 raise ImproperlyConfigured(f"{key} must be a callable. Got {repr(func)}.") 

110 

111 return func 

112 

113 

114app_settings = AppSettings()