mirror of
				https://github.com/inventree/InvenTree.git
				synced 2025-10-30 20:55:42 +00:00 
			
		
		
		
	[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:
		| @@ -215,9 +215,35 @@ class RegistratonMixin: | |||||||
|             return super().is_open_for_signup(request, *args, **kwargs) |             return super().is_open_for_signup(request, *args, **kwargs) | ||||||
|         return False |         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): |     def save_user(self, request, user, form, commit=True): | ||||||
|         """Check if a default group is set in settings.""" |         """Check if a default group is set in settings.""" | ||||||
|  |         # Create the user | ||||||
|         user = super().save_user(request, user, form) |         user = super().save_user(request, user, form) | ||||||
|  |  | ||||||
|  |         # Check if a default group is set in settings | ||||||
|         start_group = InvenTreeSetting.get_setting('SIGNUP_GROUP') |         start_group = InvenTreeSetting.get_setting('SIGNUP_GROUP') | ||||||
|         if start_group: |         if start_group: | ||||||
|             try: |             try: | ||||||
|   | |||||||
| @@ -11,6 +11,7 @@ import json | |||||||
| import logging | import logging | ||||||
| import math | import math | ||||||
| import os | import os | ||||||
|  | import re | ||||||
| import uuid | import uuid | ||||||
| from datetime import datetime, timedelta | from datetime import datetime, timedelta | ||||||
| from enum import Enum | from enum import Enum | ||||||
| @@ -81,19 +82,33 @@ class BaseInvenTreeSetting(models.Model): | |||||||
|         self.clean(**kwargs) |         self.clean(**kwargs) | ||||||
|         self.validate_unique(**kwargs) |         self.validate_unique(**kwargs) | ||||||
|  |  | ||||||
|  |         # Execute before_save action | ||||||
|  |         self._call_settings_function('before_save', args, kwargs) | ||||||
|  |  | ||||||
|         # Update this setting in the cache |         # Update this setting in the cache | ||||||
|         if do_cache: |         if do_cache: | ||||||
|             self.save_to_cache() |             self.save_to_cache() | ||||||
|  |  | ||||||
|         super().save() |         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) |         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 |         # Execute if callable | ||||||
|         if callable(after_save): |         if callable(settings_fnc): | ||||||
|             after_save(self) |             settings_fnc(self) | ||||||
|  |  | ||||||
|     @property |     @property | ||||||
|     def cache_key(self): |     def cache_key(self): | ||||||
| @@ -771,6 +786,19 @@ def update_instance_name(setting): | |||||||
|     site_obj.save() |     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): | class InvenTreeSetting(BaseInvenTreeSetting): | ||||||
|     """An InvenTreeSetting object is a key:value pair used for storing single values (e.g. one-off settings values). |     """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, |             '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': { |         'SIGNUP_GROUP': { | ||||||
|             'name': _('Group on signup'), |             'name': _('Group on signup'), | ||||||
|             'description': _('Group to which new users are assigned on registration'), |             'description': _('Group to which new users are assigned on registration'), | ||||||
|   | |||||||
| @@ -134,6 +134,7 @@ class SettingsTest(InvenTreeTestCase): | |||||||
|             'units', |             'units', | ||||||
|             'requires_restart', |             'requires_restart', | ||||||
|             'after_save', |             'after_save', | ||||||
|  |             'before_save', | ||||||
|         ] |         ] | ||||||
|  |  | ||||||
|         for k in setting.keys(): |         for k in setting.keys(): | ||||||
|   | |||||||
| @@ -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_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="SIGNUP_GROUP" icon="fa-users" %} |         {% 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> |         <tr> | ||||||
|             <th><h5>{% trans 'Single Sign On' %}</h5></th> |             <th><h5>{% trans 'Single Sign On' %}</h5></th> | ||||||
|             <td colspan='4'></td> |             <td colspan='4'></td> | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user