From 4513d9885be68ecdb0a38d414f11f9e92bf359d3 Mon Sep 17 00:00:00 2001 From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> Date: Tue, 30 Dec 2025 12:31:31 -0300 Subject: [PATCH 1/2] fix: handle envelope child signers Derive envelope ids from parent files when propagating identified dates and signing child documents, skip updating the active sign request, and avoid returning partially cached children lists. Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> --- lib/Db/FileMapper.php | 2 +- lib/Service/IdentifyMethod/IdentifyService.php | 13 +++++++++++-- lib/Service/SignFileService.php | 11 ++++++++--- 3 files changed, 20 insertions(+), 6 deletions(-) diff --git a/lib/Db/FileMapper.php b/lib/Db/FileMapper.php index e941ece8bb..dd41b3b532 100644 --- a/lib/Db/FileMapper.php +++ b/lib/Db/FileMapper.php @@ -281,7 +281,7 @@ public function neutralizeDeletedUser(string $userId, string $displayName): void */ public function getChildrenFiles(int $parentId): array { $cached = array_filter($this->file, fn ($f) => $f->getParentFileId() === $parentId); - if (!empty($cached)) { + if (!empty($cached) && count($cached) > 1) { return array_values($cached); } diff --git a/lib/Service/IdentifyMethod/IdentifyService.php b/lib/Service/IdentifyMethod/IdentifyService.php index 1111dee882..ecc0986a1e 100644 --- a/lib/Service/IdentifyMethod/IdentifyService.php +++ b/lib/Service/IdentifyMethod/IdentifyService.php @@ -67,16 +67,25 @@ private function propagateIdentifiedDateToEnvelopeChildren(IdentifyMethod $ident $signRequest = $this->signRequestMapper->getById($identifyMethod->getSignRequestId()); $fileEntity = $this->fileMapper->getById($signRequest->getFileId()); - if (method_exists($fileEntity, 'getNodeType') && $fileEntity->getNodeType() !== 'envelope') { + if ($fileEntity->getNodeType() === 'envelope') { + $envelopeId = $fileEntity->getId(); + } elseif ($fileEntity->hasParent()) { + $envelopeId = $fileEntity->getParentFileId(); + } else { return; } $children = $this->signRequestMapper->getByEnvelopeChildrenAndIdentifyMethod( - $fileEntity->getId(), + $envelopeId, $signRequest->getId(), ); foreach ($children as $childSignRequest) { + // Skip the current sign request to avoid updating it twice + if ($childSignRequest->getId() === $signRequest->getId()) { + continue; + } + $childMethods = $this->identifyMethodMapper->getIdentifyMethodsFromSignRequestId($childSignRequest->getId()); foreach ($childMethods as $childEntity) { diff --git a/lib/Service/SignFileService.php b/lib/Service/SignFileService.php index d2342d17fc..94b767337e 100644 --- a/lib/Service/SignFileService.php +++ b/lib/Service/SignFileService.php @@ -388,21 +388,26 @@ public function sign(): void { * @return array Array of ['file' => FileEntity, 'signRequest' => SignRequestEntity] */ private function getSignRequestsToSign(): array { - if (!$this->libreSignFile->isEnvelope()) { + if (!$this->libreSignFile->isEnvelope() + && !$this->libreSignFile->hasParent() + ) { return [[ 'file' => $this->libreSignFile, 'signRequest' => $this->signRequest, ]]; } - $childFiles = $this->fileMapper->getChildrenFiles($this->libreSignFile->getId()); + $envelopeId = $this->libreSignFile->isEnvelope() + ? $this->libreSignFile->getId() + : $this->libreSignFile->getParentFileId(); + $childFiles = $this->fileMapper->getChildrenFiles($envelopeId); if (empty($childFiles)) { throw new LibresignException('No files found in envelope'); } $childSignRequests = $this->signRequestMapper->getByEnvelopeChildrenAndIdentifyMethod( - $this->libreSignFile->getId(), + $envelopeId, $this->signRequest->getId() ); From 121df0bd66660aa39ea4847605c46f22afc1a524 Mon Sep 17 00:00:00 2001 From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> Date: Tue, 30 Dec 2025 12:36:35 -0300 Subject: [PATCH 2/2] test: cover envelope child flows Add unit coverage for envelope child signing selection and identify propagation when identify data is applied to sibling requests. Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> --- .../IdentifyMethod/IdentifyServiceTest.php | 152 ++++++++++++++++++ .../php/Unit/Service/SignFileServiceTest.php | 46 ++++++ 2 files changed, 198 insertions(+) create mode 100644 tests/php/Unit/Service/IdentifyMethod/IdentifyServiceTest.php diff --git a/tests/php/Unit/Service/IdentifyMethod/IdentifyServiceTest.php b/tests/php/Unit/Service/IdentifyMethod/IdentifyServiceTest.php new file mode 100644 index 0000000000..b7b8b11f05 --- /dev/null +++ b/tests/php/Unit/Service/IdentifyMethod/IdentifyServiceTest.php @@ -0,0 +1,152 @@ +identifyMethodMapper = $this->createMock(IdentifyMethodMapper::class); + $this->sessionService = $this->createMock(SessionService::class); + $this->timeFactory = $this->createMock(ITimeFactory::class); + $this->eventDispatcher = $this->createMock(IEventDispatcher::class); + $this->rootFolder = $this->createMock(IRootFolder::class); + $this->appConfig = $this->createMock(IAppConfig::class); + $this->signRequestMapper = $this->createMock(SignRequestMapper::class); + $this->l10n = $this->createMock(IL10N::class); + $this->fileMapper = $this->createMock(FileMapper::class); + $this->hasher = $this->createMock(IHasher::class); + $this->userManager = $this->createMock(IUserManager::class); + $this->urlGenerator = $this->createMock(IURLGenerator::class); + $this->logger = $this->createMock(LoggerInterface::class); + + $this->service = new IdentifyService( + $this->identifyMethodMapper, + $this->sessionService, + $this->timeFactory, + $this->eventDispatcher, + $this->rootFolder, + $this->appConfig, + $this->signRequestMapper, + $this->l10n, + $this->fileMapper, + $this->hasher, + $this->userManager, + $this->urlGenerator, + $this->logger, + ); + } + + public function testPropagateIdentifiedDateSkipsCurrentRequestAndUpdatesSiblings(): void { + $identifyMethod = new IdentifyMethod(); + $identifyMethod->setSignRequestId(1); + $identifyMethod->setIdentifierKey('email'); + $identifyMethod->setIdentifierValue('user@example.com'); + $identifyMethod->setIdentifiedAtDate('2024-01-01T00:00:00Z'); + + $parentEnvelopeId = 99; + + $currentFile = new File(); + $currentFile->setId(10); + $currentFile->setParentFileId($parentEnvelopeId); + + $currentSignRequest = new SignRequest(); + $currentSignRequest->setId(1); + $currentSignRequest->setFileId($currentFile->getId()); + + $siblingFile = new File(); + $siblingFile->setId(11); + $siblingFile->setParentFileId($parentEnvelopeId); + + $siblingSignRequest = new SignRequest(); + $siblingSignRequest->setId(2); + $siblingSignRequest->setFileId($siblingFile->getId()); + + $this->signRequestMapper + ->expects($this->once()) + ->method('getById') + ->with($identifyMethod->getSignRequestId()) + ->willReturn($currentSignRequest); + + $this->fileMapper + ->expects($this->once()) + ->method('getById') + ->with($currentFile->getId()) + ->willReturn($currentFile); + + $this->signRequestMapper + ->expects($this->once()) + ->method('getByEnvelopeChildrenAndIdentifyMethod') + ->with($parentEnvelopeId, $currentSignRequest->getId()) + ->willReturn([$currentSignRequest, $siblingSignRequest]); + + $siblingIdentifyMethod = new IdentifyMethod(); + $siblingIdentifyMethod->setSignRequestId($siblingSignRequest->getId()); + $siblingIdentifyMethod->setIdentifierKey($identifyMethod->getIdentifierKey()); + $siblingIdentifyMethod->setIdentifierValue($identifyMethod->getIdentifierValue()); + + $this->identifyMethodMapper + ->expects($this->exactly(2)) + ->method('getIdentifyMethodsFromSignRequestId') + ->willReturnMap([ + [$identifyMethod->getSignRequestId(), []], + [$siblingSignRequest->getId(), [$siblingIdentifyMethod]], + ]); + + $this->identifyMethodMapper + ->expects($this->once()) + ->method('insertOrUpdate') + ->with($identifyMethod); + + $this->identifyMethodMapper + ->expects($this->once()) + ->method('update') + ->with($this->callback(function (IdentifyMethod $updated) use ($siblingIdentifyMethod, $identifyMethod) { + return $updated->getSignRequestId() === $siblingIdentifyMethod->getSignRequestId() + && $updated->getIdentifiedAtDate() == $identifyMethod->getIdentifiedAtDate(); + })); + + $this->service->save($identifyMethod); + } +} diff --git a/tests/php/Unit/Service/SignFileServiceTest.php b/tests/php/Unit/Service/SignFileServiceTest.php index a914a8f309..3b9b542f24 100644 --- a/tests/php/Unit/Service/SignFileServiceTest.php +++ b/tests/php/Unit/Service/SignFileServiceTest.php @@ -625,6 +625,52 @@ public static function providerGetOrGeneratePfxContent(): array { ]; } + public function testGetSignRequestsToSignWhenFileHasParentEnvelope(): void { + $service = $this->getService(); + + $envelopeId = 99; + $childFile = new File(); + $childFile->setId(10); + $childFile->setParentFileId($envelopeId); + + $siblingFile = new File(); + $siblingFile->setId(11); + $siblingFile->setParentFileId($envelopeId); + + $signRequest = new SignRequest(); + $signRequest->setId(200); + $signRequest->setFileId($childFile->getId()); + + $siblingSignRequest = new SignRequest(); + $siblingSignRequest->setId(201); + $siblingSignRequest->setFileId($siblingFile->getId()); + + $this->fileMapper + ->expects($this->once()) + ->method('getChildrenFiles') + ->with($envelopeId) + ->willReturn([$childFile, $siblingFile]); + + $this->signRequestMapper + ->expects($this->once()) + ->method('getByEnvelopeChildrenAndIdentifyMethod') + ->with($envelopeId, $signRequest->getId()) + ->willReturn([$signRequest, $siblingSignRequest]); + + $result = self::invokePrivate( + $service + ->setLibreSignFile($childFile) + ->setSignRequest($signRequest), + 'getSignRequestsToSign' + ); + + $this->assertCount(2, $result); + $this->assertSame($childFile, $result[0]['file']); + $this->assertSame($signRequest, $result[0]['signRequest']); + $this->assertSame($siblingFile, $result[1]['file']); + $this->assertSame($siblingSignRequest, $result[1]['signRequest']); + } + #[DataProvider('providerStoreUserMetadata')] public function testStoreUserMetadata(bool $collectMetadata, ?array $previous, array $new, ?array $expected): void { $signRequest = new \OCA\Libresign\Db\SignRequest();