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

LibreSign / libresign / 22047397566

16 Feb 2026 01:47AM UTC coverage: 51.605%. First build
22047397566

Pull #6891

github

web-flow
Merge cc204601c into bdac9dc51
Pull Request #6891: feat: add id doc approver workflow

171 of 424 new or added lines in 24 files covered. (40.33%)

9145 of 17721 relevant lines covered (51.61%)

6.15 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\Service\AsyncSigningService;
23
use OCA\Libresign\Service\File\SettingsLoader;
24
use OCA\Libresign\Service\FileService;
25
use OCA\Libresign\Service\IdentifyMethodService;
26
use OCA\Libresign\Service\RequestMetadataService;
27
use OCA\Libresign\Service\SignFileService;
28
use OCA\Libresign\Service\Worker\WorkerHealthService;
29
use OCP\AppFramework\Http;
30
use OCP\AppFramework\Http\Attribute\ApiRoute;
31
use OCP\AppFramework\Http\Attribute\NoAdminRequired;
32
use OCP\AppFramework\Http\Attribute\NoCSRFRequired;
33
use OCP\AppFramework\Http\Attribute\PublicPage;
34
use OCP\AppFramework\Http\DataResponse;
35
use OCP\IL10N;
36
use OCP\IRequest;
37
use OCP\IUser;
38
use OCP\IUserSession;
39

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

60
        /**
61
         * Sign a file using file Id
62
         *
63
         * @param int $fileId Id of LibreSign file
64
         * @param string $method Signature method
65
         * @param array<string, mixed> $elements List of visible elements
66
         * @param string $identifyValue Identify value
67
         * @param string $token Token, commonly send by email
68
         * @param bool $async Execute signing asynchronously when possible
69
         * @return DataResponse<Http::STATUS_OK, array{action: integer, message?: string, file?: array{uuid: string}, job?: array{status: 'SIGNING_IN_PROGRESS', file: array{uuid: string}}}, array{}>|DataResponse<Http::STATUS_UNPROCESSABLE_ENTITY, array{action: integer, errors: list<array{message: string, code?: int, title?: string}>, redirect?: string}, array{}>
70
         *
71
         * 200: OK
72
         * 404: Invalid data
73
         * 422: Error
74
         */
75
        #[NoAdminRequired]
76
        #[NoCSRFRequired]
77
        #[RequireManager]
78
        #[PublicPage]
79
        #[ApiRoute(verb: 'POST', url: '/api/{apiVersion}/sign/file_id/{fileId}', requirements: ['apiVersion' => '(v1)'])]
80
        public function signUsingFileId(int $fileId, string $method, array $elements = [], string $identifyValue = '', string $token = '', bool $async = false): DataResponse {
81
                return $this->sign($method, $elements, $identifyValue, $token, $fileId, null, $async);
1✔
82
        }
83

84
        /**
85
         * Sign a file using file UUID
86
         *
87
         * @param string $uuid UUID of LibreSign file
88
         * @param string $method Signature method
89
         * @param array<string, mixed> $elements List of visible elements
90
         * @param string $identifyValue Identify value
91
         * @param string $token Token, commonly send by email
92
         * @param bool $async Execute signing asynchronously when possible
93
         * @return DataResponse<Http::STATUS_OK, array{action: integer, message?: string, file?: array{uuid: string}, job?: array{status: 'SIGNING_IN_PROGRESS', file: array{uuid: string}}}, array{}>|DataResponse<Http::STATUS_UNPROCESSABLE_ENTITY, array{action: integer, errors: list<array{message: string, code?: int, title?: string}>, redirect?: string}, array{}>
94
         *
95
         * 200: OK
96
         * 404: Invalid data
97
         * 422: Error
98
         */
99
        #[NoAdminRequired]
100
        #[NoCSRFRequired]
101
        #[RequireSigner]
102
        #[PublicPage]
103
        #[ApiRoute(verb: 'POST', url: '/api/{apiVersion}/sign/uuid/{uuid}', requirements: ['apiVersion' => '(v1)'])]
104
        public function signUsingUuid(string $uuid, string $method, array $elements = [], string $identifyValue = '', string $token = '', bool $async = false): DataResponse {
105
                return $this->sign($method, $elements, $identifyValue, $token, null, $uuid, $async);
2✔
106
        }
107

108
        /**
109
         * @return DataResponse<Http::STATUS_OK, array{action: integer, message?: string, file?: array{uuid: string}, job?: array{status: 'SIGNING_IN_PROGRESS', file: array{uuid: string}}}, array{}>|DataResponse<Http::STATUS_UNPROCESSABLE_ENTITY, array{action: integer, errors: list<array{message: string, code?: int, title?: string}>}, array{}>
110
         */
111
        public function sign(
112
                string $method,
113
                array $elements = [],
114
                string $identifyValue = '',
115
                string $token = '',
116
                ?int $fileId = null,
117
                ?string $signRequestUuid = null,
118
                bool $async = false,
119
        ): DataResponse {
120
                try {
121
                        $user = $this->userSession->getUser();
3✔
122
                        $isIdDocApproval = $this->request->getParam('idDocApproval') === 'true';
3✔
123

124
                        if ($isIdDocApproval && $signRequestUuid) {
3✔
NEW
125
                                $libreSignFile = $this->signFileService->getFileByUuid($signRequestUuid);
×
NEW
126
                                $signRequest = $this->signFileService->getSignRequestToSign($libreSignFile, null, $user);
×
127
                        } else {
128
                                $libreSignFile = $this->signFileService->getLibresignFile($fileId, $signRequestUuid);
3✔
129
                                $signRequest = $this->signFileService->getSignRequestToSign($libreSignFile, $signRequestUuid, $user);
2✔
130
                        }
131

132
                        $this->validateHelper->canSignWithIdentificationDocumentStatus(
2✔
133
                                $user,
2✔
134
                                $this->settingsLoader->getIdentificationDocumentsStatus($user, $signRequest)
2✔
135
                        );
2✔
136

137
                        $this->validateHelper->validateVisibleElementsRelation($elements, $signRequest, $user);
2✔
138
                        $this->validateHelper->validateCredentials($signRequest, $method, $identifyValue, $token);
2✔
139

140
                        $userIdentifier = $this->identifyMethodService->getUserIdentifier($signRequest->getId());
×
141
                        $metadata = $this->requestMetadataService->collectMetadata();
×
142

143
                        $this->signFileService->prepareForSigning(
×
144
                                $libreSignFile,
×
145
                                $signRequest,
×
146
                                $user,
×
147
                                $userIdentifier,
×
148
                                $signRequest->getDisplayName(),
×
149
                                $method !== 'password',
×
150
                                $method === 'password' ? $token : null,
×
151
                                $method,
×
152
                        );
×
153

154
                        if ($async && $this->workerHealthService->isAsyncLocalEnabled()) {
×
155
                                return $this->signAsync($libreSignFile, $signRequest, $user, $userIdentifier, $method, $token, $elements, $metadata);
×
156
                        }
157

158
                        return $this->signSync($libreSignFile, $elements, $metadata);
×
159
                } catch (\Throwable $e) {
3✔
160
                        $data = $this->errorHandler->handleException($e);
3✔
161
                        return new DataResponse($data, Http::STATUS_UNPROCESSABLE_ENTITY);
3✔
162
                }
163
        }
164

165
        /**
166
         * Execute asynchronous signing using background job
167
         *
168
         * @return DataResponse<Http::STATUS_OK, array{action: integer, job: array{status: 'SIGNING_IN_PROGRESS', file: array{uuid: string}}}, array{}>
169
         */
170
        private function signAsync(
171
                File $libreSignFile,
172
                SignRequest $signRequest,
173
                ?IUser $user,
174
                string $userIdentifier,
175
                string $method,
176
                ?string $token,
177
                array $elements,
178
                array $metadata,
179
        ): DataResponse {
180
                $this->signFileService->validateSigningRequirements();
×
181

182
                $this->asyncSigningService->enqueueSigningJob(
×
183
                        $libreSignFile,
×
184
                        $signRequest,
×
185
                        $user,
×
186
                        $userIdentifier,
×
187
                        $method !== 'password',
×
188
                        $method === 'password' ? $token : null,
×
189
                        $method,
×
190
                        $elements,
×
191
                        $metadata,
×
192
                );
×
193

194
                return new DataResponse(
×
195
                        [
×
196
                                'action' => JSActions::ACTION_DO_NOTHING,
×
197
                                'job' => [
×
198
                                        'status' => 'SIGNING_IN_PROGRESS',
×
199
                                        'file' => [
×
200
                                                'uuid' => $libreSignFile->getUuid(),
×
201
                                        ],
×
202
                                ],
×
203
                        ],
×
204
                        Http::STATUS_OK
×
205
                );
×
206
        }
207

208
        /**
209
         * Execute synchronous signing immediately
210
         *
211
         * @return DataResponse<Http::STATUS_OK, array{action: integer, message: string, file: array{uuid: string}}, array{}>
212
         */
213
        private function signSync($libreSignFile, array $elements, array $metadata): DataResponse {
214
                $this->signFileService
×
215
                        ->setVisibleElements($elements)
×
216
                        ->storeUserMetadata($metadata)
×
217
                        ->sign();
×
218

219
                $validationUuid = $libreSignFile->getUuid();
×
220
                if ($libreSignFile->hasParent()) {
×
221
                        $parentFile = $this->signFileService->getFile($libreSignFile->getParentFileId());
×
222
                        $validationUuid = $parentFile->getUuid();
×
223
                }
224

225
                return new DataResponse(
×
226
                        [
×
227
                                'action' => JSActions::ACTION_SIGNED,
×
228
                                'message' => $this->l10n->t('File signed'),
×
229
                                'file' => [
×
230
                                        'uuid' => $validationUuid
×
231
                                ]
×
232
                        ],
×
233
                        Http::STATUS_OK
×
234
                );
×
235
        }
236

237
        /**
238
         * Renew the signature method
239
         *
240
         * @param string $method Signature method
241
         * @return DataResponse<Http::STATUS_OK, array{message: string}, array{}>
242
         *
243
         * 200: OK
244
         */
245
        #[NoAdminRequired]
246
        #[NoCSRFRequired]
247
        #[PublicPage]
248
        #[CanSignRequestUuid]
249
        #[ApiRoute(verb: 'POST', url: '/api/{apiVersion}/sign/uuid/{uuid}/renew/{method}', requirements: ['apiVersion' => '(v1)'])]
250
        public function signRenew(string $method): DataResponse {
251
                $this->signFileService->renew(
×
252
                        $this->getSignRequestEntity(),
×
253
                        $method,
×
254
                );
×
255
                return new DataResponse(
×
256
                        [
×
257
                                // 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.
258
                                'message' => $this->l10n->t('Renewed with success. Access the link again.'),
×
259
                        ]
×
260
                );
×
261
        }
262

263
        /**
264
         * Get code to sign the document using UUID
265
         *
266
         * @param string $uuid UUID of LibreSign file
267
         * @param 'account'|'email'|null $identifyMethod Identify signer method
268
         * @param string|null $signMethod Method used to sign the document, i.e. emailToken, account, clickToSign, smsToken, signalToken, telegramToken, whatsappToken, xmppToken
269
         * @param string|null $identify Identify value, i.e. the signer email, account or phone number
270
         * @return DataResponse<Http::STATUS_OK, array{message: string}, array{}>|DataResponse<Http::STATUS_UNPROCESSABLE_ENTITY, array{message: string}, array{}>
271
         *
272
         * 200: OK
273
         * 422: Error
274
         */
275
        #[NoAdminRequired]
276
        #[NoCSRFRequired]
277
        #[RequireSigner]
278
        #[PublicPage]
279
        #[ApiRoute(verb: 'POST', url: '/api/{apiVersion}/sign/uuid/{uuid}/code', requirements: ['apiVersion' => '(v1)'])]
280
        public function getCodeUsingUuid(string $uuid, ?string $identifyMethod, ?string $signMethod, ?string $identify): DataResponse {
281
                try {
282
                        $signRequest = $this->signRequestMapper->getBySignerUuidAndUserId($uuid);
×
283
                } catch (\Throwable) {
×
284
                        throw new LibresignException($this->l10n->t('Invalid data to sign file'), 1);
×
285
                }
286
                return $this->getCode($signRequest);
×
287
        }
288

289
        /**
290
         * Get code to sign the document using FileID
291
         *
292
         * @param int $fileId Id of LibreSign file
293
         * @param 'account'|'email'|null $identifyMethod Identify signer method
294
         * @param string|null $signMethod Method used to sign the document, i.e. emailToken, account, clickToSign, smsToken, signalToken, telegramToken, whatsappToken, xmppToken
295
         * @param string|null $identify Identify value, i.e. the signer email, account or phone number
296
         * @return DataResponse<Http::STATUS_OK, array{message: string}, array{}>|DataResponse<Http::STATUS_UNPROCESSABLE_ENTITY, array{message: string}, array{}>
297
         *
298
         * 200: OK
299
         * 422: Error
300
         */
301
        #[NoAdminRequired]
302
        #[NoCSRFRequired]
303
        #[RequireSigner]
304
        #[PublicPage]
305
        #[ApiRoute(verb: 'POST', url: '/api/{apiVersion}/sign/file_id/{fileId}/code', requirements: ['apiVersion' => '(v1)'])]
306
        public function getCodeUsingFileId(int $fileId, ?string $identifyMethod, ?string $signMethod, ?string $identify): DataResponse {
307
                try {
308
                        $signRequest = $this->signRequestMapper->getByFileIdAndUserId($fileId);
×
309
                } catch (\Throwable) {
×
310
                        throw new LibresignException($this->l10n->t('Invalid data to sign file'), 1);
×
311
                }
312
                return $this->getCode($signRequest);
×
313
        }
314

315
        /**
316
         * @todo validate if can request code
317
         * @return DataResponse<Http::STATUS_OK|Http::STATUS_UNPROCESSABLE_ENTITY, array{message: string}, array{}>
318
         */
319
        private function getCode(SignRequest $signRequest): DataResponse {
320
                try {
321
                        $libreSignFile = $this->signFileService->getFile($signRequest->getFileId());
×
322
                        $this->validateHelper->fileCanBeSigned($libreSignFile);
×
323
                        $this->signFileService->requestCode(
×
324
                                signRequest: $signRequest,
×
325
                                identifyMethodName: $this->request->getParam('identifyMethod', ''),
×
326
                                signMethodName: $this->request->getParam('signMethod', ''),
×
327
                                identify: $this->request->getParam('identify', ''),
×
328
                        );
×
329
                        $message = $this->l10n->t('The code to sign file was successfully requested.');
×
330
                        $statusCode = Http::STATUS_OK;
×
331
                } catch (\Throwable $th) {
×
332
                        $message = $th->getMessage();
×
333
                        $statusCode = Http::STATUS_UNPROCESSABLE_ENTITY;
×
334
                }
335
                return new DataResponse(
×
336
                        [
×
337
                                'message' => $message,
×
338
                        ],
×
339
                        $statusCode,
×
340
                );
×
341
        }
342
}
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