diff --git a/InvenTree/InvenTree/forms.py b/InvenTree/InvenTree/forms.py index 7112c2a88b..8b4b87637c 100644 --- a/InvenTree/InvenTree/forms.py +++ b/InvenTree/InvenTree/forms.py @@ -13,7 +13,12 @@ from crispy_forms.helper import FormHelper from crispy_forms.layout import Layout, Field from crispy_forms.bootstrap import PrependedText, AppendedText, PrependedAppendedText, StrictButton, Div +from allauth.account.forms import SignupForm, set_form_field_order +from allauth.account.adapter import DefaultAccountAdapter +from allauth.socialaccount.adapter import DefaultSocialAccountAdapter + from part.models import PartCategory +from common.models import InvenTreeSetting class HelperForm(forms.ModelForm): @@ -144,7 +149,6 @@ class EditUserForm(HelperForm): 'username', 'first_name', 'last_name', - 'email' ] @@ -204,3 +208,76 @@ class SettingCategorySelectForm(forms.ModelForm): css_class='row', ), ) + + +# override allauth +class CustomSignupForm(SignupForm): + """ + Override to use dynamic settings + """ + def __init__(self, *args, **kwargs): + kwargs['email_required'] = InvenTreeSetting.get_setting('LOGIN_MAIL_REQUIRED') + + super().__init__(*args, **kwargs) + + # check for two mail fields + if InvenTreeSetting.get_setting('LOGIN_SIGNUP_MAIL_TWICE'): + self.fields["email2"] = forms.EmailField( + label=_("E-mail (again)"), + widget=forms.TextInput( + attrs={ + "type": "email", + "placeholder": _("E-mail address confirmation"), + } + ), + ) + + # check for two password fields + if not InvenTreeSetting.get_setting('LOGIN_SIGNUP_PWD_TWICE'): + self.fields.pop("password2") + + # reorder fields + set_form_field_order(self, ["username", "email", "email2", "password1", "password2", ]) + + def clean(self): + cleaned_data = super().clean() + + # check for two mail fields + if InvenTreeSetting.get_setting('LOGIN_SIGNUP_MAIL_TWICE'): + email = cleaned_data.get("email") + email2 = cleaned_data.get("email2") + if (email and email2) and email != email2: + self.add_error("email2", _("You must type the same email each time.")) + + return cleaned_data + + +class RegistratonMixin: + """ + Mixin to check if registration should be enabled + """ + def is_open_for_signup(self, request): + if InvenTreeSetting.get_setting('EMAIL_HOST', None) and InvenTreeSetting.get_setting('LOGIN_ENABLE_REG', True): + return super().is_open_for_signup(request) + return False + + +class CustomAccountAdapter(RegistratonMixin, DefaultAccountAdapter): + """ + Override of adapter to use dynamic settings + """ + def send_mail(self, template_prefix, email, context): + """only send mail if backend configured""" + if InvenTreeSetting.get_setting('EMAIL_HOST', None): + return super().send_mail(template_prefix, email, context) + return False + + +class CustomSocialAccountAdapter(RegistratonMixin, DefaultSocialAccountAdapter): + """ + Override of adapter to use dynamic settings + """ + def is_auto_signup_allowed(self, request, sociallogin): + if InvenTreeSetting.get_setting('LOGIN_SIGNUP_SSO_AUTO', True): + return super().is_auto_signup_allowed(request, sociallogin) + return False diff --git a/InvenTree/InvenTree/middleware.py b/InvenTree/InvenTree/middleware.py index 3cd5aa74f7..2df90bc5b7 100644 --- a/InvenTree/InvenTree/middleware.py +++ b/InvenTree/InvenTree/middleware.py @@ -64,15 +64,15 @@ class AuthRequiredMiddleware(object): # No authorization was found for the request if not authorized: # A logout request will redirect the user to the login screen - if request.path_info == reverse_lazy('logout'): - return HttpResponseRedirect(reverse_lazy('login')) + if request.path_info == reverse_lazy('account_logout'): + return HttpResponseRedirect(reverse_lazy('account_login')) path = request.path_info # List of URL endpoints we *do not* want to redirect to urls = [ - reverse_lazy('login'), - reverse_lazy('logout'), + reverse_lazy('account_login'), + reverse_lazy('account_logout'), reverse_lazy('admin:login'), reverse_lazy('admin:logout'), ] @@ -80,7 +80,7 @@ class AuthRequiredMiddleware(object): if path not in urls and not path.startswith('/api/'): # Save the 'next' parameter to pass through to the login view - return redirect('%s?next=%s' % (reverse_lazy('login'), request.path)) + return redirect('%s?next=%s' % (reverse_lazy('account_login'), request.path)) response = self.get_response(request) diff --git a/InvenTree/InvenTree/settings.py b/InvenTree/InvenTree/settings.py index f3c166df88..a07324ec84 100644 --- a/InvenTree/InvenTree/settings.py +++ b/InvenTree/InvenTree/settings.py @@ -249,6 +249,7 @@ INSTALLED_APPS = [ 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', + 'django.contrib.sites', # InvenTree apps 'build.apps.BuildConfig', @@ -279,6 +280,10 @@ INSTALLED_APPS = [ 'error_report', # Error reporting in the admin interface 'django_q', 'formtools', # Form wizard tools + + 'allauth', # Base app for SSO + 'allauth.account', # Extend user with accounts + 'allauth.socialaccount', # Use 'social' providers ] MIDDLEWARE = CONFIG.get('middleware', [ @@ -298,7 +303,8 @@ MIDDLEWARE = CONFIG.get('middleware', [ MIDDLEWARE.append('error_report.middleware.ExceptionProcessor') AUTHENTICATION_BACKENDS = CONFIG.get('authentication_backends', [ - 'django.contrib.auth.backends.ModelBackend' + 'django.contrib.auth.backends.ModelBackend', + 'allauth.account.auth_backends.AuthenticationBackend', # SSO login via external providers ]) # If the debug toolbar is enabled, add the modules @@ -646,3 +652,34 @@ MESSAGE_TAGS = { messages.ERROR: 'alert alert-block alert-danger', messages.INFO: 'alert alert-block alert-info', } + +SITE_ID = 1 + +# Load the allauth social backends +SOCIAL_BACKENDS = CONFIG.get('social_backends', []) +for app in SOCIAL_BACKENDS: + INSTALLED_APPS.append(app) + +SOCIALACCOUNT_PROVIDERS = CONFIG.get('social_providers', []) + +# settings for allauth +ACCOUNT_EMAIL_CONFIRMATION_EXPIRE_DAYS = get_setting('INVENTREE_LOGIN_CONFIRM_DAYS', CONFIG.get('login_confirm_days', 3)) + +ACCOUNT_LOGIN_ATTEMPTS_LIMIT = get_setting('INVENTREE_LOGIN_ATTEMPTS', CONFIG.get('login_attempts', 5)) + +ACCOUNT_LOGOUT_ON_PASSWORD_CHANGE = True + +# override forms / adapters +ACCOUNT_FORMS = { + 'login': 'allauth.account.forms.LoginForm', + 'signup': 'InvenTree.forms.CustomSignupForm', + 'add_email': 'allauth.account.forms.AddEmailForm', + 'change_password': 'allauth.account.forms.ChangePasswordForm', + 'set_password': 'allauth.account.forms.SetPasswordForm', + 'reset_password': 'allauth.account.forms.ResetPasswordForm', + 'reset_password_from_key': 'allauth.account.forms.ResetPasswordKeyForm', + 'disconnect': 'allauth.socialaccount.forms.DisconnectForm', +} + +SOCIALACCOUNT_ADAPTER = 'InvenTree.forms.CustomSocialAccountAdapter' +ACCOUNT_ADAPTER = 'InvenTree.forms.CustomAccountAdapter' diff --git a/InvenTree/InvenTree/test_urls.py b/InvenTree/InvenTree/test_urls.py index 0723332d7d..042f43b6eb 100644 --- a/InvenTree/InvenTree/test_urls.py +++ b/InvenTree/InvenTree/test_urls.py @@ -111,6 +111,10 @@ class URLTest(TestCase): if url.startswith("admin:"): return + # TODO can this be more elegant? + if url.startswith("account_"): + return + if pk: # We will assume that there is at least one item in the database reverse(url, kwargs={"pk": 1}) diff --git a/InvenTree/InvenTree/urls.py b/InvenTree/InvenTree/urls.py index 7d51c6a4cf..77a0e06a0c 100644 --- a/InvenTree/InvenTree/urls.py +++ b/InvenTree/InvenTree/urls.py @@ -8,7 +8,6 @@ Passes URL lookup downstream to each app as required. from django.conf.urls import url, include from django.urls import path from django.contrib import admin -from django.contrib.auth import views as auth_views from company.urls import company_urls from company.urls import manufacturer_part_urls @@ -38,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 +from .views import SettingsView, EditUserView, SetPasswordView, CustomEmailView, CustomConnectionsView, CustomPasswordResetFromKeyView from .views import CurrencyRefreshView from .views import AppearanceSelectView, SettingCategorySelectView from .views import DynamicJsView @@ -143,9 +142,6 @@ urlpatterns = [ url(r'^auth/', include('rest_framework.urls', namespace='rest_framework')), - url(r'^login/?', auth_views.LoginView.as_view(), name='login'), - url(r'^logout/', auth_views.LogoutView.as_view(template_name='registration/logged_out.html'), name='logout'), - url(r'^settings/', include(settings_urls)), url(r'^edit-user/', EditUserView.as_view(), name='edit-user'), @@ -154,7 +150,6 @@ urlpatterns = [ 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'accounts/', include('django.contrib.auth.urls')), url(r'^index/', IndexView.as_view(), name='index'), url(r'^search/', SearchView.as_view(), name='search'), @@ -166,6 +161,13 @@ urlpatterns = [ 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/', include('allauth.urls')), # included urlpatterns ] # Server running in "DEBUG" mode? diff --git a/InvenTree/InvenTree/views.py b/InvenTree/InvenTree/views.py index 0528c6c694..af97877933 100644 --- a/InvenTree/InvenTree/views.py +++ b/InvenTree/InvenTree/views.py @@ -17,13 +17,19 @@ from django.urls import reverse_lazy from django.shortcuts import redirect from django.conf import settings -from django.contrib.auth.mixins import PermissionRequiredMixin +from django.contrib.auth.mixins import LoginRequiredMixin, PermissionRequiredMixin from django.views import View from django.views.generic import ListView, DetailView, CreateView, FormView, DeleteView, UpdateView from django.views.generic.base import RedirectView, TemplateView from djmoney.contrib.exchange.models import ExchangeBackend, Rate +from allauth.account.forms import AddEmailForm +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 common.settings import currency_code_default, currency_codes from part.models import Part, PartCategory @@ -810,9 +816,47 @@ class SettingsView(TemplateView): except: ctx["locale_stats"] = {} + # Forms and context for allauth + ctx['add_email_form'] = AddEmailForm + ctx["can_add_email"] = EmailAddress.objects.can_add_email(self.request.user) + + # Form and context for allauth social-accounts + ctx["request"] = self.request + ctx['social_form'] = DisconnectForm(request=self.request) + return ctx +class AllauthOverrides(LoginRequiredMixin): + """ + Override allauths views to always redirect to success_url + """ + def get(self, request, *args, **kwargs): + # always redirect to settings + return HttpResponseRedirect(self.success_url) + + +class CustomEmailView(AllauthOverrides, EmailView): + """ + Override of allauths EmailView to always show the settings but leave the functions allow + """ + success_url = reverse_lazy("settings") + + +class CustomConnectionsView(AllauthOverrides, ConnectionsView): + """ + Override of allauths ConnectionsView to always show the settings but leave the functions allow + """ + success_url = reverse_lazy("settings") + + +class CustomPasswordResetFromKeyView(PasswordResetFromKeyView): + """ + Override of allauths PasswordResetFromKeyView to always show the settings but leave the functions allow + """ + success_url = reverse_lazy("account_login") + + class CurrencyRefreshView(RedirectView): """ POST endpoint to refresh / update exchange rates diff --git a/InvenTree/common/models.py b/InvenTree/common/models.py index 49d8ada561..b9b7d9e20d 100644 --- a/InvenTree/common/models.py +++ b/InvenTree/common/models.py @@ -830,6 +830,50 @@ class InvenTreeSetting(BaseInvenTreeSetting): 'default': True, 'validator': bool, }, + + # login / SSO + 'LOGIN_ENABLE_PWD_FORGOT': { + 'name': _('Enable password forgot'), + 'description': _('Enable password forgot function on the login-pages'), + 'default': True, + 'validator': bool, + }, + 'LOGIN_ENABLE_REG': { + 'name': _('Enable registration'), + 'description': _('Enable self-registration for users on the login-pages'), + 'default': False, + 'validator': bool, + }, + 'LOGIN_ENABLE_SSO': { + 'name': _('Enable SSO'), + 'description': _('Enable SSO on the login-pages'), + 'default': False, + 'validator': bool, + }, + 'LOGIN_MAIL_REQUIRED': { + 'name': _('E-Mail required'), + 'description': _('Require user to supply mail on signup'), + 'default': False, + 'validator': bool, + }, + 'LOGIN_SIGNUP_SSO_AUTO': { + 'name': _('Auto-fill SSO users'), + 'description': _('Automatically fill out user-details from SSO account-data'), + 'default': True, + 'validator': bool, + }, + 'LOGIN_SIGNUP_MAIL_TWICE': { + 'name': _('Mail twice'), + 'description': _('On signup ask users twice for their mail'), + 'default': False, + 'validator': bool, + }, + 'LOGIN_SIGNUP_PWD_TWICE': { + 'name': _('Password twice'), + 'description': _('On signup ask users twice for their password'), + 'default': True, + 'validator': bool, + }, } class Meta: diff --git a/InvenTree/config_template.yaml b/InvenTree/config_template.yaml index 0e6232d270..3472a37d8e 100644 --- a/InvenTree/config_template.yaml +++ b/InvenTree/config_template.yaml @@ -141,6 +141,14 @@ static_root: '/home/inventree/data/static' # - git # - ssh +# Login configuration +# How long do confirmation mail last? +# Use environment variable INVENTREE_LOGIN_CONFIRM_DAYS +#login_confirm_days: 3 +# How many wrong login attempts are permitted? +# Use environment variable INVENTREE_LOGIN_ATTEMPTS +#login_attempts: 5 + # Permit custom authentication backends #authentication_backends: # - 'django.contrib.auth.backends.ModelBackend' @@ -157,3 +165,14 @@ static_root: '/home/inventree/data/static' # - 'django.contrib.messages.middleware.MessageMiddleware' # - 'django.middleware.clickjacking.XFrameOptionsMiddleware' # - 'InvenTree.middleware.AuthRequiredMiddleware' + +# Add SSO login-backends +# social_backends: +# - 'allauth.socialaccount.providers.keycloak' + +# Add specific settings +# social_providers: +# keycloak: +# KEYCLOAK_URL: 'https://keycloak.custom/auth' +# KEYCLOAK_REALM: 'master' + diff --git a/InvenTree/part/templatetags/inventree_extras.py b/InvenTree/part/templatetags/inventree_extras.py index ce4ead853a..dab7be6eb9 100644 --- a/InvenTree/part/templatetags/inventree_extras.py +++ b/InvenTree/part/templatetags/inventree_extras.py @@ -351,6 +351,12 @@ def object_link(url_name, pk, ref): return mark_safe('{}'.format(ref_url, ref)) +@register.simple_tag() +def mail_configured(): + """ Return if mail is configured """ + return bool(settings.EMAIL_HOST) + + class I18nStaticNode(StaticNode): """ custom StaticNode diff --git a/InvenTree/templates/InvenTree/settings/login.html b/InvenTree/templates/InvenTree/settings/login.html new file mode 100644 index 0000000000..289f87a3c9 --- /dev/null +++ b/InvenTree/templates/InvenTree/settings/login.html @@ -0,0 +1,31 @@ +{% extends "panel.html" %} +{% load i18n %} +{% load inventree_extras %} + +{% block label %}login{% endblock %} + + +{% block heading %} +{% trans "Login Settings" %} +{% endblock %} + +{% block content %} + + + {% include "InvenTree/settings/header.html" %} + + {% include "InvenTree/settings/setting.html" with key="LOGIN_ENABLE_REG" icon="fa-info-circle" %} + {% 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_SIGNUP_MAIL_TWICE" icon="fa-info-circle" %} + {% include "InvenTree/settings/setting.html" with key="LOGIN_SIGNUP_PWD_TWICE" icon="fa-info-circle" %} + {% include "InvenTree/settings/setting.html" with key="LOGIN_SIGNUP_SSO_AUTO" icon="fa-info-circle" %} + +
{% trans 'Signup' %}
+ +{% endblock %} diff --git a/InvenTree/templates/InvenTree/settings/navbar.html b/InvenTree/templates/InvenTree/settings/navbar.html index 095e616f5d..58673d6618 100644 --- a/InvenTree/templates/InvenTree/settings/navbar.html +++ b/InvenTree/templates/InvenTree/settings/navbar.html @@ -68,6 +68,12 @@ +
  • + + {% trans "Login" %} + +
  • +
  • {% trans "Barcodes" %} diff --git a/InvenTree/templates/InvenTree/settings/settings.html b/InvenTree/templates/InvenTree/settings/settings.html index 50ed436fd6..cdc1279620 100644 --- a/InvenTree/templates/InvenTree/settings/settings.html +++ b/InvenTree/templates/InvenTree/settings/settings.html @@ -25,6 +25,7 @@ {% if user.is_staff %} {% include "InvenTree/settings/global.html" %} +{% include "InvenTree/settings/login.html" %} {% include "InvenTree/settings/barcode.html" %} {% include "InvenTree/settings/currencies.html" %} {% include "InvenTree/settings/report.html" %} diff --git a/InvenTree/templates/InvenTree/settings/user.html b/InvenTree/templates/InvenTree/settings/user.html index 922e9ebc79..569b218b43 100644 --- a/InvenTree/templates/InvenTree/settings/user.html +++ b/InvenTree/templates/InvenTree/settings/user.html @@ -2,6 +2,8 @@ {% load i18n %} {% load inventree_extras %} +{% load socialaccount %} +{% load crispy_forms_tags %} {% block label %}account{% endblock %} @@ -10,6 +12,8 @@ {% endblock %} {% block content %} +{% mail_configured as mail_conf %} +
    {% trans "Edit" %} @@ -32,12 +36,119 @@ {% trans "Last Name" %} {{ user.last_name }} - - {% trans "Email Address" %} - {{ user.email }} - +
    +

    {% trans "E-Mail" %}

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

    {% trans 'The following e-mail addresses are associated with your account:' %}

    + + + + {% else %} +

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

    + + {% endif %} + + {% if can_add_email %} +
    +

    {% trans "Add E-mail Address" %}

    + +
    + {% csrf_token %} + {{ 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" %} +
    + +
    + +

    {% trans "Theme Settings" %}

    @@ -105,4 +216,18 @@
    +{% endblock %} + +{% block js_ready %} +(function() { + var message = "{% trans 'Do you really want to remove the selected e-mail 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 diff --git a/InvenTree/templates/registration/password_reset_form.html b/InvenTree/templates/account/base.html similarity index 52% rename from InvenTree/templates/registration/password_reset_form.html rename to InvenTree/templates/account/base.html index 865a74ca1a..97ccec4bc5 100644 --- a/InvenTree/templates/registration/password_reset_form.html +++ b/InvenTree/templates/account/base.html @@ -1,6 +1,5 @@ {% load static %} {% load i18n %} -{% load crispy_forms_tags %} {% load inventree_extras %} @@ -12,57 +11,79 @@ + + - - - - - + - - {% inventree_title %} + {% inventree_title %} | {% block head_title %}{% endblock %} + +{% block extra_head %} +{% endblock %} + - + {% block extra_body %} + {% endblock %} - \ No newline at end of file + {% include 'notification.html' %} + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/InvenTree/templates/account/email_confirm.html b/InvenTree/templates/account/email_confirm.html new file mode 100644 index 0000000000..12b041f710 --- /dev/null +++ b/InvenTree/templates/account/email_confirm.html @@ -0,0 +1,31 @@ +{% extends "account/base.html" %} + +{% load i18n %} +{% load account %} + +{% block head_title %}{% trans "Confirm E-mail Address" %}{% endblock %} + + +{% block content %} +

    {% trans "Confirm E-mail Address" %}

    + +{% if confirmation %} + +{% user_display confirmation.email_address.user as user_display %} + +

    {% blocktrans with confirmation.email_address.email as email %}Please confirm that {{ email }} is an e-mail address for user {{ user_display }}.{% endblocktrans %}

    + +
    +{% csrf_token %} + +
    + +{% else %} + +{% url 'account_email' as email_url %} + +

    {% blocktrans %}This e-mail confirmation link expired or is invalid. Please issue a new e-mail confirmation request.{% endblocktrans %}

    + +{% endif %} + +{% endblock %} diff --git a/InvenTree/templates/account/login.html b/InvenTree/templates/account/login.html new file mode 100644 index 0000000000..d925867eb7 --- /dev/null +++ b/InvenTree/templates/account/login.html @@ -0,0 +1,52 @@ +{% extends "account/base.html" %} + +{% load i18n account socialaccount crispy_forms_tags inventree_extras %} + +{% block head_title %}{% trans "Sign In" %}{% endblock %} + +{% block content %} + +{% settings_value 'LOGIN_ENABLE_REG' as enable_reg %} +{% settings_value 'LOGIN_ENABLE_PWD_FORGOT' as enable_pwd_forgot %} +{% settings_value 'LOGIN_ENABLE_SSO' as enable_sso %} +{% mail_configured as mail_conf %} + +

    {% trans "Sign In" %}

    + +{% if enable_reg %} +{% get_providers as socialaccount_providers %} +{% if socialaccount_providers %} +

    {% blocktrans with site.name as site_name %}Please sign in with one +of your existing third party accounts or sign up +for a account and sign in below:{% endblocktrans %}

    +{% else %} +

    {% blocktrans %}If you have not created an account yet, then please +sign up first.{% endblocktrans %}

    +{% endif %} +{% endif %} + + + +{% if enable_sso %} +
    +

    {% trans 'or use SSO' %}

    +
    + {% include "socialaccount/snippets/provider_list.html" with process="login" %} +
    +{% include "socialaccount/snippets/login_extra.html" %} +{% endif %} + +{% endblock %} diff --git a/InvenTree/templates/account/logout.html b/InvenTree/templates/account/logout.html new file mode 100644 index 0000000000..58d32e4ab8 --- /dev/null +++ b/InvenTree/templates/account/logout.html @@ -0,0 +1,21 @@ +{% extends "account/base.html" %} + +{% load i18n %} + +{% block head_title %}{% trans "Sign Out" %}{% endblock %} + +{% block content %} +

    {% trans "Sign Out" %}

    + +

    {% trans 'Are you sure you want to sign out?' %}

    + +
    + {% csrf_token %} + {% if redirect_field_value %} + + {% endif %} + +
    + + +{% endblock %} diff --git a/InvenTree/templates/account/password_reset.html b/InvenTree/templates/account/password_reset.html new file mode 100644 index 0000000000..2cfb45a716 --- /dev/null +++ b/InvenTree/templates/account/password_reset.html @@ -0,0 +1,30 @@ +{% extends "account/base.html" %} + +{% load i18n account crispy_forms_tags inventree_extras %} + +{% block head_title %}{% trans "Password Reset" %}{% endblock %} + +{% block content %} + +{% settings_value 'LOGIN_ENABLE_PWD_FORGOT' as enable_pwd_forgot %} +{% mail_configured as mail_conf %} + +

    {% trans "Password Reset" %}

    + {% if user.is_authenticated %} + {% include "account/snippets/already_logged_in.html" %} + {% endif %} + + {% if mail_conf and enable_pwd_forgot %} +

    {% trans "Forgotten your password? Enter your e-mail address below, and we'll send you an e-mail allowing you to reset it." %}

    + +
    + {% csrf_token %} + {{ form|crispy }} + +
    + {% else %} +
    +

    {% trans "This function is currently disabled. Please contact an administrator." %}

    +
    + {% endif %} +{% endblock %} diff --git a/InvenTree/templates/account/password_reset_from_key.html b/InvenTree/templates/account/password_reset_from_key.html new file mode 100644 index 0000000000..f9fa339983 --- /dev/null +++ b/InvenTree/templates/account/password_reset_from_key.html @@ -0,0 +1,23 @@ +{% extends "account/base.html" %} + +{% load i18n crispy_forms_tags %} +{% block head_title %}{% trans "Change Password" %}{% endblock %} + +{% block content %} +

    {% if token_fail %}{% trans "Bad Token" %}{% else %}{% trans "Change Password" %}{% endif %}

    + + {% if token_fail %} + {% url 'account_reset_password' as passwd_reset_url %} +

    {% blocktrans %}The password reset link was invalid, possibly because it has already been used. Please request a new password reset.{% endblocktrans %}

    + {% else %} + {% if form %} +
    + {% csrf_token %} + {{ form|crispy }} + +
    + {% else %} +

    {% trans 'Your password is now changed.' %}

    + {% endif %} + {% endif %} +{% endblock %} diff --git a/InvenTree/templates/account/signup.html b/InvenTree/templates/account/signup.html new file mode 100644 index 0000000000..f2972ba30b --- /dev/null +++ b/InvenTree/templates/account/signup.html @@ -0,0 +1,40 @@ +{% extends "account/base.html" %} + +{% load i18n crispy_forms_tags inventree_extras %} + +{% block head_title %}{% trans "Signup" %}{% endblock %} + +{% block content %} +{% settings_value 'LOGIN_ENABLE_REG' as enable_reg %} +{% settings_value 'LOGIN_ENABLE_SSO' as enable_sso %} + +

    {% trans "Sign Up" %}

    + +

    {% blocktrans %}Already have an account? Then please sign in.{% endblocktrans %}

    + +{% if enable_reg %} + + +{% if enable_sso %} +
    +

    {% trans 'Or use a SSO-provider for signup' %}

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

    {% trans "This function is currently disabled. Please contact an administrator." %}

    +
    +{% endif %} + +{% endblock %} diff --git a/InvenTree/templates/js/dynamic/inventree.js b/InvenTree/templates/js/dynamic/inventree.js index 1821c5ef70..0324d72e3c 100644 --- a/InvenTree/templates/js/dynamic/inventree.js +++ b/InvenTree/templates/js/dynamic/inventree.js @@ -177,6 +177,11 @@ function inventreeDocReady() { 'ui-autocomplete': 'dropdown-menu search-menu', }, }); + + // Generate brand-icons + $('.brand-icon').each(function(i, obj) { + loadBrandIcon($(this), $(this).attr('brand_name')); + }); } function isFileTransfer(transfer) { @@ -275,3 +280,13 @@ function inventreeLoad(name, defaultValue) { return value; } } + +function loadBrandIcon(element, name) { + // check if icon exists + var icon = window.FontAwesome.icon({prefix: 'fab', iconName: name}); + + if (icon) { + // add icon to button + element.addClass('fab fa-' + name); + } +} diff --git a/InvenTree/templates/navbar.html b/InvenTree/templates/navbar.html index 6946b69bce..3fae5cf0a0 100644 --- a/InvenTree/templates/navbar.html +++ b/InvenTree/templates/navbar.html @@ -82,9 +82,9 @@ {% if user.is_staff %}
  • {% trans "Admin" %}
  • {% endif %} -
  • {% trans "Logout" %}
  • +
  • {% trans "Logout" %}
  • {% else %} -
  • {% trans "Login" %}
  • +
  • {% trans "Login" %}
  • {% endif %}
  • {% trans "Settings" %}
  • diff --git a/InvenTree/templates/registration/logged_out.html b/InvenTree/templates/registration/logged_out.html index 703e077f35..8b018fa7b4 100644 --- a/InvenTree/templates/registration/logged_out.html +++ b/InvenTree/templates/registration/logged_out.html @@ -1,59 +1,10 @@ -{% load static %} +{% extends "registration/logged_out.html" %} {% load i18n %} -{% load crispy_forms_tags %} -{% load inventree_extras %} - - - +{% block content %} - - - +

    {% translate "You were logged out successfully." %}

    - - - - - - +

    {% translate 'Log in again' %}

    - - - - - - - - {% inventree_title %} - - - - - - - - \ No newline at end of file +{% endblock %} diff --git a/InvenTree/templates/registration/login.html b/InvenTree/templates/registration/login.html deleted file mode 100644 index 7ea6668958..0000000000 --- a/InvenTree/templates/registration/login.html +++ /dev/null @@ -1,105 +0,0 @@ -{% load static %} -{% load i18n %} -{% load inventree_extras %} - - - - - - - - - - - - - - - - - - - - - - - - {% inventree_title %} - - - - - - - - - - \ No newline at end of file diff --git a/InvenTree/templates/registration/password_reset_complete.html b/InvenTree/templates/registration/password_reset_complete.html deleted file mode 100644 index f332e23d0e..0000000000 --- a/InvenTree/templates/registration/password_reset_complete.html +++ /dev/null @@ -1,59 +0,0 @@ -{% load static %} -{% load i18n %} -{% load crispy_forms_tags %} -{% load inventree_extras %} - - - - - - - - - - - - - - - - - - - - - - - - {% inventree_title %} - - - - - - - - \ No newline at end of file diff --git a/InvenTree/templates/registration/password_reset_confirm.html b/InvenTree/templates/registration/password_reset_confirm.html deleted file mode 100644 index cede63f770..0000000000 --- a/InvenTree/templates/registration/password_reset_confirm.html +++ /dev/null @@ -1,69 +0,0 @@ -{% load static %} -{% load i18n %} -{% load crispy_forms_tags %} -{% load inventree_extras %} - - - - - - - - - - - - - - - - - - - - - - - - {% inventree_title %} - - - - - - - - \ No newline at end of file diff --git a/InvenTree/templates/registration/password_reset_done.html b/InvenTree/templates/registration/password_reset_done.html deleted file mode 100644 index a097e518dd..0000000000 --- a/InvenTree/templates/registration/password_reset_done.html +++ /dev/null @@ -1,65 +0,0 @@ -{% load static %} -{% load i18n %} -{% load crispy_forms_tags %} -{% load inventree_extras %} - - - - - - - - - - - - - - - - - - - - - - - - {% inventree_title %} - - - - - - - - \ No newline at end of file diff --git a/InvenTree/templates/socialaccount/snippets/provider_list.html b/InvenTree/templates/socialaccount/snippets/provider_list.html new file mode 100644 index 0000000000..268134b35f --- /dev/null +++ b/InvenTree/templates/socialaccount/snippets/provider_list.html @@ -0,0 +1,17 @@ +{% load socialaccount %} + +{% get_providers as socialaccount_providers %} + +{% for provider in socialaccount_providers %} +{% if provider.id == "openid" %} +{% for brand in provider.get_brands %} + {{brand.name}} +{% endfor %} +{% endif %} + {{provider.name}} +{% endfor %} diff --git a/InvenTree/users/models.py b/InvenTree/users/models.py index 179a70ed74..8a417a050d 100644 --- a/InvenTree/users/models.py +++ b/InvenTree/users/models.py @@ -67,7 +67,12 @@ class RuleSet(models.Model): 'report_billofmaterialsreport', 'report_purchaseorderreport', 'report_salesorderreport', - + 'account_emailaddress', + 'account_emailconfirmation', + 'sites_site', + 'socialaccount_socialaccount', + 'socialaccount_socialapp', + 'socialaccount_socialtoken', ], 'part_category': [ 'part_partcategory', diff --git a/docker/Dockerfile b/docker/Dockerfile index e4ebbc1b4b..f2aa590ad1 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -68,7 +68,9 @@ RUN apk add --no-cache git make bash \ # PostgreSQL support postgresql postgresql-contrib postgresql-dev libpq \ # MySQL/MariaDB support - mariadb-connector-c mariadb-dev mariadb-client + mariadb-connector-c mariadb-dev mariadb-client \ + # Required for python cryptography support + rust cargo # Install required base-level python packages COPY requirements.txt requirements.txt diff --git a/requirements.txt b/requirements.txt index 68eb6797e5..24786cbd7a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -35,5 +35,6 @@ python-barcode[images]==0.13.1 # Barcode generator qrcode[pil]==6.1 # QR code generator django-q==1.3.4 # Background task scheduling django-formtools==2.3 # Form wizard tools +django-allauth==0.45.0 # SSO for external providers via OpenID inventree # Install the latest version of the InvenTree API python library