From aa7b78f41d80c813c631fc56bc868972ca865d0b Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 27 Oct 2021 21:35:00 +0200 Subject: [PATCH 01/58] Adding in MFA Fixes #2201 --- InvenTree/InvenTree/settings.py | 11 ++++++++++- InvenTree/InvenTree/urls.py | 3 ++- requirements.txt | 1 + 3 files changed, 13 insertions(+), 2 deletions(-) diff --git a/InvenTree/InvenTree/settings.py b/InvenTree/InvenTree/settings.py index 2095cab533..f111629bc9 100644 --- a/InvenTree/InvenTree/settings.py +++ b/InvenTree/InvenTree/settings.py @@ -284,6 +284,12 @@ INSTALLED_APPS = [ 'allauth', # Base app for SSO 'allauth.account', # Extend user with accounts 'allauth.socialaccount', # Use 'social' providers + + 'django_otp', # OTP is needed for MFA - base package + 'django_otp.plugins.otp_totp', # Time based OTP + 'django_otp.plugins.otp_static', # Backup codes + + 'allauth_2fa', # MFA flow for allauth ] MIDDLEWARE = CONFIG.get('middleware', [ @@ -294,6 +300,8 @@ MIDDLEWARE = CONFIG.get('middleware', [ 'django.middleware.csrf.CsrfViewMiddleware', 'corsheaders.middleware.CorsMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware', + 'django_otp.middleware.OTPMiddleware', # MFA support + 'allauth_2fa.middleware.AllauthTwoFactorMiddleware', # Flow control for allauth 'django.contrib.messages.middleware.MessageMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware', 'InvenTree.middleware.AuthRequiredMiddleware' @@ -689,7 +697,8 @@ ACCOUNT_FORMS = { } SOCIALACCOUNT_ADAPTER = 'InvenTree.forms.CustomSocialAccountAdapter' -ACCOUNT_ADAPTER = 'InvenTree.forms.CustomAccountAdapter' +# ACCOUNT_ADAPTER = 'InvenTree.forms.CustomAccountAdapter' # TODO monkey-patch adapter +ACCOUNT_ADAPTER = 'allauth_2fa.adapter.OTPAdapter' # Markdownx configuration # Ref: https://neutronx.github.io/django-markdownx/customization/ diff --git a/InvenTree/InvenTree/urls.py b/InvenTree/InvenTree/urls.py index 77a0e06a0c..9bc4ac8360 100644 --- a/InvenTree/InvenTree/urls.py +++ b/InvenTree/InvenTree/urls.py @@ -167,7 +167,8 @@ urlpatterns = [ url(r'^accounts/email/', CustomEmailView.as_view(), name='account_email'), url(r'^accounts/social/connections/', CustomConnectionsView.as_view(), name='socialaccount_connections'), url(r"^accounts/password/reset/key/(?P[0-9A-Za-z]+)-(?P.+)/$", CustomPasswordResetFromKeyView.as_view(), name="account_reset_password_from_key"), - url(r'^accounts/', include('allauth.urls')), # included urlpatterns + url(r'^accounts/', include('allauth_2fa.urls')), # MFA support + url(r'^accounts/', include('allauth.urls')), # included urlpatterns ] # Server running in "DEBUG" mode? diff --git a/requirements.txt b/requirements.txt index b9f1dfd692..618bb929a1 100644 --- a/requirements.txt +++ b/requirements.txt @@ -7,6 +7,7 @@ coveralls==2.1.2 # Coveralls linking (for Travis) cryptography==3.4.8 # Cryptography support django-admin-shell==0.1.2 # Python shell for the admin interface django-allauth==0.45.0 # SSO for external providers via OpenID +django-allauth-2fa==0.8 # MFA / 2FA django-cleanup==5.1.0 # Manage deletion of old / unused uploaded files django-cors-headers==3.2.0 # CORS headers extension for DRF django-crispy-forms==1.11.2 # Form helpers From 527bc4381d588e11606442fdc1a1fccf7a951ff4 Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 28 Oct 2021 11:39:28 +0200 Subject: [PATCH 02/58] show messages in base --- InvenTree/templates/base.html | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/InvenTree/templates/base.html b/InvenTree/templates/base.html index 98d83aa2d9..3d56f41c5d 100644 --- a/InvenTree/templates/base.html +++ b/InvenTree/templates/base.html @@ -92,6 +92,15 @@
+ {% if messages %} + {% for message in messages %} +
+ {{ message|safe }} +
+ {% endfor %} + {% endif %} + + {% block content %} {% endblock %} From eaf1a4baec8e5f7fc6d23148e8a47ed7604829ac Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 28 Oct 2021 11:41:45 +0200 Subject: [PATCH 03/58] enforce mfa on all frontend pages --- InvenTree/InvenTree/middleware.py | 20 +++++++++++++- InvenTree/InvenTree/settings.py | 3 ++- InvenTree/InvenTree/urls.py | 45 ++++++++++++++++++------------- InvenTree/InvenTree/views.py | 9 +++++++ 4 files changed, 56 insertions(+), 21 deletions(-) diff --git a/InvenTree/InvenTree/middleware.py b/InvenTree/InvenTree/middleware.py index 2df90bc5b7..089c178d9f 100644 --- a/InvenTree/InvenTree/middleware.py +++ b/InvenTree/InvenTree/middleware.py @@ -1,12 +1,17 @@ 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.shortcuts import redirect +from django.conf.urls import include, url import logging import time import operator from rest_framework.authtoken.models import Token +from allauth_2fa.middleware import BaseRequire2FAMiddleware + +from InvenTree.urls import frontendpatterns + logger = logging.getLogger("inventree") @@ -146,3 +151,16 @@ class QueryCountMiddleware(object): print(x[0], ':', x[1]) 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 diff --git a/InvenTree/InvenTree/settings.py b/InvenTree/InvenTree/settings.py index f111629bc9..1985ecce65 100644 --- a/InvenTree/InvenTree/settings.py +++ b/InvenTree/InvenTree/settings.py @@ -304,7 +304,8 @@ MIDDLEWARE = CONFIG.get('middleware', [ 'allauth_2fa.middleware.AllauthTwoFactorMiddleware', # Flow control for allauth 'django.contrib.messages.middleware.MessageMiddleware', '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 diff --git a/InvenTree/InvenTree/urls.py b/InvenTree/InvenTree/urls.py index 9bc4ac8360..01baa20e03 100644 --- a/InvenTree/InvenTree/urls.py +++ b/InvenTree/InvenTree/urls.py @@ -37,7 +37,7 @@ from rest_framework.documentation import include_docs_urls from .views import auth_request 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 AppearanceSelectView, SettingCategorySelectView 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'), ] -urlpatterns = [ - url(r'^part/', include(part_urls)), - url(r'^manufacturer-part/', include(manufacturer_part_urls)), - url(r'^supplier-part/', include(supplier_part_urls)), - +backendpatterns = [ # "Dynamic" javascript files which are rendered using InvenTree templating. url(r'^js/dynamic/', include(dynamic_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'^stock/', include(stock_urls)), @@ -140,37 +154,30 @@ urlpatterns = [ url(r'^build/', include(build_urls)), - url(r'^auth/', include('rest_framework.urls', namespace='rest_framework')), - url(r'^settings/', include(settings_urls)), url(r'^edit-user/', EditUserView.as_view(), name='edit-user'), 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'^search/', SearchView.as_view(), name='search'), 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 # overrides of urlpatterns url(r'^accounts/email/', CustomEmailView.as_view(), name='account_email'), url(r'^accounts/social/connections/', CustomConnectionsView.as_view(), name='socialaccount_connections'), url(r"^accounts/password/reset/key/(?P[0-9A-Za-z]+)-(?P.+)/$", 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.urls')), # included urlpatterns ] +urlpatterns = [ + url('', include(frontendpatterns)), + url('', include(backendpatterns)), +] + # Server running in "DEBUG" mode? if settings.DEBUG: # Static file access diff --git a/InvenTree/InvenTree/views.py b/InvenTree/InvenTree/views.py index af97877933..d1d3ca7436 100644 --- a/InvenTree/InvenTree/views.py +++ b/InvenTree/InvenTree/views.py @@ -29,6 +29,7 @@ from allauth.socialaccount.forms import DisconnectForm from allauth.account.models import EmailAddress from allauth.account.views import EmailView, PasswordResetFromKeyView from allauth.socialaccount.views import ConnectionsView +from allauth_2fa.views import TwoFactorAuthenticate from common.settings import currency_code_default, currency_codes @@ -857,6 +858,14 @@ class CustomPasswordResetFromKeyView(PasswordResetFromKeyView): 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): """ POST endpoint to refresh / update exchange rates From dd74cf19a7461e59a38a44cbcf50ad3b7d105c2c Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 31 Oct 2021 13:42:27 +0100 Subject: [PATCH 04/58] fix middleware to not interupt flow --- InvenTree/InvenTree/middleware.py | 12 +++++++++++- InvenTree/InvenTree/settings.py | 2 +- InvenTree/InvenTree/urls.py | 3 +-- InvenTree/InvenTree/views.py | 7 ------- 4 files changed, 13 insertions(+), 11 deletions(-) diff --git a/InvenTree/InvenTree/middleware.py b/InvenTree/InvenTree/middleware.py index 089c178d9f..3480ef0dbb 100644 --- a/InvenTree/InvenTree/middleware.py +++ b/InvenTree/InvenTree/middleware.py @@ -8,7 +8,7 @@ import time import operator from rest_framework.authtoken.models import Token -from allauth_2fa.middleware import BaseRequire2FAMiddleware +from allauth_2fa.middleware import BaseRequire2FAMiddleware, AllauthTwoFactorMiddleware from InvenTree.urls import frontendpatterns @@ -156,6 +156,7 @@ class QueryCountMiddleware(object): url_matcher = url('', include(frontendpatterns)) class Check2FAMiddleware(BaseRequire2FAMiddleware): + """check if user is required to have MFA enabled""" def require_2fa(self, request): # Superusers are require to have 2FA. try: @@ -164,3 +165,12 @@ class Check2FAMiddleware(BaseRequire2FAMiddleware): except Resolver404: pass return False + +class CustomAllauthTwoFactorMiddleware(AllauthTwoFactorMiddleware): + """This function ensures only frontend code triggers the MFA auth cycle""" + def process_request(self, request): + try: + if not url_matcher.resolve(request.path[1:]): + super().process_request(request) + except Resolver404: + pass diff --git a/InvenTree/InvenTree/settings.py b/InvenTree/InvenTree/settings.py index 1985ecce65..208af3eb1a 100644 --- a/InvenTree/InvenTree/settings.py +++ b/InvenTree/InvenTree/settings.py @@ -301,7 +301,7 @@ MIDDLEWARE = CONFIG.get('middleware', [ 'corsheaders.middleware.CorsMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware', 'django_otp.middleware.OTPMiddleware', # MFA support - 'allauth_2fa.middleware.AllauthTwoFactorMiddleware', # Flow control for allauth + 'InvenTree.middleware.CustomAllauthTwoFactorMiddleware', # Flow control for allauth 'django.contrib.messages.middleware.MessageMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware', 'InvenTree.middleware.AuthRequiredMiddleware', diff --git a/InvenTree/InvenTree/urls.py b/InvenTree/InvenTree/urls.py index 01baa20e03..0f4e20a9b0 100644 --- a/InvenTree/InvenTree/urls.py +++ b/InvenTree/InvenTree/urls.py @@ -37,7 +37,7 @@ from rest_framework.documentation import include_docs_urls from .views import auth_request from .views import IndexView, SearchView, DatabaseStatsView -from .views import SettingsView, EditUserView, SetPasswordView, CustomEmailView, CustomConnectionsView, CustomPasswordResetFromKeyView, CustomTwoFactorAuthenticate +from .views import SettingsView, EditUserView, SetPasswordView, CustomEmailView, CustomConnectionsView, CustomPasswordResetFromKeyView from .views import CurrencyRefreshView from .views import AppearanceSelectView, SettingCategorySelectView from .views import DynamicJsView @@ -168,7 +168,6 @@ frontendpatterns = [ url(r'^accounts/email/', CustomEmailView.as_view(), name='account_email'), url(r'^accounts/social/connections/', CustomConnectionsView.as_view(), name='socialaccount_connections'), url(r"^accounts/password/reset/key/(?P[0-9A-Za-z]+)-(?P.+)/$", 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.urls')), # included urlpatterns ] diff --git a/InvenTree/InvenTree/views.py b/InvenTree/InvenTree/views.py index d1d3ca7436..242b2b5e9a 100644 --- a/InvenTree/InvenTree/views.py +++ b/InvenTree/InvenTree/views.py @@ -858,13 +858,6 @@ class CustomPasswordResetFromKeyView(PasswordResetFromKeyView): 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): """ From 06822303bcfd5e1cf12a90afc8098c5ed81fa9ac Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 31 Oct 2021 13:42:41 +0100 Subject: [PATCH 05/58] refactor user settings --- .../templates/InvenTree/settings/user.html | 176 +++++++++--------- 1 file changed, 93 insertions(+), 83 deletions(-) diff --git a/InvenTree/templates/InvenTree/settings/user.html b/InvenTree/templates/InvenTree/settings/user.html index d6cbf998a7..4d6c33b222 100644 --- a/InvenTree/templates/InvenTree/settings/user.html +++ b/InvenTree/templates/InvenTree/settings/user.html @@ -47,51 +47,53 @@

{% trans 'The following email addresses are associated with your account:' %}

{% else %}

{% trans 'Warning:'%} - {% trans "You currently do not have any email address set up. You should really add an email address so you can receive notifications, reset your password, etc." %} + {% trans "You currently do not have any email address set up. You should really add an email address so you can + receive notifications, reset your password, etc." %}

{% endif %} {% if can_add_email %} -
-

{% trans "Add Email Address" %}

+
+
{% trans "Add Email Address" %}
-
- {% csrf_token %} - {{ add_email_form|crispy }} - -
+
+ {% csrf_token %} + {{ add_email_form|crispy }} + +
{% endif %}
@@ -103,37 +105,39 @@
{% if social_form.accounts %}

{% blocktrans %}You can sign in to your account using any of the following third party accounts:{% endblocktrans %}

- - -
- {% csrf_token %} - -
- {% if social_form.non_field_errors %} -
{{ social_form.non_field_errors }}
- {% endif %} - - {% for base_account in social_form.accounts %} - {% with base_account.get_provider_account as account %} -
- -
- {% endwith %} - {% endfor %} -
- -
- -
- + + + {% csrf_token %} + +
+ {% if social_form.non_field_errors %} +
{{ social_form.non_field_errors }}
+ {% endif %} + + {% for base_account in social_form.accounts %} + {% with base_account.get_provider_account as account %} +
+ +
+ {% endwith %} + {% endfor %} + +
+ +
+ +
+
- + {% else %}

{% trans 'You currently have no social network accounts connected to this account.' %}

{% endif %} @@ -141,7 +145,7 @@

{% trans 'Add a 3rd Party Account' %}

- {% include "socialaccount/snippets/provider_list.html" with process="connect" %} + {% include "socialaccount/snippets/provider_list.html" with process="connect" %}
{% include "socialaccount/snippets/login_extra.html" %}
@@ -186,25 +190,29 @@
{% csrf_token %} -
- + {% get_current_language as LANGUAGE_CODE %} + {% get_available_languages as LANGUAGES %} + {% get_language_info_list for LANGUAGES as languages %} + {% for language in languages %} + {% define language.code as lang_code %} + {% define locale_stats|keyvalue:lang_code as lang_translated %} + - {% endfor %} - -
+ {% endif %} + + {% endfor %} + +
+ +
@@ -212,7 +220,9 @@

{% trans "Help the translation efforts!" %}

-

{% blocktrans with link="https://crowdin.com/project/inventree" %}Native language translation of the InvenTree web application is community contributed via crowdin. Contributions are welcomed and encouraged.{% endblocktrans %}

+

{% blocktrans with link="https://crowdin.com/project/inventree" %}Native language translation of the + InvenTree web application is community contributed via crowdin. Contributions are + welcomed and encouraged.{% endblocktrans %}

@@ -220,14 +230,14 @@ {% block js_ready %} (function() { - var message = "{% trans 'Do you really want to remove the selected email address?' %}"; - var actions = document.getElementsByName('action_remove'); - if (actions.length) { - actions[0].addEventListener("click", function(e) { - if (! confirm(message)) { - e.preventDefault(); - } - }); - } +var message = "{% trans 'Do you really want to remove the selected email address?' %}"; +var actions = document.getElementsByName('action_remove'); +if (actions.length) { +actions[0].addEventListener("click", function(e) { +if (! confirm(message)) { +e.preventDefault(); +} +}); +} })(); {% endblock %} \ No newline at end of file From ec17bbcb95b8d4bd017cb408c54fc7cbc3fde465 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 31 Oct 2021 13:54:12 +0100 Subject: [PATCH 06/58] mkaeuser setting tighter --- .../templates/InvenTree/settings/user.html | 134 +++++++++--------- 1 file changed, 68 insertions(+), 66 deletions(-) diff --git a/InvenTree/templates/InvenTree/settings/user.html b/InvenTree/templates/InvenTree/settings/user.html index 4d6c33b222..2813590b23 100644 --- a/InvenTree/templates/InvenTree/settings/user.html +++ b/InvenTree/templates/InvenTree/settings/user.html @@ -38,11 +38,12 @@ -
-

{% trans "Email" %}

-
+
+
+

{% trans "Email" %}

+
-
+
{% if user.emailaddress_set.all %}

{% trans 'The following email addresses are associated with your account:' %}

@@ -84,9 +85,10 @@

{% endif %} +
{% if can_add_email %} -
+
{% trans "Add Email Address" %}
@@ -94,70 +96,70 @@ {{ add_email_form|crispy }} - {% endif %} -
-
- -
-

{% trans "Social Accounts" %}

-
- -
- {% if social_form.accounts %} -

{% blocktrans %}You can sign in to your account using any of the following third party accounts:{% endblocktrans %}

- - -
- {% csrf_token %} - -
- {% if social_form.non_field_errors %} -
{{ social_form.non_field_errors }}
- {% endif %} - - {% for base_account in social_form.accounts %} - {% with base_account.get_provider_account as account %} -
- -
- {% endwith %} - {% endfor %} - -
- -
- -
- -
- - {% else %} -

{% trans 'You currently have no social network accounts connected to this account.' %}

- {% endif %} - -
-

{% trans 'Add a 3rd Party Account' %}

-
- {% include "socialaccount/snippets/provider_list.html" with process="connect" %}
- {% include "socialaccount/snippets/login_extra.html" %} -
- + {% endif %}
+
+
+

{% trans "Social Accounts" %}

+
-
-

{% trans "Theme Settings" %}

+
+ {% if social_form.accounts %} +

{% blocktrans %}You can sign in to your account using any of the following third party accounts:{% endblocktrans %}

+ + +
+ {% csrf_token %} + +
+ {% if social_form.non_field_errors %} +
{{ social_form.non_field_errors }}
+ {% endif %} + + {% for base_account in social_form.accounts %} + {% with base_account.get_provider_account as account %} +
+ +
+ {% endwith %} + {% endfor %} + +
+ +
+ +
+ +
+ + {% else %} +

{% trans 'You currently have no social network accounts connected to this account.' %}

+ {% endif %} +
+ +
+
{% trans 'Add a 3rd Party Account' %}
+
+ {% include "socialaccount/snippets/provider_list.html" with process="connect" %} +
+ {% include "socialaccount/snippets/login_extra.html" %} +
+
+
+

{% trans "Theme Settings" %}

+
{% csrf_token %} @@ -181,11 +183,11 @@
-
-

{% trans "Language Settings" %}

-
-
+
+

{% trans "Language Settings" %}

+
+
{% csrf_token %} From 487bab5162c34e110f737e4e2c98125524a76b67 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 31 Oct 2021 14:02:24 +0100 Subject: [PATCH 07/58] cleanup --- InvenTree/templates/InvenTree/settings/user.html | 1 - 1 file changed, 1 deletion(-) diff --git a/InvenTree/templates/InvenTree/settings/user.html b/InvenTree/templates/InvenTree/settings/user.html index 2813590b23..26e543cd5f 100644 --- a/InvenTree/templates/InvenTree/settings/user.html +++ b/InvenTree/templates/InvenTree/settings/user.html @@ -155,7 +155,6 @@
-

{% trans "Theme Settings" %}

From 7719a07f19f1a4afe9890ef92bd8492f9a301e8c Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 31 Oct 2021 14:10:07 +0100 Subject: [PATCH 08/58] remove data --- inventree-data | 1 - 1 file changed, 1 deletion(-) delete mode 160000 inventree-data diff --git a/inventree-data b/inventree-data deleted file mode 160000 index 5a5efd9dac..0000000000 --- a/inventree-data +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 5a5efd9dac4983a56b3141abdcc9e115466da253 From 85da98a004ef84f2a57c295fa398d38ccc60081e Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 31 Oct 2021 23:07:25 +0100 Subject: [PATCH 09/58] use auth screen to make more clear this is about security --- .../templates/allauth_2fa/authenticate.html | 15 ++++++++ .../templates/allauth_2fa/backup_tokens.html | 32 +++++++++++++++++ InvenTree/templates/allauth_2fa/remove.html | 18 ++++++++++ InvenTree/templates/allauth_2fa/setup.html | 36 +++++++++++++++++++ 4 files changed, 101 insertions(+) create mode 100644 InvenTree/templates/allauth_2fa/authenticate.html create mode 100644 InvenTree/templates/allauth_2fa/backup_tokens.html create mode 100644 InvenTree/templates/allauth_2fa/remove.html create mode 100644 InvenTree/templates/allauth_2fa/setup.html diff --git a/InvenTree/templates/allauth_2fa/authenticate.html b/InvenTree/templates/allauth_2fa/authenticate.html new file mode 100644 index 0000000000..1a392fc44a --- /dev/null +++ b/InvenTree/templates/allauth_2fa/authenticate.html @@ -0,0 +1,15 @@ +{% extends "account/base.html" %} +{% load i18n crispy_forms_tags %} + +{% block content %} +

{% trans "Two-Factor Authentication" %}

+ + + {% csrf_token %} + {{ form|crispy }} + + + +{% endblock %} diff --git a/InvenTree/templates/allauth_2fa/backup_tokens.html b/InvenTree/templates/allauth_2fa/backup_tokens.html new file mode 100644 index 0000000000..fcb6a35914 --- /dev/null +++ b/InvenTree/templates/allauth_2fa/backup_tokens.html @@ -0,0 +1,32 @@ +{% extends "account/base.html" %} +{% load i18n %} + +{% block content %} +

+ {% trans "Two-Factor Authentication Backup Tokens" %} +

+ +{% if backup_tokens %} + {% if reveal_tokens %} +
    + {% for token in backup_tokens %} +
  • {{ token.token }}
  • + {% endfor %} +
+ {% else %} + {% trans 'Backup tokens have been generated, but are not revealed here for security reasons. Press the button below to generate new ones.' %} + {% endif %} +{% else %} + {% trans 'No tokens. Press the button below to generate some.' %} +{% endif %} + +
+ {% csrf_token %} + +
+ +Disable Two Factor + +{% endblock %} diff --git a/InvenTree/templates/allauth_2fa/remove.html b/InvenTree/templates/allauth_2fa/remove.html new file mode 100644 index 0000000000..dd45ad19ea --- /dev/null +++ b/InvenTree/templates/allauth_2fa/remove.html @@ -0,0 +1,18 @@ +{% extends "account/base.html" %} +{% load i18n %} + +{% block content %} +

+ {% trans "Disable Two-Factor Authentication" %} +

+ +

{% trans "Are you sure?" %}

+ +
+ {% csrf_token %} + +
+ +{% endblock %} diff --git a/InvenTree/templates/allauth_2fa/setup.html b/InvenTree/templates/allauth_2fa/setup.html new file mode 100644 index 0000000000..46d2f3a55c --- /dev/null +++ b/InvenTree/templates/allauth_2fa/setup.html @@ -0,0 +1,36 @@ +{% extends "account/base.html" %} +{% load i18n %} + +{% block content %} +

+ {% trans "Setup Two-Factor Authentication" %} +

+ +

+ {% trans 'Step 1' %}: +

+ +

+ {% trans 'Scan the QR code below with a token generator of your choice (for instance Google Authenticator).' %} +

+ + + +

+ {% trans 'Step 2' %}: +

+ +

+ {% trans 'Input a token generated by the app:' %} +

+ +
+ {% csrf_token %} + {{ form.non_field_errors }} + {{ form.token.label }}: {{ form.token }} + + +
+{% endblock %} From d8fb6d44eca788c2b1a2f46e0155dd0c2c3e150e Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 31 Oct 2021 23:42:31 +0100 Subject: [PATCH 10/58] adds faktors to user settings --- .../templates/InvenTree/settings/user.html | 45 +++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/InvenTree/templates/InvenTree/settings/user.html b/InvenTree/templates/InvenTree/settings/user.html index 26e543cd5f..567f36bebf 100644 --- a/InvenTree/templates/InvenTree/settings/user.html +++ b/InvenTree/templates/InvenTree/settings/user.html @@ -155,6 +155,51 @@
+
+
+

{% trans "Multifaktor" %}

+
+ +
+ {% if user.staticdevice_set.all and user.totpdevice_set.all %} +

{% trans 'You have these faktors available:' %}

+ + + + + + + + {% for token in user.totpdevice_set.all %} + + + + + {% endfor %} + {% for token in user.staticdevice_set.all %} + + + + + {% endfor %} + +
TypeName
{% trans 'TOTP' %}{{ token.name }}
{% trans 'Static' %}{{ token.name }}
+ + {% else %} +

{% trans 'Warning:'%} + {% trans "You currently do not have any faktors set up." %} +

+ + {% endif %} +
+ + +
+

{% trans "Theme Settings" %}

From b7d6ca12e8e171e35cf0002a3071caa9e8206357 Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 1 Nov 2021 00:16:30 +0100 Subject: [PATCH 11/58] PEP fixes --- InvenTree/InvenTree/middleware.py | 2 ++ InvenTree/InvenTree/views.py | 2 -- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/InvenTree/InvenTree/middleware.py b/InvenTree/InvenTree/middleware.py index 3480ef0dbb..ea0d6ddc2a 100644 --- a/InvenTree/InvenTree/middleware.py +++ b/InvenTree/InvenTree/middleware.py @@ -155,6 +155,7 @@ class QueryCountMiddleware(object): url_matcher = url('', include(frontendpatterns)) + class Check2FAMiddleware(BaseRequire2FAMiddleware): """check if user is required to have MFA enabled""" def require_2fa(self, request): @@ -166,6 +167,7 @@ class Check2FAMiddleware(BaseRequire2FAMiddleware): pass return False + class CustomAllauthTwoFactorMiddleware(AllauthTwoFactorMiddleware): """This function ensures only frontend code triggers the MFA auth cycle""" def process_request(self, request): diff --git a/InvenTree/InvenTree/views.py b/InvenTree/InvenTree/views.py index 2ed17fe729..00f38ce89d 100644 --- a/InvenTree/InvenTree/views.py +++ b/InvenTree/InvenTree/views.py @@ -29,7 +29,6 @@ from allauth.socialaccount.forms import DisconnectForm from allauth.account.models import EmailAddress from allauth.account.views import EmailView, PasswordResetFromKeyView from allauth.socialaccount.views import ConnectionsView -from allauth_2fa.views import TwoFactorAuthenticate from common.settings import currency_code_default, currency_codes @@ -778,7 +777,6 @@ class CustomPasswordResetFromKeyView(PasswordResetFromKeyView): success_url = reverse_lazy("account_login") - class CurrencyRefreshView(RedirectView): """ POST endpoint to refresh / update exchange rates From fc566621b7ea4d720d0e430e2afde276e6cf135a Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 1 Nov 2021 00:24:21 +0100 Subject: [PATCH 12/58] add rulesets --- InvenTree/users/models.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/InvenTree/users/models.py b/InvenTree/users/models.py index d31f2a9905..e57e9b035a 100644 --- a/InvenTree/users/models.py +++ b/InvenTree/users/models.py @@ -73,6 +73,9 @@ class RuleSet(models.Model): 'socialaccount_socialaccount', 'socialaccount_socialapp', 'socialaccount_socialtoken', + 'otp_totp_totpdevice', + 'otp_static_statictoken', + 'otp_static_staticdevice', ], 'part_category': [ 'part_partcategory', From a8e18a7972b8c505c014a8f2b503f4cb228bae98 Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 1 Nov 2021 00:24:57 +0100 Subject: [PATCH 13/58] handle modelnames / permissions with underscores --- InvenTree/users/models.py | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/InvenTree/users/models.py b/InvenTree/users/models.py index e57e9b035a..0fa9d22ebc 100644 --- a/InvenTree/users/models.py +++ b/InvenTree/users/models.py @@ -234,7 +234,7 @@ class RuleSet(models.Model): given the app_model name, and the permission type. """ - app, model = model.split('_') + model, app = split_model(model) return "{app}.{perm}_{model}".format( app=app, @@ -277,6 +277,19 @@ class RuleSet(models.Model): return self.RULESET_MODELS.get(self.name, []) +def split_model(model): + """get modelname and app from modelstring""" + app, *model = model.split('_') + + # handle models that have + if len(model) > 1: + model = '_'.join(model) + else: + model = model[0] + + return model, app + + def update_group_roles(group, debug=False): """ @@ -380,7 +393,7 @@ def update_group_roles(group, debug=False): (app, perm) = permission_string.split('.') - (permission_name, model) = perm.split('_') + (model, permission_name) = split_model(perm) try: content_type = ContentType.objects.get(app_label=app, model=model) From 9d68d6bdca92421aa97d4009b7e93dec0aba9d0a Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 1 Nov 2021 00:34:00 +0100 Subject: [PATCH 14/58] added setting to anable MFA --- InvenTree/InvenTree/middleware.py | 3 ++- InvenTree/common/models.py | 6 ++++++ InvenTree/templates/InvenTree/settings/login.html | 1 + 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/InvenTree/InvenTree/middleware.py b/InvenTree/InvenTree/middleware.py index ea0d6ddc2a..0d7a4f46f7 100644 --- a/InvenTree/InvenTree/middleware.py +++ b/InvenTree/InvenTree/middleware.py @@ -11,6 +11,7 @@ from rest_framework.authtoken.models import Token from allauth_2fa.middleware import BaseRequire2FAMiddleware, AllauthTwoFactorMiddleware from InvenTree.urls import frontendpatterns +from common.models import InvenTreeSetting logger = logging.getLogger("inventree") @@ -162,7 +163,7 @@ class Check2FAMiddleware(BaseRequire2FAMiddleware): # Superusers are require to have 2FA. try: if url_matcher.resolve(request.path[1:]): - return True + return InvenTreeSetting.get_setting('LOGIN_ENFORCE_MFA') except Resolver404: pass return False diff --git a/InvenTree/common/models.py b/InvenTree/common/models.py index 3dae13c3e0..631e2f5686 100644 --- a/InvenTree/common/models.py +++ b/InvenTree/common/models.py @@ -853,6 +853,12 @@ class InvenTreeSetting(BaseInvenTreeSetting): 'default': '', 'choices': settings_group_options }, + 'LOGIN_ENFORCE_MFA': { + 'name': _('Enforce MFA'), + 'description': _('Users must use multifaktor security.'), + 'default': False, + 'validator': bool, + }, } class Meta: diff --git a/InvenTree/templates/InvenTree/settings/login.html b/InvenTree/templates/InvenTree/settings/login.html index d3cba1180f..4ce4cd2408 100644 --- a/InvenTree/templates/InvenTree/settings/login.html +++ b/InvenTree/templates/InvenTree/settings/login.html @@ -16,6 +16,7 @@ {% include "InvenTree/settings/setting.html" with key="LOGIN_ENABLE_SSO" icon="fa-info-circle" %} {% include "InvenTree/settings/setting.html" with key="LOGIN_ENABLE_PWD_FORGOT" icon="fa-info-circle" %} {% include "InvenTree/settings/setting.html" with key="LOGIN_MAIL_REQUIRED" icon="fa-info-circle" %} + {% include "InvenTree/settings/setting.html" with key="LOGIN_ENFORCE_MFA" %} {% trans 'Signup' %} From 481a1088ee27bb3af7d4e6122e12c2210c4a4195 Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 1 Nov 2021 01:05:29 +0100 Subject: [PATCH 15/58] fix adapters to use OTP --- InvenTree/InvenTree/forms.py | 32 +++++++++++++++++++++++++++++++- InvenTree/InvenTree/settings.py | 3 +-- 2 files changed, 32 insertions(+), 3 deletions(-) diff --git a/InvenTree/InvenTree/forms.py b/InvenTree/InvenTree/forms.py index 7a7668cf66..02b993d31b 100644 --- a/InvenTree/InvenTree/forms.py +++ b/InvenTree/InvenTree/forms.py @@ -4,12 +4,15 @@ Helper forms which subclass Django forms to provide additional functionality # -*- coding: utf-8 -*- from __future__ import unicode_literals +from urllib.parse import urlencode import logging from django.utils.translation import ugettext_lazy as _ from django import forms from django.contrib.auth.models import User, Group from django.conf import settings +from django.http import HttpResponseRedirect +from django.urls import reverse from crispy_forms.helper import FormHelper from crispy_forms.layout import Layout, Field @@ -18,6 +21,9 @@ from crispy_forms.bootstrap import PrependedText, AppendedText, PrependedAppende from allauth.account.forms import SignupForm, set_form_field_order from allauth.account.adapter import DefaultAccountAdapter from allauth.socialaccount.adapter import DefaultSocialAccountAdapter +from allauth.exceptions import ImmediateHttpResponse +from allauth_2fa.adapter import OTPAdapter +from allauth_2fa.utils import user_has_valid_totp_device from part.models import PartCategory from common.models import InvenTreeSetting @@ -278,7 +284,7 @@ class RegistratonMixin: return user -class CustomAccountAdapter(RegistratonMixin, DefaultAccountAdapter): +class CustomAccountAdapter(RegistratonMixin, OTPAdapter, DefaultAccountAdapter): """ Override of adapter to use dynamic settings """ @@ -297,3 +303,27 @@ class CustomSocialAccountAdapter(RegistratonMixin, DefaultSocialAccountAdapter): if InvenTreeSetting.get_setting('LOGIN_SIGNUP_SSO_AUTO', True): return super().is_auto_signup_allowed(request, sociallogin) return False + + # from OTPAdapter + def has_2fa_enabled(self, user): + """Returns True if the user has 2FA configured.""" + return user_has_valid_totp_device(user) + + def login(self, request, user): + # Require two-factor authentication if it has been configured. + if self.has_2fa_enabled(user): + # Cast to string for the case when this is not a JSON serializable + # object, e.g. a UUID. + request.session['allauth_2fa_user_id'] = str(user.id) + + redirect_url = reverse('two-factor-authenticate') + # Add GET parameters to the URL if they exist. + if request.GET: + redirect_url += u'?' + urlencode(request.GET) + + raise ImmediateHttpResponse( + response=HttpResponseRedirect(redirect_url) + ) + + # Otherwise defer to the original allauth adapter. + return super().login(request, user) diff --git a/InvenTree/InvenTree/settings.py b/InvenTree/InvenTree/settings.py index e740107c0e..6ee0c6e3a7 100644 --- a/InvenTree/InvenTree/settings.py +++ b/InvenTree/InvenTree/settings.py @@ -767,8 +767,7 @@ ACCOUNT_FORMS = { } SOCIALACCOUNT_ADAPTER = 'InvenTree.forms.CustomSocialAccountAdapter' -# ACCOUNT_ADAPTER = 'InvenTree.forms.CustomAccountAdapter' # TODO monkey-patch adapter -ACCOUNT_ADAPTER = 'allauth_2fa.adapter.OTPAdapter' +ACCOUNT_ADAPTER = 'InvenTree.forms.CustomAccountAdapter' # Markdownx configuration # Ref: https://neutronx.github.io/django-markdownx/customization/ From 6dead88028480267cd220ca3fed0e7995985986a Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 1 Nov 2021 17:06:13 +0100 Subject: [PATCH 16/58] fix permission parsing if underscores in perm --- InvenTree/users/models.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/InvenTree/users/models.py b/InvenTree/users/models.py index 0fa9d22ebc..f32f7053ea 100644 --- a/InvenTree/users/models.py +++ b/InvenTree/users/models.py @@ -393,7 +393,13 @@ def update_group_roles(group, debug=False): (app, perm) = permission_string.split('.') - (model, permission_name) = split_model(perm) + # split up the permissions -> handle models with underscores + permission_name, *model = perm.split('_') + # handle models that have + if len(model) > 1: + app += '_' + '_'.join(model[:-1]) + perm = permission_name + '_' + model[-1:][0] + model = model[-1:][0] try: content_type = ContentType.objects.get(app_label=app, model=model) @@ -401,6 +407,8 @@ def update_group_roles(group, debug=False): except ContentType.DoesNotExist: logger.warning(f"Error: Could not find permission matching '{permission_string}'") permission = None + except Exception as _e: + print(_e) return permission From 3e3da8b2d1712cde2e728841903d9f5a15d19ea5 Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 1 Nov 2021 22:23:51 +0100 Subject: [PATCH 17/58] fix permission parser --- InvenTree/users/models.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/InvenTree/users/models.py b/InvenTree/users/models.py index f32f7053ea..395405f20d 100644 --- a/InvenTree/users/models.py +++ b/InvenTree/users/models.py @@ -279,13 +279,13 @@ class RuleSet(models.Model): def split_model(model): """get modelname and app from modelstring""" - app, *model = model.split('_') + *app, model = model.split('_') # handle models that have - if len(model) > 1: - model = '_'.join(model) + if len(app) > 1: + app = '_'.join(app) else: - model = model[0] + app = app[0] return model, app From 78d9e906da3d0e9ead4d39467145e321e486511b Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 1 Nov 2021 22:28:38 +0100 Subject: [PATCH 18/58] remove testing string --- InvenTree/users/models.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/InvenTree/users/models.py b/InvenTree/users/models.py index 395405f20d..232248963f 100644 --- a/InvenTree/users/models.py +++ b/InvenTree/users/models.py @@ -407,8 +407,6 @@ def update_group_roles(group, debug=False): except ContentType.DoesNotExist: logger.warning(f"Error: Could not find permission matching '{permission_string}'") permission = None - except Exception as _e: - print(_e) return permission From 61d3ea6293874c6d5d70738dcee57496ef76c674 Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 1 Nov 2021 22:33:56 +0100 Subject: [PATCH 19/58] refactor underscore handeling into function --- InvenTree/users/models.py | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/InvenTree/users/models.py b/InvenTree/users/models.py index 232248963f..a91b9a8121 100644 --- a/InvenTree/users/models.py +++ b/InvenTree/users/models.py @@ -290,6 +290,17 @@ def split_model(model): return model, app +def split_permission(app, perm): + """split permission string into permission and model""" + permission_name, *model = perm.split('_') + # handle models that have underscores + if len(model) > 1: + app += '_' + '_'.join(model[:-1]) + perm = permission_name + '_' + model[-1:][0] + model = model[-1:][0] + return perm, model + + def update_group_roles(group, debug=False): """ @@ -393,13 +404,7 @@ def update_group_roles(group, debug=False): (app, perm) = permission_string.split('.') - # split up the permissions -> handle models with underscores - permission_name, *model = perm.split('_') - # handle models that have - if len(model) > 1: - app += '_' + '_'.join(model[:-1]) - perm = permission_name + '_' + model[-1:][0] - model = model[-1:][0] + perm, model = split_permission(app, perm) try: content_type = ContentType.objects.get(app_label=app, model=model) From 17e7c2ee155d04f147c9e5838c9d267bfe16a0ef Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 1 Nov 2021 22:43:28 +0100 Subject: [PATCH 20/58] disable add button if already set up --- InvenTree/templates/InvenTree/settings/user.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/InvenTree/templates/InvenTree/settings/user.html b/InvenTree/templates/InvenTree/settings/user.html index 567f36bebf..1658e54bcb 100644 --- a/InvenTree/templates/InvenTree/settings/user.html +++ b/InvenTree/templates/InvenTree/settings/user.html @@ -195,7 +195,7 @@
From 392e7f6e4c0d69f4534a570e0bc26b11bd06af37 Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 1 Nov 2021 22:43:43 +0100 Subject: [PATCH 21/58] there can be only one factor --- InvenTree/templates/InvenTree/settings/user.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/InvenTree/templates/InvenTree/settings/user.html b/InvenTree/templates/InvenTree/settings/user.html index 1658e54bcb..ace2ab0038 100644 --- a/InvenTree/templates/InvenTree/settings/user.html +++ b/InvenTree/templates/InvenTree/settings/user.html @@ -161,7 +161,7 @@
- {% if user.staticdevice_set.all and user.totpdevice_set.all %} + {% if user.staticdevice_set.all or user.totpdevice_set.all %}

{% trans 'You have these faktors available:' %}

From fc0e80dd768410954c9a14b07e1dc6984cd4c523 Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 1 Nov 2021 22:45:56 +0100 Subject: [PATCH 22/58] make remove button conditional --- InvenTree/templates/InvenTree/settings/user.html | 2 ++ 1 file changed, 2 insertions(+) diff --git a/InvenTree/templates/InvenTree/settings/user.html b/InvenTree/templates/InvenTree/settings/user.html index ace2ab0038..dd7b4c896a 100644 --- a/InvenTree/templates/InvenTree/settings/user.html +++ b/InvenTree/templates/InvenTree/settings/user.html @@ -196,7 +196,9 @@
{% trans "Change faktors" %}
{% trans "Setup multifaktor" %} + {% if user.staticdevice_set.all or user.totpdevice_set.all %} {% trans "Remove multifaktor" %} + {% endif %}
From 3cb2078821d6551b3cf936230ba97d102bf36a62 Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 1 Nov 2021 22:46:56 +0100 Subject: [PATCH 23/58] fix spelling --- InvenTree/common/models.py | 2 +- InvenTree/templates/InvenTree/settings/user.html | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/InvenTree/common/models.py b/InvenTree/common/models.py index 631e2f5686..ade2c4a439 100644 --- a/InvenTree/common/models.py +++ b/InvenTree/common/models.py @@ -855,7 +855,7 @@ class InvenTreeSetting(BaseInvenTreeSetting): }, 'LOGIN_ENFORCE_MFA': { 'name': _('Enforce MFA'), - 'description': _('Users must use multifaktor security.'), + 'description': _('Users must use multifactor security.'), 'default': False, 'validator': bool, }, diff --git a/InvenTree/templates/InvenTree/settings/user.html b/InvenTree/templates/InvenTree/settings/user.html index dd7b4c896a..04829394a2 100644 --- a/InvenTree/templates/InvenTree/settings/user.html +++ b/InvenTree/templates/InvenTree/settings/user.html @@ -157,12 +157,12 @@
-

{% trans "Multifaktor" %}

+

{% trans "Multifactor" %}

{% if user.staticdevice_set.all or user.totpdevice_set.all %} -

{% trans 'You have these faktors available:' %}

+

{% trans 'You have these factors available:' %}

@@ -187,17 +187,17 @@ {% else %}

{% trans 'Warning:'%} - {% trans "You currently do not have any faktors set up." %} + {% trans "You currently do not have any factors set up." %}

{% endif %}
-
{% trans "Change faktors" %}
- {% trans "Setup multifaktor" %} +
{% trans "Change factors" %}
+ {% trans "Setup multifactor" %} {% if user.staticdevice_set.all or user.totpdevice_set.all %} - {% trans "Remove multifaktor" %} + {% trans "Remove multifactor" %} {% endif %}
From 6404764025c39428ae22a5b4470d7c601b0078b6 Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 1 Nov 2021 22:51:42 +0100 Subject: [PATCH 24/58] crispyfy --- InvenTree/templates/allauth_2fa/setup.html | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/InvenTree/templates/allauth_2fa/setup.html b/InvenTree/templates/allauth_2fa/setup.html index 46d2f3a55c..152ea19688 100644 --- a/InvenTree/templates/allauth_2fa/setup.html +++ b/InvenTree/templates/allauth_2fa/setup.html @@ -1,10 +1,10 @@ {% extends "account/base.html" %} -{% load i18n %} +{% load i18n crispy_forms_tags %} {% block content %} -

+

{% trans "Setup Two-Factor Authentication" %} -

+

{% trans 'Step 1' %}: @@ -26,10 +26,9 @@
{% csrf_token %} - {{ form.non_field_errors }} - {{ form.token.label }}: {{ form.token }} + {{ form|crispy }} - From 58bc67702969029811aa66fbde84df9dada6953f Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 1 Nov 2021 22:58:47 +0100 Subject: [PATCH 25/58] more contrast for the qr --- InvenTree/templates/allauth_2fa/setup.html | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/InvenTree/templates/allauth_2fa/setup.html b/InvenTree/templates/allauth_2fa/setup.html index 152ea19688..375470ad2f 100644 --- a/InvenTree/templates/allauth_2fa/setup.html +++ b/InvenTree/templates/allauth_2fa/setup.html @@ -14,7 +14,10 @@ {% trans 'Scan the QR code below with a token generator of your choice (for instance Google Authenticator).' %}

- +
+ +
+

{% trans 'Step 2' %}: From a8f5661a27b3051ffc971df128336463fdfc5ceb Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 1 Nov 2021 22:59:23 +0100 Subject: [PATCH 26/58] make auth a bit wider --- InvenTree/InvenTree/static/css/inventree.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/InvenTree/InvenTree/static/css/inventree.css b/InvenTree/InvenTree/static/css/inventree.css index 61037c9c54..1bd6d50f72 100644 --- a/InvenTree/InvenTree/static/css/inventree.css +++ b/InvenTree/InvenTree/static/css/inventree.css @@ -30,7 +30,7 @@ background-color: rgba(50, 50, 50, 0.75); width: 100%; - max-width: 330px; + max-width: 350px; margin: auto; } From 240859c38c2894b977dc21f4ea5ecdea5fbe9cac Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 1 Nov 2021 23:03:36 +0100 Subject: [PATCH 27/58] make crispy --- InvenTree/templates/allauth_2fa/backup_tokens.html | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/InvenTree/templates/allauth_2fa/backup_tokens.html b/InvenTree/templates/allauth_2fa/backup_tokens.html index fcb6a35914..f9a89cddc3 100644 --- a/InvenTree/templates/allauth_2fa/backup_tokens.html +++ b/InvenTree/templates/allauth_2fa/backup_tokens.html @@ -2,9 +2,9 @@ {% load i18n %} {% block content %} -

+

{% trans "Two-Factor Authentication Backup Tokens" %} -

+

{% if backup_tokens %} {% if reveal_tokens %} @@ -22,11 +22,12 @@
{% csrf_token %} - -Disable Two Factor +
+Disable Two Factor {% endblock %} From 0011c7ee62a5a8cdddd4e0408801418902e32129 Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 1 Nov 2021 23:07:27 +0100 Subject: [PATCH 28/58] crispyfy --- InvenTree/templates/allauth_2fa/remove.html | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/InvenTree/templates/allauth_2fa/remove.html b/InvenTree/templates/allauth_2fa/remove.html index dd45ad19ea..e9a7bf5671 100644 --- a/InvenTree/templates/allauth_2fa/remove.html +++ b/InvenTree/templates/allauth_2fa/remove.html @@ -2,15 +2,15 @@ {% load i18n %} {% block content %} -

+

{% trans "Disable Two-Factor Authentication" %} -

+

{% trans "Are you sure?" %}

{% csrf_token %} - From 024a9deab49e1cb40af79da9ff47a273d0223ff5 Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 1 Nov 2021 23:07:39 +0100 Subject: [PATCH 29/58] full width buttons --- InvenTree/templates/allauth_2fa/backup_tokens.html | 4 ++-- InvenTree/templates/allauth_2fa/setup.html | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/InvenTree/templates/allauth_2fa/backup_tokens.html b/InvenTree/templates/allauth_2fa/backup_tokens.html index f9a89cddc3..c92132ae81 100644 --- a/InvenTree/templates/allauth_2fa/backup_tokens.html +++ b/InvenTree/templates/allauth_2fa/backup_tokens.html @@ -22,12 +22,12 @@
{% csrf_token %} -
-Disable Two Factor +Disable Two Factor {% endblock %} diff --git a/InvenTree/templates/allauth_2fa/setup.html b/InvenTree/templates/allauth_2fa/setup.html index 375470ad2f..bacad29b55 100644 --- a/InvenTree/templates/allauth_2fa/setup.html +++ b/InvenTree/templates/allauth_2fa/setup.html @@ -31,7 +31,7 @@ {% csrf_token %} {{ form|crispy }} - From 4e9cf8492f2b3313dc4abb01c669d676bb52d829 Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 1 Nov 2021 23:11:14 +0100 Subject: [PATCH 30/58] full width --- InvenTree/templates/allauth_2fa/remove.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/InvenTree/templates/allauth_2fa/remove.html b/InvenTree/templates/allauth_2fa/remove.html index e9a7bf5671..fb9d15ae32 100644 --- a/InvenTree/templates/allauth_2fa/remove.html +++ b/InvenTree/templates/allauth_2fa/remove.html @@ -10,7 +10,7 @@
{% csrf_token %} - From 7dbbca5b3ad914984f359fee266e0613e2288b6d Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 1 Nov 2021 23:11:41 +0100 Subject: [PATCH 31/58] fix translation --- InvenTree/templates/InvenTree/settings/user.html | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/InvenTree/templates/InvenTree/settings/user.html b/InvenTree/templates/InvenTree/settings/user.html index 04829394a2..07e1e6570b 100644 --- a/InvenTree/templates/InvenTree/settings/user.html +++ b/InvenTree/templates/InvenTree/settings/user.html @@ -79,10 +79,7 @@ {% else %} -

{% trans 'Warning:'%} - {% trans "You currently do not have any email address set up. You should really add an email address so you can - receive notifications, reset your password, etc." %} -

+ {% trans "You currently do not have any email address set up. You should really add an email address so you can receive notifications, reset your password, etc." %} {% endif %} From b4570f6a7eb115c326c3f04984d86102a5cbe886 Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 1 Nov 2021 23:11:48 +0100 Subject: [PATCH 32/58] use alert --- InvenTree/templates/InvenTree/settings/user.html | 3 +++ 1 file changed, 3 insertions(+) diff --git a/InvenTree/templates/InvenTree/settings/user.html b/InvenTree/templates/InvenTree/settings/user.html index 07e1e6570b..050ad54426 100644 --- a/InvenTree/templates/InvenTree/settings/user.html +++ b/InvenTree/templates/InvenTree/settings/user.html @@ -79,7 +79,10 @@ {% else %} +
+ {% trans 'Warning:'%} {% trans "You currently do not have any email address set up. You should really add an email address so you can receive notifications, reset your password, etc." %} +
{% endif %} From 7bcab1325344f9cee775f6f42045860afa265915 Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 1 Nov 2021 23:19:50 +0100 Subject: [PATCH 33/58] translate disable button --- InvenTree/templates/allauth_2fa/backup_tokens.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/InvenTree/templates/allauth_2fa/backup_tokens.html b/InvenTree/templates/allauth_2fa/backup_tokens.html index c92132ae81..989afa611c 100644 --- a/InvenTree/templates/allauth_2fa/backup_tokens.html +++ b/InvenTree/templates/allauth_2fa/backup_tokens.html @@ -28,6 +28,6 @@
-Disable Two Factor +{% trans "Disable Two Factor" %} {% endblock %} From f8a45dcec2cbdd5dc5e2c6c0a2ef3f52e2126838 Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 1 Nov 2021 23:23:50 +0100 Subject: [PATCH 34/58] back button --- InvenTree/templates/allauth_2fa/backup_tokens.html | 2 ++ 1 file changed, 2 insertions(+) diff --git a/InvenTree/templates/allauth_2fa/backup_tokens.html b/InvenTree/templates/allauth_2fa/backup_tokens.html index 989afa611c..a38c07b810 100644 --- a/InvenTree/templates/allauth_2fa/backup_tokens.html +++ b/InvenTree/templates/allauth_2fa/backup_tokens.html @@ -6,6 +6,8 @@ {% trans "Two-Factor Authentication Backup Tokens" %} + {% trans "to settings" %}} + {% if backup_tokens %} {% if reveal_tokens %}
    From 70341d674509ef3891da829ca2d8625a0cdcd627 Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 1 Nov 2021 23:33:29 +0100 Subject: [PATCH 35/58] refactor of backup tokens --- InvenTree/templates/allauth_2fa/backup_tokens.html | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/InvenTree/templates/allauth_2fa/backup_tokens.html b/InvenTree/templates/allauth_2fa/backup_tokens.html index a38c07b810..2fe30e8222 100644 --- a/InvenTree/templates/allauth_2fa/backup_tokens.html +++ b/InvenTree/templates/allauth_2fa/backup_tokens.html @@ -6,8 +6,6 @@ {% trans "Two-Factor Authentication Backup Tokens" %} - {% trans "to settings" %}} - {% if backup_tokens %} {% if reveal_tokens %}
      @@ -22,14 +20,14 @@ {% trans 'No tokens. Press the button below to generate some.' %} {% endif %} +
      {% csrf_token %} -
      -{% trans "Disable Two Factor" %} +{% trans "back to settings" %} {% endblock %} From 58f35ebc560467b3abfa14296ec57923a44a873e Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 1 Nov 2021 23:49:22 +0100 Subject: [PATCH 36/58] use buttons isntead of links --- InvenTree/templates/InvenTree/settings/user.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/InvenTree/templates/InvenTree/settings/user.html b/InvenTree/templates/InvenTree/settings/user.html index 050ad54426..4801e7fe85 100644 --- a/InvenTree/templates/InvenTree/settings/user.html +++ b/InvenTree/templates/InvenTree/settings/user.html @@ -195,9 +195,9 @@
      {% trans "Change factors" %}
      - {% trans "Setup multifactor" %} + {% if user.staticdevice_set.all or user.totpdevice_set.all %} - {% trans "Remove multifactor" %} + {% endif %}
      From e73d3d567cbc416eb44a177547c75e6d113724b1 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 2 Nov 2021 07:56:59 +0100 Subject: [PATCH 37/58] fix MFA buttons --- InvenTree/templates/InvenTree/settings/user.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/InvenTree/templates/InvenTree/settings/user.html b/InvenTree/templates/InvenTree/settings/user.html index 4801e7fe85..676a4e6ca9 100644 --- a/InvenTree/templates/InvenTree/settings/user.html +++ b/InvenTree/templates/InvenTree/settings/user.html @@ -195,9 +195,9 @@
      {% trans "Change factors" %}
      - + {% trans "Setup multifactor" %} {% if user.staticdevice_set.all or user.totpdevice_set.all %} - + {% trans "Remove multifactor" %} {% endif %}
      From ceb06c6e622a0a15e51ad3823847659845b0646a Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 9 Dec 2021 23:54:02 +0100 Subject: [PATCH 38/58] require MFA for admin if set --- InvenTree/InvenTree/urls.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/InvenTree/InvenTree/urls.py b/InvenTree/InvenTree/urls.py index 7b41cfc7e0..e48fbc299e 100644 --- a/InvenTree/InvenTree/urls.py +++ b/InvenTree/InvenTree/urls.py @@ -129,10 +129,6 @@ backendpatterns = [ 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')), @@ -165,6 +161,11 @@ frontendpatterns = [ # plugin urls get_plugin_urls(), # appends currently loaded plugin urls = None + # admin sites + 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'), + # DB user sessions url(r'^accounts/sessions/other/delete/$', view=CustomSessionDeleteOtherView.as_view(), name='session_delete_other', ), url(r'^accounts/sessions/(?P\w+)/delete/$', view=CustomSessionDeleteView.as_view(), name='session_delete', ), From 18244f0adf72fc57f4c2fca5c0d2097731ef3c64 Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 9 Dec 2021 23:54:23 +0100 Subject: [PATCH 39/58] docstring --- InvenTree/InvenTree/urls.py | 1 + 1 file changed, 1 insertion(+) diff --git a/InvenTree/InvenTree/urls.py b/InvenTree/InvenTree/urls.py index e48fbc299e..f7035fb6f1 100644 --- a/InvenTree/InvenTree/urls.py +++ b/InvenTree/InvenTree/urls.py @@ -132,6 +132,7 @@ backendpatterns = [ url(r'^api/', include(apipatterns)), url(r'^api-doc/', include_docs_urls(title='InvenTree API')), + # 3rd party endpoints url(r'^markdownx/', include('markdownx.urls')), ] From e6ad22ec15373cc9245226c668fa55f38f74051a Mon Sep 17 00:00:00 2001 From: Matthias Date: Fri, 10 Dec 2021 01:34:47 +0100 Subject: [PATCH 40/58] fix plugin urlpattern patching --- InvenTree/plugin/registry.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/InvenTree/plugin/registry.py b/InvenTree/plugin/registry.py index d16575af2b..4fc5330f8b 100644 --- a/InvenTree/plugin/registry.py +++ b/InvenTree/plugin/registry.py @@ -11,7 +11,7 @@ from importlib import reload from django.apps import apps from django.conf import settings from django.db.utils import OperationalError, ProgrammingError -from django.conf.urls import url +from django.conf.urls import url, include from django.urls import clear_url_caches from django.contrib import admin from django.utils.text import slugify @@ -412,7 +412,7 @@ class Plugins: self.plugins_inactive = {} def _update_urls(self): - from InvenTree.urls import urlpatterns + from InvenTree.urls import urlpatterns as global_pattern, frontendpatterns as urlpatterns from plugin.urls import get_plugin_urls for index, a in enumerate(urlpatterns): @@ -421,6 +421,9 @@ class Plugins: urlpatterns[index] = url(r'^admin/', admin.site.urls, name='inventree-admin') elif a.app_name == 'plugin': urlpatterns[index] = get_plugin_urls() + + # replace frontendpatterns + global_pattern[0] = url('', include(urlpatterns)) clear_url_caches() def _reload_apps(self, force_reload: bool = False): From 67b3c339b99bb8ca429828ca6598921ae68cefb8 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 11 Dec 2021 22:10:05 +0100 Subject: [PATCH 41/58] add back button to setup screen --- InvenTree/templates/allauth_2fa/setup.html | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/InvenTree/templates/allauth_2fa/setup.html b/InvenTree/templates/allauth_2fa/setup.html index bacad29b55..ce795a3c98 100644 --- a/InvenTree/templates/allauth_2fa/setup.html +++ b/InvenTree/templates/allauth_2fa/setup.html @@ -35,4 +35,8 @@ {% trans 'Verify' %} + + {% endblock %} From 41302398e933b6d3943ff1bbef2b0a4dc6f8fb29 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 11 Dec 2021 23:07:37 +0100 Subject: [PATCH 42/58] managment and invoke commands to remove mfa --- .../management/commands/remove_mfa.py | 36 +++++++++++++++++++ tasks.py | 12 +++++++ 2 files changed, 48 insertions(+) create mode 100644 InvenTree/InvenTree/management/commands/remove_mfa.py diff --git a/InvenTree/InvenTree/management/commands/remove_mfa.py b/InvenTree/InvenTree/management/commands/remove_mfa.py new file mode 100644 index 0000000000..8c84920cc3 --- /dev/null +++ b/InvenTree/InvenTree/management/commands/remove_mfa.py @@ -0,0 +1,36 @@ +""" +Custom management command to remove MFA for a user +""" + +from django.core.management.base import BaseCommand +from django.contrib.auth import get_user_model + + +class Command(BaseCommand): + """ + Remove MFA for a user + """ + + def add_arguments(self, parser): + parser.add_argument('mail', type=str) + + def handle(self, *args, **kwargs): + + # general settings + mail = kwargs.get('mail') + if not mail: + raise KeyError('A mail is required') + user = get_user_model() + mfa_user = [*set(user.objects.filter(email=mail) | user.objects.filter(emailaddress__email=mail))] + + if len(mfa_user) == 0: + print('No user with this mail associated') + elif len(mfa_user) > 1: + print('More than one user found with this mail') + else: + # and clean out all MFA methods + # backup codes + mfa_user[0].staticdevice_set.all().delete() + # TOTP tokens + mfa_user[0].totpdevice_set.all().delete() + print(f'Removed all MFA methods for user {str(mfa_user[0])}') diff --git a/tasks.py b/tasks.py index 7408bb40b5..4d5d7ff6c8 100644 --- a/tasks.py +++ b/tasks.py @@ -154,6 +154,18 @@ def clean_settings(c): manage(c, "clean_settings") +@task(help={'mail': 'mail of the user whos MFA should be disabled'}) +def remove_mfa(c, mail=''): + """ + Remove MFA for a user + """ + + if not mail: + print('You must provide a users mail') + + manage(c, f"remove_mfa {mail}") + + @task(post=[rebuild_models, rebuild_thumbnails]) def migrate(c): """ From f9b1305f9cdd3c08132cbbaacdcd817e4225858a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 12 Dec 2021 10:06:07 +0000 Subject: [PATCH 43/58] Bump django from 3.2.5 to 3.2.10 Bumps [django](https://github.com/django/django) from 3.2.5 to 3.2.10. - [Release notes](https://github.com/django/django/releases) - [Commits](https://github.com/django/django/compare/3.2.5...3.2.10) --- updated-dependencies: - dependency-name: django dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index c4d28599de..c6cc4effff 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,5 @@ # Please keep this list sorted -Django==3.2.5 # Django package +Django==3.2.10 # Django package certifi # Certifi is (most likely) installed through one of the requirements above coreapi==2.3.0 # API documentation coverage==5.3 # Unit test coverage From a2357b5b46fa73a0f72020a58e7482b315abd23d Mon Sep 17 00:00:00 2001 From: Oliver Date: Sun, 12 Dec 2021 22:18:29 +1100 Subject: [PATCH 44/58] Fix decimal places for money field --- InvenTree/part/serializers.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/InvenTree/part/serializers.py b/InvenTree/part/serializers.py index cf9d34a44c..820962b613 100644 --- a/InvenTree/part/serializers.py +++ b/InvenTree/part/serializers.py @@ -475,9 +475,9 @@ class BomItemSerializer(InvenTreeModelSerializer): validated = serializers.BooleanField(read_only=True, source='is_line_valid') - purchase_price_min = MoneyField(max_digits=10, decimal_places=6, read_only=True) + purchase_price_min = MoneyField(max_digits=19, decimal_places=4, read_only=True) - purchase_price_max = MoneyField(max_digits=10, decimal_places=6, read_only=True) + purchase_price_max = MoneyField(max_digits=19, decimal_places=4, read_only=True) purchase_price_avg = serializers.SerializerMethodField() From e9650a94f38f0ff01fc4b0a37d17bf45e447a5cf Mon Sep 17 00:00:00 2001 From: sintech Date: Sun, 12 Dec 2021 19:57:30 +0300 Subject: [PATCH 45/58] Do not show icons for childless categories --- InvenTree/templates/js/dynamic/nav.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/InvenTree/templates/js/dynamic/nav.js b/InvenTree/templates/js/dynamic/nav.js index 28f9a26d8d..fc7e677900 100644 --- a/InvenTree/templates/js/dynamic/nav.js +++ b/InvenTree/templates/js/dynamic/nav.js @@ -175,7 +175,6 @@ function enableBreadcrumbTree(options) { for (var i = 0; i < data.length; i++) { node = data[i]; - node.nodes = []; nodes[node.pk] = node; node.selectable = false; @@ -193,7 +192,11 @@ function enableBreadcrumbTree(options) { node = data[i]; if (node.parent != null) { - nodes[node.parent].nodes.push(node); + if (nodes[node.parent].nodes) { + nodes[node.parent].nodes.push(node); + } else { + nodes[node.parent].nodes = [node]; + } if (node.state.expanded) { nodes[node.parent].state.expanded = true; From 40f73b00cefe7c1aabb3a147fed69480f8ed17e0 Mon Sep 17 00:00:00 2001 From: sintech Date: Sun, 12 Dec 2021 19:59:18 +0300 Subject: [PATCH 46/58] Propagate expanded state all the way to the root --- InvenTree/templates/js/dynamic/nav.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/InvenTree/templates/js/dynamic/nav.js b/InvenTree/templates/js/dynamic/nav.js index fc7e677900..b38dd17b32 100644 --- a/InvenTree/templates/js/dynamic/nav.js +++ b/InvenTree/templates/js/dynamic/nav.js @@ -199,7 +199,10 @@ function enableBreadcrumbTree(options) { } if (node.state.expanded) { - nodes[node.parent].state.expanded = true; + while (node.parent != null) { + nodes[node.parent].state.expanded = true; + node = nodes[node.parent] + } } } else { From d75ce451be171de7460334b60ef93f0065db0671 Mon Sep 17 00:00:00 2001 From: Oliver Date: Mon, 22 Nov 2021 20:51:55 +1100 Subject: [PATCH 47/58] Allow user to select file format when downloadin BOM import template (cherry picked from commit 8030ca0bb9be0ce564d041b406f0024512f99000) --- .../part/bom_upload/upload_file.html | 8 +++- InvenTree/templates/js/translated/bom.js | 43 ++++++++++++++++--- 2 files changed, 43 insertions(+), 8 deletions(-) diff --git a/InvenTree/part/templates/part/bom_upload/upload_file.html b/InvenTree/part/templates/part/bom_upload/upload_file.html index 6775176ede..40411f074a 100644 --- a/InvenTree/part/templates/part/bom_upload/upload_file.html +++ b/InvenTree/part/templates/part/bom_upload/upload_file.html @@ -32,7 +32,7 @@
      {% trans "Requirements for BOM upload" %}:
      @@ -60,4 +60,8 @@ enableSidebar('bom-upload'); -{% endblock js_ready %} +$('#bom-template-download').click(function() { + downloadBomTemplate(); +}); + +{% endblock js_ready %} \ No newline at end of file diff --git a/InvenTree/templates/js/translated/bom.js b/InvenTree/templates/js/translated/bom.js index 3cde5bca61..29a958d452 100644 --- a/InvenTree/templates/js/translated/bom.js +++ b/InvenTree/templates/js/translated/bom.js @@ -2,6 +2,7 @@ /* globals constructForm, + exportFormatOptions, imageHoverIcon, inventreeGet, inventreePut, @@ -14,6 +15,7 @@ */ /* exported + downloadBomTemplate, newPartFromBomWizard, loadBomTable, loadUsedInTable, @@ -21,12 +23,41 @@ removeColFromBomWizard, */ -/* BOM management functions. - * Requires follwing files to be loaded first: - * - api.js - * - part.js - * - modals.js - */ +function downloadBomTemplate(options={}) { + + var format = options.format; + + if (!format) { + format = inventreeLoad('bom-export-format', 'csv'); + } + + constructFormBody({}, { + title: '{% trans "Download BOM Template" %}', + fields: { + format: { + label: '{% trans "Format" %}', + help_text: '{% trans "Select file format" %}', + required: true, + type: 'choice', + value: format, + choices: exportFormatOptions(), + } + }, + onSubmit: function(fields, opts) { + var format = getFormFieldValue('format', fields['format'], opts); + + // Save the format for next time + inventreeSave('bom-export-format', format); + + // Hide the modal + $(opts.modal).modal('hide'); + + // Download the file + location.href = `{% url "bom-upload-template" %}?format=${format}`; + + } + }); +} function bomItemFields() { From 88fc0393b73bd854e634d020f2fca0cef65bd9ed Mon Sep 17 00:00:00 2001 From: Oliver Date: Mon, 22 Nov 2021 21:46:45 +1100 Subject: [PATCH 48/58] Logic fix for boolean fields in JS forms (cherry picked from commit 10dec7743e083ccffdd819b36b231189b26ca8d9) --- InvenTree/templates/js/translated/forms.js | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/InvenTree/templates/js/translated/forms.js b/InvenTree/templates/js/translated/forms.js index def7e41358..43e8d5ce62 100644 --- a/InvenTree/templates/js/translated/forms.js +++ b/InvenTree/templates/js/translated/forms.js @@ -811,7 +811,9 @@ function updateFieldValue(name, value, field, options) { switch (field.type) { case 'boolean': - el.prop('checked', value); + if (value == true || value.toString().toLowerCase() == 'true') { + el.prop('checked'); + } break; case 'related field': // Clear? @@ -2034,8 +2036,15 @@ function constructInputOptions(name, classes, type, parameters) { } if (parameters.value != null) { - // Existing value? - opts.push(`value='${parameters.value}'`); + if (parameters.type == 'boolean') { + // Special consideration of a boolean (checkbox) value + if (parameters.value == true || parameters.value.toString().toLowerCase() == 'true') { + opts.push('checked'); + } + } else { + // Existing value? + opts.push(`value='${parameters.value}'`); + } } else if (parameters.default != null) { // Otherwise, a defualt value? opts.push(`value='${parameters.default}'`); From 01ce752a8cd29df7d9c9d58f1d7d22e914247834 Mon Sep 17 00:00:00 2001 From: Oliver Date: Mon, 22 Nov 2021 21:51:32 +1100 Subject: [PATCH 49/58] BOM export options are now han (cherry picked from commit c797eb07037237a6ce273d1783f99d43551f77de) --- InvenTree/part/forms.py | 30 --------- InvenTree/part/templates/part/detail.html | 8 +-- InvenTree/part/views.py | 4 -- InvenTree/templates/js/translated/bom.js | 81 +++++++++++++++++++++++ 4 files changed, 82 insertions(+), 41 deletions(-) diff --git a/InvenTree/part/forms.py b/InvenTree/part/forms.py index 609acec917..a5c4f87c89 100644 --- a/InvenTree/part/forms.py +++ b/InvenTree/part/forms.py @@ -55,36 +55,6 @@ class PartImageDownloadForm(HelperForm): ] -class BomExportForm(forms.Form): - """ Simple form to let user set BOM export options, - before exporting a BOM (bill of materials) file. - """ - - file_format = forms.ChoiceField(label=_("File Format"), help_text=_("Select output file format")) - - cascading = forms.BooleanField(label=_("Cascading"), required=False, initial=True, help_text=_("Download cascading / multi-level BOM")) - - levels = forms.IntegerField(label=_("Levels"), required=True, initial=0, help_text=_("Select maximum number of BOM levels to export (0 = all levels)")) - - parameter_data = forms.BooleanField(label=_("Include Parameter Data"), required=False, initial=False, help_text=_("Include part parameters data in exported BOM")) - - stock_data = forms.BooleanField(label=_("Include Stock Data"), required=False, initial=False, help_text=_("Include part stock data in exported BOM")) - - manufacturer_data = forms.BooleanField(label=_("Include Manufacturer Data"), required=False, initial=True, help_text=_("Include part manufacturer data in exported BOM")) - - supplier_data = forms.BooleanField(label=_("Include Supplier Data"), required=False, initial=True, help_text=_("Include part supplier data in exported BOM")) - - def get_choices(self): - """ BOM export format choices """ - - return [(x, x.upper()) for x in GetExportFormats()] - - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - - self.fields['file_format'].choices = self.get_choices() - - class BomDuplicateForm(HelperForm): """ Simple confirmation form for BOM duplication. diff --git a/InvenTree/part/templates/part/detail.html b/InvenTree/part/templates/part/detail.html index f1b47bc4e2..d16de22e1b 100644 --- a/InvenTree/part/templates/part/detail.html +++ b/InvenTree/part/templates/part/detail.html @@ -620,13 +620,7 @@ }); $("#download-bom").click(function () { - launchModalForm("{% url 'bom-export' part.id %}", - { - success: function(response) { - location.href = response.url; - }, - } - ); + exportBom({{ part.id }}); }); {% if report_enabled %} diff --git a/InvenTree/part/views.py b/InvenTree/part/views.py index 6e742dc571..af35cf9c1f 100644 --- a/InvenTree/part/views.py +++ b/InvenTree/part/views.py @@ -1192,14 +1192,10 @@ class BomExport(AjaxView): """ model = Part - form_class = part_forms.BomExportForm ajax_form_title = _("Export Bill of Materials") role_required = 'part.view' - def get(self, request, *args, **kwargs): - return self.renderJsonResponse(request, self.form_class()) - def post(self, request, *args, **kwargs): # Extract POSTed form data diff --git a/InvenTree/templates/js/translated/bom.js b/InvenTree/templates/js/translated/bom.js index 29a958d452..f345db6976 100644 --- a/InvenTree/templates/js/translated/bom.js +++ b/InvenTree/templates/js/translated/bom.js @@ -16,6 +16,7 @@ /* exported downloadBomTemplate, + exportBom, newPartFromBomWizard, loadBomTable, loadUsedInTable, @@ -60,6 +61,86 @@ function downloadBomTemplate(options={}) { } +/** + * Export BOM (Bill of Materials) for the specified Part instance + */ +function exportBom(part_id, options={}) { + + constructFormBody({}, { + title: '{% trans "Export BOM" %}', + fields: { + format: { + label: '{% trans "Format" %}', + help_text: '{% trans "Select file format" %}', + required: true, + type: 'choice', + value: inventreeLoad('bom-export-format', 'csv'), + choices: exportFormatOptions(), + }, + cascading: { + label: '{% trans "Cascading" %}', + help_text: '{% trans "Download cascading / mmulti-level BOM" %}', + type: 'boolean', + value: inventreeLoad('bom-export-cascading', true), + }, + levels: { + label: '{% trans "Levels" %}', + help_text: '{% trans "Select maximum number of BOM levels to export (0 = all levels)" %}', + type: 'integer', + value: 0, + min_value: 0, + }, + parameter_data: { + label: '{% trans "Include Parameter Data" %}', + help_text: '{% trans "Include part parameter data in exported BOM" %}', + type: 'boolean', + value: inventreeLoad('bom-export-parameter_data', false), + }, + stock_data: { + label: '{% trans "Include Stock Data" %}', + help_text: '{% trans "Include part stock data in exported BOM" %}', + type: 'boolean', + value: inventreeLoad('bom-export-stock_data', false), + }, + manufacturer_data: { + label: '{% trans "Include Manufacturer Data" %}', + help_text: '{% trans "Include part manufacturer data in exported BOM" %}', + type: 'boolean', + value: inventreeLoad('bom-export-manufacturer_data', false), + }, + supplier_data: { + label: '{% trans "Include Supplier Data" %}', + help_text: '{% trans "Include part supplier data in exported BOM" %}', + type: 'boolean', + value: inventreeLoad('bom-export-supplier_data', false), + } + }, + onSubmit: function(fields, opts) { + + // Extract values from the form + var field_names = ['format', 'cascading', 'levels', 'parameter_data', 'stock_data', 'manufacturer_data', 'supplier_data']; + + var url = `/part/${part_id}/bom-download/?`; + + field_names.forEach(function(fn) { + var val = getFormFieldValue(fn, fields[fn], opts); + + // Update user preferences + inventreeSave(`bom-export-${fn}`, val); + + url += `${fn}=${val}&`; + }); + + $(opts.modal).modal('hide'); + + // Redirect to the BOM file download + location.href = url; + } + }); + +} + + function bomItemFields() { return { From 922b8b1c3078f599db00db5f3ad415589f1baad6 Mon Sep 17 00:00:00 2001 From: Oliver Date: Mon, 22 Nov 2021 22:17:13 +1100 Subject: [PATCH 50/58] Fix typo (cherry picked from commit 673a5779f9dd08aee3b821104e8c4bec3c83280e) --- InvenTree/templates/js/translated/bom.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/InvenTree/templates/js/translated/bom.js b/InvenTree/templates/js/translated/bom.js index f345db6976..ffd8195e07 100644 --- a/InvenTree/templates/js/translated/bom.js +++ b/InvenTree/templates/js/translated/bom.js @@ -79,7 +79,7 @@ function exportBom(part_id, options={}) { }, cascading: { label: '{% trans "Cascading" %}', - help_text: '{% trans "Download cascading / mmulti-level BOM" %}', + help_text: '{% trans "Download cascading / multi-level BOM" %}', type: 'boolean', value: inventreeLoad('bom-export-cascading', true), }, From 36026a9217bdbfe73fa0b71309f8af1175a34d27 Mon Sep 17 00:00:00 2001 From: Oliver Date: Mon, 13 Dec 2021 23:15:06 +1100 Subject: [PATCH 51/58] PEP fixes --- InvenTree/part/forms.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/InvenTree/part/forms.py b/InvenTree/part/forms.py index a5c4f87c89..2d9ae4dc30 100644 --- a/InvenTree/part/forms.py +++ b/InvenTree/part/forms.py @@ -11,7 +11,7 @@ from django.utils.translation import ugettext_lazy as _ from mptt.fields import TreeNodeChoiceField from InvenTree.forms import HelperForm -from InvenTree.helpers import GetExportFormats, clean_decimal +from InvenTree.helpers import clean_decimal from InvenTree.fields import RoundingDecimalFormField import common.models From 8d4f8204ca6f7551268728b3101a9f021fce46cf Mon Sep 17 00:00:00 2001 From: Oliver Date: Mon, 13 Dec 2021 23:48:30 +1100 Subject: [PATCH 52/58] call apt-get update in workflow scripts --- .github/workflows/qc_checks.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/qc_checks.yaml b/.github/workflows/qc_checks.yaml index 929a299e93..04489e360d 100644 --- a/.github/workflows/qc_checks.yaml +++ b/.github/workflows/qc_checks.yaml @@ -229,6 +229,7 @@ jobs: cache: 'pip' - name: Install Dependencies run: | + sudo apt-get update sudo apt-get install libpq-dev pip3 install invoke pip3 install psycopg2 @@ -282,6 +283,7 @@ jobs: cache: 'pip' - name: Install Dependencies run: | + sudo apt-get update sudo apt-get install mysql-server libmysqlclient-dev pip3 install invoke pip3 install mysqlclient From b6d9bc30931b462392b564cdf4ebedc84e519de8 Mon Sep 17 00:00:00 2001 From: sintech Date: Mon, 13 Dec 2021 15:51:34 +0300 Subject: [PATCH 53/58] Remove treeview state saving. --- InvenTree/templates/js/dynamic/nav.js | 20 ++------------------ 1 file changed, 2 insertions(+), 18 deletions(-) diff --git a/InvenTree/templates/js/dynamic/nav.js b/InvenTree/templates/js/dynamic/nav.js index b38dd17b32..03ed946677 100644 --- a/InvenTree/templates/js/dynamic/nav.js +++ b/InvenTree/templates/js/dynamic/nav.js @@ -218,7 +218,6 @@ function enableBreadcrumbTree(options) { collapseIcon: 'fa fa-chevron-down', }); - setBreadcrumbTreeState(label, state); } } ); @@ -226,26 +225,11 @@ function enableBreadcrumbTree(options) { $('#breadcrumb-tree-toggle').click(function() { // Add callback to "collapse" and "expand" the sidebar - // By default, the menu is "expanded" - var state = localStorage.getItem(`inventree-tree-state-${label}`) || 'expanded'; + // Toggle treeview visibilty + $('#breadcrumb-tree-collapse').toggle(); - // We wish to "toggle" the state! - setBreadcrumbTreeState(label, state == 'expanded' ? 'collapsed' : 'expanded'); }); - // Set the initial state (default = expanded) - var state = localStorage.getItem(`inventree-tree-state-${label}`) || 'expanded'; - - function setBreadcrumbTreeState(label, state) { - - if (state == 'collapsed') { - $('#breadcrumb-tree-collapse').hide(100); - } else { - $('#breadcrumb-tree-collapse').show(100); - } - - localStorage.setItem(`inventree-tree-state-${label}`, state); - } } /* From 967584b25a7fc4a8935c61a3a95abbbefdc9d1e2 Mon Sep 17 00:00:00 2001 From: Oliver Date: Tue, 14 Dec 2021 17:35:36 +1100 Subject: [PATCH 54/58] Specify mysql version --- .github/workflows/qc_checks.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/qc_checks.yaml b/.github/workflows/qc_checks.yaml index 04489e360d..41b00a785a 100644 --- a/.github/workflows/qc_checks.yaml +++ b/.github/workflows/qc_checks.yaml @@ -284,7 +284,7 @@ jobs: - name: Install Dependencies run: | sudo apt-get update - sudo apt-get install mysql-server libmysqlclient-dev + sudo apt-get install mysql-server-8.0 mysql-client-8.0 libmysqlclient-dev pip3 install invoke pip3 install mysqlclient invoke install From 58364bb3b537638fce4047f234b29d5f67c735ea Mon Sep 17 00:00:00 2001 From: sintech Date: Tue, 14 Dec 2021 11:30:09 +0300 Subject: [PATCH 55/58] JS style fix --- InvenTree/templates/js/dynamic/nav.js | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/InvenTree/templates/js/dynamic/nav.js b/InvenTree/templates/js/dynamic/nav.js index 03ed946677..eed098f162 100644 --- a/InvenTree/templates/js/dynamic/nav.js +++ b/InvenTree/templates/js/dynamic/nav.js @@ -192,17 +192,17 @@ function enableBreadcrumbTree(options) { node = data[i]; if (node.parent != null) { - if (nodes[node.parent].nodes) { - nodes[node.parent].nodes.push(node); - } else { - nodes[node.parent].nodes = [node]; - } + if (nodes[node.parent].nodes) { + nodes[node.parent].nodes.push(node); + } else { + nodes[node.parent].nodes = [node]; + } if (node.state.expanded) { - while (node.parent != null) { - nodes[node.parent].state.expanded = true; - node = nodes[node.parent] - } + while (node.parent != null) { + nodes[node.parent].state.expanded = true; + node = nodes[node.parent]; + } } } else { @@ -226,7 +226,7 @@ function enableBreadcrumbTree(options) { // Add callback to "collapse" and "expand" the sidebar // Toggle treeview visibilty - $('#breadcrumb-tree-collapse').toggle(); + $('#breadcrumb-tree-collapse').toggle(); }); From a9d09c7d2981d2bec8523ebb178ba57463d2c746 Mon Sep 17 00:00:00 2001 From: Oliver Date: Tue, 14 Dec 2021 19:56:34 +1100 Subject: [PATCH 56/58] Fix mysql installs for github actions --- .github/workflows/qc_checks.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/qc_checks.yaml b/.github/workflows/qc_checks.yaml index 41b00a785a..ba7a64f6d4 100644 --- a/.github/workflows/qc_checks.yaml +++ b/.github/workflows/qc_checks.yaml @@ -284,9 +284,9 @@ jobs: - name: Install Dependencies run: | sudo apt-get update - sudo apt-get install mysql-server-8.0 mysql-client-8.0 libmysqlclient-dev + sudo apt-get install mysql-server-8.0 mysql-client-8.0 default-libmysqlclient-dev pip3 install invoke - pip3 install mysqlclient + pip3 install mysqlclient==2.1.0 invoke install - name: Run Tests run: invoke test From 070d01b2f41e297daa22435a2b6be9bd6ab08e55 Mon Sep 17 00:00:00 2001 From: Oliver Date: Tue, 14 Dec 2021 22:48:44 +1100 Subject: [PATCH 57/58] Try with newer python --- .github/workflows/qc_checks.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/qc_checks.yaml b/.github/workflows/qc_checks.yaml index ba7a64f6d4..e0f4af3739 100644 --- a/.github/workflows/qc_checks.yaml +++ b/.github/workflows/qc_checks.yaml @@ -12,7 +12,7 @@ on: - l10* env: - python_version: 3.7 + python_version: 3.8 node_version: 16 server_start_sleep: 60 @@ -284,9 +284,9 @@ jobs: - name: Install Dependencies run: | sudo apt-get update - sudo apt-get install mysql-server-8.0 mysql-client-8.0 default-libmysqlclient-dev + sudo apt-get install mysql-server default-libmysqlclient-dev pip3 install invoke - pip3 install mysqlclient==2.1.0 + pip3 install mysqlclient invoke install - name: Run Tests run: invoke test From 4805540b4cd005b18f1936edc2dc6775e6792d9d Mon Sep 17 00:00:00 2001 From: Oliver Date: Tue, 14 Dec 2021 22:52:14 +1100 Subject: [PATCH 58/58] try simplifying --- .github/workflows/qc_checks.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/qc_checks.yaml b/.github/workflows/qc_checks.yaml index e0f4af3739..598f518f27 100644 --- a/.github/workflows/qc_checks.yaml +++ b/.github/workflows/qc_checks.yaml @@ -284,7 +284,7 @@ jobs: - name: Install Dependencies run: | sudo apt-get update - sudo apt-get install mysql-server default-libmysqlclient-dev + sudo apt-get install libmysqlclient-dev pip3 install invoke pip3 install mysqlclient invoke install