enforce mfa on all frontend pages

This commit is contained in:
Matthias 2021-10-28 11:41:45 +02:00
parent 527bc4381d
commit eaf1a4baec
No known key found for this signature in database
GPG Key ID: F50EF5741D33E076
4 changed files with 56 additions and 21 deletions

View File

@ -1,12 +1,17 @@
from django.shortcuts import HttpResponseRedirect from django.shortcuts import HttpResponseRedirect
from django.urls import reverse_lazy from django.urls import reverse_lazy, Resolver404
from django.db import connection from django.db import connection
from django.shortcuts import redirect from django.shortcuts import redirect
from django.conf.urls import include, url
import logging import logging
import time import time
import operator import operator
from rest_framework.authtoken.models import Token from rest_framework.authtoken.models import Token
from allauth_2fa.middleware import BaseRequire2FAMiddleware
from InvenTree.urls import frontendpatterns
logger = logging.getLogger("inventree") logger = logging.getLogger("inventree")
@ -146,3 +151,16 @@ class QueryCountMiddleware(object):
print(x[0], ':', x[1]) print(x[0], ':', x[1])
return response return response
url_matcher = url('', include(frontendpatterns))
class Check2FAMiddleware(BaseRequire2FAMiddleware):
def require_2fa(self, request):
# Superusers are require to have 2FA.
try:
if url_matcher.resolve(request.path[1:]):
return True
except Resolver404:
pass
return False

View File

@ -304,7 +304,8 @@ MIDDLEWARE = CONFIG.get('middleware', [
'allauth_2fa.middleware.AllauthTwoFactorMiddleware', # Flow control for allauth 'allauth_2fa.middleware.AllauthTwoFactorMiddleware', # Flow control for allauth
'django.contrib.messages.middleware.MessageMiddleware', 'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware',
'InvenTree.middleware.AuthRequiredMiddleware' 'InvenTree.middleware.AuthRequiredMiddleware',
'InvenTree.middleware.Check2FAMiddleware', # Check if the user should be forced to use MFA
]) ])
# Error reporting middleware # Error reporting middleware

View File

@ -37,7 +37,7 @@ from rest_framework.documentation import include_docs_urls
from .views import auth_request from .views import auth_request
from .views import IndexView, SearchView, DatabaseStatsView from .views import IndexView, SearchView, DatabaseStatsView
from .views import SettingsView, EditUserView, SetPasswordView, CustomEmailView, CustomConnectionsView, CustomPasswordResetFromKeyView from .views import SettingsView, EditUserView, SetPasswordView, CustomEmailView, CustomConnectionsView, CustomPasswordResetFromKeyView, CustomTwoFactorAuthenticate
from .views import CurrencyRefreshView from .views import CurrencyRefreshView
from .views import AppearanceSelectView, SettingCategorySelectView from .views import AppearanceSelectView, SettingCategorySelectView
from .views import DynamicJsView from .views import DynamicJsView
@ -122,15 +122,29 @@ translated_javascript_urls = [
url(r'^table_filters.js', DynamicJsView.as_view(template_name='js/translated/table_filters.js'), name='table_filters.js'), url(r'^table_filters.js', DynamicJsView.as_view(template_name='js/translated/table_filters.js'), name='table_filters.js'),
] ]
urlpatterns = [ backendpatterns = [
url(r'^part/', include(part_urls)),
url(r'^manufacturer-part/', include(manufacturer_part_urls)),
url(r'^supplier-part/', include(supplier_part_urls)),
# "Dynamic" javascript files which are rendered using InvenTree templating. # "Dynamic" javascript files which are rendered using InvenTree templating.
url(r'^js/dynamic/', include(dynamic_javascript_urls)), url(r'^js/dynamic/', include(dynamic_javascript_urls)),
url(r'^js/i18n/', include(translated_javascript_urls)), url(r'^js/i18n/', include(translated_javascript_urls)),
url(r'^auth/', include('rest_framework.urls', namespace='rest_framework')),
url(r'^auth/?', auth_request),
url(r'^admin/error_log/', include('error_report.urls')),
url(r'^admin/shell/', include('django_admin_shell.urls')),
url(r'^admin/', admin.site.urls, name='inventree-admin'),
url(r'^api/', include(apipatterns)),
url(r'^api-doc/', include_docs_urls(title='InvenTree API')),
url(r'^markdownx/', include('markdownx.urls')),
]
frontendpatterns = [
url(r'^part/', include(part_urls)),
url(r'^manufacturer-part/', include(manufacturer_part_urls)),
url(r'^supplier-part/', include(supplier_part_urls)),
url(r'^common/', include(common_urls)), url(r'^common/', include(common_urls)),
url(r'^stock/', include(stock_urls)), url(r'^stock/', include(stock_urls)),
@ -140,37 +154,30 @@ urlpatterns = [
url(r'^build/', include(build_urls)), url(r'^build/', include(build_urls)),
url(r'^auth/', include('rest_framework.urls', namespace='rest_framework')),
url(r'^settings/', include(settings_urls)), url(r'^settings/', include(settings_urls)),
url(r'^edit-user/', EditUserView.as_view(), name='edit-user'), url(r'^edit-user/', EditUserView.as_view(), name='edit-user'),
url(r'^set-password/', SetPasswordView.as_view(), name='set-password'), url(r'^set-password/', SetPasswordView.as_view(), name='set-password'),
url(r'^admin/error_log/', include('error_report.urls')),
url(r'^admin/shell/', include('django_admin_shell.urls')),
url(r'^admin/', admin.site.urls, name='inventree-admin'),
url(r'^index/', IndexView.as_view(), name='index'), url(r'^index/', IndexView.as_view(), name='index'),
url(r'^search/', SearchView.as_view(), name='search'), url(r'^search/', SearchView.as_view(), name='search'),
url(r'^stats/', DatabaseStatsView.as_view(), name='stats'), url(r'^stats/', DatabaseStatsView.as_view(), name='stats'),
url(r'^auth/?', auth_request),
url(r'^api/', include(apipatterns)),
url(r'^api-doc/', include_docs_urls(title='InvenTree API')),
url(r'^markdownx/', include('markdownx.urls')),
# Single Sign On / allauth # Single Sign On / allauth
# overrides of urlpatterns # overrides of urlpatterns
url(r'^accounts/email/', CustomEmailView.as_view(), name='account_email'), url(r'^accounts/email/', CustomEmailView.as_view(), name='account_email'),
url(r'^accounts/social/connections/', CustomConnectionsView.as_view(), name='socialaccount_connections'), url(r'^accounts/social/connections/', CustomConnectionsView.as_view(), name='socialaccount_connections'),
url(r"^accounts/password/reset/key/(?P<uidb36>[0-9A-Za-z]+)-(?P<key>.+)/$", CustomPasswordResetFromKeyView.as_view(), name="account_reset_password_from_key"), url(r"^accounts/password/reset/key/(?P<uidb36>[0-9A-Za-z]+)-(?P<key>.+)/$", CustomPasswordResetFromKeyView.as_view(), name="account_reset_password_from_key"),
url(r"^accounts/two-factor-authenticate/?$", CustomTwoFactorAuthenticate.as_view(), name="two-factor-authenticate"),
url(r'^accounts/', include('allauth_2fa.urls')), # MFA support url(r'^accounts/', include('allauth_2fa.urls')), # MFA support
url(r'^accounts/', include('allauth.urls')), # included urlpatterns url(r'^accounts/', include('allauth.urls')), # included urlpatterns
] ]
urlpatterns = [
url('', include(frontendpatterns)),
url('', include(backendpatterns)),
]
# Server running in "DEBUG" mode? # Server running in "DEBUG" mode?
if settings.DEBUG: if settings.DEBUG:
# Static file access # Static file access

View File

@ -29,6 +29,7 @@ from allauth.socialaccount.forms import DisconnectForm
from allauth.account.models import EmailAddress from allauth.account.models import EmailAddress
from allauth.account.views import EmailView, PasswordResetFromKeyView from allauth.account.views import EmailView, PasswordResetFromKeyView
from allauth.socialaccount.views import ConnectionsView from allauth.socialaccount.views import ConnectionsView
from allauth_2fa.views import TwoFactorAuthenticate
from common.settings import currency_code_default, currency_codes from common.settings import currency_code_default, currency_codes
@ -857,6 +858,14 @@ class CustomPasswordResetFromKeyView(PasswordResetFromKeyView):
success_url = reverse_lazy("account_login") success_url = reverse_lazy("account_login")
class CustomTwoFactorAuthenticate(TwoFactorAuthenticate):
def dispatch(self, request, *args, **kwargs):
if 'allauth_2fa_user_id' not in request.session and 'otp_token' not in request.POST:
return redirect('account_login')
if hasattr(request.user, 'id'):
request.session['allauth_2fa_user_id'] = request.user.id
return super(FormView, self).dispatch(request, *args, **kwargs)
class CurrencyRefreshView(RedirectView): class CurrencyRefreshView(RedirectView):
""" """
POST endpoint to refresh / update exchange rates POST endpoint to refresh / update exchange rates