From e5202d9b2bf3cbd0703987857083e8741acee720 Mon Sep 17 00:00:00 2001
From: "Adrian A. Baumann" Alle Vorgaben haben Referenzen, Stichworte, Text und Checklistenfragen.
- Alle Vorgaben sind vollständig!
+ Alle Vorgaben sind vollständig!
a1iYhmiu2{RVtg;;2iNOmGf1{_<^pR9$#hQ&H`HJzB zM!<=!gV7L)w$GXYuO72SKaJ>JQDtd%e9_YFdepX4;$4NosU9mv1oHtgAK)r3__nE4 z<%3D87QC)S8){2O*!CQX%RCz zqh?)*!|bd`#-=qDn_Nv9c1C7;*iO&Lc8S7vPGqjmr3-kUU@zW}`&Qn oq755N4zBgwaFjOM7(LQZ!9~XO_>Chd&X+`yXn+!Roh1pbGbM1H=w35~ z#l*U89ye+|rTQW*N%>oD7r&&XQj@Tl(*y-JE!&s|tbCr4FR-_Bj3BU+uNhN-_0824 z)$i&wtXyIpxBkI=>lSt*!ztEBxYw$~^;RzDm}-eZ`VKguY{MV_@YjPN1NIT@W4@Kb zV~qFR@p$P~JrP$D2Y|zuYVkzBZ!8Y?bdz8|KE=QAKGVH0#?2qo+099SVyY}^!2PhZ z=&&;di3c^&RaI-wWJ(((yLVKB%}j@Lg42BmMq9&KqnO|TKEub3-2&5{SvEZ8z=a-G zJRf$_fJ9wxcekj!n}SGR5CiseRSf)|1hW1D(EYrj0nX-zo*=qF!L0!}NN~`N_97ds z1J(8x%dJG+rWUH3)iSkIEn>kI7z7qP4}~J1w?fu>5f*B+SWR=aUlzEe_av6q0i6n; z%}z(T3u}37gU&i1L7JqF$L+ijIqVn-2(eK%%|li-1fP4846BCVPW&AY;RP(gJWNB) zx?=rgJ+f*z!+NW_X0pI)Cetl@k_^K~uobspy5X~$tYwzZyl!qa^Nsz+GLC)XVZJl; zDHSV>v&1p#mD$ex7JAs}@`8t_uX)(nt2FNdMJRKWda;5x@{E=35x?q5GO&^VX+Fe% zVk7>6U3e3(;UzqWr!dCHdN(?aby6X%@EqkgrH7pDXWiNUi=?s>2c?2BQ#{a3&|@tz zt35IOp0280YM8DR4ng I-r9zlV))YrzilHd`!Ma z+399!Gl-f-mP7;gB6V? n zx_0S(cZh~k*ieBE{ugI2pT86~yz|C`)wE4#w9WH;#sBjosQ<9JOl&h~gB0XauQ*1z ynIiqjk3TqexmhswJAORG?J~DZ+*-NaW&6s_a-^x6 94E4qU_v5gMk8q?g^(zY6UV1~#x?{;wj@hFWLuXH zHW-Ny*oBlNm@ |(+v{}N9D>vBb~mg6`5nG^C>)B1Vw0iR7xK#J zUUK17_wbjikYw5Ac3pKTg)N`(1&0SxL!p@FfhcsRhvR*L9{3sP84Tt9=^hEi6T`7N zg>x*u!$X?bg woZ z>Y8n1J?U`oj%c#0x38+pUAHpS+O)1}a$Q~DczAov<(4`l6UlTel$hAj5SB*6ZONLN z&RSQ<-8tHsb9LUF_jo1I=5RSAyEGlWr54>aT_)Ul>uHi9_~RJ=0e_r-XQp7+bG%$Y zRG_o^La; &S_V>cUL!}!0?m=6>po$)p8g26n<%)IshPhJZ1s3 %?DTL`D~Wp4FO2pE6H
gioE(tTy`%m8k-k8{6->q4!a|M7 zneG#MB8jAZBpQ>X*g(H?BoTLp;*&8cFf!RYv_NB;7?)*140-xIVt*(a8k0x*>`7rP zJS>Ey r3ilhM< zNQOU!-OR1U_)k^yQB;JewH#cUTdZ=lj-pi2iXOvDK?4u>+=n0x28MqZyO|AQF!W|Y zvmx3q&^=)!=35dP1|iU`{08l7waxo^k#!82=ak6aExL>kqt+A`J$FtDiI?#sZ=I z2;)EE&+w(m;D>~%m0o4KqJ4)Q#rEw>0YE4EHT|Rn0~juOLx6K ztsmA$^a0&Zboc7Mr@K=(t=pr!MlC#oEJ$%Zg?>xw9@iaK7CeoD>NQWJj|goXHjyZ% zMVCNBT0^42d^3=5`twaR-$e3FpVnAWIM=98{}OFB5Hb2)l|G7&;|%>7{Wm>9pQhi1 z&`^|Sq?GvF2t^ohG?@F&nIKTV%KRFALc@~$q(Z-m`f%n+5Nu>hOB^J&eD#+s%Ia_5 zL+dE?qRO5|A1}YSfcpUBujk+9f5<<>hxu*%CA`7(qUk}?d!}!g8ci{iU;-A<`#h(7 zV;Rm&* r}XaV1-Rtvb7n-&D~jr%s>>b?y`(oUp|vO 0whF1n)RE$yKx8V%v-_g4lh=t(6%Ig=HvwOEhGu8}17RlA)L-5sd{cuzgzU1F`5x zYA~SXGnN#9b
1d)H7Y2o8HEL{q~B>V$E>pV20( zde>!Cn9$w8!V>eI%5I{bW%v8_Im+vG)TIPO?^_D(r`D(rKh?s5nT5I%63HkR!A0s% zucZ9IWcrOl@6wodQFod4`wi-EV$>Q$SUDEPh^klHN2q%>VY31QxI|sGoBAETjN`wr zt*IaIf8y`wf6u?cpX6WVpXZ E5tdi@)Ek=TX*5S`Nfg4%?AfKFj!lu~wp z7L*+iQ{Pr@dR%u#J#~ h!k=L? zR>>pOXYa4zZo*7hoM4}3G3GPIYUrhZK_{tg#FhA?u#(1gPwEf|G#Ia|$gHA^H*CPK z?^d4r4zZ|gHo^M*6+b7(#O_v3KcZVs&$1~8kKxXR$ *@J2UhF<)7x-N=>-U9372kKs;{I8Na?2& JbZ^d~#d8jwE0^=8aGp1_4CSlUi&WkFNbUWZ zu7ZS-$I~#$;`rfR1Zh^uedyEWH=NcTK24Jchzzj@CZhgBGg(iHWD#+Oc#C+JxQEyS z5?OreA`(fsBMlkKTsUUUVJDWt%Y!?Dp}|lxbT09Dp4b}+#QM%7{%Q${L>w~N;JISQ zaun$cb63pOHrP>kSDm #!!gNDr$d)B_e@53GH$XfkPm)Y}q@#w@j|_@p+QZRohx zsP5y2SL*cN!Lc*gchDV*$->lJgqq0!xr+EJ@ffk2s3#WTZ{zpjVc4wy1Kz7k)l50F z9 S^?-QtNUDsn zvSPLlkac DA{~FmAS-gT;fUKgLY2b3bKoF-DlphNbk!x)apH hORA-1q;61_ zTidKd{n75Kt?`=5Xia5{uc@xS+c7U!(CYMgsYJGHLC);gAtX4u!nmmtqofft#X6t3ifvh9absbGTfJLY8rD0o%4J+ zCtt~y736&Qx)R|6K6F%X_tyIZZDY>9t>Ts~qocvGy72+4FCO=e_Y4dVHgwcShT=V& zh4$Wbyrp-Z59cIB*)l%o!~6>5LIucqKGZaJRBiW4W1i-=l%vZr?hN|Id%Gs2@zIf< z`oXS5vp8yZcUS}IE!#xVA$!Ko_F;bJHM8ZBL)6p?GdR-b`??}0??`=B<5<+!)aNqcXG3ea(krGL zJF0zNAk~c3c>MlRQLd|!M%$~l^!8Ma20JI)eO=*-v5xwrv(_cl)Fp?6vx7B14ZR1- z+FiE`993-0VElmTd#0V-zjGmuWe>9L#xuqYyjCG2JC= aU60top;UkVN#l<%=r=~jOzJ`_zPv}3RGox6AM zB h^1RWJ<2Qv6zC7ySHp#)CQOE+pKLU}FZJV!JoaQ5M{c_fF^ zDRyKBzDkMkl}aR=Xm`71p*`CVO3J_!qy&yp6~eZ8V(0b%j(t3Emf?1I +wWQ#XP1|fOiDUpyjdBq<$9I^@cBf5{oU+rNS&)rD+$&IK`}rX#Bd8K>7)(=D zLxJd^1-7bqD3K_K#Ha;kW_Zqq_WPf+E4pkBJA~fj%l2t8Gpsq!>J?X)gPl;Y+-tcS zMla`k#q2o_1YSG4blKdJTd>QSt=TYxE!>E*MWDtZ3(C5uxFyQ|_o+q7QP^arzM^=z zG9?IM*Zcw^hxBSiL^;^Co;}{8%PZP#9#Ik;j!Z!|1ZE|aku|d}xf#wVVM=Yc*}ax} zn0 R8j?V{ab*N%Tki%K44@l8|rW00 KSrl$nZ|U7bmO`WI-Gix8m64&r{ojljbt;qjChYYOk|1m1dsm?zZXy9n{gUl ziC#pvVShm77`h+QfR(Cq3HPFo-I5)JUAGhV;bOh=O*6NiwP#0Qua@)38)j}bcUg8A zc&c){w@Eqn5W7?n3b;j_KN|ylD2H=O-9zj}O00le%gWljo7>wVwYaB%v$3_>n}~XI zoXXn;+-Arc;I*2|8pH+MC6FV)JAx|mJmCeLmLtIIpS7zf3pj~$W+Q-TzLn@{<<$q+ zBIVQqZV}`TFyZF167xdN%2sB>fVAYb)hy(^Y;!gQ$Y>tfzmVI``LaPk8gmGt{NH_S znPMn}eqEmJ1$=uB$Cdr}vCEWNz}f0-4@9OruOU{*ZDifq0C0+cROpqT7jo;^%d@+H zXJa1usE~8AYqQ tFk*mwkgl~WD$26 zw?2C%a8?3RqF<=&{}EfFtXu@1tj%5l_^KSPY+J;wfS7jyqUBQ%^RY#ojcd(z0+Pxh zW@X#mtV8Kq46>WD9e~f~bWAZPL%((avNJE+Pz?Q&v+ZAKyR(?HnL^n%EjcX8F|msO z2kaK5L{qmu+X}3C7nREwb9NYv7C>fG#NCTwFxF+80a=qbpe}~4wq% 8DwcMDeMThoO%-;Ur>Y*uCeHOc+CBTR(pO)yVcu9xDlSXly)ifwOo~1 ze_Vg7zDZw5ze)dy9;98c`@F83k}|Cnh9uvG>WRug%%bJ}miGK#8rItU9~ObQoM{0m zx$45Dh9;LU3N9qsoR`d322 ; zUvj;hn%6zcC9Uwk&EQ}t7KkO_Po?DQ%QKB2?QFYXiF1B0FQlL$ui!#;kfz{@vpajf r26v_&G^}epzgjw{&*zJ(rhnr;YS$tDC5(Rwj!K{A)tBDq9G3qFOt0t> delta 4927 zcma)Adw3L8makinuCD63RSB;=o36YFY)I3OB%K5pNJwY`iHQVoh&a$mD(N;!cjyNQ zikRAg_*elAyLTLSXYd l&Srv7i9T9O)RW+~K zKX%ipuFCJ8bM866bMCpfa?if{d-m1uDav(I6!jilZ^BhZQE2@`bAz0l@^0ODsQ)g7 zp8@dlG3UgGikm5Qd@{l3w2EBx%4f2~_XEe|Co{qVdSbFpeFi&Dmb#fXL~ m j#sG%&2mPlE5kJXKE!=Z4YHqTGAd* |#Z%ehb4@4Uh_21nx6pJri8SrmxNN;H^Z(G{jvw0|R)0Uw-n!1LA{@&H` zaBq0?9V5QfP}R~kvG#CJ`%*2lHoUQBVZaxtt@it?12w9zp)u%dtnqn6zEDGbV4`sM z3Up|q#P>|jSP^Z_>U3i)2thni_297ATU+a^4+hd&Ds6^m8}d~L8dQI6qu f+Qn>WlM^TwKL65T?{Z{z2rU9PCuEEMy9cAVi3vVX%w=@u|kP8+it zq{E!?RY>HW<)&AX+)ByM$(NO7@>|NgilLOrw<{0JyOj~S)%<`~iOiNW9f)`vBy}?O z+M~#Ul&>h|D -z`S=`~DqEvc zKc#%Fe5rg&e8*5Fndd{Fg5YA8(Z2xgU`jZ~v~7ONjQNdemdP4M%0=+XK&tD4VqEM* zq6z_h4c1KPGet24X>$&wT%wdqCi=NzT-uGCzMPojG0M5k+2h>gOgN*?h*5qFsfczQ zAuTVWcS-qi)bDh>;P@xUe#c|Ry~oj?7%1WD5(k6m0E*eSsC^qWZ`06#^_Ow{Wz;M% zF{fz8e42((e4HV^i7xW6Al@bXc{B)ETr}SO2+iZrNkh7T{_IgD`9VtAq5NKXT=|7E zpoEp#iXgu#KP`VGKPboLPSA q#@JgKPs-!(DwNz|4rp34B z&7wmjXB#)&c&(bQqd{6HypLW)W8GNlqZ_e|84tQDUOB#JA#Lng$fN_zCRY{VN0~p; zt|~Ab5+xT?K(F)%f;F}19V2un^DCAackM9vdl-yp*A5U$h=OCx$x^g{!p5!#m`&3d zue6i!` YRl^mO`Pp?d9at%*{-(B;``dOUh=YTFR9V%B$sE zd>U`X%cLJjtHH`=E+h9S^994Eq!%b6?cr<4f@92+ eCAbROxAQL2{hmE5??u^UHGqf%6!v);!jB{K% zMJXRirwnm1_mbq`7y&x1@kTr6Wq_D*!gq01v@~u6y0~sBFNXzQt|O+Vh(#e}5i`Sh zZXMSJO7d?BzX6ow-x`4p+>d15!O);nW4smTst_2x3_0UEXz8-CYLweA-I(4Q0_iO< zDz _xa6gH+;vENPjouZI{nNB`f>EJ{h>B mK&YBgZ&&q1thVlF>Gb`<}c5-!C1PDDfMyTIk~= zd?EWJeU#2e+t8cnP3j1Bgq;Q^I+5Xi*6|)={QL;F39*7eEuARwH9K3zR7c(|ewutT zfbz-j&e4ML4~FAqXs8s`0}^YDzULj4EcyH(_qDP3Fn1?35(Di)-_F~4!ExjQ&+cQk zumcda^9z_|m>D0~>of-T3O}096f*VD!G&7DFVRN*Y2meY=U$pVPmeKo6Im6P7cwC> z!d5U}GDqnqItTpy35uZ#>NvF*W-iUE(z^r-m6T9Lu<>#a?C8$+dyhQKoyAGS0)A#ZT!6|&PP(6 zzAS69LNHF0iS?4%Kfm=D-*$>f%|7Qq>-8AcH>A6KoH->L%qiD5>;Lz-DOHg}HV(UL z1b?8;yUJVJsM@jJ(l3TZ8w=C{IHgql4tai13{SPeP@{SsZMg%ku+tu>ZzK=hCAi6H z56&Ydo^Rs~jp~v_GN}z)%Uf-0Nm-*a?NQjs)s93wmQEyN@j*2c?^oLsgM%@$cu;K1 z`r-G3AF>A*S&CP{0Dw=<#4Y5`|Kb#)4!G*evnGR$>as*%bd#1GSeNnn2KokjBgr8x zmR4skYa;K_vY&KSIEw04dHpqwYFj+54JKo0Q&g)-r&4{ *OF@6 zNMum6z4vCP0@_+K$-Zb-I-1Nt5StQlTUK4E^=4wj{kF~($4yqp-MHq4Xn bmc$qRI~$F`b_;IW9 p^t{2<_JTl3Lo1&+@w4)NJe5;^`}ll2t`mz2;H3X@R(k@JzCKDC=dn=>z#?Auh2( zTx8DF?S }9`8_TB zth5()dm#g>M@=IsD_2)DOg`%qmsy3`?S+K2#^SxPIK*N}tw{`LMs73?`5|`|;9|?` z0^4iIONSO8G=GzJ^`%Hj!sR9x2E;0hL!bzXzV(i@mW*paeQz?7jAacs*hb8X@9vA5 znS%B-Jgmhd@yPI&ly)uXt4?2EmR5;@eUsun`A|*2JjdJFyE0-Dy)Yja=Gnd_Rg=7C zEeWf`n#n4RE5eWgayc&>pT$d$nci+~)b!ZON INJA@HSEBZSeX1A^%vV9u=vAHZ&F{W5`iOe(b~Em7Qi)wLo~+ zA1~CAOe&=&>;nhHpv6l{w{5%aFO2)(J*CDQXb9AWd}GD>ph)Ef(O4V#Y?-5o%n#ul zvN-MZkhv8OLAZL5B4dpXcjJwwAjXD9U&tG*4?&Er`hY-rJVF%`ny#u{eIV!0^3zp# zSGr10Z9R}v=dY>#=`vljeE9d0|HFq!FW*l7JLH&Sbwu(=y$}|?Mfy##US-o(z7O@ zSeoE9)m-^0yEszSs}e(IQ<=^{a*dLM%mLS=&vz?pu%{$?@%H;D=kJ_PIX5~3{44w} zzS(iv@q}Z@F`v7{O>*nFx$H;mKeD~>^7;<5gZU1_(y!7F&^>eodILR*!icASL*1tw zR~}FnD~x;sj^<17MSKj8<2F1+IxGE5%1BKTcD?NSq3d>6Q2bW>mH4pmp}0o$2pffG zgq&LNs#V_zJL_85bMp`x%fs{WUj_A1*qK8%bRrMW!E+Ys36N^FH|uZma1hVFS&suW zVnYk^aTr(D>mvY}`*#r{yYsOZ&uZ6)0qV1%SMqT)t_bKu0A-H{c?EbjF8AuU15{~C ztti0dxNM;w11LM=odqysX`LPg$n*=$|4RX$i%UZKAVAsE$L9sO8mld4jiGK^s;Uq- zVo$fO0kqMEHWmUsGn@5(fI4mH 7TFN&8MQMCs`d3C G4|%7#mUBET!`~m>EDB~A#02@ z731YNZ>fG8Sh(4SU;|r#XDrayfz%R!AlP%oIDn^D>$d__VMA^=Y-!W#^&Wt-jJLai z@u{ozwE$&oshw`T0#9kuzYCCkf+J_$ID~WC^jiSB&6ZMULS#Ah`kHIDhGt@~tPJR@ z&Ew#7TkMIM_*N`0&{u(2)-mh>$8bpR1}K|_6&}cf
Date: Thu, 27 Nov 2025 23:51:04 +0100 Subject: [PATCH 3/7] XSS protection added to comments --- argocd/deployment.yaml | 2 +- .../templates/standards/standard_detail.html | 32 +++++++++- dokumente/views.py | 60 +++++++++++++++---- pages/templates/base.html | 2 +- 4 files changed, 82 insertions(+), 14 deletions(-) diff --git a/argocd/deployment.yaml b/argocd/deployment.yaml index deb99fd..61ed7eb 100644 --- a/argocd/deployment.yaml +++ b/argocd/deployment.yaml @@ -25,7 +25,7 @@ spec: mountPath: /data containers: - name: web - image: git.baumann.gr/adebaumann/vui:0.958-comments + image: git.baumann.gr/adebaumann/vui:0.958-comments-XSS imagePullPolicy: Always ports: - containerPort: 8000 diff --git a/dokumente/templates/standards/standard_detail.html b/dokumente/templates/standards/standard_detail.html index 033f96f..6f43da3 100644 --- a/dokumente/templates/standards/standard_detail.html +++ b/dokumente/templates/standards/standard_detail.html @@ -219,6 +219,36 @@ comment" + ) + + self.client.login(username='regularuser', password='testpass123') + + url = reverse('get_vorgabe_comments', kwargs={'vorgabe_id': self.vorgabe.id}) + response = self.client.get(url) + + import json + data = json.loads(response.content) + + # Find the comment with script tag + script_comment = [c for c in data['comments'] if 'script' in c['text'].lower()][0] + + # Should be escaped + self.assertIn('<script>', script_comment['text']) + self.assertNotIn(' comment"}', + content_type='application/json' + ) + + self.assertEqual(response.status_code, 400) + + import json + data = json.loads(response.content) + + self.assertIn('error', data) + self.assertIn('ungültige', data['error'].lower()) + + # No comment should be created + self.assertEqual(VorgabeComment.objects.count(), 0) + + def test_add_comment_xss_javascript_protocol_blocked(self): + """Test that comments with javascript: protocol are blocked""" + self.client.login(username='testuser', password='testpass123') + + url = reverse('add_vorgabe_comment', kwargs={'vorgabe_id': self.vorgabe.id}) + response = self.client.post(url, + data='{"text": "Click here"}', + content_type='application/json' + ) + + self.assertEqual(response.status_code, 400) + self.assertEqual(VorgabeComment.objects.count(), 0) + + def test_add_comment_xss_event_handlers_blocked(self): + """Test that comments with event handlers are blocked""" + dangerous_inputs = [ + 'Test onload=alert(1) comment', + 'Test onerror=alert(1) comment', + 'Test onclick=alert(1) comment', + 'Test onmouseover=alert(1) comment' + ] + + self.client.login(username='testuser', password='testpass123') + url = reverse('add_vorgabe_comment', kwargs={'vorgabe_id': self.vorgabe.id}) + + for dangerous_input in dangerous_inputs: + response = self.client.post(url, + data=f'{{"text": "{dangerous_input}"}}', + content_type='application/json' + ) + + self.assertEqual(response.status_code, 400) + + # No comments should be created + self.assertEqual(VorgabeComment.objects.count(), 0) + + def test_add_comment_invalid_json_fails(self): + """Test that invalid JSON is rejected""" + self.client.login(username='testuser', password='testpass123') + + url = reverse('add_vorgabe_comment', kwargs={'vorgabe_id': self.vorgabe.id}) + response = self.client.post(url, + data='invalid json', + content_type='application/json' + ) + + self.assertEqual(response.status_code, 400) + + import json + data = json.loads(response.content) + + self.assertIn('error', data) + self.assertIn('Ungültige', data['error']) + + def test_add_comment_nonexistent_vorgabe_fails(self): + """Test that adding comment to non-existent Vorgabe returns 404""" + self.client.login(username='testuser', password='testpass123') + + url = reverse('add_vorgabe_comment', kwargs={'vorgabe_id': 99999}) + response = self.client.post(url, + data='{"text": "Test comment"}', + content_type='application/json' + ) + + self.assertEqual(response.status_code, 404) + + def test_add_comment_security_headers(self): + """Test that security headers are present in response""" + self.client.login(username='testuser', password='testpass123') + + url = reverse('add_vorgabe_comment', kwargs={'vorgabe_id': self.vorgabe.id}) + response = self.client.post(url, + data='{"text": "Test comment"}', + content_type='application/json' + ) + + self.assertIn('Content-Security-Policy', response) + self.assertIn('X-Content-Type-Options', response) + self.assertEqual(response['X-Content-Type-Options'], 'nosniff') + + +class DeleteVorgabeCommentViewTest(TestCase): + """Test cases for delete_vorgabe_comment view""" + + def setUp(self): + """Set up test data""" + self.client = Client() + + self.user = User.objects.create_user( + username='testuser', + password='testpass123' + ) + + self.other_user = User.objects.create_user( + username='otheruser', + password='testpass123' + ) + + self.staff_user = User.objects.create_user( + username='staffuser', + password='testpass123' + ) + self.staff_user.is_staff = True + self.staff_user.save() + + self.dokumententyp = Dokumententyp.objects.create( + name="Test Typ", + verantwortliche_ve="Test VE" + ) + + self.thema = Thema.objects.create(name="Test Thema") + + self.dokument = Dokument.objects.create( + nummer="COMM-001", + dokumententyp=self.dokumententyp, + name="Comment Test", + aktiv=True + ) + + self.vorgabe = Vorgabe.objects.create( + order=1, + nummer=1, + dokument=self.dokument, + thema=self.thema, + titel="Test Vorgabe", + gueltigkeit_von=date.today() + ) + + self.comment = VorgabeComment.objects.create( + vorgabe=self.vorgabe, + user=self.user, + text="Test comment to delete" + ) + + def test_delete_comment_requires_login(self): + """Test that anonymous users cannot delete comments""" + url = reverse('delete_vorgabe_comment', kwargs={'comment_id': self.comment.id}) + response = self.client.post(url) + + # Should redirect to login + self.assertEqual(response.status_code, 302) + + # Comment should still exist + self.assertTrue(VorgabeComment.objects.filter(id=self.comment.id).exists()) + + def test_delete_comment_requires_post(self): + """Test that only POST method is allowed""" + self.client.login(username='testuser', password='testpass123') + + url = reverse('delete_vorgabe_comment', kwargs={'comment_id': self.comment.id}) + response = self.client.get(url) + + # Should return method not allowed + self.assertEqual(response.status_code, 405) + + def test_user_can_delete_own_comment(self): + """Test that users can delete their own comments""" + self.client.login(username='testuser', password='testpass123') + + url = reverse('delete_vorgabe_comment', kwargs={'comment_id': self.comment.id}) + response = self.client.post(url) + + self.assertEqual(response.status_code, 200) + + import json + data = json.loads(response.content) + + self.assertTrue(data['success']) + + # Comment should be deleted + self.assertFalse(VorgabeComment.objects.filter(id=self.comment.id).exists()) + + def test_user_cannot_delete_other_users_comment(self): + """Test that users cannot delete other users' comments""" + self.client.login(username='otheruser', password='testpass123') + + url = reverse('delete_vorgabe_comment', kwargs={'comment_id': self.comment.id}) + response = self.client.post(url) + + self.assertEqual(response.status_code, 403) + + import json + data = json.loads(response.content) + + self.assertIn('error', data) + self.assertIn('Berechtigung', data['error']) + + # Comment should still exist + self.assertTrue(VorgabeComment.objects.filter(id=self.comment.id).exists()) + + def test_staff_can_delete_any_comment(self): + """Test that staff users can delete any comment""" + self.client.login(username='staffuser', password='testpass123') + + url = reverse('delete_vorgabe_comment', kwargs={'comment_id': self.comment.id}) + response = self.client.post(url) + + self.assertEqual(response.status_code, 200) + + import json + data = json.loads(response.content) + + self.assertTrue(data['success']) + + # Comment should be deleted + self.assertFalse(VorgabeComment.objects.filter(id=self.comment.id).exists()) + + def test_delete_nonexistent_comment_returns_404(self): + """Test that deleting non-existent comment returns 404""" + self.client.login(username='testuser', password='testpass123') + + url = reverse('delete_vorgabe_comment', kwargs={'comment_id': 99999}) + response = self.client.post(url) + + self.assertEqual(response.status_code, 404) + + def test_delete_comment_security_headers(self): + """Test that security headers are present in response""" + self.client.login(username='testuser', password='testpass123') + + url = reverse('delete_vorgabe_comment', kwargs={'comment_id': self.comment.id}) + response = self.client.post(url) + + self.assertIn('Content-Security-Policy', response) + self.assertIn('X-Content-Type-Options', response) + self.assertEqual(response['X-Content-Type-Options'], 'nosniff') diff --git a/dokumente/views.py b/dokumente/views.py index a413894..0b535dd 100644 --- a/dokumente/views.py +++ b/dokumente/views.py @@ -259,10 +259,10 @@ def get_vorgabe_comments(request, vorgabe_id): if request.user.is_staff: # Staff can see all comments - comments = vorgabe.comments.all().select_related('user') + comments = vorgabe.comments.all().select_related('user').order_by('created_at') else: # Regular users can only see their own comments - comments = vorgabe.comments.filter(user=request.user).select_related('user') + comments = vorgabe.comments.filter(user=request.user).select_related('user').order_by('created_at') comments_data = [] for comment in comments: diff --git a/pages/templates/base.html b/pages/templates/base.html index 27b84a4..fd19f62 100644 --- a/pages/templates/base.html +++ b/pages/templates/base.html @@ -215,7 +215,7 @@ -From 3a89f6d871d0fab058515faecd3b2cdd953734a7 Mon Sep 17 00:00:00 2001 From: "Adrian A. Baumann"Version {{ version|default:"0.959" }}
+Version {{ version|default:"0.960" }}
Date: Mon, 1 Dec 2025 10:55:46 +0100 Subject: [PATCH 7/7] Full name on comments --- dokumente/templates/standards/standard_detail.html | 2 +- dokumente/views.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/dokumente/templates/standards/standard_detail.html b/dokumente/templates/standards/standard_detail.html index 6f43da3..38c2d54 100644 --- a/dokumente/templates/standards/standard_detail.html +++ b/dokumente/templates/standards/standard_detail.html @@ -297,7 +297,7 @@ document.addEventListener('DOMContentLoaded', function() { - ${comment.user} + ${comment.user} (${comment.created_at}) ${comment.updated_at !== comment.created_at ? `(bearbeitet: ${comment.updated_at})` : ''}${comment.text}diff --git a/dokumente/views.py b/dokumente/views.py index 0b535dd..53e7b49 100644 --- a/dokumente/views.py +++ b/dokumente/views.py @@ -271,7 +271,7 @@ def get_vorgabe_comments(request, vorgabe_id): comments_data.append({ 'id': comment.id, 'text': escaped_text, - 'user': escape(comment.user.username), + 'user': escape(comment.user.first_name+" "+comment.user.last_name), 'created_at': comment.created_at.strftime('%d.%m.%Y %H:%M'), 'updated_at': comment.updated_at.strftime('%d.%m.%Y %H:%M'), 'is_own': comment.user == request.user