Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion lib/Capabilities.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

namespace OCA\Libresign;

use OCA\Libresign\Service\EnvelopeService;
use OCA\Libresign\Service\Envelope\EnvelopeService;
use OCA\Libresign\Service\SignatureTextService;
use OCA\Libresign\Service\SignerElementsService;
use OCP\App\IAppManager;
Expand Down
45 changes: 45 additions & 0 deletions lib/Service/Envelope/EnvelopeFileRelocator.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
<?php

declare(strict_types=1);
/**
* SPDX-FileCopyrightText: 2025 LibreCode coop and contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/

namespace OCA\Libresign\Service\Envelope;

use OCA\Libresign\Exception\LibresignException;
use OCA\Libresign\Service\FolderService;
use OCP\Files\Node;
use OCP\IUser;

class EnvelopeFileRelocator {
public function __construct(
private FolderService $folderService,
) {
}

public function ensureFileInEnvelopeFolder(Node $sourceNode, int $envelopeFolderId, IUser $userManager): Node {
$this->folderService->setUserId($userManager->getUID());
$userRootFolder = $this->folderService->getUserRootFolder();
$envelopeFolder = $userRootFolder->getFirstNodeById($envelopeFolderId);

if (!$envelopeFolder instanceof \OCP\Files\Folder) {
throw new LibresignException('Envelope folder not found');
}

if ($this->isNodeInsideFolder($sourceNode, $envelopeFolder)) {
return $sourceNode;
}

if (!$sourceNode instanceof \OCP\Files\File) {
throw new LibresignException('Invalid file type for envelope');
}

return $envelopeFolder->newFile($sourceNode->getName(), $sourceNode->getContent());
}

private function isNodeInsideFolder(Node $node, \OCP\Files\Folder $folder): bool {
return str_starts_with($node->getPath(), $folder->getPath() . '/');
}
}
139 changes: 139 additions & 0 deletions lib/Service/Envelope/EnvelopeService.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
<?php

declare(strict_types=1);
/**
* SPDX-FileCopyrightText: 2025 LibreCode coop and contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/

namespace OCA\Libresign\Service\Envelope;

use DateTime;
use OCA\Libresign\AppInfo\Application;
use OCA\Libresign\Db\File as FileEntity;
use OCA\Libresign\Db\FileMapper;
use OCA\Libresign\Enum\NodeType;
use OCA\Libresign\Exception\LibresignException;
use OCA\Libresign\Service\FolderService;
use OCP\AppFramework\Db\DoesNotExistException;
use OCP\IAppConfig;
use OCP\IL10N;
use Sabre\DAV\UUIDUtil;

class EnvelopeService {
public function __construct(
protected FileMapper $fileMapper,
protected IL10N $l10n,
protected IAppConfig $appConfig,
protected FolderService $folderService,
) {
}

public function isEnabled(): bool {
return $this->appConfig->getValueBool(Application::APP_ID, 'envelope_enabled', true);
}

/**
* @throws LibresignException
*/
public function validateEnvelopeConstraints(int $fileCount): void {
if (!$this->isEnabled()) {
throw new LibresignException($this->l10n->t('Envelope feature is disabled'));
}

$maxFiles = $this->getMaxFilesPerEnvelope();
if ($fileCount > $maxFiles) {
throw new LibresignException(
$this->l10n->t('Maximum number of files per envelope (%s) exceeded', [$maxFiles])
);
}
}

public function createEnvelope(
string $name,
string $userId,
int $filesCount = 0,
?string $path = null,
): FileEntity {
$this->folderService->setUserId($userId);

$uuid = UUIDUtil::getUUID();
if ($path) {
$envelopeFolder = $this->folderService->getOrCreateFolderByAbsolutePath($path);
} else {
$parentFolder = $this->folderService->getFolder();
$folderName = $name . '_' . $uuid;
$envelopeFolder = $parentFolder->newFolder($folderName);
}

$envelope = new FileEntity();
$envelope->setNodeId($envelopeFolder->getId());
$envelope->setNodeTypeEnum(NodeType::ENVELOPE);
$envelope->setName($name);
$envelope->setUuid($uuid);
$envelope->setCreatedAt(new DateTime());
$envelope->setStatus(FileEntity::STATUS_DRAFT);

$envelope->setMetadata(['filesCount' => $filesCount]);

if ($userId) {
$envelope->setUserId($userId);
}

return $this->fileMapper->insert($envelope);
}

public function addFileToEnvelope(int $envelopeId, FileEntity $file): FileEntity {
$envelope = $this->fileMapper->getById($envelopeId);

if (!$envelope->isEnvelope()) {
throw new LibresignException($this->l10n->t('The specified ID is not an envelope'));
}

if ($envelope->getStatus() > FileEntity::STATUS_DRAFT) {
throw new LibresignException($this->l10n->t('Cannot add files to an envelope that is already in signing process'));
}

$maxFiles = $this->getMaxFilesPerEnvelope();
$currentCount = $this->fileMapper->countChildrenFiles($envelopeId);
if ($currentCount >= $maxFiles) {
throw new LibresignException(
$this->l10n->t('Maximum number of files per envelope (%s) exceeded', [$maxFiles])
);
}

$file->setParentFileId($envelopeId);
$file->setNodeTypeEnum(NodeType::FILE);

return $this->fileMapper->update($file);
}

public function getEnvelopeByFileId(int $fileId): ?FileEntity {
try {
return $this->fileMapper->getParentEnvelope($fileId);
} catch (DoesNotExistException) {
return null;
}
}

public function getEnvelopeFolder(FileEntity $envelope): \OCP\Files\Folder {
$userId = $envelope->getUserId();
if (!$userId) {
throw new LibresignException('Envelope does not have a user');
}

$this->folderService->setUserId($userId);
$userRootFolder = $this->folderService->getUserRootFolder();

$envelopeFolderNode = $userRootFolder->getFirstNodeById($envelope->getNodeId());
if (!$envelopeFolderNode instanceof \OCP\Files\Folder) {
throw new LibresignException('Envelope folder not found');
}

return $envelopeFolderNode;
}

private function getMaxFilesPerEnvelope(): int {
return $this->appConfig->getValueInt(Application::APP_ID, 'envelope_max_files', 50);
}
}
44 changes: 44 additions & 0 deletions lib/Service/EnvelopeFileRelocator.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
<?php

declare(strict_types=1);
/**
* SPDX-FileCopyrightText: 2025 LibreCode coop and contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/

namespace OCA\Libresign\Service;

use OCA\Libresign\Exception\LibresignException;
use OCP\Files\Node;
use OCP\IUser;

class EnvelopeFileRelocator {
public function __construct(
private FolderService $folderService,
) {
}

public function ensureFileInEnvelopeFolder(Node $sourceNode, int $envelopeFolderId, IUser $userManager): Node {
$this->folderService->setUserId($userManager->getUID());
$userRootFolder = $this->folderService->getUserRootFolder();
$envelopeFolder = $userRootFolder->getFirstNodeById($envelopeFolderId);

if (!$envelopeFolder instanceof \OCP\Files\Folder) {
throw new LibresignException('Envelope folder not found');
}

if ($this->isNodeInsideFolder($sourceNode, $envelopeFolder)) {
return $sourceNode;
}

if (!$sourceNode instanceof \OCP\Files\File) {
throw new LibresignException('Invalid file type for envelope');
}

return $envelopeFolder->newFile($sourceNode->getName(), $sourceNode->getContent());
}

private function isNodeInsideFolder(Node $node, \OCP\Files\Folder $folder): bool {
return str_starts_with($node->getPath(), $folder->getPath() . '/');
}
}
22 changes: 15 additions & 7 deletions lib/Service/EnvelopeService.php
Original file line number Diff line number Diff line change
Expand Up @@ -48,14 +48,22 @@ public function validateEnvelopeConstraints(int $fileCount): void {
}
}

public function createEnvelope(string $name, string $userId, int $filesCount = 0): FileEntity {
public function createEnvelope(
string $name,
string $userId,
int $filesCount = 0,
?string $path = null,
): FileEntity {
$this->folderService->setUserId($userId);

$parentFolder = $this->folderService->getFolder();

$uuid = UUIDUtil::getUUID();
$folderName = $name . '_' . $uuid;
$envelopeFolder = $parentFolder->newFolder($folderName);
if ($path) {
$envelopeFolder = $this->folderService->getOrCreateFolderByAbsolutePath($path);
} else {
$parentFolder = $this->folderService->getFolder();
$folderName = $name . '_' . $uuid;
$envelopeFolder = $parentFolder->newFolder($folderName);
}

$envelope = new FileEntity();
$envelope->setNodeId($envelopeFolder->getId());
Expand Down Expand Up @@ -114,9 +122,9 @@ public function getEnvelopeFolder(FileEntity $envelope): \OCP\Files\Folder {
}

$this->folderService->setUserId($userId);
$userFolder = $this->folderService->getFolder();
$userRootFolder = $this->folderService->getUserRootFolder();

$envelopeFolderNode = $userFolder->getFirstNodeById($envelope->getNodeId());
$envelopeFolderNode = $userRootFolder->getFirstNodeById($envelope->getNodeId());
if (!$envelopeFolderNode instanceof \OCP\Files\Folder) {
throw new LibresignException('Envelope folder not found');
}
Expand Down
1 change: 1 addition & 0 deletions lib/Service/FileService.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
use OCA\Libresign\Handler\SignEngine\Pkcs12Handler;
use OCA\Libresign\Helper\FileUploadHelper;
use OCA\Libresign\ResponseDefinitions;
use OCA\Libresign\Service\Envelope\EnvelopeService;
use OCA\Libresign\Service\File\CertificateChainService;
use OCA\Libresign\Service\File\EnvelopeAssembler;
use OCA\Libresign\Service\File\EnvelopeProgressService;
Expand Down
59 changes: 59 additions & 0 deletions lib/Service/FolderService.php
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,19 @@ public function getUserId(): ?string {
return $this->userId;
}

/**
* Get the user's root folder (full home), not the LibreSign container.
*
* @throws LibresignException
*/
public function getUserRootFolder(): Folder {
if (!$this->userId) {
throw new LibresignException('Invalid user to resolve folder');
}

return $this->root->getUserFolder($this->userId);
}

/**
* Get folder for user and creates it if non-existent
*
Expand Down Expand Up @@ -200,4 +213,50 @@ public function getFileByPath(string $path): Node {
throw new LibresignException($this->l10n->t('Invalid data to validate file'), 404);
}
}

/**
* Ensure a folder exists at a given absolute user path, creating missing segments.
* If the final folder already exists, it must be empty.
*
* @throws LibresignException
*/
public function getOrCreateFolderByAbsolutePath(string $path): Folder {
if (!$this->userId) {
throw new LibresignException('Invalid user to create envelope folder');
}

$cleanPath = ltrim($path, '/');
$userFolder = $this->root->getUserFolder($this->userId);

if ($cleanPath === '') {
return $userFolder;
}

$segments = array_filter(explode('/', $cleanPath), static fn (string $segment) => $segment !== '');
$folder = $userFolder;
$isLastSegment = false;

foreach ($segments as $index => $segment) {
$isLastSegment = ($index === count($segments) - 1);

try {
$node = $folder->get($segment);
if (!$node instanceof Folder) {
throw new LibresignException('Invalid folder path');
}
$folder = $node;

if ($isLastSegment) {
$contents = $folder->getDirectoryListing();
if (count($contents) > 0) {
throw new LibresignException($this->l10n->t('Folder already exists and is not empty: %s', [$path]));
}
}
} catch (NotFoundException) {
$folder = $folder->newFolder($segment);
}
}

return $folder;
}
}
Loading
Loading