From 3a026f5c10bbdc816c50f1d9ca8478c75dfb7219 Mon Sep 17 00:00:00 2001 From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> Date: Sun, 14 Dec 2025 16:11:44 -0300 Subject: [PATCH 01/11] feat: add docmdp_level column to libresign_file table - Add migration to create docmdp_level column (SMALLINT, default 0) - Add docmdpLevel property to File entity with getters/setters - Add getDocmdpLevelEnum() and setDocmdpLevelEnum() methods - DocMDP levels: 0=not certified, 1=no changes, 2=form fill, 3=annotations Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> --- lib/Db/File.php | 12 +++++ .../Version15001Date20251214000000.php | 46 +++++++++++++++++++ 2 files changed, 58 insertions(+) create mode 100644 lib/Migration/Version15001Date20251214000000.php diff --git a/lib/Db/File.php b/lib/Db/File.php index 856df3dc3e..07c112f51c 100644 --- a/lib/Db/File.php +++ b/lib/Db/File.php @@ -41,6 +41,8 @@ * @method int getModificationStatus() * @method void setSignatureFlow(int $signatureFlow) * @method int getSignatureFlow() + * @method void setDocmdpLevel(int $docmdpLevel) + * @method int getDocmdpLevel() */ class File extends Entity { protected int $nodeId = 0; @@ -56,6 +58,7 @@ class File extends Entity { protected ?array $metadata = null; protected int $modificationStatus = 0; protected int $signatureFlow = SignatureFlow::NUMERIC_PARALLEL; + protected int $docmdpLevel = 0; public const STATUS_NOT_LIBRESIGN_FILE = -1; public const STATUS_DRAFT = 0; public const STATUS_ABLE_TO_SIGN = 1; @@ -83,6 +86,7 @@ public function __construct() { $this->addType('metadata', Types::JSON); $this->addType('modificationStatus', Types::SMALLINT); $this->addType('signatureFlow', Types::SMALLINT); + $this->addType('docmdpLevel', Types::SMALLINT); } public function isDeletedAccount(): bool { @@ -102,4 +106,12 @@ public function getSignatureFlowEnum(): \OCA\Libresign\Enum\SignatureFlow { public function setSignatureFlowEnum(\OCA\Libresign\Enum\SignatureFlow $flow): void { $this->setSignatureFlow($flow->toNumeric()); } + + public function getDocmdpLevelEnum(): \OCA\Libresign\Enum\DocMdpLevel { + return \OCA\Libresign\Enum\DocMdpLevel::tryFrom($this->docmdpLevel) ?? \OCA\Libresign\Enum\DocMdpLevel::NOT_CERTIFIED; + } + + public function setDocmdpLevelEnum(\OCA\Libresign\Enum\DocMdpLevel $level): void { + $this->setDocmdpLevel($level->value); + } } diff --git a/lib/Migration/Version15001Date20251214000000.php b/lib/Migration/Version15001Date20251214000000.php new file mode 100644 index 0000000000..17f049940c --- /dev/null +++ b/lib/Migration/Version15001Date20251214000000.php @@ -0,0 +1,46 @@ +hasTable('libresign_file')) { + $tableFile = $schema->getTable('libresign_file'); + if (!$tableFile->hasColumn('docmdp_level')) { + $tableFile->addColumn('docmdp_level', Types::SMALLINT, [ + 'notnull' => true, + 'default' => 0, + 'comment' => 'DocMDP permission level for this file: 0=none, 1=no changes, 2=form fill, 3=form fill + annotations', + ]); + } + } + + return $schema; + } +} From cff28b6ceb0ae1af588940b6c0dc1b7f6749abc0 Mon Sep 17 00:00:00 2001 From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> Date: Sun, 14 Dec 2025 16:12:12 -0300 Subject: [PATCH 02/11] feat: save DocMDP level from global config to file on creation - Add DocMdpConfigService dependency to RequestSignatureService - Create setDocMdpLevelFromGlobalConfig() method - Save admin DocMDP configuration to file entity on creation - Allows per-file DocMDP configuration instead of only global Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> --- lib/Service/RequestSignatureService.php | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/lib/Service/RequestSignatureService.php b/lib/Service/RequestSignatureService.php index a57ec9b313..3308a1540a 100644 --- a/lib/Service/RequestSignatureService.php +++ b/lib/Service/RequestSignatureService.php @@ -56,6 +56,7 @@ public function __construct( protected IEventDispatcher $eventDispatcher, protected FileStatusService $fileStatusService, protected SignRequestStatusService $signRequestStatusService, + protected DocMdpConfigService $docMdpConfigService, ) { } @@ -128,6 +129,8 @@ public function saveFile(array $data): FileEntity { $this->setSignatureFlowFromGlobalConfig($file); } + $this->setDocMdpLevelFromGlobalConfig($file); + $this->fileMapper->insert($file); return $file; } @@ -138,6 +141,13 @@ private function setSignatureFlowFromGlobalConfig(FileEntity $file): void { $file->setSignatureFlowEnum($globalFlow); } + private function setDocMdpLevelFromGlobalConfig(FileEntity $file): void { + if ($this->docMdpConfigService->isEnabled()) { + $docmdpLevel = $this->docMdpConfigService->getLevel(); + $file->setDocmdpLevelEnum($docmdpLevel); + } + } + private function getFileMetadata(\OCP\Files\Node $node): array { $metadata = []; if ($extension = strtolower($node->getExtension())) { From 8acb7f8576d3b152d987b5c536b8f9387fe4fae2 Mon Sep 17 00:00:00 2001 From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> Date: Sun, 14 Dec 2025 16:12:26 -0300 Subject: [PATCH 03/11] feat: validate DocMDP per file before signing - Update validateDocMdpAllowsSignatures() to check file's docmdpLevel first - Falls back to PDF extraction for legacy files (level 0) - Throws consistent error message for DocMDP level 1 - Prevents adding signatures to certified documents with no changes allowed Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> --- lib/Service/SignFileService.php | 29 ++++++++++++++++++++--------- 1 file changed, 20 insertions(+), 9 deletions(-) diff --git a/lib/Service/SignFileService.php b/lib/Service/SignFileService.php index 5c167e7813..0d517059b3 100644 --- a/lib/Service/SignFileService.php +++ b/lib/Service/SignFileService.php @@ -338,17 +338,28 @@ public function sign(): File { * @throws LibresignException If the document has DocMDP level 1 (no changes allowed) */ protected function validateDocMdpAllowsSignatures(): void { - $resource = $this->getLibreSignFileAsResource(); + $docmdpLevel = $this->libreSignFile->getDocmdpLevelEnum(); - try { - if (!$this->docMdpHandler->allowsAdditionalSignatures($resource)) { - throw new LibresignException( - $this->l10n->t('This document has been certified with no changes allowed, so no additional signatures can be added.'), - AppFrameworkHttp::STATUS_UNPROCESSABLE_ENTITY - ); + if ($docmdpLevel === \OCA\Libresign\Enum\DocMdpLevel::CERTIFIED_NO_CHANGES_ALLOWED) { + throw new LibresignException( + $this->l10n->t('This document has been certified with no changes allowed. You cannot add more signers to this document.'), + AppFrameworkHttp::STATUS_UNPROCESSABLE_ENTITY + ); + } + + if ($docmdpLevel === \OCA\Libresign\Enum\DocMdpLevel::NOT_CERTIFIED) { + $resource = $this->getLibreSignFileAsResource(); + + try { + if (!$this->docMdpHandler->allowsAdditionalSignatures($resource)) { + throw new LibresignException( + $this->l10n->t('This document has been certified with no changes allowed. You cannot add more signers to this document.'), + AppFrameworkHttp::STATUS_UNPROCESSABLE_ENTITY + ); + } + } finally { + fclose($resource); } - } finally { - fclose($resource); } } From c43159c777e95cdfd46e3da7637249742d3ee42a Mon Sep 17 00:00:00 2001 From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> Date: Sun, 14 Dec 2025 16:13:21 -0300 Subject: [PATCH 04/11] feat: include docmdpLevel in API responses - Add docmdp_level to SELECT and GROUP BY in SignRequestMapper - Format docmdpLevel as integer in API response - Add docmdpLevel to FileService::loadLibreSignData() - Ensures frontend receives per-file DocMDP configuration Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> --- lib/Db/SignRequestMapper.php | 4 ++++ lib/Service/FileService.php | 1 + 2 files changed, 5 insertions(+) diff --git a/lib/Db/SignRequestMapper.php b/lib/Db/SignRequestMapper.php index e1da090b47..afb4046e26 100644 --- a/lib/Db/SignRequestMapper.php +++ b/lib/Db/SignRequestMapper.php @@ -502,6 +502,7 @@ private function getFilesAssociatedFilesWithMeQueryBuilder(string $userId, array 'f.metadata', 'f.created_at', 'f.signature_flow', + 'f.docmdp_level', ) ->groupBy( 'f.id', @@ -513,6 +514,7 @@ private function getFilesAssociatedFilesWithMeQueryBuilder(string $userId, array 'f.status', 'f.created_at', 'f.signature_flow', + 'f.docmdp_level', ); // metadata is a json column, the right way is to use f.metadata::text // when the database is PostgreSQL. The problem is that the command @@ -624,12 +626,14 @@ private function formatListRow(array $row): array { $row['name'] = $this->removeExtensionFromName($row['name'], $row['metadata']); $row['signatureFlow'] = SignatureFlow::fromNumeric((int)($row['signature_flow']))->value; + $row['docmdpLevel'] = (int)($row['docmdp_level'] ?? 0); unset( $row['user_id'], $row['node_id'], $row['signed_node_id'], $row['signature_flow'], + $row['docmdp_level'], ); return $row; } diff --git a/lib/Service/FileService.php b/lib/Service/FileService.php index e75cb9f845..1d0ed76c0b 100644 --- a/lib/Service/FileService.php +++ b/lib/Service/FileService.php @@ -712,6 +712,7 @@ private function loadLibreSignData(): void { $this->fileData->statusText = $this->fileMapper->getTextOfStatus($this->file->getStatus()); $this->fileData->nodeId = $this->file->getNodeId(); $this->fileData->signatureFlow = $this->file->getSignatureFlow(); + $this->fileData->docmdpLevel = $this->file->getDocmdpLevel(); $this->fileData->requested_by = [ 'userId' => $this->file->getUserId(), From 99e2c0258ab8fc5b03c70b57f893fe224335bf03 Mon Sep 17 00:00:00 2001 From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> Date: Sun, 14 Dec 2025 16:13:51 -0300 Subject: [PATCH 05/11] feat: block adding signers when DocMDP level 1 is set - Add isDocMdpNoChangesAllowed() method to files store - Update canAddSigner() to check DocMDP level 1 with existing signers - Only blocks after first signer is added (not before) - Encapsulates logic in reusable method Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> --- src/store/files.js | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/store/files.js b/src/store/files.js index 0b210bc129..c9efae2836 100644 --- a/src/store/files.js +++ b/src/store/files.js @@ -133,6 +133,11 @@ export const useFilesStore = function(...args) { }, canAddSigner(file) { file = this.getFile(file) + + if (this.isDocMdpNoChangesAllowed(file)) { + return false + } + return this.canRequestSign && ( !Object.hasOwn(file, 'requested_by') @@ -141,6 +146,10 @@ export const useFilesStore = function(...args) { && !this.isPartialSigned(file) && !this.isFullSigned(file) }, + isDocMdpNoChangesAllowed(file) { + file = this.getFile(file) + return file.docmdpLevel === 1 && file.signers && file.signers.length > 0 + }, canSave(file) { file = this.getFile(file) return this.canRequestSign From 5689200ba3567c9eabc6be779a0e97c445408d62 Mon Sep 17 00:00:00 2001 From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> Date: Sun, 14 Dec 2025 16:14:09 -0300 Subject: [PATCH 06/11] feat: show warning when DocMDP prevents adding signers - Add NcNoteCard warning message for DocMDP level 1 - Add showDocMdpWarning computed property - Warning only shows when button is hidden - Prevents UI flash during state transitions - Uses consistent error message with backend Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> --- src/Components/RightSidebar/RequestSignatureTab.vue | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/Components/RightSidebar/RequestSignatureTab.vue b/src/Components/RightSidebar/RequestSignatureTab.vue index 3391749c23..e8ea7a1a83 100644 --- a/src/Components/RightSidebar/RequestSignatureTab.vue +++ b/src/Components/RightSidebar/RequestSignatureTab.vue @@ -4,6 +4,9 @@ -->