From cb2d7f35adc4bbe053538760d6c7728202fbe719 Mon Sep 17 00:00:00 2001 From: Julian Rother <julian@cccv.de> Date: Mon, 30 Aug 2021 23:40:35 +0200 Subject: [PATCH] Restrict password alphabet to SASLprep-safe ASCII subset Prior to this change user passwords were not validated on change aside from their length, but validated on login/bind by ldap3 with SASLprep. Instead of using SASLprep on password change, this change restricts passwords to 7-bit ASCII without control characters. Control characters are forbidden by SASLprep. Multi-byte characters are uncommon in password, especially in those generated by password managers. This ensures that passwords are always SASLprep-safe without implementing the rather complex SASLprep algorithm. It also allows us to fully describe the alphabet restrictions in the relevant forms. Fixes #100 --- tests/test_selfservice.py | 13 +++ tests/test_user.py | 23 +++- .../templates/selfservice/self.html | 4 +- .../templates/selfservice/set_password.html | 6 +- uffd/signup/templates/signup/start.html | 4 +- uffd/translations/de/LC_MESSAGES/messages.mo | Bin 30920 -> 30889 bytes uffd/translations/de/LC_MESSAGES/messages.po | 99 +++++++++--------- uffd/user/models.py | 19 +++- uffd/user/templates/user/show.html | 4 +- uffd/user/views_user.py | 3 + 10 files changed, 112 insertions(+), 63 deletions(-) diff --git a/tests/test_selfservice.py b/tests/test_selfservice.py index f36cfe93..da3c94dd 100644 --- a/tests/test_selfservice.py +++ b/tests/test_selfservice.py @@ -101,6 +101,19 @@ class TestSelfservice(UffdTestCase): self.assertFalse(ldap.test_user_bind(_user.dn, 'shortpw')) self.assertTrue(ldap.test_user_bind(_user.dn, 'userpassword')) + # Regression test for #100 (login not possible if password contains character disallowed by SASLprep) + def test_change_password_samlprep_invalid(self): + self.login_as('user') + user = request.user + r = self.client.post(path=url_for('selfservice.change_password'), + data={'password1': 'shortpw\n', 'password2': 'shortpw\n'}, + follow_redirects=True) + dump('change_password_samlprep_invalid', r) + self.assertEqual(r.status_code, 200) + _user = request.user + self.assertFalse(ldap.test_user_bind(_user.dn, 'shortpw\n')) + self.assertTrue(ldap.test_user_bind(_user.dn, 'userpassword')) + def test_change_password_mismatch(self): self.login_as('user') user = request.user diff --git a/tests/test_user.py b/tests/test_user.py index 962840db..ea6bb9a6 100644 --- a/tests/test_user.py +++ b/tests/test_user.py @@ -193,6 +193,7 @@ class TestUserViews(UffdTestCase): self.assertEqual(user_updated.uid, user_unupdated.uid) self.assertEqual(user_updated.loginname, user_unupdated.loginname) self.assertTrue(ldap.test_user_bind(user_updated.dn, 'newpassword')) + self.assertFalse(ldap.test_user_bind(user_updated.dn, self.test_data.get('user').get('password'))) def test_update_invalid_password(self): user_unupdated = self.get_user() @@ -201,10 +202,28 @@ class TestUserViews(UffdTestCase): r = self.client.post(path=url_for('user.update', uid=user_unupdated.uid), data={'loginname': 'testuser', 'mail': 'newuser@example.com', 'displayname': 'New User', 'password': 'A'}, follow_redirects=True) - dump('user_update_password', r) + dump('user_update_invalid_password', r) self.assertEqual(r.status_code, 200) user_updated = self.get_user() self.assertFalse(ldap.test_user_bind(user_updated.dn, 'A')) + self.assertTrue(ldap.test_user_bind(user_updated.dn, self.test_data.get('user').get('password'))) + self.assertEqual(user_updated.displayname, user_unupdated.displayname) + self.assertEqual(user_updated.mail, user_unupdated.mail) + self.assertEqual(user_updated.loginname, user_unupdated.loginname) + + # Regression test for #100 (login not possible if password contains character disallowed by SASLprep) + def test_update_saslprep_invalid_password(self): + user_unupdated = self.get_user() + r = self.client.get(path=url_for('user.show', uid=user_unupdated.uid), follow_redirects=True) + self.assertEqual(r.status_code, 200) + r = self.client.post(path=url_for('user.update', uid=user_unupdated.uid), + data={'loginname': 'testuser', 'mail': 'newuser@example.com', 'displayname': 'New User', + 'password': 'newpassword\n'}, follow_redirects=True) + dump('user_update_saslprep_invalid_password', r) + self.assertEqual(r.status_code, 200) + user_updated = self.get_user() + self.assertFalse(ldap.test_user_bind(user_updated.dn, 'newpassword\n')) + self.assertTrue(ldap.test_user_bind(user_updated.dn, self.test_data.get('user').get('password'))) self.assertEqual(user_updated.displayname, user_unupdated.displayname) self.assertEqual(user_updated.mail, user_unupdated.mail) self.assertEqual(user_updated.loginname, user_unupdated.loginname) @@ -223,6 +242,7 @@ class TestUserViews(UffdTestCase): self.assertEqual(user_updated.mail, user_unupdated.mail) self.assertEqual(user_updated.loginname, user_unupdated.loginname) self.assertFalse(ldap.test_user_bind(user_updated.dn, 'newpassword')) + self.assertTrue(ldap.test_user_bind(user_updated.dn, self.test_data.get('user').get('password'))) def test_update_invalid_display_name(self): user_unupdated = self.get_user() @@ -238,6 +258,7 @@ class TestUserViews(UffdTestCase): self.assertEqual(user_updated.mail, user_unupdated.mail) self.assertEqual(user_updated.loginname, user_unupdated.loginname) self.assertFalse(ldap.test_user_bind(user_updated.dn, 'newpassword')) + self.assertTrue(ldap.test_user_bind(user_updated.dn, self.test_data.get('user').get('password'))) def test_show(self): r = self.client.get(path=url_for('user.show', uid=self.get_user().uid), follow_redirects=True) diff --git a/uffd/selfservice/templates/selfservice/self.html b/uffd/selfservice/templates/selfservice/self.html index edee7bdc..158e3f3d 100644 --- a/uffd/selfservice/templates/selfservice/self.html +++ b/uffd/selfservice/templates/selfservice/self.html @@ -47,9 +47,9 @@ <div class="col-12 col-md-7"> <form class="form" action="{{ url_for("selfservice.change_password") }}" method="POST"> <div class="form-group"> - <input type="password" class="form-control" id="user-password1" name="password1" placeholder="{{_("New Password")}}" required> + <input type="password" class="form-control" id="user-password1" name="password1" placeholder="{{_("New Password")}}" minlength={{ User.PASSWORD_MINLEN }} maxlength={{ User.PASSWORD_MAXLEN }} pattern="{{ User.PASSWORD_REGEX }}" required> <small class="form-text text-muted"> - {{_('At least 8 and at most 256 characters, no other special requirements.')}} + {{ User.PASSWORD_DESCRIPTION|safe }} </small> </div> <div class="form-group"> diff --git a/uffd/selfservice/templates/selfservice/set_password.html b/uffd/selfservice/templates/selfservice/set_password.html index 42eaa866..ff4c4478 100644 --- a/uffd/selfservice/templates/selfservice/set_password.html +++ b/uffd/selfservice/templates/selfservice/set_password.html @@ -12,14 +12,14 @@ </div> <div class="form-group col-12"> <label for="user-password1">{{_("New Password")}}</label> - <input type="password" class="form-control" id="user-password1" name="password1" required="required" tabindex = "2"> + <input type="password" class="form-control" id="user-password1" name="password1" tabindex="2" minlength={{ User.PASSWORD_MINLEN }} maxlength={{ User.PASSWORD_MAXLEN }} pattern="{{ User.PASSWORD_REGEX }}" required> <small class="form-text text-muted"> - {{_("At least 8 and at most 256 characters, no other special requirements. But please use a password manager.")}} + {{ User.PASSWORD_DESCRIPTION|safe }} </small> </div> <div class="form-group col-12"> <label for="user-password2">{{_("Repeat Password")}}</label> - <input type="password" class="form-control" id="user-password2" name="password2" required="required" tabindex = "2"> + <input type="password" class="form-control" id="user-password2" name="password2" tabindex="3" required> </div> <div class="form-group col-12"> <button type="submit" class="btn btn-primary btn-block" tabindex = "3">{{_("Set password")}}</button> diff --git a/uffd/signup/templates/signup/start.html b/uffd/signup/templates/signup/start.html index a60dbdb2..ad150f4b 100644 --- a/uffd/signup/templates/signup/start.html +++ b/uffd/signup/templates/signup/start.html @@ -44,9 +44,9 @@ </div> <div class="form-group col-12"> <label for="user-password1">{{_('Password')}}</label> - <input type="password" class="form-control" id="user-password1" name="password1" minlength=8 maxlength=256 required> + <input type="password" class="form-control" id="user-password1" name="password1" minlength={{ User.PASSWORD_MINLEN }} maxlength={{ User.PASSWORD_MAXLEN }} pattern="{{ User.PASSWORD_REGEX }}" required> <small class="form-text text-muted"> - {{_("At least 8 and at most 256 characters, no other special requirements. But please use a password manager.")}} + {{ User.PASSWORD_DESCRIPTION|safe }} </small> </div> <div class="form-group col-12"> diff --git a/uffd/translations/de/LC_MESSAGES/messages.mo b/uffd/translations/de/LC_MESSAGES/messages.mo index 73a0ce629d71d5336446229934354d0b64f25f98..e58966921aed2726349395182b5de736f144f297 100644 GIT binary patch delta 5164 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 delta 5032 zcmZ4ak@3Vw#`=3gEK?a67#MUJ85m?37#LbOK|BP0BgDYK&%nU&R)~Q?n1O-euMh(R z8v_G_pfCf24+8^(gfIgG4+8^3sxSis7Xt%B5tLsG<#!1)Fz_-kFiaI@U|?rpU|1~7 zz#ziFz_41FfkBahp`PJ@Fav`<0|Ub&VFm_61_lNt5e9}r1_p)@5s1cPA`A@C3=9lc zL>L%c85kH0L>U+|7#JAJL>U;|85kJui!w0OGB7Zxi!m@LF)%PJ6=Ps<V_;x7A;!QU z$-uzCFV4Uq#=yX!DGu?7gE#|&1p@;^fH=s(^$ZN1;t(HAfzq>~3KxkpFgP$UFl-WM zU|?lnVE7EBe~LpK`X8#!P=bL$mVtr6QG$U%l!1XEUV?!^nt_3#RDyv)kb!|=q67m2 z2gv6V5QnUhfP~~>2?hqfdIkoD>k<qM+zbp1&m=%DVqo|L)$msW;xINzh{3#)kf2tU zgs3-=gaoAxR6Gz$$4N3USTZm$WJ^NyFNKP)kpu@h!%j(vgKkSQFw}$M?g`X_kCF@w z>I@7F>{1L2QVa|X`ce!GDGUq@UQ!STte0Y7;A3E5I0~h&NHH)dFfcGYmSSMAW?*1o zk%mO2gERv}CIbUQgES=0Ur9qO{3H!=&~Ir51_1^J1|}JXdT`<rmVx+4S%!gu8Dy~x zBnXXV7#QRj7#RFzAQt4wK+?o28HhnUWFQXOFT=oam4SiboD2g)1p@=a5?Ka@i3|)3 zvT_h}*2_WEpO#}_P-b9YxFiQj3*Y4GAqKF?Lj*+SA&E#;o`GRA0|SExRQ!)T#3wuo zkVGY-z`!8Rz`&rV0EtUC1&EKLp!{qFNN%cEfLJ_70ph{MQ2F%=5PJ`U6exmRqyP!J zFA5NgOcWs+Z4?<8v>6x}oE0I7FkcbkfKo*U1{MYehJHl`22hTjq6mqKd5R1SstgPa z`xPM}_f(OA!37jGN|31aRDy&+p%TQr`l(8gL^m6%V3`ucfOAR=4Dt*N3}2KW7K$h{ zfb+ecG6RDK0|SGpGQ`3-Wr#!Sl_80<MHv#36O|!pW}z|zgDwLD!*;0pr^*nYv#EeR zTF)S=0&#$>3Phs@ly9K|2{LCDh!6c$7#Q>!7#Jc|AQpD2FffENFfjB%)jv^zq@DLF z5Qlt*(xBh~B{p7F1_mJp1_n7*NC+6Jg3PUFU<g%(_%vRXfkB0VfuUX%lC74gGB8YG zU|`s(%D}LZfq}tK4U&pMSp<}yIn^O?E~E}g1Ip?Q44MoK4Cd;Pe4Ys9SExfgvPd1` zuyyK?sNSv4z>p5g|G%LMk~P3V!jP{4F|b?%5>(w93=H`U3=Fe07#P|Z7#O%T8NfyA zL`{f;=4(PMTmco|ugSn*%fP^JRud9s+***3719Fvn1Mk_3u3O076U^)D0^9JF)(N` zFfc?x6|`zWEa->w=W0O`%W^G9!Ln8hQa(J_f}{l|ZAg@8YeOtF&}LwW0EL7$#OD*V z85krO7#QYgLqcGyHY9`(X*1M=i^l8P5EngwDtNEWz%YY>f#Huf#D`OLAP$<R0}1jq zIuHZ)>Od^KqytGqH+3L!{6q&5BHwi&l@y;Yq+E#Bg*fbrE(60PP^G6=4>90?9;8e^ zuE)U8%D}+zQ;&gR0w^u$LsGwh0VGPa3>X*|GcYjN89-A1BLfBo0|o{LW<yBan;1eu zD9#X4In6MH=zm}cQUA-3fx(f1fkD~`Qn$p`8$nXzJR?XD9x{Ri^>HIe_Bm_B!0;AS z#2P^=nTy8IRBj9jiO<H6kYh1{ctqHQfgz89fkDcIfx&};fuYlcfx!z@zL-GLmaHiQ zgAD@%gQ_VcD(aI=Ar|JFLgK2_6jDhvnnE15*c4Kb95;nn@XQqAqqk5#yBQ=k^O-R) zM1gXN8Kk;i38h)hAwivJ&cKigDo@NA7|cLv&4Pg;22}q~w1Cv>?<^oCorxs_!!ZU1 zh6GE9Pt>g-_4za_28I#_28O#<3=GB$3=Bcm5TCbMLxOawHKgdiZq2};#=yYv%Nk;? zq79@#OSgg41-&*545bVV3@dFwMR+{}gS;)Igfh2fV2A{@WNaB25<x{MlvcEZ7|?9T zz;Fbl(T;%u)MU!Dha^fC2S|trIY2^C#{puIg##o?LmVK9IMM-<NRu2G7?>Cs7;+sL z7}!DizW~HwU|^_lfaKE#2L^^-Mh1on4v^F==LAW7hE5QJT%Eu{$KdA#Ndv2$AQta& zf;i}e6U3rxP7wVMogh*C#R-z8emOz1yMQwTLp{jH*3JwJ&Y(7$GXp~$0|UcNXGr$4 zaA9CbW?*3Ob%8{|Mi&N#Tm}Y)A5eapD+5Cj0|UcaS4h#U;KslZ3~E}rK|)}O8^po` zZV-oDb7NpIWnf@<=>~D2ygNfZxc1U=honvucZiQ{+#weFx<gXw6nBV*h3=5Vw#FS& zRKIbD#JQjcgf{Vj6ineBkfv9W2PDz9ctAp?8!A831LDCg9?<&#qz5E1-SB`Ek<UFK zLH*GKQjh<F8Yu1wage4b!~ioW-y2FtctY}hswc!lYds+j+3E=?CyskUqUr~fW~ujr zlvDy<3=DdpR;(8!pNDxtDxVTBNae)o4M_u<-Vle{dNVNSFfcIqctcWur8gw#cY8xx z!~8yw5bO1UICzE+#6e4aAm*&~fka9DK_5t>yW#^WSf2Pmvd;@2NUq@bWnc(lU|_KI zh4^HWFT|iFzL2O|<IBMCih+S)uP*~bB?AM)3O@#h)u3j)KcqxG=MV8QLjWY-^8`RF zb`AiWSI-a@015Jp07wy86u`i6h=GA&asb3(VSy0&v_MG66a+#NRbL<^u9pNt4BQ_G zY2jQ7gaq}kKuAbw2SI47Ac%U;AV>(L1TioeG1N0KR0J`A9LKOa2x8%%Ac%{3f+5wY zL@>kwVZo3@mk<oe*R8>jG}09eNqm!oAr9CU49N}mf+4wyFNA@?50u|SARgQk0?~Ij zgn@w@l>eWFK(fWB5J(!(2!+ILcqqijEujz%i$Wptt3x3c+zN&Gj4KR6ONK$RokkcW z(Yk~&Fjz4#FeHXSqH<0cILH}Jg+bEBoiGN5dQhM3IaK0P7$mW=ghO1a5)KI}*KkPl zJ2D*N^QqyG0%%q^B<R0~L(&dc1jHeH5s>;`HUc7V76B@185lexAnk?32uPyrieRV* zH@T)qKwP*d0+JhEMKCa|WME)mjAURq0O~A8LR_2^1qq4rD2TxgQ4sZ$qaY!(DhiT# zZ$?3?S5U_h)M0UqhD2>ZG^AEch=$Z9E2HZnsaP%sQc&o}K=@`c5Er}0FfjCh`gAc2 z4C@#e7~aG{f^c>$#0P6*A&F{NEX3zmV;LBn7#J9y#6r@HdK{#jFpPufbBlvGI3f;G z+2z;ALF(h#aS$Kgh=U})$58(JIEcf3#X+KgF&+}Pw($%MCZK9G9%4~Hl%5$6DWVt0 zL(CCOfK*<x36KzQPJl#BZ~`P7*XJZag0cjvpgn<sVHE=d!}J75k?E8O@j*Z$L}O|q zq_(R|gk-}Ri4dQyON3Z-AQ6&Fo+U!k2zL?#!z=~{hKwXg!Ni;lY1%a=gFRNyAe911 z?TRT33^PFuh!jYJ;YA80TUDn*f_7diq+D2=3aPyc(jYyX*=dlb*Ml@jCB~J`zz_s7 zCmoV|7N$eY-=EIF;0=nxbOr`b1_lO;3~2su%7CQatr?Kiy)y&ivway13?hsS3|BHB zsrF?iM4eC;#9*;3h&q!jNLuj9VqnN-U|<N(VqmaiU|=|$1+j=J8xplX*$fPUAo*-a zh_1|LVBi4d|9#nzxH_H<@yX?ENNWC@4av_sIS>mxav<3<GzVf}Sq`MKY0QCC!zXhf z-ENy)NNaalE(5~`1_lQCJV-%wArDfk>gO{sRDniN@);QFK`#234+&D|0tSXp3=9l; z1&}6EVj-kWcfF8-p@V^e!Ket5=yn%D;`~Dqq&j9Qh9pX-Vu%Ogiy;o{EQVz3i^Y%- z`(F%+V%ZXi$F`R+)PuWBmrEc%_*(+0L<&kF{8yzAgJa7W7(O#FFjSR6y5)K05C<q# zK(e1n1tf(0D<F;4m<mW#R#!kmwgF1_K*eWNK+2K175tEFu&e@-s@HA4DHOmYXrYjp zm!gnZqL7<kT%urPYBu?as3}%aZ8im`(h`M&&8tL>v^flo6%0+Rj7&B^aoENz<C~e6 ml3HAnnpdn~p-`HaqL7<dk(ryA12!<(Rhy6kt<CbTUB&<uOla-^ diff --git a/uffd/translations/de/LC_MESSAGES/messages.po b/uffd/translations/de/LC_MESSAGES/messages.po index 3e355080..bae852dd 100644 --- a/uffd/translations/de/LC_MESSAGES/messages.po +++ b/uffd/translations/de/LC_MESSAGES/messages.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: PROJECT VERSION\n" "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" -"POT-Creation-Date: 2021-08-13 14:24+0200\n" +"POT-Creation-Date: 2021-08-30 23:22+0200\n" "PO-Revision-Date: 2021-05-25 21:18+0200\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Language: de\n" @@ -267,7 +267,7 @@ msgstr "Enthaltene Rollen" #: uffd/role/templates/role/list.html:14 #: uffd/rolemod/templates/rolemod/list.html:9 #: uffd/rolemod/templates/rolemod/show.html:46 -#: uffd/selfservice/templates/selfservice/self.html:103 +#: uffd/selfservice/templates/selfservice/self.html:101 #: uffd/user/templates/group/list.html:10 #: uffd/user/templates/group/show.html:11 uffd/user/templates/user/show.html:95 #: uffd/user/templates/user/show.html:127 @@ -278,7 +278,7 @@ msgstr "Name" #: uffd/role/templates/role/list.html:15 uffd/role/templates/role/show.html:48 #: uffd/rolemod/templates/rolemod/list.html:10 #: uffd/rolemod/templates/rolemod/show.html:28 -#: uffd/selfservice/templates/selfservice/self.html:104 +#: uffd/selfservice/templates/selfservice/self.html:102 #: uffd/user/templates/group/list.html:11 uffd/user/templates/user/show.html:96 #: uffd/user/templates/user/show.html:128 msgid "Description" @@ -426,7 +426,7 @@ msgid "Two-factor authentication failed" msgstr "Zwei-Faktor-Authentifizierung fehlgeschlagen" #: uffd/mfa/templates/mfa/auth.html:12 -#: uffd/selfservice/templates/selfservice/self.html:69 +#: uffd/selfservice/templates/selfservice/self.html:67 msgid "Two-Factor Authentication" msgstr "Zwei-Faktor-Authentifizierung" @@ -473,12 +473,12 @@ msgid "Disable two-factor authentication" msgstr "Zwei-Faktor-Authentifizierung (2FA) deaktivieren" #: uffd/mfa/templates/mfa/setup.html:18 -#: uffd/selfservice/templates/selfservice/self.html:75 +#: uffd/selfservice/templates/selfservice/self.html:73 msgid "Two-factor authentication is currently <strong>enabled</strong>." msgstr "Die Zwei-Faktor-Authentifizierung ist derzeit <strong>aktiviert</strong>." #: uffd/mfa/templates/mfa/setup.html:20 -#: uffd/selfservice/templates/selfservice/self.html:77 +#: uffd/selfservice/templates/selfservice/self.html:75 msgid "Two-factor authentication is currently <strong>disabled</strong>." msgstr "" "Die Zwei-Faktor-Authentifizierung ist derzeit " @@ -758,11 +758,11 @@ msgstr "" #: uffd/role/views.py:44 uffd/rolemod/views.py:36 uffd/rolemod/views.py:45 #: uffd/rolemod/views.py:60 uffd/session/views.py:130 -#: uffd/user/views_group.py:14 uffd/user/views_user.py:22 +#: uffd/user/views_group.py:14 uffd/user/views_user.py:25 msgid "Access denied" msgstr "Zugriff verweigert" -#: uffd/role/views.py:51 uffd/selfservice/templates/selfservice/self.html:88 +#: uffd/role/views.py:51 uffd/selfservice/templates/selfservice/self.html:86 #: uffd/user/templates/user/list.html:20 uffd/user/templates/user/show.html:21 #: uffd/user/templates/user/show.html:90 msgid "Roles" @@ -797,7 +797,7 @@ msgid "Set as default" msgstr "Als Default setzen" #: uffd/role/templates/role/show.html:19 uffd/role/templates/role/show.html:21 -#: uffd/selfservice/templates/selfservice/self.html:119 +#: uffd/selfservice/templates/selfservice/self.html:117 #: uffd/user/templates/user/show.html:11 msgid "Are you sure?" msgstr "Wirklich fortfahren?" @@ -980,7 +980,7 @@ msgid "Forgot password" msgstr "Passwort vergessen" #: uffd/selfservice/templates/selfservice/forgot_password.html:14 -#: uffd/selfservice/templates/selfservice/self.html:22 +#: uffd/selfservice/templates/selfservice/self.html:21 #: uffd/session/templates/session/login.html:14 #: uffd/signup/templates/signup/start.html:19 #: uffd/user/templates/user/list.html:18 uffd/user/templates/user/show.html:48 @@ -1023,35 +1023,35 @@ msgstr "" msgid "Changes may take serveral minutes to be visible in all services." msgstr "Änderungen sind erst nach einigen Minuten in allen Diensten sichtbar." -#: uffd/selfservice/templates/selfservice/self.html:27 +#: uffd/selfservice/templates/selfservice/self.html:25 #: uffd/signup/templates/signup/start.html:32 #: uffd/user/templates/user/list.html:19 uffd/user/templates/user/show.html:63 msgid "Display Name" msgstr "Anzeigename" -#: uffd/selfservice/templates/selfservice/self.html:31 +#: uffd/selfservice/templates/selfservice/self.html:29 #: uffd/signup/templates/signup/start.html:39 msgid "E-Mail Address" msgstr "E-Mail-Adresse" -#: uffd/selfservice/templates/selfservice/self.html:34 +#: uffd/selfservice/templates/selfservice/self.html:32 msgid "We will send you a confirmation mail to this address if you change it" msgstr "" "Wir werden dir eine Bestätigungsmail zum Setzen der neuen E-Mail-Adresse " "senden." -#: uffd/selfservice/templates/selfservice/self.html:37 +#: uffd/selfservice/templates/selfservice/self.html:35 msgid "Update Profile" msgstr "Änderungen speichern" -#: uffd/selfservice/templates/selfservice/self.html:46 +#: uffd/selfservice/templates/selfservice/self.html:44 #: uffd/session/templates/session/login.html:18 #: uffd/signup/templates/signup/start.html:46 #: uffd/user/templates/user/show.html:77 msgid "Password" msgstr "Passwort" -#: uffd/selfservice/templates/selfservice/self.html:47 +#: uffd/selfservice/templates/selfservice/self.html:45 msgid "" "Your login password for the Single-Sign-On. Only enter it on the Single-" "Sign-On login page! No other legit websites will ask you for this " @@ -1062,27 +1062,22 @@ msgstr "" " Webseite wird dich nach diesem Passwort fragen. Es wird auch niemals für" " Support-Anfragen benötigt." -#: uffd/selfservice/templates/selfservice/self.html:52 +#: uffd/selfservice/templates/selfservice/self.html:50 #: uffd/selfservice/templates/selfservice/set_password.html:14 msgid "New Password" msgstr "Neues Passwort" -#: uffd/selfservice/templates/selfservice/self.html:54 -#: uffd/user/templates/user/show.html:84 -msgid "At least 8 and at most 256 characters, no other special requirements." -msgstr "Mindestens 8 und maximal 256 Zeichen, keine weiteren Einschränkungen." - -#: uffd/selfservice/templates/selfservice/self.html:58 +#: uffd/selfservice/templates/selfservice/self.html:56 #: uffd/selfservice/templates/selfservice/set_password.html:21 #: uffd/signup/templates/signup/start.html:53 msgid "Repeat Password" msgstr "Passwort wiederholen" -#: uffd/selfservice/templates/selfservice/self.html:60 +#: uffd/selfservice/templates/selfservice/self.html:58 msgid "Change Password" msgstr "Passwort ändern" -#: uffd/selfservice/templates/selfservice/self.html:70 +#: uffd/selfservice/templates/selfservice/self.html:68 msgid "" "Setting up Two-Factor Authentication (2FA) adds an additional step to the" " Single-Sign-On login and increases the security of your account " @@ -1092,11 +1087,11 @@ msgstr "" "Anmeldung im Single-Sign-On hinzu und verbessert damit die Sicherheit " "deines Accounts erheblich." -#: uffd/selfservice/templates/selfservice/self.html:80 +#: uffd/selfservice/templates/selfservice/self.html:78 msgid "Manage two-factor authentication" msgstr "Zwei-Faktor-Authentifizierung (2FA) verwalten" -#: uffd/selfservice/templates/selfservice/self.html:89 +#: uffd/selfservice/templates/selfservice/self.html:87 msgid "" "Aside from a set of base permissions, your roles determine the " "permissions of your account." @@ -1104,7 +1099,7 @@ msgstr "" "Deine Berechtigungen werden, von einigen Basis-Berechtigungen abgesehen, " "von deinen Rollen bestimmt" -#: uffd/selfservice/templates/selfservice/self.html:91 +#: uffd/selfservice/templates/selfservice/self.html:89 #, python-format msgid "" "See <a href=\"%(services_url)s\">Services</a> for an overview of your " @@ -1113,17 +1108,17 @@ msgstr "" "Auf <a href=\"%(services_url)s\">Dienste</a> erhälst du einen Überblick " "über deine aktuellen Berechtigungen." -#: uffd/selfservice/templates/selfservice/self.html:96 +#: uffd/selfservice/templates/selfservice/self.html:94 msgid "Administrators and role moderators can invite you to new roles." msgstr "" "Accounts mit Adminrechten oder Rollen-Moderationsrechten können dich zu " "Rollen einladen." -#: uffd/selfservice/templates/selfservice/self.html:98 +#: uffd/selfservice/templates/selfservice/self.html:96 msgid "Administrators can add new roles to your account." msgstr "Accounts mit Adminrechten können dich zu neuen Rollen hinzufügen." -#: uffd/selfservice/templates/selfservice/self.html:113 +#: uffd/selfservice/templates/selfservice/self.html:111 msgid "" "Some permissions in this role require you to setup two-factor " "authentication" @@ -1131,11 +1126,11 @@ msgstr "" "Einige Berechtigungen dieser Rolle erfordern das Einrichten von Zwei-" "Faktor-Authentifikation" -#: uffd/selfservice/templates/selfservice/self.html:120 +#: uffd/selfservice/templates/selfservice/self.html:118 msgid "Leave" msgstr "Verlassen" -#: uffd/selfservice/templates/selfservice/self.html:128 +#: uffd/selfservice/templates/selfservice/self.html:126 msgid "You currently don't have any roles" msgstr "Du hast derzeit keine Rollen" @@ -1143,15 +1138,6 @@ msgstr "Du hast derzeit keine Rollen" msgid "Reset password" msgstr "Passwort zurücksetzen" -#: uffd/selfservice/templates/selfservice/set_password.html:17 -#: uffd/signup/templates/signup/start.html:49 -msgid "" -"At least 8 and at most 256 characters, no other special requirements. But" -" please use a password manager." -msgstr "" -"Mindestens 8 und maximal 256 Zeichen, keine weiteren Einschränkungen. " -"Bitte verwende einen Passwort-Manager." - #: uffd/selfservice/templates/selfservice/set_password.html:25 msgid "Set password" msgstr "Passwort setzen" @@ -1417,45 +1403,56 @@ msgstr "Ändern" msgid "About uffd" msgstr "Über uffd" +#: uffd/user/models.py:52 +#, python-format +msgid "" +"At least %(minlen)d and at most %(maxlen)d characters. Only letters, " +"digits, spaces and some symbols (<code>%(symbols)s</code>) allowed. " +"Please use a password manager." +msgstr "" +"%(minlen)d bis %(maxlen)d Zeichen. Nur Buchstaben, Ziffern, Leerzeichen " +"und manche Symbole (<code>%(symbols)s</code>), keine Umlaute. Bitte " +"verwende einen Passwort-Manager." + #: uffd/user/views_group.py:21 msgid "Groups" msgstr "Gruppen" -#: uffd/user/views_user.py:29 +#: uffd/user/views_user.py:32 msgid "Users" msgstr "Accounts" -#: uffd/user/views_user.py:49 +#: uffd/user/views_user.py:52 msgid "Login name does not meet requirements" msgstr "Anmeldename entspricht nicht den Anforderungen" -#: uffd/user/views_user.py:54 +#: uffd/user/views_user.py:57 msgid "Mail is invalid" msgstr "E-Mail-Adresse nicht valide" -#: uffd/user/views_user.py:58 +#: uffd/user/views_user.py:61 msgid "Display name does not meet requirements" msgstr "Anzeigename entspricht nicht den Anforderungen" -#: uffd/user/views_user.py:63 +#: uffd/user/views_user.py:66 msgid "Password is invalid" msgstr "Passwort ist ungültig" -#: uffd/user/views_user.py:77 +#: uffd/user/views_user.py:80 msgid "Service user created" msgstr "Service-Account erstellt" -#: uffd/user/views_user.py:80 +#: uffd/user/views_user.py:83 msgid "User created. We sent the user a password reset link by mail" msgstr "" "Account erstellt. E-Mail mit einem Link zum Setzen des Passworts wurde " "versendet." -#: uffd/user/views_user.py:82 +#: uffd/user/views_user.py:85 msgid "User updated" msgstr "Account aktualisiert" -#: uffd/user/views_user.py:93 +#: uffd/user/views_user.py:96 msgid "Deleted user" msgstr "Account gelöscht" diff --git a/uffd/user/models.py b/uffd/user/models.py index 81a6e363..3a3069f7 100644 --- a/uffd/user/models.py +++ b/uffd/user/models.py @@ -2,7 +2,8 @@ import secrets import string import re -from flask import current_app +from flask import current_app, escape +from flask_babel import lazy_gettext from ldap3.utils.hashed import hashed, HASHED_SALTED_SHA512 from uffd.ldap import ldap @@ -36,6 +37,20 @@ def format_with_attributes(fmtstr, obj): return fmtstr.format_map(ObjectAttributeDict(obj)) class BaseUser(ldap.Model): + # Allows 8 to 256 ASCII letters (lower and upper case), digits, spaces and + # symbols/punctuation characters. It disallows control characters and + # non-ASCII characters to prevent setting passwords considered invalid by + # SASLprep. + # + # This REGEX ist used both in Python and JS. + PASSWORD_REGEX = '[ -~]*' + PASSWORD_MINLEN = 8 + PASSWORD_MAXLEN = 256 + PASSWORD_DESCRIPTION = lazy_gettext('At least %(minlen)d and at most %(maxlen)d characters. ' + \ + 'Only letters, digits, spaces and some symbols (<code>%(symbols)s</code>) allowed. ' + \ + 'Please use a password manager.', + minlen=PASSWORD_MINLEN, maxlen=PASSWORD_MAXLEN, symbols=escape('!"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~')) + ldap_search_base = lazyconfig_str('LDAP_USER_SEARCH_BASE') ldap_filter_params = lazyconfig_list('LDAP_USER_SEARCH_FILTER') ldap_object_classes = lazyconfig_list('LDAP_USER_OBJECTCLASSES') @@ -129,7 +144,7 @@ class BaseUser(ldap.Model): return True def set_password(self, value): - if len(value) < 8 or len(value) > 256: + if len(value) < self.PASSWORD_MINLEN or len(value) > self.PASSWORD_MAXLEN or not re.fullmatch(self.PASSWORD_REGEX, value): return False self.password = value return True diff --git a/uffd/user/templates/user/show.html b/uffd/user/templates/user/show.html index 59ef1bd1..052dd09a 100644 --- a/uffd/user/templates/user/show.html +++ b/uffd/user/templates/user/show.html @@ -76,12 +76,12 @@ <div class="form-group col"> <label for="user-loginname">{{_("Password")}}</label> {% if user.uid %} - <input type="password" class="form-control" id="user-password" name="password" placeholder="●●●●●●●●"> + <input type="password" class="form-control" id="user-password" name="password" placeholder="●●●●●●●●" minlength={{ User.PASSWORD_MINLEN }} maxlength={{ User.PASSWORD_MAXLEN }} pattern="{{ User.PASSWORD_REGEX }}"> {% else %} <input type="password" class="form-control" id="user-password" name="password" placeholder="{{_("mail to set it will be sent")}}" readonly> {% endif %} <small class="form-text text-muted"> - {{_("At least 8 and at most 256 characters, no other special requirements.")}} + {{ User.PASSWORD_DESCRIPTION|safe }} </small> </div> </div> diff --git a/uffd/user/views_user.py b/uffd/user/views_user.py index ee5bc71e..2b273946 100644 --- a/uffd/user/views_user.py +++ b/uffd/user/views_user.py @@ -15,6 +15,9 @@ from uffd.ldap import ldap, LDAPCommitError from .models import User bp = Blueprint("user", __name__, template_folder='templates', url_prefix='/user/') + +bp.add_app_template_global(User, 'User') + @bp.before_request @login_required() def user_acl(): #pylint: disable=inconsistent-return-statements -- GitLab