2
0
mirror of https://github.com/inventree/InvenTree.git synced 2025-11-09 09:35:42 +00:00

MFA middleware tweaks (#10768)

* MFA middleware tweaks

- Remove Check2FAMiddleware if MFA not enabled
- Refactor into middleware.py

* Update settings.py

- Disable MFA_SUPPORTED_TYPES if MFA_ENABLED is False

* Update docs
This commit is contained in:
Oliver
2025-11-05 07:00:42 +11:00
committed by GitHub
parent 2bc2966d22
commit 7e943293c7
4 changed files with 92 additions and 92 deletions

View File

@@ -425,7 +425,7 @@ InvenTree provides allowance for additional sign-in options. The following optio
| Environment Variable | Configuration File | Description | Default | | Environment Variable | Configuration File | Description | Default |
| --- | --- | --- | --- | | --- | --- | --- | --- |
| INVENTREE_MFA_ENABLED | mfa_enabled | Enable or disable multi-factor authentication support for the InvenTree server | True | | INVENTREE_MFA_ENABLED | mfa_enabled | Enable or disable multi-factor authentication support for the InvenTree server | True |
| INVENTREE_MFA_SUPPORTED_TYPES | mfa_supported_types | List of supported multi-factor authentication types | recovery_codes,totp | | INVENTREE_MFA_SUPPORTED_TYPES | mfa_supported_types | List of supported multi-factor authentication types | recovery_codes,totp,webauthn |
### Single Sign On ### Single Sign On

View File

@@ -1,79 +0,0 @@
"""Middleware to require 2FA for users."""
from django.http import HttpRequest, HttpResponse, JsonResponse
from django.utils.deprecation import MiddlewareMixin
from django.utils.translation import gettext_lazy as _
from allauth.account.authentication import get_authentication_records
from allauth.mfa.utils import is_mfa_enabled
from allauth.mfa.webauthn.internal.flows import did_use_passwordless_login
def is_multifactor_logged_in(request: HttpRequest) -> bool:
"""Check if the user is logged in with multifactor authentication."""
authns = get_authentication_records(request)
return is_mfa_enabled(request.user) and (
did_use_passwordless_login(request)
or any(record.get('method') == 'mfa' for record in authns)
)
class AllUserRequire2FAMiddleware(MiddlewareMixin):
"""Ensure that users have two-factor authentication enabled before they have access restricted endpoints.
Adapted from https://github.com/pennersr/django-allauth/issues/3649
"""
allowed_pages = [
'api-user-meta',
'api-user-me',
'api-user-roles',
'api-inventree-info',
'api-token',
# web platform urls
'password_reset_confirm',
'web',
'web-wildcard',
'web-assets',
]
app_names = ['headless']
require_2fa_message = _(
'You must enable two-factor authentication before doing anything else.'
)
def on_require_2fa(self, request: HttpRequest) -> HttpResponse:
"""Force user to mfa activation."""
return JsonResponse(
{'id': 'mfa_register', 'error': self.require_2fa_message}, status=401
)
def is_allowed_page(self, request: HttpRequest) -> bool:
"""Check if the current page can be accessed without mfa."""
match = request.resolver_match
return (
None
if match is None
else any(ref in self.app_names for ref in match.app_names)
or match.url_name in self.allowed_pages
or match.route == 'favicon.ico'
)
def enforce_2fa(self, request):
"""Check if 2fa should be enforced for this request."""
return True
def process_view(
self, request: HttpRequest, view_func, view_args, view_kwargs
) -> HttpResponse:
"""If set up enforce 2fa registration."""
if request.user.is_anonymous:
return None
if self.is_allowed_page(request):
return None
if is_multifactor_logged_in(request):
return None
if self.enforce_2fa(request):
return self.on_require_2fa(request)
return None

View File

@@ -5,17 +5,17 @@ from urllib.parse import urlsplit
from django.conf import settings from django.conf import settings
from django.contrib.auth.middleware import PersistentRemoteUserMiddleware from django.contrib.auth.middleware import PersistentRemoteUserMiddleware
from django.http import HttpResponse from django.http import HttpRequest, HttpResponse, JsonResponse
from django.shortcuts import redirect, render from django.shortcuts import redirect, render
from django.urls import resolve, reverse_lazy from django.urls import resolve, reverse_lazy
from django.utils.deprecation import MiddlewareMixin from django.utils.deprecation import MiddlewareMixin
from django.utils.http import is_same_domain from django.utils.http import is_same_domain
from django.utils.translation import gettext_lazy as _
import structlog import structlog
from error_report.middleware import ExceptionProcessor from error_report.middleware import ExceptionProcessor
from common.settings import get_global_setting from common.settings import get_global_setting
from InvenTree.AllUserRequire2FAMiddleware import AllUserRequire2FAMiddleware
from InvenTree.cache import create_session_cache, delete_session_cache from InvenTree.cache import create_session_cache, delete_session_cache
from InvenTree.config import CONFIG_LOOKUPS, inventreeInstaller from InvenTree.config import CONFIG_LOOKUPS, inventreeInstaller
from users.models import ApiToken from users.models import ApiToken
@@ -146,8 +146,80 @@ class AuthRequiredMiddleware:
return response return response
class Check2FAMiddleware(AllUserRequire2FAMiddleware): class Check2FAMiddleware(MiddlewareMixin):
"""Ensure that mfa is enforced if set so.""" """Ensure that users have two-factor authentication enabled before they have access restricted endpoints.
Adapted from https://github.com/pennersr/django-allauth/issues/3649
"""
allowed_pages = [
'api-user-meta',
'api-user-me',
'api-user-roles',
'api-inventree-info',
'api-token',
# web platform urls
'password_reset_confirm',
'index',
'web',
'web-wildcard',
'web-assets',
]
app_names = ['headless']
require_2fa_message = _(
'You must enable two-factor authentication before doing anything else.'
)
def on_require_2fa(self, request: HttpRequest) -> HttpResponse:
"""Force user to mfa activation."""
return JsonResponse(
{'id': 'mfa_register', 'error': self.require_2fa_message}, status=401
)
def is_allowed_page(self, request: HttpRequest) -> bool:
"""Check if the current page can be accessed without mfa."""
match = request.resolver_match
return (
None
if match is None
else any(ref in self.app_names for ref in match.app_names)
or match.url_name in self.allowed_pages
or match.route == 'favicon.ico'
)
def is_multifactor_logged_in(self, request: HttpRequest) -> bool:
"""Check if the user is logged in with multifactor authentication."""
from allauth.account.authentication import get_authentication_records
from allauth.mfa.utils import is_mfa_enabled
from allauth.mfa.webauthn.internal.flows import did_use_passwordless_login
authns = get_authentication_records(request)
return is_mfa_enabled(request.user) and (
did_use_passwordless_login(request)
or any(record.get('method') == 'mfa' for record in authns)
)
def process_view(
self, request: HttpRequest, view_func, view_args, view_kwargs
) -> HttpResponse:
"""Determine if the server is set up enforce 2fa registration."""
from django.conf import settings
# Exit early if MFA is not enabled
if not settings.MFA_ENABLED:
return None
if request.user.is_anonymous:
return None
if self.is_allowed_page(request):
return None
if self.is_multifactor_logged_in(request):
return None
if self.enforce_2fa(request):
return self.on_require_2fa(request)
return None
def enforce_2fa(self, request): def enforce_2fa(self, request):
"""Use setting to check if MFA should be enforced.""" """Use setting to check if MFA should be enforced."""

View File

@@ -1378,15 +1378,22 @@ ACCOUNT_LOGOUT_ON_PASSWORD_CHANGE = True
HEADLESS_ONLY = True HEADLESS_ONLY = True
HEADLESS_CLIENTS = 'browser' HEADLESS_CLIENTS = 'browser'
MFA_ENABLED = get_boolean_setting( MFA_ENABLED = get_boolean_setting('INVENTREE_MFA_ENABLED', 'mfa_enabled', True)
'INVENTREE_MFA_ENABLED', 'mfa_enabled', True
) # TODO re-implement if not MFA_ENABLED:
MFA_SUPPORTED_TYPES = get_setting( MIDDLEWARE.remove('InvenTree.middleware.Check2FAMiddleware')
MFA_SUPPORTED_TYPES = (
get_setting(
'INVENTREE_MFA_SUPPORTED_TYPES', 'INVENTREE_MFA_SUPPORTED_TYPES',
'mfa_supported_types', 'mfa_supported_types',
['totp', 'recovery_codes', 'webauthn'], ['totp', 'recovery_codes', 'webauthn'],
typecast=list, typecast=list,
)
if MFA_ENABLED
else []
) )
MFA_TRUST_ENABLED = True MFA_TRUST_ENABLED = True
MFA_PASSKEY_LOGIN_ENABLED = True MFA_PASSKEY_LOGIN_ENABLED = True
MFA_WEBAUTHN_ALLOW_INSECURE_ORIGIN = DEBUG MFA_WEBAUTHN_ALLOW_INSECURE_ORIGIN = DEBUG