diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3f77d1cc..5aa3a196 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -91,6 +91,11 @@ jobs: - name: PHP & Vue Unit Tests run: | + mkdir -p server/apps/integration_openproject + cp -r `ls -A | grep -v 'server'` server/apps/integration_openproject/ + cd server + ./occ a:e integration_openproject + cd apps/integration_openproject make phpunit make jsunit @@ -99,7 +104,7 @@ jobs: uses: romeovs/lcov-reporter-action@v0.3.1 with: github-token: ${{ secrets.GITHUB_TOKEN }} - lcov-file: ./coverage/jest/lcov.info + lcov-file: ./server/apps/integration_openproject/coverage/jest/lcov.info delete-old-comments: true title: "JS Code Coverage" @@ -107,8 +112,8 @@ jobs: if: ${{ github.event_name == 'pull_request' && matrix.nextcloudVersion == 'master' && matrix.phpVersion == '8.1' }} uses: danielpalme/ReportGenerator-GitHub-Action@5.0.3 with: - reports: './coverage/php/cobertura.xml' # REQUIRED # The coverage reports that should be parsed (separated by semicolon). Globbing is supported. - targetdir: './coverage/php' # REQUIRED # The directory where the generated report should be saved. + reports: './server/apps/integration_openproject/coverage/php/cobertura.xml' # REQUIRED # The coverage reports that should be parsed (separated by semicolon). Globbing is supported. + targetdir: './server/apps/integration_openproject/coverage/php' # REQUIRED # The directory where the generated report should be saved. reporttypes: 'lcov' # The output formats and scope (separated by semicolon) Values: Badges, Clover, Cobertura, CsvSummary, Html, HtmlChart, HtmlInline, HtmlInline_AzurePipelines, HtmlInline_AzurePipelines_Dark, HtmlSummary, JsonSummary, Latex, LatexSummary, lcov, MarkdownSummary, MHtml, PngChart, SonarQube, TeamCitySummary, TextSummary, Xml, XmlSummary sourcedirs: '' # Optional directories which contain the corresponding source code (separated by semicolon). The source directories are used if coverage report contains classes without path information. historydir: '' # Optional directory for storing persistent coverage information. Can be used in future reports to show coverage evolution. @@ -127,7 +132,7 @@ jobs: uses: romeovs/lcov-reporter-action@v0.3.1 with: github-token: ${{ secrets.GITHUB_TOKEN }} - lcov-file: ./coverage/php/lcov.info + lcov-file: ./server/apps/integration_openproject/coverage/php/lcov.info delete-old-comments: true title: "PHP Code Coverage" @@ -136,14 +141,15 @@ jobs: uses: VeryGoodOpenSource/very_good_coverage@v1.2.0 with: min_coverage: '59' - path: './coverage/jest/lcov.info' + path: './server/apps/integration_openproject/coverage/jest/lcov.info' - name: PHP coverage check if: ${{ github.event_name == 'pull_request' && matrix.nextcloudVersion == 'master' && matrix.phpVersion == '8.1' }} uses: VeryGoodOpenSource/very_good_coverage@v1.2.0 with: min_coverage: '57' - path: './coverage/php/lcov.info' + path: './server/apps/integration_openproject/coverage/php/lcov.info' + api-tests: name: API tests strategy: diff --git a/appinfo/info.xml b/appinfo/info.xml index 41a03dc5..5604ff6c 100644 --- a/appinfo/info.xml +++ b/appinfo/info.xml @@ -38,7 +38,7 @@ For more information on how to set up and use the OpenProject application, pleas - OCA\OpenProject\BackgroundJob\CheckNotifications + OCA\OpenProject\BackgroundJob\RemoveExpiredDirectUploadTokens OCA\OpenProject\Settings\Admin diff --git a/lib/BackgroundJob/RemoveExpiredDirectUploadTokens.php b/lib/BackgroundJob/RemoveExpiredDirectUploadTokens.php new file mode 100644 index 00000000..60355702 --- /dev/null +++ b/lib/BackgroundJob/RemoveExpiredDirectUploadTokens.php @@ -0,0 +1,58 @@ + + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OCA\OpenProject\BackgroundJob; + +use OCP\BackgroundJob\TimedJob; +use OCP\AppFramework\Utility\ITimeFactory; +use OCP\DB\Exception; +use Psr\Log\LoggerInterface; + +use OCA\OpenProject\Service\DatabaseService; + +class RemoveExpiredDirectUploadTokens extends TimedJob { + + /** @var LoggerInterface */ + protected LoggerInterface $logger; + + /** @var DatabaseService */ + protected DatabaseService $databaseService; + + public function __construct(ITimeFactory $time, DatabaseService $databaseService, LoggerInterface $logger) { + parent::__construct($time); + // runs once a day + $this->setInterval(24 * 3600); + $this->databaseService = $databaseService; + $this->logger = $logger; + } + + /** + * @param mixed $argument + * @return void + * @throws Exception + */ + public function run($argument): void { + $this->databaseService->deleteExpiredTokens(); + $this->logger->info('Deleted all the expired tokens from Database'); + } +} diff --git a/lib/Service/DatabaseService.php b/lib/Service/DatabaseService.php index d6a474c3..393f7d55 100644 --- a/lib/Service/DatabaseService.php +++ b/lib/Service/DatabaseService.php @@ -31,7 +31,7 @@ class DatabaseService { /** * @var IDBConnection */ - private IDBConnection $db; + public IDBConnection $db; /** @var string table name */ @@ -102,6 +102,19 @@ class DatabaseService { ]; } + /** + * + * @throws Exception + */ + public function deleteExpiredTokens(): void { + $query = $this->db->getQueryBuilder(); + $query->delete($this->table) + ->where( + $query->expr()->lt('expires_on', $query->createNamedParameter(time())) + ); + $query->execute(); + } + /** * deletes the token from the table * @param string $token diff --git a/tests/lib/Service/DatabaseServiceTest.php b/tests/lib/Service/DatabaseServiceTest.php new file mode 100644 index 00000000..bdd264de --- /dev/null +++ b/tests/lib/Service/DatabaseServiceTest.php @@ -0,0 +1,194 @@ + + * + * @author Your name + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OCA\OpenProject\Service; + +use OCA\OpenProject\AppInfo\Application; +use OCP\DB\Exception; +use PHPUnit\Framework\TestCase; + +class DatabaseServiceTest extends TestCase { + /** + * @var DatabaseService + */ + private $databaseService; + private const TABLE_NAME = 'direct_upload'; + + /** + * createdAt and expiresOn info is not included since it requires current timestamp + * + * @var array + */ + private array $unexpiredDirectUploadInfo = [ + [ + "token" => 'unExpiredToken1', + "folderId" => 1, + "userId" => 'u1', + ], + [ + "token" => 'unExpiredToken2', + "folderId" => 1, + "userId" => 'u1', + ], + [ + "token" => 'unExpiredToken3', + "folderId" => 1, + "userId" => 'u1', + ], + + ]; + + /** + * createdAt and expiresOn info is not included since it requires current timestamp + * + * @var array + */ + private array $expiredDirectUploadInfo = [ + [ + "token" => 'expiredToken1', + "folderId" => 1, + "userId" => 'u1', + ], + [ + "token" => 'expiredToken2', + "folderId" => 1, + "userId" => 'u1', + ], + [ + "token" => 'expiredToken3', + "folderId" => 1, + "userId" => 'u1', + ], + + ]; + + protected function setUp(): void { + $app = new Application(); + $c = $app->getContainer(); + + /** @var DatabaseService $databaseService */ + $databaseService = $c->get(DatabaseService::class); + $this->databaseService = $databaseService; + } + + /** + * @throws Exception + */ + protected function tearDown(): void { + $query = $this->databaseService->db->getQueryBuilder(); + $query->delete(self::TABLE_NAME); + $query->execute(); + } + + /** + * @throws Exception + * @return array + */ + public function getAllTokensFromTable(): array { + $tokens = []; + $query = $this->databaseService->db->getQueryBuilder(); + $query->select('token') + ->from(self::TABLE_NAME); + $req = $query->executeQuery(); + while ($row = $req->fetch()) { + $tokens[] = $row['token']; + } + return $tokens; + } + + + /** + * @return void + * + * @throws Exception + */ + public function testDeleteSingleExpiredToken(): void { + $this->databaseService->setInfoForDirectUpload("expiredToken", 1, "u1", time(), time() - 1000); + $this->databaseService->deleteExpiredTokens(); + $token = $this->getAllTokensFromTable(); + self::assertEquals(0, sizeof($token)); + } + + /** + * @return void + * + * @throws Exception + */ + public function testDeleteMultipleExpiredTokens(): void { + foreach ($this->expiredDirectUploadInfo as $info) { + $this->databaseService->setInfoForDirectUpload($info['token'], $info['folderId'], $info['userId'], time(), time() - 1000); + } + $this->databaseService->deleteExpiredTokens(); + $tokens = $this->getAllTokensFromTable(); + self::assertEquals(0, sizeof($tokens)); + } + + /** + * @return void + * + * @throws Exception + */ + public function testDeleteSingleUnExpiredToken(): void { + $this->databaseService->setInfoForDirectUpload("unExpiredToken", 1, "u1", time(), time() + 1000); + $this->databaseService->deleteExpiredTokens(); + $token = $this->getAllTokensFromTable(); + self::assertSame('unExpiredToken', $token[0]); + } + + /** + * @return void + * + * @throws Exception + */ + public function testDeleteMultipleUnExpiredTokens(): void { + foreach ($this->unexpiredDirectUploadInfo as $info) { + $this->databaseService->setInfoForDirectUpload($info['token'], $info['folderId'], $info['userId'], time(), time() + 1000); + } + $this->databaseService->deleteExpiredTokens(); + $tokens = $this->getAllTokensFromTable(); + self::assertEquals(sizeof($this->unexpiredDirectUploadInfo), sizeof($tokens)); + foreach ($this->unexpiredDirectUploadInfo as $info) { + self::assertContains($info['token'], $tokens); + } + } + + /** + * @return void + * + * @throws Exception + */ + public function testDeleteMultipleExpiredAndUnexpiredTokens(): void { + foreach ($this->expiredDirectUploadInfo as $info) { + $this->databaseService->setInfoForDirectUpload($info['token'], $info['folderId'], $info['userId'], time(), time() - 1000); + } + foreach ($this->unexpiredDirectUploadInfo as $info) { + $this->databaseService->setInfoForDirectUpload($info['token'], $info['folderId'], $info['userId'], time(), time() + 1000); + } + $this->databaseService->deleteExpiredTokens(); + $tokens = $this->getAllTokensFromTable(); + self::assertEquals(sizeof($this->unexpiredDirectUploadInfo), sizeof($tokens)); + foreach ($this->unexpiredDirectUploadInfo as $info) { + self::assertContains($info['token'], $tokens); + } + } +}