[FR] Enable restrictions on allowed domains for signup (#4172)

* [FR] Enable restrictions on allowed domains for signup
Fixes #4168

* raise permission errors

* add setting to page

* move checks to clean_email

* remove unneeded check

* simplify

* log error to database

* factor settings fnc call out

* Add validation before setting save

* add before_save to accepted tokens
This commit is contained in:
Matthias Mair 2023-01-08 22:22:50 +01:00 committed by GitHub
parent 41318e4056
commit be859183a5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 67 additions and 4 deletions

View File

@ -215,9 +215,35 @@ class RegistratonMixin:
return super().is_open_for_signup(request, *args, **kwargs)
return False
def clean_email(self, email):
"""Check if the mail is valid to the pattern in LOGIN_SIGNUP_MAIL_RESTRICTION (if enabled in settings)."""
mail_restriction = InvenTreeSetting.get_setting('LOGIN_SIGNUP_MAIL_RESTRICTION', None)
if not mail_restriction:
return super().clean_email(email)
split_email = email.split('@')
if len(split_email) != 2:
logger.error(f'The user {email} has an invalid email address')
raise forms.ValidationError(_('The provided primary email address is not valid.'))
mailoptions = mail_restriction.split(',')
for option in mailoptions:
if not option.startswith('@'):
log_error('LOGIN_SIGNUP_MAIL_RESTRICTION is not configured correctly')
raise forms.ValidationError(_('The provided primary email address is not valid.'))
else:
if split_email[1] == option[1:]:
return super().clean_email(email)
logger.info(f'The provided email domain for {email} is not approved')
raise forms.ValidationError(_('The provided email domain is not approved.'))
def save_user(self, request, user, form, commit=True):
"""Check if a default group is set in settings."""
# Create the user
user = super().save_user(request, user, form)
# Check if a default group is set in settings
start_group = InvenTreeSetting.get_setting('SIGNUP_GROUP')
if start_group:
try:

View File

@ -11,6 +11,7 @@ import json
import logging
import math
import os
import re
import uuid
from datetime import datetime, timedelta
from enum import Enum
@ -81,19 +82,33 @@ class BaseInvenTreeSetting(models.Model):
self.clean(**kwargs)
self.validate_unique(**kwargs)
# Execute before_save action
self._call_settings_function('before_save', args, kwargs)
# Update this setting in the cache
if do_cache:
self.save_to_cache()
super().save()
# Get after_save action
# Execute after_save action
self._call_settings_function('after_save', args, kwargs)
def _call_settings_function(self, reference: str, args, kwargs):
"""Call a function associated with a particular setting.
Args:
reference (str): The name of the function to call
args: Positional arguments to pass to the function
kwargs: Keyword arguments to pass to the function
"""
# Get action
setting = self.get_setting_definition(self.key, *args, **kwargs)
after_save = setting.get('after_save', None)
settings_fnc = setting.get(reference, None)
# Execute if callable
if callable(after_save):
after_save(self)
if callable(settings_fnc):
settings_fnc(self)
@property
def cache_key(self):
@ -771,6 +786,19 @@ def update_instance_name(setting):
site_obj.save()
def validate_email_domains(setting):
"""Validate the email domains setting."""
if not setting.value:
return
domains = setting.value.split(',')
for domain in domains:
if not domain:
raise ValidationError(_('An empty domain is not allowed.'))
if not re.match(r'^@[a-zA-Z0-9\.\-_]+$', domain):
raise ValidationError(_(f'Invalid domain name: {domain}'))
class InvenTreeSetting(BaseInvenTreeSetting):
"""An InvenTreeSetting object is a key:value pair used for storing single values (e.g. one-off settings values).
@ -1375,6 +1403,13 @@ class InvenTreeSetting(BaseInvenTreeSetting):
'validator': bool,
},
'LOGIN_SIGNUP_MAIL_RESTRICTION': {
'name': _('Allowed domains'),
'description': _('Restrict signup to certain domains (comma-separated, strarting with @)'),
'default': '',
'before_save': validate_email_domains,
},
'SIGNUP_GROUP': {
'name': _('Group on signup'),
'description': _('Group to which new users are assigned on registration'),

View File

@ -134,6 +134,7 @@ class SettingsTest(InvenTreeTestCase):
'units',
'requires_restart',
'after_save',
'before_save',
]
for k in setting.keys():

View File

@ -30,6 +30,7 @@
{% 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="SIGNUP_GROUP" icon="fa-users" %}
{% include "InvenTree/settings/setting.html" with key="LOGIN_SIGNUP_MAIL_RESTRICTION" icon="fa-sitemap" %}
<tr>
<th><h5>{% trans 'Single Sign On' %}</h5></th>
<td colspan='4'></td>