+ {{ t('libresign', 'DocMDP adds certification signatures to protect PDF documents from unauthorized modifications.') }} +
+
+
+ {{ level.description }} +
+6E@RXLPR*|)Wp##SfVrs>ME zUYgdC%J;e))+aOOxyBCtEcJ7&N_U(6x8Ac#dNMmd%Tvx4%A&0G+w+yJ?~R}0ldc}t zqi%9I8sBdnHMhrk`L)c8wUcx5hc)=xs>owiWM!O{EwQlHpN6bhzcbRsif^WRP8#X* zKAZZYSW+=Dx}huTdMc-?F|TvKEJeNymFVuQd7hgr?^2()`BF{Al+V?enz&9HRK z% uKC7-%-oH>AMoVlF3UdnOo>Qt2NFw}FAEcKMn;eduV z^`4*xErxQMy0-6!ibG1CtY5~i&r->^&m4I-awMGaz#A<^oefLh45=#kgJGVlG&WpF zReGp4tHyk&n{F8EGS#_X(vr$HO~ahkh4nx8?Ns&cJdE>{_ft2H%`gw$keAb(mt$A( zdZ}(5<2O@VXt&OrYV4 Q&KIVVz=MK@2SDBfhr3eNMf>Z?3A^r;)UpNg~X z`Z945OHnT4Sh%bx%Xw(#c}c1Dz$4F -^^2yhJA@{6~=gVT&gicSe z|84L8GqbSzk9=ld*XLEM2QRwtFA*Df;e0k7RSvx5{4q0(s1Vj!i!RZN*FSsdYWH!q zzg1Jz9cw7M#iZ(Ko25C=`mW2({?gCmGVyoabQ1yGdyPmwajQD+y!I&fOtrg+3%W66 zm7%RBQEX_sw(?(A4(4OwZA~RlI5iOxOEb9evnk?5+nM{!`I*l+LI0KO6$fws!ar+) zKc3!njP1YCU$4G;VT Ee zYc#(&xtxTbr2~M&>?hVE_AOluZ%V~tSu$8or0V8!sLWbkPs=21i=|yt2)$)K3}ez)mW6xSPQ&k!&>{&BqiRg%nNaUvA&DO zf@Muv?wMN-wKI*KpV~ZiRhnuK$LKi1y2 Ye#^L(R9t*e#sq_X_Ui u0wNf>$horC>sjO;2MBH)^RjCjKaMqkCg@Iu78)KiawI2#F{$T_ zGni```>M&u)b+d(SGz2E;lq*7 *Z{h7%Gk@)Bed#Ij;;IVI>Y gFos`ofm-R001tn7+lxIFr{gnJpKr-LQc5=F~BWTD&bXevXx^< zs*0g)TVXuUb*axmU8z&Hbi-VTC{-35)sTac^W64JT^T4Ilu>kgtl=rf9}WOn2RW#* z?m^YL8Csd`R8<~DE1pU++82GMp|J$My52p3C^_Sp#+Lmw*@O-A$VJtaLSfU5aCMz} zH*SY^?$AN=O9+fDiDRoxi4ZSx*cFAzseJ;$tQqx+U`+qo7j4wZk-vPj)dR+2dolo{ z=sLDDFe`L*9zRTDY&-^JA0ElHUt>LZ%kKdwfG?!L*G6q7CLg33zB0dK*7)nl=DCeG z@G5nFJ$r$R!535LFZ|$_tSaE*z`0+Ai+>|9aE@-t>$%Q5OP}XzD=FhVPAyUx >A?Exjs2Ukq`1{4mpiQCx=Z#Eq_I-q{JnZ(=b}?{EK{f z>EZ3Jk Nq|LZd?XHyXzz~i38=Wg*?n0=PaGX6sKu+ z0!KdKn%r2XKIefg#lE?hY)3-u@=-_>28v25 xXal2-icptW*PF(2)^aKH1Y%qIrKeu+;>Wo0UTl1S>l12sG6~t z`%776E3)Vu@Rl8jM#iw4p?afNcj0;_NLpT-3{_Uaw8JvW;c6I449$g?@TB>a68|XD zhd&u*phvIr>+mYZSQh-|?BdPb@X7!KAmY}WSm!<~1L5*Yuk+DCN(3f*^@w_uYnC@~ zmBCla^~<*A*^1?j*Rq>J&cs;K(vCUX#_j}LvS1#%DR>#{z=~f=OwlmO3Fm}oEhjnS zVBK3nwN!ead(~BV)LT7t*T9=ySf_rj))`#WejE-=I5sG(7DFc^_C42~gZ299rS?{C zUfSuYYFQrKuph10M|+UjzGal7KX1P-*dcev)49oYKaT6?*Rg)S+-6@o8kU@|4E({O zxlPtmgau$u4(!%7GF&~Mn0^bJ<>owZz6maa1b!o|?}q1+U Y^>H=T-aZn3s#rt5PQO_W20yC2}-efbDcTn_!-+T1$l(n*san2_U zZRTboqnt~^GbyWfxs+?kBoY}aMpDFC4Ks%^*UJzQ9}PQ9=&-g<18ywg#KohokF2bl zxQv9z6tvOGlC?>(s?YlbS_p*Tgzk<-JIL1LM2c8QBau>?5qvaaz33r@MG()MUeJrS z^Qo`k0Hi#!S0zhgDAKlbxUM978CqP{%!h$4+VaFob?Q0`=4gWNTSzDae9%rao}Z(G zTlx;-z_APASkMJ4UI_bGc$NqB49a*| n@G}LsryU-wUY1JXZfKR7aPbv@C7fvpUo;I!enCu~JF17KxKjkr%3cXA zno=)l)XQA11Tja6PL3gS;w*UdB8Z_bXeW0I$}E$uR0GB(ID6FJ(5lUGv@_?SF@Zxm z;Q`=0RJ~dzYG&m8+S$p$bc6;V%{-F=qJAr2;N6TBx9BkUiZ4{nTT(6atD;8LV@XhS zDul?p(Q_yXD5_4uB1(Y-6*QKlMtu>4tQQC}kfCZxU1)@Vt~xc6VwMJ}>hYdasoM*h zu2zo}BoInNt=3;HlC&m#EVo@NT~u;RX;NPyx~zHt6(R^$<`xP)pvHY=xC(?xYgDW8 zsM1DtR}n*wS|zuQ!VqO!Bf~U4MUoee*)p?{QJPvUN+iNVR9g%5p?YFD2GwQw#J@_Y ztE|l^lvV?&jp(Ve)2dJoxtN&Ml{! c~G%L@rAk3j$xL224D)q2jK#%HZgI4XTT3`kmM(O#YDwZtJCG~xdszt3?uK J6rvd0V03((RF#zW^}UE_uaRsBXi(MKs^O0r z*%82_#JVYRMP{^qk2>}EN<~OmhyU^6aYhx)UVMKg!&z $8B(i ?CaQ}W$KYNjp+rvBtkE%V6Nj2E c*cAc0!^P3(J(98t5DqFdS|HsIR9`AnT>0I z(E@i)gi)ze_v+9vR9MHVA!<}JknDcevZoFW5;f6U)t;;)b?fyr%-DK7>eY2 -rpH1(^f1b z8G(iFA*)u4{3|gF*S_X0WBn!b6j@QI-9p1Auk&50$5)`pPyMax?Ea{Ex_xx-laA{< zyx4Jl-(KIVWdMV3U9UHKy(sPO<00s|>h)HnHr4{MguY7DErchK?F1@CeSvgNxkBSG zGVMq)MW7f_1GR`s64Na{p5W95f^92A0*x8jTjoPRF>-@MOGRu)7lfqXRw#3D936EU z#}siSJdX-PxGb((QOMFzk5D@Z9S*X@8d2%s&!F)oR}YAsJ;)U0hN7g{>UuiCaYkqi zq0^&)fuVsDvKlqa0;(NqVizqzRf$v=$id*N^K*0pEP9535SgQ2q&6TYqNc?k)ViYr z02GqJ_Q7co$jyryjN(Bz4z>TOVW@pex+tlIYy*0lt0==N(2zTikVigI{q%ScS&>hX zOC7+qkjr(#6aWitqm)9T1NN#$z)!S7SevYYdeu7(qOd?zpoZV#lVNx{OA$&lu?h zY|YIpZPV`rB9ws
MT49UM6%xJ^7O&HCuvk*_`?pMg`D?oqPsTjOZ3q z-2>@SM{;B`GyEddTS#7J9J^VdAyfD2uu90GNH)4C;M9UTNL4n{oYb~73DU@BA)10W zXMn=2q!IR@0b{|%0`kK6 PpAS&X3dgWS(r+d zOcmyX-DGxpU`tA(2xywgNDxOAtJF%dh}26R&_E!)41VNZGXWTsjr&u5lH5``B!ir! zUIX;j>9;b|q{cOmaxhs8y&6>~Z9yZI2nAJ?{sSgZn3@*#CK!J(gS1%)L*=X?1m|G3 zg*E!CMNcE75OI`Zs^^}fc2E{fMmoa0>qsC6OPHv@Bt%uC#sIb>AhUvyRj {k_{Fp*&V z>gTSa;dGZU0LcH9 HN$D ~*Qmb%p6kz|6We^{iP;^jOx^g+di4?_k3OyX*mXYOYjYjR#w&BZztcB;{ 5EnC!UnPrKE6*{DlgA&8i|xG4pFC>8p0DN6Z`9l4V&V!>HR38Em@X}5X zRFxXXmBp432-9HpVdeH1Xs1Vm_@P6P(Hv6DGfd=mz?IlS(g)X& rIJaB2~^Gs+%?5H;|O4^ZcxWI7b%E# zdCq|N?#OvMPZ)yD7aLka1;}5@vk=Cm7)8dXPpihTAygo=4!xcp$yb&nqiPl1_^?nA zC$~~KQp4wsqsli4hT>~%4Z|545|TXmu?mYgCC-FK96lHSI8Bb3yR{jI_*qB@1+yv{ zW f*_7J2RP zi5vnL0a$E_k+K5GB9sklVvZQtc{0_g*w^9|v94*F&>`A!Y8wo%h||dUeR2t+!46lg zOa+U{5oy_NXXE5;x$0zM{B&lF^#npuK}3$&2n8X`wlS%rq2d8j+oEbAu$2LMKL6QV zCmNYqwxSq1XFvpw0-G}CZb?{5C@$L!d1a$+B##Y@V*Xl%s C7UFOO#K@i$J1{2fStJyyDL%+#%F22P zf7S)51XJ0>NfEDv7kjVlOjy7W1vwdbHAQGg7^!@WtY!%*9JF9oRcYl(wq7xf66ml` zL1TV%KNB~_n$bO+6yPS?wP`eJdjn-lRf2G8nrgB*0~${tO92^jurCx4M7V$sA%fKj zO(^N&>LpP^piJhF9~bnz5jl&{GhgrGIONAN#`Rn`@YCD(G0L1Y8CBGM7W-l+;@8iC zV^8Jv_4)`tL|0{B%r}SjzFsaJ+IR$otJScNk>wjjayIaK-Nf{-neK~BoaR6V?r#W_ zDBl&c5vt!T=lzll{Dw S|M&FKb%rt2sl;J9N9X=D)im{ZL02G7}O4Cy~Pw787u#H>EhAkX`C@ARH z;)P65LMGDKhlQq+mx4BYsyJW=bZlE2yU^JrIt;>TJgV9+H!SE9TL3jMSa4b^b!=}M ze1PkRn?y8Ukib$|jHwJ1X{!3JMqdc4vEeuN4oo9?4vsUhU8KPYeJRUD-P(xd#x+rm z>O2$_n}y{`5 Pg>;lWG-B&m zY#_tK0y45Xq0>opJhnE65JBE-@4`ZG8t*MY9%CXRRm7hHDjrwzF73v!+G!R9xzNdo zJhZn5z#`}zTrd9FXvLJ06hSU_Tfn4|bV{$87IZ0-4Kb9d5+V(jrfUi-Obo|m53?Aj z^dW>{7eGSnMCjY8fP~{Ysxwq8wyK~7I+S+k5oFm$ECvU;Vj@)xRGB;qZHgW5u@eJY zZiQGC4+< Jc#$ z6SyjfdTiT6q6DCq_TW@=joEIzbOM_Ip#e$3znd&2A&62f3Z8*W6@h#=j5#_RlQ5T< zcH@BVPoBn3Z4f+>f5AsAzk|53ZLnqo>moC31PXbZY@H}4nuUOnb1|p3>{)}h=TN>X zBzfad8LzS+pv8fpnFyRb5=*3tqeDg(4JujcG`18>AW~2`gYBv)BHq9$>vuU0OO;kp z;tLDM9Mced?HtqCy^aLL*S#m^7G8df(W* 6cj2@u;i43+H+2!7m1Q=h&po&MH-R3TBnycXgEZ%A&d8pHCc&i z7MN)NrC)qMuZx*S2;jnH7qputR#S#Vr5L-TMI=Azgp@x@(FjpmRC^dGSZ6Q15G~Nr zii4`Gf<3yxd>~aFf-VG(c>f7tfLKVOX2lFJwi@=}Mn&`xpSmLw1nDb8(9GJ%D1wQh zQczu&ew%-SDprKtjP2UJ9(%KffY0g6Z^cq zcBahpvXt2QfCaHTksGE5;D>8)qB4K>bD2CeyV{7ZdOF2g<#o%1YAw8|z2x8!eWi*9 zt6JT;*LGmCrJ1FEn-B&949v BS1uI2P$CX &}Mg}p-A?Dz-mxPbW7EORd{!SC_yMV zdmjqQSwvD;w}*v<*1HV^eo}3qFO1$i`=Jk=0udcV0bLkgN>xx!n!{W=%Puw(;d$UU zS#<25Gf{35G!=M@gl9H!SiBU#s0XYKuQCB ljLbS5H5>bZ8q zKqSZ#V^6^lk`z^Y(p-_UC@DIW&_ie =?Jj=*sQjw5g! zf#V1qN8mUD#}PP=z;OhQBXAsn;|Lr_;5Y)u5jc*(aRiPda2$c-2pmV?I0DBJIF7(^ z1dbzc9D(Bq97o_d0>=?Jj=*sQjw5g!f#V1qN8mUD#}PP=z;OhAlSkm*_R-n1UAo-f zpD)IA=keno{P1j-wokm9@-m%I>65#6x2I=M)79>W7t^piyEy+O+hm_^Ke`;wua=9; z&)!eFpI*${(~If-)5qJ7e(`zQet7oO^@qz2$KOA#4 RA5B;7ojL7>>8EMlzW?s&?|83(e}3Gn-~2-{ zS^M3r{C1Xo`#bmUouqSP`m>X~(BJsM1OEQ-S=# g@XWTem1*xy4V0)A6fs@sr{6 HT-t8S>jli!V<%-`So%aY@XIZ!xrNY`tB~(Po`r_85P*j|XdI zQ>{lIr_0r<_iJO*$A@v!wPD0rhX?t=S@a-IICOb!XZ!Ht`T6el$ZeXn<@eKkHe8Qi z-Pap$2l(`+;b`d4@b@k+o_`+CyI*ae53#C$vHkvLclq4Fe7HSLKR=t&lkYvy`G=k` zo$szTVX;s4V;3E{nYqLLtw-k_9`pYB`Ni&L{9f(b9dC??f33mWpu0ReddI5`{6j?E zXj~WFtzLIGy?)39b{Cg- (bk Pi~0GKF5ms(*?5^gSQg{3Y42peyZOv=Th7wFd46SeY{JSOpFY~; zcgj0uc57vSahaBrY*U=vc=(O?8kF^B+3Y`ye-)|TtcN$}o0DpjUw`i3l)Cx6-L%)A z8?Bqq*9(nQ#pdHrw6Xr+Ws1F7e(5v+CSLyPwecEuX7=)V@g{~>Kl2u2<9~hL?s7PL zmM+6%egAy9*u>O4*?#|Y^U2+Khvgivnl8P8(!VF Eg@5J+(2miK(H(z)s49ETC;$pXV;}+r{^9_~RNI&0rKQb%6+6|Yxb! Date: Mon, 8 Dec 2025 13:19:05 -0300 Subject: [PATCH 30/44] fix: add missing DocMdpHandler mock in RequestSignatureServiceTest Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> --- tests/php/Unit/Service/RequestSignatureServiceTest.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/php/Unit/Service/RequestSignatureServiceTest.php b/tests/php/Unit/Service/RequestSignatureServiceTest.php index be59154b1e..ef32e66370 100644 --- a/tests/php/Unit/Service/RequestSignatureServiceTest.php +++ b/tests/php/Unit/Service/RequestSignatureServiceTest.php @@ -11,6 +11,7 @@ use OCA\Libresign\Db\FileMapper; use OCA\Libresign\Db\IdentifyMethodMapper; use OCA\Libresign\Db\SignRequestMapper; +use OCA\Libresign\Handler\DocMdpHandler; use OCA\Libresign\Helper\ValidateHelper; use OCA\Libresign\Service\FileElementService; use OCA\Libresign\Service\FolderService; @@ -43,6 +44,7 @@ final class RequestSignatureServiceTest extends \OCA\Libresign\Tests\Unit\TestCa private PdfParserService&MockObject $pdfParserService; private IMimeTypeDetector&MockObject $mimeTypeDetector; private IClientService&MockObject $client; + private DocMdpHandler&MockObject $docMdpHandler; private LoggerInterface&MockObject $loggerInterface; public function setUp(): void { @@ -66,6 +68,7 @@ public function setUp(): void { $this->pdfParserService = $this->createMock(PdfParserService::class); $this->mimeTypeDetector = $this->createMock(IMimeTypeDetector::class); $this->client = $this->createMock(IClientService::class); + $this->docMdpHandler = $this->createMock(DocMdpHandler::class); $this->loggerInterface = $this->createMock(LoggerInterface::class); } @@ -84,6 +87,7 @@ private function getService(): RequestSignatureService { $this->mimeTypeDetector, $this->validateHelper, $this->client, + $this->docMdpHandler, $this->loggerInterface ); } From d3c0594296acc90ecc45bfa1cd2036375ac02919 Mon Sep 17 00:00:00 2001 From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> Date: Mon, 8 Dec 2025 13:25:29 -0300 Subject: [PATCH 31/44] chore: add real_jsignpdf_level1.pdf to REUSE.toml Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> --- REUSE.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/REUSE.toml b/REUSE.toml index ed835c94c3..4e7bea0c17 100644 --- a/REUSE.toml +++ b/REUSE.toml @@ -50,6 +50,7 @@ path = [ "src/types/openapi/openapi.ts", "tests/php/Unit/Handler/mock/cert.json", "tests/php/fixtures/cfssl/newcert-with-success.json", + "tests/php/fixtures/real_jsignpdf_level1.pdf", "tests/php/fixtures/small_valid-signed.pdf", "tests/php/fixtures/small_valid.pdf", "tests/integration/composer.json", From 07e9921fa92f18b7d4ddfe34616d0cfc5fb478bf Mon Sep 17 00:00:00 2001 From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> Date: Mon, 8 Dec 2025 14:40:10 -0300 Subject: [PATCH 32/44] Update error message: use more generic phrase for PDF resource creation failure Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> --- lib/Service/SignFileService.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Service/SignFileService.php b/lib/Service/SignFileService.php index 8f6b310eed..9f68574df9 100644 --- a/lib/Service/SignFileService.php +++ b/lib/Service/SignFileService.php @@ -359,7 +359,7 @@ protected function getLibreSignFileAsResource() { $content = $fileToSign->getContent(); $resource = fopen('php://temp', 'r+'); if ($resource === false) { - throw new LibresignException($this->l10n->t('Failed to create temporary resource for PDF validation')); + throw new LibresignException('Failed to create temporary resource for PDF validation'); } fwrite($resource, $content); rewind($resource); From 977f110aa6297a893f705c47cc2ec80f770eb3e3 Mon Sep 17 00:00:00 2001 From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> Date: Mon, 8 Dec 2025 14:42:46 -0300 Subject: [PATCH 33/44] Use php://memory instead of php://temp for PDF resource creation (in-memory only) Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> --- lib/Service/SignFileService.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Service/SignFileService.php b/lib/Service/SignFileService.php index 9f68574df9..850e5a53ee 100644 --- a/lib/Service/SignFileService.php +++ b/lib/Service/SignFileService.php @@ -357,7 +357,7 @@ protected function validateDocMdpAllowsSignatures(): void { protected function getLibreSignFileAsResource() { $fileToSign = $this->getNextcloudFile($this->libreSignFile); $content = $fileToSign->getContent(); - $resource = fopen('php://temp', 'r+'); + $resource = fopen('php://memory', 'r+'); if ($resource === false) { throw new LibresignException('Failed to create temporary resource for PDF validation'); } From 5fc98d4e755ab9aec5da48c110f13f960b2ef3e5 Mon Sep 17 00:00:00 2001 From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> Date: Mon, 8 Dec 2025 14:43:13 -0300 Subject: [PATCH 34/44] Use php://memory instead of php://temp in SignFileServiceTest for in-memory resource creation Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> --- tests/php/Unit/Service/SignFileServiceTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/php/Unit/Service/SignFileServiceTest.php b/tests/php/Unit/Service/SignFileServiceTest.php index f5989be57b..9ac31f6ce2 100644 --- a/tests/php/Unit/Service/SignFileServiceTest.php +++ b/tests/php/Unit/Service/SignFileServiceTest.php @@ -1249,7 +1249,7 @@ public function testValidateDocMdpAllowsSignaturesWithVariousPdfFixtures( $service = $this->getService(['getLibreSignFileAsResource']); $pdfContent = $pdfContentGenerator($this); - $resource = fopen('php://temp', 'r+'); + $resource = fopen('php://memory', 'r+'); fwrite($resource, $pdfContent); rewind($resource); From 7947b231afb9b57805b94d9216f6abc6409a2d01 Mon Sep 17 00:00:00 2001 From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> Date: Mon, 8 Dec 2025 15:35:42 -0300 Subject: [PATCH 35/44] chore: use a most generic text Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> --- lib/Controller/AdminController.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Controller/AdminController.php b/lib/Controller/AdminController.php index 8e24c5bc07..63933cc6d0 100644 --- a/lib/Controller/AdminController.php +++ b/lib/Controller/AdminController.php @@ -889,7 +889,7 @@ public function setDocMdpConfig(bool $enabled, int $defaultLevel): DataResponse } return new DataResponse([ - 'message' => $this->l10n->t('DocMDP configuration saved successfully'), + 'message' => $this->l10n->t('Settings saved'), ]); } catch (\Exception $e) { return new DataResponse([ From 7ffee0d234d50344c70ec1f363ad4622f63f46c3 Mon Sep 17 00:00:00 2001 From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> Date: Mon, 8 Dec 2025 16:20:36 -0300 Subject: [PATCH 36/44] Update DocMdpLevel labels and descriptions for clarity and user-friendliness Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> --- lib/Enum/DocMdpLevel.php | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/Enum/DocMdpLevel.php b/lib/Enum/DocMdpLevel.php index 6ae07a704b..0a4258a88d 100644 --- a/lib/Enum/DocMdpLevel.php +++ b/lib/Enum/DocMdpLevel.php @@ -25,17 +25,17 @@ public function getLabel(IL10N $l10n): string { return match($this) { self::NOT_CERTIFIED => $l10n->t('No certification'), self::CERTIFIED_NO_CHANGES_ALLOWED => $l10n->t('No changes allowed'), - self::CERTIFIED_FORM_FILLING => $l10n->t('Form filling and additional signatures'), - self::CERTIFIED_FORM_FILLING_AND_ANNOTATIONS => $l10n->t('Form filling, annotations and additional signatures'), + self::CERTIFIED_FORM_FILLING => $l10n->t('Form filling allowed'), + self::CERTIFIED_FORM_FILLING_AND_ANNOTATIONS => $l10n->t('Form filling and commenting allowed'), }; } public function getDescription(IL10N $l10n): string { return match($this) { - self::NOT_CERTIFIED => $l10n->t('Approval signature - allows all modifications'), - self::CERTIFIED_NO_CHANGES_ALLOWED => $l10n->t('Certifying signature - no modifications or additional signatures allowed'), - self::CERTIFIED_FORM_FILLING => $l10n->t('Certifying signature - allows form filling and additional approval signatures'), - self::CERTIFIED_FORM_FILLING_AND_ANNOTATIONS => $l10n->t('Certifying signature - allows form filling, comments and additional approval signatures'), + self::NOT_CERTIFIED => $l10n->t('The document is not certified; edits and new signatures are allowed, but any change will mark previous signatures as modified.'), + self::CERTIFIED_NO_CHANGES_ALLOWED => $l10n->t('After the first signature, no further edits or signatures are allowed; any change invalidates the certification.'), + self::CERTIFIED_FORM_FILLING => $l10n->t('After the first signature, only form filling and additional signatures are allowed; other changes invalidate the certification.'), + self::CERTIFIED_FORM_FILLING_AND_ANNOTATIONS => $l10n->t('After the first signature, form filling, comments, and additional signatures are allowed; other changes invalidate the certification.'), }; } } From 4413d3b72f5c9054524cae6a5a487749e96c2c28 Mon Sep 17 00:00:00 2001 From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> Date: Mon, 8 Dec 2025 16:35:34 -0300 Subject: [PATCH 37/44] fix: update logic in SignFileService.php\n\nChanged implementation at line 344 to improve behavior as required.\n\nSigned-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> --- lib/Service/SignFileService.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Service/SignFileService.php b/lib/Service/SignFileService.php index 850e5a53ee..75a7ef9466 100644 --- a/lib/Service/SignFileService.php +++ b/lib/Service/SignFileService.php @@ -341,7 +341,7 @@ protected function validateDocMdpAllowsSignatures(): void { try { if (!$this->docMdpHandler->allowsAdditionalSignatures($resource)) { throw new LibresignException( - $this->l10n->t('This document is certified with DocMDP level 1 (no changes allowed) and cannot receive additional signatures'), + $this->l10n->t('This document has been certified with no changes allowed, so no additional signatures can be added.'), AppFrameworkHttp::STATUS_UNPROCESSABLE_ENTITY ); } From 68122736f6ea410d7d257dc19206ed33b447543e Mon Sep 17 00:00:00 2001 From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> Date: Mon, 8 Dec 2025 16:37:37 -0300 Subject: [PATCH 38/44] fix: update logic in TFile.php Changed implementation at line 172 as required. Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> --- lib/Service/TFile.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Service/TFile.php b/lib/Service/TFile.php index f9dfc57b36..77a10f144f 100644 --- a/lib/Service/TFile.php +++ b/lib/Service/TFile.php @@ -169,7 +169,7 @@ private function validateDocMdpAllowsSignatures(string $pdfContent): void { if (!$this->docMdpHandler->allowsAdditionalSignatures($resource)) { throw new LibresignException( - $this->l10n->t('This document is certified with DocMDP level 1 (No changes allowed). Additional signatures are not permitted.') + $this->l10n->t('This document has been certified with no changes allowed, so no additional signatures can be added.') ); } } finally { From ac6d795e04aa25184737d0b36e198ae6de21f8a6 Mon Sep 17 00:00:00 2001 From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> Date: Mon, 8 Dec 2025 16:40:51 -0300 Subject: [PATCH 39/44] fix: use generic error messages for DocMDP configuration\n\nChanged error messages for loading and saving configuration to be more generic and user-friendly. Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> --- src/views/Settings/DocMDP.vue | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/views/Settings/DocMDP.vue b/src/views/Settings/DocMDP.vue index 1477a9a90f..433de48223 100644 --- a/src/views/Settings/DocMDP.vue +++ b/src/views/Settings/DocMDP.vue @@ -102,7 +102,7 @@ export default { } } catch (error) { console.error('Error loading DocMDP configuration:', error) - this.errorMessage = t('libresign', 'Failed to load DocMDP configuration') + this.errorMessage = t('libresign', 'Could not load configuration.') } }, onEnabledChange(value) { @@ -147,7 +147,7 @@ export default { } catch (error) { console.error('Error saving DocMDP configuration:', error) this.errorMessage = error.response?.data?.ocs?.data?.error - || t('libresign', 'Failed to save DocMDP configuration') + || t('libresign', 'Could not save configuration.') this.showErrorIcon = true setTimeout(() => { this.showErrorIcon = false From 7aa85ce967082bd32b0949a74a40009a3824c60c Mon Sep 17 00:00:00 2001 From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> Date: Mon, 8 Dec 2025 16:44:18 -0300 Subject: [PATCH 40/44] chore: code style fixes in DocMdpLevel.php Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> --- lib/Enum/DocMdpLevel.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/Enum/DocMdpLevel.php b/lib/Enum/DocMdpLevel.php index 0a4258a88d..ad75d6bc08 100644 --- a/lib/Enum/DocMdpLevel.php +++ b/lib/Enum/DocMdpLevel.php @@ -32,10 +32,10 @@ public function getLabel(IL10N $l10n): string { public function getDescription(IL10N $l10n): string { return match($this) { - self::NOT_CERTIFIED => $l10n->t('The document is not certified; edits and new signatures are allowed, but any change will mark previous signatures as modified.'), - self::CERTIFIED_NO_CHANGES_ALLOWED => $l10n->t('After the first signature, no further edits or signatures are allowed; any change invalidates the certification.'), - self::CERTIFIED_FORM_FILLING => $l10n->t('After the first signature, only form filling and additional signatures are allowed; other changes invalidate the certification.'), - self::CERTIFIED_FORM_FILLING_AND_ANNOTATIONS => $l10n->t('After the first signature, form filling, comments, and additional signatures are allowed; other changes invalidate the certification.'), + self::NOT_CERTIFIED => $l10n->t('The document is not certified; edits and new signatures are allowed, but any change will mark previous signatures as modified.'), + self::CERTIFIED_NO_CHANGES_ALLOWED => $l10n->t('After the first signature, no further edits or signatures are allowed; any change invalidates the certification.'), + self::CERTIFIED_FORM_FILLING => $l10n->t('After the first signature, only form filling and additional signatures are allowed; other changes invalidate the certification.'), + self::CERTIFIED_FORM_FILLING_AND_ANNOTATIONS => $l10n->t('After the first signature, form filling, comments, and additional signatures are allowed; other changes invalidate the certification.'), }; } } From 0a578c23cae3a45bf500fd51fbd6fae4167b0a7c Mon Sep 17 00:00:00 2001 From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> Date: Mon, 8 Dec 2025 16:52:38 -0300 Subject: [PATCH 41/44] test: remove exception message validation from DocMDP tests Tests now only check for exception type, not message, for DocMDP-related logic. Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> --- tests/php/Unit/Service/FileServiceTest.php | 1 - tests/php/Unit/Service/SignFileServiceTest.php | 8 -------- 2 files changed, 9 deletions(-) diff --git a/tests/php/Unit/Service/FileServiceTest.php b/tests/php/Unit/Service/FileServiceTest.php index 4fa6cfcb62..0a5592d9ad 100644 --- a/tests/php/Unit/Service/FileServiceTest.php +++ b/tests/php/Unit/Service/FileServiceTest.php @@ -466,7 +466,6 @@ public function testValidateFileContentRejectsDocMdpLevel1(): void { $service = $this->getService(); $this->expectException(\OCA\Libresign\Exception\LibresignException::class); - $this->expectExceptionMessage('This document is certified with DocMDP level 1'); $service->validateFileContent($pdfContent, 'pdf'); } diff --git a/tests/php/Unit/Service/SignFileServiceTest.php b/tests/php/Unit/Service/SignFileServiceTest.php index 9ac31f6ce2..cf85c1903d 100644 --- a/tests/php/Unit/Service/SignFileServiceTest.php +++ b/tests/php/Unit/Service/SignFileServiceTest.php @@ -1237,13 +1237,11 @@ public function testSignThrowsExceptionWhenDocMdpLevel1Detected(): void { public function testValidateDocMdpAllowsSignaturesWithVariousPdfFixtures( callable $pdfContentGenerator, bool $shouldThrowException, - ?string $expectedExceptionMessage, ): void { if (!$shouldThrowException) { $this->expectNotToPerformAssertions(); } else { $this->expectException(LibresignException::class); - $this->expectExceptionMessage($expectedExceptionMessage); } $service = $this->getService(['getLibreSignFileAsResource']); @@ -1263,32 +1261,26 @@ public static function provideValidateDocMdpAllowsSignaturesScenarios(): array { 'Unsigned PDF - should NOT throw exception' => [ 'pdfContentGenerator' => fn (self $test) => $test->createMinimalPdf(), 'shouldThrowException' => false, - 'expectedExceptionMessage' => null, ], 'DocMDP level 0 (not certified) - should NOT throw exception' => [ 'pdfContentGenerator' => fn (self $test) => $test->createPdfWithDocMdp(0, false), 'shouldThrowException' => false, - 'expectedExceptionMessage' => null, ], 'DocMDP level 1 (no changes allowed) - SHOULD throw exception' => [ 'pdfContentGenerator' => fn (self $test) => $test->createPdfWithDocMdp(1, false), 'shouldThrowException' => true, - 'expectedExceptionMessage' => 'This document is certified with DocMDP level 1 (no changes allowed) and cannot receive additional signatures', ], 'DocMDP level 2 (form filling allowed) - should NOT throw exception' => [ 'pdfContentGenerator' => fn (self $test) => $test->createPdfWithDocMdp(2, false), 'shouldThrowException' => false, - 'expectedExceptionMessage' => null, ], 'DocMDP level 3 (annotations allowed) - should NOT throw exception' => [ 'pdfContentGenerator' => fn (self $test) => $test->createPdfWithDocMdp(3, false), 'shouldThrowException' => false, - 'expectedExceptionMessage' => null, ], 'DocMDP level 1 with modifications - SHOULD throw exception' => [ 'pdfContentGenerator' => fn (self $test) => $test->createPdfWithDocMdp(1, true), 'shouldThrowException' => true, - 'expectedExceptionMessage' => 'This document is certified with DocMDP level 1 (no changes allowed) and cannot receive additional signatures', ], ]; } From 5599183a2d0b20414017274263819cd6a0a8a656 Mon Sep 17 00:00:00 2001 From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> Date: Mon, 8 Dec 2025 17:07:29 -0300 Subject: [PATCH 42/44] docs: add TRANSLATORS comment explaining DocMDP for translators Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> --- src/views/Settings/DocMDP.vue | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/views/Settings/DocMDP.vue b/src/views/Settings/DocMDP.vue index 433de48223..65cfa4a579 100644 --- a/src/views/Settings/DocMDP.vue +++ b/src/views/Settings/DocMDP.vue @@ -5,7 +5,7 @@ - {{ t('libresign', 'DocMDP adds certification signatures to protect PDF documents from unauthorized modifications.') }} + {{ t('libresign', 'DocMDP defines what types of changes are allowed in a PDF after it is signed, ensuring viewers can detect unauthorized modifications.') }}
Date: Mon, 8 Dec 2025 17:09:47 -0300 Subject: [PATCH 43/44] test: update DocMDP level 1 exception test to only check exception type Removes translated message assertion for robustness. Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> --- tests/php/Unit/Service/SignFileServiceTest.php | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/php/Unit/Service/SignFileServiceTest.php b/tests/php/Unit/Service/SignFileServiceTest.php index cf85c1903d..9b9bb52249 100644 --- a/tests/php/Unit/Service/SignFileServiceTest.php +++ b/tests/php/Unit/Service/SignFileServiceTest.php @@ -1176,7 +1176,6 @@ public static function providerGetNodeByIdUsingUid(): array { public function testSignThrowsExceptionWhenDocMdpLevel1Detected(): void { $this->expectException(LibresignException::class); - $this->expectExceptionMessage('This document is certified with DocMDP level 1 (no changes allowed) and cannot receive additional signatures'); // Create a real DocMdpHandler for this test $realDocMdpHandler = new DocMdpHandler($this->l10n); From 9088d4fabf232c11c6a8e2acaa640eb6f32d21a8 Mon Sep 17 00:00:00 2001 From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> Date: Mon, 8 Dec 2025 17:16:17 -0300 Subject: [PATCH 44/44] test: refactor DocMDP level 1 exception test to use getService helper Simplifies test setup and improves maintainability. Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> --- .../php/Unit/Service/SignFileServiceTest.php | 40 +------------------ 1 file changed, 1 insertion(+), 39 deletions(-) diff --git a/tests/php/Unit/Service/SignFileServiceTest.php b/tests/php/Unit/Service/SignFileServiceTest.php index 9b9bb52249..61a62ed970 100644 --- a/tests/php/Unit/Service/SignFileServiceTest.php +++ b/tests/php/Unit/Service/SignFileServiceTest.php @@ -1176,50 +1176,12 @@ public static function providerGetNodeByIdUsingUid(): array { public function testSignThrowsExceptionWhenDocMdpLevel1Detected(): void { $this->expectException(LibresignException::class); - - // Create a real DocMdpHandler for this test - $realDocMdpHandler = new DocMdpHandler($this->l10n); - - // Create service with custom methods mocked and real DocMdpHandler - $service = $this->getMockBuilder(SignFileService::class) - ->setConstructorArgs([ - $this->l10n, - $this->fileMapper, - $this->signRequestMapper, - $this->idDocsMapper, - $this->footerHandler, - $this->folderService, - $this->clientService, - $this->userManager, - $this->logger, - $this->appConfig, - $this->validateHelper, - $this->signerElementsService, - $this->root, - $this->userSession, - $this->dateTimeZone, - $this->fileElementMapper, - $this->userElementMapper, - $this->eventDispatcher, - $this->secureRandom, - $this->urlGenerator, - $this->identifyMethodMapper, - $this->tempManager, - $this->identifyMethodService, - $this->timeFactory, - $this->signEngineFactory, - $this->signedEventFactory, - $this->pdf, - $realDocMdpHandler, - ]) - ->onlyMethods(['getNextcloudFile', 'getEngine']) - ->getMock(); + $service = $this->getService(['getNextcloudFile', 'getEngine']); $nextcloudFile = $this->createMock(\OCP\Files\File::class); $nextcloudFile->method('getContent')->willReturn(file_get_contents(__DIR__ . '/../../fixtures/real_jsignpdf_level1.pdf')); $service->method('getNextcloudFile')->willReturn($nextcloudFile); - // Mock getEngine to prevent actual signing (should not be called) $engineMock = $this->createMock(Pkcs12Handler::class); $service->method('getEngine')->willReturn($engineMock);