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

LibreSign / libresign / 20606697273

30 Dec 2025 09:53PM UTC coverage: 44.868%. First build
20606697273

Pull #6277

github

web-flow
Merge edce7a9fa into 1098b4452
Pull Request #6277: fix: use nodeId for user element

19 of 56 new or added lines in 9 files covered. (33.93%)

6610 of 14732 relevant lines covered (44.87%)

5.08 hits per line

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

66.96
/lib/Helper/ValidateHelper.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\Helper;
10

11
use InvalidArgumentException;
12
use OC\AppFramework\Http;
13
use OC\User\NoUserException;
14
use OCA\Libresign\AppInfo\Application;
15
use OCA\Libresign\Db\File;
16
use OCA\Libresign\Db\FileElement;
17
use OCA\Libresign\Db\FileElementMapper;
18
use OCA\Libresign\Db\FileMapper;
19
use OCA\Libresign\Db\FileTypeMapper;
20
use OCA\Libresign\Db\IdDocsMapper;
21
use OCA\Libresign\Db\IdentifyMethod;
22
use OCA\Libresign\Db\IdentifyMethodMapper;
23
use OCA\Libresign\Db\SignRequest;
24
use OCA\Libresign\Db\SignRequestMapper;
25
use OCA\Libresign\Db\UserElementMapper;
26
use OCA\Libresign\Exception\LibresignException;
27
use OCA\Libresign\Service\FileService;
28
use OCA\Libresign\Service\IdentifyMethod\IIdentifyMethod;
29
use OCA\Libresign\Service\IdentifyMethodService;
30
use OCA\Libresign\Service\SignerElementsService;
31
use OCP\AppFramework\Db\DoesNotExistException;
32
use OCP\Files\IMimeTypeDetector;
33
use OCP\Files\IRootFolder;
34
use OCP\Files\NotFoundException;
35
use OCP\Files\NotPermittedException;
36
use OCP\IAppConfig;
37
use OCP\IGroupManager;
38
use OCP\IL10N;
39
use OCP\IUser;
40
use OCP\IUserManager;
41
use OCP\Security\IHasher;
42

43
class ValidateHelper {
44
        /** @var \OCP\Files\File[] */
45
        private $file = [];
46

47
        public const TYPE_TO_SIGN = 1;
48
        public const TYPE_VISIBLE_ELEMENT_PDF = 2;
49
        public const TYPE_VISIBLE_ELEMENT_USER = 3;
50
        public const TYPE_ACCOUNT_DOCUMENT = 4;
51

52
        public const STATUS_DRAFT = 0;
53
        public const STATUS_ABLE_TO_SIGN = 1;
54
        public const STATUS_PARTIAL_SIGNED = 2;
55
        public const STATUS_SIGNED = 3;
56
        public const STATUS_DELETED = 4;
57
        public const VALID_MIMETIPE = [
58
                'application/pdf',
59
                'image/png',
60
        ];
61

62
        public function __construct(
63
                private IL10N $l10n,
64
                private SignRequestMapper $signRequestMapper,
65
                private FileMapper $fileMapper,
66
                private FileTypeMapper $fileTypeMapper,
67
                private FileElementMapper $fileElementMapper,
68
                private IdDocsMapper $idDocsMapper,
69
                private UserElementMapper $userElementMapper,
70
                private IdentifyMethodMapper $identifyMethodMapper,
71
                private IdentifyMethodService $identifyMethodService,
72
                private SignerElementsService $signerElementsService,
73
                private IMimeTypeDetector $mimeTypeDetector,
74
                private IHasher $hasher,
75
                private IAppConfig $appConfig,
76
                private IGroupManager $groupManager,
77
                private IUserManager $userManager,
78
                private IRootFolder $root,
79
        ) {
80
        }
131✔
81

82
        public function validateNewFile(array $data, int $type = self::TYPE_TO_SIGN, ?IUser $user = null): void {
83
                $this->validateFile($data, $type, $user);
3✔
84
                if (!empty($data['file']['fileId'])) {
3✔
85
                        $this->validateNotRequestedSign((int)$data['file']['fileId']);
1✔
86
                } elseif (!empty($data['file']['path'])) {
2✔
87
                        $userFolder = $this->root->getUserFolder($user?->getUID() ?? $data['userManager']->getUID());
×
88
                        try {
89
                                $node = $userFolder->get($data['file']['path']);
×
90
                        } catch (NotFoundException) {
×
91
                                throw new LibresignException($this->l10n->t('Invalid data to validate file'), 404);
×
92
                        }
93
                        $this->validateNotRequestedSign($node->getId());
×
94
                }
95
        }
96

97
        /**
98
         * @property array $data
99
         * @property int $type to_sign|visible_element
100
         */
101
        public function validateFile(array $data, int $type = self::TYPE_TO_SIGN, ?IUser $user = null): void {
102
                if (empty($data['file'])) {
10✔
103
                        if ($type === self::TYPE_TO_SIGN) {
3✔
104
                                throw new LibresignException($this->l10n->t('File type: %s. Empty file.', [$this->getTypeOfFile($type)]));
1✔
105
                        }
106
                        if ($type === self::TYPE_VISIBLE_ELEMENT_USER) {
2✔
107
                                if ($this->elementNeedFile($data)) {
×
108
                                        throw new LibresignException($this->l10n->t('Elements of type %s need file.', [$data['type']]));
×
109
                                }
110
                        }
111
                        return;
2✔
112
                }
113
                if (!empty($data['file']['url'])) {
7✔
114
                        if (!filter_var($data['file']['url'], FILTER_VALIDATE_URL)) {
×
115
                                throw new LibresignException($this->l10n->t('File type: %s. Specify a URL, a Base64 string or a fileID.', [$this->getTypeOfFile($type)]));
×
116
                        }
117
                } elseif (!empty($data['file']['fileId'])) {
7✔
118
                        if (!is_numeric($data['file']['fileId'])) {
2✔
119
                                throw new LibresignException($this->l10n->t('File type: %s. Invalid fileID.', [$this->getTypeOfFile($type)]));
1✔
120
                        }
121
                        if (!is_a($user, IUser::class)) {
1✔
122
                                if (!is_a($data['userManager'], IUser::class)) {
1✔
123
                                        throw new LibresignException($this->l10n->t('User not found.'));
×
124
                                }
125
                        }
126
                        $this->validateIfNodeIdExists((int)$data['file']['fileId'], $data['userManager']->getUID(), $type);
1✔
127
                        $this->validateMimeTypeAcceptedByNodeId((int)$data['file']['fileId'], $data['userManager']->getUID(), $type);
1✔
128
                } elseif (!empty($data['file']['base64'])) {
5✔
129
                        $this->validateBase64($data['file']['base64'], $type);
4✔
130
                } elseif (!empty($data['file']['path'])) {
1✔
131
                        if (!is_a($user, IUser::class)) {
×
132
                                if (!is_a($data['userManager'], IUser::class)) {
×
133
                                        throw new LibresignException($this->l10n->t('User not found.'));
×
134
                                }
135
                        }
136
                        $userFolder = $this->root->getUserFolder($user?->getUID() ?? $data['userManager']->getUID());
×
137
                        try {
138
                                $userFolder->get($data['file']['path']);
×
139
                        } catch (NotFoundException) {
×
140
                                throw new LibresignException($this->l10n->t('Invalid data to validate file'), 404);
×
141
                        }
142
                } else {
143
                        throw new LibresignException($this->l10n->t('File type: %s. Specify a URL, Base64 string, path or a fileID.', [$this->getTypeOfFile($type)]));
1✔
144
                }
145
        }
146

147
        private function elementNeedFile(array $data): bool {
148
                return in_array($data['type'], ['signature', 'initial']);
×
149
        }
150

151
        private function getTypeOfFile(int $type): string {
152
                if ($type === self::TYPE_TO_SIGN) {
11✔
153
                        return $this->l10n->t('document to sign');
10✔
154
                }
155
                return $this->l10n->t('visible element');
1✔
156
        }
157

158
        public function validateBase64(string $base64, int $type = self::TYPE_TO_SIGN): void {
159
                $withMime = explode(',', $base64);
10✔
160
                if (count($withMime) === 2) {
10✔
161
                        $withMime[0] = explode(';', $withMime[0]);
4✔
162
                        if (count($withMime[0]) !== 2) {
4✔
163
                                throw new LibresignException($this->l10n->t('File type: %s. Invalid Base64 file.', [$this->getTypeOfFile($type)]));
×
164
                        }
165
                        if ($withMime[0][1] !== 'base64') {
4✔
166
                                throw new LibresignException($this->l10n->t('File type: %s. Invalid Base64 file.', [$this->getTypeOfFile($type)]));
1✔
167
                        }
168

169
                        if ($type === self::TYPE_TO_SIGN) {
3✔
170
                                if ($withMime[0][0] !== 'data:application/pdf') {
3✔
171
                                        throw new LibresignException($this->l10n->t('File type: %s. Invalid Base64 file.', [$this->getTypeOfFile($type)]));
1✔
172
                                }
173
                        }
174
                        $base64 = $withMime[1];
2✔
175
                }
176
                $string = base64_decode($base64);
8✔
177
                if (in_array($type, [self::TYPE_VISIBLE_ELEMENT_USER, self::TYPE_VISIBLE_ELEMENT_PDF])) {
8✔
178
                        if (strlen($string) > 5000 * 1024) { // 5Mb
×
179
                                // TRANSLATORS Error when the visible element to add to document, like a signature or initial is bigger than normal
180
                                throw new InvalidArgumentException($this->l10n->t('File is too big'));
×
181
                        }
182
                }
183
                $newBase64 = base64_encode($string);
8✔
184
                if ($newBase64 !== $base64) {
8✔
185
                        throw new LibresignException($this->l10n->t('File type: %s. Invalid Base64 file.', [$this->getTypeOfFile($type)]));
3✔
186
                }
187

188
                $mimeType = $this->mimeTypeDetector->detectString($string);
5✔
189

190
                if ($type === self::TYPE_TO_SIGN) {
5✔
191
                        if ($mimeType !== 'application/pdf') {
4✔
192
                                throw new LibresignException($this->l10n->t('File type: %s. Invalid Base64 file.', [$this->getTypeOfFile($type)]));
×
193
                        }
194
                } elseif ($mimeType !== 'image/png') {
1✔
195
                        if (in_array($type, [self::TYPE_VISIBLE_ELEMENT_USER, self::TYPE_VISIBLE_ELEMENT_PDF])) {
1✔
196
                                throw new LibresignException($this->l10n->t('File type: %s. Invalid Base64 file.', [$this->getTypeOfFile($type)]));
×
197
                        }
198
                }
199
        }
200

201
        public function validateNotRequestedSign(int $nodeId): void {
202
                try {
203
                        $fileMapper = $this->signRequestMapper->getByNodeId($nodeId);
3✔
204
                } catch (\Throwable) {
1✔
205
                }
206
                if (!empty($fileMapper)) {
3✔
207
                        throw new LibresignException($this->l10n->t('Already asked to sign this document'));
1✔
208
                }
209
        }
210

211
        public function validateVisibleElements(?array $visibleElements, int $type): void {
212
                if (!is_array($visibleElements)) {
2✔
213
                        throw new LibresignException($this->l10n->t('Visible elements need to be an array'));
1✔
214
                }
215
                foreach ($visibleElements as $element) {
1✔
216
                        $this->validateVisibleElement($element, $type);
1✔
217
                }
218
        }
219

220
        public function validateVisibleElement(array $element, int $type): void {
221
                $this->validateElementType($element);
3✔
222
                $this->validateElementSignRequestId($element, $type);
3✔
223
                $this->validateFile($element, $type);
3✔
224
                $this->validateElementCoordinates($element);
3✔
225
        }
226

227
        public function validateElementSignRequestId(array $element, int $type): void {
228
                if ($type !== self::TYPE_VISIBLE_ELEMENT_PDF) {
3✔
229
                        return;
1✔
230
                }
231
                if (!array_key_exists('signRequestId', $element) && !array_key_exists('uuid', $element)) {
2✔
232
                        // TRANSLATION The element can be an image or text. It has to be associated with an user. The element will be added to the document.
233
                        throw new LibresignException($this->l10n->t('Element must be associated with a user'));
×
234
                }
235

236
                $getter = array_key_exists('signRequestId', $element)
2✔
237
                        ? fn () => $this->signRequestMapper->getById($element['signRequestId'])
2✔
238
                        : fn () => $this->signRequestMapper->getByUuid($element['uuid']);
2✔
239

240
                try {
241
                        $getter();
2✔
242
                } catch (\Throwable) {
×
243
                        throw new LibresignException($this->l10n->t('User not found for element.'));
×
244
                }
245
        }
246

247
        public function validateElementCoordinates(array $element): void {
248
                if (!array_key_exists('coordinates', $element)) {
5✔
249
                        return;
2✔
250
                }
251
                $this->validateElementPage($element);
3✔
252
                $this->validateElementCoordinate($element);
3✔
253
        }
254

255
        private function validateElementCoordinate(array $element): void {
256
                foreach ($element['coordinates'] as $type => $value) {
3✔
257
                        if (in_array($type, ['llx', 'lly', 'urx', 'ury', 'width', 'height', 'left', 'top'])) {
3✔
258
                                if (!is_int($value)) {
2✔
259
                                        throw new LibresignException($this->l10n->t('Coordinate %s must be an integer', [$type]));
×
260
                                }
261
                                if ($value < 0) {
2✔
262
                                        // TRANSLATORS Is an error that occur when the visible element added to the PDF file have your position outside the page margin
263
                                        throw new LibresignException($this->l10n->t('Object outside the page margin'));
×
264
                                }
265
                        }
266
                }
267
        }
268

269
        public function validateElementPage(array $element): void {
270
                if (!array_key_exists('page', $element['coordinates'])) {
5✔
271
                        return;
×
272
                }
273
                if (!is_int($element['coordinates']['page'])) {
5✔
274
                        throw new LibresignException($this->l10n->t('Page number must be an integer'));
1✔
275
                }
276
                if ($element['coordinates']['page'] < 1) {
4✔
277
                        throw new LibresignException($this->l10n->t('Page must be equal to or greater than 1'));
1✔
278
                }
279
        }
280

281
        public function validateElementType(array $element): void {
282
                if (!array_key_exists('type', $element)) {
10✔
283
                        if (!array_key_exists('elementId', $element)) {
1✔
284
                                throw new LibresignException($this->l10n->t('Element needs a type'));
1✔
285
                        }
286
                        return;
×
287
                }
288
                if (!in_array($element['type'], ['signature', 'initial', 'date', 'datetime', 'text'])) {
9✔
289
                        throw new LibresignException($this->l10n->t('Invalid element type'));
1✔
290
                }
291
        }
292

293
        public function validateVisibleElementsRelation(array $list, SignRequest $signRequest, ?IUser $user): void {
294
                $canCreateSignature = $this->signerElementsService->canCreateSignature();
2✔
295
                foreach ($list as $elements) {
2✔
296
                        if (!array_key_exists('documentElementId', $elements)) {
×
297
                                throw new LibresignException($this->l10n->t('Field %s not found', ['documentElementId']));
×
298
                        }
299
                        if ($canCreateSignature && !array_key_exists('profileNodeId', $elements)) {
×
300
                                throw new LibresignException($this->l10n->t('Field %s not found', ['profileNodeId']));
×
301
                        }
302
                        $this->validateSignerIsOwnerOfPdfVisibleElement($elements['documentElementId'], $signRequest);
×
303
                        if ($canCreateSignature && $user instanceof IUser) {
×
304
                                try {
NEW
305
                                        $this->userElementMapper->findOne(['node_id' => $elements['profileNodeId'], 'user_id' => $user->getUID()]);
×
306
                                } catch (\Throwable) {
×
307
                                        throw new LibresignException($this->l10n->t('Field %s does not belong to user', $elements['profileNodeId']));
×
308
                                }
309
                        }
310
                }
311
                $this->validateUserHasNecessaryElements($signRequest, $user, $list);
2✔
312
        }
313

314
        private function validateUserHasNecessaryElements(SignRequest $signRequest, ?IUser $user, array $list = []): void {
315
                $fileElements = $this->fileElementMapper->getByFileIdAndSignRequestId($signRequest->getFileId(), $signRequest->getId());
2✔
316
                $total = array_filter($fileElements, function (FileElement $fileElement) use ($list, $user): bool {
2✔
317
                        $found = array_filter($list, fn ($item): bool => $item['documentElementId'] === $fileElement->getId());
×
318
                        if (!$found) {
×
319
                                if (!$this->signerElementsService->canCreateSignature()) {
×
320
                                        return true;
×
321
                                }
322
                                try {
323
                                        if (!$user instanceof IUser) {
×
324
                                                throw new \Exception();
×
325
                                        }
326
                                        $this->userElementMapper->findMany([
×
327
                                                'user_id' => $user->getUID(),
×
328
                                                'type' => $fileElement->getType(),
×
329
                                        ]);
×
330
                                        return true;
×
331
                                } catch (\Throwable) {
×
332
                                        throw new LibresignException($this->l10n->t('You need to define a visible signature or initials to sign this document.'));
×
333
                                }
334
                        }
335
                        return true;
×
336
                });
2✔
337
                if (count($total) !== count($fileElements)) {
2✔
338
                        throw new LibresignException($this->l10n->t('You need to define a visible signature or initials to sign this document.'));
×
339
                }
340
        }
341

342
        private function validateSignerIsOwnerOfPdfVisibleElement(int $documentElementId, SignRequest $signRequest): void {
343
                $documentElement = $this->fileElementMapper->getById($documentElementId);
×
344
                if ($documentElement->getSignRequestId() !== $signRequest->getId()) {
×
345
                        throw new LibresignException($this->l10n->t('Invalid data to sign file'), 1);
×
346
                }
347
        }
348

349
        public function validateAuthenticatedUserIsOwnerOfPdfVisibleElement(int $documentElementId, string $uid): void {
350
                try {
351
                        $documentElement = $this->fileElementMapper->getById($documentElementId);
1✔
352
                        $signRequest = $this->signRequestMapper->getById($documentElement->getSignRequestId());
1✔
353
                        $file = $this->fileMapper->getById($signRequest->getFileId());
1✔
354
                        if ($file->getUserId() !== $uid) {
1✔
355
                                throw new LibresignException($this->l10n->t('Field %s does not belong to user', (string)$documentElementId));
1✔
356
                        }
357
                } catch (\Throwable) {
×
358
                        ($signRequest->getFileId());
×
359
                        throw new LibresignException($this->l10n->t('Field %s does not belong to user', (string)$documentElementId));
×
360
                }
361
        }
362

363
        public function validateIdDocIsOwnedByUser(int $nodeId, string $uid): void {
364
                try {
365
                        $this->idDocsMapper->getByUserIdAndNodeId($uid, $nodeId);
×
366
                } catch (\Throwable) {
×
367
                        throw new LibresignException($this->l10n->t('This file is not yours'));
×
368
                }
369
        }
370

371
        public function fileCanBeSigned(File $file): void {
372
                $statusList = [
2✔
373
                        File::STATUS_ABLE_TO_SIGN,
2✔
374
                        File::STATUS_PARTIAL_SIGNED
2✔
375
                ];
2✔
376
                if (!in_array($file->getStatus(), $statusList)) {
2✔
377
                        $statusText = $this->fileMapper->getTextOfStatus($file->getStatus());
×
378
                        throw new LibresignException($this->l10n->t('This file cannot be signed. Invalid status: %s', $statusText));
×
379
                }
380
        }
381

382
        public function validateIfNodeIdExists(int $nodeId, string $userId = '', int $type = self::TYPE_TO_SIGN): void {
383
                if (!$userId) {
5✔
384
                        $libresignFile = $this->fileMapper->getByNodeId($nodeId);
4✔
385
                        $userId = $libresignFile->getUserId();
4✔
386
                }
387
                try {
388
                        $file = $this->root->getUserFolder($userId)->getFirstNodeById($nodeId);
5✔
389
                } catch (NoUserException) {
2✔
390
                        throw new LibresignException($this->l10n->t('User not found.'));
1✔
391
                } catch (NotPermittedException) {
1✔
392
                        throw new LibresignException($this->l10n->t('You do not have permission for this action.'));
1✔
393
                }
394
                if (!$file) {
3✔
395
                        throw new LibresignException($this->l10n->t('File type: %s. Invalid fileID.', [$this->getTypeOfFile($type)]));
1✔
396
                }
397
        }
398

399
        public function validateMimeTypeAcceptedByNodeId(int $nodeId, string $userId = '', int $type = self::TYPE_TO_SIGN): void {
400
                if (!$userId) {
6✔
401
                        $libresignFile = $this->fileMapper->getByNodeId($nodeId);
5✔
402
                        $userId = $libresignFile->getUserId();
5✔
403
                }
404
                $file = $this->root->getUserFolder($userId)->getFirstNodeById($nodeId);
6✔
405
                $this->validateMimeTypeAcceptedByMime($file->getMimeType(), $type);
6✔
406
        }
407

408
        public function validateMimeTypeAcceptedByMime(string $mimetype, int $type = self::TYPE_TO_SIGN): void {
409
                switch ($type) {
410
                        case self::TYPE_TO_SIGN:
411
                                if ($mimetype !== 'application/pdf') {
19✔
412
                                        throw new LibresignException($this->l10n->t('File type: %s. Must be a fileID of %s format.', [$this->getTypeOfFile($type), 'PDF']));
1✔
413
                                }
414
                                break;
18✔
415
                        case self::TYPE_VISIBLE_ELEMENT_PDF:
416
                        case self::TYPE_VISIBLE_ELEMENT_USER:
417
                                if ($mimetype !== 'image/png') {
2✔
418
                                        throw new LibresignException($this->l10n->t('File type: %s. Must be a fileID of %s format.', [$this->getTypeOfFile($type), 'png']));
1✔
419
                                }
420
                                break;
1✔
421
                }
422
        }
423

424
        public function validateLibreSignFileId(int $fileId): void {
425
                try {
426
                        $this->fileMapper->getById($fileId);
5✔
427
                } catch (\Throwable) {
1✔
428
                        throw new LibresignException($this->l10n->t('Invalid fileID'));
1✔
429
                }
430
        }
431

432
        private function getLibreSignFileByNodeId(int $nodeId): ?\OCP\Files\File {
433
                if (isset($this->file[$nodeId])) {
×
434
                        return $this->file[$nodeId];
×
435
                }
436
                $libresignFile = $this->fileMapper->getByNodeId($nodeId);
×
437

438
                $userFolder = $this->root->getUserFolder($libresignFile->getUserId());
×
439
                $file = $userFolder->getFirstNodeById($nodeId);
×
440
                if ($file instanceof \OCP\Files\File) {
×
441
                        $this->file[$nodeId] = $file;
×
442
                        return $this->file[$nodeId];
×
443
                }
444
                return null;
×
445
        }
446

447
        public function canRequestSign(IUser $user): void {
448
                $authorized = $this->appConfig->getValueArray(Application::APP_ID, 'groups_request_sign', ['admin']);
15✔
449
                if (empty($authorized)) {
15✔
450
                        $authorized = ['admin'];
2✔
451
                }
452
                if (!is_array($authorized)) {
15✔
453
                        throw new LibresignException(
×
454
                                json_encode([
×
455
                                        'action' => JSActions::ACTION_DO_NOTHING,
×
456
                                        'errors' => [['message' => $this->l10n->t('You are not allowed to request signing')]],
×
457
                                ]),
×
458
                                Http::STATUS_UNPROCESSABLE_ENTITY,
×
459
                        );
×
460
                }
461
                $userGroups = $this->groupManager->getUserGroupIds($user);
15✔
462
                if (!array_intersect($userGroups, $authorized)) {
15✔
463
                        throw new LibresignException(
7✔
464
                                json_encode([
7✔
465
                                        'action' => JSActions::ACTION_DO_NOTHING,
7✔
466
                                        'errors' => [['message' => $this->l10n->t('You are not allowed to request signing')]],
7✔
467
                                ]),
7✔
468
                                Http::STATUS_UNPROCESSABLE_ENTITY,
7✔
469
                        );
7✔
470
                }
471
        }
472

473
        public function iRequestedSignThisFile(IUser $user, int $fileId): void {
474
                $libresignFile = $this->fileMapper->getById($fileId);
10✔
475
                if ($libresignFile->getUserId() !== $user->getUID()) {
10✔
476
                        throw new LibresignException($this->l10n->t('You do not have permission for this action.'));
1✔
477
                }
478
        }
479

480
        public function validateFileStatus(array $data): void {
481
                if (array_key_exists('status', $data)) {
2✔
482
                        $validStatusList = [
2✔
483
                                File::STATUS_DRAFT,
2✔
484
                                File::STATUS_ABLE_TO_SIGN,
2✔
485
                                File::STATUS_DELETED
2✔
486
                        ];
2✔
487
                        if (!in_array($data['status'], $validStatusList)) {
2✔
488
                                throw new LibresignException($this->l10n->t('Invalid status code for file.'));
×
489
                        }
490
                        if (!empty($data['uuid'])) {
2✔
491
                                $file = $this->fileMapper->getByUuid($data['uuid']);
1✔
492
                        } elseif (!empty($data['file']['fileId'])) {
1✔
493
                                try {
494
                                        $file = $this->fileMapper->getById($data['file']['fileId']);
×
495
                                } catch (\Throwable) {
×
496
                                }
497
                        }
498
                        if (isset($file)) {
2✔
499
                                if ($data['status'] > $file->getStatus()) {
1✔
500
                                        if ($file->getStatus() >= File::STATUS_ABLE_TO_SIGN) {
×
501
                                                if ($data['status'] !== File::STATUS_DELETED) {
×
502
                                                        throw new LibresignException($this->l10n->t('Sign process already started. Unable to change status.'));
×
503
                                                }
504
                                        }
505
                                }
506
                        } elseif ($data['status'] === File::STATUS_DELETED) {
1✔
507
                                throw new LibresignException($this->l10n->t('Invalid status code for file.'));
×
508
                        }
509
                }
510
        }
511

512
        public function validateIdentifySigners(array $data): void {
513
                if (empty($data['users'])) {
22✔
514
                        return;
3✔
515
                }
516

517
                $this->validateSignersDataStructure($data);
19✔
518

519
                foreach ($data['users'] as $signer) {
18✔
520
                        $this->validateSignerData($signer);
18✔
521
                }
522
        }
523

524
        private function validateSignersDataStructure(array $data): void {
525
                if (empty($data) || !array_key_exists('users', $data) || !is_array($data['users']) || empty($data['users'])) {
19✔
526
                        throw new LibresignException($this->l10n->t('No signers'));
1✔
527
                }
528
        }
529

530
        private function validateSignerData(mixed $signer): void {
531
                if (!is_array($signer) || empty($signer)) {
18✔
532
                        throw new LibresignException($this->l10n->t('No signers'));
2✔
533
                }
534

535
                $this->validateSignerDisplayName($signer);
16✔
536
                $this->validateSignerIdentifyMethods($signer);
14✔
537
        }
538

539
        private function validateSignerDisplayName(array $signer): void {
540
                if (isset($signer['displayName']) && strlen($signer['displayName']) > 64) {
16✔
541
                        // It's an api error, don't translate
542
                        throw new LibresignException('Display name must not be longer than 64 characters');
2✔
543
                }
544
        }
545

546
        private function validateSignerIdentifyMethods(array $signer): void {
547
                $normalizedMethods = $this->normalizeIdentifyMethods($signer);
14✔
548

549
                foreach ($normalizedMethods as $method) {
9✔
550
                        $this->validateIdentifyMethodForRequest($method['name'], $method['value']);
9✔
551
                }
552
        }
553

554
        /**
555
         * @todo unify the key to be only 'identify' or only 'identifyMethods'
556
         */
557
        private function normalizeIdentifyMethods(array $signer): array {
558
                $key = array_key_exists('identifyMethods', $signer) ? 'identifyMethods' : 'identify';
14✔
559

560
                if (empty($signer[$key]) || !is_array($signer[$key])) {
14✔
561
                        throw new LibresignException('No identify methods for signer');
3✔
562
                }
563

564
                $normalizedMethods = [];
11✔
565

566
                foreach ($signer[$key] as $name => $data) {
11✔
567
                        $normalizedMethods[] = $this->normalizeIdentifyMethodEntry($key, $name, $data);
11✔
568
                }
569
                return $normalizedMethods;
9✔
570
        }
571

572
        /**
573
         * Extracted from normalizeIdentifyMethods to reduce cyclomatic complexity.
574
         */
575
        private function normalizeIdentifyMethodEntry(string $key, $name, $data): array {
576
                if ($key === 'identifyMethods') {
11✔
577
                        return $this->normalizeIdentifyMethodsStructure($data);
5✔
578
                } else {
579
                        return $this->normalizeIdentifyStructure($name, $data);
7✔
580
                }
581
        }
582

583
        private function normalizeIdentifyMethodsStructure(mixed $data): array {
584
                if (!is_array($data) || !array_key_exists('method', $data) || !array_key_exists('value', $data)) {
5✔
585
                        throw new LibresignException('Invalid identify method structure');
2✔
586
                }
587

588
                return [
3✔
589
                        'name' => $data['method'],
3✔
590
                        'value' => $data['value'],
3✔
591
                ];
3✔
592
        }
593

594
        private function normalizeIdentifyStructure(string $name, mixed $value): array {
595
                return [
7✔
596
                        'name' => $name,
7✔
597
                        'value' => $value,
7✔
598
                ];
7✔
599
        }
600

601
        private function validateIdentifyMethodForRequest(string $name, string $identifyValue): void {
602
                $identifyMethod = $this->identifyMethodService->getInstanceOfIdentifyMethod($name, $identifyValue);
11✔
603
                $identifyMethod->validateToRequest();
11✔
604

605
                $signatureMethods = $identifyMethod->getSignatureMethods();
11✔
606
                if (empty($signatureMethods)) {
11✔
607
                        // It's an api error, don't translate
608
                        throw new LibresignException('No signature methods for identify method ' . $name);
1✔
609
                }
610
        }
611

612
        public function validateExistingFile(array $data): void {
613
                if (isset($data['uuid'])) {
11✔
614
                        $this->validateFileUuid($data);
5✔
615
                        $file = $this->fileMapper->getByUuid($data['uuid']);
5✔
616
                        $this->iRequestedSignThisFile($data['userManager'], $file->getId());
5✔
617
                } elseif (isset($data['file'])) {
6✔
618
                        if (!isset($data['file']['fileId'])) {
5✔
619
                                throw new LibresignException($this->l10n->t('Invalid fileID'));
1✔
620
                        }
621
                        $this->validateLibreSignFileId($data['file']['fileId']);
4✔
622
                        $this->iRequestedSignThisFile($data['userManager'], $data['file']['fileId']);
3✔
623
                } else {
624
                        // TRANSLATORS This message is at API side. When an application or a
625
                        // developer send a structure to API without an UUID or without a
626
                        // File Object, throws this error. Normally LibreSign don't throws
627
                        // this error because the User Interface of LibreSign or send an
628
                        // UUID or a File object to API.
629
                        throw new LibresignException($this->l10n->t('Please provide either UUID or File object'));
1✔
630
                }
631
        }
632

633
        public function haveValidMail(array $data, ?int $type = null): void {
634
                if ($type === self::TYPE_TO_SIGN) {
4✔
635
                        return;
×
636
                }
637
                if (empty($data)) {
4✔
638
                        throw new LibresignException($this->l10n->t('No user data'));
1✔
639
                }
640
                if (empty($data['email'])) {
3✔
641
                        if (!empty($data['uid'])) {
1✔
642
                                $user = $this->userManager->get($data['uid']);
×
643
                                if (!$user) {
×
644
                                        throw new LibresignException($this->l10n->t('User not found.'));
×
645
                                }
646
                                if (!$user->getEMailAddress()) {
×
647
                                        // TRANSLATORS There is no email address for given user
648
                                        throw new LibresignException($this->l10n->t('User %s has no email address.', [$data['uid']]));
×
649
                                }
650
                        } else {
651
                                throw new LibresignException($this->l10n->t('Email required'));
1✔
652
                        }
653
                } elseif (!filter_var($data['email'], FILTER_VALIDATE_EMAIL)) {
2✔
654
                        throw new LibresignException($this->l10n->t('Invalid email'));
1✔
655
                }
656
        }
657

658
        public function signerWasAssociated(array $signer): void {
659
                try {
660
                        $libresignFile = $this->fileMapper->getByNodeId();
1✔
661
                } catch (\Throwable) {
×
662
                        throw new LibresignException($this->l10n->t('File not loaded'));
×
663
                }
664
                $signatures = $this->signRequestMapper->getByFileUuid($libresignFile->getUuid());
1✔
665
                $exists = array_filter($signatures, function (SignRequest $signRequest) use ($signer): bool {
1✔
666
                        $key = key($signer);
1✔
667
                        $value = current($signer);
1✔
668
                        $identifyMethods = $this->identifyMethodMapper->getIdentifyMethodsFromSignRequestId($signRequest->getId());
1✔
669
                        $found = array_filter($identifyMethods, function (IdentifyMethod $identifyMethod) use ($key, $value) {
1✔
670
                                if ($identifyMethod->getIdentifierKey() === $key && $identifyMethod->getIdentifierValue() === $value) {
1✔
671
                                        return true;
1✔
672
                                }
673
                                return false;
×
674
                        });
1✔
675
                        return count($found) > 0;
1✔
676
                });
1✔
677
                if (!$exists) {
1✔
678
                        throw new LibresignException($this->l10n->t('No signature was requested to %s', $signer['email']));
×
679
                }
680
        }
681

682
        public function notSigned(array $signer): void {
683
                try {
684
                        $libresignFile = $this->fileMapper->getByNodeId();
1✔
685
                } catch (\Throwable) {
×
686
                        throw new LibresignException($this->l10n->t('File not loaded'));
×
687
                }
688
                $signatures = $this->signRequestMapper->getByFileUuid($libresignFile->getUuid());
1✔
689

690
                $exists = array_filter($signatures, function (SignRequest $signRequest) use ($signer): bool {
1✔
691
                        $key = key($signer);
1✔
692
                        $value = current($signer);
1✔
693
                        $identifyMethods = $this->identifyMethodMapper->getIdentifyMethodsFromSignRequestId($signRequest->getId());
1✔
694
                        $found = array_filter($identifyMethods, function (IdentifyMethod $identifyMethod) use ($key, $value) {
1✔
695
                                if ($identifyMethod->getIdentifierKey() === $key && $identifyMethod->getIdentifierValue() === $value) {
1✔
696
                                        return true;
1✔
697
                                }
698
                                return false;
×
699
                        });
1✔
700
                        if (count($found) > 0) {
1✔
701
                                return $signRequest->getSigned() !== null;
1✔
702
                        }
703
                        return false;
×
704
                });
1✔
705
                if (!$exists) {
1✔
706
                        return;
1✔
707
                }
708
                $firstSigner = array_values($exists)[0];
×
709
                throw new LibresignException($this->l10n->t('%s already signed this file', $firstSigner->getDisplayName()));
×
710
        }
711

712
        public function validateFileUuid(array $data): void {
713
                try {
714
                        $this->fileMapper->getByUuid($data['uuid']);
7✔
715
                } catch (\Throwable) {
1✔
716
                        throw new LibresignException($this->l10n->t('Invalid UUID file'));
1✔
717
                }
718
        }
719

720
        public function validateSigner(string $uuid, ?IUser $user = null): void {
721
                $this->validateSignerUuidExists($uuid);
5✔
722
                $this->validateSignerStatus($uuid);
4✔
723
                $this->validateIdentifyMethod($uuid, $user);
4✔
724
        }
725

726
        /**
727
         * @throws LibresignException
728
         */
729
        private function validateSignerStatus(string $uuid): void {
730
                $signRequest = $this->signRequestMapper->getByUuid($uuid);
4✔
731
                $status = $signRequest->getStatusEnum();
4✔
732

733
                if ($status === \OCA\Libresign\Enum\SignRequestStatus::DRAFT) {
4✔
734
                        throw new LibresignException(json_encode([
×
735
                                'action' => JSActions::ACTION_DO_NOTHING,
×
736
                                'errors' => [['message' => $this->l10n->t('You are not allowed to sign this document yet')]],
×
737
                        ]));
×
738
                }
739

740
                if ($status === \OCA\Libresign\Enum\SignRequestStatus::SIGNED) {
4✔
741
                        throw new LibresignException(json_encode([
×
742
                                'action' => JSActions::ACTION_DO_NOTHING,
×
743
                                'errors' => [['message' => $this->l10n->t('Document already signed')]],
×
744
                        ]));
×
745
                }
746
        }
747

748
        public function validateRenewSigner(string $uuid, ?IUser $user = null): void {
749
                $this->validateSignerUuidExists($uuid);
×
750
                $signRequest = $this->signRequestMapper->getByUuid($uuid);
×
751
                $identifyMethods = $this->identifyMethodService->getIdentifyMethodsFromSignRequestId($signRequest->getId());
×
752
                foreach ($identifyMethods as $methods) {
×
753
                        foreach ($methods as $identifyMethod) {
×
754
                                $identifyMethod->validateToRenew($user);
×
755
                        }
756
                }
757
        }
758

759
        private function validateIdentifyMethod(string $uuid, ?IUser $user = null): void {
760
                $signRequest = $this->signRequestMapper->getByUuid($uuid);
4✔
761
                $identifyMethods = $this->identifyMethodService->getIdentifyMethodsFromSignRequestId($signRequest->getId());
4✔
762
                foreach ($identifyMethods as $methods) {
4✔
763
                        foreach ($methods as $identifyMethod) {
4✔
764
                                $identifyMethod->validateToIdentify();
4✔
765
                        }
766
                }
767
        }
768

769
        private function validateSignerUuidExists(string $uuid): void {
770
                $this->validateUuidFormat($uuid);
5✔
771
                try {
772
                        $signRequest = $this->signRequestMapper->getByUuid($uuid);
4✔
773
                        $this->fileMapper->getById($signRequest->getFileId());
4✔
774
                } catch (DoesNotExistException) {
×
775
                        throw new LibresignException(json_encode([
×
776
                                'action' => JSActions::ACTION_DO_NOTHING,
×
777
                                'errors' => [['message' => $this->l10n->t('Invalid UUID')]],
×
778
                        ]));
×
779
                }
780
        }
781

782
        /**
783
         * @throws LibresignException
784
         */
785
        public function validateUuidFormat(string $uuid): void {
786
                if (!$uuid || !preg_match('/^[a-f\d]{8}(-[a-f\d]{4}){4}[a-f\d]{8}$/i', $uuid)) {
7✔
787
                        throw new LibresignException(json_encode([
2✔
788
                                'action' => JSActions::ACTION_DO_NOTHING,
2✔
789
                                'errors' => [['message' => $this->l10n->t('Invalid UUID')]],
2✔
790
                        ]), Http::STATUS_NOT_FOUND);
2✔
791
                }
792
        }
793

794
        public function validateIsSignerOfFile(int $signRequestId, int $fileId): void {
795
                try {
796
                        $this->signRequestMapper->getByFileIdAndSignRequestId($fileId, $signRequestId);
3✔
797
                } catch (\Throwable) {
1✔
798
                        throw new LibresignException($this->l10n->t('Signer not associated to this file'));
1✔
799
                }
800
        }
801

802
        public function validateUserHasNoFileWithThisType(string $uid, string $type): void {
803
                $exists = $this->idDocsMapper->getByUserAndType($uid, $type);
3✔
804
                if ($exists !== null) {
3✔
805
                        throw new LibresignException($this->l10n->t('A file of this type has been associated.'));
1✔
806
                }
807
        }
808

809
        public function canSignWithIdentificationDocumentStatus(?IUser $user, int $status): void {
810
                if ($user && $this->userCanApproveValidationDocuments($user, false)) {
3✔
811
                        return;
×
812
                }
813

814
                $allowedStatus = [
3✔
815
                        FileService::IDENTIFICATION_DOCUMENTS_DISABLED,
3✔
816
                        FileService::IDENTIFICATION_DOCUMENTS_APPROVED,
3✔
817
                ];
3✔
818
                if (!in_array($status, $allowedStatus)) {
3✔
819
                        throw new LibresignException($this->l10n->t('You need to have an approved identification document to sign.'));
×
820
                }
821
        }
822

823
        public function validateCredentials(SignRequest $signRequest, string $identifyMethodName, string $identifyValue, string $token): void {
824
                $this->validateIfIdentifyMethodExists($identifyMethodName);
2✔
825
                if ($signRequest->getSigned()) {
2✔
826
                        throw new LibresignException($this->l10n->t('File already signed.'));
×
827
                }
828
                $identifyMethod = $this->resolveIdentifyMethod($signRequest, $identifyMethodName, $identifyValue);
2✔
829
                $identifyMethod->setCodeSentByUser($token);
2✔
830
                $identifyMethod->validateToSign();
2✔
831
        }
832

833
        private function resolveIdentifyMethod(SignRequest $signRequest, string $methodName, ?string $identifyValue): IIdentifyMethod {
834
                if (!$signRequest->getId()) {
2✔
835
                        return $this->identifyMethodService
×
836
                                ->setCurrentIdentifyMethod()
×
837
                                ->getInstanceOfIdentifyMethod($methodName, $identifyValue);
×
838
                }
839

840
                $methodsList = $this->identifyMethodService->getIdentifyMethodsFromSignRequestId($signRequest->getId());
2✔
841
                $identifyMethod = $this->searchMethodByNameAndValue($methodsList, $methodName, $identifyValue);
2✔
842
                if ($identifyMethod) {
2✔
843
                        return $identifyMethod;
×
844
                }
845

846
                $signMethods = $this->identifyMethodService->getSignMethodsOfIdentifiedFactors($signRequest->getId());
2✔
847
                $identifyMethod = $this->searchMethodByNameAndValue($signMethods, $methodName, $identifyValue);
2✔
848
                if ($identifyMethod) {
2✔
849
                        return $identifyMethod;
×
850
                }
851

852
                if (!empty($methodsList)) {
2✔
853
                        return $this->getFirstAvailableMethod($methodsList);
2✔
854
                }
855

856
                if (!empty($signMethods)) {
×
857
                        return $this->getFirstAvailableMethod($signMethods);
×
858
                }
859

860
                throw new LibresignException($this->l10n->t('Invalid identification method'));
×
861
        }
862

863
        private function searchMethodByNameAndValue(array $methods, string $methodName, ?string $identifyValue): ?IIdentifyMethod {
864
                if (isset($methods[$methodName])) {
2✔
865
                        if ($identifyValue) {
×
866
                                foreach ($methods[$methodName] as $identifyMethod) {
×
867
                                        if (!$identifyMethod instanceof IIdentifyMethod) {
×
868
                                                $identifyMethod = $this->getIdentifyMethodByNameAndValue($methodName, $identifyValue);
×
869
                                        }
870
                                        if ($identifyMethod->getEntity()->getIdentifierValue() === $identifyValue) {
×
871
                                                return $identifyMethod;
×
872
                                        }
873
                                }
874
                        } else {
875
                                $identifyMethod = current($methods[$methodName]);
×
876
                                if (!$identifyMethod instanceof IIdentifyMethod) {
×
877
                                        $identifyMethod = $this->getIdentifyMethodByNameAndValue($methodName, $identifyValue);
×
878
                                }
879
                                return $identifyMethod;
×
880
                        }
881
                }
882

883
                return null;
2✔
884
        }
885

886
        private function getIdentifyMethodByNameAndValue(string $identifyMethodName, ?string $identifyValue): IIdentifyMethod {
887
                return $this->identifyMethodService
×
888
                        ->setCurrentIdentifyMethod()
×
889
                        ->getInstanceOfIdentifyMethod($identifyMethodName, $identifyValue);
×
890
        }
891

892
        private function getFirstAvailableMethod(array $methods): IIdentifyMethod {
893
                foreach ($methods as $methodGroup) {
2✔
894
                        if (!empty($methodGroup)) {
2✔
895
                                return current($methodGroup);
2✔
896
                        }
897
                }
898
                throw new LibresignException($this->l10n->t('Invalid identification method'));
×
899
        }
900

901
        public function validateIfIdentifyMethodExists(string $identifyMethod): void {
902
                if (!in_array($identifyMethod, IdentifyMethodService::IDENTIFY_METHODS)) {
10✔
903
                        // TRANSLATORS When is requested to a person to sign a file, is
904
                        // necessary identify what is the identification method. The
905
                        // identification method is used to define how will be the sign
906
                        // flow.
907
                        throw new LibresignException($this->l10n->t('Invalid identification method'));
2✔
908
                }
909
        }
910

911
        public function validateFileTypeExists(string $type): void {
912
                $profileFileTypes = $this->fileTypeMapper->getTypes();
3✔
913
                if (!array_key_exists($type, $profileFileTypes)) {
3✔
914
                        throw new LibresignException($this->l10n->t('Invalid file type.'));
1✔
915
                }
916
        }
917

918
        public function userCanApproveValidationDocuments(?IUser $user, bool $throw = true): bool {
919
                if ($user == null) {
4✔
920
                        return false;
×
921
                }
922

923
                $authorized = $this->appConfig->getValueArray(Application::APP_ID, 'approval_group', ['admin']);
4✔
924
                if (!$authorized || !is_array($authorized) || empty($authorized)) {
4✔
925
                        $authorized = ['admin'];
×
926
                }
927
                $userGroups = $this->groupManager->getUserGroupIds($user);
4✔
928
                if (!array_intersect($userGroups, $authorized)) {
4✔
929
                        if ($throw) {
3✔
930
                                throw new LibresignException($this->l10n->t('You are not allowed to approve user profile documents.'));
×
931
                        }
932
                        return false;
3✔
933
                }
934
                return true;
1✔
935
        }
936
}
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