Skip to content
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
ba29270
feat(utils): add SetupCheckUtils trait
guilhermercarvalho Mar 3, 2026
3ab2f05
test: add test infrastructure for setup checks
guilhermercarvalho Mar 3, 2026
71abe0c
feat(setup-check): add JavaSetupCheck
guilhermercarvalho Mar 3, 2026
59ce235
feat(setup-check): add JSignPdfSetupCheck
guilhermercarvalho Mar 3, 2026
1ab95c9
feat(setup-check): add PDFtkSetupCheck
guilhermercarvalho Mar 3, 2026
39cef8b
feat(setup-check): add PopplerSetupCheck
guilhermercarvalho Mar 3, 2026
ef6695f
feat(setup-check): add ImagickSetupCheck
guilhermercarvalho Mar 3, 2026
67c627b
feat(setup-check): add CertificateEngineSetupCheck
guilhermercarvalho Mar 3, 2026
350323e
feat(application): register new setup checks
guilhermercarvalho Mar 3, 2026
2b31973
refactor: deprecate ConfigureCheckService
guilhermercarvalho Mar 3, 2026
3766c42
refactor(setup-check): reorganize mocks and apply code standards
guilhermercarvalho Mar 5, 2026
1a56510
style(setup-check): apply code style fixes and improve Psalm annotations
guilhermercarvalho Mar 5, 2026
ec70886
fix(setup-check): address Psalm issues and improve robustness
guilhermercarvalho Mar 5, 2026
453c15a
test(setup-check): update PDFtkSetupCheckTest to match new error message
guilhermercarvalho Mar 5, 2026
037da11
test(setupcheck): reorganize test mocks into dedicated namespace
guilhermercarvalho Mar 6, 2026
c06327f
feat(command): update configure:check to use ISetupCheckManager
guilhermercarvalho Mar 6, 2026
934b7ac
refactor: replace deprecated ConfigureCheckService with SetupCheckRes…
guilhermercarvalho Mar 7, 2026
03283ef
fix(api): ensure OU field is always returned as string in certificate…
guilhermercarvalho Mar 7, 2026
c640d0f
refactor(service): remove deprecated ConfigureCheckService
guilhermercarvalho Mar 7, 2026
db325bd
test(service): add unit tests for SetupCheckResultService
guilhermercarvalho Mar 7, 2026
0ac2fed
chore(service): remove redundant comment in SetupCheckResultService
guilhermercarvalho Mar 7, 2026
ebb17c5
test(handler): update OU filtering test to expect string
guilhermercarvalho Mar 7, 2026
502124e
chore(service): remove redundant comments
guilhermercarvalho Mar 7, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
154 changes: 154 additions & 0 deletions lib/SetupCheck/JavaSetupCheck.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
<?php

declare(strict_types=1);
/**
* SPDX-FileCopyrightText: 2026 LibreCode coop and contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/

namespace OCA\Libresign\SetupCheck;

use OCA\Libresign\Helper\JavaHelper;
use OCA\Libresign\Service\Install\SignSetupService;
use OCA\Libresign\Service\Install\InstallService;
use OCP\IL10N;
use OCP\IURLGenerator;
use OCP\App\IAppManager;
use OCP\SetupCheck\ISetupCheck;
use OCP\SetupCheck\SetupResult;
use OCP\IConfig;
use Psr\Log\LoggerInterface;

class JavaSetupCheck implements ISetupCheck
{
use SetupCheckUtils;

private IL10N $l10n;
private JavaHelper $javaHelper;
private IConfig $systemConfig;

private SignSetupService $signSetupService;
private IURLGenerator $urlGenerator;
private IAppManager $appManager;
private LoggerInterface $logger;

public function __construct(
IL10N $l10n,
JavaHelper $javaHelper,
SignSetupService $signSetupService,
IURLGenerator $urlGenerator,
IAppManager $appManager,
LoggerInterface $logger,
IConfig $systemConfig
) {
$this->l10n = $l10n;
$this->javaHelper = $javaHelper;
$this->signSetupService = $signSetupService;
$this->urlGenerator = $urlGenerator;
$this->appManager = $appManager;
$this->logger = $logger;
$this->systemConfig = $systemConfig;
}

public function getName(): string
{
return $this->l10n->t('Java');
}

public function getCategory(): string
{
return 'system';
}

public function run(): SetupResult
{
$debugEnabled = $this->systemConfig->getSystemValueBool('debug', false);
$javaPath = $this->javaHelper->getJavaPath();

if (!$javaPath) {
return SetupResult::error(
$this->l10n->t('Java not installed'),
$this->l10n->t('Run occ libresign:install --java')
);
}

$verifyResult = $this->verifyResourceIntegrity('java', $debugEnabled);
if (!empty($verifyResult)) {
[$errorMsg, $tip] = $this->getErrorAndTipFromVerify($verifyResult, 'java', $debugEnabled, $this->l10n);
return SetupResult::error($errorMsg, $tip);
}

if (!file_exists($javaPath)) {
return SetupResult::error(
$this->l10n->t('Java binary not found: %s', [$javaPath]),
$this->l10n->t('Run occ libresign:install --java')
);
}

exec($javaPath . ' -version 2>&1', $output, $returnCode);
if (empty($output)) {
return SetupResult::error(
$this->l10n->t('Failed to execute Java. Sounds that your operational system is blocking the JVM.'),
'https://github.com/LibreSign/libresign/issues/2327#issuecomment-1961988790'
);
}
if ($returnCode !== 0) {
return SetupResult::error(
$this->l10n->t('Failure to check Java version.'),
$this->l10n->t('Run occ libresign:install --java')
);
}

$javaVersion = trim($output[0] ?? '');
if ($javaVersion !== InstallService::JAVA_VERSION) {
return SetupResult::error(
$this->l10n->t('Invalid java version. Found: %s expected: %s', [$javaVersion, InstallService::JAVA_VERSION]),
$this->l10n->t('Run occ libresign:install --java')
);
}

exec($javaPath . ' -XshowSettings:properties -version 2>&1', $encodingOutput);
$fullOutput = implode("\n", $encodingOutput);
preg_match('/native.encoding = (?<encoding>.*?)(\n|$)/', $fullOutput, $matches);
$encoding = $matches['encoding'] ?? null;

if (!$encoding) {
return SetupResult::error(
$this->l10n->t('Java encoding not found.'),
sprintf('The command %s need to have native.encoding', $javaPath . ' -XshowSettings:properties -version')
);
}

if (!str_contains($encoding, 'UTF-8')) {
$detectedEncoding = trim($encoding);
$phpLocale = setlocale(LC_CTYPE, 0) ?: 'not set';
$phpLcAll = getenv('LC_ALL') ?: 'not set';
$phpLang = getenv('LANG') ?: 'not set';

$tip = sprintf(
"Java detected encoding \"%s\" but UTF-8 is required.\n\n"
. "**Current PHP environment:**\n"
. "- LC_CTYPE: %s\n"
. "- LC_ALL: %s\n"
. "- LANG: %s\n\n"
. "**To fix this issue:**\n"
. "1. Set LC_ALL and LANG environment variables (e.g., LC_ALL=en_US.UTF-8) for your web server user\n"
. "2. Restart your web server after making changes\n"
. "3. Verify with command: `locale charmap` (should return UTF-8)\n\n"
. 'For more details, see: [Issue #4872](https://github.com/LibreSign/libresign/issues/4872)',
$detectedEncoding,
$phpLocale,
$phpLcAll,
$phpLang
);
return SetupResult::info(
$this->l10n->t('Non-UTF-8 encoding detected: %s. This may cause issues with accented or special characters', [$detectedEncoding]),
$tip
);
}

$message = $this->l10n->t('Java version: %s', [$javaVersion]) . "\n" .
$this->l10n->t('Java binary: %s', [$javaPath]);
return SetupResult::success($message);
}
}
74 changes: 74 additions & 0 deletions lib/SetupCheck/SetupCheckUtils.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
<?php

declare(strict_types=1);
/**
* SPDX-FileCopyrightText: 2026 LibreCode coop and contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/

namespace OCA\Libresign\SetupCheck;

use OCA\Libresign\Service\Install\SignSetupService;
use OCP\IURLGenerator;
use OCP\App\IAppManager;
use Psr\Log\LoggerInterface;
use OCP\IL10N;

trait SetupCheckUtils
{
private SignSetupService $signSetupService;
private IURLGenerator $urlGenerator;
private IAppManager $appManager;
private LoggerInterface $logger;

private function verifyResourceIntegrity(string $resource, bool $debugEnabled): array
{
$this->signSetupService->willUseLocalCert($debugEnabled);
$result = $this->signSetupService->verify(php_uname('m'), $resource);
if (count($result) === 1 && $debugEnabled) {
if (isset($result['SIGNATURE_DATA_NOT_FOUND']) || isset($result['EMPTY_SIGNATURE_DATA'])) {
return [];
}
}
return $result;
}

private function getErrorAndTipFromVerify(array $result, string $resource, bool $debugEnabled, IL10N $l10n): array
{
if (count($result) === 1 && !$debugEnabled) {
if (isset($result['SIGNATURE_DATA_NOT_FOUND'])) {
return [
$l10n->t('Signature data not found.'),
$l10n->t("Sounds that you are running from source code of LibreSign.\nEnable debug mode by: occ config:system:set debug --value true --type boolean"),
];
}
if (isset($result['EMPTY_SIGNATURE_DATA'])) {
return [
$l10n->t('Your signature data is empty.'),
$l10n->t("Sounds that you are running from source code of LibreSign.\nEnable debug mode by: occ config:system:set debug --value true --type boolean"),
];
}
}
if (isset($result['HASH_FILE_ERROR'])) {
if ($debugEnabled) {
return [
$l10n->t('Invalid hash of binaries files.'),
$l10n->t('Debug mode is enabled at your config.php and your LibreSign app was signed using a production signature. If you are not working at development of LibreSign, disable your debug mode or run the command: occ libresign install --%s --use-local-cert', [$resource]),
];
}
}
$this->logger->error('Invalid hash of binaries files', ['result' => $result]);
if ($this->appManager->isEnabledForUser('logreader')) {
return [
$l10n->t('Invalid hash of binaries files.'),
$l10n->t('Check your nextcloud.log file on %s and run occ libresign:install --all', [
$this->urlGenerator->linkToRouteAbsolute('settings.adminsettings.form', ['section' => 'logging'])
]),
];
}
return [
$l10n->t('Invalid hash of binaries files.'),
$l10n->t('Check your nextcloud.log file and run occ libresign:install --all'),
];
}
}
33 changes: 33 additions & 0 deletions tests/php/SetupCheckFunctions.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<?php

declare(strict_types=1);

/**
* SPDX-FileCopyrightText: 2026 LibreCode coop and contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/

namespace OCA\Libresign\SetupCheck;

if (!function_exists('OCA\Libresign\SetupCheck\file_exists')) {
function file_exists(string $filename): bool
{
if (array_key_exists($filename, FileSystemMock::$files)) {
return FileSystemMock::$files[$filename];
}
return \file_exists($filename);
}
}

if (!function_exists('OCA\Libresign\SetupCheck\exec')) {
function exec(string $command, &$output = null, &$result_code = null): string|false
{
if (array_key_exists($command, ExecMock::$commands)) {
$mock = ExecMock::$commands[$command];
$output = $mock['output'];
$result_code = $mock['result_code'];
return $output ? implode("\n", $output) : '';
}
return \exec($command, $output, $result_code);
}
}
14 changes: 14 additions & 0 deletions tests/php/Unit/SetupCheck/ExecMock.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<?php

declare(strict_types=1);

/**
* SPDX-FileCopyrightText: 2026 LibreCode coop and contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/

namespace OCA\Libresign\SetupCheck;

class ExecMock {
public static array $commands = [];
}
9 changes: 9 additions & 0 deletions tests/php/Unit/SetupCheck/FileSystemMock.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<?php

declare(strict_types=1);

namespace OCA\Libresign\SetupCheck;

class FileSystemMock {
public static array $files = [];
}
Loading