SSO tweaks (#4169)

* Slight tweaks to login settings page

* Cleanup login screen

* Custom socialaccount connect page

- Better rendering, just looks nicer etc

* Add custom account templates

- signup_closed
- social signup

* Catch potential email errors when signing up new user

* Add custom template for authentication error

* Bug fix for account base.html
This commit is contained in:
Oliver 2023-01-08 07:51:16 +11:00 committed by GitHub
parent 82bdd7780d
commit 41318e4056
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 198 additions and 35 deletions

View File

@ -3,11 +3,13 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from __future__ import unicode_literals from __future__ import unicode_literals
import logging
import sys import sys
import traceback import traceback
from django.conf import settings from django.conf import settings
from django.core.exceptions import ValidationError as DjangoValidationError from django.core.exceptions import ValidationError as DjangoValidationError
from django.db.utils import IntegrityError, OperationalError
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
import rest_framework.views as drfviews import rest_framework.views as drfviews
@ -16,6 +18,8 @@ from rest_framework import serializers
from rest_framework.exceptions import ValidationError as DRFValidationError from rest_framework.exceptions import ValidationError as DRFValidationError
from rest_framework.response import Response from rest_framework.response import Response
logger = logging.getLogger('inventree')
def log_error(path): def log_error(path):
"""Log an error to the database. """Log an error to the database.
@ -32,12 +36,19 @@ def log_error(path):
if kind in settings.IGNORED_ERRORS: if kind in settings.IGNORED_ERRORS:
return return
Error.objects.create( # Log error to stderr
kind=kind.__name__, logger.error(info)
info=info,
data='\n'.join(traceback.format_exception(kind, info, data)), try:
path=path, Error.objects.create(
) kind=kind.__name__,
info=info,
data='\n'.join(traceback.format_exception(kind, info, data)),
path=path,
)
except (OperationalError, IntegrityError):
# Not much we can do if logging the error throws a db exception
pass
def exception_handler(exc, context): def exception_handler(exc, context):

View File

@ -23,6 +23,7 @@ from crispy_forms.helper import FormHelper
from crispy_forms.layout import Field, Layout from crispy_forms.layout import Field, Layout
from common.models import InvenTreeSetting from common.models import InvenTreeSetting
from InvenTree.exceptions import log_error
logger = logging.getLogger('inventree') logger = logging.getLogger('inventree')
@ -239,10 +240,20 @@ class CustomUrlMixin:
class CustomAccountAdapter(CustomUrlMixin, RegistratonMixin, OTPAdapter, DefaultAccountAdapter): class CustomAccountAdapter(CustomUrlMixin, RegistratonMixin, OTPAdapter, DefaultAccountAdapter):
"""Override of adapter to use dynamic settings.""" """Override of adapter to use dynamic settings."""
def send_mail(self, template_prefix, email, context): def send_mail(self, template_prefix, email, context):
"""Only send mail if backend configured.""" """Only send mail if backend configured."""
if settings.EMAIL_HOST: if settings.EMAIL_HOST:
return super().send_mail(template_prefix, email, context) try:
result = super().send_mail(template_prefix, email, context)
except Exception:
# An exception ocurred while attempting to send email
# Log it (for admin users) and return silently
log_error('account email')
result = False
return result
return False return False

View File

@ -1070,3 +1070,24 @@ a {
margin-left: 0.25rem; margin-left: 0.25rem;
margin-right: 0.25rem; margin-right: 0.25rem;
} }
.sso-header {
text-align: center;
width: 100%;
padding-bottom: 10px;
}
.sso-provider-list {
width: 100%;
list-style-type: none;
padding-left: 0px;
}
.sso-provider-link {
width: 100%;
}
.sso-provider-link a {
width: 100%;
text-align: left;
}

View File

@ -11,9 +11,14 @@
{% block content %} {% block content %}
{% if not email_configured %}
<div class='alert alert-block alert-danger'>
{% trans "Outgoing email has not been configured. Some login and sign-up features may not work correctly!" %}
</div>
{% endif %}
<table class='table table-striped table-condensed'> <table class='table table-striped table-condensed'>
<tbody> <tbody>
{% include "InvenTree/settings/setting.html" with key="LOGIN_ENABLE_SSO" icon="fa-user-shield" %}
{% include "InvenTree/settings/setting.html" with key="LOGIN_ENABLE_PWD_FORGOT" icon="fa-user-lock" %} {% include "InvenTree/settings/setting.html" with key="LOGIN_ENABLE_PWD_FORGOT" icon="fa-user-lock" %}
{% include "InvenTree/settings/setting.html" with key="LOGIN_MAIL_REQUIRED" icon="fa-at" %} {% include "InvenTree/settings/setting.html" with key="LOGIN_MAIL_REQUIRED" icon="fa-at" %}
{% include "InvenTree/settings/setting.html" with key="LOGIN_ENFORCE_MFA" icon='fa-key' %} {% include "InvenTree/settings/setting.html" with key="LOGIN_ENFORCE_MFA" icon='fa-key' %}
@ -24,8 +29,13 @@
{% include "InvenTree/settings/setting.html" with key="LOGIN_ENABLE_REG" icon="fa-user-plus" %} {% include "InvenTree/settings/setting.html" with key="LOGIN_ENABLE_REG" icon="fa-user-plus" %}
{% include "InvenTree/settings/setting.html" with key="LOGIN_SIGNUP_MAIL_TWICE" icon="fa-at" %} {% include "InvenTree/settings/setting.html" with key="LOGIN_SIGNUP_MAIL_TWICE" icon="fa-at" %}
{% include "InvenTree/settings/setting.html" with key="LOGIN_SIGNUP_PWD_TWICE" icon="fa-user-lock" %} {% include "InvenTree/settings/setting.html" with key="LOGIN_SIGNUP_PWD_TWICE" icon="fa-user-lock" %}
{% include "InvenTree/settings/setting.html" with key="LOGIN_SIGNUP_SSO_AUTO" icon="fa-key" %}
{% include "InvenTree/settings/setting.html" with key="SIGNUP_GROUP" icon="fa-users" %} {% include "InvenTree/settings/setting.html" with key="SIGNUP_GROUP" icon="fa-users" %}
<tr>
<th><h5>{% trans 'Single Sign On' %}</h5></th>
<td colspan='4'></td>
</tr>
{% include "InvenTree/settings/setting.html" with key="LOGIN_ENABLE_SSO" icon="fa-user-shield" %}
{% include "InvenTree/settings/setting.html" with key="LOGIN_SIGNUP_SSO_AUTO" icon="fa-key" %}
</tbody> </tbody>
</table> </table>

View File

@ -76,6 +76,7 @@
{% endblock %} {% endblock %}
</div> </div>
</div> </div>
</div> </div>
{% block extra_body %} {% block extra_body %}
@ -87,6 +88,7 @@
<!-- general JS --> <!-- general JS -->
{% include "third_party_js.html" %} {% include "third_party_js.html" %}
<script type='text/javascript' src='{% static "script/inventree/inventree.js" %}'></script>
<script type='text/javascript' src='{% static "script/inventree/message.js" %}'></script> <script type='text/javascript' src='{% static "script/inventree/message.js" %}'></script>
<script type='text/javascript'> <script type='text/javascript'>
@ -95,7 +97,7 @@ $(document).ready(function () {
{% if messages %} {% if messages %}
{% for message in messages %} {% for message in messages %}
showMessage(messsage); showMessage("{{ messsage }}");
{% endfor %} {% endfor %}
{% endif %} {% endif %}
@ -109,7 +111,7 @@ $(document).ready(function () {
var icon = window.FontAwesome.icon({prefix: 'fab', iconName: tag}); var icon = window.FontAwesome.icon({prefix: 'fab', iconName: tag});
if (icon) { if (icon) {
el.append(`&nbsp;<span class='fab fa-${tag}'></span>`); el.prepend(`<span class='fab fa-${tag}'></span>&nbsp;`);
} }
}); });

View File

@ -13,19 +13,18 @@
{% inventree_customize 'login_message' as login_message %} {% inventree_customize 'login_message' as login_message %}
{% mail_configured as mail_conf %} {% mail_configured as mail_conf %}
<h1>{% trans "Sign In" %}</h1> <div class='d-flex flex-wrap'>
<h3>{% trans "Sign In" %}</h3>
{% if enable_reg %} {% include "spacer.html" %}
{% get_providers as socialaccount_providers %} {% if enable_reg %}
{% if socialaccount_providers %} <div class='float-right'>
<p>{% blocktrans with site.name as site_name %}Please sign in with one {% trans "Not a member?" %}&nbsp;
of your existing third party accounts or <a class="btn btn-primary btn-small" href="{{ signup_url }}">sign up</a> <a class="btn btn-primary btn-small" href="{{ signup_url }}">
for a account and sign in below:{% endblocktrans %}</p> <span class='fas fa-user-plus'></span>&nbsp;{% trans "Sign Up" %}
{% else %} </a>
<p>{% blocktrans %}If you have not created an account yet, then please </div>
<a href="{{ signup_url }}">sign up</a> first.{% endblocktrans %}</p> {% endif %}
{% endif %} </div>
{% endif %}
<form class="login" method="POST" action="{% url 'account_login' %}"> <form class="login" method="POST" action="{% url 'account_login' %}">
{% csrf_token %} {% csrf_token %}
@ -34,15 +33,14 @@ for a account and sign in below:{% endblocktrans %}</p>
<input type="hidden" name="{{ redirect_field_name }}" value="{{ redirect_field_value }}" /> <input type="hidden" name="{{ redirect_field_name }}" value="{{ redirect_field_value }}" />
{% endif %} {% endif %}
<hr> <div class="btn-group" role="group" style='width: 100%;'>
{% if login_message %}
<div>{{ login_message | safe }}<hr></div>
{% endif %}
<div class="btn-group" role="group">
<button class="btn btn-success" type="submit"> <button class="btn btn-success" type="submit">
<span class='fas fa-sign-in-alt'></span> {% trans "Sign In" %} <span class='fas fa-sign-in-alt'></span> {% trans "Sign In" %}
</button> </button>
</div> </div>
{% if login_message %}
<div>{{ login_message | safe }}<hr></div>
{% endif %}
{% if mail_conf and enable_pwd_forgot %} {% if mail_conf and enable_pwd_forgot %}
<a class="" href="{% url 'account_reset_password' %}"><small>{% trans "Forgot Password?" %}</small></a> <a class="" href="{% url 'account_reset_password' %}"><small>{% trans "Forgot Password?" %}</small></a>
{% endif %} {% endif %}
@ -50,10 +48,15 @@ for a account and sign in below:{% endblocktrans %}</p>
{% if enable_sso %} {% if enable_sso %}
<hr> <hr>
<span class='float-right'>{% trans "Sign in using third-party SSO" %}</span>
<div class='sso-header'>
<em>{% trans "or log in with" %}</em>
</div>
<div> <div>
{% include "socialaccount/snippets/provider_list.html" with process="login" %} {% include "socialaccount/snippets/provider_list.html" with process="login" %}
</div> </div>
{% include "socialaccount/snippets/login_extra.html" %} {% include "socialaccount/snippets/login_extra.html" %}
{% endif %} {% endif %}

View File

@ -8,7 +8,7 @@
{% settings_value 'LOGIN_ENABLE_REG' as enable_reg %} {% settings_value 'LOGIN_ENABLE_REG' as enable_reg %}
{% settings_value 'LOGIN_ENABLE_SSO' as enable_sso %} {% settings_value 'LOGIN_ENABLE_SSO' as enable_sso %}
<h1>{% trans "Sign Up" %}</h1> <h3>{% trans "Sign Up" %}</h3>
<p>{% blocktrans %}Already have an account? Then please <a href="{{ login_url }}">sign in</a>.{% endblocktrans %}</p> <p>{% blocktrans %}Already have an account? Then please <a href="{{ login_url }}">sign in</a>.{% endblocktrans %}</p>

View File

@ -0,0 +1,19 @@
{% extends "account/base.html" %}
{% load i18n %}
{% block head_title %}{% trans "Sign Up Closed" %}{% endblock %}
{% block content %}
<h3>{% trans "Sign Up Closed" %}</h3>
<p>{% trans "Sign up is currently closed." %}</p>
<hr>
<div>
<a href='{% url "account_login" %}'>
{% trans "Return to login page" %}
</a>
</div>
{% endblock %}

View File

@ -0,0 +1,23 @@
{% extends "socialaccount/base.html" %}
{% load i18n %}
{% block head_title %}{% trans "Social Network Login Failure" %}{% endblock %}
{% block content %}
<h3>{% trans "Account Login Failure" %}</h3>
<p>
{% trans "An error occurred while attempting to login via your social network account." %}
<br>
{% trans "Contact your system administrator for further information." %}
</p>
<hr>
<div>
<a href='{% url "account_login" %}'>
{% trans "Return to login page" %}
</a>
</div>
{% endblock %}

View File

@ -0,0 +1,28 @@
{% extends "socialaccount/base.html" %}
{% load i18n %}
{% block head_title %}{% trans "Sign In" %}{% endblock %}
{% block content %}
{% if process == "connect" %}
<h3>{% blocktrans with provider.name as provider %}Connect {{ provider }}{% endblocktrans %}</h3>
<p>{% blocktrans with provider.name as provider %}You are about to connect a new third party account from {{ provider }}.{% endblocktrans %}</p>
{% else %}
<h3>{% blocktrans with provider.name as provider %}Sign In Via {{ provider }}{% endblocktrans %}</h3>
<p>{% blocktrans with provider.name as provider %}You are about to sign in using a third party account from {{ provider }}.{% endblocktrans %}</p>
{% endif %}
<form method="post">
{% csrf_token %}
<button class='btn btn-success sso-provider-link' type="submit"><span class='fas fa-sign-in-alt'></span>&nbsp;{% trans "Continue" %}</button>
</form>
<hr>
<div>
<a href='{% url "account_login" %}'>
{% trans "Return to login page" %}
</a>
</div>
{% endblock %}

View File

@ -0,0 +1,31 @@
{% extends "socialaccount/base.html" %}
{% load i18n crispy_forms_tags inventree_extras %}
{% block head_title %}{% trans "Signup" %}{% endblock %}
{% block content %}
<h3>{% trans "Sign Up" %}</h3>
<p>{% blocktrans with provider_name=account.get_provider.name site_name=site.name %}You are about to use your {{provider_name}} account to login to
{{site_name}}.<br>As a final step, please complete the following form:{% endblocktrans %}</p>
<form class="signup" id="signup_form" method="post" action="{% url 'socialaccount_signup' %}">
{% csrf_token %}
{{ form|crispy }}
{% if redirect_field_value %}
<input type="hidden" name="{{ redirect_field_name }}" value="{{ redirect_field_value }}" />
{% endif %}
<button class='btn btn-success sso-provider-link' type="submit">
<span class='fas fa-user-plus'></span>&nbsp;{% trans "Sign Up" %} &raquo;
</button>
</form>
<hr>
<div>
<a href='{% url "account_login" %}'>
{% trans "Return to login page" %}
</a>
</div>
{% endblock %}

View File

@ -2,17 +2,21 @@
{% get_providers as socialaccount_providers %} {% get_providers as socialaccount_providers %}
<ul class='sso-provider-list'>
{% for provider in socialaccount_providers %} {% for provider in socialaccount_providers %}
<li class='sso-provider-link'>
{% if provider.id == "openid" %} {% if provider.id == "openid" %}
{% for brand in provider.get_brands %} {% for brand in provider.get_brands %}
<a title="{{brand.name}}" brand_name='{{ provider.id }}' <a title="{{brand.name}}" brand_name='{{ provider.id }}'
class="btn btn-primary socialaccount_provider {{provider.id}} {{brand.id}}" class="btn btn-light socialaccount_provider {{provider.id}} {{brand.id}}"
href="{% provider_login_url provider.id openid=brand.openid_url process=process %}"> href="{% provider_login_url provider.id openid=brand.openid_url process=process %}">
<span class='brand-icon' brand_name='{{provider.id}}'></span> {{brand.name}}</a> <span class='brand-icon' brand_name='{{provider.id}}'></span> {{ brand.name }}</a>
{% endfor %} {% endfor %}
{% endif %} {% endif %}
<a title="{{provider.name}}" brand_name='{{ provider.id }}' <a title="{{provider.name}}" brand_name='{{ provider.id }}'
class="btn btn-primary socialaccount_provider {{provider.id}}" class="btn btn-light socialaccount_provider {{provider.id}}"
href="{% provider_login_url provider.id process=process scope=scope auth_params=auth_params %}" href="{% provider_login_url provider.id process=process scope=scope auth_params=auth_params %}"
><span class='brand-icon' brand_name='{{provider.id}}'></span> {{provider.name}}</a> ><span class='brand-icon' brand_name='{{provider.id}}'></span> {{ provider.name }}</a>
</li>
{% endfor %} {% endfor %}
</ul>