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

LibreSign / libresign / 21479429942

29 Jan 2026 01:10PM UTC coverage: 46.511%. First build
21479429942

Pull #6638

github

web-flow
Merge 48f41a13c into 4eb6eb527
Pull Request #6638: feat: expose use async to api

2 of 3 new or added lines in 1 file covered. (66.67%)

7859 of 16897 relevant lines covered (46.51%)

5.09 hits per line

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

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

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

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

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

104
        /**
105
         * @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, title?: string}>}, array{}>
106
         */
107
        public function sign(
108
                string $method,
109
                array $elements = [],
110
                string $identifyValue = '',
111
                string $token = '',
112
                ?int $fileId = null,
113
                ?string $signRequestUuid = null,
114
                bool $async = false,
115
        ): DataResponse {
116
                try {
117
                        $user = $this->userSession->getUser();
3✔
118
                        $this->validateHelper->canSignWithIdentificationDocumentStatus(
3✔
119
                                $user,
3✔
120
                                $this->fileService->getIdentificationDocumentsStatus($user?->getUID() ?? '')
3✔
121
                        );
3✔
122

123
                        $libreSignFile = $this->signFileService->getLibresignFile($fileId, $signRequestUuid);
3✔
124
                        $signRequest = $this->signFileService->getSignRequestToSign($libreSignFile, $signRequestUuid, $user);
2✔
125
                        $this->validateHelper->validateVisibleElementsRelation($elements, $signRequest, $user);
2✔
126
                        $this->validateHelper->validateCredentials($signRequest, $method, $identifyValue, $token);
2✔
127

128
                        $userIdentifier = $this->identifyMethodService->getUserIdentifier($signRequest->getId());
×
129
                        $metadata = $this->requestMetadataService->collectMetadata();
×
130

131
                        $this->signFileService->prepareForSigning(
×
132
                                $libreSignFile,
×
133
                                $signRequest,
×
134
                                $user,
×
135
                                $userIdentifier,
×
136
                                $signRequest->getDisplayName(),
×
137
                                $method !== 'password',
×
138
                                $method === 'password' ? $token : null,
×
139
                                $method,
×
140
                        );
×
141

NEW
142
                        if ($async && $this->workerHealthService->isAsyncLocalEnabled()) {
×
143
                                return $this->signAsync($libreSignFile, $signRequest, $user, $userIdentifier, $method, $token, $elements, $metadata);
×
144
                        }
145

146
                        return $this->signSync($libreSignFile, $elements, $metadata);
×
147
                } catch (\Throwable $e) {
3✔
148
                        $data = $this->errorHandler->handleException($e);
3✔
149
                        return new DataResponse($data, Http::STATUS_UNPROCESSABLE_ENTITY);
3✔
150
                }
151
        }
152

153
        /**
154
         * Execute asynchronous signing using background job
155
         *
156
         * @return DataResponse<Http::STATUS_OK, array{action: integer, job: array{status: 'SIGNING_IN_PROGRESS', file: array{uuid: string}}}, array{}>
157
         */
158
        private function signAsync(
159
                $libreSignFile,
160
                $signRequest,
161
                $user,
162
                string $userIdentifier,
163
                string $method,
164
                ?string $token,
165
                array $elements,
166
                array $metadata,
167
        ): DataResponse {
168
                $this->signFileService->validateSigningRequirements();
×
169

170
                $this->asyncSigningService->enqueueSigningJob(
×
171
                        $libreSignFile,
×
172
                        $signRequest,
×
173
                        $user,
×
174
                        $userIdentifier,
×
175
                        $method !== 'password',
×
176
                        $method === 'password' ? $token : null,
×
177
                        $method,
×
178
                        $elements,
×
179
                        $metadata,
×
180
                );
×
181

182
                return new DataResponse(
×
183
                        [
×
184
                                'action' => JSActions::ACTION_DO_NOTHING,
×
185
                                'job' => [
×
186
                                        'status' => 'SIGNING_IN_PROGRESS',
×
187
                                        'file' => [
×
188
                                                'uuid' => $libreSignFile->getUuid(),
×
189
                                        ],
×
190
                                ],
×
191
                        ],
×
192
                        Http::STATUS_OK
×
193
                );
×
194
        }
195

196
        /**
197
         * Execute synchronous signing immediately
198
         *
199
         * @return DataResponse<Http::STATUS_OK, array{action: integer, message: string, file: array{uuid: string}}, array{}>
200
         */
201
        private function signSync($libreSignFile, array $elements, array $metadata): DataResponse {
202
                $this->signFileService
×
203
                        ->setVisibleElements($elements)
×
204
                        ->storeUserMetadata($metadata)
×
205
                        ->sign();
×
206

207
                $validationUuid = $libreSignFile->getUuid();
×
208
                if ($libreSignFile->hasParent()) {
×
209
                        $parentFile = $this->signFileService->getFile($libreSignFile->getParentFileId());
×
210
                        $validationUuid = $parentFile->getUuid();
×
211
                }
212

213
                return new DataResponse(
×
214
                        [
×
215
                                'action' => JSActions::ACTION_SIGNED,
×
216
                                'message' => $this->l10n->t('File signed'),
×
217
                                'file' => [
×
218
                                        'uuid' => $validationUuid
×
219
                                ]
×
220
                        ],
×
221
                        Http::STATUS_OK
×
222
                );
×
223
        }
224

225
        /**
226
         * Renew the signature method
227
         *
228
         * @param string $method Signature method
229
         * @return DataResponse<Http::STATUS_OK, array{message: string}, array{}>
230
         *
231
         * 200: OK
232
         */
233
        #[NoAdminRequired]
234
        #[NoCSRFRequired]
235
        #[PublicPage]
236
        #[CanSignRequestUuid]
237
        #[ApiRoute(verb: 'POST', url: '/api/{apiVersion}/sign/uuid/{uuid}/renew/{method}', requirements: ['apiVersion' => '(v1)'])]
238
        public function signRenew(string $method): DataResponse {
239
                $this->signFileService->renew(
×
240
                        $this->getSignRequestEntity(),
×
241
                        $method,
×
242
                );
×
243
                return new DataResponse(
×
244
                        [
×
245
                                // 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.
246
                                'message' => $this->l10n->t('Renewed with success. Access the link again.'),
×
247
                        ]
×
248
                );
×
249
        }
250

251
        /**
252
         * Get code to sign the document using UUID
253
         *
254
         * @param string $uuid UUID of LibreSign file
255
         * @param 'account'|'email'|null $identifyMethod Identify signer method
256
         * @param string|null $signMethod Method used to sign the document, i.e. emailToken, account, clickToSign, sms, signal, telegram, whatsapp, xmpp
257
         * @param string|null $identify Identify value, i.e. the signer email, account or phone number
258
         * @return DataResponse<Http::STATUS_OK, array{message: string}, array{}>|DataResponse<Http::STATUS_UNPROCESSABLE_ENTITY, array{message: string}, array{}>
259
         *
260
         * 200: OK
261
         * 422: Error
262
         */
263
        #[NoAdminRequired]
264
        #[NoCSRFRequired]
265
        #[RequireSigner]
266
        #[PublicPage]
267
        #[ApiRoute(verb: 'POST', url: '/api/{apiVersion}/sign/uuid/{uuid}/code', requirements: ['apiVersion' => '(v1)'])]
268
        public function getCodeUsingUuid(string $uuid, ?string $identifyMethod, ?string $signMethod, ?string $identify): DataResponse {
269
                try {
270
                        $signRequest = $this->signRequestMapper->getBySignerUuidAndUserId($uuid);
×
271
                } catch (\Throwable) {
×
272
                        throw new LibresignException($this->l10n->t('Invalid data to sign file'), 1);
×
273
                }
274
                return $this->getCode($signRequest);
×
275
        }
276

277
        /**
278
         * Get code to sign the document using FileID
279
         *
280
         * @param int $fileId Id of LibreSign file
281
         * @param 'account'|'email'|null $identifyMethod Identify signer method
282
         * @param string|null $signMethod Method used to sign the document, i.e. emailToken, account, clickToSign, sms, signal, telegram, whatsapp, xmpp
283
         * @param string|null $identify Identify value, i.e. the signer email, account or phone number
284
         * @return DataResponse<Http::STATUS_OK, array{message: string}, array{}>|DataResponse<Http::STATUS_UNPROCESSABLE_ENTITY, array{message: string}, array{}>
285
         *
286
         * 200: OK
287
         * 422: Error
288
         */
289
        #[NoAdminRequired]
290
        #[NoCSRFRequired]
291
        #[RequireSigner]
292
        #[PublicPage]
293
        #[ApiRoute(verb: 'POST', url: '/api/{apiVersion}/sign/file_id/{fileId}/code', requirements: ['apiVersion' => '(v1)'])]
294
        public function getCodeUsingFileId(int $fileId, ?string $identifyMethod, ?string $signMethod, ?string $identify): DataResponse {
295
                try {
296
                        $signRequest = $this->signRequestMapper->getByFileIdAndUserId($fileId);
×
297
                } catch (\Throwable) {
×
298
                        throw new LibresignException($this->l10n->t('Invalid data to sign file'), 1);
×
299
                }
300
                return $this->getCode($signRequest);
×
301
        }
302

303
        /**
304
         * @todo validate if can request code
305
         * @return DataResponse<Http::STATUS_OK|Http::STATUS_UNPROCESSABLE_ENTITY, array{message: string}, array{}>
306
         */
307
        private function getCode(SignRequest $signRequest): DataResponse {
308
                try {
309
                        $libreSignFile = $this->signFileService->getFile($signRequest->getFileId());
×
310
                        $this->validateHelper->fileCanBeSigned($libreSignFile);
×
311
                        $this->signFileService->requestCode(
×
312
                                signRequest: $signRequest,
×
313
                                identifyMethodName: $this->request->getParam('identifyMethod', ''),
×
314
                                signMethodName: $this->request->getParam('signMethod', ''),
×
315
                                identify: $this->request->getParam('identify', ''),
×
316
                        );
×
317
                        $message = $this->l10n->t('The code to sign file was successfully requested.');
×
318
                        $statusCode = Http::STATUS_OK;
×
319
                } catch (\Throwable $th) {
×
320
                        $message = $th->getMessage();
×
321
                        $statusCode = Http::STATUS_UNPROCESSABLE_ENTITY;
×
322
                }
323
                return new DataResponse(
×
324
                        [
×
325
                                'message' => $message,
×
326
                        ],
×
327
                        $statusCode,
×
328
                );
×
329
        }
330
}
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