From f5620a2812abc5b2071af70dad49041c16c1368b Mon Sep 17 00:00:00 2001 From: Lennart Dohmann Date: Tue, 26 Aug 2025 15:07:46 +0000 Subject: [PATCH] Add additional settings - Control scan timeout - Control usage of cache - Control usage of hash lookup --- .devcontainer/devcontainer.json | 8 ++- Makefile | 1 + appinfo/routes.php | 4 +- css/style.css | 4 ++ lib/Controller/SettingsController.php | 17 +++++++ lib/Service/VerdictService.php | 7 ++- lib/Settings/VaasAdmin.php | 5 +- src/admin-settings.js | 11 +++- src/files-action.js | 65 +++++++++++++----------- templates/admin.php | 18 ++++++- tests/bats/functionality-parallel.bats | 3 +- tests/bats/functionality-sequential.bats | 4 +- 12 files changed, 106 insertions(+), 41 deletions(-) diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index f92a7009..77e82be6 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -8,10 +8,14 @@ "customizations": { "vscode": { "extensions": [ - "junstyle.php-cs-fixer", "stylelint.vscode-stylelint", "jetmartin.bats", - "recca0120.vscode-phpunit" + "ms-azuretools.vscode-containers", + "github.vscode-github-actions", + "ms-vscode.makefile-tools", + "ms-vscode-remote.remote-containers", + "DEVSENSE.phptools-vscode", + "redhat.vscode-yaml" ] } }, diff --git a/Makefile b/Makefile index 6ebbb1c0..4ba00d24 100644 --- a/Makefile +++ b/Makefile @@ -101,6 +101,7 @@ local: build docker stop nextcloud-container || true docker container rm nextcloud-container || true docker run --rm -d -p 8080:80 --name nextcloud-container -e SERVER_BRANCH="v31.0.8" -v .:/var/www/html/apps-extra/gdatavaas ghcr.io/juliusknorr/nextcloud-dev-php84:latest + composer install # Builds the app for production and prepares it for the appstore under ./build/artifacts .PHONY: appstore diff --git a/appinfo/routes.php b/appinfo/routes.php index 0d0741d2..ffaaf294 100644 --- a/appinfo/routes.php +++ b/appinfo/routes.php @@ -27,6 +27,8 @@ => '/getSendMailSummaryOfMaliciousFiles', 'verb' => 'GET'], ['name' => 'settings#setSendMailSummaryOfMaliciousFiles', 'url' => '/setSendMailSummaryOfMaliciousFiles', 'verb' => 'POST'], - ['name' => 'settings#testsettings', 'url' => '/testsettings', 'verb' => 'POST'] + ['name' => 'settings#testsettings', 'url' => '/testsettings', 'verb' => 'POST'], + ['name' => 'settings#getCache', 'url' => '/getCache', 'verb' => 'GET'], + ['name' => 'settings#getHashlookup', 'url' => '/getHashlookup', 'verb' => 'GET'] ] ]; diff --git a/css/style.css b/css/style.css index 98e73ef9..4099cff2 100644 --- a/css/style.css +++ b/css/style.css @@ -25,6 +25,10 @@ select { box-sizing: border-box; } +.basic_settings_table .input_field input[type=checkbox] { + margin: 8px 0 8px 50px; +} + input.toggle-round { display: none; } diff --git a/lib/Controller/SettingsController.php b/lib/Controller/SettingsController.php index 6ed5d532..651a40dc 100644 --- a/lib/Controller/SettingsController.php +++ b/lib/Controller/SettingsController.php @@ -51,6 +51,9 @@ public function setconfig( $doNotScanThis, $notifyMails, $maxScanSize, + $timeout, + bool $cache, + bool $hashlookup, ): JSONResponse { if (!empty($notifyMails)) { $mails = explode(',', preg_replace('/\s+/', '', $notifyMails)); @@ -63,6 +66,9 @@ public function setconfig( if ((int)$maxScanSize < 1) { return new JSONResponse(['status' => 'error', 'message' => 'Invalid max scan size: ' . $maxScanSize]); } + if ((int)$timeout < 1) { + return new JSONResponse(['status' => 'error', 'message' => 'Invalid timeout: ' . $timeout]); + } $this->config->setValueString($this->appName, 'username', $username); $this->config->setValueString($this->appName, 'password', $password); $this->config->setValueString($this->appName, 'clientId', $clientId); @@ -73,6 +79,9 @@ public function setconfig( $this->config->setValueString($this->appName, 'doNotScanThis', $doNotScanThis); $this->config->setValueString($this->appName, 'notifyMails', $notifyMails); $this->config->setValueInt($this->appName, 'maxScanSizeInMB', (int)$maxScanSize); + $this->config->setValueInt($this->appName, 'timeout', (int)$timeout); + $this->config->setValueBool($this->appName, 'cache', $cache); + $this->config->setValueBool($this->appName, 'hashlookup', $hashlookup); return new JSONResponse(['status' => 'success']); } @@ -186,4 +195,12 @@ public function testSettings(string $tokenEndpoint, string $vaasUrl): JSONRespon return new JSONResponse(['status' => 'error', 'message' => $e->getMessage()]); } } + + public function getCache(): JSONResponse { + return new JSONResponse(['status' => $this->config->getValueBool($this->appName, 'cache', true)]); + } + + public function getHashlookup(): JSONResponse { + return new JSONResponse(['status' => $this->config->getValueBool($this->appName, 'hashlookup', true)]); + } } diff --git a/lib/Service/VerdictService.php b/lib/Service/VerdictService.php index 5a14dd1a..7e9f12c9 100644 --- a/lib/Service/VerdictService.php +++ b/lib/Service/VerdictService.php @@ -210,7 +210,12 @@ public function createAndConnectVaas(): Vaas { $this->vaasUrl = 'https://' . substr($this->vaasUrl, 6); } } - $options = new VaasOptions(true, true, $this->vaasUrl); + $options = new VaasOptions( + useHashLookup: $this->appConfig->getValueBool(Application::APP_ID, 'hashlookup', true), + useCache: $this->appConfig->getValueBool(Application::APP_ID, 'cache', true), + vaasUrl: $this->vaasUrl, + timeout: $this->appConfig->getValueInt(Application::APP_ID, 'timeout', 300) + ); return Vaas::builder() ->withAuthenticator($this->getAuthenticator($this->authMethod, $this->tokenEndpoint)) ->withOptions($options) diff --git a/lib/Settings/VaasAdmin.php b/lib/Settings/VaasAdmin.php index 4e8b92b1..4e3e050f 100644 --- a/lib/Settings/VaasAdmin.php +++ b/lib/Settings/VaasAdmin.php @@ -57,7 +57,10 @@ public function getForm(): TemplateResponse { => $this->config->getValueBool(Application::APP_ID, 'sendMailOnVirusUpload'), 'notifyAdminEnabled' => $this->config->getValueBool(Application::APP_ID, 'notifyAdminEnabled'), 'maxScanSizeInMB' - => $this->config->getValueInt(Application::APP_ID, 'maxScanSizeInMB', 256) + => $this->config->getValueInt(Application::APP_ID, 'maxScanSizeInMB', 256), + 'timeout' => $this->config->getValueInt(Application::APP_ID, 'timeout', 300), + 'cache' => $this->config->getValueBool(Application::APP_ID, 'cache', true), + 'hashlookup' => $this->config->getValueBool(Application::APP_ID, 'hashlookup', true), ]; return new TemplateResponse(Application::APP_ID, 'admin', $params); diff --git a/src/admin-settings.js b/src/admin-settings.js index 56a0a494..305ea4eb 100644 --- a/src/admin-settings.js +++ b/src/admin-settings.js @@ -66,6 +66,9 @@ document.addEventListener('DOMContentLoaded', async () => { const doNotScanThis = document.querySelector('#doNotScanThis').value; const notifyMails = document.querySelector('#notify_mails').value; const maxScanSize = document.querySelector('#max-scan-size').value; + const timeout = document.querySelector('#timeout').value; + const cache = document.querySelector('#cache').checked; + const hashlookup = document.querySelector('#hashlookup').checked; const response = await postData(OC.generateUrl('apps/gdatavaas/setconfig'), { username: username, @@ -77,7 +80,10 @@ document.addEventListener('DOMContentLoaded', async () => { scanOnlyThis, doNotScanThis, notifyMails, - maxScanSize + maxScanSize, + timeout, + cache, + hashlookup }); const msgElement = document.querySelector('#auth_save_msg'); @@ -203,4 +209,7 @@ document.addEventListener('DOMContentLoaded', async () => { scanCounter.textContent = ' N/A'; console.log('Error getting files counter:', filesCounter['message']); } + + document.querySelector('#cache').checked = (await getData(OC.generateUrl('apps/gdatavaas/getCache'))).status; + document.querySelector('#hashlookup').checked = (await getData(OC.generateUrl('apps/gdatavaas/getHashlookup'))).status; }); diff --git a/src/files-action.js b/src/files-action.js index 347cf0ab..cd6f8482 100644 --- a/src/files-action.js +++ b/src/files-action.js @@ -15,37 +15,42 @@ registerFileAction(new FileAction({ }, iconSvgInline: () => Magnifier, async exec(file) { - const fileId = file.fileid; - let response = await fetch(OC.generateUrl('/apps/gdatavaas/scan'), { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - 'requesttoken': oc_requesttoken - }, - body: JSON.stringify({ - fileId: fileId - }) - }); - let vaasVerdict = await response.json(); - if (response.status === 200) { - switch (vaasVerdict['verdict']) { - case 'Malicious': - showError(t('gdatavaas', 'The file "' + file.basename + '" has been scanned with G DATA as verdict Malicious')); - break; - case 'Clean': - showSuccess(t('gdatavaas', 'The file "' + file.basename + '" has been scanned with G DATA as verdict Clean')); - break; - case 'Pup': - showWarning(t('gdatavaas', 'The file "' + file.basename + '" has been scanned with G DATA as ' + - 'verdict PUP (Potentially unwanted program)')); - break; - } - } else { - try { - showError(t('gdatavaas', vaasVerdict.error)); - } catch (e) { - showError(t('gdatavaas', 'An unknown error occurred while scanning the file')); + try { + const fileId = file.fileid; + let response = await fetch(OC.generateUrl('/apps/gdatavaas/scan'), { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'requesttoken': oc_requesttoken + }, + body: JSON.stringify({ + fileId: fileId + }) + }); + let vaasVerdict = await response.json(); + if (response.status === 200) { + switch (vaasVerdict['verdict']) { + case 'Malicious': + showError(t('gdatavaas', 'The file "' + file.basename + '" has been scanned with G DATA as verdict Malicious')); + break; + case 'Clean': + showSuccess(t('gdatavaas', 'The file "' + file.basename + '" has been scanned with G DATA as verdict Clean')); + break; + case 'Pup': + showWarning(t('gdatavaas', 'The file "' + file.basename + '" has been scanned with G DATA as ' + + 'verdict PUP (Potentially unwanted program)')); + break; + } + } else { + try { + showError(t('gdatavaas', vaasVerdict.error)); + } catch (e) { + showError(t('gdatavaas', 'An unknown error occurred while scanning the file')); + } } } + catch (e) { + showError(t('gdatavaas', 'An error occurred while trying to scan the file: ') + e); + } }, })) diff --git a/templates/admin.php b/templates/admin.php index 095bb5e2..3825c674 100644 --- a/templates/admin.php +++ b/templates/admin.php @@ -19,7 +19,7 @@
You may use self registration and create a new username and password by yourself here for free.
- + + + + + + + + + + + + +
" class="visible">
" class="visible">
/>
/>
diff --git a/tests/bats/functionality-parallel.bats b/tests/bats/functionality-parallel.bats index 97b38047..0f6edf30 100755 --- a/tests/bats/functionality-parallel.bats +++ b/tests/bats/functionality-parallel.bats @@ -16,6 +16,7 @@ setup_file() { # this is cache busting $DOCKER_EXEC_WITH_USER nextcloud-container php occ files:scan --all sleep 2 + $DOCKER_EXEC_WITH_USER nextcloud-container php occ app:enable gdatavaas } @test "test admin eicar Upload" { @@ -68,7 +69,6 @@ setup_file() { dd if=/dev/zero of=$FOLDER_PREFIX/too-large.dat bs=268435457 count=1 docker cp $FOLDER_PREFIX/too-large.dat nextcloud-container:/var/www/html/data/$TESTUSER/files/$TESTUSER.too-large.dat - docker exec -i nextcloud-container chown www-data:www-data /var/www/html/data/$TESTUSER/files/$TESTUSER.too-large.dat $DOCKER_EXEC_WITH_USER nextcloud-container php occ files:scan --all $DOCKER_EXEC_WITH_USER nextcloud-container php occ gdatavaas:scan @@ -93,7 +93,6 @@ setup_file() { @test "test unscanned job for testuser" { docker cp $FOLDER_PREFIX/pup.exe nextcloud-container:/var/www/html/data/$TESTUSER/files/$TESTUSER.unscanned.pup.exe - docker exec -i nextcloud-container chown www-data:www-data /var/www/html/data/$TESTUSER/files/$TESTUSER.unscanned.pup.exe $DOCKER_EXEC_WITH_USER nextcloud-container php occ files:scan $TESTUSER $DOCKER_EXEC_WITH_USER nextcloud-container php occ gdatavaas:tag-unscanned diff --git a/tests/bats/functionality-sequential.bats b/tests/bats/functionality-sequential.bats index 39833f34..10ce98ef 100644 --- a/tests/bats/functionality-sequential.bats +++ b/tests/bats/functionality-sequential.bats @@ -6,7 +6,7 @@ setup_file() { source tests/bats/.env-test || return 1 - source .env-local || echo "No .env-local file found." + source .env-local || source .env || echo "No .env files found." mkdir -p $FOLDER_PREFIX/ curl --output $FOLDER_PREFIX/pup.exe http://amtso.eicar.org/PotentiallyUnwanted.exe $DOCKER_EXEC_WITH_USER --env OC_PASS=$TESTUSER_PASSWORD nextcloud-container php occ user:add $TESTUSER --password-from-env || echo "already exists" @@ -16,7 +16,7 @@ setup_file() { BATS_NO_PARALLELIZE_WITHIN_FILE=true # this is cache busting $DOCKER_EXEC_WITH_USER nextcloud-container php occ files:scan --all - docker exec nextcloud-container chown -R www-data:www-data /var/www/html/ + $DOCKER_EXEC_WITH_USER nextcloud-container php occ app:enable gdatavaas } @test "test upload when vaas does not function" {