2
0
mirror of https://github.com/inventree/InvenTree.git synced 2025-11-09 01:25:41 +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 |
| --- | --- | --- | --- |
| 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

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.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.urls import resolve, reverse_lazy
from django.utils.deprecation import MiddlewareMixin
from django.utils.http import is_same_domain
from django.utils.translation import gettext_lazy as _
import structlog
from error_report.middleware import ExceptionProcessor
from common.settings import get_global_setting
from InvenTree.AllUserRequire2FAMiddleware import AllUserRequire2FAMiddleware
from InvenTree.cache import create_session_cache, delete_session_cache
from InvenTree.config import CONFIG_LOOKUPS, inventreeInstaller
from users.models import ApiToken
@@ -146,8 +146,80 @@ class AuthRequiredMiddleware:
return response
class Check2FAMiddleware(AllUserRequire2FAMiddleware):
"""Ensure that mfa is enforced if set so."""
class Check2FAMiddleware(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',
'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):
"""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_CLIENTS = 'browser'
MFA_ENABLED = get_boolean_setting(
'INVENTREE_MFA_ENABLED', 'mfa_enabled', True
) # TODO re-implement
MFA_SUPPORTED_TYPES = get_setting(
MFA_ENABLED = get_boolean_setting('INVENTREE_MFA_ENABLED', 'mfa_enabled', True)
if not MFA_ENABLED:
MIDDLEWARE.remove('InvenTree.middleware.Check2FAMiddleware')
MFA_SUPPORTED_TYPES = (
get_setting(
'INVENTREE_MFA_SUPPORTED_TYPES',
'mfa_supported_types',
['totp', 'recovery_codes', 'webauthn'],
typecast=list,
)
if MFA_ENABLED
else []
)
MFA_TRUST_ENABLED = True
MFA_PASSKEY_LOGIN_ENABLED = True
MFA_WEBAUTHN_ALLOW_INSECURE_ORIGIN = DEBUG