django-mfa3

multi factor authentication for django
git clone https://git.ce9e.org/django-mfa3.git

commit
49003746783e32cd60e55c4593bef5d7e709c4bd
parent
a8d453ae80bfda29d8e151d440b9971391a956cc
Author
Tobias Bengfort <tobias.bengfort@posteo.de>
Date
2024-12-06 07:18
Revert "Allow http on localhost"

This reverts commit 203b60d3b09b412e888188a18c940eeab8f4cd85.

Fixes #24

This behavior is now available upstream, but with some differences:

- localhost is considered secure regardless of DEBUG
- 127.0.0.1 is not considered secure

Diffstat

M mfa/methods/fido2.py 24 ------------------------
M pyproject.toml 2 +-
M tests/tests.py 32 +++++++++++++-------------------

3 files changed, 14 insertions, 44 deletions


diff --git a/mfa/methods/fido2.py b/mfa/methods/fido2.py

@@ -1,7 +1,3 @@
    1    -1 from urllib.parse import urlparse
    2    -1 
    3    -1 from django.conf import settings as django_settings
    4    -1 from django.utils.http import is_same_domain
    5     1 from fido2 import cbor
    6     2 from fido2.server import Fido2Server
    7     3 from fido2.utils import websafe_decode
@@ -16,28 +12,8 @@ from .. import settings
   16    12 
   17    13 name = 'FIDO2'
   18    14 
   19    -1 def _get_verify_origin_fn(domain):
   20    -1     """Do not require https on localhost in DEBUG mode.
   21    -1 
   22    -1     See https://github.com/Yubico/python-fido2/issues/122
   23    -1     """
   24    -1 
   25    -1     def is_localhost(hostname):
   26    -1         allowed_hosts = ['.localhost', '127.0.0.1', '[::1]']
   27    -1         return any(is_same_domain(hostname, h) for h in allowed_hosts)
   28    -1 
   29    -1     def verify_localhost_origin(origin):
   30    -1         return urlparse(origin).hostname == domain
   31    -1 
   32    -1     if django_settings.DEBUG and is_localhost(domain):
   33    -1         return verify_localhost_origin
   34    -1     else:
   35    -1         return None
   36    -1 
   37    -1 
   38    15 fido2 = Fido2Server(
   39    16     PublicKeyCredentialRpEntity(id=settings.DOMAIN, name=settings.SITE_TITLE),
   40    -1     verify_origin=_get_verify_origin_fn(settings.DOMAIN),
   41    17 )
   42    18 
   43    19 

diff --git a/pyproject.toml b/pyproject.toml

@@ -24,7 +24,7 @@ classifiers = [
   24    24 ]
   25    25 dependencies = [
   26    26     "pyotp",
   27    -1     "fido2>=1.0.0",
   -1    27     "fido2>=1.2.0",
   28    28     # https://github.com/lincolnloop/python-qrcode/issues/317
   29    29     "qrcode>=7.1,<7.4",
   30    30     "django>=3.2",

diff --git a/tests/tests.py b/tests/tests.py

@@ -194,26 +194,20 @@ class FIDO2Test(MFATestCase):
  194   194         self.assertEqual(fido2.decode('a163666f6f820102'), {'foo': [1, 2]})
  195   195 
  196   196     def test_origin_https(self):
  197    -1         for debug, domain, value, expected in [
  198    -1             (False, 'example.com', 'https://example.com', True),
  199    -1             (False, 'example.com', 'http://example.com', False),
  200    -1             (False, 'example.com', 'http://localhost:8000', False),
  201    -1             (False, 'localhost', 'http://localhost:8000', False),
  202    -1             (True, 'localhost', 'https://example.com', False),
  203    -1             (True, 'localhost', 'http://localhost:8000', True),
  204    -1             (True, 'localhost', 'http://127.0.0.1', False),
  205    -1             (True, 'localhost', 'http://foo.localhost', False),
  206    -1             (True, '127.0.0.1', 'http://127.0.0.1', True),
  207    -1             (True, 'foo.localhost', 'http://foo.localhost', True),
  208    -1             (True, 'example.com', 'http://example.com', False),
   -1   197         for domain, value, expected in [
   -1   198             ('example.com', 'https://example.com', True),
   -1   199             ('example.com', 'http://example.com', False),
   -1   200             ('example.com', 'http://localhost:8000', False),
   -1   201             ('localhost', 'https://example.com', False),
   -1   202             ('localhost', 'http://localhost:8000', True),
   -1   203             ('localhost', 'http://127.0.0.1', False),
   -1   204             ('localhost', 'http://foo.localhost', True),
   -1   205             ('127.0.0.1', 'http://127.0.0.1', False),
   -1   206             ('foo.localhost', 'http://foo.localhost', True),
  209   207         ]:
  210    -1             with self.subTest(debug=debug, domain=domain, value=value):
  211    -1                 with self.settings(DEBUG=debug, MFA_DOMAIN=domain):
  212    -1                     verify = (
  213    -1                         fido2._get_verify_origin_fn(domain)
  214    -1                         or _verify_origin_for_rp(domain)
  215    -1                     )
  216    -1                     self.assertEqual(verify(value), expected)
   -1   208             with self.subTest(domain=domain, value=value):
   -1   209                 verify = _verify_origin_for_rp(domain)
   -1   210                 self.assertEqual(verify(value), expected)
  217   211 
  218   212 
  219   213 class RecoveryTest(MFATestCase):