Merge pull request #3561 from modoboa/FuzzyParrabellum-specialchar_pswdgenerator

Fuzzy parrabellum specialchar pswdgenerator
This commit is contained in:
Antoine Nguyen
2025-06-23 10:36:51 +02:00
committed by GitHub
6 changed files with 69 additions and 11 deletions
+4 -4
View File
@@ -24,6 +24,10 @@ const imapMigrationEnabled = computed(
() => parametersStore.imapMigrationEnabled
)
const openRspamdDashboard = () => {
router.push(rspamdUrl.value)
}
const settings = []
const adminMenuItems = [
{
@@ -136,8 +140,4 @@ onMounted(() => {
})
}
})
const openRspamdDashboard = () => {
router.push(rspamdUrl.value)
}
</script>
+25 -5
View File
@@ -292,12 +292,32 @@ def domain_has_authorized_mx(name):
def make_password():
"""Create a random password."""
length = int(param_tools.get_global_parameter("random_password_length", app="core"))
while True:
password = "".join(
random.SystemRandom().choice(string.ascii_letters + string.digits)
for _ in range(length)
password_length = int(
param_tools.get_global_parameter("random_password_length", app="core")
)
allow_special_characters = bool(
param_tools.get_global_parameter("allow_special_characters", app="core")
)
for i in range(10): # limit tries
all_possible_chars = ""
possible_chars_types = [string.ascii_letters, string.digits]
certain_chars = ""
if allow_special_characters: # add special characters in random passwd
possible_chars_types.append(string.punctuation)
# make it so there is always one character of each type in password
# and then will randomize their placement
for possible_chars in possible_chars_types:
all_possible_chars += possible_chars
certain_chars += random.SystemRandom().choice(possible_chars)
password = (
"".join(
random.SystemRandom().choice(all_possible_chars)
for _ in range(password_length - len(possible_chars_types))
)
+ certain_chars
)
random.shuffle(list(password))
password = "".join(password)
try:
password_validation.validate_password(password)
except ValidationError:
+28 -1
View File
@@ -1,4 +1,5 @@
from unittest import skipIf
import string
from django.test import override_settings
from django.urls import reverse
@@ -8,7 +9,7 @@ from modoboa.core.models import User
from modoboa.core.tests import test_ldap
from modoboa.lib.tests import NO_LDAP, ModoAPITestCase
from modoboa.limits import utils as limits_utils
from .. import factories, models
from .. import factories, models, lib
class AuthenticationTestCase(ModoAPITestCase):
@@ -45,6 +46,32 @@ class AccountTestCase(ModoAPITestCase):
super().setUpTestData()
factories.populate_database()
def test_random_password(self):
# Setting allow_special_characters to False
self.localconfig.parameters.set_value("allow_special_characters", False, "core")
random_password_length = self.localconfig.parameters.get_value(
"random_password_length", "core"
)
self.localconfig.save()
random_password = lib.make_password()
self.assertEqual(len(random_password), random_password_length)
self.assertEqual(
len([char for char in random_password if char in list(string.punctuation)]),
0,
)
# And with allow_special_characters to True
self.localconfig.parameters.set_value("allow_special_characters", True, "core")
self.localconfig.save()
random_password = lib.make_password()
self.assertEqual(len(random_password), random_password_length)
self.assertNotEqual(
len([char for char in random_password if char in list(string.punctuation)]),
0,
)
def test_aliases_update_on_disable(self):
"""Check if aliases are disabled when account is disabled."""
account = User.objects.get(username="user@test.com")
+1
View File
@@ -75,6 +75,7 @@ class CoreGlobalParametersSerializer(serializers.Serializer):
update_scheme = serializers.BooleanField(default=True)
default_password = serializers.CharField(default="ChangeMe1!")
random_password_length = serializers.IntegerField(min_value=8, default=8)
allow_special_characters = serializers.BooleanField(default=False)
update_password_url = serializers.URLField(required=False, allow_blank=True)
password_recovery_msg = serializers.CharField(required=False, allow_blank=True)
sms_password_recovery = serializers.BooleanField(default=False)
+10
View File
@@ -96,6 +96,16 @@ GLOBAL_PARAMETERS_STRUCT = collections.OrderedDict(
),
},
),
(
"allow_special_characters",
{
"label": gettext_lazy("Allow special characters for random password"),
"help_text": gettext_lazy(
"Enable special characters in randomly generated "
"passwords."
),
}
),
(
"update_password_url",
{
+1 -1
View File
@@ -61,7 +61,7 @@ def get_dovecot_schemes():
return [f"{{{smart_str(scheme)}}}" for scheme in schemes.split()], status
def cache_available_password_hasher(bypass_cache=False):
def cache_available_password_hasher(bypass_cache: bool = False):
available_schemes, status = get_dovecot_schemes()
password_scheme_choice = [
(hasher.name, hasher.label)