fix: restrict access to validation endpoints

- Created the attribute PrivateValidation to restrict access to public
endpoints when this is enabled at administration settings.
- Added the attribute PrivateValidation to all endpoints that have
  /validation/ at path.

Signed-off-by: Vitor Mattos <vitor@php.rio>
This commit is contained in:
Vitor Mattos 2025-02-27 17:02:57 -03:00
parent 85e3bd2e97
commit b4c12293ce
No known key found for this signature in database
GPG key ID: B7AB4B76A7CA7318
5 changed files with 73 additions and 33 deletions

View file

@ -16,6 +16,7 @@ use OCA\Libresign\Db\SignRequestMapper;
use OCA\Libresign\Exception\LibresignException;
use OCA\Libresign\Helper\JSActions;
use OCA\Libresign\Helper\ValidateHelper;
use OCA\Libresign\Middleware\Attribute\PrivateValidation;
use OCA\Libresign\Middleware\Attribute\RequireManager;
use OCA\Libresign\ResponseDefinitions;
use OCA\Libresign\Service\AccountService;
@ -85,6 +86,7 @@ class FileController extends AEnvironmentAwareController {
* 404: Request failed
* 422: Request failed
*/
#[PrivateValidation]
#[NoAdminRequired]
#[NoCSRFRequired]
#[PublicPage]
@ -105,6 +107,7 @@ class FileController extends AEnvironmentAwareController {
* 404: Request failed
* 422: Request failed
*/
#[PrivateValidation]
#[NoAdminRequired]
#[NoCSRFRequired]
#[PublicPage]
@ -125,6 +128,7 @@ class FileController extends AEnvironmentAwareController {
* 404: Request failed
* 400: Request failed
*/
#[PrivateValidation]
#[NoAdminRequired]
#[NoCSRFRequired]
#[PublicPage]
@ -174,6 +178,7 @@ class FileController extends AEnvironmentAwareController {
* 404: Request failed
* 422: Request failed
*/
#[PrivateValidation]
#[NoAdminRequired]
#[NoCSRFRequired]
#[PublicPage]

View file

@ -12,6 +12,7 @@ use OCA\Libresign\AppInfo\Application;
use OCA\Libresign\Exception\LibresignException;
use OCA\Libresign\Helper\JSActions;
use OCA\Libresign\Helper\ValidateHelper;
use OCA\Libresign\Middleware\Attribute\PrivateValidation;
use OCA\Libresign\Middleware\Attribute\RequireSetupOk;
use OCA\Libresign\Middleware\Attribute\RequireSignRequestUuid;
use OCA\Libresign\Service\AccountService;
@ -273,6 +274,7 @@ class PageController extends AEnvironmentPageAwareController {
*
* 200: OK
*/
#[PrivateValidation]
#[NoAdminRequired]
#[NoCSRFRequired]
#[RequireSetupOk]
@ -280,7 +282,6 @@ class PageController extends AEnvironmentPageAwareController {
#[RequireSignRequestUuid]
#[FrontpageRoute(verb: 'GET', url: '/p/sign/{uuid}')]
public function sign(string $uuid): TemplateResponse {
$this->throwIfValidationPageNotAccessible();
$this->initialState->provideInitialState('action', JSActions::ACTION_SIGN);
$this->initialState->provideInitialState('config',
$this->accountService->getConfig($this->userSession->getUser())
@ -407,6 +408,7 @@ class PageController extends AEnvironmentPageAwareController {
* 401: Validation page not accessible if unauthenticated
* 404: File not found
*/
#[PrivateValidation]
#[NoAdminRequired]
#[NoCSRFRequired]
#[RequireSetupOk]
@ -414,7 +416,6 @@ class PageController extends AEnvironmentPageAwareController {
#[AnonRateLimit(limit: 30, period: 60)]
#[FrontpageRoute(verb: 'GET', url: '/p/pdf/{uuid}')]
public function getPdf($uuid) {
$this->throwIfValidationPageNotAccessible();
try {
$file = $this->accountService->getPdfByUuid($uuid);
} catch (DoesNotExistException $th) {
@ -433,6 +434,7 @@ class PageController extends AEnvironmentPageAwareController {
* 200: OK
* 401: Validation page not accessible if unauthenticated
*/
#[PrivateValidation]
#[NoAdminRequired]
#[NoCSRFRequired]
#[RequireSignRequestUuid]
@ -441,7 +443,6 @@ class PageController extends AEnvironmentPageAwareController {
#[AnonRateLimit(limit: 30, period: 60)]
#[FrontpageRoute(verb: 'GET', url: '/pdf/{uuid}')]
public function getPdfFile($uuid): FileDisplayResponse {
$this->throwIfValidationPageNotAccessible();
$file = $this->getNextcloudFile();
return new FileDisplayResponse($file, Http::STATUS_OK, ['Content-Type' => $file->getMimeType()]);
}
@ -454,6 +455,7 @@ class PageController extends AEnvironmentPageAwareController {
* 200: OK
* 401: Validation page not accessible if unauthenticated
*/
#[PrivateValidation]
#[NoAdminRequired]
#[NoCSRFRequired]
#[RequireSetupOk(template: 'validation')]
@ -461,7 +463,6 @@ class PageController extends AEnvironmentPageAwareController {
#[AnonRateLimit(limit: 30, period: 60)]
#[FrontpageRoute(verb: 'GET', url: '/p/validation')]
public function validation(): TemplateResponse {
$this->throwIfValidationPageNotAccessible();
if ($this->getFileEntity()) {
$this->initialState->provideInitialState('config',
$this->accountService->getConfig($this->userSession->getUser())
@ -500,6 +501,7 @@ class PageController extends AEnvironmentPageAwareController {
* 303: Redirected to validation page
* 401: Validation page not accessible if unauthenticated
*/
#[PrivateValidation]
#[NoAdminRequired]
#[NoCSRFRequired]
#[RequireSetupOk]
@ -507,7 +509,6 @@ class PageController extends AEnvironmentPageAwareController {
#[AnonRateLimit(limit: 30, period: 60)]
#[FrontpageRoute(verb: 'GET', url: '/validation/{uuid}')]
public function validationFileWithShortUrl(): TemplateResponse {
$this->throwIfValidationPageNotAccessible();
return $this->validationFilePublic($this->request->getParam('uuid'));
}
@ -545,6 +546,7 @@ class PageController extends AEnvironmentPageAwareController {
* 200: OK
* 401: Validation page not accessible if unauthenticated
*/
#[PrivateValidation]
#[NoAdminRequired]
#[NoCSRFRequired]
#[RequireSetupOk(template: 'validation')]
@ -552,7 +554,6 @@ class PageController extends AEnvironmentPageAwareController {
#[AnonRateLimit(limit: 30, period: 60)]
#[FrontpageRoute(verb: 'GET', url: '/p/validation/{uuid}')]
public function validationFilePublic(string $uuid): TemplateResponse {
$this->throwIfValidationPageNotAccessible();
try {
$this->signFileService->getFileByUuid($uuid);
$this->fileService->setFileByType('uuid', $uuid);
@ -599,31 +600,4 @@ class PageController extends AEnvironmentPageAwareController {
return $response;
}
private function throwIfValidationPageNotAccessible(): void {
$isValidationUrlPrivate = (bool)$this->appConfig->getValueBool(Application::APP_ID, 'make_validation_url_private', false);
if ($this->userSession->isLoggedIn()) {
return;
}
if ($isValidationUrlPrivate) {
if ($uuid = $this->request->getParam('uuid')) {
$redirectUrl = $this->urlGenerator->linkToRoute(
'libresign.page.validationFilePublic',
['uuid' => $uuid]
);
} else {
$redirectUrl = $this->urlGenerator->linkToRoute(
'libresign.page.validation',
);
}
throw new LibresignException(json_encode([
'action' => JSActions::ACTION_REDIRECT,
'errors' => [$this->l10n->t('You are not logged in. Please log in.')],
'redirect' => $this->urlGenerator->linkToRoute('core.login.showLoginForm', [
'redirect_url' => $redirectUrl,
]),
]), Http::STATUS_UNAUTHORIZED);
}
}
}

View file

@ -0,0 +1,15 @@
<?php
declare(strict_types=1);
/**
* SPDX-FileCopyrightText: 2025 LibreCode coop and contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OCA\Libresign\Middleware\Attribute;
use Attribute;
#[Attribute]
class PrivateValidation {
}

View file

@ -20,6 +20,7 @@ use OCA\Libresign\Handler\CertificateEngine\Handler as CertificateEngineHandler;
use OCA\Libresign\Helper\JSActions;
use OCA\Libresign\Helper\ValidateHelper;
use OCA\Libresign\Middleware\Attribute\CanSignRequestUuid;
use OCA\Libresign\Middleware\Attribute\PrivateValidation;
use OCA\Libresign\Middleware\Attribute\RequireManager;
use OCA\Libresign\Middleware\Attribute\RequireSetupOk;
use OCA\Libresign\Middleware\Attribute\RequireSigner;
@ -35,9 +36,11 @@ use OCP\AppFramework\Http\Response;
use OCP\AppFramework\Http\TemplateResponse;
use OCP\AppFramework\Middleware;
use OCP\AppFramework\Services\IInitialState;
use OCP\IAppConfig;
use OCP\IL10N;
use OCP\IRequest;
use OCP\ISession;
use OCP\IURLGenerator;
use OCP\IUser;
use OCP\IUserSession;
use OCP\Util;
@ -56,6 +59,8 @@ class InjectionMiddleware extends Middleware {
private IInitialState $initialState,
private SignFileService $signFileService,
private IL10N $l10n,
private IAppConfig $appConfig,
private IURLGenerator $urlGenerator,
?string $userId,
) {
$this->request = $request;
@ -87,6 +92,39 @@ class InjectionMiddleware extends Middleware {
$this->requireSetupOk($reflectionMethod);
$this->handleUuid($controller, $reflectionMethod);
$this->privateValidation($reflectionMethod);
}
private function privateValidation(\ReflectionMethod $reflectionMethod): void {
if (empty($reflectionMethod->getAttributes(PrivateValidation::class))) {
return;
}
if ($this->userSession->isLoggedIn()) {
return;
}
$isValidationUrlPrivate = (bool)$this->appConfig->getValueBool(Application::APP_ID, 'make_validation_url_private', false);
if (!$isValidationUrlPrivate) {
return;
}
if ($uuid = $this->request->getParam('uuid')) {
$redirectUrl = $this->urlGenerator->linkToRoute(
'libresign.page.validationFilePublic',
['uuid' => $uuid]
);
} else {
$redirectUrl = $this->urlGenerator->linkToRoute(
'libresign.page.validation',
);
}
throw new LibresignException(json_encode([
'action' => JSActions::ACTION_REDIRECT,
'errors' => [$this->l10n->t('You are not logged in. Please log in.')],
'redirect' => $this->urlGenerator->linkToRoute('core.login.showLoginForm', [
'redirect_url' => $redirectUrl,
]),
]), Http::STATUS_UNAUTHORIZED);
}
private function handleUuid(Controller $controller, \ReflectionMethod $reflectionMethod): void {

View file

@ -18,10 +18,12 @@ use OCP\AppFramework\Http\JSONResponse;
use OCP\AppFramework\Http\RedirectResponse;
use OCP\AppFramework\Http\TemplateResponse;
use OCP\AppFramework\Services\IInitialState;
use OCP\IAppConfig;
use OCP\IL10N;
use OCP\IRequest;
use OCP\IServerContainer;
use OCP\ISession;
use OCP\IURLGenerator;
use OCP\IUserSession;
use PHPUnit\Framework\MockObject\MockObject;
use Psr\Log\LoggerInterface;
@ -42,6 +44,8 @@ final class InjectionMiddlewareTest extends \OCA\Libresign\Tests\Unit\TestCase {
private IInitialState $initialState;
private SignFileService&MockObject $signFileService;
private IL10N&MockObject $l10n;
private IappConfig&MockObject $appConfig;
private IurlGenerator&MockObject $urlGenerator;
private ?string $userId;
private InitialStateService $initialStateService;
@ -54,6 +58,8 @@ final class InjectionMiddlewareTest extends \OCA\Libresign\Tests\Unit\TestCase {
$this->signRequestMapper = $this->createMock(SignRequestMapper::class);
$this->certificateEngineHandler = $this->createMock(CertificateEngineHandler::class);
$this->fileMapper = $this->createMock(FileMapper::class);
$this->appConfig = $this->createMock(IAppConfig::class);
$this->urlGenerator = $this->createMock(IURLGenerator::class);
$this->initialStateService = new InitialStateService(
$this->createMock(LoggerInterface::class),
@ -78,6 +84,8 @@ final class InjectionMiddlewareTest extends \OCA\Libresign\Tests\Unit\TestCase {
$this->initialState,
$this->signFileService,
$this->l10n,
$this->appConfig,
$this->urlGenerator,
$this->userId,
);
}