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

LibreSign / libresign / 21268273482

22 Jan 2026 11:13PM UTC coverage: 45.185%. First build
21268273482

Pull #6526

github

web-flow
Merge 16c32ce29 into 068f2ad0f
Pull Request #6526: fix: progress cache factory

84 of 183 new or added lines in 6 files covered. (45.9%)

7348 of 16262 relevant lines covered (45.19%)

4.95 hits per line

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

60.63
/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;
50✔
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) {
45✔
77
                        $apiVersion = $this->request->getParam('apiVersion');
39✔
78
                        /** @var AEnvironmentAwareController $controller */
79
                        $controller->setAPIVersion((int)substr((string)$apiVersion, 1));
39✔
80
                }
81

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

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

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

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

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

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

102
        private function privateValidation(\ReflectionMethod $reflectionMethod): void {
103
                if (empty($reflectionMethod->getAttributes(PrivateValidation::class))) {
38✔
104
                        return;
34✔
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) {
38✔
126
                        return;
28✔
127
                }
128

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

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

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

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

166
        private function getLoggedIn(): void {
167
                $user = $this->userSession->getUser();
11✔
168
                if (!$user instanceof IUser) {
11✔
169
                        throw new \Exception($this->l10n->t('You are not allowed to request signing'), Http::STATUS_UNPROCESSABLE_ENTITY);
×
170
                }
171
                $this->validateHelper->canRequestSign($user);
11✔
172
        }
173

174
        private function requireSigner(): void {
175
                $uuid = $this->getUuidFromRequest();
5✔
176

177
                try {
178
                        $user = $this->userSession->getUser();
5✔
179
                        $this->validateHelper->validateSigner($uuid, $user);
5✔
180
                } catch (LibresignException $e) {
3✔
181
                        throw new LibresignException($e->getMessage());
3✔
182
                }
183
        }
184

185
        private function requireSignerUuid(): void {
NEW
186
                $uuid = $this->getUuidFromRequest();
×
187

188
                try {
NEW
189
                        $this->validateHelper->validateSignerUuid($uuid);
×
NEW
190
                } catch (LibresignException $e) {
×
NEW
191
                        throw new LibresignException($e->getMessage());
×
192
                }
193
        }
194

195
        private function redirectSignedToValidationIfNeeded(RequireSignRequestUuid $requirement): void {
196
                if (!$requirement->redirectIfSignedToValidation()) {
2✔
197
                        return;
2✔
198
                }
199

NEW
200
                $uuid = $this->getUuidFromRequest();
×
NEW
201
                if (!$uuid) {
×
NEW
202
                        return;
×
203
                }
204

205
                try {
NEW
206
                        $signRequest = $this->signRequestMapper->getByUuid($uuid);
×
NEW
207
                        if ($signRequest->getStatusEnum() !== SignRequestStatus::SIGNED) {
×
NEW
208
                                return;
×
209
                        }
NEW
210
                        $file = $this->fileMapper->getById($signRequest->getFileId());
×
NEW
211
                        $fileUuid = $file->getUuid();
×
NEW
212
                } catch (\Throwable) {
×
NEW
213
                        return;
×
214
                }
215

NEW
216
                $path = $this->request->getRawPathInfo() ?? '';
×
NEW
217
                $redirectUrl = str_contains($path, '/p/')
×
NEW
218
                        ? $this->urlGenerator->linkToRouteAbsolute('libresign.page.validationFilePublic', [
×
NEW
219
                                'uuid' => $fileUuid,
×
NEW
220
                        ])
×
NEW
221
                        : $this->urlGenerator->linkToRouteAbsolute('libresign.page.indexFPath', [
×
NEW
222
                                'path' => 'validation/' . $fileUuid,
×
NEW
223
                        ]);
×
224

NEW
225
                throw new LibresignException(json_encode([
×
NEW
226
                        'action' => JSActions::ACTION_REDIRECT,
×
NEW
227
                        'redirect' => $redirectUrl,
×
NEW
228
                ]), Http::STATUS_SEE_OTHER);
×
229
        }
230

231
        private function requireSetupOk(\ReflectionMethod $reflectionMethod): void {
232
                $attribute = $reflectionMethod->getAttributes(RequireSetupOk::class);
38✔
233
                if (empty($attribute)) {
38✔
234
                        return;
38✔
235
                }
236
                $attribute = current($attribute);
×
237
                if (!$this->certificateEngineFactory->getEngine()->isSetupOk()) {
×
238
                        /** @var RequireSetupOk $requirement */
239
                        $requireSetupOk = $attribute->newInstance();
×
240
                        throw new LibresignException(json_encode([
×
241
                                'action' => JSActions::ACTION_INCOMPLETE_SETUP,
×
242
                                'template' => $requireSetupOk->getTemplate(),
×
243
                        ]));
×
244
                }
245
        }
246

247
        /**
248
         * @param Controller $controller
249
         * @param string $methodName
250
         * @param \Exception $exception
251
         * @throws \Exception
252
         * @return Response
253
         */
254
        #[\Override]
255
        public function afterException($controller, $methodName, \Exception $exception): Response {
256
                if (str_contains($this->request->getHeader('Accept'), 'html')) {
12✔
257
                        $template = 'external';
3✔
258
                        if ($this->isJson($exception->getMessage())) {
3✔
259
                                $settings = json_decode($exception->getMessage(), true);
2✔
260
                                if (isset($settings['action']) && $settings['action'] === JSActions::ACTION_REDIRECT && isset($settings['redirect'])) {
2✔
261
                                        if (isset($settings['errors'])) {
1✔
262
                                                $this->session->set('loginMessages', [
×
263
                                                        [], $settings['errors'],
×
264
                                                ]);
×
265
                                        }
266
                                        return new RedirectResponse($settings['redirect']);
1✔
267
                                }
268
                                foreach ($settings as $key => $value) {
1✔
269
                                        if ($key === 'template') {
1✔
270
                                                $template = $value;
×
271
                                                continue;
×
272
                                        }
273
                                        $this->initialState->provideInitialState($key, $value);
1✔
274
                                }
275
                        } else {
276
                                $this->initialState->provideInitialState('error', ['message' => $exception->getMessage()]);
1✔
277
                        }
278

279
                        Util::addScript(Application::APP_ID, 'libresign-' . $template);
2✔
280
                        $response = new TemplateResponse(
2✔
281
                                appName: Application::APP_ID,
2✔
282
                                templateName: $template,
2✔
283
                                renderAs: $this->getRenderAsFromTemplate($template),
2✔
284
                                status: $this->getStatusCodeFromException($exception)
2✔
285
                        );
2✔
286

287
                        $policy = new ContentSecurityPolicy();
2✔
288
                        $policy->allowEvalScript(true);
2✔
289
                        $policy->addAllowedFrameDomain('\'self\'');
2✔
290
                        $response->setContentSecurityPolicy($policy);
2✔
291
                        return $response;
2✔
292
                }
293
                if ($exception instanceof LibresignException) {
9✔
294
                        if ($this->isJson($exception->getMessage())) {
9✔
295
                                $body = json_decode($exception->getMessage());
8✔
296
                        } else {
297
                                $body = [
1✔
298
                                        'message' => $exception->getMessage(),
1✔
299
                                ];
1✔
300
                        }
301
                        if ($controller instanceof \OCP\AppFramework\OCSController) {
9✔
302
                                $format = $this->request->getParam('format');
7✔
303

304
                                // if none is given try the first Accept header
305
                                if ($format === null) {
7✔
306
                                        $headers = $this->request->getHeader('Accept');
7✔
307
                                        $format = $controller->getResponderByHTTPHeader($headers, 'json');
7✔
308
                                }
309

310
                                $response = new DataResponse(
7✔
311
                                        data: $body,
7✔
312
                                        statusCode: $this->getStatusCodeFromException($exception)
7✔
313
                                );
7✔
314
                                if ($format !== null) {
7✔
315
                                        $response = $controller->buildResponse($response, $format);
7✔
316
                                } else {
317
                                        $response = $controller->buildResponse($response);
×
318
                                }
319
                        } else {
320
                                $response = new JSONResponse(
2✔
321
                                        data: $body,
2✔
322
                                        statusCode: $this->getStatusCodeFromException($exception)
2✔
323
                                );
2✔
324
                        }
325
                        return $response;
9✔
326
                }
327

328
                throw $exception;
×
329
        }
330

331
        private function getRenderAsFromTemplate(string $template): string {
332
                if ($template === 'external') {
2✔
333
                        return TemplateResponse::RENDER_AS_BASE;
2✔
334
                }
335
                return TemplateResponse::RENDER_AS_USER;
×
336
        }
337

338
        private function getStatusCodeFromException(\Exception $exception): int {
339
                if ($exception->getCode() === 0) {
11✔
340
                        return AppFrameworkHttp::STATUS_UNPROCESSABLE_ENTITY;
3✔
341
                }
342
                return (int)$exception->getCode();
8✔
343
        }
344

345
        protected function isJson(string $string): bool {
346
                json_decode($string);
12✔
347
                return json_last_error() === JSON_ERROR_NONE;
12✔
348
        }
349
}
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