mirror of
https://github.com/LibreSign/libresign.git
synced 2025-12-18 05:20:45 +01:00
- 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>
992 lines
33 KiB
PHP
992 lines
33 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
/**
|
|
* SPDX-FileCopyrightText: 2020-2024 LibreCode coop and contributors
|
|
* SPDX-License-Identifier: AGPL-3.0-or-later
|
|
*/
|
|
|
|
namespace OCA\Libresign\Service;
|
|
|
|
use DateTime;
|
|
use DateTimeInterface;
|
|
use InvalidArgumentException;
|
|
use OC\Files\Filesystem;
|
|
use OCA\Libresign\AppInfo\Application;
|
|
use OCA\Libresign\Db\File;
|
|
use OCA\Libresign\Db\FileElement;
|
|
use OCA\Libresign\Db\FileElementMapper;
|
|
use OCA\Libresign\Db\FileMapper;
|
|
use OCA\Libresign\Db\IdDocsMapper;
|
|
use OCA\Libresign\Db\IdentifyMethod;
|
|
use OCA\Libresign\Db\SignRequest;
|
|
use OCA\Libresign\Db\SignRequestMapper;
|
|
use OCA\Libresign\Exception\LibresignException;
|
|
use OCA\Libresign\Handler\DocMdpHandler;
|
|
use OCA\Libresign\Handler\SignEngine\Pkcs12Handler;
|
|
use OCA\Libresign\Helper\ValidateHelper;
|
|
use OCA\Libresign\ResponseDefinitions;
|
|
use OCA\Libresign\Service\IdentifyMethod\IIdentifyMethod;
|
|
use OCP\Accounts\IAccountManager;
|
|
use OCP\AppFramework\Db\DoesNotExistException;
|
|
use OCP\Files\IMimeTypeDetector;
|
|
use OCP\Files\IRootFolder;
|
|
use OCP\Files\NotFoundException;
|
|
use OCP\Http\Client\IClientService;
|
|
use OCP\IAppConfig;
|
|
use OCP\IDateTimeFormatter;
|
|
use OCP\IL10N;
|
|
use OCP\IURLGenerator;
|
|
use OCP\IUser;
|
|
use OCP\IUserManager;
|
|
use OCP\IUserSession;
|
|
use Psr\Log\LoggerInterface;
|
|
use stdClass;
|
|
|
|
/**
|
|
* @psalm-import-type LibresignValidateFile from ResponseDefinitions
|
|
*/
|
|
class FileService {
|
|
use TFile;
|
|
|
|
private bool $showSigners = false;
|
|
private bool $showSettings = false;
|
|
private bool $showVisibleElements = false;
|
|
private bool $showMessages = false;
|
|
private bool $validateFile = false;
|
|
private bool $signersLibreSignLoaded = false;
|
|
private bool $signerIdentified = false;
|
|
private string $fileContent = '';
|
|
private string $host = '';
|
|
private ?File $file = null;
|
|
private ?SignRequest $signRequest = null;
|
|
private ?IUser $me = null;
|
|
private ?int $identifyMethodId = null;
|
|
private array $certData = [];
|
|
private stdClass $fileData;
|
|
public const IDENTIFICATION_DOCUMENTS_DISABLED = 0;
|
|
public const IDENTIFICATION_DOCUMENTS_NEED_SEND = 1;
|
|
public const IDENTIFICATION_DOCUMENTS_NEED_APPROVAL = 2;
|
|
public const IDENTIFICATION_DOCUMENTS_APPROVED = 3;
|
|
public function __construct(
|
|
protected FileMapper $fileMapper,
|
|
protected SignRequestMapper $signRequestMapper,
|
|
protected FileElementMapper $fileElementMapper,
|
|
protected FileElementService $fileElementService,
|
|
protected FolderService $folderService,
|
|
protected ValidateHelper $validateHelper,
|
|
protected PdfParserService $pdfParserService,
|
|
private IdDocsMapper $idDocsMapper,
|
|
private AccountService $accountService,
|
|
private IdentifyMethodService $identifyMethodService,
|
|
private IUserSession $userSession,
|
|
private IUserManager $userManager,
|
|
private IAccountManager $accountManager,
|
|
protected IClientService $client,
|
|
private IDateTimeFormatter $dateTimeFormatter,
|
|
private IAppConfig $appConfig,
|
|
private IURLGenerator $urlGenerator,
|
|
protected IMimeTypeDetector $mimeTypeDetector,
|
|
protected Pkcs12Handler $pkcs12Handler,
|
|
DocMdpHandler $docMdpHandler,
|
|
private IRootFolder $root,
|
|
protected LoggerInterface $logger,
|
|
protected IL10N $l10n,
|
|
) {
|
|
$this->docMdpHandler = $docMdpHandler;
|
|
$this->fileData = new stdClass();
|
|
}
|
|
|
|
/**
|
|
* @return static
|
|
*/
|
|
public function showSigners(bool $show = true): self {
|
|
$this->showSigners = $show;
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* @return static
|
|
*/
|
|
public function showSettings(bool $show = true): self {
|
|
$this->showSettings = $show;
|
|
if ($show) {
|
|
$this->fileData->settings = [
|
|
'canSign' => false,
|
|
'canRequestSign' => false,
|
|
'signerFileUuid' => null,
|
|
'phoneNumber' => '',
|
|
];
|
|
} else {
|
|
unset($this->fileData->settings);
|
|
}
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* @return static
|
|
*/
|
|
public function showVisibleElements(bool $show = true): self {
|
|
$this->showVisibleElements = $show;
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* @return static
|
|
*/
|
|
public function showMessages(bool $show = true): self {
|
|
$this->showMessages = $show;
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* @return static
|
|
*/
|
|
public function setMe(?IUser $user): self {
|
|
$this->me = $user;
|
|
return $this;
|
|
}
|
|
|
|
public function setSignerIdentified(bool $identified = true): self {
|
|
$this->signerIdentified = $identified;
|
|
return $this;
|
|
}
|
|
|
|
public function setIdentifyMethodId(?int $id): self {
|
|
$this->identifyMethodId = $id;
|
|
return $this;
|
|
}
|
|
|
|
public function setHost(string $host): self {
|
|
$this->host = $host;
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* @return static
|
|
*/
|
|
public function setFile(File $file): self {
|
|
$this->file = $file;
|
|
$this->fileData->status = $this->file->getStatus();
|
|
return $this;
|
|
}
|
|
|
|
public function setSignRequest(SignRequest $signRequest): self {
|
|
$this->signRequest = $signRequest;
|
|
return $this;
|
|
}
|
|
|
|
public function showValidateFile(bool $validateFile = true): self {
|
|
$this->validateFile = $validateFile;
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* @return static
|
|
*/
|
|
public function setFileByType(string $type, $identifier): self {
|
|
try {
|
|
/** @var File */
|
|
$file = call_user_func(
|
|
[$this->fileMapper, 'getBy' . $type],
|
|
$identifier
|
|
);
|
|
} catch (\Throwable) {
|
|
throw new LibresignException($this->l10n->t('Invalid data to validate file'), 404);
|
|
}
|
|
if (!$file) {
|
|
throw new LibresignException($this->l10n->t('Invalid file identifier'), 404);
|
|
}
|
|
$this->setFile($file);
|
|
return $this;
|
|
}
|
|
|
|
public function setFileFromRequest(?array $file): self {
|
|
if ($file === null) {
|
|
throw new InvalidArgumentException($this->l10n->t('No file provided'));
|
|
}
|
|
if (
|
|
$file['error'] !== 0
|
|
|| !is_uploaded_file($file['tmp_name'])
|
|
|| Filesystem::isFileBlacklisted($file['tmp_name'])
|
|
) {
|
|
unlink($file['tmp_name']);
|
|
throw new InvalidArgumentException($this->l10n->t('Invalid file provided'));
|
|
}
|
|
if ($file['size'] > \OCP\Util::uploadLimit()) {
|
|
unlink($file['tmp_name']);
|
|
throw new InvalidArgumentException($this->l10n->t('File is too big'));
|
|
}
|
|
|
|
$this->fileContent = file_get_contents($file['tmp_name']);
|
|
$mimeType = $this->mimeTypeDetector->detectString($this->fileContent);
|
|
if ($mimeType !== 'application/pdf') {
|
|
$this->fileContent = '';
|
|
unlink($file['tmp_name']);
|
|
throw new InvalidArgumentException($this->l10n->t('Invalid file provided'));
|
|
}
|
|
$this->fileData->size = $file['size'];
|
|
|
|
$memoryFile = fopen($file['tmp_name'], 'rb');
|
|
try {
|
|
$this->certData = $this->pkcs12Handler->getCertificateChain($memoryFile);
|
|
$this->fileData->status = File::STATUS_SIGNED;
|
|
// Ignore when isnt a signed file
|
|
} catch (LibresignException) {
|
|
$this->fileData->status = File::STATUS_DRAFT;
|
|
}
|
|
fclose($memoryFile);
|
|
unlink($file['tmp_name']);
|
|
$this->fileData->hash = hash('sha256', $this->fileContent);
|
|
try {
|
|
$libresignFile = $this->fileMapper->getBySignedHash($this->fileData->hash);
|
|
$this->setFile($libresignFile);
|
|
} catch (DoesNotExistException) {
|
|
$this->fileData->status = File::STATUS_NOT_LIBRESIGN_FILE;
|
|
}
|
|
$this->fileData->name = $file['name'];
|
|
return $this;
|
|
}
|
|
|
|
private function getFile(): \OCP\Files\File {
|
|
$nodeId = $this->file->getSignedNodeId();
|
|
if (!$nodeId) {
|
|
$nodeId = $this->file->getNodeId();
|
|
}
|
|
$fileToValidate = $this->root->getUserFolder($this->file->getUserId())->getFirstNodeById($nodeId);
|
|
if (!$fileToValidate instanceof \OCP\Files\File) {
|
|
throw new LibresignException($this->l10n->t('File not found'), 404);
|
|
}
|
|
return $fileToValidate;
|
|
}
|
|
|
|
public function getStatus(): int {
|
|
return $this->file->getStatus();
|
|
}
|
|
|
|
public function getSignedNodeId(): ?int {
|
|
$status = $this->file->getStatus();
|
|
|
|
if (!in_array($status, [File::STATUS_PARTIAL_SIGNED, File::STATUS_SIGNED])) {
|
|
return null;
|
|
}
|
|
return $this->file->getSignedNodeId();
|
|
}
|
|
|
|
private function getFileContent(): string {
|
|
if ($this->fileContent) {
|
|
return $this->fileContent;
|
|
} elseif ($this->file) {
|
|
try {
|
|
return $this->fileContent = $this->getFile()->getContent();
|
|
} catch (LibresignException $e) {
|
|
throw $e;
|
|
} catch (\Throwable $e) {
|
|
$this->logger->error('Failed to get file content: ' . $e->getMessage(), [
|
|
'fileId' => $this->file->getId(),
|
|
'exception' => $e,
|
|
]);
|
|
throw new LibresignException($this->l10n->t('Invalid data to validate file'), 404, $e);
|
|
}
|
|
}
|
|
return '';
|
|
}
|
|
|
|
public function isLibresignFile(int $nodeId): bool {
|
|
try {
|
|
return $this->fileMapper->fileIdExists($nodeId);
|
|
} catch (\Throwable) {
|
|
throw new LibresignException($this->l10n->t('Invalid data to validate file'), 404);
|
|
}
|
|
}
|
|
|
|
private function loadFileMetadata(): void {
|
|
if (!$content = $this->getFileContent()) {
|
|
return;
|
|
}
|
|
$pdfParserService = $this->pdfParserService->setFile($content);
|
|
if ($this->file) {
|
|
$metadata = $this->file->getMetadata();
|
|
}
|
|
if (isset($metadata) && isset($metadata['p'])) {
|
|
$dimensions = $metadata;
|
|
} else {
|
|
$dimensions = $pdfParserService->getPageDimensions();
|
|
}
|
|
$this->fileData->totalPages = $dimensions['p'];
|
|
$this->fileData->size = strlen($content);
|
|
$this->fileData->pdfVersion = $pdfParserService->getPdfVersion();
|
|
}
|
|
|
|
private function loadCertDataFromLibreSignFile(): void {
|
|
if (!empty($this->certData) || !$this->validateFile || !$this->file || !$this->file->getSignedNodeId()) {
|
|
return;
|
|
}
|
|
$file = $this->getFile();
|
|
|
|
$resource = $file->fopen('rb');
|
|
$sha256 = $this->getSha256FromResource($resource);
|
|
if ($sha256 === $this->file->getSignedHash()) {
|
|
$this->pkcs12Handler->setIsLibreSignFile();
|
|
}
|
|
$this->certData = $this->pkcs12Handler->getCertificateChain($resource);
|
|
fclose($resource);
|
|
}
|
|
|
|
private function getSha256FromResource($resource): string {
|
|
$hashContext = hash_init('sha256');
|
|
while (!feof($resource)) {
|
|
$buffer = fread($resource, 8192); // 8192 bytes = 8 KB
|
|
hash_update($hashContext, $buffer);
|
|
}
|
|
return hash_final($hashContext);
|
|
}
|
|
|
|
private function loadLibreSignSigners(): void {
|
|
if ($this->signersLibreSignLoaded || !$this->file) {
|
|
return;
|
|
}
|
|
$signers = $this->signRequestMapper->getByFileId($this->file->getId());
|
|
foreach ($signers as $signer) {
|
|
$identifyMethods = $this->identifyMethodService
|
|
->setIsRequest(false)
|
|
->getIdentifyMethodsFromSignRequestId($signer->getId());
|
|
if (!empty($this->fileData->signers)) {
|
|
$found = array_filter($this->fileData->signers, function ($found) use ($identifyMethods) {
|
|
if (!isset($found['uid'])) {
|
|
return false;
|
|
}
|
|
[$key, $value] = explode(':', (string)$found['uid']);
|
|
foreach ($identifyMethods as $methods) {
|
|
foreach ($methods as $identifyMethod) {
|
|
$entity = $identifyMethod->getEntity();
|
|
if ($key === $entity->getIdentifierKey() && $value === $entity->getIdentifierValue()) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
});
|
|
if (!empty($found)) {
|
|
$index = key($found);
|
|
} else {
|
|
$totalSigners = count($signers);
|
|
$totalCert = count($this->certData);
|
|
// When only have a signature, consider that who signed is who need to sign
|
|
if ($totalCert === 1 && $totalSigners === $totalCert) {
|
|
$index = 0;
|
|
} else {
|
|
$index = count($this->fileData->signers);
|
|
}
|
|
}
|
|
} else {
|
|
$index = 0;
|
|
}
|
|
$this->fileData->signers[$index]['identifyMethods'] = $identifyMethods;
|
|
$this->fileData->signers[$index]['displayName'] = $signer->getDisplayName();
|
|
$this->fileData->signers[$index]['me'] = false;
|
|
$this->fileData->signers[$index]['signRequestId'] = $signer->getId();
|
|
$this->fileData->signers[$index]['description'] = $signer->getDescription();
|
|
$this->fileData->signers[$index]['status'] = $signer->getStatus();
|
|
$this->fileData->signers[$index]['statusText'] = $this->signRequestMapper->getTextOfSignerStatus($signer->getStatus());
|
|
$this->fileData->signers[$index]['signingOrder'] = $signer->getSigningOrder();
|
|
$this->fileData->signers[$index]['visibleElements'] = $this->getVisibleElements($signer->getId());
|
|
$this->fileData->signers[$index]['request_sign_date'] = $signer->getCreatedAt()->format(DateTimeInterface::ATOM);
|
|
if (empty($this->fileData->signers[$index]['signed'])) {
|
|
if ($signer->getSigned()) {
|
|
$this->fileData->signers[$index]['signed'] = $signer->getSigned()->format(DateTimeInterface::ATOM);
|
|
} else {
|
|
$this->fileData->signers[$index]['signed'] = null;
|
|
}
|
|
}
|
|
$metadata = $signer->getMetadata();
|
|
if (!empty($metadata['remote-address'])) {
|
|
$this->fileData->signers[$index]['remote_address'] = $metadata['remote-address'];
|
|
}
|
|
if (!empty($metadata['user-agent'])) {
|
|
$this->fileData->signers[$index]['user_agent'] = $metadata['user-agent'];
|
|
}
|
|
if (!empty($metadata['notify'])) {
|
|
foreach ($metadata['notify'] as $notify) {
|
|
$this->fileData->signers[$index]['notify'][] = [
|
|
'method' => $notify['method'],
|
|
'date' => (new \DateTime('@' . $notify['date'], new \DateTimeZone('UTC')))->format(DateTimeInterface::ATOM),
|
|
];
|
|
}
|
|
}
|
|
if ($signer->getSigned() && empty($this->fileData->signers[$index]['signed'])) {
|
|
if ($signer->getSigned()) {
|
|
$this->fileData->signers[$index]['signed'] = $signer->getSigned()->format(DateTimeInterface::ATOM);
|
|
} else {
|
|
$this->fileData->signers[$index]['signed'] = null;
|
|
}
|
|
}
|
|
// @todo refactor this code
|
|
if ($this->me || $this->identifyMethodId) {
|
|
$this->fileData->signers[$index]['sign_uuid'] = $signer->getUuid();
|
|
// Identifi if I'm file owner
|
|
if ($this->me?->getUID() === $this->file->getUserId()) {
|
|
$email = array_reduce($identifyMethods[IdentifyMethodService::IDENTIFY_EMAIL] ?? [], function (?string $carry, IIdentifyMethod $identifyMethod): ?string {
|
|
if ($identifyMethod->getEntity()->getIdentifierKey() === IdentifyMethodService::IDENTIFY_EMAIL) {
|
|
$carry = $identifyMethod->getEntity()->getIdentifierValue();
|
|
}
|
|
return $carry;
|
|
}, '');
|
|
$this->fileData->signers[$index]['email'] = $email;
|
|
$user = $this->userManager->getByEmail($email);
|
|
if ($user && count($user) === 1) {
|
|
$this->fileData->signers[$index]['userId'] = $user[0]->getUID();
|
|
}
|
|
}
|
|
// Identify if I'm signer
|
|
foreach ($identifyMethods as $methods) {
|
|
foreach ($methods as $identifyMethod) {
|
|
$entity = $identifyMethod->getEntity();
|
|
if ($this->identifyMethodId === $entity->getId()
|
|
|| $this->me?->getUID() === $entity->getIdentifierValue()
|
|
|| $this->me?->getEMailAddress() === $entity->getIdentifierValue()
|
|
) {
|
|
$this->fileData->signers[$index]['me'] = true;
|
|
if (!$signer->getSigned()) {
|
|
$this->fileData->settings['canSign'] = true;
|
|
$this->fileData->settings['signerFileUuid'] = $signer->getUuid();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if ($this->fileData->signers[$index]['me']) {
|
|
$this->fileData->url = $this->urlGenerator->linkToRoute('libresign.page.getPdfFile', ['uuid' => $this->fileData->signers[$index]['sign_uuid']]);
|
|
$this->fileData->signers[$index]['signatureMethods'] = $this->identifyMethodService->getSignMethodsOfIdentifiedFactors($signer->getId());
|
|
}
|
|
$this->fileData->signers[$index]['identifyMethods'] = array_reduce($this->fileData->signers[$index]['identifyMethods'], function ($carry, $list) {
|
|
foreach ($list as $identifyMethod) {
|
|
$carry[] = [
|
|
'method' => $identifyMethod->getEntity()->getIdentifierKey(),
|
|
'value' => $identifyMethod->getEntity()->getIdentifierValue(),
|
|
'mandatory' => $identifyMethod->getEntity()->getMandatory(),
|
|
];
|
|
}
|
|
return $carry;
|
|
}, []);
|
|
ksort($this->fileData->signers[$index]);
|
|
}
|
|
|
|
usort($this->fileData->signers, function ($a, $b) {
|
|
$orderA = $a['signingOrder'] ?? PHP_INT_MAX;
|
|
$orderB = $b['signingOrder'] ?? PHP_INT_MAX;
|
|
|
|
if ($orderA !== $orderB) {
|
|
return $orderA <=> $orderB;
|
|
}
|
|
|
|
return ($a['signRequestId'] ?? 0) <=> ($b['signRequestId'] ?? 0);
|
|
});
|
|
|
|
$this->signersLibreSignLoaded = true;
|
|
}
|
|
|
|
private function loadSignersFromCertData(): void {
|
|
$this->loadCertDataFromLibreSignFile();
|
|
foreach ($this->certData as $index => $signer) {
|
|
$this->fileData->signers[$index]['status'] = 2;
|
|
$this->fileData->signers[$index]['statusText'] = $this->signRequestMapper->getTextOfSignerStatus(2);
|
|
|
|
if (isset($signer['timestamp'])) {
|
|
$this->fileData->signers[$index]['timestamp'] = $signer['timestamp'];
|
|
if (isset($signer['timestamp']['genTime']) && $signer['timestamp']['genTime'] instanceof DateTimeInterface) {
|
|
$this->fileData->signers[$index]['timestamp']['genTime'] = $signer['timestamp']['genTime']->format(DateTimeInterface::ATOM);
|
|
}
|
|
}
|
|
if (isset($signer['signingTime']) && $signer['signingTime'] instanceof DateTimeInterface) {
|
|
$this->fileData->signers[$index]['signingTime'] = $signer['signingTime'];
|
|
$this->fileData->signers[$index]['signed'] = $signer['signingTime']->format(DateTimeInterface::ATOM);
|
|
}
|
|
if (isset($signer['docmdp'])) {
|
|
$this->fileData->signers[$index]['docmdp'] = $signer['docmdp'];
|
|
}
|
|
if (isset($signer['modifications'])) {
|
|
$this->fileData->signers[$index]['modifications'] = $signer['modifications'];
|
|
}
|
|
if (isset($signer['modification_validation'])) {
|
|
$this->fileData->signers[$index]['modification_validation'] = $signer['modification_validation'];
|
|
}
|
|
if (isset($signer['chain'])) {
|
|
foreach ($signer['chain'] as $chainIndex => $chainItem) {
|
|
$chainArr = $chainItem;
|
|
if (isset($chainItem['validFrom_time_t']) && is_numeric($chainItem['validFrom_time_t'])) {
|
|
$chainArr['valid_from'] = (new DateTime('@' . $chainItem['validFrom_time_t'], new \DateTimeZone('UTC')))->format(DateTimeInterface::ATOM);
|
|
}
|
|
if (isset($chainItem['validTo_time_t']) && is_numeric($chainItem['validTo_time_t'])) {
|
|
$chainArr['valid_to'] = (new DateTime('@' . $chainItem['validTo_time_t'], new \DateTimeZone('UTC')))->format(DateTimeInterface::ATOM);
|
|
}
|
|
$chainArr['displayName'] = $chainArr['name'] ?? ($chainArr['subject']['CN'] ?? '');
|
|
$this->fileData->signers[$index]['chain'][$chainIndex] = $chainArr;
|
|
if ($chainIndex === 0) {
|
|
$this->fileData->signers[$index] = array_merge($chainArr, $this->fileData->signers[$index] ?? []);
|
|
$this->fileData->signers[$index]['uid'] = $this->resolveUid($chainArr);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private function resolveUid(array $chainArr): ?string {
|
|
if (!empty($chainArr['subject']['UID'])) {
|
|
return $chainArr['subject']['UID'];
|
|
}
|
|
if (!empty($chainArr['subject']['CN'])) {
|
|
$cn = $chainArr['subject']['CN'];
|
|
if (is_array($cn)) {
|
|
$cn = $cn[0];
|
|
}
|
|
if (preg_match('/^(?<key>.*):(?<value>.*), /', (string)$cn, $matches)) {
|
|
return $matches['key'] . ':' . $matches['value'];
|
|
}
|
|
}
|
|
if (!empty($chainArr['extensions']['subjectAltName'])) {
|
|
$subjectAltName = $chainArr['extensions']['subjectAltName'];
|
|
if (is_array($subjectAltName)) {
|
|
$subjectAltName = $subjectAltName[0];
|
|
}
|
|
preg_match('/^(?<key>(email|account)):(?<value>.*)$/', (string)$subjectAltName, $matches);
|
|
if ($matches) {
|
|
if (str_ends_with($matches['value'], $this->host)) {
|
|
$uid = str_replace('@' . $this->host, '', $matches['value']);
|
|
$userFound = $this->userManager->get($uid);
|
|
if ($userFound) {
|
|
return 'account:' . $uid;
|
|
} else {
|
|
$userFound = $this->userManager->getByEmail($matches['value']);
|
|
if ($userFound) {
|
|
$userFound = current($userFound);
|
|
return 'account:' . $userFound->getUID();
|
|
} else {
|
|
return 'email:' . $matches['value'];
|
|
}
|
|
}
|
|
} else {
|
|
$userFound = $this->userManager->getByEmail($matches['value']);
|
|
if ($userFound) {
|
|
$userFound = current($userFound);
|
|
return 'account:' . $userFound->getUID();
|
|
} else {
|
|
$userFound = $this->userManager->get($matches['value']);
|
|
if ($userFound) {
|
|
return 'account:' . $userFound->getUID();
|
|
} else {
|
|
return $matches['key'] . ':' . $matches['value'];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
private function loadSigners(): void {
|
|
if (!$this->showSigners) {
|
|
return;
|
|
}
|
|
$this->loadSignersFromCertData();
|
|
$this->loadLibreSignSigners();
|
|
}
|
|
|
|
/**
|
|
* @return (mixed|string)[][]
|
|
*
|
|
* @psalm-return list<array{url: string, resolution: mixed}>
|
|
*/
|
|
private function getPages(): array {
|
|
$return = [];
|
|
|
|
$metadata = $this->file->getMetadata();
|
|
for ($page = 1; $page <= $metadata['p']; $page++) {
|
|
$return[] = [
|
|
'url' => $this->urlGenerator->linkToRoute('ocs.libresign.File.getPage', [
|
|
'apiVersion' => 'v1',
|
|
'uuid' => $this->file->getUuid(),
|
|
'page' => $page,
|
|
]),
|
|
'resolution' => $metadata['d'][$page - 1]
|
|
];
|
|
}
|
|
return $return;
|
|
}
|
|
|
|
private function getVisibleElements(int $signRequestId): array {
|
|
$return = [];
|
|
if (!$this->showVisibleElements) {
|
|
return $return;
|
|
}
|
|
try {
|
|
$visibleElements = $this->fileElementMapper->getByFileIdAndSignRequestId($this->file->getId(), $signRequestId);
|
|
foreach ($visibleElements as $visibleElement) {
|
|
$element = [
|
|
'elementId' => $visibleElement->getId(),
|
|
'signRequestId' => $visibleElement->getSignRequestId(),
|
|
'type' => $visibleElement->getType(),
|
|
'coordinates' => [
|
|
'page' => $visibleElement->getPage(),
|
|
'urx' => $visibleElement->getUrx(),
|
|
'ury' => $visibleElement->getUry(),
|
|
'llx' => $visibleElement->getLlx(),
|
|
'lly' => $visibleElement->getLly()
|
|
]
|
|
];
|
|
$element['coordinates'] = array_merge(
|
|
$element['coordinates'],
|
|
$this->fileElementService->translateCoordinatesFromInternalNotation($element, $this->file)
|
|
);
|
|
$return[] = $element;
|
|
}
|
|
} catch (\Throwable) {
|
|
}
|
|
return $return;
|
|
}
|
|
|
|
private function getPhoneNumber(): string {
|
|
if (!$this->me) {
|
|
return '';
|
|
}
|
|
$userAccount = $this->accountManager->getAccount($this->me);
|
|
return $userAccount->getProperty(IAccountManager::PROPERTY_PHONE)->getValue();
|
|
}
|
|
|
|
private function loadSettings(): void {
|
|
if (!$this->showSettings) {
|
|
return;
|
|
}
|
|
if ($this->me) {
|
|
$this->fileData->settings = array_merge($this->fileData->settings, $this->accountService->getSettings($this->me));
|
|
$this->fileData->settings['phoneNumber'] = $this->getPhoneNumber();
|
|
}
|
|
if ($this->signerIdentified || $this->me) {
|
|
$status = $this->getIdentificationDocumentsStatus();
|
|
if ($status === self::IDENTIFICATION_DOCUMENTS_NEED_SEND) {
|
|
$this->fileData->settings['needIdentificationDocuments'] = true;
|
|
$this->fileData->settings['identificationDocumentsWaitingApproval'] = false;
|
|
} elseif ($status === self::IDENTIFICATION_DOCUMENTS_NEED_APPROVAL) {
|
|
$this->fileData->settings['needIdentificationDocuments'] = true;
|
|
$this->fileData->settings['identificationDocumentsWaitingApproval'] = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
public function getIdentificationDocumentsStatus(string $userId = ''): int {
|
|
if (!$this->appConfig->getValueBool(Application::APP_ID, 'identification_documents', false)) {
|
|
return self::IDENTIFICATION_DOCUMENTS_DISABLED;
|
|
}
|
|
|
|
if (!$userId && $this->me instanceof IUser) {
|
|
$userId = $this->me->getUID();
|
|
}
|
|
if (!empty($userId)) {
|
|
$files = $this->fileMapper->getFilesOfAccount($userId);
|
|
}
|
|
|
|
if (empty($files) || !count($files)) {
|
|
return self::IDENTIFICATION_DOCUMENTS_NEED_SEND;
|
|
}
|
|
$deleted = array_filter($files, fn (File $file) => $file->getStatus() === File::STATUS_DELETED);
|
|
if (count($deleted) === count($files)) {
|
|
return self::IDENTIFICATION_DOCUMENTS_NEED_SEND;
|
|
}
|
|
|
|
$signed = array_filter($files, fn (File $file) => $file->getStatus() === File::STATUS_SIGNED);
|
|
if (count($signed) !== count($files)) {
|
|
return self::IDENTIFICATION_DOCUMENTS_NEED_APPROVAL;
|
|
}
|
|
|
|
return self::IDENTIFICATION_DOCUMENTS_APPROVED;
|
|
}
|
|
|
|
private function loadLibreSignData(): void {
|
|
if (!$this->file) {
|
|
return;
|
|
}
|
|
$this->fileData->uuid = $this->file->getUuid();
|
|
$this->fileData->name = $this->file->getName();
|
|
$this->fileData->status = $this->file->getStatus();
|
|
$this->fileData->created_at = $this->file->getCreatedAt()->format(DateTimeInterface::ATOM);
|
|
$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(),
|
|
'displayName' => $this->userManager->get($this->file->getUserId())->getDisplayName(),
|
|
];
|
|
$this->fileData->file = $this->urlGenerator->linkToRoute('libresign.page.getPdf', ['uuid' => $this->file->getUuid()]);
|
|
if ($this->showVisibleElements) {
|
|
$signers = $this->signRequestMapper->getByMultipleFileId([$this->file->getId()]);
|
|
$this->fileData->visibleElements = [];
|
|
foreach ($this->signRequestMapper->getVisibleElementsFromSigners($signers) as $visibleElements) {
|
|
$this->fileData->visibleElements = array_merge(
|
|
$this->formatVisibleElementsToArray(
|
|
$visibleElements,
|
|
$this->file->getMetadata()
|
|
),
|
|
$this->fileData->visibleElements
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
private function loadMessages(): void {
|
|
if (!$this->showMessages) {
|
|
return;
|
|
}
|
|
$messages = [];
|
|
if ($this->fileData->settings['canSign']) {
|
|
$messages[] = [
|
|
'type' => 'info',
|
|
'message' => $this->l10n->t('You need to sign this document')
|
|
];
|
|
}
|
|
if ($this->fileData->settings['canRequestSign']) {
|
|
$this->loadLibreSignSigners();
|
|
if (empty($this->fileData->signers)) {
|
|
$messages[] = [
|
|
'type' => 'info',
|
|
'message' => $this->l10n->t('You cannot request signature for this document, please contact your administrator')
|
|
];
|
|
}
|
|
}
|
|
if ($messages) {
|
|
$this->fileData->messages = $messages;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @return LibresignValidateFile
|
|
*/
|
|
public function toArray(): array {
|
|
$this->loadLibreSignData();
|
|
$this->loadFileMetadata();
|
|
$this->loadSettings();
|
|
$this->loadSigners();
|
|
$this->loadMessages();
|
|
$return = json_decode(json_encode($this->fileData), true);
|
|
ksort($return);
|
|
return $return;
|
|
}
|
|
|
|
public function setFileByPath(string $path): self {
|
|
$node = $this->folderService->getFileByPath($path);
|
|
$this->setFileByType('FileId', $node->getId());
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* @return array[]
|
|
*
|
|
* @psalm-return array{data: array, pagination: array}
|
|
*/
|
|
public function listAssociatedFilesOfSignFlow(
|
|
$page = null,
|
|
$length = null,
|
|
array $filter = [],
|
|
array $sort = [],
|
|
): array {
|
|
$page ??= 1;
|
|
$length ??= (int)$this->appConfig->getValueInt(Application::APP_ID, 'length_of_page', 100);
|
|
|
|
$return = $this->signRequestMapper->getFilesAssociatedFilesWithMeFormatted(
|
|
$this->me,
|
|
$filter,
|
|
$page,
|
|
$length,
|
|
$sort,
|
|
);
|
|
|
|
$signers = $this->signRequestMapper->getByMultipleFileId(array_column($return['data'], 'id'));
|
|
$identifyMethods = $this->signRequestMapper->getIdentifyMethodsFromSigners($signers);
|
|
$visibleElements = $this->signRequestMapper->getVisibleElementsFromSigners($signers);
|
|
$return['data'] = $this->associateAllAndFormat($this->me, $return['data'], $signers, $identifyMethods, $visibleElements);
|
|
|
|
$return['pagination']->setRouteName('ocs.libresign.File.list');
|
|
return [
|
|
'data' => $return['data'],
|
|
'pagination' => $return['pagination']->getPagination($page, $length, $filter)
|
|
];
|
|
}
|
|
|
|
private function associateAllAndFormat(IUser $user, array $files, array $signers, array $identifyMethods, array $visibleElements): array {
|
|
foreach ($files as $key => $file) {
|
|
$totalSigned = 0;
|
|
foreach ($signers as $signerKey => $signer) {
|
|
if ($signer->getFileId() === $file['id']) {
|
|
/** @var array<IdentifyMethod> */
|
|
$identifyMethodsOfSigner = $identifyMethods[$signer->getId()] ?? [];
|
|
$data = [
|
|
'email' => array_reduce($identifyMethodsOfSigner, function (string $carry, IdentifyMethod $identifyMethod): string {
|
|
if ($identifyMethod->getIdentifierKey() === IdentifyMethodService::IDENTIFY_EMAIL) {
|
|
return $identifyMethod->getIdentifierValue();
|
|
}
|
|
if (filter_var($identifyMethod->getIdentifierValue(), FILTER_VALIDATE_EMAIL)) {
|
|
return $identifyMethod->getIdentifierValue();
|
|
}
|
|
return $carry;
|
|
}, ''),
|
|
'description' => $signer->getDescription(),
|
|
'displayName'
|
|
=> array_reduce($identifyMethodsOfSigner, function (string $carry, IdentifyMethod $identifyMethod): string {
|
|
if (!$carry && $identifyMethod->getMandatory()) {
|
|
return $identifyMethod->getIdentifierValue();
|
|
}
|
|
return $carry;
|
|
}, $signer->getDisplayName()),
|
|
'request_sign_date' => $signer->getCreatedAt()->format(DateTimeInterface::ATOM),
|
|
'signed' => null,
|
|
'signRequestId' => $signer->getId(),
|
|
'signingOrder' => $signer->getSigningOrder(),
|
|
'status' => $signer->getStatus(),
|
|
'statusText' => $this->signRequestMapper->getTextOfSignerStatus($signer->getStatus()),
|
|
'me' => array_reduce($identifyMethodsOfSigner, function (bool $carry, IdentifyMethod $identifyMethod) use ($user): bool {
|
|
if ($identifyMethod->getIdentifierKey() === IdentifyMethodService::IDENTIFY_ACCOUNT) {
|
|
if ($user->getUID() === $identifyMethod->getIdentifierValue()) {
|
|
return true;
|
|
}
|
|
} elseif ($identifyMethod->getIdentifierKey() === IdentifyMethodService::IDENTIFY_EMAIL) {
|
|
if (!$user->getEMailAddress()) {
|
|
return false;
|
|
}
|
|
if ($user->getEMailAddress() === $identifyMethod->getIdentifierValue()) {
|
|
return true;
|
|
}
|
|
}
|
|
return $carry;
|
|
}, false),
|
|
'visibleElements' => $this->formatVisibleElementsToArray(
|
|
$visibleElements[$signer->getId()] ?? [],
|
|
!empty($file['metadata'])?json_decode((string)$file['metadata'], true):[]
|
|
),
|
|
'identifyMethods' => array_map(fn (IdentifyMethod $identifyMethod): array => [
|
|
'method' => $identifyMethod->getIdentifierKey(),
|
|
'value' => $identifyMethod->getIdentifierValue(),
|
|
'mandatory' => $identifyMethod->getMandatory(),
|
|
], array_values($identifyMethodsOfSigner)),
|
|
];
|
|
|
|
if ($data['me']) {
|
|
$temp = array_map(function (IdentifyMethod $identifyMethodEntity) use ($signer): array {
|
|
$this->identifyMethodService->setCurrentIdentifyMethod($identifyMethodEntity);
|
|
$identifyMethod = $this->identifyMethodService
|
|
->setIsRequest(false)
|
|
->getInstanceOfIdentifyMethod(
|
|
$identifyMethodEntity->getIdentifierKey(),
|
|
$identifyMethodEntity->getIdentifierValue(),
|
|
);
|
|
$signatureMethods = $identifyMethod->getSignatureMethods();
|
|
$return = [];
|
|
foreach ($signatureMethods as $signatureMethod) {
|
|
if (!$signatureMethod->isEnabled()) {
|
|
continue;
|
|
}
|
|
$signatureMethod->setEntity($identifyMethod->getEntity());
|
|
$return[$signatureMethod->getName()] = $signatureMethod->toArray();
|
|
}
|
|
return $return;
|
|
}, array_values($identifyMethodsOfSigner));
|
|
$data['signatureMethods'] = [];
|
|
foreach ($temp as $methods) {
|
|
$data['signatureMethods'] = array_merge($data['signatureMethods'], $methods);
|
|
}
|
|
$data['sign_uuid'] = $signer->getUuid();
|
|
$files[$key]['url'] = $this->urlGenerator->linkToRoute('libresign.page.getPdfFile', ['uuid' => $signer->getuuid()]);
|
|
}
|
|
|
|
if ($signer->getSigned()) {
|
|
$data['signed'] = $signer->getSigned()->format(DateTimeInterface::ATOM);
|
|
$totalSigned++;
|
|
}
|
|
ksort($data);
|
|
$files[$key]['signers'][] = $data;
|
|
unset($signers[$signerKey]);
|
|
}
|
|
}
|
|
if (empty($files[$key]['signers'])) {
|
|
$files[$key]['signers'] = [];
|
|
$files[$key]['statusText'] = $this->l10n->t('no signers');
|
|
} else {
|
|
usort($files[$key]['signers'], function ($a, $b) {
|
|
$orderA = $a['signingOrder'] ?? PHP_INT_MAX;
|
|
$orderB = $b['signingOrder'] ?? PHP_INT_MAX;
|
|
|
|
if ($orderA !== $orderB) {
|
|
return $orderA <=> $orderB;
|
|
}
|
|
|
|
return ($a['signRequestId'] ?? 0) <=> ($b['signRequestId'] ?? 0);
|
|
});
|
|
|
|
$files[$key]['statusText'] = $this->fileMapper->getTextOfStatus((int)$files[$key]['status']);
|
|
}
|
|
unset($files[$key]['id']);
|
|
ksort($files[$key]);
|
|
}
|
|
return $files;
|
|
}
|
|
|
|
/**
|
|
* @param FileElement[] $visibleElements
|
|
* @param array
|
|
* @return array
|
|
*/
|
|
private function formatVisibleElementsToArray(array $visibleElements, array $metadata): array {
|
|
return array_map(function (FileElement $visibleElement) use ($metadata) {
|
|
$element = [
|
|
'elementId' => $visibleElement->getId(),
|
|
'signRequestId' => $visibleElement->getSignRequestId(),
|
|
'type' => $visibleElement->getType(),
|
|
'coordinates' => [
|
|
'page' => $visibleElement->getPage(),
|
|
'urx' => $visibleElement->getUrx(),
|
|
'ury' => $visibleElement->getUry(),
|
|
'llx' => $visibleElement->getLlx(),
|
|
'lly' => $visibleElement->getLly()
|
|
]
|
|
];
|
|
$dimension = $metadata['d'][$element['coordinates']['page'] - 1];
|
|
|
|
$element['coordinates']['left'] = $element['coordinates']['llx'];
|
|
$element['coordinates']['height'] = abs($element['coordinates']['ury'] - $element['coordinates']['lly']);
|
|
$element['coordinates']['top'] = $dimension['h'] - $element['coordinates']['ury'];
|
|
$element['coordinates']['width'] = $element['coordinates']['urx'] - $element['coordinates']['llx'];
|
|
|
|
return $element;
|
|
}, $visibleElements);
|
|
}
|
|
|
|
public function getMyLibresignFile(int $nodeId): File {
|
|
return $this->signRequestMapper->getMyLibresignFile(
|
|
userId: $this->me->getUID(),
|
|
filter: [
|
|
'email' => $this->me->getEMailAddress(),
|
|
'nodeId' => $nodeId,
|
|
],
|
|
);
|
|
}
|
|
|
|
public function delete(int $fileId): void {
|
|
$file = $this->fileMapper->getByFileId($fileId);
|
|
$this->fileElementService->deleteVisibleElements($file->getId());
|
|
$list = $this->signRequestMapper->getByFileId($file->getId());
|
|
foreach ($list as $signRequest) {
|
|
$this->signRequestMapper->delete($signRequest);
|
|
}
|
|
$this->idDocsMapper->deleteByFileId($file->getId());
|
|
$this->fileMapper->delete($file);
|
|
if ($file->getSignedNodeId()) {
|
|
$signedNextcloudFile = $this->folderService->getFileById($file->getSignedNodeId());
|
|
$signedNextcloudFile->delete();
|
|
}
|
|
try {
|
|
$nextcloudFile = $this->folderService->getFileById($fileId);
|
|
$nextcloudFile->delete();
|
|
} catch (NotFoundException) {
|
|
}
|
|
}
|
|
}
|