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

LibreSign / libresign / 20621495436

31 Dec 2025 02:59PM UTC coverage: 44.647%. First build
20621495436

Pull #6256

github

web-flow
Merge 4343635f1 into 27812ed76
Pull Request #6256: feat: add dashboard pending signatures widget

57 of 239 new or added lines in 16 files covered. (23.85%)

6618 of 14823 relevant lines covered (44.65%)

5.05 hits per line

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

19.83
/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\FileMapper;
13
use OCA\Libresign\Db\SignRequest;
14
use OCA\Libresign\Db\SignRequestMapper;
15
use OCA\Libresign\Exception\LibresignException;
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\FileService;
22
use OCA\Libresign\Service\IdentifyMethodService;
23
use OCA\Libresign\Service\SignFileService;
24
use OCP\AppFramework\Http;
25
use OCP\AppFramework\Http\Attribute\ApiRoute;
26
use OCP\AppFramework\Http\Attribute\NoAdminRequired;
27
use OCP\AppFramework\Http\Attribute\NoCSRFRequired;
28
use OCP\AppFramework\Http\Attribute\PublicPage;
29
use OCP\AppFramework\Http\DataResponse;
30
use OCP\IL10N;
31
use OCP\IRequest;
32
use OCP\IUserSession;
33
use Psr\Log\LoggerInterface;
34

35
class SignFileController extends AEnvironmentAwareController implements ISignatureUuid {
36
        use LibresignTrait;
37
        public function __construct(
38
                IRequest $request,
39
                protected IL10N $l10n,
40
                private SignRequestMapper $signRequestMapper,
41
                private FileMapper $fileMapper,
42
                protected IUserSession $userSession,
43
                private ValidateHelper $validateHelper,
44
                protected SignFileService $signFileService,
45
                private IdentifyMethodService $identifyMethodService,
46
                private FileService $fileService,
47
                protected LoggerInterface $logger,
48
        ) {
49
                parent::__construct(Application::APP_ID, $request);
6✔
50
        }
51

52
        /**
53
         * Sign a file using file Id
54
         *
55
         * @param int $fileId Id of LibreSign file
56
         * @param string $method Signature method
57
         * @param array<string, mixed> $elements List of visible elements
58
         * @param string $identifyValue Identify value
59
         * @param string $token Token, commonly send by email
60
         * @return DataResponse<Http::STATUS_OK, array{action: integer, message: string, file: array{uuid: string}}, array{}>|DataResponse<Http::STATUS_UNPROCESSABLE_ENTITY, array{action: integer, errors: list<array{message: string, title?: string}>, redirect?: string}, array{}>
61
         *
62
         * 200: OK
63
         * 404: Invalid data
64
         * 422: Error
65
         */
66
        #[NoAdminRequired]
67
        #[NoCSRFRequired]
68
        #[RequireManager]
69
        #[PublicPage]
70
        #[ApiRoute(verb: 'POST', url: '/api/{apiVersion}/sign/file_id/{fileId}', requirements: ['apiVersion' => '(v1)'])]
71
        public function signUsingFileId(int $fileId, string $method, array $elements = [], string $identifyValue = '', string $token = ''): DataResponse {
72
                return $this->sign($method, $elements, $identifyValue, $token, $fileId, null);
1✔
73
        }
74

75
        /**
76
         * Sign a file using file UUID
77
         *
78
         * @param string $uuid UUID of LibreSign file
79
         * @param string $method Signature method
80
         * @param array<string, mixed> $elements List of visible elements
81
         * @param string $identifyValue Identify value
82
         * @param string $token Token, commonly send by email
83
         * @return DataResponse<Http::STATUS_OK, array{action: integer, message: string, file: array{uuid: string}}, array{}>|DataResponse<Http::STATUS_UNPROCESSABLE_ENTITY, array{action: integer, errors: list<array{message: string, title?: string}>, redirect?: string}, array{}>
84
         *
85
         * 200: OK
86
         * 404: Invalid data
87
         * 422: Error
88
         */
89
        #[NoAdminRequired]
90
        #[NoCSRFRequired]
91
        #[RequireSigner]
92
        #[PublicPage]
93
        #[ApiRoute(verb: 'POST', url: '/api/{apiVersion}/sign/uuid/{uuid}', requirements: ['apiVersion' => '(v1)'])]
94
        public function signUsingUuid(string $uuid, string $method, array $elements = [], string $identifyValue = '', string $token = ''): DataResponse {
95
                return $this->sign($method, $elements, $identifyValue, $token, null, $uuid);
2✔
96
        }
97

98
        /**
99
         * @return DataResponse<Http::STATUS_OK, array{action: integer, message: string, file: array{uuid: string}}, array{}>|DataResponse<Http::STATUS_UNPROCESSABLE_ENTITY, array{action: integer, errors: list<array{message: string, title?: string}>, redirect?: string}, array{}>
100
         */
101
        public function sign(string $method, array $elements = [], string $identifyValue = '', string $token = '', ?int $fileId = null, ?string $signRequestUuid = null): DataResponse {
102
                try {
103
                        $user = $this->userSession->getUser();
3✔
104
                        $this->validateHelper->canSignWithIdentificationDocumentStatus(
3✔
105
                                $user,
3✔
106
                                $this->fileService->getIdentificationDocumentsStatus($user?->getUID() ?? '')
3✔
107
                        );
3✔
108
                        $libreSignFile = $this->signFileService->getLibresignFile($fileId, $signRequestUuid);
3✔
109
                        $signRequest = $this->signFileService->getSignRequestToSign($libreSignFile, $signRequestUuid, $user);
2✔
110
                        $this->validateHelper->validateVisibleElementsRelation($elements, $signRequest, $user);
2✔
111
                        $this->validateHelper->validateCredentials($signRequest, $method, $identifyValue, $token);
2✔
112
                        if ($method === 'password') {
×
113
                                $this->signFileService->setPassword($token);
×
114
                        } else {
115
                                $this->signFileService->setSignWithoutPassword();
×
116
                        }
117
                        $identifyMethod = $this->identifyMethodService->getIdentifiedMethod($signRequest->getId());
×
118
                        $this->signFileService
×
119
                                ->setLibreSignFile($libreSignFile)
×
120
                                ->setSignRequest($signRequest)
×
121
                                ->setUserUniqueIdentifier(
×
122
                                        $identifyMethod->getEntity()->getIdentifierKey()
×
123
                                        . ':'
×
124
                                        . $identifyMethod->getEntity()->getIdentifierValue()
×
125
                                )
×
126
                                ->setFriendlyName($signRequest->getDisplayName())
×
127
                                ->storeUserMetadata([
×
128
                                        'user-agent' => $this->request->getHeader('User-Agent'),
×
129
                                        'remote-address' => $this->request->getRemoteAddress(),
×
130
                                ])
×
131
                                ->setCurrentUser($user)
×
132
                                ->setVisibleElements($elements)
×
133
                                ->sign();
×
134

NEW
135
                        $validationUuid = $libreSignFile->getUuid();
×
NEW
136
                        if ($libreSignFile->hasParent()) {
×
NEW
137
                                $parentFile = $this->signFileService->getFile($libreSignFile->getParentFileId());
×
NEW
138
                                $validationUuid = $parentFile->getUuid();
×
139
                        }
140

141
                        return new DataResponse(
×
142
                                [
×
143
                                        'action' => JSActions::ACTION_SIGNED,
×
144
                                        'message' => $this->l10n->t('File signed'),
×
145
                                        'file' => [
×
NEW
146
                                                'uuid' => $validationUuid
×
147
                                        ]
×
148
                                ],
×
149
                                Http::STATUS_OK
×
150
                        );
×
151
                } catch (LibresignException $e) {
3✔
152
                        $code = $e->getCode();
3✔
153
                        if ($code === 400) {
3✔
154
                                $action = JSActions::ACTION_CREATE_SIGNATURE_PASSWORD;
×
155
                        } else {
156
                                $action = JSActions::ACTION_DO_NOTHING;
3✔
157
                        }
158
                        $data = [
3✔
159
                                'action' => $action,
3✔
160
                                'errors' => [['message' => $e->getMessage()]],
3✔
161
                        ];
3✔
162
                } catch (\Throwable $th) {
×
163
                        $message = $th->getMessage();
×
164
                        $data = [
×
165
                                'action' => JSActions::ACTION_DO_NOTHING,
×
166
                        ];
×
167
                        switch ($message) {
168
                                case 'Host violates local access rules.':
×
169
                                case 'Certificate Password Invalid.':
×
170
                                case 'Certificate Password is Empty.':
×
171
                                        $data['errors'] = [['message' => $this->l10n->t($message)]];
×
172
                                        break;
×
173
                                default:
174
                                        $this->logger->error($message, ['exception' => $th]);
×
175
                                        $data['errors'] = [[
×
176
                                                'message'
×
177
                                                        => sprintf(
×
178
                                                                "The server was unable to complete your request.\n"
×
179
                                                                . "If this happens again, please send the technical details below to the server administrator.\n"
×
180
                                                                . "## Technical details:\n"
×
181
                                                                . "**Remote Address**: %s\n"
×
182
                                                                . "**Request ID**: %s\n"
×
183
                                                                . '**Message**: %s',
×
184
                                                                $this->request->getRemoteAddress(),
×
185
                                                                $this->request->getId(),
×
186
                                                                $message,
×
187
                                                        ),
×
188
                                                'title' => $this->l10n->t('Internal Server Error'),
×
189
                                        ]];
×
190
                        }
191
                }
192
                return new DataResponse(
3✔
193
                        $data,
3✔
194
                        Http::STATUS_UNPROCESSABLE_ENTITY
3✔
195
                );
3✔
196
        }
197

198
        /**
199
         * Renew the signature method
200
         *
201
         * @param string $method Signature method
202
         * @return DataResponse<Http::STATUS_OK, array{message: string}, array{}>
203
         *
204
         * 200: OK
205
         */
206
        #[NoAdminRequired]
207
        #[NoCSRFRequired]
208
        #[PublicPage]
209
        #[CanSignRequestUuid]
210
        #[ApiRoute(verb: 'POST', url: '/api/{apiVersion}/sign/uuid/{uuid}/renew/{method}', requirements: ['apiVersion' => '(v1)'])]
211
        public function signRenew(string $method): DataResponse {
212
                $this->signFileService->renew(
×
213
                        $this->getSignRequestEntity(),
×
214
                        $method,
×
215
                );
×
216
                return new DataResponse(
×
217
                        [
×
218
                                // 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.
219
                                'message' => $this->l10n->t('Renewed with success. Access the link again.'),
×
220
                        ]
×
221
                );
×
222
        }
223

224
        /**
225
         * Get code to sign the document using UUID
226
         *
227
         * @param string $uuid UUID of LibreSign file
228
         * @param 'account'|'email'|null $identifyMethod Identify signer method
229
         * @param string|null $signMethod Method used to sign the document, i.e. emailToken, account, clickToSign, sms, signal, telegram, whatsapp, xmpp
230
         * @param string|null $identify Identify value, i.e. the signer email, account or phone number
231
         * @return DataResponse<Http::STATUS_OK, array{message: string}, array{}>|DataResponse<Http::STATUS_UNPROCESSABLE_ENTITY, array{message: string}, array{}>
232
         *
233
         * 200: OK
234
         * 422: Error
235
         */
236
        #[NoAdminRequired]
237
        #[NoCSRFRequired]
238
        #[RequireSigner]
239
        #[PublicPage]
240
        #[ApiRoute(verb: 'POST', url: '/api/{apiVersion}/sign/uuid/{uuid}/code', requirements: ['apiVersion' => '(v1)'])]
241
        public function getCodeUsingUuid(string $uuid, ?string $identifyMethod, ?string $signMethod, ?string $identify): DataResponse {
242
                try {
243
                        $signRequest = $this->signRequestMapper->getBySignerUuidAndUserId($uuid);
×
244
                } catch (\Throwable) {
×
245
                        throw new LibresignException($this->l10n->t('Invalid data to sign file'), 1);
×
246
                }
247
                return $this->getCode($signRequest);
×
248
        }
249

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

276
        /**
277
         * @todo validate if can request code
278
         * @return DataResponse<Http::STATUS_OK|Http::STATUS_UNPROCESSABLE_ENTITY, array{message: string}, array{}>
279
         */
280
        private function getCode(SignRequest $signRequest): DataResponse {
281
                try {
282
                        $libreSignFile = $this->fileMapper->getById($signRequest->getFileId());
×
283
                        $this->validateHelper->fileCanBeSigned($libreSignFile);
×
284
                        $this->signFileService->requestCode(
×
285
                                signRequest: $signRequest,
×
286
                                identifyMethodName: $this->request->getParam('identifyMethod', ''),
×
287
                                signMethodName: $this->request->getParam('signMethod', ''),
×
288
                                identify: $this->request->getParam('identify', ''),
×
289
                        );
×
290
                        $message = $this->l10n->t('The code to sign file was successfully requested.');
×
291
                        $statusCode = Http::STATUS_OK;
×
292
                } catch (\Throwable $th) {
×
293
                        $message = $th->getMessage();
×
294
                        $statusCode = Http::STATUS_UNPROCESSABLE_ENTITY;
×
295
                }
296
                return new DataResponse(
×
297
                        [
×
298
                                'message' => $message,
×
299
                        ],
×
300
                        $statusCode,
×
301
                );
×
302
        }
303
}
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