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

LibreSign / libresign / 21967842076

12 Feb 2026 11:09PM UTC coverage: 51.292%. First build
21967842076

Pull #6824

github

web-flow
Merge e1e14865d into 34f32c1fd
Pull Request #6824: fix: signature methods names

217 of 241 new or added lines in 21 files covered. (90.04%)

8989 of 17525 relevant lines covered (51.29%)

6.03 hits per line

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

60.37
/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 OCP\AppFramework\Controller;
32
use OCP\AppFramework\Http;
33
use OCP\AppFramework\Http\ContentSecurityPolicy;
34
use OCP\AppFramework\Http\DataResponse;
35
use OCP\AppFramework\Http\JSONResponse;
36
use OCP\AppFramework\Http\RedirectResponse;
37
use OCP\AppFramework\Http\Response;
38
use OCP\AppFramework\Http\TemplateResponse;
39
use OCP\AppFramework\Middleware;
40
use OCP\AppFramework\Services\IInitialState;
41
use OCP\IAppConfig;
42
use OCP\IL10N;
43
use OCP\IRequest;
44
use OCP\ISession;
45
use OCP\IURLGenerator;
46
use OCP\IUser;
47
use OCP\IUserSession;
48
use OCP\Util;
49

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

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

82
                $reflectionMethod = new \ReflectionMethod($controller, $methodName);
55✔
83

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

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

95
                $this->requireSetupOk($reflectionMethod);
48✔
96

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

99
                $this->handleUuid($controller, $reflectionMethod);
48✔
100
        }
101

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

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

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

129
                $uuid = $this->getUuidFromRequest();
10✔
130
                $this->maintainSessionConsistencyWithUuid($uuid);
10✔
131

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

143
                if (!empty($attribute = $reflectionMethod->getAttributes(RequireSignRequestUuid::class))) {
10✔
144
                        $attribute = $reflectionMethod->getAttributes(RequireSignRequestUuid::class);
2✔
145
                        $attribute = current($attribute);
2✔
146
                        /** @var RequireSignRequestUuid $intance */
147
                        $intance = $attribute->newInstance();
2✔
148
                        $user = $this->userSession->getUser();
2✔
149
                        $this->redirectSignedToValidationIfNeeded($intance);
2✔
150
                        if (!($intance->skipIfAuthenticated() && $user instanceof IUser)) {
2✔
151
                                /** @var AEnvironmentPageAwareController $controller */
152
                                $controller->validateSignRequestUuid(
×
153
                                        uuid: $uuid,
×
154
                                );
×
155
                                /** @var AEnvironmentPageAwareController $controller */
156
                                $controller->loadNextcloudFileFromSignRequestUuid(
×
157
                                        uuid: $uuid,
×
158
                                );
×
159
                        }
160
                }
161
        }
162

163
        private function getUuidFromRequest(): ?string {
164
                return $this->request->getParam('uuid', $this->request->getHeader('libresign-sign-request-uuid'));
13✔
165
        }
166

167
        /**
168
         * Maintain session consistency when sign request UUID is provided
169
         *
170
         * For unauthenticated requests with a sign request UUID header,
171
         * store the UUID in the session to ensure consistent session ID
172
         * across multiple HTTP requests. Each request without authentication
173
         * would otherwise create a new session ID, causing data lookup failures.
174
         *
175
         * @param string|null $uuid The sign request UUID from request header
176
         */
177
        private function maintainSessionConsistencyWithUuid(?string $uuid): void {
178
                if ($uuid && !($this->userSession->getUser() instanceof IUser)) {
10✔
NEW
179
                        if (!$this->session->get('libresign-uuid')) {
×
NEW
180
                                $this->session->set('libresign-uuid', $uuid);
×
181
                        }
182
                }
183
        }
184

185
        private function getLoggedIn(): void {
186
                $user = $this->userSession->getUser();
11✔
187
                if (!$user instanceof IUser) {
11✔
188
                        throw new \Exception($this->l10n->t('You are not allowed to request signing'), Http::STATUS_UNPROCESSABLE_ENTITY);
×
189
                }
190
                $this->validateHelper->canRequestSign($user);
11✔
191
        }
192

193
        private function requireSigner(): void {
194
                $uuid = $this->getUuidFromRequest();
5✔
195

196
                try {
197
                        $user = $this->userSession->getUser();
5✔
198
                        $this->validateHelper->validateSigner($uuid, $user);
5✔
199
                } catch (LibresignException $e) {
3✔
200
                        throw new LibresignException($e->getMessage());
3✔
201
                }
202
        }
203

204
        private function requireSignerUuid(): void {
205
                $uuid = $this->getUuidFromRequest();
×
206

207
                try {
208
                        $this->validateHelper->validateSignerUuid($uuid);
×
209
                } catch (LibresignException $e) {
×
210
                        throw new LibresignException($e->getMessage());
×
211
                }
212
        }
213

214
        private function redirectSignedToValidationIfNeeded(RequireSignRequestUuid $requirement): void {
215
                if (!$requirement->redirectIfSignedToValidation()) {
2✔
216
                        return;
2✔
217
                }
218

219
                $uuid = $this->getUuidFromRequest();
×
220
                if (!$uuid) {
×
221
                        return;
×
222
                }
223

224
                try {
225
                        $signRequest = $this->signRequestMapper->getByUuid($uuid);
×
226
                        if ($signRequest->getStatusEnum() !== SignRequestStatus::SIGNED) {
×
227
                                return;
×
228
                        }
229
                        $file = $this->fileMapper->getById($signRequest->getFileId());
×
230
                        $fileUuid = $file->getUuid();
×
231
                } catch (\Throwable) {
×
232
                        return;
×
233
                }
234

235
                $path = $this->request->getRawPathInfo() ?? '';
×
236
                $redirectUrl = str_contains($path, '/p/')
×
237
                        ? $this->urlGenerator->linkToRouteAbsolute('libresign.page.validationFilePublic', [
×
238
                                'uuid' => $fileUuid,
×
239
                        ])
×
240
                        : $this->urlGenerator->linkToRouteAbsolute('libresign.page.indexFPath', [
×
241
                                'path' => 'validation/' . $fileUuid,
×
242
                        ]);
×
243

244
                throw new LibresignException(json_encode([
×
245
                        'action' => JSActions::ACTION_REDIRECT,
×
246
                        'redirect' => $redirectUrl,
×
247
                ]), Http::STATUS_SEE_OTHER);
×
248
        }
249

250
        private function requireSetupOk(\ReflectionMethod $reflectionMethod): void {
251
                $attribute = $reflectionMethod->getAttributes(RequireSetupOk::class);
48✔
252
                if (empty($attribute)) {
48✔
253
                        return;
48✔
254
                }
255
                $attribute = current($attribute);
×
256
                if (!$this->certificateEngineFactory->getEngine()->isSetupOk()) {
×
257
                        /** @var RequireSetupOk $requirement */
258
                        $requireSetupOk = $attribute->newInstance();
×
259
                        throw new LibresignException(json_encode([
×
260
                                'action' => JSActions::ACTION_INCOMPLETE_SETUP,
×
261
                                'template' => $requireSetupOk->getTemplate(),
×
262
                        ]));
×
263
                }
264
        }
265

266
        /**
267
         * @param Controller $controller
268
         * @param string $methodName
269
         * @param \Exception $exception
270
         * @throws \Exception
271
         * @return Response
272
         */
273
        #[\Override]
274
        public function afterException($controller, $methodName, \Exception $exception): Response {
275
                if (str_contains($this->request->getHeader('Accept'), 'html')) {
12✔
276
                        $template = 'external';
3✔
277
                        if ($this->isJson($exception->getMessage())) {
3✔
278
                                $settings = json_decode($exception->getMessage(), true);
2✔
279
                                if (isset($settings['action']) && $settings['action'] === JSActions::ACTION_REDIRECT && isset($settings['redirect'])) {
2✔
280
                                        if (isset($settings['errors'])) {
1✔
281
                                                $this->session->set('loginMessages', [
×
282
                                                        [], $settings['errors'],
×
283
                                                ]);
×
284
                                        }
285
                                        return new RedirectResponse($settings['redirect']);
1✔
286
                                }
287
                                foreach ($settings as $key => $value) {
1✔
288
                                        if ($key === 'template') {
1✔
289
                                                $template = $value;
×
290
                                                continue;
×
291
                                        }
292
                                        $this->initialState->provideInitialState($key, $value);
1✔
293
                                }
294
                        } else {
295
                                $this->initialState->provideInitialState('error', ['message' => $exception->getMessage()]);
1✔
296
                        }
297

298
                        Util::addScript(Application::APP_ID, 'libresign-' . $template);
2✔
299
                        $response = new TemplateResponse(
2✔
300
                                appName: Application::APP_ID,
2✔
301
                                templateName: $template,
2✔
302
                                renderAs: $this->getRenderAsFromTemplate($template),
2✔
303
                                status: $this->getStatusCodeFromException($exception)
2✔
304
                        );
2✔
305

306
                        $policy = new ContentSecurityPolicy();
2✔
307
                        $policy->allowEvalScript(true);
2✔
308
                        $policy->addAllowedFrameDomain('\'self\'');
2✔
309
                        $response->setContentSecurityPolicy($policy);
2✔
310
                        return $response;
2✔
311
                }
312
                if ($exception instanceof LibresignException) {
9✔
313
                        if ($this->isJson($exception->getMessage())) {
9✔
314
                                $body = json_decode($exception->getMessage());
8✔
315
                        } else {
316
                                $body = [
1✔
317
                                        'message' => $exception->getMessage(),
1✔
318
                                ];
1✔
319
                        }
320
                        if ($controller instanceof \OCP\AppFramework\OCSController) {
9✔
321
                                $format = $this->request->getParam('format');
7✔
322

323
                                // if none is given try the first Accept header
324
                                if ($format === null) {
7✔
325
                                        $headers = $this->request->getHeader('Accept');
7✔
326
                                        $format = $controller->getResponderByHTTPHeader($headers, 'json');
7✔
327
                                }
328

329
                                $response = new DataResponse(
7✔
330
                                        data: $body,
7✔
331
                                        statusCode: $this->getStatusCodeFromException($exception)
7✔
332
                                );
7✔
333
                                if ($format !== null) {
7✔
334
                                        $response = $controller->buildResponse($response, $format);
7✔
335
                                } else {
336
                                        $response = $controller->buildResponse($response);
×
337
                                }
338
                        } else {
339
                                $response = new JSONResponse(
2✔
340
                                        data: $body,
2✔
341
                                        statusCode: $this->getStatusCodeFromException($exception)
2✔
342
                                );
2✔
343
                        }
344
                        return $response;
9✔
345
                }
346

347
                throw $exception;
×
348
        }
349

350
        private function getRenderAsFromTemplate(string $template): string {
351
                if ($template === 'external') {
2✔
352
                        return TemplateResponse::RENDER_AS_BASE;
2✔
353
                }
354
                return TemplateResponse::RENDER_AS_USER;
×
355
        }
356

357
        private function getStatusCodeFromException(\Exception $exception): int {
358
                if ($exception->getCode() === 0) {
11✔
359
                        return AppFrameworkHttp::STATUS_UNPROCESSABLE_ENTITY;
3✔
360
                }
361
                return (int)$exception->getCode();
8✔
362
        }
363

364
        protected function isJson(string $string): bool {
365
                json_decode($string);
12✔
366
                return json_last_error() === JSON_ERROR_NONE;
12✔
367
        }
368
}
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