From a3228fbf308a469429c696c847dc68df98b8b231 Mon Sep 17 00:00:00 2001 From: Emmanuel Garette Date: Tue, 5 Nov 2024 08:55:53 +0100 Subject: [PATCH] feat: add min_len, max_len, forbidden_char for password option --- LICENSE.txt => LICENSE | 0 docs/options.rst | 3 +++ locale/fr/LC_MESSAGES/tiramisu.po | 14 ++++++++++- locale/tiramisu.pot | 14 ++++++++++- pyproject.toml | 1 + tests/test_option_type.py | 27 +++++++++++++++++++- tiramisu/i18n.py | 2 +- tiramisu/locale/fr/LC_MESSAGES/tiramisu.mo | Bin 24243 -> 24517 bytes tiramisu/option/passwordoption.py | 28 +++++++++++++++++++++ 9 files changed, 85 insertions(+), 4 deletions(-) rename LICENSE.txt => LICENSE (100%) 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 64c604e0a9ef1abf0cd79f9aa03bbc88b191c4df..a7796ffe6bff4f1578e59aa8befebcf60fe569a7 100644 GIT binary patch delta 4634 zcmZwK32;@_9mny1AZ%d?WC2M8UKV56LjV&ZOTsQ8vTs2GfjmNxtSClC+{WrBt>c$gX87cB+-ibXw|+%TS6sns%_?-@7+6Os9A9d!KVJ@7;6H|D1Dc z8%_sZXbkdy78bn2IIfb1$%@uy9fQnHbH$L|&EoJeY>C;}19Pw^uEluzw>>nHxo{Ro;yq-LmfY2B5RSnltUz5q zjQaj@)X2ZUUf8XhGqM!a1I|N=V72b`!>IdQMm^{c7)k#Y@sL>x_QAop7*)cZ_z1p- zS@;D$g{j@02iSoboL@%$K7_lf5~ER5k&ha{%kKFV)Pw)vUhfx8{q@Bh8vNN7KGLur zRf*fEH4)DTMX?Ng6z8Bi^kX_6#~l0~Tj3n)totoMO~qcEt5vr26(}^x$1g#V}5^b~4e2Td@-c(@94RN4{z? zF@ERCGTaM`kUy*9BM}dv2d}y3U!kTZGS+M&jzQL~y@H)@}*2XkH4G)ba$e(TCqZE%|1xE2J7PM_ejr5S~DO3kHa0uQ(Jy<+t8iUi3jbb}d zQ&*3C#m?drcpKBvKadGxP*#HKXgfZJucJ!;8EQlkOuRZ8i{o%LYH>AU3%rZ1@EaVC zO*jlwC?iF%QuJUgGOKnBc?Q3IL_<&Bgz7M{x3fxfQ0Ln)1TUd}_#w8%yQq>kA?wbf z`Z)VL1vQXasI{`(y}sUcKThI$BW}?C4`Bsr+r5NYcpevG7dE#>Qi|j8D5|4xkp*mF zJT*Jdl29d|gz2~i%kes9;Mf7qi)|z7!7rlvyN>0JVtPvm*6k8i%S(73$xQp%&LYY>!I*kf57e99HPRWVk^7MKYI{+u|1z>;?HX#E-Eob0 z+}TaTa5~pZ@F6^pBk`u6MmmjXR$4cF3K_JOqvoz2HASBw+r`>2>?g24M&c^e18zq8 zwBx8L_!suT2zsFlOGZ_$2sMC%$e7K4okk9gwxgU^Wj^XgpQ1|EHQia=dB`-|5u}>- zDe`CGEOCltGf}%>i|dCtn)9xFsB-gA_gjsbcnM>)|HJu}Mm`kPK^F4xR)yN9XRrj@ zjpctZa1Hjvb*Pc-!vuUEXW~7q#)-V8^q|*}^=&Q3I|Cbvi#adB4DJ6LG_?I9CpfEg zv}+D_=lU8{>1$D?KY@7|%t~5^1sINBqPEqK_$Y?*2x^_WQicAEy( zwoVL}s@apM#j+Kn@NcL!a39$@){pun;3CumZbIF7Kk7lQp%&RKOvLX|4-mtOuEYtb zelJKZqH#=&N;)6oaEy&Z^yxEja%@w!%N4zV|0&-CB3bq>;}>4WJtP z;(pYWet^6htO-@&K{K5DmZHwzm_Z#n)A*VT`oVqdf_+&@y736teAjLGFxO9_rr=}O zA2EjWgju|vaU#~@Zq)W0IGfixK8>Y#1=I0y{~YHVG6@3K<;+r@OZy{6Wx9&O4lcu6> z;6-)t0&4Z1L6z`x)Re?6bQWC}_TqdgM&m|Q1rJ~l?k8!4@!6Z?C^=1jN>sTu#MuXd z25&ZekE|fomzqzIT^D#SL}AMZuInY#olGX0%1vZ6 zd4-H3I{d$Jv<`Tctai_RuB%YI9P{)9q~&%;am#56SlZ*QRB9?Fg2w* zmXXb>|A4@$Gq>B^b~D3iXigtIp5~;IOe1?pB=Hg*?~?wcmrgi%o!G`e%lYUCT72!v zDe_w2JxHU9^dPU2I`ZIgg_E)Fg`c=iL2bKwGJ#Z+GEz&nlIG(qjm2cIdtoVRJ0}q> z>gN9K9opI^=gIF#FqucR=btC7$TLKHdL0QR>9KiqAl2`MEPNb_-s#?Q!cvX7*a3ZkPcd5PqclVm;7;UR6vWpakJ zBd?PLjlTtr=3_n$?TCdWnM9Lc6CFFqKvF)7qx;wn6a z#RWw^=T3nR&mSrC`aHhkqH?ECUqfZu#n1s2MV>;p*F5k4|9Wmkd2v~R?|hxlY8DO~ oQnPB(uEZj*C$G4m+*6Y4^DN<>-lBps2Fb|-o#|;hWgZIuFPYXmZU6uP delta 4358 zcmYk;3y@b;9S86O@|5>WUdrx*5G)T7g=KjzDjNt0$Ri#Gh^rYYK3Fjj{Uc&(qLPG! z4+z1OQqWey2e?RrO`|ng4x^;a=rrRPU^SCgI@Ra*zjsIP%y&QM-o1OzIlp_(g(LNi z>JBz4e9*DU=Hz%o&Xn2BQfkvEr46m)YD$ONr__Y6aSV^LA5ZI$QW?uRh*NnES8*_R zb0m*3p4+}-N-a5<@pl!Rl2Ref*SN@qCpn&n(e#zobe(MsW_K5*r!s?01Zj zp5Qmw*v06uoK;-PMcm1zY^=U+O)VI&;A}>zr?8sKxRTFvAp7@H|0vOHcSVU-ayp;l zaDL9H#9(zc`7|r{TB>%si=}*wBYBXqmOkSOjxJ59E%&kwU!`BAH|f#oL}?+r(8?%c zAOrpA&uMf|TA813VXW#yoWxVKPE$qiti%^HX1Y#T(hN z;Ou6N^|WBqe;6}s=dZ(lj0cb9`JB%f$OF8P2WUeS%M`DwvvRqV$~l?SS;eh1=JXz; zpD#Irg$`a+lzI|lvE0t+Xb&gyBgSGHU;#AdLN?_hj$sWexq~K^j!ou&1(e$o!cOzIhH#vM%;;zh?X2_&qvS8JivMCQD@|(x z_i#M_#u)g}Az6o&bZfdU_Yp>=-{ATBPoHbVwi@K>T-NYXzQVY%t*sG@XB^{Q&tN;& zP+zH*9+944OyKv7fp_!X;=U1#`>NT5wN!+(iUsc=?b3)D9;51|zcU`t(=TJ6Rx)0} zm5c%1#aK-H7_ZEY_y#>>HEHcv~aYWpwD%{KCR2t(x_tf52GELhBLO=eCj*h&P%zE-PmGu_9ZQ2mGdR+${jSP z^b+HheOl0nchk!XjBPTNlX(-n@Bm|AZ_~5WmyFU^C`-Je`Sgf%3!`FnjDdellT4k* zWuNG2T;co~#&b%?XO$`})QDBRn_gc!NmWawRoTerQ4!Ks#(jUt?P|$i;CvxVxQ$Wz zS2=~P{j!Yn7&BkZc>Y5)wseFS$NoRfQ*UtL5;kK!3F_XWrA3I#+-wBT7I^NG1 zXnS8N>orYc%xpE6avzN)b+aX6`z>Hk-krOX-DCgPYxMPtcNwMsA8R=Iny!;>DHl z9!3XG(>El&#yj~BMoCv*k}a}#83SxPJ)3ENs&=}9u~v37X8I8qu``EKjJ-%4$qq7W5oHqk^5Q20duqO$uh>; z*w0eF#~%EG-{Kj5c>QU4L1V4Pi?m)+<-Ba0tz#?aM;WDkn=zwq-q#3DWChpczQ8Ky zC%BY@)jbBdk+I!Qu_Fg9%s$!U+0J=kw#G1x8V=_cs#d$N{M!i{XiZ4w=s zV0uP=E-%YEu|v}>^7Z3MC$Gsev0;iwxu0j{?U?Kxv2U){I8WmJmdYxLqh5X@TV$+k z70V*+kpmK2^B%cYz9+ws6%vQy72mI-q-&&3;)u#tNi5Rhq5tClPF~N?1G}wQvK~Aw zF62in(C#utI?8sLAdkp6iR1Z<)S7R|%KZF>+*-zhULp@l^j|!@j^be*7I(zc+@D=4 zeiQD?+rh@X9UqhU62@_r{6GfBi6h?i1I5-?KjOWYeRUeGLi(h)yA#vQ6ksjqw^L7JAH7?9gZf%&q4_fhGv!_@sQI_lF zhjPC(90xTnm&Y0|(3URlgi&9+_Dk*9C5L36G!Z+vxW&KErgF0kjE-)Xc5s{E|j@}c5vc}`l$!!l7Cj!t=FJnxlX$!6&z*U54@EDcBD39Y;31$kU58(blsrE8oxo|U#Tr|x9?=bF?V?(#wFO^13vU3YJv4PEO-jefn| Pra@EQtSg(ktJD7hf_2hj 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) + ) + )