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

LibreSign / libresign / 32993735329

26 Aug 2026 05:19PM UTC coverage: 68.022% (+22.7%) from 45.341%
32993735329

push

github

web-flow
Merge pull request #8072 from LibreSign/chore/say-hello-to-nextcloud36

chore: say hello to Nextcloud 36

16292 of 23951 relevant lines covered (68.02%)

11.0 hits per line

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

15.18
/lib/Controller/SignFileController.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\File;
13
use OCA\Libresign\Db\SignRequest;
14
use OCA\Libresign\Db\SignRequestMapper;
15
use OCA\Libresign\Exception\LibresignException;
16
use OCA\Libresign\Handler\SigningErrorHandler;
17
use OCA\Libresign\Helper\JSActions;
18
use OCA\Libresign\Helper\ValidateHelper;
19
use OCA\Libresign\Middleware\Attribute\CanSignRequestUuid;
20
use OCA\Libresign\Middleware\Attribute\RequireManager;
21
use OCA\Libresign\Middleware\Attribute\RequireSigner;
22
use OCA\Libresign\ResponseDefinitions;
23
use OCA\Libresign\Service\AsyncSigningService;
24
use OCA\Libresign\Service\File\SettingsLoader;
25
use OCA\Libresign\Service\FileService;
26
use OCA\Libresign\Service\IdentifyMethodService;
27
use OCA\Libresign\Service\RequestMetadataService;
28
use OCA\Libresign\Service\SignFileService;
29
use OCA\Libresign\Service\Worker\WorkerHealthService;
30
use OCP\AppFramework\Http;
31
use OCP\AppFramework\Http\Attribute\ApiRoute;
32
use OCP\AppFramework\Http\Attribute\NoAdminRequired;
33
use OCP\AppFramework\Http\Attribute\NoCSRFRequired;
34
use OCP\AppFramework\Http\Attribute\OpenAPI;
35
use OCP\AppFramework\Http\Attribute\PublicPage;
36
use OCP\AppFramework\Http\DataResponse;
37
use OCP\IL10N;
38
use OCP\IRequest;
39
use OCP\IUser;
40
use OCP\IUserSession;
41

42
/**
43
 * @psalm-import-type LibresignMessageResponse from ResponseDefinitions
44
 * @psalm-import-type LibresignSignActionErrorResponse from ResponseDefinitions
45
 * @psalm-import-type LibresignSignActionResponse from ResponseDefinitions
46
 */
47

48
class SignFileController extends AEnvironmentAwareController implements ISignatureUuid {
49
        use LibresignTrait;
50
        public function __construct(
51
                IRequest $request,
52
                protected IL10N $l10n,
53
                private SignRequestMapper $signRequestMapper,
54
                protected IUserSession $userSession,
55
                private ValidateHelper $validateHelper,
56
                protected SignFileService $signFileService,
57
                private IdentifyMethodService $identifyMethodService,
58
                private FileService $fileService,
59
                private SettingsLoader $settingsLoader,
60
                private WorkerHealthService $workerHealthService,
61
                private AsyncSigningService $asyncSigningService,
62
                private RequestMetadataService $requestMetadataService,
63
                private SigningErrorHandler $errorHandler,
64
        ) {
65
                parent::__construct(Application::APP_ID, $request);
6✔
66
        }
67

68
        /**
69
         * Sign a file using file Id
70
         *
71
         * @param int $fileId Id of LibreSign file
72
         * @param string $method Signature method
73
         * @param array<string, mixed> $elements List of visible elements
74
         * @param string $identifyValue Identify value
75
         * @param string $token Token, commonly send by email
76
         * @param bool $async Execute signing asynchronously when possible
77
         * @return DataResponse<Http::STATUS_OK, LibresignSignActionResponse, array{}>|DataResponse<Http::STATUS_UNPROCESSABLE_ENTITY, LibresignSignActionErrorResponse, array{}>
78
         *
79
         * 200: OK
80
         * 404: Invalid data
81
         * 422: Error
82
         */
83
        #[NoAdminRequired]
84
        #[NoCSRFRequired]
85
        #[RequireManager]
86
        #[PublicPage]
87
        #[OpenAPI(tags: ['signing'])]
88
        #[ApiRoute(verb: 'POST', url: '/api/{apiVersion}/sign/file_id/{fileId}', requirements: ['apiVersion' => '(v1)'])]
89
        public function signByFileId(int $fileId, string $method, array $elements = [], string $identifyValue = '', string $token = '', bool $async = false): DataResponse {
90
                return $this->sign($method, $elements, $identifyValue, $token, $fileId, null, $async);
1✔
91
        }
92

93
        /**
94
         * Sign a file using file UUID
95
         *
96
         * @param string $uuid UUID of LibreSign file
97
         * @param string $method Signature method
98
         * @param array<string, mixed> $elements List of visible elements
99
         * @param string $identifyValue Identify value
100
         * @param string $token Token, commonly send by email
101
         * @param bool $async Execute signing asynchronously when possible
102
         * @return DataResponse<Http::STATUS_OK, LibresignSignActionResponse, array{}>|DataResponse<Http::STATUS_UNPROCESSABLE_ENTITY, LibresignSignActionErrorResponse, array{}>
103
         *
104
         * 200: OK
105
         * 404: Invalid data
106
         * 422: Error
107
         */
108
        #[NoAdminRequired]
109
        #[NoCSRFRequired]
110
        #[RequireSigner]
111
        #[PublicPage]
112
        #[OpenAPI(tags: ['signing'])]
113
        #[ApiRoute(verb: 'POST', url: '/api/{apiVersion}/sign/uuid/{uuid}', requirements: ['apiVersion' => '(v1)'])]
114
        public function signBySignerUuid(string $uuid, string $method, array $elements = [], string $identifyValue = '', string $token = '', bool $async = false): DataResponse {
115
                return $this->sign($method, $elements, $identifyValue, $token, null, $uuid, $async);
2✔
116
        }
117

118
        /**
119
         * @return DataResponse<Http::STATUS_OK, LibresignSignActionResponse, array{}>|DataResponse<Http::STATUS_UNPROCESSABLE_ENTITY, LibresignSignActionErrorResponse, array{}>
120
         */
121
        public function sign(
122
                string $method,
123
                array $elements = [],
124
                string $identifyValue = '',
125
                string $token = '',
126
                ?int $fileId = null,
127
                ?string $signRequestUuid = null,
128
                bool $async = false,
129
        ): DataResponse {
130
                try {
131
                        $user = $this->userSession->getUser();
3✔
132
                        $isIdDocApproval = $this->request->getParam('idDocApproval') === 'true';
3✔
133

134
                        if ($isIdDocApproval && $signRequestUuid) {
3✔
135
                                $libreSignFile = $this->signFileService->getFileByUuid($signRequestUuid);
×
136
                                $signRequest = $this->signFileService->getSignRequestToSign($libreSignFile, null, $user);
×
137
                        } else {
138
                                $libreSignFile = $this->signFileService->getLibresignFile($fileId, $signRequestUuid);
3✔
139
                                $signRequest = $this->signFileService->getSignRequestToSign($libreSignFile, $signRequestUuid, $user);
2✔
140
                        }
141

142
                        $this->validateHelper->canSignWithIdentificationDocumentStatus(
2✔
143
                                $user,
2✔
144
                                $this->settingsLoader->getIdentificationDocumentsStatus($user, $signRequest)
2✔
145
                        );
2✔
146

147
                        $this->validateHelper->validateVisibleElementsRelation($elements, $signRequest, $user);
2✔
148
                        $this->validateHelper->validateCredentials($signRequest, $method, $identifyValue, $token);
2✔
149

150
                        $userIdentifier = $this->identifyMethodService->getUserIdentifier($signRequest->getId());
×
151
                        $metadata = $this->requestMetadataService->collectMetadata();
×
152

153
                        $this->signFileService->prepareForSigning(
×
154
                                $libreSignFile,
×
155
                                $signRequest,
×
156
                                $user,
×
157
                                $userIdentifier,
×
158
                                $signRequest->getDisplayName(),
×
159
                                $method !== 'password',
×
160
                                $method === 'password' ? $token : null,
×
161
                                $method,
×
162
                        );
×
163

164
                        if ($async && $this->workerHealthService->isAsyncLocalEnabled()) {
×
165
                                return $this->signAsync($libreSignFile, $signRequest, $user, $userIdentifier, $method, $token, $elements, $metadata);
×
166
                        }
167

168
                        return $this->signSync($libreSignFile, $elements, $metadata);
×
169
                } catch (\Throwable $e) {
3✔
170
                        $data = $this->errorHandler->handleException($e);
3✔
171
                        return new DataResponse($data, Http::STATUS_UNPROCESSABLE_ENTITY);
3✔
172
                }
173
        }
174

175
        /**
176
         * Execute asynchronous signing using background job
177
         *
178
         * @return DataResponse<Http::STATUS_OK, LibresignSignActionResponse, array{}>
179
         */
180
        private function signAsync(
181
                File $libreSignFile,
182
                SignRequest $signRequest,
183
                ?IUser $user,
184
                string $userIdentifier,
185
                string $method,
186
                ?string $token,
187
                array $elements,
188
                array $metadata,
189
        ): DataResponse {
190
                $this->signFileService->validateSigningRequirements();
×
191

192
                $this->asyncSigningService->enqueueSigningJob(
×
193
                        $libreSignFile,
×
194
                        $signRequest,
×
195
                        $user,
×
196
                        $userIdentifier,
×
197
                        $method !== 'password',
×
198
                        $method === 'password' ? $token : null,
×
199
                        $method,
×
200
                        $elements,
×
201
                        $metadata,
×
202
                );
×
203

204
                return new DataResponse(
×
205
                        [
×
206
                                'action' => JSActions::ACTION_DO_NOTHING,
×
207
                                'job' => [
×
208
                                        'status' => 'SIGNING_IN_PROGRESS',
×
209
                                        'file' => [
×
210
                                                'uuid' => $libreSignFile->getUuid(),
×
211
                                        ],
×
212
                                ],
×
213
                        ],
×
214
                        Http::STATUS_OK
×
215
                );
×
216
        }
217

218
        /**
219
         * Execute synchronous signing immediately
220
         *
221
         * @return DataResponse<Http::STATUS_OK, LibresignSignActionResponse, array{}>
222
         */
223
        private function signSync($libreSignFile, array $elements, array $metadata): DataResponse {
224
                $this->signFileService
×
225
                        ->setVisibleElements($elements)
×
226
                        ->storeUserMetadata($metadata)
×
227
                        ->sign();
×
228

229
                $validationUuid = $libreSignFile->getUuid();
×
230
                if ($libreSignFile->hasParent()) {
×
231
                        $parentFile = $this->signFileService->getFile($libreSignFile->getParentFileId());
×
232
                        $validationUuid = $parentFile->getUuid();
×
233
                }
234

235
                return new DataResponse(
×
236
                        [
×
237
                                'action' => JSActions::ACTION_SIGNED,
×
238
                                // TRANSLATORS Success message shown to the signer after the digital signature is applied to the document.
239
                                'message' => $this->l10n->t('File signed'),
×
240
                                'file' => [
×
241
                                        'uuid' => $validationUuid
×
242
                                ]
×
243
                        ],
×
244
                        Http::STATUS_OK
×
245
                );
×
246
        }
247

248
        /**
249
         * Renew the signature method
250
         *
251
         * @param string $method Signature method
252
         * @return DataResponse<Http::STATUS_OK, LibresignMessageResponse, array{}>
253
         *
254
         * 200: OK
255
         */
256
        #[NoAdminRequired]
257
        #[NoCSRFRequired]
258
        #[PublicPage]
259
        #[CanSignRequestUuid]
260
        #[OpenAPI(tags: ['signing'])]
261
        #[ApiRoute(verb: 'POST', url: '/api/{apiVersion}/sign/uuid/{uuid}/renew/{method}', requirements: ['apiVersion' => '(v1)'])]
262
        public function signRenew(string $method): DataResponse {
263
                $this->signFileService->renew(
×
264
                        $this->getSignRequestEntity(),
×
265
                        $method,
×
266
                );
×
267
                return new DataResponse(
×
268
                        [
×
269
                                // TRANSLATORS Message sent to signer when the sign link was expired and was possible to request to renew. The signer will see this message on the screen and nothing more.
270
                                'message' => $this->l10n->t('Renewed with success. Access the link again.'),
×
271
                        ]
×
272
                );
×
273
        }
274

275
        /**
276
         * Get code to sign the document using UUID
277
         *
278
         * @param string $uuid UUID of LibreSign file
279
         * @param 'account'|'email'|null $identifyMethod Identify signer method
280
         * @param string|null $signMethod Method used to sign the document, i.e. emailToken, account, clickToSign, smsToken, signalToken, telegramToken, whatsappToken, xmppToken
281
         * @param string|null $identify Identify value, i.e. the signer email, account or phone number
282
         * @return DataResponse<Http::STATUS_OK, LibresignMessageResponse, array{}>|DataResponse<Http::STATUS_UNPROCESSABLE_ENTITY, LibresignMessageResponse, array{}>
283
         *
284
         * 200: OK
285
         * 422: Error
286
         */
287
        #[NoAdminRequired]
288
        #[NoCSRFRequired]
289
        #[RequireSigner]
290
        #[PublicPage]
291
        #[OpenAPI(tags: ['signing'])]
292
        #[ApiRoute(verb: 'POST', url: '/api/{apiVersion}/sign/uuid/{uuid}/code', requirements: ['apiVersion' => '(v1)'])]
293
        public function requestCodeBySignerUuid(string $uuid, ?string $identifyMethod, ?string $signMethod, ?string $identify): DataResponse {
294
                try {
295
                        $signRequest = $this->signRequestMapper->getBySignerUuidAndUserId($uuid);
×
296
                } catch (\Throwable) {
×
297
                        // TRANSLATORS Error shown when the data required to apply a digital signature is missing or invalid.
298
                        throw new LibresignException($this->l10n->t('Invalid data to sign file'), 1);
×
299
                }
300
                return $this->getCode($signRequest);
×
301
        }
302

303
        /**
304
         * Get code to sign the document using FileID
305
         *
306
         * @param int $fileId Id of LibreSign file
307
         * @param 'account'|'email'|null $identifyMethod Identify signer method
308
         * @param string|null $signMethod Method used to sign the document, i.e. emailToken, account, clickToSign, smsToken, signalToken, telegramToken, whatsappToken, xmppToken
309
         * @param string|null $identify Identify value, i.e. the signer email, account or phone number
310
         * @return DataResponse<Http::STATUS_OK, LibresignMessageResponse, array{}>|DataResponse<Http::STATUS_UNPROCESSABLE_ENTITY, LibresignMessageResponse, array{}>
311
         *
312
         * 200: OK
313
         * 422: Error
314
         */
315
        #[NoAdminRequired]
316
        #[NoCSRFRequired]
317
        #[RequireSigner]
318
        #[PublicPage]
319
        #[OpenAPI(tags: ['signing'])]
320
        #[ApiRoute(verb: 'POST', url: '/api/{apiVersion}/sign/file_id/{fileId}/code', requirements: ['apiVersion' => '(v1)'])]
321
        public function requestCodeByFileId(int $fileId, ?string $identifyMethod, ?string $signMethod, ?string $identify): DataResponse {
322
                try {
323
                        $signRequest = $this->signRequestMapper->getByFileIdAndUserId($fileId);
×
324
                } catch (\Throwable) {
×
325
                        // TRANSLATORS Error shown when the data required to apply a digital signature is missing or invalid.
326
                        throw new LibresignException($this->l10n->t('Invalid data to sign file'), 1);
×
327
                }
328
                return $this->getCode($signRequest);
×
329
        }
330

331
        /**
332
         * @todo validate if can request code
333
         * @return DataResponse<Http::STATUS_OK|Http::STATUS_UNPROCESSABLE_ENTITY, LibresignMessageResponse, array{}>
334
         */
335
        private function getCode(SignRequest $signRequest): DataResponse {
336
                try {
337
                        $libreSignFile = $this->signFileService->getFile($signRequest->getFileId());
×
338
                        $this->validateHelper->fileCanBeSigned($libreSignFile);
×
339
                        $this->signFileService->requestCode(
×
340
                                signRequest: $signRequest,
×
341
                                identifyMethodName: $this->request->getParam('identifyMethod', ''),
×
342
                                signMethodName: $this->request->getParam('signMethod', ''),
×
343
                                identify: $this->request->getParam('identify', ''),
×
344
                        );
×
345
                        // TRANSLATORS Success message shown after sending a one-time verification code used to confirm the signer identity before signing.
346
                        $message = $this->l10n->t('Verification code sent.');
×
347
                        $statusCode = Http::STATUS_OK;
×
348
                } catch (\Throwable $th) {
×
349
                        $message = $th->getMessage();
×
350
                        $statusCode = Http::STATUS_UNPROCESSABLE_ENTITY;
×
351
                }
352
                return new DataResponse(
×
353
                        [
×
354
                                'message' => $message,
×
355
                        ],
×
356
                        $statusCode,
×
357
                );
×
358
        }
359
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc