django-pfx 1.4.dev46__tar.gz → 1.4.dev50__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/PKG-INFO +1 -1
- {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/django_pfx.egg-info/PKG-INFO +1 -1
- {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/doc/source/authentication.md +4 -0
- {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/pfx/pfxcore/default_settings.py +2 -0
- {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/pfx/pfxcore/models/otp_user_mixin.py +14 -4
- {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/tests/tests/test_auth_api.py +50 -1
- {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/.gitignore +0 -0
- {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/.gitlab-ci.yml +0 -0
- {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/.pre-commit-config.yaml +0 -0
- {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/LICENSE +0 -0
- {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/MANIFEST.in +0 -0
- {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/README.md +0 -0
- {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/django_pfx.egg-info/SOURCES.txt +0 -0
- {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/django_pfx.egg-info/dependency_links.txt +0 -0
- {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/django_pfx.egg-info/requires.txt +0 -0
- {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/django_pfx.egg-info/top_level.txt +0 -0
- {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/doc/Makefile +0 -0
- {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/doc/conf.py +0 -0
- {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/doc/index.rst +0 -0
- {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/doc/source/api.views.rst +0 -0
- {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/doc/source/decorator.md +0 -0
- {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/doc/source/generate_openapi.md +0 -0
- {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/doc/source/getting_started.md +0 -0
- {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/doc/source/internationalisation.md +0 -0
- {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/doc/source/model.md +0 -0
- {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/doc/source/pfx_views.md +0 -0
- {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/doc/source/profiling.md +0 -0
- {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/doc/source/settings.md +0 -0
- {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/doc/source/testing.md +0 -0
- {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/img/pfx.png +0 -0
- {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/img/pfx.svg +0 -0
- {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/make_messages +0 -0
- {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/manage.py +0 -0
- {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/pfx/__init__.py +0 -0
- {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/pfx/pfxcore/__init__.py +0 -0
- {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/pfx/pfxcore/apidoc/__init__.py +0 -0
- {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/pfx/pfxcore/apidoc/parameters.py +0 -0
- {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/pfx/pfxcore/apidoc/schema.py +0 -0
- {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/pfx/pfxcore/apidoc/tags.py +0 -0
- {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/pfx/pfxcore/apps.py +0 -0
- {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/pfx/pfxcore/decorator/__init__.py +0 -0
- {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/pfx/pfxcore/decorator/rest.py +0 -0
- {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/pfx/pfxcore/exceptions.py +0 -0
- {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/pfx/pfxcore/fields.py +0 -0
- {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/pfx/pfxcore/http/__init__.py +0 -0
- {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/pfx/pfxcore/http/json_response.py +0 -0
- {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/pfx/pfxcore/locale/fr/LC_MESSAGES/django.mo +0 -0
- {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/pfx/pfxcore/locale/fr/LC_MESSAGES/django.po +0 -0
- {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/pfx/pfxcore/management/__init__.py +0 -0
- {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/pfx/pfxcore/management/commands/__init__.py +0 -0
- {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/pfx/pfxcore/management/commands/makeapidoc.py +0 -0
- {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/pfx/pfxcore/management/commands/profile.py +0 -0
- {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/pfx/pfxcore/middleware/__init__.py +0 -0
- {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/pfx/pfxcore/middleware/authentication.py +0 -0
- {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/pfx/pfxcore/middleware/locale.py +0 -0
- {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/pfx/pfxcore/middleware/profiling.py +0 -0
- {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/pfx/pfxcore/migrations/0001_initial.py +0 -0
- {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/pfx/pfxcore/migrations/__init__.py +0 -0
- {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/pfx/pfxcore/models/__init__.py +0 -0
- {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/pfx/pfxcore/models/abstract_pfx_base_user.py +0 -0
- {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/pfx/pfxcore/models/cache_mixins.py +0 -0
- {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/pfx/pfxcore/models/login_ban.py +0 -0
- {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/pfx/pfxcore/models/not_null_fields.py +0 -0
- {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/pfx/pfxcore/models/pfx_models.py +0 -0
- {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/pfx/pfxcore/models/pfx_user.py +0 -0
- {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/pfx/pfxcore/models/user_filtered_queryset_mixin.py +0 -0
- {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/pfx/pfxcore/serializers/__init__.py +0 -0
- {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/pfx/pfxcore/serializers/json.py +0 -0
- {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/pfx/pfxcore/settings.py +0 -0
- {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/pfx/pfxcore/shortcuts.py +0 -0
- {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/pfx/pfxcore/storage/__init__.py +0 -0
- {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/pfx/pfxcore/storage/s3_storage.py +0 -0
- {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/pfx/pfxcore/templates/registration/otp_code_email.txt +0 -0
- {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/pfx/pfxcore/templates/registration/otp_code_subject.txt +0 -0
- {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/pfx/pfxcore/templates/registration/password_reset_email.txt +0 -0
- {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/pfx/pfxcore/templates/registration/password_reset_subject.txt +0 -0
- {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/pfx/pfxcore/templates/registration/welcome_email.txt +0 -0
- {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/pfx/pfxcore/templates/registration/welcome_subject.txt +0 -0
- {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/pfx/pfxcore/test.py +0 -0
- {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/pfx/pfxcore/urls.py +0 -0
- {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/pfx/pfxcore/views/__init__.py +0 -0
- {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/pfx/pfxcore/views/authentication_views.py +0 -0
- {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/pfx/pfxcore/views/fields.py +0 -0
- {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/pfx/pfxcore/views/filters_views.py +0 -0
- {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/pfx/pfxcore/views/locale_views.py +0 -0
- {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/pfx/pfxcore/views/parameters/__init__.py +0 -0
- {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/pfx/pfxcore/views/parameters/date_format.py +0 -0
- {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/pfx/pfxcore/views/parameters/groups.py +0 -0
- {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/pfx/pfxcore/views/parameters/list_count.py +0 -0
- {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/pfx/pfxcore/views/parameters/list_items.py +0 -0
- {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/pfx/pfxcore/views/parameters/list_mode.py +0 -0
- {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/pfx/pfxcore/views/parameters/list_order.py +0 -0
- {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/pfx/pfxcore/views/parameters/list_search.py +0 -0
- {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/pfx/pfxcore/views/parameters/media_redirect.py +0 -0
- {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/pfx/pfxcore/views/parameters/meta_fields.py +0 -0
- {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/pfx/pfxcore/views/parameters/meta_filters.py +0 -0
- {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/pfx/pfxcore/views/parameters/meta_orders.py +0 -0
- {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/pfx/pfxcore/views/parameters/subset.py +0 -0
- {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/pfx/pfxcore/views/parameters/subset_limit.py +0 -0
- {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/pfx/pfxcore/views/parameters/subset_offset.py +0 -0
- {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/pfx/pfxcore/views/parameters/subset_page.py +0 -0
- {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/pfx/pfxcore/views/parameters/subset_page_size.py +0 -0
- {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/pfx/pfxcore/views/parameters/subset_page_subset.py +0 -0
- {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/pfx/pfxcore/views/rest_views.py +0 -0
- {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/pfx/settings/__init__.py +0 -0
- {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/pfx/settings/dev.py +0 -0
- {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/pyproject.toml +0 -0
- {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/requirements.txt +0 -0
- {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/serve-doc +0 -0
- {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/setup.cfg +0 -0
- {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/setup.py +0 -0
- {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/tests/__init__.py +0 -0
- {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/tests/locale/fr/LC_MESSAGES/django.po +0 -0
- {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/tests/models.py +0 -0
- {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/tests/settings/__init__.py +0 -0
- {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/tests/settings/ci.py +0 -0
- {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/tests/settings/common.py +0 -0
- {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/tests/settings/dev.py +0 -0
- {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/tests/settings/dev_custom_example.py +0 -0
- {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/tests/settings/dev_default.py +0 -0
- {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/tests/tests/__init__.py +0 -0
- {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/tests/tests/basic_api_errors.py +0 -0
- {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/tests/tests/basic_api_test.py +0 -0
- {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/tests/tests/test_api_doc.py +0 -0
- {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/tests/tests/test_body_mixin.py +0 -0
- {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/tests/tests/test_cache.py +0 -0
- {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/tests/tests/test_client.py +0 -0
- {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/tests/tests/test_fields.py +0 -0
- {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/tests/tests/test_filters.py +0 -0
- {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/tests/tests/test_locale_api.py +0 -0
- {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/tests/tests/test_perm_tests.py +0 -0
- {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/tests/tests/test_perms_api.py +0 -0
- {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/tests/tests/test_profiling_middleware.py +0 -0
- {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/tests/tests/test_settings.py +0 -0
- {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/tests/tests/test_shortcuts.py +0 -0
- {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/tests/tests/test_timezone_middleware.py +0 -0
- {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/tests/tests/test_tools.py +0 -0
- {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/tests/tests/test_user_queryset.py +0 -0
- {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/tests/tests/test_view_decorators.py +0 -0
- {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/tests/tests/test_view_fields.py +0 -0
- {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/tests/urls.py +0 -0
- {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/tests/views.py +0 -0
|
@@ -97,6 +97,10 @@ class MyUser(OtpUserMixin, AbstractPFXBaseUser):
|
|
|
97
97
|
* `PFX_TOKEN_OTP_VALIDITY`: Validity for OTP tokens (corresponds to the maximum time to enter
|
|
98
98
|
an OTP code after logging in with a password) (optional, default `{'minutes': 15}`)
|
|
99
99
|
* `PFX_HOTP_CODE_VALIDITY`: Validity of HOTP codes in minutes (used to send code by email) (optional, default `15`).
|
|
100
|
+
* `PFX_OTP_VALID_WINDOW`: TOTP valid window (optional, default `1`).
|
|
101
|
+
According to [RFC 6238 section 5.2](https://www.ietf.org/rfc/rfc6238.html#section-5.2).
|
|
102
|
+
* `PFX_OTP_IMAGE`: An image https URL used by FreeOTP. See [FreeOTP URI](https://github.com/npmccallum/freeotp-android/blob/master/URI.md).
|
|
103
|
+
* `PFX_OTP_COLOR`: A brand color (in RRGGBB format) for used by FreeOTP. See [FreeOTP URI](https://github.com/npmccallum/freeotp-android/blob/master/URI.md).
|
|
100
104
|
|
|
101
105
|
The user can then enable or disable the OTP auth using the [services documented below](#enable-mfa-otp).
|
|
102
106
|
|
|
@@ -2,7 +2,9 @@ PFX_TOKEN_SHORT_VALIDITY = {'hours': 12}
|
|
|
2
2
|
PFX_TOKEN_LONG_VALIDITY = {'days': 30}
|
|
3
3
|
PFX_TOKEN_OTP_VALIDITY = {'minutes': 15}
|
|
4
4
|
PFX_HOTP_CODE_VALIDITY = 15
|
|
5
|
+
PFX_OTP_VALID_WINDOW = 1
|
|
5
6
|
PFX_OTP_IMAGE = None
|
|
7
|
+
PFX_OTP_COLOR = None
|
|
6
8
|
|
|
7
9
|
PFX_COOKIE_DOMAIN = None
|
|
8
10
|
PFX_COOKIE_SECURE = True
|
|
@@ -65,7 +65,7 @@ class OtpUserMixin(models.Model):
|
|
|
65
65
|
self.otp_secret_token = None
|
|
66
66
|
self.save(update_fields=['otp_secret_token'])
|
|
67
67
|
|
|
68
|
-
def get_otp_setup_uri(self, tmp=False):
|
|
68
|
+
def get_otp_setup_uri(self, tmp=False, with_color=True):
|
|
69
69
|
"""Return the setup URL for OTP activation.
|
|
70
70
|
"""
|
|
71
71
|
import pyotp
|
|
@@ -73,9 +73,14 @@ class OtpUserMixin(models.Model):
|
|
|
73
73
|
name=self.get_username(), issuer_name=settings.PFX_SITE_NAME)
|
|
74
74
|
if settings.PFX_OTP_IMAGE:
|
|
75
75
|
args['image'] = settings.PFX_OTP_IMAGE
|
|
76
|
-
|
|
76
|
+
uri = pyotp.totp.TOTP(
|
|
77
77
|
tmp and self.otp_secret_token_tmp or
|
|
78
78
|
self.otp_secret_token).provisioning_uri(**args)
|
|
79
|
+
if with_color and settings.PFX_OTP_COLOR:
|
|
80
|
+
# TODO: Can be put in provisioning_uri args if
|
|
81
|
+
# https://github.com/pyauth/pyotp/pull/164 is merge and published.
|
|
82
|
+
uri = f"{uri}&color={settings.PFX_OTP_COLOR}"
|
|
83
|
+
return uri
|
|
79
84
|
|
|
80
85
|
def is_otp_valid(self, otp_code, tmp=False):
|
|
81
86
|
"""Verify an OTP code.
|
|
@@ -86,8 +91,13 @@ class OtpUserMixin(models.Model):
|
|
|
86
91
|
:returns: `True` if the code is valid, `False` otherwise.
|
|
87
92
|
"""
|
|
88
93
|
import pyotp
|
|
89
|
-
|
|
90
|
-
|
|
94
|
+
|
|
95
|
+
# TODO : The with_color paramter can be removed if
|
|
96
|
+
# https://github.com/pyauth/pyotp/pull/164 is merge and published.
|
|
97
|
+
totp = pyotp.parse_uri(
|
|
98
|
+
self.get_otp_setup_uri(tmp=tmp, with_color=False))
|
|
99
|
+
valid = totp.verify(
|
|
100
|
+
otp_code, valid_window=settings.PFX_OTP_VALID_WINDOW)
|
|
91
101
|
if not valid and timezone.now() <= self.hotp_expiry:
|
|
92
102
|
hotp = pyotp.hotp.HOTP(
|
|
93
103
|
tmp and self.otp_secret_token_tmp or
|
|
@@ -8,6 +8,7 @@ from django.conf import settings
|
|
|
8
8
|
from django.contrib.auth.tokens import default_token_generator
|
|
9
9
|
from django.core import mail
|
|
10
10
|
from django.test import TransactionTestCase, modify_settings, override_settings
|
|
11
|
+
from django.utils import timezone
|
|
11
12
|
from django.utils.encoding import force_bytes
|
|
12
13
|
from django.utils.http import urlsafe_base64_encode
|
|
13
14
|
|
|
@@ -894,7 +895,9 @@ class AuthAPITest(TestAssertMixin, TransactionTestCase):
|
|
|
894
895
|
self.assertRC(response, 200)
|
|
895
896
|
self.client.token = self.get_val(response, 'token')
|
|
896
897
|
|
|
897
|
-
@override_settings(
|
|
898
|
+
@override_settings(
|
|
899
|
+
PFX_OTP_IMAGE="https://example.org/fake.png",
|
|
900
|
+
PFX_OTP_COLOR="FF0000")
|
|
898
901
|
def test_otp_enable(self):
|
|
899
902
|
self.client.login(username='jrr.tolkien', password='RIGHT PASSWORD')
|
|
900
903
|
|
|
@@ -904,6 +907,7 @@ class AuthAPITest(TestAssertMixin, TransactionTestCase):
|
|
|
904
907
|
self.assertJIn(response, 'setup_uri', "otpauth://totp/")
|
|
905
908
|
self.assertJIn(response, 'setup_uri', "Books%20Demo:jrr.tolkien")
|
|
906
909
|
self.assertJIn(response, 'setup_uri', "issuer=Books%20Demo")
|
|
910
|
+
self.assertJIn(response, 'setup_uri', "color=FF0000")
|
|
907
911
|
self.assertJIn(
|
|
908
912
|
response, 'setup_uri',
|
|
909
913
|
"image=https%3A%2F%2Fexample.org%2Ffake.png")
|
|
@@ -1088,6 +1092,51 @@ class AuthAPITest(TestAssertMixin, TransactionTestCase):
|
|
|
1088
1092
|
datetime.fromtimestamp(decoded['exp']),
|
|
1089
1093
|
datetime(2023, 5, 2, 8, 10))
|
|
1090
1094
|
|
|
1095
|
+
@override_settings(
|
|
1096
|
+
PFX_OTP_VALID_WINDOW=0,
|
|
1097
|
+
PFX_TOKEN_SHORT_VALIDITY={'minutes': 30})
|
|
1098
|
+
def test_otp_login_valid_window_0(self):
|
|
1099
|
+
totp = self.enable_otp(self.user1)
|
|
1100
|
+
|
|
1101
|
+
with freeze_time("2023-05-01 08:00:00"):
|
|
1102
|
+
response = self.client.post('/api/auth/login', dict(
|
|
1103
|
+
username='jrr.tolkien',
|
|
1104
|
+
password='RIGHT PASSWORD'))
|
|
1105
|
+
self.assertRC(response, 200)
|
|
1106
|
+
token = self.get_val(response, 'token')
|
|
1107
|
+
|
|
1108
|
+
response = self.client.post('/api/auth/otp/login', dict(
|
|
1109
|
+
token=token, otp_code=totp.at(
|
|
1110
|
+
timezone.now() - timedelta(seconds=1))))
|
|
1111
|
+
self.assertRC(response, 422)
|
|
1112
|
+
|
|
1113
|
+
response = self.client.post('/api/auth/otp/login', dict(
|
|
1114
|
+
token=token, otp_code=totp.at(timezone.now())))
|
|
1115
|
+
self.assertRC(response, 200)
|
|
1116
|
+
|
|
1117
|
+
@override_settings(
|
|
1118
|
+
PFX_OTP_VALID_WINDOW=1,
|
|
1119
|
+
PFX_TOKEN_SHORT_VALIDITY={'minutes': 30})
|
|
1120
|
+
def test_otp_login_valid_window_1(self):
|
|
1121
|
+
totp = self.enable_otp(self.user1)
|
|
1122
|
+
|
|
1123
|
+
with freeze_time("2023-05-01 08:00:00"):
|
|
1124
|
+
response = self.client.post('/api/auth/login', dict(
|
|
1125
|
+
username='jrr.tolkien',
|
|
1126
|
+
password='RIGHT PASSWORD'))
|
|
1127
|
+
self.assertRC(response, 200)
|
|
1128
|
+
token = self.get_val(response, 'token')
|
|
1129
|
+
|
|
1130
|
+
response = self.client.post('/api/auth/otp/login', dict(
|
|
1131
|
+
token=token, otp_code=totp.at(
|
|
1132
|
+
timezone.now() - timedelta(seconds=31))))
|
|
1133
|
+
self.assertRC(response, 422)
|
|
1134
|
+
|
|
1135
|
+
response = self.client.post('/api/auth/otp/login', dict(
|
|
1136
|
+
token=token, otp_code=totp.at(
|
|
1137
|
+
timezone.now() - timedelta(seconds=30))))
|
|
1138
|
+
self.assertRC(response, 200)
|
|
1139
|
+
|
|
1091
1140
|
@override_settings(
|
|
1092
1141
|
PFX_TOKEN_SHORT_VALIDITY={'minutes': 30},
|
|
1093
1142
|
PFX_LOGIN_BAN_FAILED_NUMBER=2,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/pfx/pfxcore/models/user_filtered_queryset_mixin.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/pfx/pfxcore/templates/registration/otp_code_email.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/pfx/pfxcore/templates/registration/welcome_email.txt
RENAMED
|
File without changes
|
{django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/pfx/pfxcore/templates/registration/welcome_subject.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/pfx/pfxcore/views/parameters/media_redirect.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/pfx/pfxcore/views/parameters/subset_page_size.py
RENAMED
|
File without changes
|
{django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/pfx/pfxcore/views/parameters/subset_page_subset.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|