diff --git a/LICENSE.txt b/LICENSE similarity index 100% rename from LICENSE.txt rename to LICENSE diff --git a/docs/options.rst b/docs/options.rst index 5452326..63ffff4 100644 --- a/docs/options.rst +++ b/docs/options.rst @@ -293,6 +293,9 @@ Unix options * - PasswordOption - Simple string with no other restriction: - + - min_len: minimum length autorise for a password + - max_len: maximum length autorise for a passwword + - forbidden_char: list of forbidden characters for a password * - FilenameOption - For this option, only lowercase and uppercas ASCII character, "-", ".", "_", "~", and "/" are allowed. diff --git a/locale/fr/LC_MESSAGES/tiramisu.po b/locale/fr/LC_MESSAGES/tiramisu.po index 16f4bad..9f53f92 100644 --- a/locale/fr/LC_MESSAGES/tiramisu.po +++ b/locale/fr/LC_MESSAGES/tiramisu.po @@ -2,7 +2,7 @@ msgid "" msgstr "" "Project-Id-Version: Tiramisu\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-10-30 13:15+0100\n" +"POT-Creation-Date: 2024-11-05 08:49+0100\n" "PO-Revision-Date: \n" "Last-Translator: Emmanuel Garette \n" "Language-Team: Tiramisu's team \n" @@ -762,6 +762,18 @@ msgstr "ne peut changer group_type si déjà spécifié (ancien {0}, nouveau {1} msgid "group_type: {0} not allowed" msgstr "group_type : {0} non autorisé" +#: tiramisu/option/passwordoption.py:49 +msgid "at least {0} characters are required" +msgstr "au moins {0} caractères sont requis" + +#: tiramisu/option/passwordoption.py:52 +msgid "maximum {0} characters required" +msgstr "un maximum de {0} caractères sont autorisés" + +#: tiramisu/option/passwordoption.py:57 +msgid "must not have the characters {0}" +msgstr "ne doit pas contenir les caractères {0}" + #: tiramisu/option/permissionsoption.py:52 msgid "only 3 or 4 octal digits are allowed" msgstr "seulement 3 ou 4 chiffres octal sont autorisées" diff --git a/locale/tiramisu.pot b/locale/tiramisu.pot index 4a37617..860622c 100644 --- a/locale/tiramisu.pot +++ b/locale/tiramisu.pot @@ -5,7 +5,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" -"POT-Creation-Date: 2024-10-30 13:15+0100\n" +"POT-Creation-Date: 2024-11-05 08:52+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -668,6 +668,18 @@ msgstr "" msgid "group_type: {0} not allowed" msgstr "" +#: tiramisu/option/passwordoption.py:49 +msgid "at least {0} characters are required" +msgstr "" + +#: tiramisu/option/passwordoption.py:52 +msgid "maximum {0} characters required" +msgstr "" + +#: tiramisu/option/passwordoption.py:57 +msgid "must not have the characters {0}" +msgstr "" + #: tiramisu/option/permissionsoption.py:52 msgid "only 3 or 4 octal digits are allowed" msgstr "" diff --git a/pyproject.toml b/pyproject.toml index 55c20af..2a9ad93 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -9,6 +9,7 @@ authors = [{name = "Emmanuel Garette", email = "gnunux@gnunux.info"}] readme = "README.md" description = "an options controller tool" requires-python = ">=3.8" +license = {file = "LICENSE"} classifiers = [ "License :: OSI Approved :: GNU Library or Lesser General Public License (LGPL)", "Programming Language :: Python", diff --git a/tests/test_option_type.py b/tests/test_option_type.py index 70645bd..f38b750 100644 --- a/tests/test_option_type.py +++ b/tests/test_option_type.py @@ -158,13 +158,38 @@ def test_with_many_subgroups(config_type): def test_password_option(config_type): o = PasswordOption('o', '') - od1 = OptionDescription('d', '', [o]) + o1 = PasswordOption('o1', '', min_len=4) + o2 = PasswordOption('o2', '', max_len=4) + o3 = PasswordOption('o3', '', forbidden_char=['p']) + od1 = OptionDescription('d', '', [o, o1, o2, o3]) cfg = Config(od1) cfg = get_config(cfg, config_type) cfg.option('o').value.set('a_valid_password') with pytest.raises(ValueError): cfg.option('o').value.set(1) + # + assert cfg.option('o1').value.get() is None + with pytest.raises(ValueError): + cfg.option('o1').value.set("1") + with pytest.raises(ValueError): + cfg.option('o1').value.set("12") + with pytest.raises(ValueError): + cfg.option('o1').value.set("123") + cfg.option('o1').value.set("1234") + cfg.option('o1').value.set("12345") + # + assert cfg.option('o2').value.get() is None + with pytest.raises(ValueError): + cfg.option('o2').value.set("12345") + cfg.option('o2').value.set("1") + cfg.option('o2').value.set("12") + cfg.option('o2').value.set("123") + cfg.option('o2').value.set("1234") + # + with pytest.raises(ValueError): + cfg.option('o3').value.set("password") + cfg.option('o3').value.set("assword") # assert not list_sessions() diff --git a/tiramisu/i18n.py b/tiramisu/i18n.py index ddc766e..901835c 100644 --- a/tiramisu/i18n.py +++ b/tiramisu/i18n.py @@ -21,6 +21,6 @@ from gettext import translation from pathlib import Path -t = translation('tiramisu', str(Path(__file__).parent / 'locale'), fallback=True) +t = translation("tiramisu", str(Path(__file__).parent / "locale"), fallback=True) _ = t.gettext diff --git a/tiramisu/locale/fr/LC_MESSAGES/tiramisu.mo b/tiramisu/locale/fr/LC_MESSAGES/tiramisu.mo index 64c604e..a7796ff 100644 Binary files a/tiramisu/locale/fr/LC_MESSAGES/tiramisu.mo and b/tiramisu/locale/fr/LC_MESSAGES/tiramisu.mo differ diff --git a/tiramisu/option/passwordoption.py b/tiramisu/option/passwordoption.py index 44582f8..d346e05 100644 --- a/tiramisu/option/passwordoption.py +++ b/tiramisu/option/passwordoption.py @@ -22,6 +22,7 @@ """ from ..i18n import _ +from ..error import display_list from .stroption import StrOption @@ -30,3 +31,30 @@ class PasswordOption(StrOption): __slots__ = tuple() _type = "password" + + def __init__(self, *args, min_len=None, max_len=None, forbidden_char=[], **kwargs): + extra = {} + if min_len is not None: + extra["min_len"] = min_len + if max_len is not None: + extra["max_len"] = max_len + if forbidden_char: + extra["forbidden_char"] = set(forbidden_char) + super().__init__(*args, extra=extra, **kwargs) + + def validate(self, value: str) -> None: + super().validate(value) + min_len = self.impl_get_extra("min_len") + if min_len and len(value) < min_len: + raise ValueError(_("at least {0} characters are required").format(min_len)) + max_len = self.impl_get_extra("max_len") + if max_len and len(value) > max_len: + raise ValueError(_("maximum {0} characters required").format(max_len)) + if self.impl_get_extra("forbidden_char"): + forbidden_char = set(value) & self.impl_get_extra("forbidden_char") + if forbidden_char: + raise ValueError( + _("must not have the characters {0}").format( + display_list(list(forbidden_char), add_quote=True) + ) + )