• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

LibreSign / libresign / 20005319967

07 Dec 2025 02:01PM UTC coverage: 44.022%. First build
20005319967

Pull #6021

github

web-flow
Merge aac338839 into ae175d66d
Pull Request #6021: feat: docmdp implementation

27 of 72 new or added lines in 7 files covered. (37.5%)

5648 of 12830 relevant lines covered (44.02%)

4.96 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

51.58
/lib/Service/FileService.php
1
<?php
2

3
declare(strict_types=1);
4
/**
5
 * SPDX-FileCopyrightText: 2020-2024 LibreCode coop and contributors
6
 * SPDX-License-Identifier: AGPL-3.0-or-later
7
 */
8

9
namespace OCA\Libresign\Service;
10

11
use DateTime;
12
use DateTimeInterface;
13
use InvalidArgumentException;
14
use OC\Files\Filesystem;
15
use OCA\Libresign\AppInfo\Application;
16
use OCA\Libresign\Db\File;
17
use OCA\Libresign\Db\FileElement;
18
use OCA\Libresign\Db\FileElementMapper;
19
use OCA\Libresign\Db\FileMapper;
20
use OCA\Libresign\Db\IdDocsMapper;
21
use OCA\Libresign\Db\IdentifyMethod;
22
use OCA\Libresign\Db\SignRequest;
23
use OCA\Libresign\Db\SignRequestMapper;
24
use OCA\Libresign\Exception\LibresignException;
25
use OCA\Libresign\Handler\SignEngine\Pkcs12Handler;
26
use OCA\Libresign\Helper\ValidateHelper;
27
use OCA\Libresign\ResponseDefinitions;
28
use OCA\Libresign\Service\IdentifyMethod\IIdentifyMethod;
29
use OCP\Accounts\IAccountManager;
30
use OCP\AppFramework\Db\DoesNotExistException;
31
use OCP\Files\IMimeTypeDetector;
32
use OCP\Files\IRootFolder;
33
use OCP\Files\NotFoundException;
34
use OCP\Http\Client\IClientService;
35
use OCP\IAppConfig;
36
use OCP\IDateTimeFormatter;
37
use OCP\IL10N;
38
use OCP\IURLGenerator;
39
use OCP\IUser;
40
use OCP\IUserManager;
41
use OCP\IUserSession;
42
use Psr\Log\LoggerInterface;
43
use stdClass;
44

45
/**
46
 * @psalm-import-type LibresignValidateFile from ResponseDefinitions
47
 */
48
class FileService {
49
        use TFile;
50

51
        private bool $showSigners = false;
52
        private bool $showSettings = false;
53
        private bool $showVisibleElements = false;
54
        private bool $showMessages = false;
55
        private bool $validateFile = false;
56
        private bool $signersLibreSignLoaded = false;
57
        private bool $signerIdentified = false;
58
        private string $fileContent = '';
59
        private string $host = '';
60
        private ?File $file = null;
61
        private ?SignRequest $signRequest = null;
62
        private ?IUser $me = null;
63
        private ?int $identifyMethodId = null;
64
        private array $certData = [];
65
        private stdClass $fileData;
66
        public const IDENTIFICATION_DOCUMENTS_DISABLED = 0;
67
        public const IDENTIFICATION_DOCUMENTS_NEED_SEND = 1;
68
        public const IDENTIFICATION_DOCUMENTS_NEED_APPROVAL = 2;
69
        public const IDENTIFICATION_DOCUMENTS_APPROVED = 3;
70
        public function __construct(
71
                protected FileMapper $fileMapper,
72
                protected SignRequestMapper $signRequestMapper,
73
                protected FileElementMapper $fileElementMapper,
74
                protected FileElementService $fileElementService,
75
                protected FolderService $folderService,
76
                protected ValidateHelper $validateHelper,
77
                protected PdfParserService $pdfParserService,
78
                private IdDocsMapper $idDocsMapper,
79
                private AccountService $accountService,
80
                private IdentifyMethodService $identifyMethodService,
81
                private IUserSession $userSession,
82
                private IUserManager $userManager,
83
                private IAccountManager $accountManager,
84
                protected IClientService $client,
85
                private IDateTimeFormatter $dateTimeFormatter,
86
                private IAppConfig $appConfig,
87
                private IURLGenerator $urlGenerator,
88
                protected IMimeTypeDetector $mimeTypeDetector,
89
                protected Pkcs12Handler $pkcs12Handler,
90
                private IRootFolder $root,
91
                protected LoggerInterface $logger,
92
                protected IL10N $l10n,
93
        ) {
94
                $this->fileData = new stdClass();
30✔
95
        }
96

97
        /**
98
         * @return static
99
         */
100
        public function showSigners(bool $show = true): self {
101
                $this->showSigners = $show;
5✔
102
                return $this;
5✔
103
        }
104

105
        /**
106
         * @return static
107
         */
108
        public function showSettings(bool $show = true): self {
109
                $this->showSettings = $show;
4✔
110
                if ($show) {
4✔
111
                        $this->fileData->settings = [
4✔
112
                                'canSign' => false,
4✔
113
                                'canRequestSign' => false,
4✔
114
                                'signerFileUuid' => null,
4✔
115
                                'phoneNumber' => '',
4✔
116
                        ];
4✔
117
                } else {
118
                        unset($this->fileData->settings);
×
119
                }
120
                return $this;
4✔
121
        }
122

123
        /**
124
         * @return static
125
         */
126
        public function showVisibleElements(bool $show = true): self {
127
                $this->showVisibleElements = $show;
4✔
128
                return $this;
4✔
129
        }
130

131
        /**
132
         * @return static
133
         */
134
        public function showMessages(bool $show = true): self {
135
                $this->showMessages = $show;
4✔
136
                return $this;
4✔
137
        }
138

139
        /**
140
         * @return static
141
         */
142
        public function setMe(?IUser $user): self {
143
                $this->me = $user;
5✔
144
                return $this;
5✔
145
        }
146

147
        public function setSignerIdentified(bool $identified = true): self {
148
                $this->signerIdentified = $identified;
×
149
                return $this;
×
150
        }
151

152
        public function setIdentifyMethodId(?int $id): self {
153
                $this->identifyMethodId = $id;
2✔
154
                return $this;
2✔
155
        }
156

157
        public function setHost(string $host): self {
158
                $this->host = $host;
4✔
159
                return $this;
4✔
160
        }
161

162
        /**
163
         * @return static
164
         */
165
        public function setFile(File $file): self {
166
                $this->file = $file;
4✔
167
                $this->fileData->status = $this->file->getStatus();
4✔
168
                return $this;
4✔
169
        }
170

171
        public function setSignRequest(SignRequest $signRequest): self {
172
                $this->signRequest = $signRequest;
×
173
                return $this;
×
174
        }
175

176
        public function showValidateFile(bool $validateFile = true): self {
177
                $this->validateFile = $validateFile;
2✔
178
                return $this;
2✔
179
        }
180

181
        /**
182
         * @return static
183
         */
184
        public function setFileByType(string $type, $identifier): self {
185
                try {
186
                        /** @var File */
187
                        $file = call_user_func(
4✔
188
                                [$this->fileMapper, 'getBy' . $type],
4✔
189
                                $identifier
4✔
190
                        );
4✔
191
                } catch (\Throwable) {
2✔
192
                        throw new LibresignException($this->l10n->t('Invalid data to validate file'), 404);
2✔
193
                }
194
                if (!$file) {
2✔
195
                        throw new LibresignException($this->l10n->t('Invalid file identifier'), 404);
×
196
                }
197
                $this->setFile($file);
2✔
198
                return $this;
2✔
199
        }
200

201
        public function setFileFromRequest(?array $file): self {
202
                if ($file === null) {
8✔
203
                        throw new InvalidArgumentException($this->l10n->t('No file provided'));
1✔
204
                }
205
                if (
206
                        $file['error'] !== 0
7✔
207
                        || !is_uploaded_file($file['tmp_name'])
6✔
208
                        || Filesystem::isFileBlacklisted($file['tmp_name'])
7✔
209
                ) {
210
                        unlink($file['tmp_name']);
2✔
211
                        throw new InvalidArgumentException($this->l10n->t('Invalid file provided'));
2✔
212
                }
213
                if ($file['size'] > \OCP\Util::uploadLimit()) {
5✔
214
                        unlink($file['tmp_name']);
1✔
215
                        throw new InvalidArgumentException($this->l10n->t('File is too big'));
1✔
216
                }
217

218
                $this->fileContent = file_get_contents($file['tmp_name']);
4✔
219
                $mimeType = $this->mimeTypeDetector->detectString($this->fileContent);
4✔
220
                if ($mimeType !== 'application/pdf') {
4✔
221
                        $this->fileContent = '';
1✔
222
                        unlink($file['tmp_name']);
1✔
223
                        throw new InvalidArgumentException($this->l10n->t('Invalid file provided'));
1✔
224
                }
225
                $this->fileData->size = $file['size'];
3✔
226

227
                $memoryFile = fopen($file['tmp_name'], 'rb');
3✔
228
                try {
229
                        $this->certData = $this->pkcs12Handler->getCertificateChain($memoryFile);
3✔
230
                        $this->fileData->status = File::STATUS_SIGNED;
2✔
231
                        // Ignore when isnt a signed file
232
                } catch (LibresignException) {
1✔
233
                        $this->fileData->status = File::STATUS_DRAFT;
1✔
234
                }
235
                fclose($memoryFile);
3✔
236
                unlink($file['tmp_name']);
3✔
237
                $this->fileData->hash = hash('sha256', $this->fileContent);
3✔
238
                try {
239
                        $libresignFile = $this->fileMapper->getBySignedHash($this->fileData->hash);
3✔
240
                        $this->setFile($libresignFile);
×
241
                } catch (DoesNotExistException) {
3✔
242
                        $this->fileData->status = File::STATUS_NOT_LIBRESIGN_FILE;
3✔
243
                }
244
                $this->fileData->name = $file['name'];
3✔
245
                return $this;
3✔
246
        }
247

248
        private function getFile(): \OCP\Files\File {
249
                $nodeId = $this->file->getSignedNodeId();
4✔
250
                if (!$nodeId) {
4✔
251
                        $nodeId = $this->file->getNodeId();
4✔
252
                }
253
                $fileToValidate = $this->root->getUserFolder($this->file->getUserId())->getFirstNodeById($nodeId);
4✔
254
                if (!$fileToValidate instanceof \OCP\Files\File) {
4✔
255
                        throw new LibresignException($this->l10n->t('File not found'), 404);
×
256
                }
257
                return $fileToValidate;
4✔
258
        }
259

260
        public function getStatus(): int {
261
                return $this->file->getStatus();
×
262
        }
263

264
        public function getSignedNodeId(): ?int {
265
                $status = $this->file->getStatus();
×
266

267
                if (!in_array($status, [File::STATUS_PARTIAL_SIGNED, File::STATUS_SIGNED])) {
×
268
                        return null;
×
269
                }
270
                return $this->file->getSignedNodeId();
×
271
        }
272

273
        private function getFileContent(): string {
274
                if ($this->fileContent) {
8✔
275
                        return $this->fileContent;
3✔
276
                } elseif ($this->file) {
5✔
277
                        try {
278
                                return $this->fileContent = $this->getFile()->getContent();
4✔
279
                        } catch (LibresignException $e) {
×
NEW
280
                                throw $e;
×
NEW
281
                        } catch (\Throwable $e) {
×
NEW
282
                                $this->logger->error('Failed to get file content: ' . $e->getMessage(), [
×
NEW
283
                                        'fileId' => $this->file->getId(),
×
NEW
284
                                        'exception' => $e,
×
NEW
285
                                ]);
×
NEW
286
                                throw new LibresignException($this->l10n->t('Invalid data to validate file'), 404, $e);
×
287
                        }
288
                }
289
                return '';
1✔
290
        }
291

292
        public function isLibresignFile(int $nodeId): bool {
293
                try {
294
                        return $this->fileMapper->fileIdExists($nodeId);
×
295
                } catch (\Throwable) {
×
296
                        throw new LibresignException($this->l10n->t('Invalid data to validate file'), 404);
×
297
                }
298
        }
299

300
        private function loadFileMetadata(): void {
301
                if (!$content = $this->getFileContent()) {
8✔
302
                        return;
1✔
303
                }
304
                $pdfParserService = $this->pdfParserService->setFile($content);
7✔
305
                if ($this->file) {
7✔
306
                        $metadata = $this->file->getMetadata();
4✔
307
                }
308
                if (isset($metadata) && isset($metadata['p'])) {
7✔
309
                        $dimensions = $metadata;
4✔
310
                } else {
311
                        $dimensions = $pdfParserService->getPageDimensions();
3✔
312
                }
313
                $this->fileData->totalPages = $dimensions['p'];
7✔
314
                $this->fileData->size = strlen($content);
7✔
315
                $this->fileData->pdfVersion = $pdfParserService->getPdfVersion();
7✔
316
        }
317

318
        private function loadCertDataFromLibreSignFile(): void {
319
                if (!empty($this->certData) || !$this->validateFile || !$this->file || !$this->file->getSignedNodeId()) {
5✔
320
                        return;
5✔
321
                }
322
                $file = $this->getFile();
×
323

324
                $resource = $file->fopen('rb');
×
325
                $sha256 = $this->getSha256FromResource($resource);
×
326
                if ($sha256 === $this->file->getSignedHash()) {
×
327
                        $this->pkcs12Handler->setIsLibreSignFile();
×
328
                }
329
                $this->certData = $this->pkcs12Handler->getCertificateChain($resource);
×
330
                fclose($resource);
×
331
        }
332

333
        private function getSha256FromResource($resource): string {
334
                $hashContext = hash_init('sha256');
×
335
                while (!feof($resource)) {
×
336
                        $buffer = fread($resource, 8192); // 8192 bytes = 8 KB
×
337
                        hash_update($hashContext, $buffer);
×
338
                }
339
                return hash_final($hashContext);
×
340
        }
341

342
        private function loadLibreSignSigners(): void {
343
                if ($this->signersLibreSignLoaded || !$this->file) {
5✔
344
                        return;
3✔
345
                }
346
                $signers = $this->signRequestMapper->getByFileId($this->file->getId());
4✔
347
                foreach ($signers as $signer) {
4✔
348
                        $identifyMethods = $this->identifyMethodService
4✔
349
                                ->setIsRequest(false)
4✔
350
                                ->getIdentifyMethodsFromSignRequestId($signer->getId());
4✔
351
                        if (!empty($this->fileData->signers)) {
4✔
352
                                $found = array_filter($this->fileData->signers, function ($found) use ($identifyMethods) {
×
353
                                        if (!isset($found['uid'])) {
×
354
                                                return false;
×
355
                                        }
356
                                        [$key, $value] = explode(':', (string)$found['uid']);
×
357
                                        foreach ($identifyMethods as $methods) {
×
358
                                                foreach ($methods as $identifyMethod) {
×
359
                                                        $entity = $identifyMethod->getEntity();
×
360
                                                        if ($key === $entity->getIdentifierKey() && $value === $entity->getIdentifierValue()) {
×
361
                                                                return true;
×
362
                                                        }
363
                                                }
364
                                        }
365
                                        return false;
×
366
                                });
×
367
                                if (!empty($found)) {
×
368
                                        $index = key($found);
×
369
                                } else {
370
                                        $totalSigners = count($signers);
×
371
                                        $totalCert = count($this->certData);
×
372
                                        // When only have a signature, consider that who signed is who need to sign
373
                                        if ($totalCert === 1 && $totalSigners === $totalCert) {
×
374
                                                $index = 0;
×
375
                                        } else {
376
                                                $index = count($this->fileData->signers);
×
377
                                        }
378
                                }
379
                        } else {
380
                                $index = 0;
4✔
381
                        }
382
                        $this->fileData->signers[$index]['identifyMethods'] = $identifyMethods;
4✔
383
                        $this->fileData->signers[$index]['displayName'] = $signer->getDisplayName();
4✔
384
                        $this->fileData->signers[$index]['me'] = false;
4✔
385
                        $this->fileData->signers[$index]['signRequestId'] = $signer->getId();
4✔
386
                        $this->fileData->signers[$index]['description'] = $signer->getDescription();
4✔
387
                        $this->fileData->signers[$index]['visibleElements'] = $this->getVisibleElements($signer->getId());
4✔
388
                        $this->fileData->signers[$index]['request_sign_date'] = $signer->getCreatedAt()->format(DateTimeInterface::ATOM);
4✔
389
                        if (empty($this->fileData->signers[$index]['signed'])) {
4✔
390
                                if ($signer->getSigned()) {
4✔
391
                                        $this->fileData->signers[$index]['signed'] = $signer->getSigned()->format(DateTimeInterface::ATOM);
×
392
                                } else {
393
                                        $this->fileData->signers[$index]['signed'] = null;
4✔
394
                                }
395
                        }
396
                        $metadata = $signer->getMetadata();
4✔
397
                        if (!empty($metadata['remote-address'])) {
4✔
398
                                $this->fileData->signers[$index]['remote_address'] = $metadata['remote-address'];
×
399
                        }
400
                        if (!empty($metadata['user-agent'])) {
4✔
401
                                $this->fileData->signers[$index]['user_agent'] = $metadata['user-agent'];
×
402
                        }
403
                        if (!empty($metadata['notify'])) {
4✔
404
                                foreach ($metadata['notify'] as $notify) {
4✔
405
                                        $this->fileData->signers[$index]['notify'][] = [
4✔
406
                                                'method' => $notify['method'],
4✔
407
                                                'date' => (new \DateTime('@' . $notify['date'], new \DateTimeZone('UTC')))->format(DateTimeInterface::ATOM),
4✔
408
                                        ];
4✔
409
                                }
410
                        }
411
                        if ($signer->getSigned() && empty($this->fileData->signers[$index]['signed'])) {
4✔
412
                                if ($signer->getSigned()) {
×
413
                                        $this->fileData->signers[$index]['signed'] = $signer->getSigned()->format(DateTimeInterface::ATOM);
×
414
                                } else {
415
                                        $this->fileData->signers[$index]['signed'] = null;
×
416
                                }
417
                        }
418
                        // @todo refactor this code
419
                        if ($this->me || $this->identifyMethodId) {
4✔
420
                                $this->fileData->signers[$index]['sign_uuid'] = $signer->getUuid();
3✔
421
                                // Identifi if I'm file owner
422
                                if ($this->me?->getUID() === $this->file->getUserId()) {
3✔
423
                                        $email = array_reduce($identifyMethods[IdentifyMethodService::IDENTIFY_EMAIL] ?? [], function (?string $carry, IIdentifyMethod $identifyMethod): ?string {
3✔
424
                                                if ($identifyMethod->getEntity()->getIdentifierKey() === IdentifyMethodService::IDENTIFY_EMAIL) {
3✔
425
                                                        $carry = $identifyMethod->getEntity()->getIdentifierValue();
3✔
426
                                                }
427
                                                return $carry;
3✔
428
                                        }, '');
3✔
429
                                        $this->fileData->signers[$index]['email'] = $email;
3✔
430
                                        $user = $this->userManager->getByEmail($email);
3✔
431
                                        if ($user && count($user) === 1) {
3✔
432
                                                $this->fileData->signers[$index]['userId'] = $user[0]->getUID();
1✔
433
                                        }
434
                                }
435
                                // Identify if I'm signer
436
                                foreach ($identifyMethods as $methods) {
3✔
437
                                        foreach ($methods as $identifyMethod) {
3✔
438
                                                $entity = $identifyMethod->getEntity();
3✔
439
                                                if ($this->identifyMethodId === $entity->getId()
3✔
440
                                                        || $this->me?->getUID() === $entity->getIdentifierValue()
3✔
441
                                                        || $this->me?->getEMailAddress() === $entity->getIdentifierValue()
3✔
442
                                                ) {
443
                                                        $this->fileData->signers[$index]['me'] = true;
1✔
444
                                                        if (!$signer->getSigned()) {
1✔
445
                                                                $this->fileData->settings['canSign'] = true;
1✔
446
                                                                $this->fileData->settings['signerFileUuid'] = $signer->getUuid();
1✔
447
                                                        }
448
                                                }
449
                                        }
450
                                }
451
                        }
452
                        if ($this->fileData->signers[$index]['me']) {
4✔
453
                                $this->fileData->url = $this->urlGenerator->linkToRoute('libresign.page.getPdfFile', ['uuid' => $this->fileData->signers[$index]['sign_uuid']]);
1✔
454
                                $this->fileData->signers[$index]['signatureMethods'] = $this->identifyMethodService->getSignMethodsOfIdentifiedFactors($signer->getId());
1✔
455
                        }
456
                        $this->fileData->signers[$index]['identifyMethods'] = array_reduce($this->fileData->signers[$index]['identifyMethods'], function ($carry, $list) {
4✔
457
                                foreach ($list as $identifyMethod) {
4✔
458
                                        $carry[] = [
4✔
459
                                                'method' => $identifyMethod->getEntity()->getIdentifierKey(),
4✔
460
                                                'value' => $identifyMethod->getEntity()->getIdentifierValue(),
4✔
461
                                                'mandatory' => $identifyMethod->getEntity()->getMandatory(),
4✔
462
                                        ];
4✔
463
                                }
464
                                return $carry;
4✔
465
                        }, []);
4✔
466
                        ksort($this->fileData->signers[$index]);
4✔
467
                }
468
                $this->signersLibreSignLoaded = true;
4✔
469
        }
470

471
        private function loadSignersFromCertData(): void {
472
                $this->loadCertDataFromLibreSignFile();
5✔
473
                foreach ($this->certData as $index => $signer) {
5✔
474
                        if (isset($signer['timestamp'])) {
1✔
475
                                $this->fileData->signers[$index]['timestamp'] = $signer['timestamp'];
×
476
                                if (isset($signer['timestamp']['genTime']) && $signer['timestamp']['genTime'] instanceof DateTimeInterface) {
×
477
                                        $this->fileData->signers[$index]['timestamp']['genTime'] = $signer['timestamp']['genTime']->format(DateTimeInterface::ATOM);
×
478
                                }
479
                        }
480
                        if (isset($signer['signingTime']) && $signer['signingTime'] instanceof DateTimeInterface) {
1✔
481
                                $this->fileData->signers[$index]['signingTime'] = $signer['signingTime'];
1✔
482
                                $this->fileData->signers[$index]['signed'] = $signer['signingTime']->format(DateTimeInterface::ATOM);
1✔
483
                        }
484
                        if (isset($signer['docmdp'])) {
1✔
485
                                $this->fileData->signers[$index]['docmdp'] = $signer['docmdp'];
1✔
486
                        }
487
                        if (isset($signer['modifications'])) {
1✔
488
                                $this->fileData->signers[$index]['modifications'] = $signer['modifications'];
1✔
489
                        }
490
                        if (isset($signer['modification_validation'])) {
1✔
NEW
491
                                $this->fileData->signers[$index]['modification_validation'] = $signer['modification_validation'];
×
492
                        }
493
                        if (isset($signer['chain'])) {
1✔
494
                                foreach ($signer['chain'] as $chainIndex => $chainItem) {
1✔
495
                                        $chainArr = $chainItem;
1✔
496
                                        if (isset($chainItem['validFrom_time_t']) && is_numeric($chainItem['validFrom_time_t'])) {
1✔
497
                                                $chainArr['valid_from'] = (new DateTime('@' . $chainItem['validFrom_time_t'], new \DateTimeZone('UTC')))->format(DateTimeInterface::ATOM);
1✔
498
                                        }
499
                                        if (isset($chainItem['validTo_time_t']) && is_numeric($chainItem['validTo_time_t'])) {
1✔
500
                                                $chainArr['valid_to'] = (new DateTime('@' . $chainItem['validTo_time_t'], new \DateTimeZone('UTC')))->format(DateTimeInterface::ATOM);
1✔
501
                                        }
502
                                        $chainArr['displayName'] = $chainArr['name'] ?? ($chainArr['subject']['CN'] ?? '');
1✔
503
                                        $this->fileData->signers[$index]['chain'][$chainIndex] = $chainArr;
1✔
504
                                        if ($chainIndex === 0) {
1✔
505
                                                $this->fileData->signers[$index] = array_merge($chainArr, $this->fileData->signers[$index] ?? []);
1✔
506
                                                $this->fileData->signers[$index]['uid'] = $this->resolveUid($chainArr);
1✔
507
                                        }
508
                                }
509
                        }
510
                }
511
        }
512

513
        private function resolveUid(array $chainArr): ?string {
514
                if (!empty($chainArr['subject']['UID'])) {
1✔
515
                        return $chainArr['subject']['UID'];
×
516
                }
517
                if (!empty($chainArr['subject']['CN'])) {
1✔
518
                        $cn = $chainArr['subject']['CN'];
1✔
519
                        if (is_array($cn)) {
1✔
520
                                $cn = $cn[0];
1✔
521
                        }
522
                        if (preg_match('/^(?<key>.*):(?<value>.*), /', (string)$cn, $matches)) {
1✔
523
                                return $matches['key'] . ':' . $matches['value'];
×
524
                        }
525
                }
526
                if (!empty($chainArr['extensions']['subjectAltName'])) {
1✔
527
                        $subjectAltName = $chainArr['extensions']['subjectAltName'];
1✔
528
                        if (is_array($subjectAltName)) {
1✔
529
                                $subjectAltName = $subjectAltName[0];
×
530
                        }
531
                        preg_match('/^(?<key>(email|account)):(?<value>.*)$/', (string)$subjectAltName, $matches);
1✔
532
                        if ($matches) {
1✔
533
                                if (str_ends_with($matches['value'], $this->host)) {
1✔
534
                                        $uid = str_replace('@' . $this->host, '', $matches['value']);
1✔
535
                                        $userFound = $this->userManager->get($uid);
1✔
536
                                        if ($userFound) {
1✔
537
                                                return 'account:' . $uid;
×
538
                                        } else {
539
                                                $userFound = $this->userManager->getByEmail($matches['value']);
1✔
540
                                                if ($userFound) {
1✔
541
                                                        $userFound = current($userFound);
×
542
                                                        return 'account:' . $userFound->getUID();
×
543
                                                } else {
544
                                                        return 'email:' . $matches['value'];
1✔
545
                                                }
546
                                        }
547
                                } else {
548
                                        $userFound = $this->userManager->getByEmail($matches['value']);
×
549
                                        if ($userFound) {
×
550
                                                $userFound = current($userFound);
×
551
                                                return 'account:' . $userFound->getUID();
×
552
                                        } else {
553
                                                $userFound = $this->userManager->get($matches['value']);
×
554
                                                if ($userFound) {
×
555
                                                        return 'account:' . $userFound->getUID();
×
556
                                                } else {
557
                                                        return $matches['key'] . ':' . $matches['value'];
×
558
                                                }
559
                                        }
560
                                }
561
                        }
562
                }
563
                return null;
×
564
        }
565

566
        private function loadSigners(): void {
567
                if (!$this->showSigners) {
8✔
568
                        return;
3✔
569
                }
570
                $this->loadSignersFromCertData();
5✔
571
                $this->loadLibreSignSigners();
5✔
572
        }
573

574
        /**
575
         * @return (mixed|string)[][]
576
         *
577
         * @psalm-return list<array{url: string, resolution: mixed}>
578
         */
579
        private function getPages(): array {
580
                $return = [];
×
581

582
                $metadata = $this->file->getMetadata();
×
583
                for ($page = 1; $page <= $metadata['p']; $page++) {
×
584
                        $return[] = [
×
585
                                'url' => $this->urlGenerator->linkToRoute('ocs.libresign.File.getPage', [
×
586
                                        'apiVersion' => 'v1',
×
587
                                        'uuid' => $this->file->getUuid(),
×
588
                                        'page' => $page,
×
589
                                ]),
×
590
                                'resolution' => $metadata['d'][$page - 1]
×
591
                        ];
×
592
                }
593
                return $return;
×
594
        }
595

596
        private function getVisibleElements(int $signRequestId): array {
597
                $return = [];
4✔
598
                if (!$this->showVisibleElements) {
4✔
599
                        return $return;
×
600
                }
601
                try {
602
                        $visibleElements = $this->fileElementMapper->getByFileIdAndSignRequestId($this->file->getId(), $signRequestId);
4✔
603
                        foreach ($visibleElements as $visibleElement) {
4✔
604
                                $element = [
×
605
                                        'elementId' => $visibleElement->getId(),
×
606
                                        'signRequestId' => $visibleElement->getSignRequestId(),
×
607
                                        'type' => $visibleElement->getType(),
×
608
                                        'coordinates' => [
×
609
                                                'page' => $visibleElement->getPage(),
×
610
                                                'urx' => $visibleElement->getUrx(),
×
611
                                                'ury' => $visibleElement->getUry(),
×
612
                                                'llx' => $visibleElement->getLlx(),
×
613
                                                'lly' => $visibleElement->getLly()
×
614
                                        ]
×
615
                                ];
×
616
                                $element['coordinates'] = array_merge(
×
617
                                        $element['coordinates'],
×
618
                                        $this->fileElementService->translateCoordinatesFromInternalNotation($element, $this->file)
×
619
                                );
×
620
                                $return[] = $element;
×
621
                        }
622
                } catch (\Throwable) {
×
623
                }
624
                return $return;
4✔
625
        }
626

627
        private function getPhoneNumber(): string {
628
                if (!$this->me) {
3✔
629
                        return '';
×
630
                }
631
                $userAccount = $this->accountManager->getAccount($this->me);
3✔
632
                return $userAccount->getProperty(IAccountManager::PROPERTY_PHONE)->getValue();
3✔
633
        }
634

635
        private function loadSettings(): void {
636
                if (!$this->showSettings) {
8✔
637
                        return;
4✔
638
                }
639
                if ($this->me) {
4✔
640
                        $this->fileData->settings = array_merge($this->fileData->settings, $this->accountService->getSettings($this->me));
3✔
641
                        $this->fileData->settings['phoneNumber'] = $this->getPhoneNumber();
3✔
642
                }
643
                if ($this->signerIdentified || $this->me) {
4✔
644
                        $status = $this->getIdentificationDocumentsStatus();
3✔
645
                        if ($status === self::IDENTIFICATION_DOCUMENTS_NEED_SEND) {
3✔
646
                                $this->fileData->settings['needIdentificationDocuments'] = true;
×
647
                                $this->fileData->settings['identificationDocumentsWaitingApproval'] = false;
×
648
                        } elseif ($status === self::IDENTIFICATION_DOCUMENTS_NEED_APPROVAL) {
3✔
649
                                $this->fileData->settings['needIdentificationDocuments'] = true;
×
650
                                $this->fileData->settings['identificationDocumentsWaitingApproval'] = true;
×
651
                        }
652
                }
653
        }
654

655
        public function getIdentificationDocumentsStatus(string $userId = ''): int {
656
                if (!$this->appConfig->getValueBool(Application::APP_ID, 'identification_documents', false)) {
6✔
657
                        return self::IDENTIFICATION_DOCUMENTS_DISABLED;
6✔
658
                }
659

660
                if (!$userId && $this->me instanceof IUser) {
×
661
                        $userId = $this->me->getUID();
×
662
                }
663
                if (!empty($userId)) {
×
664
                        $files = $this->fileMapper->getFilesOfAccount($userId);
×
665
                }
666

667
                if (empty($files) || !count($files)) {
×
668
                        return self::IDENTIFICATION_DOCUMENTS_NEED_SEND;
×
669
                }
670
                $deleted = array_filter($files, fn (File $file) => $file->getStatus() === File::STATUS_DELETED);
×
671
                if (count($deleted) === count($files)) {
×
672
                        return self::IDENTIFICATION_DOCUMENTS_NEED_SEND;
×
673
                }
674

675
                $signed = array_filter($files, fn (File $file) => $file->getStatus() === File::STATUS_SIGNED);
×
676
                if (count($signed) !== count($files)) {
×
677
                        return self::IDENTIFICATION_DOCUMENTS_NEED_APPROVAL;
×
678
                }
679

680
                return self::IDENTIFICATION_DOCUMENTS_APPROVED;
×
681
        }
682

683
        private function loadLibreSignData(): void {
684
                if (!$this->file) {
8✔
685
                        return;
4✔
686
                }
687
                $this->fileData->uuid = $this->file->getUuid();
4✔
688
                $this->fileData->name = $this->file->getName();
4✔
689
                $this->fileData->status = $this->file->getStatus();
4✔
690
                $this->fileData->created_at = $this->file->getCreatedAt()->format(DateTimeInterface::ATOM);
4✔
691
                $this->fileData->statusText = $this->fileMapper->getTextOfStatus($this->file->getStatus());
4✔
692
                $this->fileData->nodeId = $this->file->getNodeId();
4✔
693

694
                $this->fileData->requested_by = [
4✔
695
                        'userId' => $this->file->getUserId(),
4✔
696
                        'displayName' => $this->userManager->get($this->file->getUserId())->getDisplayName(),
4✔
697
                ];
4✔
698
                $this->fileData->file = $this->urlGenerator->linkToRoute('libresign.page.getPdf', ['uuid' => $this->file->getUuid()]);
4✔
699
                if ($this->showVisibleElements) {
4✔
700
                        $signers = $this->signRequestMapper->getByMultipleFileId([$this->file->getId()]);
4✔
701
                        $this->fileData->visibleElements = [];
4✔
702
                        foreach ($this->signRequestMapper->getVisibleElementsFromSigners($signers) as $visibleElements) {
4✔
703
                                $this->fileData->visibleElements = array_merge(
×
704
                                        $this->formatVisibleElementsToArray(
×
705
                                                $visibleElements,
×
706
                                                $this->file->getMetadata()
×
707
                                        ),
×
708
                                        $this->fileData->visibleElements
×
709
                                );
×
710
                        }
711
                }
712
        }
713

714
        private function loadMessages(): void {
715
                if (!$this->showMessages) {
8✔
716
                        return;
4✔
717
                }
718
                $messages = [];
4✔
719
                if ($this->fileData->settings['canSign']) {
4✔
720
                        $messages[] = [
1✔
721
                                'type' => 'info',
1✔
722
                                'message' => $this->l10n->t('You need to sign this document')
1✔
723
                        ];
1✔
724
                }
725
                if ($this->fileData->settings['canRequestSign']) {
4✔
726
                        $this->loadLibreSignSigners();
2✔
727
                        if (empty($this->fileData->signers)) {
2✔
728
                                $messages[] = [
×
729
                                        'type' => 'info',
×
730
                                        'message' => $this->l10n->t('You cannot request signature for this document, please contact your administrator')
×
731
                                ];
×
732
                        }
733
                }
734
                if ($messages) {
4✔
735
                        $this->fileData->messages = $messages;
1✔
736
                }
737
        }
738

739
        /**
740
         * @return LibresignValidateFile
741
         */
742
        public function toArray(): array {
743
                $this->loadLibreSignData();
8✔
744
                $this->loadFileMetadata();
8✔
745
                $this->loadSettings();
8✔
746
                $this->loadSigners();
8✔
747
                $this->loadMessages();
8✔
748
                $return = json_decode(json_encode($this->fileData), true);
8✔
749
                ksort($return);
8✔
750
                return $return;
8✔
751
        }
752

753
        public function setFileByPath(string $path): self {
754
                $node = $this->folderService->getFileByPath($path);
×
755
                $this->setFileByType('FileId', $node->getId());
×
756
                return $this;
×
757
        }
758

759
        /**
760
         * @return array[]
761
         *
762
         * @psalm-return array{data: array, pagination: array}
763
         */
764
        public function listAssociatedFilesOfSignFlow(
765
                $page = null,
766
                $length = null,
767
                array $filter = [],
768
                array $sort = [],
769
        ): array {
770
                $page ??= 1;
1✔
771
                $length ??= (int)$this->appConfig->getValueInt(Application::APP_ID, 'length_of_page', 100);
1✔
772

773
                $return = $this->signRequestMapper->getFilesAssociatedFilesWithMeFormatted(
1✔
774
                        $this->me,
1✔
775
                        $filter,
1✔
776
                        $page,
1✔
777
                        $length,
1✔
778
                        $sort,
1✔
779
                );
1✔
780

781
                $signers = $this->signRequestMapper->getByMultipleFileId(array_column($return['data'], 'id'));
1✔
782
                $identifyMethods = $this->signRequestMapper->getIdentifyMethodsFromSigners($signers);
1✔
783
                $visibleElements = $this->signRequestMapper->getVisibleElementsFromSigners($signers);
1✔
784
                $return['data'] = $this->associateAllAndFormat($this->me, $return['data'], $signers, $identifyMethods, $visibleElements);
1✔
785

786
                $return['pagination']->setRouteName('ocs.libresign.File.list');
1✔
787
                return [
1✔
788
                        'data' => $return['data'],
1✔
789
                        'pagination' => $return['pagination']->getPagination($page, $length, $filter)
1✔
790
                ];
1✔
791
        }
792

793
        private function associateAllAndFormat(IUser $user, array $files, array $signers, array $identifyMethods, array $visibleElements): array {
794
                foreach ($files as $key => $file) {
1✔
795
                        $totalSigned = 0;
×
796
                        foreach ($signers as $signerKey => $signer) {
×
797
                                if ($signer->getFileId() === $file['id']) {
×
798
                                        /** @var array<IdentifyMethod> */
799
                                        $identifyMethodsOfSigner = $identifyMethods[$signer->getId()] ?? [];
×
800
                                        $data = [
×
801
                                                'email' => array_reduce($identifyMethodsOfSigner, function (string $carry, IdentifyMethod $identifyMethod): string {
×
802
                                                        if ($identifyMethod->getIdentifierKey() === IdentifyMethodService::IDENTIFY_EMAIL) {
×
803
                                                                return $identifyMethod->getIdentifierValue();
×
804
                                                        }
805
                                                        if (filter_var($identifyMethod->getIdentifierValue(), FILTER_VALIDATE_EMAIL)) {
×
806
                                                                return $identifyMethod->getIdentifierValue();
×
807
                                                        }
808
                                                        return $carry;
×
809
                                                }, ''),
×
810
                                                'description' => $signer->getDescription(),
×
811
                                                'displayName'
×
812
                                                        => array_reduce($identifyMethodsOfSigner, function (string $carry, IdentifyMethod $identifyMethod): string {
×
813
                                                                if (!$carry && $identifyMethod->getMandatory()) {
×
814
                                                                        return $identifyMethod->getIdentifierValue();
×
815
                                                                }
816
                                                                return $carry;
×
817
                                                        }, $signer->getDisplayName()),
×
818
                                                'request_sign_date' => $signer->getCreatedAt()->format(DateTimeInterface::ATOM),
×
819
                                                'signed' => null,
×
820
                                                'signRequestId' => $signer->getId(),
×
821
                                                'me' => array_reduce($identifyMethodsOfSigner, function (bool $carry, IdentifyMethod $identifyMethod) use ($user): bool {
×
822
                                                        if ($identifyMethod->getIdentifierKey() === IdentifyMethodService::IDENTIFY_ACCOUNT) {
×
823
                                                                if ($user->getUID() === $identifyMethod->getIdentifierValue()) {
×
824
                                                                        return true;
×
825
                                                                }
826
                                                        } elseif ($identifyMethod->getIdentifierKey() === IdentifyMethodService::IDENTIFY_EMAIL) {
×
827
                                                                if (!$user->getEMailAddress()) {
×
828
                                                                        return false;
×
829
                                                                }
830
                                                                if ($user->getEMailAddress() === $identifyMethod->getIdentifierValue()) {
×
831
                                                                        return true;
×
832
                                                                }
833
                                                        }
834
                                                        return $carry;
×
835
                                                }, false),
×
836
                                                'visibleElements' => $this->formatVisibleElementsToArray(
×
837
                                                        $visibleElements[$signer->getId()] ?? [],
×
838
                                                        !empty($file['metadata'])?json_decode((string)$file['metadata'], true):[]
×
839
                                                ),
×
840
                                                'identifyMethods' => array_map(fn (IdentifyMethod $identifyMethod): array => [
×
841
                                                        'method' => $identifyMethod->getIdentifierKey(),
×
842
                                                        'value' => $identifyMethod->getIdentifierValue(),
×
843
                                                        'mandatory' => $identifyMethod->getMandatory(),
×
844
                                                ], array_values($identifyMethodsOfSigner)),
×
845
                                        ];
×
846

847
                                        if ($data['me']) {
×
848
                                                $temp = array_map(function (IdentifyMethod $identifyMethodEntity) use ($signer): array {
×
849
                                                        $this->identifyMethodService->setCurrentIdentifyMethod($identifyMethodEntity);
×
850
                                                        $identifyMethod = $this->identifyMethodService
×
851
                                                                ->setIsRequest(false)
×
852
                                                                ->getInstanceOfIdentifyMethod(
×
853
                                                                        $identifyMethodEntity->getIdentifierKey(),
×
854
                                                                        $identifyMethodEntity->getIdentifierValue(),
×
855
                                                                );
×
856
                                                        $signatureMethods = $identifyMethod->getSignatureMethods();
×
857
                                                        $return = [];
×
858
                                                        foreach ($signatureMethods as $signatureMethod) {
×
859
                                                                if (!$signatureMethod->isEnabled()) {
×
860
                                                                        continue;
×
861
                                                                }
862
                                                                $signatureMethod->setEntity($identifyMethod->getEntity());
×
863
                                                                $return[$signatureMethod->getName()] = $signatureMethod->toArray();
×
864
                                                        }
865
                                                        return $return;
×
866
                                                }, array_values($identifyMethodsOfSigner));
×
867
                                                $data['signatureMethods'] = [];
×
868
                                                foreach ($temp as $methods) {
×
869
                                                        $data['signatureMethods'] = array_merge($data['signatureMethods'], $methods);
×
870
                                                }
871
                                                $data['sign_uuid'] = $signer->getUuid();
×
872
                                                $files[$key]['url'] = $this->urlGenerator->linkToRoute('libresign.page.getPdfFile', ['uuid' => $signer->getuuid()]);
×
873
                                        }
874

875
                                        if ($signer->getSigned()) {
×
876
                                                $data['signed'] = $signer->getSigned()->format(DateTimeInterface::ATOM);
×
877
                                                $totalSigned++;
×
878
                                        }
879
                                        ksort($data);
×
880
                                        $files[$key]['signers'][] = $data;
×
881
                                        unset($signers[$signerKey]);
×
882
                                }
883
                        }
884
                        if (empty($files[$key]['signers'])) {
×
885
                                $files[$key]['signers'] = [];
×
886
                                $files[$key]['statusText'] = $this->l10n->t('no signers');
×
887
                        } else {
888
                                $files[$key]['statusText'] = $this->fileMapper->getTextOfStatus((int)$files[$key]['status']);
×
889
                        }
890
                        unset($files[$key]['id']);
×
891
                        ksort($files[$key]);
×
892
                }
893
                return $files;
1✔
894
        }
895

896
        /**
897
         * @param FileElement[] $visibleElements
898
         * @param array
899
         * @return array
900
         */
901
        private function formatVisibleElementsToArray(array $visibleElements, array $metadata): array {
902
                return array_map(function (FileElement $visibleElement) use ($metadata) {
×
903
                        $element = [
×
904
                                'elementId' => $visibleElement->getId(),
×
905
                                'signRequestId' => $visibleElement->getSignRequestId(),
×
906
                                'type' => $visibleElement->getType(),
×
907
                                'coordinates' => [
×
908
                                        'page' => $visibleElement->getPage(),
×
909
                                        'urx' => $visibleElement->getUrx(),
×
910
                                        'ury' => $visibleElement->getUry(),
×
911
                                        'llx' => $visibleElement->getLlx(),
×
912
                                        'lly' => $visibleElement->getLly()
×
913
                                ]
×
914
                        ];
×
915
                        $dimension = $metadata['d'][$element['coordinates']['page'] - 1];
×
916

917
                        $element['coordinates']['left'] = $element['coordinates']['llx'];
×
918
                        $element['coordinates']['height'] = abs($element['coordinates']['ury'] - $element['coordinates']['lly']);
×
919
                        $element['coordinates']['top'] = $dimension['h'] - $element['coordinates']['ury'];
×
920
                        $element['coordinates']['width'] = $element['coordinates']['urx'] - $element['coordinates']['llx'];
×
921

922
                        return $element;
×
923
                }, $visibleElements);
×
924
        }
925

926
        public function getMyLibresignFile(int $nodeId): File {
927
                return $this->signRequestMapper->getMyLibresignFile(
×
928
                        userId: $this->me->getUID(),
×
929
                        filter: [
×
930
                                'email' => $this->me->getEMailAddress(),
×
931
                                'nodeId' => $nodeId,
×
932
                        ],
×
933
                );
×
934
        }
935

936
        public function delete(int $fileId): void {
937
                $file = $this->fileMapper->getByFileId($fileId);
×
938
                $this->fileElementService->deleteVisibleElements($file->getId());
×
939
                $list = $this->signRequestMapper->getByFileId($file->getId());
×
940
                foreach ($list as $signRequest) {
×
941
                        $this->signRequestMapper->delete($signRequest);
×
942
                }
943
                $this->idDocsMapper->deleteByFileId($file->getId());
×
944
                $this->fileMapper->delete($file);
×
945
                if ($file->getSignedNodeId()) {
×
946
                        $signedNextcloudFile = $this->folderService->getFileById($file->getSignedNodeId());
×
947
                        $signedNextcloudFile->delete();
×
948
                }
949
                try {
950
                        $nextcloudFile = $this->folderService->getFileById($fileId);
×
951
                        $nextcloudFile->delete();
×
952
                } catch (NotFoundException) {
×
953
                }
954
        }
955
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc