From 1795d93b34cfcdf92edd051e7d0a90515ec5cd97 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 04f5c0a99d0e7923d543df68a86cf1438e35708a 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();