diff --git a/.gitignore b/.gitignore index d85572b6..93d5a1ba 100644 --- a/.gitignore +++ b/.gitignore @@ -61,4 +61,5 @@ package-lock.json move_app.sh dev-environment*/ js/ -*.cache \ No newline at end of file +*.cache +.uuid \ No newline at end of file diff --git a/README.md b/README.md index 8316b764..924c4253 100644 --- a/README.md +++ b/README.md @@ -53,3 +53,10 @@ The command may take a while and starts Nextcloud directly. Nextcloud can then b In the future, Nextcloud can then be started again by changing to the folder "nextcloud-docker-dev" and running ```docker compose up nextcloud proxy```. For more information see the [Nextcloud app development tutorials](https://cloud.nextcloud.com/s/iyNGp8ryWxc7Efa). These steps set up the official Nextcloud Dev Environment. It uses an SQLite databse. If you want to test on a production like instance you can set up a real Nextcloud Server using this [compose file](compose.yaml). + +### Useful commands + +To trigger cronjobs manually you can use the following command: +```bash +docker exec --user www-data {nextcloud_container} php /var/www/html/cron.php +``` diff --git a/compose.yaml b/compose.yaml index 28a1a1c7..37ce9978 100644 --- a/compose.yaml +++ b/compose.yaml @@ -38,6 +38,8 @@ services: hostname: nextcloud-db restart: unless-stopped image: mariadb:10.8 + ports: + - "3306:3306" environment: MYSQL_ROOT_PASSWORD: root MYSQL_DATABASE: ncdb diff --git a/lib/BackgroundJobs/ScanJob.php b/lib/BackgroundJobs/ScanJob.php index 0d721ada..c1a31747 100644 --- a/lib/BackgroundJobs/ScanJob.php +++ b/lib/BackgroundJobs/ScanJob.php @@ -5,9 +5,10 @@ use Exception; use OCA\GDataVaas\Service\TagService; use OCA\GDataVaas\Service\VerdictService; -use OCP\BackgroundJob\TimedJob; use OCP\AppFramework\Utility\ITimeFactory; +use OCP\BackgroundJob\TimedJob; use OCP\IConfig; +use Psr\Log\LoggerInterface; class ScanJob extends TimedJob { @@ -16,11 +17,13 @@ class ScanJob extends TimedJob private TagService $tagService; private VerdictService $scanService; private IConfig $appConfig; + private LoggerInterface $logger; - public function __construct(ITimeFactory $time, TagService $tagService, VerdictService $scanService, IConfig $appConfig) + public function __construct(LoggerInterface $logger, ITimeFactory $time, TagService $tagService, VerdictService $scanService, IConfig $appConfig) { parent::__construct($time); + $this->logger = $logger; $this->tagService = $tagService; $this->scanService = $scanService; $this->appConfig = $appConfig; @@ -37,14 +40,17 @@ public function __construct(ITimeFactory $time, TagService $tagService, VerdictS */ protected function run($argument): void { - $unscannedTagIsDisabled = $this->appConfig->getAppValue(self::APP_ID, 'disableUnscannedTag'); $autoScan = $this->appConfig->getAppValue(self::APP_ID, 'autoScanFiles'); if (!$autoScan) { return; } + $unscannedTagIsDisabled = $this->appConfig->getAppValue(self::APP_ID, 'disableUnscannedTag'); $autoScanOnlyNewFiles = $this->appConfig->getAppValue(self::APP_ID, 'scanOnlyNewFiles'); $quantity = $this->appConfig->getAppValue(self::APP_ID, 'scanQueueLength'); - if ($quantity == "") { + try { + $quantity = intval($quantity); + } + catch (Exception) { $quantity = 5; } @@ -67,12 +73,14 @@ protected function run($argument): void $fileIds = $this->tagService->getRandomTaggedFileIds([$maliciousTag->getId(), $cleanTag->getId(), $unscannedTag->getId(), $pupTag->getId()], $quantity, $unscannedTag); } } + + $this->logger->debug("Scanning " . count($fileIds) . " files: " . implode(", ", $fileIds)); foreach ($fileIds as $fileId) { try { $this->scanService->scanFileById($fileId); - } catch (Exception) { - // Do nothing + } catch (Exception $e) { + $this->logger->error("Failed to scan file with id " . $fileId . ": " . $e->getMessage()); } } } diff --git a/lib/Service/VerdictService.php b/lib/Service/VerdictService.php index b9fa4dac..4241d6dc 100644 --- a/lib/Service/VerdictService.php +++ b/lib/Service/VerdictService.php @@ -35,7 +35,7 @@ class VerdictService private IConfig $appConfig; private FileService $fileService; private TagService $tagService; - private Vaas $vaas; + private ?Vaas $vaas = null; private LoggerInterface $logger; public function __construct(LoggerInterface $logger, IConfig $appConfig, FileService $fileService, TagService $tagService) @@ -52,23 +52,6 @@ public function __construct(LoggerInterface $logger, IConfig $appConfig, FileSer $this->clientSecret = $this->appConfig->getAppValue(self::APP_ID, 'clientSecret'); $this->username = $this->appConfig->getAppValue(self::APP_ID, 'username'); $this->password = $this->appConfig->getAppValue(self::APP_ID, 'password'); - - if ($this->authMethod === 'ResourceOwnerPassword') { - $this->authenticator = new ResourceOwnerPasswordGrantAuthenticator( - "nextcloud-customer", - $this->username, - $this->password, - $this->tokenEndpoint - ); - } elseif ($this->authMethod === 'ClientCredentials') { - $this->authenticator = new ClientCredentialsGrantAuthenticator( - $this->clientId, - $this->clientSecret, - $this->tokenEndpoint - ); - } - - $this->vaas = new Vaas($this->vaasUrl); } /** @@ -80,10 +63,10 @@ public function __construct(LoggerInterface $logger, IConfig $appConfig, FileSer * @throws NotFoundException * @throws UploadFailedException * @throws TimeoutException - * @throws VaasAuthenticationException * @throws NotPermittedException * @throws FileDoesNotExistException if the VaaS SDK could not find the file * @throws EntityTooLargeException if the file that should be scanned is too large + * @throws VaasAuthenticationException if the authentication with the VaaS service fails */ public function scanFileById(int $fileId): VaasVerdict { @@ -110,9 +93,19 @@ public function scanFileById(int $fileId): VaasVerdict throw new NotPermittedException("File is not in the allowlist"); } } + + if ($this->vaas == null) { + $this->vaas = $this->createAndConnectVaas(); + } - $this->vaas->Connect($this->authenticator->getToken()); - $verdict = $this->vaas->ForFile($filePath); + try { + $verdict = $this->vaas->ForFile($filePath); + } + catch (Exception $e) { + $this->logger->error("Vaas for file: " . $e->getMessage()); + $this->vaas = null; + throw $e; + } $this->logger->info("VaaS scan result for " . $node->getName() . " (" . $fileId . "): Verdict: " . $verdict->Verdict->value . ", Detection: " . $verdict->Detection . ", SHA256: " . $verdict->Sha256 . @@ -173,4 +166,29 @@ private function getBlocklist(): array } return explode(",", $blocklist); } + + /** + * @throws VaasAuthenticationException + */ + private function createAndConnectVaas(): Vaas + { + if ($this->authMethod === 'ResourceOwnerPassword') { + $this->authenticator = new ResourceOwnerPasswordGrantAuthenticator( + "nextcloud-customer", + $this->username, + $this->password, + $this->tokenEndpoint + ); + } elseif ($this->authMethod === 'ClientCredentials') { + $this->authenticator = new ClientCredentialsGrantAuthenticator( + $this->clientId, + $this->clientSecret, + $this->tokenEndpoint + ); + } + + $vaas = new Vaas($this->vaasUrl); + $vaas->Connect($this->authenticator->getToken()); + return $vaas; + } } diff --git a/src/files-action.js b/src/files-action.js index fb996af3..35d37c30 100644 --- a/src/files-action.js +++ b/src/files-action.js @@ -1,4 +1,4 @@ -import { showError, showSuccess } from '@nextcloud/dialogs' +import { showError, showSuccess, showWarning } from '@nextcloud/dialogs' import {FileAction, Permission, registerFileAction} from '@nextcloud/files' import Magnifier from '@mdi/svg/svg/magnify.svg?raw' @@ -32,6 +32,9 @@ if (parseInt(OC.config.version.split('.')[0]) >= 28) { 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 {