From a4217ccb4c83279f9e00f0b7e8826578e46e0a66 Mon Sep 17 00:00:00 2001 From: Viicos <65306057+Viicos@users.noreply.github.com> Date: Tue, 13 Feb 2024 17:53:20 +0100 Subject: [PATCH] Upgrade to `maykin-2fa` Upgrade is pretty straightforward. Followed documentation --- docs/installation/config.rst | 6 --- requirements/base.in | 3 +- requirements/base.txt | 27 +++++++++---- requirements/ci.txt | 40 ++++++++++++++----- requirements/dev.txt | 40 ++++++++++++++----- src/objects/conf/base.py | 19 ++++++--- src/objects/conf/ci.py | 6 --- src/objects/conf/dev.py | 6 ++- src/objects/templates/admin/base_site.html | 6 +-- src/objects/templates/admin/login.html | 16 -------- src/objects/templates/maykin_2fa/base.html | 9 +++++ src/objects/templates/maykin_2fa/login.html | 23 +++++++++++ .../templates/two_factor/admin/login.html | 1 - .../tests/admin/test_token_permissions.py | 2 + src/objects/urls.py | 5 +++ 15 files changed, 138 insertions(+), 71 deletions(-) delete mode 100644 src/objects/templates/admin/login.html create mode 100644 src/objects/templates/maykin_2fa/base.html create mode 100644 src/objects/templates/maykin_2fa/login.html delete mode 100644 src/objects/templates/two_factor/admin/login.html diff --git a/docs/installation/config.rst b/docs/installation/config.rst index ae1d0d80..36663e1d 100644 --- a/docs/installation/config.rst +++ b/docs/installation/config.rst @@ -84,12 +84,6 @@ Other settings sent to the Notificaties API for operations on the Object endpoint. Defaults to ``True`` for the ``dev`` environment, otherwise defaults to ``False``. -* ``TWO_FACTOR_FORCE_OTP_ADMIN``: Enforce 2 Factor Authentication in the admin or not. - Default ``True``. You'll probably want to disable this when using OIDC. - -* ``TWO_FACTOR_PATCH_ADMIN``: Whether to use the 2 Factor Authentication login flow for - the admin or not. Default ``True``. You'll probably want to disable this when using OIDC. - Initial superuser creation -------------------------- diff --git a/requirements/base.in b/requirements/base.in index 14052f5d..f9f5d4d3 100644 --- a/requirements/base.in +++ b/requirements/base.in @@ -14,8 +14,7 @@ django-admin-index django-axes django-redis django-rosetta -maykin-django-two-factor-auth -maykin-django-two-factor-auth[phonenumbers] +maykin-2fa mozilla-django-oidc-db # API libraries diff --git a/requirements/base.txt b/requirements/base.txt index f7245d2e..58fa4f7f 100644 --- a/requirements/base.txt +++ b/requirements/base.txt @@ -8,6 +8,8 @@ amqp==5.2.0 # via kombu asgiref==3.7.2 # via django +asn1crypto==1.5.1 + # via webauthn attrs==20.3.0 # via # glom @@ -18,6 +20,8 @@ boltons==21.0.0 # via # face # glom +cbor2==5.6.1 + # via webauthn celery==5.2.2 # via # -r requirements/base.in @@ -60,6 +64,7 @@ cryptography==41.0.7 # josepy # mozilla-django-oidc # pyopenssl + # webauthn django==3.2.23 # via # -r requirements/base.in @@ -80,11 +85,12 @@ django==3.2.23 # django-sendfile2 # django-simple-certmanager # django-solo + # django-two-factor-auth # djangorestframework # drf-nested-routers # drf-spectacular # drf-yasg - # maykin-django-two-factor-auth + # maykin-2fa # mozilla-django-oidc # mozilla-django-oidc-db # notifications-api-common @@ -98,7 +104,7 @@ django-filter==23.5 # -r requirements/base.in # commonground-api-common django-formtools==2.3 - # via maykin-django-two-factor-auth + # via django-two-factor-auth django-jsonform==2.21.4 # via mozilla-django-oidc-db django-markup==1.3 @@ -106,9 +112,9 @@ django-markup==1.3 django-ordered-model==3.7.4 # via django-admin-index django-otp==1.0.6 - # via maykin-django-two-factor-auth + # via django-two-factor-auth django-phonenumber-field==5.2.0 - # via maykin-django-two-factor-auth + # via django-two-factor-auth django-privates==2.0.0.post0 # via django-simple-certmanager django-redis==5.4.0 @@ -129,6 +135,8 @@ django-solo==2.2.0 # mozilla-django-oidc-db # notifications-api-common # zgw-consumers +django-two-factor-auth[phonenumberslite,webauthn]==1.16.0 + # via maykin-2fa djangorestframework==3.12.4 # via # -r requirements/base.in @@ -191,7 +199,7 @@ markdown==3.3.4 # via commonground-api-common markupsafe==2.1.3 # via jinja2 -maykin-django-two-factor-auth[phonenumbers]==2.0.3 +maykin-2fa==1.0.0 # via -r requirements/base.in mozilla-django-oidc==4.0.0 # via mozilla-django-oidc-db @@ -205,8 +213,8 @@ oyaml==1.0 # via commonground-api-common packaging==23.2 # via drf-yasg -phonenumbers==8.12.29 - # via maykin-django-two-factor-auth +phonenumberslite==8.13.30 + # via django-two-factor-auth pillow==10.2.0 # via -r requirements/base.in polib==1.1.1 @@ -225,6 +233,7 @@ pyopenssl==23.3.0 # via # django-simple-certmanager # josepy + # webauthn # zgw-consumers pyrsistent==0.17.3 # via jsonschema @@ -248,7 +257,7 @@ pyyaml==6.0.1 # gemma-zds-client # oyaml qrcode==6.1 - # via maykin-django-two-factor-auth + # via django-two-factor-auth redis==3.5.3 # via django-redis requests==2.25.1 @@ -298,6 +307,8 @@ vine==5.1.0 # kombu wcwidth==0.2.13 # via prompt-toolkit +webauthn==2.0.0 + # via django-two-factor-auth zgw-consumers==0.27.0 # via # -r requirements/base.in diff --git a/requirements/ci.txt b/requirements/ci.txt index f57fd03f..36b497cc 100644 --- a/requirements/ci.txt +++ b/requirements/ci.txt @@ -12,6 +12,10 @@ asgiref==3.7.2 # via # -r requirements/base.txt # django +asn1crypto==1.5.1 + # via + # -r requirements/base.txt + # webauthn attrs==20.3.0 # via # -r requirements/base.txt @@ -28,6 +32,10 @@ boltons==21.0.0 # -r requirements/base.txt # face # glom +cbor2==5.6.1 + # via + # -r requirements/base.txt + # webauthn celery==5.2.2 # via # -r requirements/base.txt @@ -89,6 +97,7 @@ cryptography==41.0.7 # josepy # mozilla-django-oidc # pyopenssl + # webauthn cssselect==1.1.0 # via pyquery django==3.2.23 @@ -111,11 +120,12 @@ django==3.2.23 # django-sendfile2 # django-simple-certmanager # django-solo + # django-two-factor-auth # djangorestframework # drf-nested-routers # drf-spectacular # drf-yasg - # maykin-django-two-factor-auth + # maykin-2fa # mozilla-django-oidc # mozilla-django-oidc-db # notifications-api-common @@ -131,7 +141,7 @@ django-filter==23.5 django-formtools==2.3 # via # -r requirements/base.txt - # maykin-django-two-factor-auth + # django-two-factor-auth django-jsonform==2.21.4 # via # -r requirements/base.txt @@ -147,11 +157,11 @@ django-ordered-model==3.7.4 django-otp==1.0.6 # via # -r requirements/base.txt - # maykin-django-two-factor-auth + # django-two-factor-auth django-phonenumber-field==5.2.0 # via # -r requirements/base.txt - # maykin-django-two-factor-auth + # django-two-factor-auth django-privates==2.0.0.post0 # via # -r requirements/base.txt @@ -183,6 +193,11 @@ django-solo==2.2.0 # mozilla-django-oidc-db # notifications-api-common # zgw-consumers +django-two-factor-auth[phonenumberslite,webauthn]==1.16.0 + # via + # -r requirements/base.txt + # django-two-factor-auth + # maykin-2fa django-webtest==1.9.7 # via -r requirements/test-tools.in djangorestframework==3.12.4 @@ -283,10 +298,8 @@ markupsafe==2.1.3 # via # -r requirements/base.txt # jinja2 -maykin-django-two-factor-auth[phonenumbers]==2.0.3 - # via - # -r requirements/base.txt - # maykin-django-two-factor-auth +maykin-2fa==1.0.0 + # via -r requirements/base.txt mozilla-django-oidc==4.0.0 # via # -r requirements/base.txt @@ -305,10 +318,10 @@ packaging==23.2 # via # -r requirements/base.txt # drf-yasg -phonenumbers==8.12.29 +phonenumberslite==8.13.30 # via # -r requirements/base.txt - # maykin-django-two-factor-auth + # django-two-factor-auth pillow==10.2.0 # via -r requirements/base.txt polib==1.1.1 @@ -335,6 +348,7 @@ pyopenssl==23.3.0 # -r requirements/base.txt # django-simple-certmanager # josepy + # webauthn # zgw-consumers pyquery==1.4.3 # via -r requirements/test-tools.in @@ -367,7 +381,7 @@ pyyaml==6.0.1 qrcode==6.1 # via # -r requirements/base.txt - # maykin-django-two-factor-auth + # django-two-factor-auth redis==3.5.3 # via # -r requirements/base.txt @@ -444,6 +458,10 @@ wcwidth==0.2.13 # via # -r requirements/base.txt # prompt-toolkit +webauthn==2.0.0 + # via + # -r requirements/base.txt + # django-two-factor-auth webob==1.8.7 # via webtest webtest==2.0.35 diff --git a/requirements/dev.txt b/requirements/dev.txt index e919ecca..f4315223 100644 --- a/requirements/dev.txt +++ b/requirements/dev.txt @@ -14,6 +14,10 @@ asgiref==3.7.2 # via # -r requirements/ci.txt # django +asn1crypto==1.5.1 + # via + # -r requirements/ci.txt + # webauthn attrs==20.3.0 # via # -r requirements/ci.txt @@ -42,6 +46,10 @@ bump2version==1.0.1 # via bumpversion bumpversion==0.6.0 # via -r requirements/dev.in +cbor2==5.6.1 + # via + # -r requirements/ci.txt + # webauthn celery==5.2.2 # via # -r requirements/ci.txt @@ -107,6 +115,7 @@ cryptography==41.0.7 # josepy # mozilla-django-oidc # pyopenssl + # webauthn cssselect==1.1.0 # via # -r requirements/ci.txt @@ -133,11 +142,12 @@ django==3.2.23 # django-sendfile2 # django-simple-certmanager # django-solo + # django-two-factor-auth # djangorestframework # drf-nested-routers # drf-spectacular # drf-yasg - # maykin-django-two-factor-auth + # maykin-2fa # mozilla-django-oidc # mozilla-django-oidc-db # notifications-api-common @@ -157,7 +167,7 @@ django-filter==23.5 django-formtools==2.3 # via # -r requirements/ci.txt - # maykin-django-two-factor-auth + # django-two-factor-auth django-jsonform==2.21.4 # via # -r requirements/ci.txt @@ -171,11 +181,11 @@ django-ordered-model==3.7.4 django-otp==1.0.6 # via # -r requirements/ci.txt - # maykin-django-two-factor-auth + # django-two-factor-auth django-phonenumber-field==5.2.0 # via # -r requirements/ci.txt - # maykin-django-two-factor-auth + # django-two-factor-auth django-privates==2.0.0.post0 # via # -r requirements/ci.txt @@ -207,6 +217,11 @@ django-solo==2.2.0 # mozilla-django-oidc-db # notifications-api-common # zgw-consumers +django-two-factor-auth[phonenumberslite,webauthn]==1.16.0 + # via + # -r requirements/ci.txt + # django-two-factor-auth + # maykin-2fa django-webtest==1.9.7 # via -r requirements/ci.txt djangorestframework==3.12.4 @@ -320,10 +335,8 @@ markupsafe==2.1.3 # via # -r requirements/ci.txt # jinja2 -maykin-django-two-factor-auth[phonenumbers]==2.0.3 - # via - # -r requirements/ci.txt - # maykin-django-two-factor-auth +maykin-2fa==1.0.0 + # via -r requirements/ci.txt mccabe==0.7.0 # via flake8 mozilla-django-oidc==4.0.0 @@ -351,10 +364,10 @@ packaging==23.2 # sphinx pathspec==0.11.2 # via black -phonenumbers==8.12.29 +phonenumberslite==8.13.30 # via # -r requirements/ci.txt - # maykin-django-two-factor-auth + # django-two-factor-auth pillow==10.2.0 # via -r requirements/ci.txt pip-tools==7.3.0 @@ -393,6 +406,7 @@ pyopenssl==23.3.0 # -r requirements/ci.txt # django-simple-certmanager # josepy + # webauthn # zgw-consumers pyproject-hooks==1.0.0 # via build @@ -428,7 +442,7 @@ pyyaml==6.0.1 qrcode==6.1 # via # -r requirements/ci.txt - # maykin-django-two-factor-auth + # django-two-factor-auth recommonmark==0.7.1 # via -r requirements/dev.in redis==3.5.3 @@ -546,6 +560,10 @@ wcwidth==0.2.13 # via # -r requirements/ci.txt # prompt-toolkit +webauthn==2.0.0 + # via + # -r requirements/ci.txt + # django-two-factor-auth webob==1.8.7 # via # -r requirements/ci.txt diff --git a/src/objects/conf/base.py b/src/objects/conf/base.py index 9080f78a..7a3e2017 100644 --- a/src/objects/conf/base.py +++ b/src/objects/conf/base.py @@ -80,11 +80,12 @@ "notifications_api_common", "simple_certmanager", "zgw_consumers", - # 2fa apps + # Two-factor authentication in the Django admin, enforced. "django_otp", "django_otp.plugins.otp_static", "django_otp.plugins.otp_totp", "two_factor", + "maykin_2fa", # Project applications. "objects.accounts", "objects.api", @@ -100,11 +101,11 @@ "django.middleware.common.CommonMiddleware", "django.middleware.csrf.CsrfViewMiddleware", "django.contrib.auth.middleware.AuthenticationMiddleware", + "maykin_2fa.middleware.OTPMiddleware", "mozilla_django_oidc_db.middleware.SessionRefresh", "django.contrib.messages.middleware.MessageMiddleware", "django.middleware.clickjacking.XFrameOptionsMiddleware", "axes.middleware.AxesMiddleware", - "django_otp.middleware.OTPMiddleware", ] ROOT_URLCONF = "objects.urls" @@ -409,10 +410,18 @@ NOTIFICATIONS_DISABLED = config("NOTIFICATIONS_DISABLED", False) # -# Maykin fork of DJANGO-TWO-FACTOR-AUTH +# MAYKIN-2FA +# Uses django-two-factor-auth under the hood, so relevant upstream package settings +# apply too. # -TWO_FACTOR_FORCE_OTP_ADMIN = config("TWO_FACTOR_FORCE_OTP_ADMIN", not DEBUG) -TWO_FACTOR_PATCH_ADMIN = config("TWO_FACTOR_PATCH_ADMIN", True) + +# we run the admin site monkeypatch instead. +TWO_FACTOR_PATCH_ADMIN = False +# add entries from AUTHENTICATION_BACKENDS that already enforce their own two-factor +# auth, avoiding having some set up MFA again in the project. +MAYKIN_2FA_ALLOW_MFA_BYPASS_BACKENDS = [ + "mozilla_django_oidc_db.backends.OIDCAuthenticationBackend", +] # # Mozilla Django OIDC DB settings diff --git a/src/objects/conf/ci.py b/src/objects/conf/ci.py index 23946766..def866cd 100644 --- a/src/objects/conf/ci.py +++ b/src/objects/conf/ci.py @@ -29,9 +29,3 @@ AXES_BEHIND_REVERSE_PROXY = False NOTIFICATIONS_DISABLED = True - - -# -# Maykin fork of django-two-factor-auth -# -TWO_FACTOR_FORCE_OTP_ADMIN = False diff --git a/src/objects/conf/dev.py b/src/objects/conf/dev.py index f06e07ec..07843250 100644 --- a/src/objects/conf/dev.py +++ b/src/objects/conf/dev.py @@ -102,8 +102,10 @@ if "test" in sys.argv: NOTIFICATIONS_DISABLED = True - TWO_FACTOR_PATCH_ADMIN = False - TWO_FACTOR_FORCE_OTP_ADMIN = False + +# None of the authentication backends require two-factor authentication. +if config("DISABLE_2FA", default=False): # pragma: no cover + MAYKIN_2FA_ALLOW_MFA_BYPASS_BACKENDS = AUTHENTICATION_BACKENDS # Override settings with local settings. try: diff --git a/src/objects/templates/admin/base_site.html b/src/objects/templates/admin/base_site.html index 6e1a9e1f..1352afce 100644 --- a/src/objects/templates/admin/base_site.html +++ b/src/objects/templates/admin/base_site.html @@ -23,9 +23,9 @@

{{ settings.PROJECT_NAME }} {% if site_url %} {{ settings.SITE_TITLE }} / {% endif %} - {% url 'admin:two_factor:profile' as 2fa_profile_url %} - {% if 2fa_profile_url %} - {% trans "View 2fa profile" %} / + {% url 'maykin_2fa:account_security' as 2fa_account_security_url %} + {% if 2fa_account_security_url %} + {% trans "Account security" %} / {% endif %} {% if user.has_usable_password %} {% trans 'Change password' %} / diff --git a/src/objects/templates/admin/login.html b/src/objects/templates/admin/login.html deleted file mode 100644 index d408af04..00000000 --- a/src/objects/templates/admin/login.html +++ /dev/null @@ -1,16 +0,0 @@ -{% extends "two_factor/admin/login.html" %} -{% load solo_tags i18n %} - - -{% block content %} -{{ block.super }} - -{% get_solo 'mozilla_django_oidc_db.OpenIDConnectConfig' as oidc_config %} -{% if oidc_config.enabled %} -
{% trans "or" %}
-
- {% trans "Login with organization account" %} -
-{% endif %} - -{% endblock %} diff --git a/src/objects/templates/maykin_2fa/base.html b/src/objects/templates/maykin_2fa/base.html new file mode 100644 index 00000000..68fa4301 --- /dev/null +++ b/src/objects/templates/maykin_2fa/base.html @@ -0,0 +1,9 @@ +{% extends "maykin_2fa/base.html" %} + +{# Django 3.2 #} +{% block breadcrumbs %}{% endblock %} + +{# Do not show any version information #} +{% block footer %} + +{% endblock %} diff --git a/src/objects/templates/maykin_2fa/login.html b/src/objects/templates/maykin_2fa/login.html new file mode 100644 index 00000000..51987a80 --- /dev/null +++ b/src/objects/templates/maykin_2fa/login.html @@ -0,0 +1,23 @@ +{% extends "maykin_2fa/login.html" %} +{% load solo_tags i18n %} + +{% block extra_login_options %} + {% get_solo 'mozilla_django_oidc_db.OpenIDConnectConfig' as oidc_config %} + {% if oidc_config.enabled %} +
{% trans "or" %}
+
+ {% trans "Login with organization account" %} +
+ {% endif %} +{% endblock %} + +{% block extra_recovery_options %} +
  • + {% trans 'Contact support to start the account recovery process' %} +
  • +{% endblock extra_recovery_options %} + +{# Do not show any version information #} +{% block footer %} + +{% endblock %} diff --git a/src/objects/templates/two_factor/admin/login.html b/src/objects/templates/two_factor/admin/login.html deleted file mode 100644 index afdff9b3..00000000 --- a/src/objects/templates/two_factor/admin/login.html +++ /dev/null @@ -1 +0,0 @@ -{% extends "admin/login.html" %} diff --git a/src/objects/tests/admin/test_token_permissions.py b/src/objects/tests/admin/test_token_permissions.py index a9195275..40eb9d59 100644 --- a/src/objects/tests/admin/test_token_permissions.py +++ b/src/objects/tests/admin/test_token_permissions.py @@ -1,6 +1,7 @@ from django.urls import reverse_lazy from django_webtest import WebTest +from maykin_2fa.test import disable_admin_mfa from requests_mock import Mocker from objects.accounts.tests.factories import UserFactory @@ -11,6 +12,7 @@ OBJECT_TYPES_API = "https://example.com/objecttypes/v1/" +@disable_admin_mfa() class AddPermissionTests(WebTest): url = reverse_lazy("admin:token_permission_add") diff --git a/src/objects/urls.py b/src/objects/urls.py index 42833103..93bd2f34 100644 --- a/src/objects/urls.py +++ b/src/objects/urls.py @@ -7,6 +7,8 @@ from django.urls import include, path from django.views.generic.base import TemplateView +from maykin_2fa import monkeypatch_admin +from maykin_2fa.urls import urlpatterns as maykin_2fa_urlpatterns from rest_framework.settings import api_settings handler500 = "objects.utils.views.server_error" @@ -14,6 +16,8 @@ admin.site.site_title = "objects admin" admin.site.index_title = "Welcome to the objects admin" +monkeypatch_admin() + urlpatterns = [ path( "admin/password_reset/", @@ -25,6 +29,7 @@ auth_views.PasswordResetDoneView.as_view(), name="password_reset_done", ), + path("admin/", include((maykin_2fa_urlpatterns, "maykin_2fa"))), path("admin/", admin.site.urls), path( "reset///",