• 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

58.96
/lib/Middleware/InjectionMiddleware.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\Middleware;
10

11
use OC\AppFramework\Http as AppFrameworkHttp;
12
use OCA\Libresign\AppInfo\Application;
13
use OCA\Libresign\Controller\AEnvironmentAwareController;
14
use OCA\Libresign\Controller\AEnvironmentPageAwareController;
15
use OCA\Libresign\Controller\ISignatureUuid;
16
use OCA\Libresign\Db\FileMapper;
17
use OCA\Libresign\Db\SignRequestMapper;
18
use OCA\Libresign\Enum\SignRequestStatus;
19
use OCA\Libresign\Exception\LibresignException;
20
use OCA\Libresign\Handler\CertificateEngine\CertificateEngineFactory;
21
use OCA\Libresign\Helper\JSActions;
22
use OCA\Libresign\Helper\ValidateHelper;
23
use OCA\Libresign\Middleware\Attribute\CanSignRequestUuid;
24
use OCA\Libresign\Middleware\Attribute\PrivateValidation;
25
use OCA\Libresign\Middleware\Attribute\RequireManager;
26
use OCA\Libresign\Middleware\Attribute\RequireSetupOk;
27
use OCA\Libresign\Middleware\Attribute\RequireSigner;
28
use OCA\Libresign\Middleware\Attribute\RequireSignerUuid;
29
use OCA\Libresign\Middleware\Attribute\RequireSignRequestUuid;
30
use OCA\Libresign\Service\SignFileService;
31
use OCA\Libresign\Service\UuidResolverService;
32
use OCP\AppFramework\Controller;
33
use OCP\AppFramework\Http;
34
use OCP\AppFramework\Http\ContentSecurityPolicy;
35
use OCP\AppFramework\Http\DataResponse;
36
use OCP\AppFramework\Http\JSONResponse;
37
use OCP\AppFramework\Http\RedirectResponse;
38
use OCP\AppFramework\Http\Response;
39
use OCP\AppFramework\Http\TemplateResponse;
40
use OCP\AppFramework\Middleware;
41
use OCP\AppFramework\Services\IInitialState;
42
use OCP\IAppConfig;
43
use OCP\IL10N;
44
use OCP\IRequest;
45
use OCP\ISession;
46
use OCP\IURLGenerator;
47
use OCP\IUser;
48
use OCP\IUserSession;
49
use OCP\Util;
50

51
class InjectionMiddleware extends Middleware {
52
        public function __construct(
53
                private IRequest $request,
54
                private ISession $session,
55
                private IUserSession $userSession,
56
                private ValidateHelper $validateHelper,
57
                private SignRequestMapper $signRequestMapper,
58
                private CertificateEngineFactory $certificateEngineFactory,
59
                private FileMapper $fileMapper,
60
                private IInitialState $initialState,
61
                private SignFileService $signFileService,
62
                private UuidResolverService $uuidResolverService,
63
                private IL10N $l10n,
64
                private IAppConfig $appConfig,
65
                private IURLGenerator $urlGenerator,
66
                protected ?string $userId,
67
        ) {
68
                $this->request = $request;
60✔
69
        }
70

71
        /**
72
         * @param Controller $controller
73
         * @param string $methodName
74
         * @throws \Exception
75
         */
76
        #[\Override]
77
        public function beforeController(Controller $controller, string $methodName) {
78
                if ($controller instanceof AEnvironmentAwareController) {
55✔
79
                        $apiVersion = $this->request->getParam('apiVersion');
49✔
80
                        /** @var AEnvironmentAwareController $controller */
81
                        $controller->setAPIVersion((int)substr((string)$apiVersion, 1));
49✔
82
                }
83

84
                $reflectionMethod = new \ReflectionMethod($controller, $methodName);
55✔
85

86
                if (!empty($reflectionMethod->getAttributes(RequireManager::class))) {
55✔
87
                        $this->getLoggedIn();
11✔
88
                }
89

90
                if (!empty($reflectionMethod->getAttributes(RequireSigner::class))) {
51✔
91
                        $this->requireSigner();
5✔
92
                }
93
                if (!empty($reflectionMethod->getAttributes(RequireSignerUuid::class))) {
48✔
94
                        $this->requireSignerUuid();
×
95
                }
96

97
                $this->requireSetupOk($reflectionMethod);
48✔
98

99
                $this->privateValidation($reflectionMethod);
48✔
100

101
                $this->handleUuid($controller, $reflectionMethod);
48✔
102
        }
103

104
        private function privateValidation(\ReflectionMethod $reflectionMethod): void {
105
                if (empty($reflectionMethod->getAttributes(PrivateValidation::class))) {
48✔
106
                        return;
44✔
107
                }
108
                if ($this->userSession->isLoggedIn()) {
4✔
109
                        return;
1✔
110
                }
111
                $isValidationUrlPrivate = (bool)$this->appConfig->getValueBool(Application::APP_ID, 'make_validation_url_private', false);
3✔
112
                if (!$isValidationUrlPrivate) {
3✔
113
                        return;
3✔
114
                }
115
                $redirectUrl = $this->request->getRawPathInfo();
×
116

117
                throw new LibresignException(json_encode([
×
118
                        'action' => JSActions::ACTION_REDIRECT,
×
119
                        'errors' => [$this->l10n->t('You are not logged in. Please log in.')],
×
120
                        'redirect' => $this->urlGenerator->linkToRoute('core.login.showLoginForm', [
×
121
                                'redirect_url' => $redirectUrl,
×
122
                        ]),
×
123
                ]), Http::STATUS_UNAUTHORIZED);
×
124
        }
125

126
        private function handleUuid(Controller $controller, \ReflectionMethod $reflectionMethod): void {
127
                if (!$controller instanceof ISignatureUuid) {
48✔
128
                        return;
38✔
129
                }
130

131
                $uuid = $this->getUuidFromRequest();
10✔
132
                $this->maintainSessionConsistencyWithUuid($uuid);
10✔
133

134
                if (!empty($reflectionMethod->getAttributes(CanSignRequestUuid::class))) {
10✔
135
                        /** @var AEnvironmentPageAwareController $controller */
136
                        $controller->validateRenewSigner(
×
137
                                uuid: $uuid,
×
138
                        );
×
139
                        /** @var AEnvironmentPageAwareController $controller */
140
                        $controller->loadNextcloudFileFromSignRequestUuid(
×
141
                                uuid: $uuid,
×
142
                        );
×
143
                }
144

145
                if (!empty($attribute = $reflectionMethod->getAttributes(RequireSignRequestUuid::class))) {
10✔
146
                        $attribute = $reflectionMethod->getAttributes(RequireSignRequestUuid::class);
2✔
147
                        $attribute = current($attribute);
2✔
148
                        /** @var RequireSignRequestUuid $intance */
149
                        $intance = $attribute->newInstance();
2✔
150
                        $user = $this->userSession->getUser();
2✔
151
                        $this->redirectSignedToValidationIfNeeded($intance);
2✔
152

153
                        $isIdDocApproval = $intance->allowIdDocs() && $this->request->getParam('idDocApproval') === 'true';
2✔
154

155
                        if (!($intance->skipIfAuthenticated() && $user instanceof IUser)) {
2✔
NEW
156
                                if ($isIdDocApproval) {
×
157
                                        try {
NEW
158
                                                $resolution = $this->uuidResolverService->resolveUuidForUser($uuid, $user);
×
159
                                                /** @var AEnvironmentPageAwareController $controller */
NEW
160
                                                $controller->loadIdDocApprovalFromResolution($resolution);
×
NEW
161
                                        } catch (LibresignException $e) {
×
NEW
162
                                                throw $e;
×
163
                                        }
164
                                } else {
165
                                        /** @var AEnvironmentPageAwareController $controller */
NEW
166
                                        $controller->validateSignRequestUuid(
×
NEW
167
                                                uuid: $uuid,
×
NEW
168
                                        );
×
169
                                        /** @var AEnvironmentPageAwareController $controller */
NEW
170
                                        $controller->loadNextcloudFileFromSignRequestUuid(
×
NEW
171
                                                uuid: $uuid,
×
NEW
172
                                        );
×
173
                                }
174
                        }
175
                }
176
        }
177

178
        private function getUuidFromRequest(): ?string {
179
                return $this->request->getParam('uuid', $this->request->getHeader('libresign-sign-request-uuid'));
13✔
180
        }
181

182
        /**
183
         * Maintain session consistency when sign request UUID is provided
184
         *
185
         * For unauthenticated requests with a sign request UUID header,
186
         * store the UUID in the session to ensure consistent session ID
187
         * across multiple HTTP requests. Each request without authentication
188
         * would otherwise create a new session ID, causing data lookup failures.
189
         *
190
         * @param string|null $uuid The sign request UUID from request header
191
         */
192
        private function maintainSessionConsistencyWithUuid(?string $uuid): void {
193
                if ($uuid && !($this->userSession->getUser() instanceof IUser)) {
10✔
194
                        if (!$this->session->get('libresign-uuid')) {
×
195
                                $this->session->set('libresign-uuid', $uuid);
×
196
                        }
197
                }
198
        }
199

200
        private function getLoggedIn(): void {
201
                $user = $this->userSession->getUser();
11✔
202
                if (!$user instanceof IUser) {
11✔
203
                        throw new \Exception($this->l10n->t('You are not allowed to request signing'), Http::STATUS_UNPROCESSABLE_ENTITY);
×
204
                }
205
                $this->validateHelper->canRequestSign($user);
11✔
206
        }
207

208
        private function requireSigner(): void {
209
                $uuid = $this->getUuidFromRequest();
5✔
210

211
                try {
212
                        $user = $this->userSession->getUser();
5✔
213
                        $isIdDocApproval = $this->request->getParam('idDocApproval') === 'true';
5✔
214

215
                        if ($isIdDocApproval) {
5✔
NEW
216
                                $this->uuidResolverService->resolveUuidForUser($uuid, $user);
×
217
                        } else {
218
                                $this->validateHelper->validateSigner($uuid, $user);
5✔
219
                        }
220
                } catch (LibresignException $e) {
3✔
221
                        throw new LibresignException($e->getMessage());
3✔
222
                }
223
        }
224

225
        private function requireSignerUuid(): void {
226
                $uuid = $this->getUuidFromRequest();
×
227

228
                try {
229
                        $this->validateHelper->validateSignerUuid($uuid);
×
230
                } catch (LibresignException $e) {
×
231
                        throw new LibresignException($e->getMessage());
×
232
                }
233
        }
234

235
        private function redirectSignedToValidationIfNeeded(RequireSignRequestUuid $requirement): void {
236
                if (!$requirement->redirectIfSignedToValidation()) {
2✔
237
                        return;
2✔
238
                }
239

240
                $uuid = $this->getUuidFromRequest();
×
241
                if (!$uuid) {
×
242
                        return;
×
243
                }
244

245
                try {
246
                        $signRequest = $this->signRequestMapper->getByUuid($uuid);
×
247
                        if ($signRequest->getStatusEnum() !== SignRequestStatus::SIGNED) {
×
248
                                return;
×
249
                        }
250
                        $file = $this->fileMapper->getById($signRequest->getFileId());
×
251
                        $fileUuid = $file->getUuid();
×
252
                } catch (\Throwable) {
×
253
                        return;
×
254
                }
255

256
                $path = $this->request->getRawPathInfo() ?? '';
×
257
                $redirectUrl = str_contains($path, '/p/')
×
258
                        ? $this->urlGenerator->linkToRouteAbsolute('libresign.page.validationFilePublic', [
×
259
                                'uuid' => $fileUuid,
×
260
                        ])
×
261
                        : $this->urlGenerator->linkToRouteAbsolute('libresign.page.indexFPath', [
×
262
                                'path' => 'validation/' . $fileUuid,
×
263
                        ]);
×
264

265
                throw new LibresignException(json_encode([
×
266
                        'action' => JSActions::ACTION_REDIRECT,
×
267
                        'redirect' => $redirectUrl,
×
268
                ]), Http::STATUS_SEE_OTHER);
×
269
        }
270

271
        private function requireSetupOk(\ReflectionMethod $reflectionMethod): void {
272
                $attribute = $reflectionMethod->getAttributes(RequireSetupOk::class);
48✔
273
                if (empty($attribute)) {
48✔
274
                        return;
48✔
275
                }
276
                $attribute = current($attribute);
×
277
                if (!$this->certificateEngineFactory->getEngine()->isSetupOk()) {
×
278
                        /** @var RequireSetupOk $requirement */
279
                        $requireSetupOk = $attribute->newInstance();
×
280
                        throw new LibresignException(json_encode([
×
281
                                'action' => JSActions::ACTION_INCOMPLETE_SETUP,
×
282
                                'template' => $requireSetupOk->getTemplate(),
×
283
                        ]));
×
284
                }
285
        }
286

287
        /**
288
         * @param Controller $controller
289
         * @param string $methodName
290
         * @param \Exception $exception
291
         * @throws \Exception
292
         * @return Response
293
         */
294
        #[\Override]
295
        public function afterException($controller, $methodName, \Exception $exception): Response {
296
                if (str_contains($this->request->getHeader('Accept'), 'html')) {
12✔
297
                        $template = 'external';
3✔
298
                        if ($this->isJson($exception->getMessage())) {
3✔
299
                                $settings = json_decode($exception->getMessage(), true);
2✔
300
                                if (isset($settings['action']) && $settings['action'] === JSActions::ACTION_REDIRECT && isset($settings['redirect'])) {
2✔
301
                                        if (isset($settings['errors'])) {
1✔
302
                                                $this->session->set('loginMessages', [
×
303
                                                        [], $settings['errors'],
×
304
                                                ]);
×
305
                                        }
306
                                        return new RedirectResponse($settings['redirect']);
1✔
307
                                }
308
                                foreach ($settings as $key => $value) {
1✔
309
                                        if ($key === 'template') {
1✔
310
                                                $template = $value;
×
311
                                                continue;
×
312
                                        }
313
                                        $this->initialState->provideInitialState($key, $value);
1✔
314
                                }
315
                        } else {
316
                                $this->initialState->provideInitialState('error', ['message' => $exception->getMessage()]);
1✔
317
                        }
318

319
                        Util::addScript(Application::APP_ID, 'libresign-' . $template);
2✔
320
                        $response = new TemplateResponse(
2✔
321
                                appName: Application::APP_ID,
2✔
322
                                templateName: $template,
2✔
323
                                renderAs: $this->getRenderAsFromTemplate($template),
2✔
324
                                status: $this->getStatusCodeFromException($exception)
2✔
325
                        );
2✔
326

327
                        $policy = new ContentSecurityPolicy();
2✔
328
                        $policy->allowEvalScript(true);
2✔
329
                        $policy->addAllowedFrameDomain('\'self\'');
2✔
330
                        $response->setContentSecurityPolicy($policy);
2✔
331
                        return $response;
2✔
332
                }
333
                if ($exception instanceof LibresignException) {
9✔
334
                        if ($this->isJson($exception->getMessage())) {
9✔
335
                                $body = json_decode($exception->getMessage());
8✔
336
                        } else {
337
                                $body = [
1✔
338
                                        'message' => $exception->getMessage(),
1✔
339
                                ];
1✔
340
                        }
341
                        if ($controller instanceof \OCP\AppFramework\OCSController) {
9✔
342
                                $format = $this->request->getParam('format');
7✔
343

344
                                // if none is given try the first Accept header
345
                                if ($format === null) {
7✔
346
                                        $headers = $this->request->getHeader('Accept');
7✔
347
                                        $format = $controller->getResponderByHTTPHeader($headers, 'json');
7✔
348
                                }
349

350
                                $response = new DataResponse(
7✔
351
                                        data: $body,
7✔
352
                                        statusCode: $this->getStatusCodeFromException($exception)
7✔
353
                                );
7✔
354
                                if ($format !== null) {
7✔
355
                                        $response = $controller->buildResponse($response, $format);
7✔
356
                                } else {
357
                                        $response = $controller->buildResponse($response);
×
358
                                }
359
                        } else {
360
                                $response = new JSONResponse(
2✔
361
                                        data: $body,
2✔
362
                                        statusCode: $this->getStatusCodeFromException($exception)
2✔
363
                                );
2✔
364
                        }
365
                        return $response;
9✔
366
                }
367

368
                throw $exception;
×
369
        }
370

371
        private function getRenderAsFromTemplate(string $template): string {
372
                if ($template === 'external') {
2✔
373
                        return TemplateResponse::RENDER_AS_BASE;
2✔
374
                }
375
                return TemplateResponse::RENDER_AS_USER;
×
376
        }
377

378
        private function getStatusCodeFromException(\Exception $exception): int {
379
                if ($exception->getCode() === 0) {
11✔
380
                        return AppFrameworkHttp::STATUS_UNPROCESSABLE_ENTITY;
3✔
381
                }
382
                return (int)$exception->getCode();
8✔
383
        }
384

385
        protected function isJson(string $string): bool {
386
                json_decode($string);
12✔
387
                return json_last_error() === JSON_ERROR_NONE;
12✔
388
        }
389
}
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