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

LibreSign / libresign / 29053227110

09 Jul 2026 10:00PM UTC coverage: 55.077%. First build
29053227110

Pull #7887

github

web-flow
Merge e1981442c into e4d7ad4bb
Pull Request #7887: fix(crl): improve CRL generation performance and reliability

38 of 81 new or added lines in 5 files covered. (46.91%)

13142 of 23861 relevant lines covered (55.08%)

7.41 hits per line

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

46.1
/lib/Controller/PageController.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\Controller;
10

11
use OCA\Libresign\AppInfo\Application;
12
use OCA\Libresign\Db\FileMapper;
13
use OCA\Libresign\Db\SignRequestMapper;
14
use OCA\Libresign\Exception\LibresignException;
15
use OCA\Libresign\Handler\CertificateEngine\CertificateEngineFactory;
16
use OCA\Libresign\Helper\JSActions;
17
use OCA\Libresign\Helper\ValidateHelper;
18
use OCA\Libresign\Middleware\Attribute\PrivateValidation;
19
use OCA\Libresign\Middleware\Attribute\RequireSetupOk;
20
use OCA\Libresign\Middleware\Attribute\RequireSignRequestUuid;
21
use OCA\Libresign\Service\AccountService;
22
use OCA\Libresign\Service\File\FileListService;
23
use OCA\Libresign\Service\FileService;
24
use OCA\Libresign\Service\IdentifyMethod\SignatureMethod\TokenService;
25
use OCA\Libresign\Service\IdentifyMethodService;
26
use OCA\Libresign\Service\Policy\PolicyAuthorizationService;
27
use OCA\Libresign\Service\Policy\PolicyService;
28
use OCA\Libresign\Service\RequestSignatureService;
29
use OCA\Libresign\Service\SessionService;
30
use OCA\Libresign\Service\SignerElementsService;
31
use OCA\Libresign\Service\SignFileService;
32
use OCA\Viewer\Event\LoadViewer;
33
use OCP\AppFramework\Db\DoesNotExistException;
34
use OCP\AppFramework\Http;
35
use OCP\AppFramework\Http\Attribute\AnonRateLimit;
36
use OCP\AppFramework\Http\Attribute\FrontpageRoute;
37
use OCP\AppFramework\Http\Attribute\NoAdminRequired;
38
use OCP\AppFramework\Http\Attribute\NoCSRFRequired;
39
use OCP\AppFramework\Http\Attribute\OpenAPI;
40
use OCP\AppFramework\Http\Attribute\PublicPage;
41
use OCP\AppFramework\Http\ContentSecurityPolicy;
42
use OCP\AppFramework\Http\DataResponse;
43
use OCP\AppFramework\Http\FileDisplayResponse;
44
use OCP\AppFramework\Http\RedirectResponse;
45
use OCP\AppFramework\Http\TemplateResponse;
46
use OCP\AppFramework\Services\IInitialState;
47
use OCP\EventDispatcher\IEventDispatcher;
48
use OCP\IAppConfig;
49
use OCP\IL10N;
50
use OCP\IRequest;
51
use OCP\IURLGenerator;
52
use OCP\IUserSession;
53
use OCP\Util;
54
use Psr\Log\LoggerInterface;
55

56
#[OpenAPI(scope: OpenAPI::SCOPE_IGNORE)]
57
class PageController extends AEnvironmentPageAwareController {
58
        public function __construct(
59
                IRequest $request,
60
                protected IUserSession $userSession,
61
                private SessionService $sessionService,
62
                private IInitialState $initialState,
63
                private AccountService $accountService,
64
                private CertificateEngineFactory $certificateEngineFactory,
65
                protected SignFileService $signFileService,
66
                protected RequestSignatureService $requestSignatureService,
67
                private PolicyService $policyService,
68
                private PolicyAuthorizationService $policyAuthorizationService,
69
                private SignerElementsService $signerElementsService,
70
                protected IL10N $l10n,
71
                private IdentifyMethodService $identifyMethodService,
72
                private IAppConfig $appConfig,
73
                private FileService $fileService,
74
                private FileListService $fileListService,
75
                private FileMapper $fileMapper,
76
                private SignRequestMapper $signRequestMapper,
77
                private LoggerInterface $logger,
78
                private ValidateHelper $validateHelper,
79
                private IEventDispatcher $eventDispatcher,
80
                private IURLGenerator $urlGenerator,
81
        ) {
82
                parent::__construct(
6✔
83
                        request: $request,
6✔
84
                        signFileService: $signFileService,
6✔
85
                        l10n: $l10n,
6✔
86
                        userSession: $userSession,
6✔
87
                );
6✔
88
        }
89

90
        /**
91
         * Index page
92
         *
93
         * @return TemplateResponse<Http::STATUS_OK, array{}>
94
         *
95
         * 200: OK
96
         */
97
        #[NoAdminRequired]
98
        #[NoCSRFRequired]
99
        #[RequireSetupOk(template: 'main')]
100
        #[FrontpageRoute(verb: 'GET', url: '/')]
101
        public function index(): TemplateResponse {
102
                $this->initialState->provideInitialState('config', $this->accountService->getConfig($this->userSession->getUser()));
1✔
103
                $this->initialState->provideInitialState('filters', $this->accountService->getConfigFilters($this->userSession->getUser()));
1✔
104
                $this->initialState->provideInitialState('sorting', $this->accountService->getConfigSorting($this->userSession->getUser()));
1✔
105
                $this->initialState->provideInitialState('certificate_engine', $this->accountService->getCertificateEngineName());
1✔
106

107
                try {
108
                        $this->validateHelper->canRequestSign($this->userSession->getUser());
1✔
109
                        $this->initialState->provideInitialState('can_request_sign', true);
1✔
110
                } catch (LibresignException) {
×
111
                        $this->initialState->provideInitialState('can_request_sign', false);
×
112
                }
113

114
                $this->provideSignerSignatues();
1✔
115
                $this->initialState->provideInitialState('effective_policies', [
1✔
116
                        'policies' => $this->policyService->resolveKnownPolicyStates(),
1✔
117
                ]);
1✔
118

119
                Util::addScript(Application::APP_ID, 'libresign-main');
1✔
120
                Util::addStyle(Application::APP_ID, 'libresign-main');
1✔
121

122
                if (class_exists(LoadViewer::class)) {
1✔
123
                        $this->eventDispatcher->dispatchTyped(new LoadViewer());
×
124
                }
125

126
                $response = new TemplateResponse(Application::APP_ID, 'main');
1✔
127

128
                $policy = new ContentSecurityPolicy();
1✔
129
                $policy->addAllowedFrameDomain('\'self\'');
1✔
130
                $policy->addAllowedWorkerSrcDomain("'self'");
1✔
131
                $response->setContentSecurityPolicy($policy);
1✔
132

133
                return $response;
1✔
134
        }
135

136
        /**
137
         * Index page to authenticated users
138
         *
139
         * This router is used to be possible render pages with /f/, is a
140
         * workaround at frontend side to identify pages with authenticated accounts
141
         *
142
         * @return TemplateResponse<Http::STATUS_OK, array{}>
143
         *
144
         * 200: OK
145
         */
146
        #[NoAdminRequired]
147
        #[NoCSRFRequired]
148
        #[RequireSetupOk(template: 'main')]
149
        #[FrontpageRoute(verb: 'GET', url: '/f/')]
150
        public function indexF(): TemplateResponse {
151
                return $this->index();
×
152
        }
153

154
        /**
155
         * Incomplete page
156
         *
157
         * @return TemplateResponse|RedirectResponse
158
         *
159
         * 200: OK
160
         * 303: Redirected when setup is already complete
161
         */
162
        #[NoAdminRequired]
163
        #[NoCSRFRequired]
164
        #[FrontpageRoute(verb: 'GET', url: '/f/incomplete')]
165
        public function incomplete(): TemplateResponse|RedirectResponse {
166
                if ($this->isSetupComplete()) {
1✔
167
                        return new RedirectResponse(
1✔
168
                                $this->urlGenerator->linkToRouteAbsolute('libresign.page.indexF'),
1✔
169
                        );
1✔
170
                }
171

172
                return $this->renderIncompletePage();
×
173
        }
174

175
        /**
176
         * Incomplete page in full screen
177
         *
178
         * @return TemplateResponse|RedirectResponse
179
         *
180
         * 200: OK
181
         * 303: Redirected when setup is already complete
182
         */
183
        #[PublicPage]
184
        #[NoCSRFRequired]
185
        #[FrontpageRoute(verb: 'GET', url: '/p/incomplete')]
186
        public function incompleteP(): TemplateResponse|RedirectResponse {
187
                if ($this->isSetupComplete()) {
1✔
188
                        return new RedirectResponse(
1✔
189
                                $this->urlGenerator->linkToRouteAbsolute('libresign.page.index'),
1✔
190
                        );
1✔
191
                }
192

193
                return $this->renderIncompletePage(publicPage: true);
×
194
        }
195

196
        private function renderIncompletePage(bool $publicPage = false): TemplateResponse {
197
                Util::addScript(Application::APP_ID, 'libresign-main');
×
198
                Util::addStyle(Application::APP_ID, 'libresign-main');
×
199

200
                if ($publicPage) {
×
201
                        return new TemplateResponse(Application::APP_ID, 'main', [], TemplateResponse::RENDER_AS_BASE);
×
202
                }
203

204
                return new TemplateResponse(Application::APP_ID, 'main');
×
205
        }
206

207
        private function isSetupComplete(): bool {
208
                return $this->certificateEngineFactory->getEngine()->isSetupOk();
2✔
209
        }
210

211
        /**
212
         * Main page to authenticated signer with a path
213
         *
214
         * The path is used only by frontend
215
         *
216
         * @param string $path The path that was sent from frontend
217
         * @return TemplateResponse<Http::STATUS_OK, array{}>|RedirectResponse<Http::STATUS_SEE_OTHER, array{}>
218
         *
219
         * 200: OK
220
         * 303: Redirected when the current actor cannot access the requested internal page
221
         */
222
        #[NoAdminRequired]
223
        #[NoCSRFRequired]
224
        #[RequireSetupOk(template: 'main')]
225
        #[FrontpageRoute(verb: 'GET', url: '/f/{path}', requirements: ['path' => '.+'])]
226
        public function indexFPath(string $path): TemplateResponse|RedirectResponse {
227
                if ($this->isPoliciesWorkbenchPath($path) && !$this->canAccessPoliciesWorkbench()) {
1✔
228
                        return new RedirectResponse(
1✔
229
                                $this->urlGenerator->linkToRouteAbsolute('libresign.page.indexF'),
1✔
230
                        );
1✔
231
                }
232

233
                if (preg_match('/validation\/(?<uuid>[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12})$/', $path, $matches)) {
×
234
                        $signRequest = null;
×
235

236
                        try {
237
                                $this->fileService->setFileByUuid($matches['uuid']);
×
238
                        } catch (LibresignException) {
×
239
                                try {
240
                                        $this->fileService->setFileBySignerUuid($matches['uuid']);
×
241
                                        $signRequest = $this->signRequestMapper->getBySignerUuidAndUserId($matches['uuid']);
×
242
                                } catch (LibresignException) {
×
243
                                        throw new LibresignException(json_encode([
×
244
                                                'action' => JSActions::ACTION_DO_NOTHING,
×
245
                                                'errors' => [['message' => $this->l10n->t('Invalid UUID')]],
×
246
                                        ]), Http::STATUS_NOT_FOUND);
×
247
                                }
248
                        }
249

250
                        if ($signRequest) {
×
251
                                $this->fileService->setSignRequest($signRequest);
×
252
                        }
253

NEW
254
                        $this->provideDeferredValidationFileInfo($matches['uuid']);
×
255
                } elseif (preg_match('/sign\/(?<uuid>[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12})/', $path, $matches)) {
×
256
                        try {
257
                                $signRequest = $this->signFileService->getSignRequestByUuid($matches['uuid']);
×
258
                                if ($signRequest->getStatusEnum() === \OCA\Libresign\Enum\SignRequestStatus::SIGNED) {
×
259
                                        $file = $this->signFileService->getFile($signRequest->getFileId());
×
260
                                        $redirectUrl = $this->urlGenerator->linkToRouteAbsolute('libresign.page.indexFPath', [
×
261
                                                'path' => 'validation/' . $file->getUuid(),
×
262
                                        ]);
×
263
                                        throw new LibresignException(json_encode([
×
264
                                                'action' => JSActions::ACTION_REDIRECT,
×
265
                                                'redirect' => $redirectUrl,
×
266
                                        ]), Http::STATUS_SEE_OTHER);
×
267
                                }
268
                                $file = $this->fileService
×
269
                                        ->setFile($this->signFileService->getFile($signRequest->getFileId()))
×
270
                                        ->setSignRequest($signRequest)
×
271
                                        ->setMe($this->userSession->getUser())
×
272
                                        ->showSettings()
×
273
                                        ->toArray();
×
274
                                $this->initialState->provideInitialState('needIdentificationDocuments', $file['settings']['needIdentificationDocuments'] ?? false);
×
275
                                $this->initialState->provideInitialState('identificationDocumentsWaitingApproval', $file['settings']['identificationDocumentsWaitingApproval'] ?? false);
×
276
                        } catch (LibresignException $e) {
×
277
                                throw $e;
×
278
                        } catch (\Throwable) {
×
279
                                throw new LibresignException(json_encode([
×
280
                                        'action' => JSActions::ACTION_DO_NOTHING,
×
281
                                        'errors' => [['message' => $this->l10n->t('Invalid UUID')]],
×
282
                                ]), Http::STATUS_NOT_FOUND);
×
283
                        }
284
                }
285
                return $this->index();
×
286
        }
287

288
        private function canAccessPoliciesWorkbench(): bool {
289
                $user = $this->userSession->getUser();
1✔
290
                if ($user === null) {
1✔
291
                        return false;
×
292
                }
293

294
                return $this->policyAuthorizationService->canUserManageGroupPolicies($user);
1✔
295
        }
296

297
        private function isPoliciesWorkbenchPath(string $path): bool {
298
                return preg_match('/^policies(?:\/|$)/', $path) === 1;
1✔
299
        }
300

301
        /**
302
         * Sign page to authenticated signer
303
         *
304
         * @param string $uuid Sign request uuid
305
         * @return TemplateResponse<Http::STATUS_OK, array{}>
306
         *
307
         * 200: OK
308
         */
309
        #[PrivateValidation(allowValidSignRequestUuid: false)]
310
        #[NoAdminRequired]
311
        #[NoCSRFRequired]
312
        #[RequireSetupOk]
313
        #[PublicPage]
314
        #[RequireSignRequestUuid(redirectIfSignedToValidation: true, allowIdDocs: true)]
315
        #[FrontpageRoute(verb: 'GET', url: '/f/sign/{uuid}')]
316
        public function signF(string $uuid): TemplateResponse {
317
                $this->initialState->provideInitialState('action', JSActions::ACTION_SIGN_INTERNAL);
×
318
                return $this->index();
×
319
        }
320

321
        /**
322
         * Sign page to authenticated signer with the path of file
323
         *
324
         * The path is used only by frontend
325
         *
326
         * @param string $uuid Sign request uuid
327
         * @return TemplateResponse<Http::STATUS_OK, array{}>
328
         *
329
         * 200: OK
330
         */
331
        #[NoAdminRequired]
332
        #[NoCSRFRequired]
333
        #[RequireSetupOk]
334
        #[PublicPage]
335
        #[RequireSignRequestUuid(redirectIfSignedToValidation: true, allowIdDocs: true)]
336
        #[FrontpageRoute(verb: 'GET', url: '/f/sign/{uuid}/{path}', requirements: ['path' => '.+'])]
337
        public function signFPath(string $uuid): TemplateResponse {
338
                $this->initialState->provideInitialState('action', JSActions::ACTION_SIGN_INTERNAL);
×
339
                return $this->index();
×
340
        }
341

342
        /**
343
         * Sign page to unauthenticated signer
344
         *
345
         * The path is used only by frontend
346
         *
347
         * @param string $uuid Sign request uuid
348
         * @return TemplateResponse<Http::STATUS_OK, array{}>
349
         *
350
         * 200: OK
351
         */
352
        #[NoAdminRequired]
353
        #[NoCSRFRequired]
354
        #[RequireSetupOk]
355
        #[PublicPage]
356
        #[PrivateValidation(allowValidSignRequestUuid: true)]
357
        #[RequireSignRequestUuid(redirectIfSignedToValidation: true, allowIdDocs: true)]
358
        #[FrontpageRoute(verb: 'GET', url: '/p/sign/{uuid}/{path}', requirements: ['path' => '.+'])]
359
        public function signPPath(string $uuid): TemplateResponse {
360
                return $this->sign($uuid);
×
361
        }
362

363
        /**
364
         * Sign page to unauthenticated signer
365
         *
366
         * The path is used only by frontend
367
         *
368
         * @param string $uuid Sign request uuid
369
         * @return TemplateResponse<Http::STATUS_OK, array{}>
370
         *
371
         * 200: OK
372
         */
373
        #[NoAdminRequired]
374
        #[NoCSRFRequired]
375
        #[RequireSetupOk]
376
        #[PublicPage]
377
        #[PrivateValidation(allowValidSignRequestUuid: true)]
378
        #[RequireSignRequestUuid(redirectIfSignedToValidation: true, allowIdDocs: true)]
379
        #[FrontpageRoute(verb: 'GET', url: '/p/sign/{uuid}')]
380
        public function sign(string $uuid): TemplateResponse {
381
                $this->initialState->provideInitialState('action', JSActions::ACTION_SIGN);
1✔
382
                $config = $this->accountService->getConfig($this->userSession->getUser());
1✔
383
                $this->initialState->provideInitialState('filename', $this->getFileEntity()->getName());
1✔
384
                $file = $this->fileService
1✔
385
                        ->setFile($this->getFileEntity())
1✔
386
                        ->setSignRequest($this->getSignRequestEntity())
1✔
387
                        ->setHost($this->request->getServerHost())
1✔
388
                        ->setMe($this->userSession->getUser())
1✔
389
                        ->setSignerIdentified()
1✔
390
                        ->setIdentifyMethodId($this->sessionService->getIdentifyMethodId())
1✔
391
                        ->showVisibleElements()
1✔
392
                        ->showSigners()
1✔
393
                        ->showSettings()
1✔
394
                        ->toArray();
1✔
395
                $this->initialState->provideInitialState('config', array_merge($config, [
1✔
396
                        'identificationDocumentsFlow' => $file['settings']['needIdentificationDocuments'] ?? false,
1✔
397
                ]));
1✔
398
                $this->initialState->provideInitialState('id', $file['id']);
1✔
399
                $this->initialState->provideInitialState('nodeId', $file['nodeId']);
1✔
400
                $this->initialState->provideInitialState('needIdentificationDocuments', $file['settings']['needIdentificationDocuments'] ?? false);
1✔
401
                $this->initialState->provideInitialState('identificationDocumentsWaitingApproval', $file['settings']['identificationDocumentsWaitingApproval'] ?? false);
1✔
402
                $this->initialState->provideInitialState('status', $file['status']);
1✔
403
                $this->initialState->provideInitialState('statusText', $file['statusText']);
1✔
404
                $this->initialState->provideInitialState('signers', $file['signers']);
1✔
405
                $this->initialState->provideInitialState('visibleElements', $file['visibleElements'] ?? []);
1✔
406
                $this->initialState->provideInitialState('sign_request_uuid', $uuid);
1✔
407
                $this->provideSignerSignatues();
1✔
408
                $this->initialState->provideInitialState('token_length', TokenService::TOKEN_LENGTH);
1✔
409
                $this->initialState->provideInitialState('description', $this->getSignRequestEntity()->getDescription() ?? '');
1✔
410
                if ($this->getFileEntity()->getNodeType() === 'envelope') {
1✔
411
                        $this->initialState->provideInitialState('pdfs', []);
×
412
                        $this->initialState->provideInitialState('envelopeFiles', $this->getEnvelopeChildFiles());
×
413
                } else {
414
                        $this->initialState->provideInitialState('pdfs', $this->getPdfUrls());
1✔
415
                        $this->initialState->provideInitialState('envelopeFiles', []);
1✔
416
                }
417
                $this->initialState->provideInitialState('nodeId', $this->getFileEntity()->getNodeId());
1✔
418
                $this->initialState->provideInitialState('nodeType', $this->getFileEntity()->getNodeType());
1✔
419

420
                Util::addScript(Application::APP_ID, 'libresign-external');
1✔
421
                Util::addStyle(Application::APP_ID, 'libresign-external');
1✔
422
                if (class_exists(LoadViewer::class)) {
1✔
423
                        $this->eventDispatcher->dispatchTyped(new LoadViewer());
×
424
                }
425
                $response = new TemplateResponse(Application::APP_ID, 'external', [], TemplateResponse::RENDER_AS_BASE);
1✔
426

427
                $policy = new ContentSecurityPolicy();
1✔
428
                $policy->addAllowedWorkerSrcDomain("'self'");
1✔
429
                $response->setContentSecurityPolicy($policy);
1✔
430

431
                return $response;
1✔
432
        }
433

434
        private function provideSignerSignatues(): void {
435
                $signatures = [];
2✔
436
                if ($this->userSession->getUser()) {
2✔
437
                        $signatures = $this->signerElementsService->getUserElements($this->userSession->getUser()->getUID());
2✔
438
                } else {
439
                        $signatures = $this->signerElementsService->getElementsFromSessionAsArray();
×
440
                }
441
                $this->initialState->provideInitialState('user_signatures', $signatures);
2✔
442
        }
443

444
        /**
445
         * @return string[] Array of PDF URLs
446
         */
447
        private function getPdfUrls(): array {
448
                return $this->signFileService->getPdfUrlsForSigning(
1✔
449
                        $this->getFileEntity(),
1✔
450
                        $this->getSignRequestEntity()
1✔
451
                );
1✔
452
        }
453

454
        private function getEnvelopeChildFiles(): array {
455
                $parentFileId = $this->getFileEntity()->getId();
×
456
                $currentSignRequest = $this->getSignRequestEntity();
×
457
                $childFiles = $this->fileMapper->getChildrenFiles($parentFileId);
×
458
                $childSignRequests = $this->signRequestMapper->getByEnvelopeChildrenAndIdentifyMethod(
×
459
                        $parentFileId,
×
460
                        $currentSignRequest->getId()
×
461
                );
×
462

463
                $signRequestsByFileId = [];
×
464
                foreach ($childSignRequests as $childSignRequest) {
×
465
                        $signRequestsByFileId[$childSignRequest->getFileId()] = true;
×
466
                }
467

468
                foreach ($childFiles as $childFile) {
×
469
                        if (!isset($signRequestsByFileId[$childFile->getId()])) {
×
470
                                $this->logger->warning('Missing sign request for envelope child file', [
×
471
                                        'parentFileId' => $parentFileId,
×
472
                                        'childFileId' => $childFile->getId(),
×
473
                                        'signRequestId' => $currentSignRequest->getId(),
×
474
                                ]);
×
475
                        }
476
                }
477

478
                return $this->fileListService->formatEnvelopeChildFilesForSignRequest(
×
479
                        $childFiles,
×
480
                        $childSignRequests,
×
481
                        $currentSignRequest,
×
482
                );
×
483
        }
484

485
        /**
486
         * Use UUID of file to get PDF
487
         *
488
         * @param string $uuid File uuid
489
         * @return FileDisplayResponse<Http::STATUS_OK, array{Content-Type: string}>|DataResponse<Http::STATUS_NOT_FOUND, array<empty>, array{}>
490
         *
491
         * 200: OK
492
         * 401: Validation page not accessible if unauthenticated
493
         * 404: File not found
494
         */
495
        #[PrivateValidation(allowValidSignRequestUuid: false)]
496
        #[NoAdminRequired]
497
        #[NoCSRFRequired]
498
        #[RequireSetupOk]
499
        #[PublicPage]
500
        #[AnonRateLimit(limit: 300, period: 60)]
501
        #[FrontpageRoute(verb: 'GET', url: '/p/pdf/{uuid}')]
502
        public function getPdf($uuid) {
503
                try {
504
                        $file = $this->accountService->getPdfByUuid($uuid);
×
505
                } catch (DoesNotExistException) {
×
506
                        return new DataResponse([], Http::STATUS_NOT_FOUND);
×
507
                }
508

509
                return new FileDisplayResponse($file, Http::STATUS_OK, ['Content-Type' => $file->getMimeType()]);
×
510
        }
511

512
        /**
513
         * Use UUID of user to get PDF
514
         *
515
         * @param string $uuid Sign request uuid
516
         * @return FileDisplayResponse<Http::STATUS_OK, array{Content-Type: string}>
517
         *
518
         * 200: OK
519
         * 401: Validation page not accessible if unauthenticated without a valid sign request UUID
520
         */
521
        #[PrivateValidation(allowValidSignRequestUuid: true)]
522
        #[NoAdminRequired]
523
        #[NoCSRFRequired]
524
        #[RequireSignRequestUuid(allowIdDocs: true)]
525
        #[PublicPage]
526
        #[RequireSetupOk]
527
        #[AnonRateLimit(limit: 300, period: 60)]
528
        #[FrontpageRoute(verb: 'GET', url: '/pdf/{uuid}')]
529
        public function getPdfFile($uuid): FileDisplayResponse {
530
                $files = $this->getNextcloudFiles();
×
531
                if (empty($files)) {
×
532
                        throw new LibresignException(json_encode([
×
533
                                'action' => JSActions::ACTION_DO_NOTHING,
×
534
                                'errors' => [['message' => $this->l10n->t('File not found')]],
×
535
                        ]), Http::STATUS_NOT_FOUND);
×
536
                }
537
                $file = current($files);
×
538
                return new FileDisplayResponse($file, Http::STATUS_OK, ['Content-Type' => $file->getMimeType()]);
×
539
        }
540

541
        /**
542
         * Show validation page
543
         *
544
         * @return TemplateResponse<Http::STATUS_OK, array{}>
545
         *
546
         * 200: OK
547
         * 401: Validation page not accessible if unauthenticated
548
         */
549
        #[PrivateValidation(allowValidSignRequestUuid: false)]
550
        #[NoAdminRequired]
551
        #[NoCSRFRequired]
552
        #[RequireSetupOk(template: 'validation')]
553
        #[PublicPage]
554
        #[AnonRateLimit(limit: 30, period: 60)]
555
        #[FrontpageRoute(verb: 'GET', url: '/p/validation')]
556
        public function validation(): TemplateResponse {
557
                if ($this->getFileEntity()) {
×
558
                        $this->initialState->provideInitialState('config',
×
559
                                $this->accountService->getConfig($this->userSession->getUser())
×
560
                        );
×
561
                        $this->initialState->provideInitialState('file', [
×
562
                                'uuid' => $this->getFileEntity()?->getUuid(),
×
563
                                'description' => $this->getSignRequestEntity()?->getDescription(),
×
564
                        ]);
×
565
                        $this->initialState->provideInitialState('filename', $this->getFileEntity()?->getName());
×
566
                        $this->initialState->provideInitialState('pdfs', $this->getPdfUrls());
×
567
                        $this->initialState->provideInitialState('signer',
×
568
                                $this->signFileService->getSignerData(
×
569
                                        $this->userSession->getUser(),
×
570
                                        $this->getSignRequestEntity(),
×
571
                                )
×
572
                        );
×
573
                }
574

575
                Util::addScript(Application::APP_ID, 'libresign-validation');
×
576
                Util::addStyle(Application::APP_ID, 'libresign-validation');
×
577
                $response = new TemplateResponse(Application::APP_ID, 'validation', [], TemplateResponse::RENDER_AS_BASE);
×
578

579
                return $response;
×
580
        }
581

582
        /**
583
         * Show validation page
584
         *
585
         * The path is used only by frontend
586
         *
587
         * @param string $uuid Sign request uuid
588
         * @return TemplateResponse<Http::STATUS_OK, array{}>
589
         *
590
         * 200: OK
591
         * 303: Redirected to validation page
592
         * 401: Validation page not accessible if unauthenticated
593
         */
594
        #[PrivateValidation(allowValidSignRequestUuid: false)]
595
        #[NoAdminRequired]
596
        #[NoCSRFRequired]
597
        #[RequireSetupOk]
598
        #[PublicPage]
599
        #[AnonRateLimit(limit: 30, period: 60)]
600
        #[FrontpageRoute(verb: 'GET', url: '/validation/{uuid}')]
601
        public function validationFileWithShortUrl(): TemplateResponse {
602
                return $this->validationFilePublic($this->request->getParam('uuid'));
×
603
        }
604

605
        /**
606
         * Show validation page
607
         *
608
         * @param string $uuid Sign request uuid
609
         * @return TemplateResponse<Http::STATUS_OK, array{}>
610
         *
611
         * 200: OK
612
         */
613
        #[NoAdminRequired]
614
        #[NoCSRFRequired]
615
        #[RequireSetupOk(template: 'main')]
616
        #[PublicPage]
617
        #[RequireSignRequestUuid]
618
        #[FrontpageRoute(verb: 'GET', url: '/reset-password')]
619
        public function resetPassword(): TemplateResponse {
620
                $this->initialState->provideInitialState('config',
×
621
                        $this->accountService->getConfig($this->userSession->getUser())
×
622
                );
×
623

624
                Util::addScript(Application::APP_ID, 'libresign-main');
×
625
                Util::addStyle(Application::APP_ID, 'libresign-main');
×
626
                $response = new TemplateResponse(Application::APP_ID, 'reset_password');
×
627

628
                return $response;
×
629
        }
630

631
        /**
632
         * Public page to show validation for a specific file UUID
633
         *
634
         * @param string $uuid File uuid
635
         * @return TemplateResponse<Http::STATUS_OK, array{}>
636
         *
637
         * 200: OK
638
         * 401: Validation page not accessible if unauthenticated
639
         */
640
        #[PrivateValidation(allowValidSignRequestUuid: false)]
641
        #[NoAdminRequired]
642
        #[NoCSRFRequired]
643
        #[PublicPage]
644
        #[AnonRateLimit(limit: 30, period: 60)]
645
        #[FrontpageRoute(verb: 'GET', url: '/p/validation/{uuid}')]
646
        public function validationFilePublic(string $uuid): TemplateResponse {
647
                $signRequest = null;
1✔
648
                try {
649
                        $this->signFileService->getFileByUuid($uuid);
1✔
650
                        $this->fileService->setFileByUuid($uuid);
1✔
651
                } catch (DoesNotExistException) {
×
652
                        try {
653
                                $signRequest = $this->signFileService->getSignRequestByUuid($uuid);
×
654
                                $libresignFile = $this->signFileService->getFile($signRequest->getFileId());
×
655
                                $this->fileService->setFile($libresignFile);
×
656
                        } catch (DoesNotExistException) {
×
657
                                $this->initialState->provideInitialState('action', JSActions::ACTION_DO_NOTHING);
×
658
                                $this->initialState->provideInitialState('errors', [['message' => $this->l10n->t('Invalid UUID')]]);
×
659
                        }
660
                }
661
                if ($this->userSession->isLoggedIn()) {
1✔
662
                        $this->initialState->provideInitialState('config',
×
663
                                $this->accountService->getConfig($this->userSession->getUser())
×
664
                        );
×
665
                        $this->fileService->setMe($this->userSession->getUser());
×
666
                } else {
667
                        $this->initialState->provideInitialState('config',
1✔
668
                                $this->accountService->getConfig()
1✔
669
                        );
1✔
670
                }
671

672
                if ($signRequest) {
1✔
673
                        $this->fileService->setSignRequest($signRequest);
×
674
                }
675

676
                $fileInfo = $this->fileService
1✔
677
                        ->setIdentifyMethodId($this->sessionService->getIdentifyMethodId())
1✔
678
                        ->setHost($this->request->getServerHost())
1✔
679
                        ->showVisibleElements()
1✔
680
                        ->showSigners()
1✔
681
                        ->showSettings()
1✔
682
                        ->showMessages()
1✔
683
                        ->showValidateFile()
1✔
684
                        ->toArray();
1✔
685

686
                $requesterUserId = null;
1✔
687
                $requestedBy = $fileInfo['requested_by'] ?? null;
1✔
688
                if (is_array($requestedBy) && is_string($requestedBy['userId'] ?? null)) {
1✔
689
                        $requestedByUserId = trim($requestedBy['userId']);
1✔
690
                        $requesterUserId = $requestedByUserId !== '' ? $requestedByUserId : null;
1✔
691
                }
692

693
                $this->initialState->provideInitialState('effective_policies', [
1✔
694
                        'policies' => $requesterUserId !== null
1✔
695
                                ? $this->policyService->resolveKnownPolicyStatesForUserIdWithoutUserScope($requesterUserId)
1✔
696
                                : $this->policyService->resolveKnownPolicyStates(),
1✔
697
                ]);
1✔
698

699
                $this->initialState->provideInitialState('file_info', $fileInfo);
1✔
700

701
                Util::addScript(Application::APP_ID, 'libresign-validation');
1✔
702
                if (class_exists(LoadViewer::class)) {
1✔
703
                        $this->eventDispatcher->dispatchTyped(new LoadViewer());
×
704
                }
705
                $response = new TemplateResponse(Application::APP_ID, 'validation', [], TemplateResponse::RENDER_AS_BASE);
1✔
706

707
                return $response;
1✔
708
        }
709

710
        /**
711
         * Provide a lightweight placeholder for file_info initial state.
712
         *
713
         * The validation page SPA fetches fresh data via the validate API on mount,
714
         * so the heavy server-side preload is redundant. This method provides only
715
         * the UUID so the frontend can issue its API call with the correct identifier.
716
         */
717
        private function provideDeferredValidationFileInfo(string $uuid): void {
NEW
718
                $this->initialState->provideInitialState('file_info', [
×
NEW
719
                        'uuid' => $uuid,
×
NEW
720
                        'name' => '',
×
NEW
721
                        'files' => [],
×
NEW
722
                        'filesCount' => 0,
×
NEW
723
                        'signers' => [],
×
NEW
724
                        'messages' => [],
×
NEW
725
                ]);
×
726
        }
727
}
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