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 -*-
from __future__ import unicode_literals
import logging
import sys
import traceback
from django.conf import settings
from django.core.exceptions import ValidationError as DjangoValidationError
from django.db.utils import IntegrityError, OperationalError
from django.utils.translation import gettext_lazy as _
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.response import Response
logger = logging.getLogger('inventree')
def log_error(path):
"""Log an error to the database.
@ -32,12 +36,19 @@ def log_error(path):
if kind in settings.IGNORED_ERRORS:
return
Error.objects.create(
kind=kind.__name__,
info=info,
data='\n'.join(traceback.format_exception(kind, info, data)),
path=path,
)
# Log error to stderr
logger.error(info)
try:
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):

View File

@ -23,6 +23,7 @@ from crispy_forms.helper import FormHelper
from crispy_forms.layout import Field, Layout
from common.models import InvenTreeSetting
from InvenTree.exceptions import log_error
logger = logging.getLogger('inventree')
@ -239,10 +240,20 @@ class CustomUrlMixin:
class CustomAccountAdapter(CustomUrlMixin, RegistratonMixin, OTPAdapter, DefaultAccountAdapter):
"""Override of adapter to use dynamic settings."""
def send_mail(self, template_prefix, email, context):
"""Only send mail if backend configured."""
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

View File

@ -1070,3 +1070,24 @@ a {
margin-left: 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 %}
{% 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'>
<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_MAIL_REQUIRED" icon="fa-at" %}
{% 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_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_SSO_AUTO" icon="fa-key" %}
{% 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>
</table>

View File

@ -76,6 +76,7 @@
{% endblock %}
</div>
</div>
</div>
{% block extra_body %}
@ -87,6 +88,7 @@
<!-- general JS -->
{% 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'>
@ -95,7 +97,7 @@ $(document).ready(function () {
{% if messages %}
{% for message in messages %}
showMessage(messsage);
showMessage("{{ messsage }}");
{% endfor %}
{% endif %}
@ -109,7 +111,7 @@ $(document).ready(function () {
var icon = window.FontAwesome.icon({prefix: 'fab', iconName: tag});
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 %}
{% mail_configured as mail_conf %}
<h1>{% trans "Sign In" %}</h1>
{% if enable_reg %}
{% get_providers as socialaccount_providers %}
{% if socialaccount_providers %}
<p>{% blocktrans with site.name as site_name %}Please sign in with one
of your existing third party accounts or <a class="btn btn-primary btn-small" href="{{ signup_url }}">sign up</a>
for a account and sign in below:{% endblocktrans %}</p>
{% else %}
<p>{% blocktrans %}If you have not created an account yet, then please
<a href="{{ signup_url }}">sign up</a> first.{% endblocktrans %}</p>
{% endif %}
{% endif %}
<div class='d-flex flex-wrap'>
<h3>{% trans "Sign In" %}</h3>
{% include "spacer.html" %}
{% if enable_reg %}
<div class='float-right'>
{% trans "Not a member?" %}&nbsp;
<a class="btn btn-primary btn-small" href="{{ signup_url }}">
<span class='fas fa-user-plus'></span>&nbsp;{% trans "Sign Up" %}
</a>
</div>
{% endif %}
</div>
<form class="login" method="POST" action="{% url 'account_login' %}">
{% 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 }}" />
{% endif %}
<hr>
{% if login_message %}
<div>{{ login_message | safe }}<hr></div>
{% endif %}
<div class="btn-group" role="group">
<div class="btn-group" role="group" style='width: 100%;'>
<button class="btn btn-success" type="submit">
<span class='fas fa-sign-in-alt'></span> {% trans "Sign In" %}
</button>
</div>
{% if login_message %}
<div>{{ login_message | safe }}<hr></div>
{% endif %}
{% if mail_conf and enable_pwd_forgot %}
<a class="" href="{% url 'account_reset_password' %}"><small>{% trans "Forgot Password?" %}</small></a>
{% endif %}
@ -50,10 +48,15 @@ for a account and sign in below:{% endblocktrans %}</p>
{% if enable_sso %}
<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>
{% include "socialaccount/snippets/provider_list.html" with process="login" %}
</div>
{% include "socialaccount/snippets/login_extra.html" %}
{% endif %}

View File

@ -8,7 +8,7 @@
{% settings_value 'LOGIN_ENABLE_REG' as enable_reg %}
{% 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>

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 %}
<ul class='sso-provider-list'>
{% for provider in socialaccount_providers %}
<li class='sso-provider-link'>
{% if provider.id == "openid" %}
{% for brand in provider.get_brands %}
<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 %}">
<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 %}
{% endif %}
<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 %}"
><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 %}
</ul>