From 1c1868ba630cbbe2895f7b8a3ef4111546027ac9 Mon Sep 17 00:00:00 2001 From: Vitor Mattos Date: Thu, 18 Apr 2024 14:57:36 -0300 Subject: [PATCH 01/18] fix: save and retrieve file to guest account Signed-off-by: Vitor Mattos --- lib/Service/AccountService.php | 4 +- lib/Service/FolderService.php | 57 +++++++++++---------------- lib/Service/SignFileService.php | 9 +---- lib/Service/SignerElementsService.php | 2 +- lib/Service/TFile.php | 3 +- 5 files changed, 29 insertions(+), 46 deletions(-) diff --git a/lib/Service/AccountService.php b/lib/Service/AccountService.php index 9221398202..41456be8e8 100644 --- a/lib/Service/AccountService.php +++ b/lib/Service/AccountService.php @@ -380,9 +380,7 @@ private function updateFileOfVisibleElement(array $data): void { return; } $userElement = $this->userElementMapper->findOne(['id' => $data['elementId']]); - $userFolder = $this->folderService->getFolder($userElement->getFileId()); - /** @var \OCP\Files\File */ - $file = $userFolder->getById($userElement->getFileId())[0]; + $file = $this->folderService->getFileById($userElement->getFileId()); $file->putContent($this->getFileRaw($data)); } diff --git a/lib/Service/FolderService.php b/lib/Service/FolderService.php index 0e056221f4..6ad481dabd 100644 --- a/lib/Service/FolderService.php +++ b/lib/Service/FolderService.php @@ -28,6 +28,7 @@ use OCP\AppFramework\Services\IAppConfig; use OCP\Files\AppData\IAppDataFactory; use OCP\Files\Config\IUserMountCache; +use OCP\Files\File; use OCP\Files\Folder; use OCP\Files\IAppData; use OCP\Files\IRootFolder; @@ -62,37 +63,34 @@ public function getUserId(): ?string { } /** - * Get folder for user + * Get folder for user and creates it if non-existent * * @psalm-suppress MixedReturnStatement - * @psalm-suppress InvalidReturnStatement - * @psalm-suppress MixedMethodCall + * @throws NotFoundException + * @throws NotPermittedException */ - public function getFolder(int $nodeId = null): Folder { - if ($nodeId) { - $mountsContainingFile = $this->userMountCache->getMountsForFileId($nodeId); - foreach ($mountsContainingFile as $fileInfo) { - $this->root->getByIdInPath($nodeId, $fileInfo->getMountPoint()); - } - $node = $this->root->getById($nodeId); - if (!$node) { - throw new \Exception('Invalid node'); - } - return $node[0]->getParent(); + public function getFolder(): Folder { + $path = $this->getLibreSignDefaultPath(); + $containerFolder = $this->getContainerFolder(); + if (!$containerFolder->nodeExists($path)) { + return $containerFolder->newFolder($path); } - - return $this->getOrCreateFolder(); + return $containerFolder->get($path); } - /** - * Finds a folder and creates it if non-existent - * - * @psalm-suppress MixedReturnStatement - * @throws NotFoundException - * @throws NotPermittedException - */ - private function getOrCreateFolder(): Folder { + public function getFileById(int $nodeId = null): File { $path = $this->getLibreSignDefaultPath(); + $containerFolder = $this->getContainerFolder(); + if (!$containerFolder->nodeExists($path)) { + throw new \Exception('Invalid node'); + } + /** @var Folder $folder */ + $folder = $containerFolder->get($path); + $file = $folder->getById($nodeId); + return current($file); + } + + private function getContainerFolder(): Folder { $withoutPermission = false; if ($this->getUserId()) { $containerFolder = $this->root->getUserFolder($this->getUserId()); @@ -110,16 +108,9 @@ private function getOrCreateFolder(): Folder { $reflection = new \ReflectionClass($containerFolder); $reflectionProperty = $reflection->getProperty('folder'); $reflectionProperty->setAccessible(true); - $containerFolder = $reflectionProperty->getValue($containerFolder); - } else { - $containerFolder = $this->root->getUserFolder($this->getUserId()); - } - if ($containerFolder->nodeExists($path)) { - $folder = $containerFolder->get($path); - } else { - $folder = $containerFolder->newFolder($path); + return $reflectionProperty->getValue($containerFolder); } - return $folder; + return $this->root->getUserFolder($this->getUserId()); } /** diff --git a/lib/Service/SignFileService.php b/lib/Service/SignFileService.php index 37408d54b6..afff9c206e 100644 --- a/lib/Service/SignFileService.php +++ b/lib/Service/SignFileService.php @@ -246,20 +246,15 @@ public function setVisibleElements(array $list): self { } try { if ($this->user instanceof IUser) { - $mountsContainingFile = $this->userMountCache->getMountsForFileId($nodeId); - foreach ($mountsContainingFile as $fileInfo) { - $this->root->getByIdInPath($nodeId, $fileInfo->getMountPoint()); - } - /** @var \OCP\Files\File[] */ - $node = $this->root->getById($nodeId); + $node = $this->folderService->getFileById($nodeId); } else { $filesOfElementes = $this->signerElementsService->getElementsFromSession(); $node = array_filter($filesOfElementes, fn ($file) => $file->getId() === $nodeId); + $node = current($node); } if (!$node) { throw new \Exception('empty'); } - $node = current($node); } catch (\Throwable $th) { throw new LibresignException($this->l10n->t('You need to define a visible signature or initials to sign this document.')); } diff --git a/lib/Service/SignerElementsService.php b/lib/Service/SignerElementsService.php index cb3acf70ff..84430cc1ba 100644 --- a/lib/Service/SignerElementsService.php +++ b/lib/Service/SignerElementsService.php @@ -85,7 +85,7 @@ public function getUserElements(string $userId): array { private function signatureFileExists(UserElement $userElement): bool { try { - $this->folderService->getFolder($userElement->getFileId()); + $this->folderService->getFileById($userElement->getFileId()); } catch (\Exception $e) { $this->userElementMapper->delete($userElement); return false; diff --git a/lib/Service/TFile.php b/lib/Service/TFile.php index c1165f5695..7231e66bbb 100644 --- a/lib/Service/TFile.php +++ b/lib/Service/TFile.php @@ -42,8 +42,7 @@ public function getNodeFromData(array $data): Node { return $data['file']['fileNode']; } if (isset($data['file']['fileId'])) { - $userFolder = $this->folderService->getFolder($data['file']['fileId']); - return $userFolder->getById($data['file']['fileId'])[0]; + return $this->folderService->getFileById($data['file']['fileId']); } if (isset($data['file']['path'])) { return $this->folderService->getFileByPath($data['file']['path']); From f2f70d6663b8ec53bda42ac76414395e1ab6a816 Mon Sep 17 00:00:00 2001 From: Vitor Mattos Date: Thu, 18 Apr 2024 15:23:41 -0300 Subject: [PATCH 02/18] fix: unit test Signed-off-by: Vitor Mattos --- tests/Unit/Service/FolderServiceTest.php | 54 +++++++++++++++++++++++- 1 file changed, 52 insertions(+), 2 deletions(-) diff --git a/tests/Unit/Service/FolderServiceTest.php b/tests/Unit/Service/FolderServiceTest.php index 4f3d7b429c..43b64841a0 100644 --- a/tests/Unit/Service/FolderServiceTest.php +++ b/tests/Unit/Service/FolderServiceTest.php @@ -2,14 +2,54 @@ namespace OCA\Libresign\Tests\Unit\Service; +use Exception; use OCA\Libresign\Service\FolderService; use OCP\AppFramework\Services\IAppConfig; use OCP\Files\AppData\IAppDataFactory; use OCP\Files\Config\IUserMountCache; +use OCP\Files\Folder; +use OCP\Files\IAppData; use OCP\Files\IRootFolder; +use OCP\Files\SimpleFS\ISimpleFile; +use OCP\Files\SimpleFS\ISimpleFolder; use OCP\IGroupManager; use OCP\IL10N; +final class FakeFolder implements ISimpleFolder { + public Folder $folder; + + public function getName(): string { + return 'fake'; + } + + public function getDirectoryListing(): array { + return []; + } + + public function delete(): void { + } + + public function fileExists(string $name): bool { + return false; + } + + public function getFile(string $name): ISimpleFile { + throw new Exception('fake class'); + } + + public function newFile(string $name, $content = null): ISimpleFile { + throw new Exception('fake class'); + } + + public function getFolder(string $name): ISimpleFolder { + throw new Exception('fake class'); + } + + public function newFolder(string $path): ISimpleFolder { + throw new Exception('fake class'); + } +} + final class FolderServiceTest extends \OCA\Libresign\Tests\Unit\TestCase { public function testGetFolderWithInvalidNodeId() { $folder = $this->createMock(\OCP\Files\Folder::class); @@ -49,9 +89,19 @@ public function testGetFolderWithValidNodeId() { $node->method('getParent') ->willReturn($folder); $root = $this->createMock(IRootFolder::class); - $root->method('getById') - ->willReturn([$node]); + $root->method('getUserFolder') + ->willReturn($node); + + $folder->method('nodeExists')->willReturn(true); + $folder->method('get')->willReturn($folder); + $fakeFolder = new FakeFolder(); + $fakeFolder->folder = $folder; + $appData = $this->createMock(IAppData::class); + $appData->method('getFolder') + ->willReturn($fakeFolder); $appDataFactory = $this->createMock(IAppDataFactory::class); + $appDataFactory->method('get') + ->willReturn($appData); $groupManager = $this->createMock(IGroupManager::class); $appConfig = $this->createMock(IAppConfig::class); $l10n = $this->createMock(IL10N::class); From a7f0b746aa500978397ccd790d000ab160b342fc Mon Sep 17 00:00:00 2001 From: Vitor Mattos Date: Thu, 18 Apr 2024 16:17:30 -0300 Subject: [PATCH 03/18] chore: Change visibility of method that only is used at class scope Signed-off-by: Vitor Mattos --- lib/Service/FolderService.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Service/FolderService.php b/lib/Service/FolderService.php index 6ad481dabd..12429786a5 100644 --- a/lib/Service/FolderService.php +++ b/lib/Service/FolderService.php @@ -116,7 +116,7 @@ private function getContainerFolder(): Folder { /** * @psalm-suppress MixedReturnStatement */ - public function getLibreSignDefaultPath(): string { + private function getLibreSignDefaultPath(): string { if (!$this->userId) { return 'unauthenticated'; } From 663580b123963e5290fe0eb4602cd3595ee368ef Mon Sep 17 00:00:00 2001 From: Vitor Mattos Date: Thu, 18 Apr 2024 16:18:02 -0300 Subject: [PATCH 04/18] chore: Remove unecessary annotation Signed-off-by: Vitor Mattos --- lib/Service/FolderService.php | 3 --- 1 file changed, 3 deletions(-) diff --git a/lib/Service/FolderService.php b/lib/Service/FolderService.php index 12429786a5..9443cf2a7e 100644 --- a/lib/Service/FolderService.php +++ b/lib/Service/FolderService.php @@ -113,9 +113,6 @@ private function getContainerFolder(): Folder { return $this->root->getUserFolder($this->getUserId()); } - /** - * @psalm-suppress MixedReturnStatement - */ private function getLibreSignDefaultPath(): string { if (!$this->userId) { return 'unauthenticated'; From 72facf64891d0e07f67d077211e23cb521ed32d9 Mon Sep 17 00:00:00 2001 From: Vitor Mattos Date: Thu, 18 Apr 2024 16:40:41 -0300 Subject: [PATCH 05/18] chore: simplify tests Signed-off-by: Vitor Mattos --- tests/Unit/Service/FolderServiceTest.php | 79 ++++++++++++------------ 1 file changed, 40 insertions(+), 39 deletions(-) diff --git a/tests/Unit/Service/FolderServiceTest.php b/tests/Unit/Service/FolderServiceTest.php index 43b64841a0..929c74a6c5 100644 --- a/tests/Unit/Service/FolderServiceTest.php +++ b/tests/Unit/Service/FolderServiceTest.php @@ -14,6 +14,7 @@ use OCP\Files\SimpleFS\ISimpleFolder; use OCP\IGroupManager; use OCP\IL10N; +use PHPUnit\Framework\MockObject\MockObject; final class FakeFolder implements ISimpleFolder { public Folder $folder; @@ -51,45 +52,57 @@ public function newFolder(string $path): ISimpleFolder { } final class FolderServiceTest extends \OCA\Libresign\Tests\Unit\TestCase { - public function testGetFolderWithInvalidNodeId() { + private IRootFolder|MockObject $root; + private IUserMountCache|MockObject $userMountCache; + private IAppDataFactory|MockObject $appDataFactory; + private IGroupManager|MockObject $groupManager; + private IAppConfig|MockObject $appConfig; + private IL10N|MockObject $l10n; + + public function setUp(): void { + parent::setUp(); + $this->root = $this->createMock(IRootFolder::class); + $this->userMountCache = $this->createMock(IUserMountCache::class); + $this->appDataFactory = $this->createMock(IAppDataFactory::class); + $this->groupManager = $this->createMock(IGroupManager::class); + $this->appConfig = $this->createMock(IAppConfig::class); + $this->l10n = $this->createMock(IL10N::class); + } + + private function getFolderService(int $userId = 171): FolderSErvice { + $service = new FolderService( + $this->root, + $this->userMountCache, + $this->appDataFactory, + $this->groupManager, + $this->appConfig, + $this->l10n, + $userId + ); + return $service; + } + + public function testGetFileWithInvalidNodeId() { $folder = $this->createMock(\OCP\Files\Folder::class); - $root = $this->createMock(IRootFolder::class); - $root + $folder->method('isUpdateable')->willReturn(true); + $this->root ->method('getUserFolder') ->willReturn($folder); - $userMountCache = $this->createMock(IUserMountCache::class); - $userMountCache - ->method('getMountsForFileId') - ->willreturn([]); - $appDataFactory = $this->createMock(IAppDataFactory::class); - $groupManager = $this->createMock(IGroupManager::class); - $appConfig = $this->createMock(IAppConfig::class); - $l10n = $this->createMock(IL10N::class); - $service = new FolderService( - $root, - $userMountCache, - $appDataFactory, - $groupManager, - $appConfig, - $l10n, - 171 - ); + $service = $this->getFolderService(); $this->expectExceptionMessage('Invalid node'); - $service->getFolder(171); + $service->getFileById(171); } public function testGetFolderWithValidNodeId() { - $userMountCache = $this->createMock(IUserMountCache::class); - $userMountCache + $this->userMountCache ->method('getMountsForFileId') ->willreturn([]); $node = $this->createMock(\OCP\Files\File::class); $folder = $this->createMock(\OCP\Files\Folder::class); $node->method('getParent') ->willReturn($folder); - $root = $this->createMock(IRootFolder::class); - $root->method('getUserFolder') + $this->root->method('getUserFolder') ->willReturn($node); $folder->method('nodeExists')->willReturn(true); @@ -99,22 +112,10 @@ public function testGetFolderWithValidNodeId() { $appData = $this->createMock(IAppData::class); $appData->method('getFolder') ->willReturn($fakeFolder); - $appDataFactory = $this->createMock(IAppDataFactory::class); - $appDataFactory->method('get') + $this->appDataFactory->method('get') ->willReturn($appData); - $groupManager = $this->createMock(IGroupManager::class); - $appConfig = $this->createMock(IAppConfig::class); - $l10n = $this->createMock(IL10N::class); - $service = new FolderService( - $root, - $userMountCache, - $appDataFactory, - $groupManager, - $appConfig, - $l10n, - 1 - ); + $service = $this->getFolderService(1); $actual = $service->getFolder(171); $this->assertInstanceOf(\OCP\Files\Folder::class, $actual); } From 52f82e2cf274bd4b22680e1f8c3b75d695024c63 Mon Sep 17 00:00:00 2001 From: Vitor Mattos Date: Thu, 18 Apr 2024 16:59:17 -0300 Subject: [PATCH 06/18] chore: add more tests Signed-off-by: Vitor Mattos --- tests/Unit/Service/FolderServiceTest.php | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/tests/Unit/Service/FolderServiceTest.php b/tests/Unit/Service/FolderServiceTest.php index 929c74a6c5..646dd481db 100644 --- a/tests/Unit/Service/FolderServiceTest.php +++ b/tests/Unit/Service/FolderServiceTest.php @@ -69,7 +69,7 @@ public function setUp(): void { $this->l10n = $this->createMock(IL10N::class); } - private function getFolderService(int $userId = 171): FolderSErvice { + private function getFolderService(?int $userId = 171): FolderSErvice { $service = new FolderService( $this->root, $this->userMountCache, @@ -82,12 +82,27 @@ private function getFolderService(int $userId = 171): FolderSErvice { return $service; } + public function testGetFolderAsUnauthenticatedWhenUserIdIsInvalid() { + $folder = $this->createMock(\OCP\Files\Folder::class); + $folder->method('nodeExists') + ->with($this->equalTo('unauthenticated')) + ->willReturn(true); + $folder->method('get')->willReturn($folder); + $fakeFolder = new FakeFolder(); + $fakeFolder->folder = $folder; + $appData = $this->createMock(IAppData::class); + $appData->method('getFolder')->willReturn($fakeFolder); + $this->appDataFactory->method('get')->willReturn($appData); + + $service = $this->getFolderService(null); + $service->getFolder(171); + $this->assertTrue(true); + } + public function testGetFileWithInvalidNodeId() { $folder = $this->createMock(\OCP\Files\Folder::class); $folder->method('isUpdateable')->willReturn(true); - $this->root - ->method('getUserFolder') - ->willReturn($folder); + $this->root->method('getUserFolder')->willReturn($folder); $service = $this->getFolderService(); $this->expectExceptionMessage('Invalid node'); From d81f31dd00230dd9a8a9386e933c8e5de6e7f0f9 Mon Sep 17 00:00:00 2001 From: Vitor Mattos Date: Thu, 18 Apr 2024 17:42:14 -0300 Subject: [PATCH 07/18] feat: return the result of command that have not expected result code Signed-off-by: Vitor Mattos --- tests/integration/features/bootstrap/FeatureContext.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/integration/features/bootstrap/FeatureContext.php b/tests/integration/features/bootstrap/FeatureContext.php index f0a79259fd..30f0832d21 100644 --- a/tests/integration/features/bootstrap/FeatureContext.php +++ b/tests/integration/features/bootstrap/FeatureContext.php @@ -57,7 +57,7 @@ public static function runCommand(string $command): array { */ public static function runCommandWithResultCode(string $command, int $resultCode = 0): void { $return = self::runCommand($command); - Assert::assertEquals($resultCode, $return['resultCode']); + Assert::assertEquals($resultCode, $return['resultCode'], print_r($return, true)); } public function setOpenedEmailStorage(OpenedEmailStorage $storage): void { From 6c06ef3bcc169ef32e02b3f438a7756e470c96f2 Mon Sep 17 00:00:00 2001 From: Vitor Mattos Date: Thu, 18 Apr 2024 18:24:35 -0300 Subject: [PATCH 08/18] feat: able to set environment to occ commands Was necessary to use the guest app to create a new guest with a default password Signed-off-by: Vitor Mattos --- .../features/bootstrap/FeatureContext.php | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/tests/integration/features/bootstrap/FeatureContext.php b/tests/integration/features/bootstrap/FeatureContext.php index 30f0832d21..b84a8a51fc 100644 --- a/tests/integration/features/bootstrap/FeatureContext.php +++ b/tests/integration/features/bootstrap/FeatureContext.php @@ -14,6 +14,7 @@ class FeatureContext extends NextcloudApiContext implements OpenedEmailStorageAwareContext { private array $signer = []; private array $file = []; + private static array $environments = []; private OpenedEmailStorage $openedEmailStorage; /** @@ -31,6 +32,7 @@ public static function beforeSuite(BeforeSuiteScope $scope) { * @BeforeScenario */ public static function BeforeScenario(): void { + self::$environments = []; self::runCommand('libresign:developer:reset --all'); } @@ -44,6 +46,9 @@ public static function runCommand(string $command): array { if (posix_getuid() !== $owner['uid']) { $fullCommand = 'runuser -u ' . $owner['name'] . ' -- ' . $fullCommand; } + if (!empty(self::$environments)) { + $fullCommand = http_build_query(self::$environments,'',' ') . ' ' . $fullCommand; + } $fullCommand .= ' 2>&1'; exec($fullCommand, $output, $resultCode); return [ @@ -60,6 +65,14 @@ public static function runCommandWithResultCode(string $command, int $resultCode Assert::assertEquals($resultCode, $return['resultCode'], print_r($return, true)); } + /** + * @Given create an environment :name with value :value to be used by occ command + */ + public static function createAnEnvironmentWithValueToBeUsedByOccCommand($name, $value) + { + self::$environments[$name] = $value; + } + public function setOpenedEmailStorage(OpenedEmailStorage $storage): void { $this->openedEmailStorage = $storage; } From b2fa5e4643924b76e775211ad0815d0bcbeb334b Mon Sep 17 00:00:00 2001 From: Vitor Mattos Date: Thu, 18 Apr 2024 20:23:09 -0300 Subject: [PATCH 09/18] feat: add more scenarios Signed-off-by: Vitor Mattos --- .../features/account/signature.feature | 46 +++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/tests/integration/features/account/signature.feature b/tests/integration/features/account/signature.feature index ea644d8a64..7115ff518b 100644 --- a/tests/integration/features/account/signature.feature +++ b/tests/integration/features/account/signature.feature @@ -119,3 +119,49 @@ Feature: account/signature And the response should be a JSON array with the following mandatory values | key | value | | message | Certificate file deleted with success. | + + Scenario: Create password to guest + Given guest "guest@test.coop" exists + And run the command "config:app:set guests whitelist --value libresign" with result code 0 + And as user "guest@test.coop" + And sending "post" to ocs "/apps/libresign/api/v1/account/signature" + | signPassword | password | + Then the response should have a status code 200 + + Scenario: CRUD of signature element to guest + Given guest "guest@test.coop" exists + And run the command "config:app:set guests whitelist --value libresign" with result code 0 + And as user "guest@test.coop" + When sending "post" to ocs "/apps/libresign/api/v1/account/signature/elements" + | elements | [{"type":"signature","file":{"base64":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII="}}] | + Then the response should have a status code 200 + When sending "get" to ocs "/apps/libresign/api/v1/account/signature/elements" + Then the response should be a JSON array with the following mandatory values + | key | value | + | elements | (jq).[]\|.type == "signature" | + And fetch field "(NODE_ID)elements.0.file.nodeId" from prevous JSON response + When sending "delete" to ocs "/apps/libresign/api/v1/account/signature/elements/" + Then the response should have a status code 200 + + Scenario: CRUD of signature element to signer by email without account + Given guest "guest@test.coop" exists + And run the command "config:app:set guests whitelist --value libresign" with result code 0 + And run the command "libresign:configure:openssl --cn test" with result code 0 + And as user "admin" + And sending "post" to ocs "/apps/provisioning_api/api/v1/config/apps/libresign/identify_methods" + | value | (string)[{"name":"email","enabled":true,"mandatory":true,"can_create_account":false}] | + And sending "post" to ocs "/apps/libresign/api/v1/request-signature" + | file | {"url":"/apps/libresign/develop/pdf"} | + | users | [{"identify":{"email":"guest@test.coop"}}] | + | name | document | + And as user "" + When sending "post" to ocs "/apps/libresign/api/v1/account/signature/elements" + | elements | [{"type":"signature","file":{"base64":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII="}}] | + Then the response should have a status code 200 + When sending "get" to ocs "/apps/libresign/api/v1/account/signature/elements" + Then the response should be a JSON array with the following mandatory values + | key | value | + | elements | (jq).[]\|.type == "signature" | + And fetch field "(NODE_ID)elements.0.file.nodeId" from prevous JSON response + When sending "delete" to ocs "/apps/libresign/api/v1/account/signature/elements/" + Then the response should have a status code 200 From c9c33a53f5b5c8284799ae10cd34acabb5233f2f Mon Sep 17 00:00:00 2001 From: Vitor Mattos Date: Thu, 18 Apr 2024 20:23:37 -0300 Subject: [PATCH 10/18] chore: linter Signed-off-by: Vitor Mattos --- tests/integration/features/bootstrap/FeatureContext.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/integration/features/bootstrap/FeatureContext.php b/tests/integration/features/bootstrap/FeatureContext.php index b84a8a51fc..ad9aaa6353 100644 --- a/tests/integration/features/bootstrap/FeatureContext.php +++ b/tests/integration/features/bootstrap/FeatureContext.php @@ -47,7 +47,7 @@ public static function runCommand(string $command): array { $fullCommand = 'runuser -u ' . $owner['name'] . ' -- ' . $fullCommand; } if (!empty(self::$environments)) { - $fullCommand = http_build_query(self::$environments,'',' ') . ' ' . $fullCommand; + $fullCommand = http_build_query(self::$environments, '', ' ') . ' ' . $fullCommand; } $fullCommand .= ' 2>&1'; exec($fullCommand, $output, $resultCode); From ce1036cdec9837d69c6359aee0fe35ec39a4f960 Mon Sep 17 00:00:00 2001 From: Vitor Mattos Date: Thu, 18 Apr 2024 20:24:00 -0300 Subject: [PATCH 11/18] feat: step to create guest account Signed-off-by: Vitor Mattos --- .../features/bootstrap/FeatureContext.php | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/tests/integration/features/bootstrap/FeatureContext.php b/tests/integration/features/bootstrap/FeatureContext.php index ad9aaa6353..b45ba4d38e 100644 --- a/tests/integration/features/bootstrap/FeatureContext.php +++ b/tests/integration/features/bootstrap/FeatureContext.php @@ -68,11 +68,26 @@ public static function runCommandWithResultCode(string $command, int $resultCode /** * @Given create an environment :name with value :value to be used by occ command */ - public static function createAnEnvironmentWithValueToBeUsedByOccCommand($name, $value) - { + public static function createAnEnvironmentWithValueToBeUsedByOccCommand($name, $value) { self::$environments[$name] = $value; } + /** + * @When guest :guest exists + * @param string $guest + */ + public function assureGuestExists(string $guest): void { + $response = $this->userExists($guest); + if ($response->getStatusCode() !== 200) { + $this->createAnEnvironmentWithValueToBeUsedByOccCommand('OC_PASS', '123456'); + $this->runCommandWithResultCode('guest:add admin ' . $guest . ' --password-from-env', 0); + // Set a display name different than the user ID to be able to + // ensure in the tests that the right value was returned. + $this->setUserDisplayName($guest); + $this->createdUsers[] = $guest; + } + } + public function setOpenedEmailStorage(OpenedEmailStorage $storage): void { $this->openedEmailStorage = $storage; } From 1257026bb5e49e8c9f07d4ff06296647ed4f3446 Mon Sep 17 00:00:00 2001 From: Vitor Mattos Date: Thu, 18 Apr 2024 20:24:36 -0300 Subject: [PATCH 12/18] fix: use LibreSign preview URL to visible elements Is necessary to a signer that will sign without account to be able to retrieve your own signature preview Signed-off-by: Vitor Mattos --- lib/Service/SignerElementsService.php | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/lib/Service/SignerElementsService.php b/lib/Service/SignerElementsService.php index 84430cc1ba..f656025a66 100644 --- a/lib/Service/SignerElementsService.php +++ b/lib/Service/SignerElementsService.php @@ -52,7 +52,10 @@ public function getUserElementByNodeId(string $userId, $nodeId): array { 'id' => $element->getId(), 'type' => $element->getType(), 'file' => [ - 'url' => $this->urlGenerator->linkToRoute('core.Preview.getPreviewByFileId', ['fileId' => $element->getFileId(), 'x' => self::ELEMENT_SIGN_WIDTH, 'y' => self::ELEMENT_SIGN_HEIGHT]), + 'url' => $this->urlGenerator->linkToRoute('ocs.libresign.account.getSignatureElementPreview', [ + 'apiVersion' => 'v1', + 'nodeId' => $element->getFileId(), + ]), 'nodeId' => $element->getFileId() ], 'uid' => $element->getUserId(), @@ -73,7 +76,10 @@ public function getUserElements(string $userId): array { 'id' => $element->getId(), 'type' => $element->getType(), 'file' => [ - 'url' => $this->urlGenerator->linkToRoute('core.Preview.getPreviewByFileId', ['fileId' => $element->getFileId(), 'x' => self::ELEMENT_SIGN_WIDTH, 'y' => self::ELEMENT_SIGN_HEIGHT]), + 'url' => $this->urlGenerator->linkToRoute('ocs.libresign.account.getSignatureElementPreview', [ + 'apiVersion' => 'v1', + 'nodeId' => $element->getFileId(), + ]), 'nodeId' => $element->getFileId() ], 'starred' => $element->getStarred() ? 1 : 0, From 6b7ef3ae9137dc9ed266e7eb11463e438e8ebae4 Mon Sep 17 00:00:00 2001 From: Vitor Mattos Date: Thu, 18 Apr 2024 20:25:51 -0300 Subject: [PATCH 13/18] chore: Use more specific exception Signed-off-by: Vitor Mattos --- lib/Service/FolderService.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/Service/FolderService.php b/lib/Service/FolderService.php index 9443cf2a7e..1255ccd0b4 100644 --- a/lib/Service/FolderService.php +++ b/lib/Service/FolderService.php @@ -78,11 +78,14 @@ public function getFolder(): Folder { return $containerFolder->get($path); } + /** + * @throws NotFoundException + */ public function getFileById(int $nodeId = null): File { $path = $this->getLibreSignDefaultPath(); $containerFolder = $this->getContainerFolder(); if (!$containerFolder->nodeExists($path)) { - throw new \Exception('Invalid node'); + throw new NotFoundException('Invalid node'); } /** @var Folder $folder */ $folder = $containerFolder->get($path); From 37395d6a28482ddc7d424555d379b3ce5240d7ab Mon Sep 17 00:00:00 2001 From: Vitor Mattos Date: Thu, 18 Apr 2024 20:26:40 -0300 Subject: [PATCH 14/18] feat: make public the method to retrieve the signature file Signers that will sign without account need to retrieve the own signature preview Signed-off-by: Vitor Mattos --- lib/Controller/AccountController.php | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/Controller/AccountController.php b/lib/Controller/AccountController.php index b69a4b5aa2..aa6e7c5c32 100644 --- a/lib/Controller/AccountController.php +++ b/lib/Controller/AccountController.php @@ -297,6 +297,7 @@ public function createSignatureElement(array $elements): JSONResponse { #[NoAdminRequired] #[NoCSRFRequired] + #[PublicPage] public function getSignatureElements(): JSONResponse { $userId = $this->userSession->getUser()?->getUID(); try { From 3052a6ba90f2d7667eaa94f5c0f1179a465752b8 Mon Sep 17 00:00:00 2001 From: Vitor Mattos Date: Thu, 18 Apr 2024 20:27:38 -0300 Subject: [PATCH 15/18] chore: replace the way to retrieve the signature file Signed-off-by: Vitor Mattos --- lib/Service/AccountService.php | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/lib/Service/AccountService.php b/lib/Service/AccountService.php index 41456be8e8..fa908dcc25 100644 --- a/lib/Service/AccountService.php +++ b/lib/Service/AccountService.php @@ -50,6 +50,7 @@ use OCP\Files\Folder; use OCP\Files\IMimeTypeDetector; use OCP\Files\IRootFolder; +use OCP\Files\NotFoundException; use OCP\Http\Client\IClientService; use OCP\IConfig; use OCP\IGroupManager; @@ -308,14 +309,17 @@ public function getPdfByUuid(string $uuid): File { public function getFileByNodeIdAndSessionId(int $nodeId, string $sessionId): File { $rootSignatureFolder = $this->folderService->getFolder(); if (!$rootSignatureFolder->nodeExists($sessionId)) { - throw new DoesNotExistException('Not found'); + try { + return $this->folderService->getFileById($nodeId); + } catch (NotFoundException $th) { + throw new DoesNotExistException('Not found'); + } } - $nodes = $rootSignatureFolder->getById($nodeId); - if (empty($nodes)) { + try { + return $this->folderService->getFileById($nodeId); + } catch (NotFoundException $th) { throw new DoesNotExistException('Not found'); } - $file = current($nodes); - return $file; } public function canRequestSign(?IUser $user = null): bool { @@ -473,9 +477,10 @@ public function deleteSignatureElement(?IUser $user, string $sessionId, int $nod 'user_id' => $user->getUID(), ]); $this->userElementMapper->delete($element); - $file = $this->root->getById($element->getFileId()); - if ($file) { - current($file)->delete(); + try { + $file = $this->folderService->getFileById($element->getFileId()); + $file->delete(); + } catch (NotFoundException $e) { } } else { $rootSignatureFolder = $this->folderService->getFolder(); From 20c30bb5323df03055e3f5474558459b0d6b7b2e Mon Sep 17 00:00:00 2001 From: Vitor Mattos Date: Thu, 18 Apr 2024 20:28:03 -0300 Subject: [PATCH 16/18] feat: add Guests app to intetration flow Signed-off-by: Vitor Mattos --- .github/workflows/behat-mariadb.yml | 1 + .github/workflows/behat-mysql.yml | 1 + .github/workflows/behat-pgsql.yml | 1 + 3 files changed, 3 insertions(+) diff --git a/.github/workflows/behat-mariadb.yml b/.github/workflows/behat-mariadb.yml index 697a379da4..1dc91ca3bb 100644 --- a/.github/workflows/behat-mariadb.yml +++ b/.github/workflows/behat-mariadb.yml @@ -129,6 +129,7 @@ jobs: ./occ app:enable --force notifications git clone --depth 1 -b ${{ matrix.server-versions }} https://github.com/nextcloud/activity apps/activity ./occ app:enable --force activity + ./occ app:enable --force guests ./occ config:system:set mail_smtpport --value 1025 --type integer ./occ config:system:set mail_smtphost --value mailhog ./occ config:system:set allow_local_remote_servers --value true --type boolean diff --git a/.github/workflows/behat-mysql.yml b/.github/workflows/behat-mysql.yml index 87d0b8ec5c..9a2588fb93 100644 --- a/.github/workflows/behat-mysql.yml +++ b/.github/workflows/behat-mysql.yml @@ -137,6 +137,7 @@ jobs: ./occ app:enable --force notifications git clone --depth 1 -b ${{ matrix.server-versions }} https://github.com/nextcloud/activity apps/activity ./occ app:enable --force activity + ./occ app:enable --force guests ./occ config:system:set mail_smtpport --value 1025 --type integer ./occ config:system:set mail_smtphost --value mailhog ./occ config:system:set allow_local_remote_servers --value true --type boolean diff --git a/.github/workflows/behat-pgsql.yml b/.github/workflows/behat-pgsql.yml index 440cd0f571..7cd1878280 100644 --- a/.github/workflows/behat-pgsql.yml +++ b/.github/workflows/behat-pgsql.yml @@ -133,6 +133,7 @@ jobs: ./occ app:enable --force notifications git clone --depth 1 -b ${{ matrix.server-versions }} https://github.com/nextcloud/activity apps/activity ./occ app:enable --force activity + ./occ app:enable --force guests ./occ config:system:set mail_smtpport --value 1025 --type integer ./occ config:system:set mail_smtphost --value mailhog ./occ config:system:set allow_local_remote_servers --value true --type boolean From b4f94b52e386e0be6a83805d7a02ffad8416fbd6 Mon Sep 17 00:00:00 2001 From: Vitor Mattos Date: Fri, 19 Apr 2024 09:16:17 -0300 Subject: [PATCH 17/18] fix: command is not guest but is guests Signed-off-by: Vitor Mattos --- tests/integration/features/bootstrap/FeatureContext.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/integration/features/bootstrap/FeatureContext.php b/tests/integration/features/bootstrap/FeatureContext.php index b45ba4d38e..6e82696022 100644 --- a/tests/integration/features/bootstrap/FeatureContext.php +++ b/tests/integration/features/bootstrap/FeatureContext.php @@ -80,7 +80,7 @@ public function assureGuestExists(string $guest): void { $response = $this->userExists($guest); if ($response->getStatusCode() !== 200) { $this->createAnEnvironmentWithValueToBeUsedByOccCommand('OC_PASS', '123456'); - $this->runCommandWithResultCode('guest:add admin ' . $guest . ' --password-from-env', 0); + $this->runCommandWithResultCode('guests:add admin ' . $guest . ' --password-from-env', 0); // Set a display name different than the user ID to be able to // ensure in the tests that the right value was returned. $this->setUserDisplayName($guest); From 7ae99e787e3ddedc3d651c015e559fc03249f6f9 Mon Sep 17 00:00:00 2001 From: Vitor Mattos Date: Fri, 19 Apr 2024 09:25:24 -0300 Subject: [PATCH 18/18] fix: add pending commands at integration tests Signed-off-by: Vitor Mattos --- .github/workflows/behat-mariadb.yml | 1 + .github/workflows/behat-sqlite.yml | 1 + 2 files changed, 2 insertions(+) diff --git a/.github/workflows/behat-mariadb.yml b/.github/workflows/behat-mariadb.yml index 1dc91ca3bb..bec4b96e9e 100644 --- a/.github/workflows/behat-mariadb.yml +++ b/.github/workflows/behat-mariadb.yml @@ -133,6 +133,7 @@ jobs: ./occ config:system:set mail_smtpport --value 1025 --type integer ./occ config:system:set mail_smtphost --value mailhog ./occ config:system:set allow_local_remote_servers --value true --type boolean + ./occ config:system:set auth.bruteforce.protection.enabled --value false --type boolean - name: Run behat working-directory: apps/${{ env.APP_NAME }}/tests/integration diff --git a/.github/workflows/behat-sqlite.yml b/.github/workflows/behat-sqlite.yml index e3d6f9734f..a89714f635 100644 --- a/.github/workflows/behat-sqlite.yml +++ b/.github/workflows/behat-sqlite.yml @@ -124,6 +124,7 @@ jobs: ./occ app:enable --force notifications git clone --depth 1 -b ${{ matrix.server-versions }} https://github.com/nextcloud/activity apps/activity ./occ app:enable --force activity + ./occ app:enable --force guests ./occ config:system:set mail_smtpport --value 1025 --type integer ./occ config:system:set mail_smtphost --value mailhog ./occ config:system:set allow_local_remote_servers --value true --type boolean