Skip to content
14 changes: 13 additions & 1 deletion lib/Service/AccountService.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,15 @@
use OCA\Libresign\Db\SignRequestMapper;
use OCA\Libresign\Db\UserElement;
use OCA\Libresign\Db\UserElementMapper;
use OCA\Libresign\Enum\CRLReason;
use OCA\Libresign\Enum\FileStatus;
use OCA\Libresign\Exception\InvalidPasswordException;
use OCA\Libresign\Exception\LibresignException;
use OCA\Libresign\Handler\CertificateEngine\CertificateEngineFactory;
use OCA\Libresign\Handler\SignEngine\Pkcs12Handler;
use OCA\Libresign\Helper\FileUploadHelper;
use OCA\Libresign\Helper\ValidateHelper;
use OCA\Libresign\Service\Crl\CrlService;
use OCA\Settings\Mailer\NewUserMailHelper;
use OCP\Accounts\IAccountManager;
use OCP\AppFramework\Db\DoesNotExistException;
Expand Down Expand Up @@ -78,6 +80,7 @@ public function __construct(
private IClientService $clientService,
private ITimeFactory $timeFactory,
private FileUploadHelper $uploadHelper,
private CrlService $crlService,
) {
}

Expand Down Expand Up @@ -565,7 +568,16 @@ public function uploadPfx(array $file, IUser $user): void {
}

public function deletePfx(IUser $user): void {
$this->pkcs12Handler->deletePfx($user->getUID());
$uid = $user->getUID();

$this->crlService->revokeUserCertificates(
$uid,
CRLReason::CESSATION_OF_OPERATION,
'Certificate deleted by account owner.',
$uid,
);

$this->pkcs12Handler->deletePfx($uid);
}

/**
Expand Down
8 changes: 4 additions & 4 deletions lib/Service/IdentifyMethod/SignatureMethod/Password.php
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ private function validateCertificateRevocation(array $certificateData): void {
return;
}
if ($status === CrlValidationStatus::REVOKED) {
throw new LibresignException($this->identifyService->getL10n()->t('Certificate has been revoked'), 400);
throw new LibresignException($this->identifyService->getL10n()->t('Certificate has been revoked'), 422);
}
// Admin explicitly disabled external CRL validation – allow signing.
if ($status === CrlValidationStatus::DISABLED) {
Expand All @@ -63,19 +63,19 @@ private function validateCertificateRevocation(array $certificateData): void {
// fail-closed – we cannot confirm the certificate is not revoked.
throw new LibresignException(
$this->identifyService->getL10n()->t('Certificate revocation status could not be verified'),
400
422
);
}

private function validateCertificateExpiration(array $certificateData): void {
if (array_key_exists('validTo_time_t', $certificateData)) {
$validTo = $certificateData['validTo_time_t'];
if (!is_int($validTo)) {
throw new LibresignException($this->identifyService->getL10n()->t('Invalid certificate'), 400);
throw new LibresignException($this->identifyService->getL10n()->t('Invalid certificate'), 422);
}
$now = (new \DateTime())->getTimestamp();
if ($validTo <= $now) {
throw new LibresignException($this->identifyService->getL10n()->t('Certificate has expired'), 400);
throw new LibresignException($this->identifyService->getL10n()->t('Certificate has expired'), 422);
}
}
}
Expand Down
14 changes: 14 additions & 0 deletions src/tests/store/sign.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -357,6 +357,20 @@ describe('useSignStore', () => {
expect(result.type).toBe('signError')
expect(result.errors).toEqual(['err'])
})

it('returns signError for certificate validation errors and preserves API message', () => {
const store = useSignStore()
const apiErrors = [{ message: 'Certificate has been revoked', code: 422 }]
const error = {
response: { data: { ocs: { data: { errors: apiErrors } } } },
}

const result = store.parseSignError(error)

expect(result.type).toBe('signError')
expect(result.action).toBeUndefined()
expect(result.errors).toEqual(apiErrors)
})
})

describe('setFileToSign', () => {
Expand Down
47 changes: 47 additions & 0 deletions src/tests/views/SignPDF/Sign.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -400,6 +400,53 @@ describe('Sign.vue - signWithTokenCode', () => {
})
})

describe('Sign.vue - API error handling', () => {
it('keeps certificate validation errors in signStore and does not open certificate modal', async () => {
const SignComponent = await import('../../../views/SignPDF/_partials/Sign.vue')
const submitSignature = (SignComponent.default as any).methods.submitSignature

const apiErrors = [{ message: 'Certificate has been revoked', code: 422 }]
const context = {
loading: false,
elements: [],
canCreateSignature: false,
signRequestUuid: 'test-sign-request-uuid',
signMethodsStore: {
certificateEngine: 'openssl',
},
signatureElementsStore: {
signs: {},
},
actionHandler: {
showModal: vi.fn(),
closeModal: vi.fn(),
},
signStore: {
document: { id: 10 },
clearSigningErrors: vi.fn(),
setSigningErrors: vi.fn(),
submitSignature: vi.fn().mockRejectedValue({
type: 'signError',
errors: apiErrors,
}),
},
$emit: vi.fn(),
sidebarStore: {
hideSidebar: vi.fn(),
},
}

await submitSignature.call(context, {
method: 'password',
token: '123456',
})

expect(context.actionHandler.showModal).not.toHaveBeenCalled()
expect(context.signStore.setSigningErrors).toHaveBeenCalledWith(apiErrors)
expect(context.loading).toBe(false)
})
})

describe('proceedWithSigning - Full flow with WhatsApp token', () => {
let proceedWithSigningLogic: ProceedWithSigningLogic

Expand Down
5 changes: 5 additions & 0 deletions tests/php/Unit/Handler/SigningErrorHandlerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,11 @@ public static function libresignExceptionProvider(): array {
'message' => 'Password required',
'expectedAction' => JSActions::ACTION_CREATE_SIGNATURE_PASSWORD,
],
'code 422 revoked certificate triggers do nothing action' => [
'code' => 422,
'message' => 'Certificate has been revoked',
'expectedAction' => JSActions::ACTION_DO_NOTHING,
],
'code 401 triggers do nothing action' => [
'code' => 401,
'message' => 'Unauthorized',
Expand Down
28 changes: 27 additions & 1 deletion tests/php/Unit/Service/AccountServiceTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,13 @@
use OCA\Libresign\Db\SignRequestMapper;
use OCA\Libresign\Db\UserElement;
use OCA\Libresign\Db\UserElementMapper;
use OCA\Libresign\Enum\CRLReason;
use OCA\Libresign\Handler\CertificateEngine\CertificateEngineFactory;
use OCA\Libresign\Handler\SignEngine\Pkcs12Handler;
use OCA\Libresign\Helper\FileUploadHelper;
use OCA\Libresign\Helper\ValidateHelper;
use OCA\Libresign\Service\AccountService;
use OCA\Libresign\Service\Crl\CrlService;
use OCA\Libresign\Service\FolderService;
use OCA\Libresign\Service\IdDocsService;
use OCA\Libresign\Service\IdentifyMethod\IIdentifyMethod;
Expand Down Expand Up @@ -81,6 +83,7 @@ final class AccountServiceTest extends \OCA\Libresign\Tests\Unit\TestCase {
private RequestSignatureService&MockObject $requestSignatureService;
private Pkcs12Handler&MockObject $pkcs12Handler;
private FileUploadHelper&MockObject $uploadHelper;
private CrlService&MockObject $crlService;

public function setUp(): void {
parent::setUp();
Expand Down Expand Up @@ -115,6 +118,7 @@ public function setUp(): void {
$this->clientService = $this->createMock(ClientService::class);
$this->timeFactory = $this->createMock(TimeFactory::class);
$this->uploadHelper = $this->createMock(FileUploadHelper::class);
$this->crlService = $this->createMock(CrlService::class);
}

private function getService(): AccountService {
Expand Down Expand Up @@ -146,10 +150,32 @@ private function getService(): AccountService {
$this->folderService,
$this->clientService,
$this->timeFactory,
$this->uploadHelper
$this->uploadHelper,
$this->crlService
);
}

public function testDeletePfxRevokesCertificatesWithReasonAndDeletesPfx(): void {
$user = $this->createMock(IUser::class);
$user->method('getUID')->willReturn('admin');

$this->crlService->expects($this->once())
->method('revokeUserCertificates')
->with(
'admin',
CRLReason::CESSATION_OF_OPERATION,
'Certificate deleted by account owner.',
'admin'
)
->willReturn(1);

$this->pkcs12Handler->expects($this->once())
->method('deletePfx')
->with('admin');

$this->getService()->deletePfx($user);
}

#[DataProvider('provideValidateCertificateDataCases')]
public function testValidateCertificateDataUsingDataProvider($arguments, $expectedErrorMessage):void {
if (is_callable($arguments)) {
Expand Down
14 changes: 13 additions & 1 deletion tests/php/Unit/Service/IdentifyMethod/PasswordTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,12 @@ public static function providerValidateToSignWithError(): array {
}

#[DataProvider('providerValidateToSignWithCertificateData')]
public function testValidateToSignWithCertificateData(array $certificateData, bool $shouldThrow, string $expectedMessage = ''): void {
public function testValidateToSignWithCertificateData(
array $certificateData,
bool $shouldThrow,
string $expectedMessage = '',
?int $expectedCode = null,
): void {
$this->pkcs12Handler = $this->getPkcs12Instance(['getPfxOfCurrentSigner', 'setCertificate', 'setPassword', 'readCertificate']);
$this->pkcs12Handler->method('getPfxOfCurrentSigner')->willReturn('mock-pfx');
$this->pkcs12Handler->method('setCertificate')->willReturnSelf();
Expand All @@ -153,6 +158,9 @@ public function testValidateToSignWithCertificateData(array $certificateData, bo
if ($expectedMessage) {
$this->expectExceptionMessage($expectedMessage);
}
if ($expectedCode !== null) {
$this->expectExceptionCode($expectedCode);
}
}

$password->validateToSign();
Expand Down Expand Up @@ -183,13 +191,15 @@ public static function providerValidateToSignWithCertificateData(): array {
],
'shouldThrow' => true,
'expectedMessage' => 'Certificate has expired',
'expectedCode' => 422,
],
'invalid certificate - validTo_time_t is string' => [
'certificateData' => [
'validTo_time_t' => '1234567890',
],
'shouldThrow' => true,
'expectedMessage' => 'Invalid certificate',
'expectedCode' => 422,
],
'invalid certificate - validTo_time_t is null' => [
'certificateData' => [
Expand Down Expand Up @@ -233,6 +243,7 @@ public static function providerValidateToSignWithCertificateData(): array {
],
'shouldThrow' => true,
'expectedMessage' => 'Certificate has been revoked',
'expectedCode' => 422,
],
'valid certificate with crl validation' => [
'certificateData' => [
Expand All @@ -255,6 +266,7 @@ public static function providerValidateToSignWithCertificateData(): array {
],
'shouldThrow' => true,
'expectedMessage' => 'Certificate revocation status could not be verified',
'expectedCode' => 422,
],
'invalid certificate - crl validation empty string' => [
'certificateData' => [
Expand Down
Loading