diff --git a/.github/workflows/release-app.yml b/.github/workflows/release-app.yml
index 04ddd8cd..660b6531 100644
--- a/.github/workflows/release-app.yml
+++ b/.github/workflows/release-app.yml
@@ -16,10 +16,12 @@ jobs:
name: setup node
with:
node-version: 20
+
- name: Setup BATS
uses: mig4/setup-bats@v1
with:
bats-version: 1.11.0
+
- uses: docker-practice/actions-setup-docker@master
- name: install nextcloud
env:
@@ -33,26 +35,6 @@ jobs:
CLIENT_SECRET: ${{ secrets.VAAS_CLIENT_SECRET }}
run: bats --no-parallelize-across-files --jobs 3 ./tests
- - name: test testuser clean Upload
- run: |
- STATUS_CODE=$(curl --silent -w "%{http_code}" -u testuser:myfancysecurepassword234 -T /tmp/clean.txt http://127.0.0.1/remote.php/dav/files/testuser/clean.txt)
- [[ $STATUS_CODE -ge 200 && $STATUS_CODE -lt 300 ]] || exit 1
-
- - name: test testuser pup Upload
- run: |
- STATUS_CODE=$(curl --silent -w "%{http_code}" -u testuser:myfancysecurepassword234 -T /tmp/pup.exe http://127.0.0.1/remote.php/dav/files/testuser/pup.exe)
- [[ $STATUS_CODE -ge 200 && $STATUS_CODE -lt 300 ]] || exit 1
-
- - name: test upload when vaas does not function
- env:
- CLIENT_ID: ${{ secrets.VAAS_CLIENT_ID }}
- CLIENT_SECRET: ${{ secrets.VAAS_CLIENT_SECRET }}
- run: |
- docker exec --user www-data -i nextcloud-container php occ config:app:set gdatavaas clientSecret --value="WRONG_PASSWORD"
- STATUS_CODE=$(curl --silent -w "%{http_code}" -u admin:admin -T /tmp/eicar.com.txt http://127.0.0.1/remote.php/dav/files/admin/eicar.com.txt)
- [[ $STATUS_CODE -ge 200 && $STATUS_CODE -lt 300 ]] || exit 1
- docker exec --user www-data -i nextcloud-container php occ config:app:set gdatavaas clientSecret --value="$CLIENT_SECRET"
-
- uses: actions/upload-artifact@master
with:
name: build-dir
diff --git a/appinfo/info.xml b/appinfo/info.xml
index 116b4592..da271bd9 100644
--- a/appinfo/info.xml
+++ b/appinfo/info.xml
@@ -35,6 +35,10 @@ If you have any questions about scanning, usage or similar, please feel free to
OCA\GDataVaas\Settings\VaasAdminSection
OCA\GDataVaas\Settings\VaasAdmin
+
+ OCA\GDataVaas\Command\ScanCommand
+ OCA\GDataVaas\Command\TagUnscannedCommand
+
diff --git a/composer.json b/composer.json
index a4d5e279..9ffd3b09 100644
--- a/composer.json
+++ b/composer.json
@@ -15,7 +15,8 @@
"require-dev": {
"nextcloud/ocp": "dev-stable28",
"psalm/phar": "^5.17.0",
- "nextcloud/coding-standard": "^v1.1.1"
+ "nextcloud/coding-standard": "^v1.1.1",
+ "symfony/console": "^6.4"
},
"scripts": {
"lint": "find lib -name \\*.php -not -path './vendor/*' -print0 | xargs -0 -n1 php -l",
diff --git a/lib/Command/ScanCommand.php b/lib/Command/ScanCommand.php
new file mode 100644
index 00000000..4006abfa
--- /dev/null
+++ b/lib/Command/ScanCommand.php
@@ -0,0 +1,81 @@
+logger = $logger;
+ $this->tagService = $tagService;
+ $this->scanService = $scanService;
+ $this->appConfig = $appConfig;
+ }
+
+ /**
+ * @return void
+ */
+ protected function configure() {
+ $this->setName('gdatavaas:scan');
+ $this->setDescription('scan files for malware');
+ }
+
+ /**
+ * @param $argument
+ * @return void
+ * @throws \OCP\DB\Exception if the database platform is not supported
+ */
+ protected function execute(InputInterface $input, OutputInterface $output): int {
+ $unscannedTagIsDisabled = $this->appConfig->getAppValue(self::APP_ID, 'disableUnscannedTag');
+ $quantity = $this->appConfig->getAppValue(self::APP_ID, 'scanQueueLength');
+ try {
+ $quantity = intval($quantity);
+ } catch (Exception) {
+ $quantity = 5;
+ }
+
+ $maliciousTag = $this->tagService->getTag(TagService::MALICIOUS);
+ $pupTag = $this->tagService->getTag(TagService::PUP);
+ $cleanTag = $this->tagService->getTag(TagService::CLEAN);
+ $unscannedTag = $this->tagService->getTag(TagService::UNSCANNED);
+ $wontScanTag = $this->tagService->getTag(TagService::WONT_SCAN);
+
+ if ($unscannedTagIsDisabled) {
+ $excludedTagIds = [$unscannedTag->getId(), $maliciousTag->getId(), $cleanTag->getId(), $pupTag->getId(), $wontScanTag->getId()];
+ $fileIds = $this->tagService->getFileIdsWithoutTags($excludedTagIds, $quantity);
+ } else {
+ $fileIds = $this->tagService->getFileIdsWithTag(TagService::UNSCANNED, $quantity, 0);
+ }
+
+ $this->logger->debug("Scanning files");
+
+ foreach ($fileIds as $fileId) {
+ try {
+ $this->scanService->scanFileById($fileId);
+ } catch (Exception $e) {
+ $output->writeln("Failed to scan file with id " . $fileId . ": " . $e->getMessage());
+ }
+ }
+
+ $this->logger->debug("Scanned " . count($fileIds) . " files");
+ return 0;
+ }
+}
diff --git a/lib/Command/TagUnscannedCommand.php b/lib/Command/TagUnscannedCommand.php
new file mode 100644
index 00000000..0e197459
--- /dev/null
+++ b/lib/Command/TagUnscannedCommand.php
@@ -0,0 +1,70 @@
+appConfig = $appConfig;
+ $this->tagService = $tagService;
+ $this->logger = $logger;
+ }
+
+ /**
+ * @return void
+ */
+ protected function configure() {
+ $this->setName('gdatavaas:tag-unscanned');
+ $this->setDescription('tags all files without tag from this app as unscanned');
+ }
+
+ protected function execute(InputInterface $input, OutputInterface $output): int {
+ $unscannedTagIsDisabled = $this->appConfig->getAppValue(self::APP_ID, 'disableUnscannedTag');
+ if ($unscannedTagIsDisabled) {
+ $this->tagService->removeTag(TagService::UNSCANNED);
+ $this->logger->info("Unscanned Tag is disabled, exiting.");
+ return 0;
+ }
+
+ $this->logger->debug("Tagging unscanned files");
+
+ $unscannedTag = $this->tagService->getTag(TagService::UNSCANNED);
+ $maliciousTag = $this->tagService->getTag(TagService::MALICIOUS);
+ $pupTag = $this->tagService->getTag(TagService::PUP);
+ $cleanTag = $this->tagService->getTag(TagService::CLEAN);
+ $wontScanTag = $this->tagService->getTag(TagService::WONT_SCAN);
+
+ $excludedTagIds = [$unscannedTag->getId(), $maliciousTag->getId(), $cleanTag->getId(), $pupTag->getId(), $wontScanTag->getId()];
+
+ $fileIds = $this->tagService->getFileIdsWithoutTags($excludedTagIds, 10000);
+ $this->logger->info("Found " . count($fileIds) . " files without tags from this app");
+
+ foreach ($fileIds as $fileId) {
+ if ($this->tagService->hasAnyButUnscannedTag($fileId)) {
+ continue;
+ }
+ $this->tagService->setTag($fileId, TagService::UNSCANNED);
+ }
+
+ $this->logger->debug("Tagged " . count($fileIds) . " unscanned files");
+
+ return 0;
+ }
+}
diff --git a/tests/functionality-parallel.bats b/tests/functionality-parallel.bats
index 6241a336..72113a74 100755
--- a/tests/functionality-parallel.bats
+++ b/tests/functionality-parallel.bats
@@ -3,29 +3,26 @@
FOLDER_PREFIX=./tmp/functionality-parallel
TESTUSER=testuser
TESTUSER_PASSWORD=myfancysecurepassword234
+EICAR_STRING='X5O!P%@AP[4\PZX54(P^)7CC)7}$EICAR-STANDARD-ANTIVIRUS-TEST-FILE!$H+H*'
+CLEAN_STRING='nothingwronghere'
setup_file() {
mkdir -p $FOLDER_PREFIX
+ curl --output $FOLDER_PREFIX/pup.exe http://amtso.eicar.org/PotentiallyUnwanted.exe
docker exec --env OC_PASS=$TESTUSER_PASSWORD --user www-data nextcloud-container php occ user:add $TESTUSER --password-from-env || echo "already exists"
docker exec --user www-data -i nextcloud-container php occ config:app:set gdatavaas clientSecret --value="$CLIENT_SECRET"
sleep 2
}
-setup() {
- echo 'nothingwronghere' > $FOLDER_PREFIX/clean.txt
- echo 'X5O!P%@AP[4\PZX54(P^)7CC)7}$EICAR-STANDARD-ANTIVIRUS-TEST-FILE!$H+H*' > $FOLDER_PREFIX/eicar.com.txt
- curl --output $FOLDER_PREFIX/pup.exe http://amtso.eicar.org/PotentiallyUnwanted.exe
-}
-
@test "test admin eicar Upload" {
- RESULT=$(curl --silent -w "%{http_code}" -u admin:admin -T $FOLDER_PREFIX/eicar.com.txt http://127.0.0.1/remote.php/dav/files/admin/functionality-parallel.eicar.com.txt)
+ RESULT=$(echo $EICAR_STRING | curl --silent -w "%{http_code}" -u admin:admin -T - http://127.0.0.1/remote.php/dav/files/admin/functionality-parallel.eicar.com.txt)
echo "Actual: $RESULT"
curl --silent -q -u admin:admin -X DELETE http://127.0.0.1/remote.php/dav/files/admin/functionality-parallel.eicar.com.txt
[[ "$RESULT" =~ "Upload cannot be completed." ]]
}
@test "test admin clean upload" {
- RESULT=$(curl -w "%{http_code}" -u admin:admin -T $FOLDER_PREFIX/clean.txt http://127.0.0.1/remote.php/dav/files/admin/functionality-parallel.clean.txt)
+ RESULT=$(echo $CLEAN_STRING | curl -w "%{http_code}" -u admin:admin -T - http://127.0.0.1/remote.php/dav/files/admin/functionality-parallel.clean.txt)
echo "Actual: $RESULT"
curl --silent -q -u admin:admin -X DELETE http://127.0.0.1/remote.php/dav/files/admin/functionality-parallel.clean.txt
[[ $RESULT -ge 200 && $RESULT -lt 300 ]]
@@ -39,7 +36,7 @@ setup() {
}
@test "test testuser eicar Upload" {
- RESULT=$(curl --silent -w "%{http_code}" -u $TESTUSER:$TESTUSER_PASSWORD -T ./tmp/functionality-sequential//eicar.com.txt http://127.0.0.1/remote.php/dav/files/$TESTUSER/functionality-parallel.eicar.com.txt)
+ RESULT=$(echo $EICAR_STRING | curl --silent -w "%{http_code}" -u $TESTUSER:$TESTUSER_PASSWORD -T - http://127.0.0.1/remote.php/dav/files/$TESTUSER/functionality-parallel.eicar.com.txt)
echo "Actual: $RESULT"
docker exec --user www-data -i nextcloud-container php occ config:app:get gdatavaas clientSecret
curl --silent -q -u $TESTUSER:$TESTUSER_PASSWORD -X DELETE http://127.0.0.1/remote.php/dav/files/$TESTUSER/functionality-parallel.eicar.com.txt
@@ -47,14 +44,14 @@ setup() {
}
@test "test testuser clean Upload" {
- STATUS_CODE=$(curl --silent -w "%{http_code}" -w "%{http_code}" -u $TESTUSER:$TESTUSER_PASSWORD -T $FOLDER_PREFIX/clean.txt http://127.0.0.1/remote.php/dav/files/$TESTUSER/functionality-parallel.clean.txt)
+ STATUS_CODE=$(echo $CLEAN_STRING | curl --silent -w "%{http_code}" -u $TESTUSER:$TESTUSER_PASSWORD -T - http://127.0.0.1/remote.php/dav/files/$TESTUSER/functionality-parallel.clean.txt)
echo "Actual: $RESULT"
curl --silent -q -u $TESTUSER:$TESTUSER_PASSWORD -X DELETE http://127.0.0.1/remote.php/dav/files/$TESTUSER/functionality-parallel.clean.txt
[[ $STATUS_CODE -ge 200 && $STATUS_CODE -lt 300 ]] || exit 1
}
@test "test testuser pup Upload" {
- RESULT=$(curl --silent -w "%{http_code}" -w "%{http_code}" -u $TESTUSER:$TESTUSER_PASSWORD -T $FOLDER_PREFIX/pup.exe http://127.0.0.1/remote.php/dav/files/$TESTUSER/functionality-parallel.pup.exe)
+ RESULT=$(curl --silent -w "%{http_code}" -u $TESTUSER:$TESTUSER_PASSWORD -T $FOLDER_PREFIX/pup.exe http://127.0.0.1/remote.php/dav/files/$TESTUSER/functionality-parallel.pup.exe)
echo "Actual: $RESULT"
curl --silent -q -u $TESTUSER:$TESTUSER_PASSWORD -X DELETE http://127.0.0.1/remote.php/dav/files/$TESTUSER/functionality-parallel.pup.exe
[[ $RESULT -ge 200 && $RESULT -lt 300 ]] || exit 1
diff --git a/tests/functionality-sequential.bats b/tests/functionality-sequential.bats
index d1a5cc49..195e91f7 100644
--- a/tests/functionality-sequential.bats
+++ b/tests/functionality-sequential.bats
@@ -3,19 +3,18 @@
FOLDER_PREFIX=./tmp/functionality-sequential
TESTUSER=testuser
TESTUSER_PASSWORD=myfancysecurepassword234
+EICAR_STRING='X5O!P%@AP[4\PZX54(P^)7CC)7}$EICAR-STANDARD-ANTIVIRUS-TEST-FILE!$H+H*'
setup_file() {
mkdir -p $FOLDER_PREFIX/
- echo 'nothingwronghere' > $FOLDER_PREFIX/clean.txt
- echo 'X5O!P%@AP[4\PZX54(P^)7CC)7}$EICAR-STANDARD-ANTIVIRUS-TEST-FILE!$H+H*' > $FOLDER_PREFIX/eicar.com.txt
curl --output $FOLDER_PREFIX/pup.exe http://amtso.eicar.org/PotentiallyUnwanted.exe
+ docker exec --env OC_PASS=$TESTUSER_PASSWORD --user www-data nextcloud-container php occ user:add $TESTUSER --password-from-env || echo "already exists"
BATS_NO_PARALLELIZE_WITHIN_FILE=true
}
-
@test "test upload when vaas does not function" {
docker exec --user www-data -i nextcloud-container php occ config:app:set gdatavaas clientSecret --value="WRONG_PASSWORD"
- RESULT=$(curl --silent -w "%{http_code}" -u admin:admin -T $FOLDER_PREFIX/eicar.com.txt http://127.0.0.1/remote.php/dav/files/admin/functionality-sequential.eicar.com.txt)
+ RESULT=$(echo $EICAR_STRING | curl --silent -w "%{http_code}" -u admin:admin -T - http://127.0.0.1/remote.php/dav/files/admin/functionality-sequential.eicar.com.txt)
docker exec --user www-data -i nextcloud-container php occ config:app:set gdatavaas clientSecret --value="$CLIENT_SECRET"
curl --silent -q -u admin:admin -X DELETE http://127.0.0.1/remote.php/dav/files/admin/functionality-sequential.eicar.com.txt
@@ -25,31 +24,42 @@ setup_file() {
@test "test croned scan for admin files" {
docker exec --user www-data -i nextcloud-container php occ config:app:set gdatavaas clientSecret --value="WRONG_PASSWORD"
- curl --silent -w "%{http_code}" -u admin:admin -T $FOLDER_PREFIX/eicar.com.txt http://127.0.0.1/remote.php/dav/files/admin/admin.functionality-sequential.eicar.com.txt
+ echo $EICAR_STRING | curl --silent -w "%{http_code}" -u admin:admin -T - http://127.0.0.1/remote.php/dav/files/admin/admin.functionality-sequential.eicar.com.txt
+ curl --silent -w "%{http_code}" -u admin:admin -T $FOLDER_PREFIX/pup.exe http://127.0.0.1/remote.php/dav/files/admin/admin.pup.exe
+
docker exec --user www-data -i nextcloud-container php occ config:app:set gdatavaas clientSecret --value="$CLIENT_SECRET"
- docker exec --user www-data -i nextcloud-container php ./cron.php # tag unscanned
- docker exec --user www-data -i nextcloud-container php ./cron.php # actual scan
- LOGS=$(docker exec --user www-data -i nextcloud-container cat data/nextcloud.log | egrep "admin.functionality-sequential.eicar.com.txt|Readme.md" )
+ docker exec -i --user www-data nextcloud-container php occ gdatavaas:tag-unscanned
+ docker exec -i --user www-data nextcloud-container php occ gdatavaas:scan
+
+ LOGS=$(docker exec --user www-data -i nextcloud-container cat data/nextcloud.log | egrep "admin.functionality-sequential.eicar.com.txt|Readme.md|admin.pup.exe" )
curl --silent -q -u admin:admin -X DELETE http://127.0.0.1/remote.php/dav/files/admin/admin.functionality-sequential.eicar.com.txt
+ curl --silent -q -u admin:admin -X DELETE http://127.0.0.1/remote.php/dav/files/admin/admin.pup.exe
[[ $LOGS =~ ^.*admin.functionality-sequential.eicar.com.txt.*Verdict:.*Malicious ]]
+ [[ $LOGS =~ ^.*admin.pup.exe.*Verdict:.*Pup ]]
[[ $LOGS =~ ^.*Readme.md.*Verdict:.*Clean ]]
}
@test "test croned scan for testuser files" {
docker exec --user www-data -i nextcloud-container php occ config:app:set gdatavaas clientSecret --value="WRONG_PASSWORD"
- curl --silent -w "%{http_code}" -u $TESTUSER:$TESTUSER_PASSWORD -T $FOLDER_PREFIX/eicar.com.txt http://127.0.0.1/remote.php/dav/files/$TESTUSER/$TESTUSER.functionality-sequential.eicar.com.txt
+
+ echo $EICAR_STRING |curl --silent -w "%{http_code}" -u $TESTUSER:$TESTUSER_PASSWORD -T - http://127.0.0.1/remote.php/dav/files/$TESTUSER/$TESTUSER.functionality-sequential.eicar.com.txt
+ curl --silent -w "%{http_code}" -u $TESTUSER:$TESTUSER_PASSWORD -T $FOLDER_PREFIX/pup.exe http://127.0.0.1/remote.php/dav/files/$TESTUSER/$TESTUSER.pup.exe
+
docker exec --user www-data -i nextcloud-container php occ config:app:set gdatavaas clientSecret --value="$CLIENT_SECRET"
- docker exec --user www-data -i nextcloud-container php ./cron.php # tag unscanned
- docker exec --user www-data -i nextcloud-container php ./cron.php # actual scan
- LOGS=$(docker exec --user www-data -i nextcloud-container cat data/nextcloud.log | egrep "$TESTUSER.functionality-sequential.eicar.com.txt")
+ docker exec -i --user www-data nextcloud-container php occ gdatavaas:tag-unscanned
+ docker exec -i --user www-data nextcloud-container php occ gdatavaas:scan
+
+ LOGS=$(docker exec --user www-data -i nextcloud-container cat data/nextcloud.log | egrep "$TESTUSER.functionality-sequential.eicar.com.txt|$TESTUSER.pup.exe")
curl --silent -q -u $TESTUSER:$TESTUSER_PASSWORD -X DELETE http://127.0.0.1/remote.php/dav/files/$TESTUSER/$TESTUSER.functionality-sequential.eicar.com.txt
+ curl --silent -q -u $TESTUSER:$TESTUSER_PASSWORD -X DELETE http://127.0.0.1/remote.php/dav/files/$TESTUSER/$TESTUSER.pup.exe
[[ $LOGS =~ ^.*$TESTUSER.functionality-sequential.eicar.com.txt.*Verdict:.*Malicious ]]
+ [[ $LOGS =~ ^.*$TESTUSER.pup.exe.*Verdict:.*Pup ]]
}
tearddown_file() {