From 45a90ee7a7dd39e56f99e71d8884e50de75684ff Mon Sep 17 00:00:00 2001 From: Louis Chemineau Date: Wed, 22 May 2024 11:55:47 +0200 Subject: [PATCH 01/21] fix(files_sharing): Delete user shares if needed when user is removed from a group Signed-off-by: Louis Chemineau --- lib/private/Share20/DefaultShareProvider.php | 67 ++++++++++++++++---- lib/private/Share20/ProviderFactory.php | 3 +- 2 files changed, 58 insertions(+), 12 deletions(-) diff --git a/lib/private/Share20/DefaultShareProvider.php b/lib/private/Share20/DefaultShareProvider.php index 9dd862abb3175..3034e2ad2ee34 100644 --- a/lib/private/Share20/DefaultShareProvider.php +++ b/lib/private/Share20/DefaultShareProvider.php @@ -53,6 +53,7 @@ use OCP\Mail\IMailer; use OCP\Share\Exceptions\ShareNotFound; use OCP\Share\IAttributes; +use OCP\Share\IManager; use OCP\Share\IShare; use OCP\Share\IShareProvider; use function str_starts_with; @@ -94,15 +95,17 @@ class DefaultShareProvider implements IShareProvider { private $config; public function __construct( - IDBConnection $connection, - IUserManager $userManager, - IGroupManager $groupManager, - IRootFolder $rootFolder, - IMailer $mailer, - Defaults $defaults, - IFactory $l10nFactory, - IURLGenerator $urlGenerator, - IConfig $config) { + IDBConnection $connection, + IUserManager $userManager, + IGroupManager $groupManager, + IRootFolder $rootFolder, + IMailer $mailer, + Defaults $defaults, + IFactory $l10nFactory, + IURLGenerator $urlGenerator, + IConfig $config, + private IManager $shareManager, + ) { $this->dbConn = $connection; $this->userManager = $userManager; $this->groupManager = $groupManager; @@ -1313,7 +1316,7 @@ public function userDeletedFromGroup($uid, $gid) { ->where($qb->expr()->eq('share_type', $qb->createNamedParameter(IShare::TYPE_GROUP))) ->andWhere($qb->expr()->eq('share_with', $qb->createNamedParameter($gid))); - $cursor = $qb->execute(); + $cursor = $qb->executeQuery(); $ids = []; while ($row = $cursor->fetch()) { $ids[] = (int)$row['id']; @@ -1330,9 +1333,51 @@ public function userDeletedFromGroup($uid, $gid) { ->where($qb->expr()->eq('share_type', $qb->createNamedParameter(IShare::TYPE_USERGROUP))) ->andWhere($qb->expr()->eq('share_with', $qb->createNamedParameter($uid))) ->andWhere($qb->expr()->in('parent', $qb->createNamedParameter($chunk, IQueryBuilder::PARAM_INT_ARRAY))); - $qb->execute(); + $qb->executeStatement(); } } + + if ($this->shareManager->shareWithGroupMembersOnly()) { + $deleteQuery = $this->dbConn->getQueryBuilder(); + $deleteQuery->delete('share') + ->where($deleteQuery->expr()->in('id', $deleteQuery->createParameter('id'))); + + // Delete direct shares received by the user from users in the group. + $selectInboundShares = $this->dbConn->getQueryBuilder(); + $selectInboundShares->select('id') + ->from('share', 's') + ->join('s', 'group_user', 'g', 's.uid_initiator = g.uid') + ->where($selectInboundShares->expr()->eq('share_type', $selectInboundShares->createNamedParameter(IShare::TYPE_USER))) + ->andWhere($selectInboundShares->expr()->eq('share_with', $selectInboundShares->createNamedParameter($uid))) + ->andWhere($selectInboundShares->expr()->eq('gid', $selectInboundShares->createNamedParameter($gid))) + ->setMaxResults(1000) + ->executeQuery(); + + do { + $rows = $selectInboundShares->executeQuery(); + $ids = $rows->fetchAll(); + $deleteQuery->setParameter('id', array_column($ids, 'id'), IQueryBuilder::PARAM_INT_ARRAY); + $deleteQuery->executeStatement(); + } while (count($ids) > 0); + + // Delete direct shares from the user to users in the group. + $selectOutboundShares = $this->dbConn->getQueryBuilder(); + $selectOutboundShares->select('id') + ->from('share', 's') + ->join('s', 'group_user', 'g', 's.share_with = g.uid') + ->where($selectOutboundShares->expr()->eq('share_type', $selectOutboundShares->createNamedParameter(IShare::TYPE_USER))) + ->andWhere($selectOutboundShares->expr()->eq('uid_initiator', $selectOutboundShares->createNamedParameter($uid))) + ->andWhere($selectOutboundShares->expr()->eq('gid', $selectOutboundShares->createNamedParameter($gid))) + ->setMaxResults(1000) + ->executeQuery(); + + do { + $rows = $selectOutboundShares->executeQuery(); + $ids = $rows->fetchAll(); + $deleteQuery->setParameter('id', array_column($ids, 'id'), IQueryBuilder::PARAM_INT_ARRAY); + $deleteQuery->executeStatement(); + } while (count($ids) > 0); + } } /** diff --git a/lib/private/Share20/ProviderFactory.php b/lib/private/Share20/ProviderFactory.php index 6abfb372a4d1e..f861eafda7f0f 100644 --- a/lib/private/Share20/ProviderFactory.php +++ b/lib/private/Share20/ProviderFactory.php @@ -104,7 +104,8 @@ protected function defaultShareProvider() { $this->serverContainer->query(Defaults::class), $this->serverContainer->getL10NFactory(), $this->serverContainer->getURLGenerator(), - $this->serverContainer->getConfig() + $this->serverContainer->getConfig(), + $this->serverContainer->get(IManager::class), ); } From a0ea38f401d3e504f3d4c2506c74da1431adfce5 Mon Sep 17 00:00:00 2001 From: Louis Chemineau Date: Wed, 22 May 2024 11:59:10 +0200 Subject: [PATCH 02/21] chore(tests): Test limiting sharing to same group Signed-off-by: Louis Chemineau --- .../files_sharing/limit_to_same_group.cy.ts | 97 +++++++++++++++++++ 1 file changed, 97 insertions(+) create mode 100644 cypress/e2e/files_sharing/limit_to_same_group.cy.ts diff --git a/cypress/e2e/files_sharing/limit_to_same_group.cy.ts b/cypress/e2e/files_sharing/limit_to_same_group.cy.ts new file mode 100644 index 0000000000000..6d9a4170cbacb --- /dev/null +++ b/cypress/e2e/files_sharing/limit_to_same_group.cy.ts @@ -0,0 +1,97 @@ +/** + * @copyright Copyright (c) 2024 Louis Chmn + * + * @author Louis Chmn + * + * @license AGPL-3.0-or-later + * + * 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 . + * + */ + +import { User } from "@nextcloud/cypress" +import { createShare } from "./filesSharingUtils" + +describe('Limit to sharing to people in the same group', () => { + let alice: User + let bob: User + let randomFileName1 = '' + let randomFileName2 = '' + let randomGroupName = '' + + before(() => { + randomFileName1 = Math.random().toString(36).replace(/[^a-z]+/g, '').substring(0, 10) + '.txt' + randomFileName2 = Math.random().toString(36).replace(/[^a-z]+/g, '').substring(0, 10) + '.txt' + randomGroupName = Math.random().toString(36).replace(/[^a-z]+/g, '').substring(0, 10) + + cy.runOccCommand('config:app:set core shareapi_only_share_with_group_members --value yes') + + cy.createRandomUser() + .then(user => { + alice = user + cy.createRandomUser() + }) + .then(user => { + bob = user + + cy.runOccCommand(`group:add ${randomGroupName}`) + cy.runOccCommand(`group:adduser ${randomGroupName} ${alice.userId}`) + cy.runOccCommand(`group:adduser ${randomGroupName} ${bob.userId}`) + + cy.uploadContent(alice, new Blob(['share to bob'], { type: 'text/plain' }), 'text/plain', `/${randomFileName1}`) + cy.uploadContent(bob, new Blob(['share by bob'], { type: 'text/plain' }), 'text/plain', `/${randomFileName2}`) + + cy.login(alice) + cy.visit('/apps/files') + createShare(randomFileName1, bob.userId) + cy.login(bob) + cy.visit('/apps/files') + createShare(randomFileName2, alice.userId) + }) + }) + + after(() => { + cy.runOccCommand('config:app:set core shareapi_only_share_with_group_members --value no') + }) + + it('Alice can see the shared file', () => { + cy.login(alice) + cy.visit('/apps/files') + cy.get(`[data-cy-files-list] [data-cy-files-list-row-name="${randomFileName2}"]`).should('exist') + }) + + it('Bob can see the shared file', () => { + cy.login(alice) + cy.visit('/apps/files') + cy.get(`[data-cy-files-list] [data-cy-files-list-row-name="${randomFileName1}"]`).should('exist') + }) + + context('Bob is removed from the group', () => { + before(() => { + cy.runOccCommand(`group:removeuser ${randomGroupName} ${bob.userId}`) + }) + + it('Alice cannot see the shared file', () => { + cy.login(alice) + cy.visit('/apps/files') + cy.get(`[data-cy-files-list] [data-cy-files-list-row-name="${randomFileName2}"]`).should('not.exist') + }) + + it('Bob cannot see the shared file', () => { + cy.login(alice) + cy.visit('/apps/files') + cy.get(`[data-cy-files-list] [data-cy-files-list-row-name="${randomFileName1}"]`).should('not.exist') + }) + }) +}) From 798f9ee68e7c5879dd07af03cfb3df61ed7292be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=B4me=20Chilliet?= Date: Thu, 13 Jun 2024 17:05:29 +0200 Subject: [PATCH 03/21] fix: Remove shares only if there are no more common groups between users MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Côme Chilliet --- .../files_sharing/limit_to_same_group.cy.ts | 29 +++++++- lib/private/Share20/DefaultShareProvider.php | 74 +++++++++---------- 2 files changed, 64 insertions(+), 39 deletions(-) diff --git a/cypress/e2e/files_sharing/limit_to_same_group.cy.ts b/cypress/e2e/files_sharing/limit_to_same_group.cy.ts index 6d9a4170cbacb..0c771c931f7ca 100644 --- a/cypress/e2e/files_sharing/limit_to_same_group.cy.ts +++ b/cypress/e2e/files_sharing/limit_to_same_group.cy.ts @@ -29,11 +29,15 @@ describe('Limit to sharing to people in the same group', () => { let randomFileName1 = '' let randomFileName2 = '' let randomGroupName = '' + let randomGroupName2 = '' + let randomGroupName3 = '' before(() => { randomFileName1 = Math.random().toString(36).replace(/[^a-z]+/g, '').substring(0, 10) + '.txt' randomFileName2 = Math.random().toString(36).replace(/[^a-z]+/g, '').substring(0, 10) + '.txt' randomGroupName = Math.random().toString(36).replace(/[^a-z]+/g, '').substring(0, 10) + randomGroupName2 = Math.random().toString(36).replace(/[^a-z]+/g, '').substring(0, 10) + randomGroupName3 = Math.random().toString(36).replace(/[^a-z]+/g, '').substring(0, 10) cy.runOccCommand('config:app:set core shareapi_only_share_with_group_members --value yes') @@ -46,8 +50,13 @@ describe('Limit to sharing to people in the same group', () => { bob = user cy.runOccCommand(`group:add ${randomGroupName}`) + cy.runOccCommand(`group:add ${randomGroupName2}`) + cy.runOccCommand(`group:add ${randomGroupName3}`) cy.runOccCommand(`group:adduser ${randomGroupName} ${alice.userId}`) cy.runOccCommand(`group:adduser ${randomGroupName} ${bob.userId}`) + cy.runOccCommand(`group:adduser ${randomGroupName2} ${alice.userId}`) + cy.runOccCommand(`group:adduser ${randomGroupName2} ${bob.userId}`) + cy.runOccCommand(`group:adduser ${randomGroupName3} ${bob.userId}`) cy.uploadContent(alice, new Blob(['share to bob'], { type: 'text/plain' }), 'text/plain', `/${randomFileName1}`) cy.uploadContent(bob, new Blob(['share by bob'], { type: 'text/plain' }), 'text/plain', `/${randomFileName2}`) @@ -77,11 +86,29 @@ describe('Limit to sharing to people in the same group', () => { cy.get(`[data-cy-files-list] [data-cy-files-list-row-name="${randomFileName1}"]`).should('exist') }) - context('Bob is removed from the group', () => { + context('Bob is removed from the first group', () => { before(() => { cy.runOccCommand(`group:removeuser ${randomGroupName} ${bob.userId}`) }) + it('Alice can see the shared file', () => { + cy.login(alice) + cy.visit('/apps/files') + cy.get(`[data-cy-files-list] [data-cy-files-list-row-name="${randomFileName2}"]`).should('exist') + }) + + it('Bob can see the shared file', () => { + cy.login(alice) + cy.visit('/apps/files') + cy.get(`[data-cy-files-list] [data-cy-files-list-row-name="${randomFileName1}"]`).should('exist') + }) + }) + + context('Bob is removed from the second group', () => { + before(() => { + cy.runOccCommand(`group:removeuser ${randomGroupName2} ${bob.userId}`) + }) + it('Alice cannot see the shared file', () => { cy.login(alice) cy.visit('/apps/files') diff --git a/lib/private/Share20/DefaultShareProvider.php b/lib/private/Share20/DefaultShareProvider.php index 3034e2ad2ee34..cffe9dc6c9d67 100644 --- a/lib/private/Share20/DefaultShareProvider.php +++ b/lib/private/Share20/DefaultShareProvider.php @@ -1305,6 +1305,7 @@ public function groupDeleted($gid) { * * @param string $uid * @param string $gid + * @return void */ public function userDeletedFromGroup($uid, $gid) { /* @@ -1338,45 +1339,42 @@ public function userDeletedFromGroup($uid, $gid) { } if ($this->shareManager->shareWithGroupMembersOnly()) { - $deleteQuery = $this->dbConn->getQueryBuilder(); - $deleteQuery->delete('share') - ->where($deleteQuery->expr()->in('id', $deleteQuery->createParameter('id'))); + $user = $this->userManager->get($uid); + if ($user === null) { + return; + } + $userGroups = $this->groupManager->getUserGroupIds($user); + $userGroups = array_diff($userGroups, $this->shareManager->shareWithGroupMembersOnlyExcludeGroupsList()); + + // Delete user shares received by the user from users in the group. + $userReceivedShares = $this->shareManager->getSharedWith($uid, IShare::TYPE_USER, null, -1); + foreach ($userReceivedShares as $share) { + $owner = $this->userManager->get($share->getSharedBy()); + if ($owner === null) { + continue; + } + $ownerGroups = $this->groupManager->getUserGroupIds($owner); + $mutualGroups = array_intersect($userGroups, $ownerGroups); - // Delete direct shares received by the user from users in the group. - $selectInboundShares = $this->dbConn->getQueryBuilder(); - $selectInboundShares->select('id') - ->from('share', 's') - ->join('s', 'group_user', 'g', 's.uid_initiator = g.uid') - ->where($selectInboundShares->expr()->eq('share_type', $selectInboundShares->createNamedParameter(IShare::TYPE_USER))) - ->andWhere($selectInboundShares->expr()->eq('share_with', $selectInboundShares->createNamedParameter($uid))) - ->andWhere($selectInboundShares->expr()->eq('gid', $selectInboundShares->createNamedParameter($gid))) - ->setMaxResults(1000) - ->executeQuery(); - - do { - $rows = $selectInboundShares->executeQuery(); - $ids = $rows->fetchAll(); - $deleteQuery->setParameter('id', array_column($ids, 'id'), IQueryBuilder::PARAM_INT_ARRAY); - $deleteQuery->executeStatement(); - } while (count($ids) > 0); - - // Delete direct shares from the user to users in the group. - $selectOutboundShares = $this->dbConn->getQueryBuilder(); - $selectOutboundShares->select('id') - ->from('share', 's') - ->join('s', 'group_user', 'g', 's.share_with = g.uid') - ->where($selectOutboundShares->expr()->eq('share_type', $selectOutboundShares->createNamedParameter(IShare::TYPE_USER))) - ->andWhere($selectOutboundShares->expr()->eq('uid_initiator', $selectOutboundShares->createNamedParameter($uid))) - ->andWhere($selectOutboundShares->expr()->eq('gid', $selectOutboundShares->createNamedParameter($gid))) - ->setMaxResults(1000) - ->executeQuery(); - - do { - $rows = $selectOutboundShares->executeQuery(); - $ids = $rows->fetchAll(); - $deleteQuery->setParameter('id', array_column($ids, 'id'), IQueryBuilder::PARAM_INT_ARRAY); - $deleteQuery->executeStatement(); - } while (count($ids) > 0); + if (count($mutualGroups) === 0) { + $this->shareManager->deleteShare($share); + } + } + + // Delete user shares from the user to users in the group. + $userEmittedShares = $this->shareManager->getSharesBy($uid, IShare::TYPE_USER, null, true, -1); + foreach ($userEmittedShares as $share) { + $recipient = $this->userManager->get($share->getSharedWith()); + if ($recipient === null) { + continue; + } + $recipientGroups = $this->groupManager->getUserGroupIds($recipient); + $mutualGroups = array_intersect($userGroups, $recipientGroups); + + if (count($mutualGroups) === 0) { + $this->shareManager->deleteShare($share); + } + } } } From 4f929b2635e2b8aa4257ba3d37ef54f92dfc80be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=B4me=20Chilliet?= Date: Mon, 12 Aug 2024 15:15:58 +0200 Subject: [PATCH 04/21] fix(tests): Adapt tests to change of DefaultShareProvider constructor MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Côme Chilliet --- .../lib/Share20/DefaultShareProviderTest.php | 25 +++++++++++++------ 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/tests/lib/Share20/DefaultShareProviderTest.php b/tests/lib/Share20/DefaultShareProviderTest.php index 0a6f106a5dba9..36e563a1343a1 100644 --- a/tests/lib/Share20/DefaultShareProviderTest.php +++ b/tests/lib/Share20/DefaultShareProviderTest.php @@ -82,6 +82,8 @@ class DefaultShareProviderTest extends \Test\TestCase { /** @var IConfig|MockObject */ protected $config; + protected IShareManager&MockObject $shareManager; + protected function setUp(): void { $this->dbConn = \OC::$server->getDatabaseConnection(); $this->userManager = $this->createMock(IUserManager::class); @@ -93,6 +95,7 @@ protected function setUp(): void { $this->defaults = $this->getMockBuilder(Defaults::class)->disableOriginalConstructor()->getMock(); $this->urlGenerator = $this->createMock(IURLGenerator::class); $this->config = $this->createMock(IConfig::class); + $this->shareManager = $this->createMock(IShareManager::class); $this->userManager->expects($this->any())->method('userExists')->willReturn(true); @@ -108,7 +111,8 @@ protected function setUp(): void { $this->defaults, $this->l10nFactory, $this->urlGenerator, - $this->config + $this->config, + $this->shareManager, ); } @@ -132,8 +136,8 @@ protected function tearDown(): void { * @return int */ private function addShareToDB($shareType, $sharedWith, $sharedBy, $shareOwner, - $itemType, $fileSource, $fileTarget, $permissions, $token, $expiration, - $parent = null) { + $itemType, $fileSource, $fileTarget, $permissions, $token, $expiration, + $parent = null) { $qb = $this->dbConn->getQueryBuilder(); $qb->insert('share'); @@ -469,7 +473,8 @@ public function testDeleteSingleShare() { $this->defaults, $this->l10nFactory, $this->urlGenerator, - $this->config + $this->config, + $this->shareManager, ]) ->setMethods(['getShareById']) ->getMock(); @@ -564,7 +569,8 @@ public function testDeleteGroupShareWithUserGroupShares() { $this->defaults, $this->l10nFactory, $this->urlGenerator, - $this->config + $this->config, + $this->shareManager, ]) ->setMethods(['getShareById']) ->getMock(); @@ -2524,7 +2530,8 @@ public function testGetSharesInFolder() { $this->defaults, $this->l10nFactory, $this->urlGenerator, - $this->config + $this->config, + $this->shareManager, ); $password = md5(time()); @@ -2622,7 +2629,8 @@ public function testGetAccessListNoCurrentAccessRequired() { $this->defaults, $this->l10nFactory, $this->urlGenerator, - $this->config + $this->config, + $this->shareManager, ); $u1 = $userManager->createUser('testShare1', 'test'); @@ -2718,7 +2726,8 @@ public function testGetAccessListCurrentAccessRequired() { $this->defaults, $this->l10nFactory, $this->urlGenerator, - $this->config + $this->config, + $this->shareManager, ); $u1 = $userManager->createUser('testShare1', 'test'); From 0d8c3d1f3f2da1a68d5e725fef6e7653d3dbabd7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=B4me=20Chilliet?= Date: Tue, 13 Aug 2024 15:17:57 +0200 Subject: [PATCH 05/21] fix(tests): Fix PHP 8.0 compatibility MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Côme Chilliet --- tests/lib/Share20/DefaultShareProviderTest.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/lib/Share20/DefaultShareProviderTest.php b/tests/lib/Share20/DefaultShareProviderTest.php index 36e563a1343a1..0ddf9856a30cd 100644 --- a/tests/lib/Share20/DefaultShareProviderTest.php +++ b/tests/lib/Share20/DefaultShareProviderTest.php @@ -82,7 +82,8 @@ class DefaultShareProviderTest extends \Test\TestCase { /** @var IConfig|MockObject */ protected $config; - protected IShareManager&MockObject $shareManager; + /** @var IShareManager&MockObject */ + protected $shareManager; protected function setUp(): void { $this->dbConn = \OC::$server->getDatabaseConnection(); From 62837314130a88f0c2e85da2ef504940f81213e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=B4me=20Chilliet?= Date: Mon, 19 Aug 2024 10:31:30 +0200 Subject: [PATCH 06/21] fix: Remove call to non-existing method in 28 and add missing use in test MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Côme Chilliet --- lib/private/Share20/DefaultShareProvider.php | 1 - tests/lib/Share20/DefaultShareProviderTest.php | 1 + 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/private/Share20/DefaultShareProvider.php b/lib/private/Share20/DefaultShareProvider.php index cffe9dc6c9d67..d455bd4c46613 100644 --- a/lib/private/Share20/DefaultShareProvider.php +++ b/lib/private/Share20/DefaultShareProvider.php @@ -1344,7 +1344,6 @@ public function userDeletedFromGroup($uid, $gid) { return; } $userGroups = $this->groupManager->getUserGroupIds($user); - $userGroups = array_diff($userGroups, $this->shareManager->shareWithGroupMembersOnlyExcludeGroupsList()); // Delete user shares received by the user from users in the group. $userReceivedShares = $this->shareManager->getSharedWith($uid, IShare::TYPE_USER, null, -1); diff --git a/tests/lib/Share20/DefaultShareProviderTest.php b/tests/lib/Share20/DefaultShareProviderTest.php index 0ddf9856a30cd..75acae1ffcaee 100644 --- a/tests/lib/Share20/DefaultShareProviderTest.php +++ b/tests/lib/Share20/DefaultShareProviderTest.php @@ -39,6 +39,7 @@ use OCP\IUserManager; use OCP\L10N\IFactory; use OCP\Mail\IMailer; +use OCP\Share\IManager as IShareManager; use OCP\Share\IShare; use PHPUnit\Framework\MockObject\MockObject; From f735db9a7d25415127b6eea8499a8bda3a35e915 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=B4me=20Chilliet?= Date: Thu, 22 Aug 2024 15:08:25 +0200 Subject: [PATCH 07/21] chore: Remove test not compatible with 27 and below MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Côme Chilliet --- .../files_sharing/limit_to_same_group.cy.ts | 124 ------------------ 1 file changed, 124 deletions(-) delete mode 100644 cypress/e2e/files_sharing/limit_to_same_group.cy.ts diff --git a/cypress/e2e/files_sharing/limit_to_same_group.cy.ts b/cypress/e2e/files_sharing/limit_to_same_group.cy.ts deleted file mode 100644 index 0c771c931f7ca..0000000000000 --- a/cypress/e2e/files_sharing/limit_to_same_group.cy.ts +++ /dev/null @@ -1,124 +0,0 @@ -/** - * @copyright Copyright (c) 2024 Louis Chmn - * - * @author Louis Chmn - * - * @license AGPL-3.0-or-later - * - * 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 . - * - */ - -import { User } from "@nextcloud/cypress" -import { createShare } from "./filesSharingUtils" - -describe('Limit to sharing to people in the same group', () => { - let alice: User - let bob: User - let randomFileName1 = '' - let randomFileName2 = '' - let randomGroupName = '' - let randomGroupName2 = '' - let randomGroupName3 = '' - - before(() => { - randomFileName1 = Math.random().toString(36).replace(/[^a-z]+/g, '').substring(0, 10) + '.txt' - randomFileName2 = Math.random().toString(36).replace(/[^a-z]+/g, '').substring(0, 10) + '.txt' - randomGroupName = Math.random().toString(36).replace(/[^a-z]+/g, '').substring(0, 10) - randomGroupName2 = Math.random().toString(36).replace(/[^a-z]+/g, '').substring(0, 10) - randomGroupName3 = Math.random().toString(36).replace(/[^a-z]+/g, '').substring(0, 10) - - cy.runOccCommand('config:app:set core shareapi_only_share_with_group_members --value yes') - - cy.createRandomUser() - .then(user => { - alice = user - cy.createRandomUser() - }) - .then(user => { - bob = user - - cy.runOccCommand(`group:add ${randomGroupName}`) - cy.runOccCommand(`group:add ${randomGroupName2}`) - cy.runOccCommand(`group:add ${randomGroupName3}`) - cy.runOccCommand(`group:adduser ${randomGroupName} ${alice.userId}`) - cy.runOccCommand(`group:adduser ${randomGroupName} ${bob.userId}`) - cy.runOccCommand(`group:adduser ${randomGroupName2} ${alice.userId}`) - cy.runOccCommand(`group:adduser ${randomGroupName2} ${bob.userId}`) - cy.runOccCommand(`group:adduser ${randomGroupName3} ${bob.userId}`) - - cy.uploadContent(alice, new Blob(['share to bob'], { type: 'text/plain' }), 'text/plain', `/${randomFileName1}`) - cy.uploadContent(bob, new Blob(['share by bob'], { type: 'text/plain' }), 'text/plain', `/${randomFileName2}`) - - cy.login(alice) - cy.visit('/apps/files') - createShare(randomFileName1, bob.userId) - cy.login(bob) - cy.visit('/apps/files') - createShare(randomFileName2, alice.userId) - }) - }) - - after(() => { - cy.runOccCommand('config:app:set core shareapi_only_share_with_group_members --value no') - }) - - it('Alice can see the shared file', () => { - cy.login(alice) - cy.visit('/apps/files') - cy.get(`[data-cy-files-list] [data-cy-files-list-row-name="${randomFileName2}"]`).should('exist') - }) - - it('Bob can see the shared file', () => { - cy.login(alice) - cy.visit('/apps/files') - cy.get(`[data-cy-files-list] [data-cy-files-list-row-name="${randomFileName1}"]`).should('exist') - }) - - context('Bob is removed from the first group', () => { - before(() => { - cy.runOccCommand(`group:removeuser ${randomGroupName} ${bob.userId}`) - }) - - it('Alice can see the shared file', () => { - cy.login(alice) - cy.visit('/apps/files') - cy.get(`[data-cy-files-list] [data-cy-files-list-row-name="${randomFileName2}"]`).should('exist') - }) - - it('Bob can see the shared file', () => { - cy.login(alice) - cy.visit('/apps/files') - cy.get(`[data-cy-files-list] [data-cy-files-list-row-name="${randomFileName1}"]`).should('exist') - }) - }) - - context('Bob is removed from the second group', () => { - before(() => { - cy.runOccCommand(`group:removeuser ${randomGroupName2} ${bob.userId}`) - }) - - it('Alice cannot see the shared file', () => { - cy.login(alice) - cy.visit('/apps/files') - cy.get(`[data-cy-files-list] [data-cy-files-list-row-name="${randomFileName2}"]`).should('not.exist') - }) - - it('Bob cannot see the shared file', () => { - cy.login(alice) - cy.visit('/apps/files') - cy.get(`[data-cy-files-list] [data-cy-files-list-row-name="${randomFileName1}"]`).should('not.exist') - }) - }) -}) From afef7e44337018e022dd66fd2cef41d6023ae5da Mon Sep 17 00:00:00 2001 From: Daniel Kesselberg Date: Mon, 23 Sep 2024 11:39:15 +0200 Subject: [PATCH 08/21] chore: add .git-blame-ignore-revs Signed-off-by: Daniel Kesselberg --- .git-blame-ignore-revs | 4 ++++ build/files-checker.php | 1 + 2 files changed, 5 insertions(+) create mode 100644 .git-blame-ignore-revs diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs new file mode 100644 index 0000000000000..9178a49e25633 --- /dev/null +++ b/.git-blame-ignore-revs @@ -0,0 +1,4 @@ +# .git-blame-ignore-revs + +# SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors +# SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/build/files-checker.php b/build/files-checker.php index 8913b8a9fdc44..08cc971f82be8 100644 --- a/build/files-checker.php +++ b/build/files-checker.php @@ -29,6 +29,7 @@ '.eslintignore', '.eslintrc.js', '.git', + '.git-blame-ignore-revs', '.gitattributes', '.github', '.gitignore', From db644155c3aa7aba2c86d4d7d9ebb50e13dd31fc Mon Sep 17 00:00:00 2001 From: nextcloud-command Date: Wed, 25 Sep 2024 02:30:58 +0000 Subject: [PATCH 09/21] fix(security): Update CA certificate bundle Signed-off-by: GitHub --- build/ca-bundle-etag.txt | 2 +- resources/config/ca-bundle.crt | 102 ++++++++++++++++++++++++++++++++- 2 files changed, 101 insertions(+), 3 deletions(-) diff --git a/build/ca-bundle-etag.txt b/build/ca-bundle-etag.txt index aa05e09184869..faad55357deed 100644 --- a/build/ca-bundle-etag.txt +++ b/build/ca-bundle-etag.txt @@ -1 +1 @@ -"37d19-61c3b1405de33" +"3955f-622d4deb06d62" diff --git a/resources/config/ca-bundle.crt b/resources/config/ca-bundle.crt index 86d6cd80cc066..f2c24a589d982 100644 --- a/resources/config/ca-bundle.crt +++ b/resources/config/ca-bundle.crt @@ -1,7 +1,9 @@ ## ## Bundle of CA Root Certificates ## -## Certificate data from Mozilla as of: Tue Jul 2 03:12:04 2024 GMT +## Certificate data from Mozilla as of: Tue Sep 24 03:12:04 2024 GMT +## +## Find updated versions here: https://curl.se/docs/caextract.html ## ## This is a bundle of X.509 certificates of public Certificate Authorities ## (CA). These were automatically extracted from Mozilla's root certificates @@ -14,7 +16,7 @@ ## Just configure this file as the SSLCACertificateFile. ## ## Conversion done with mk-ca-bundle.pl version 1.29. -## SHA256: 456ff095dde6dd73354c5c28c73d9c06f53b61a803963414cb91a1d92945cdd3 +## SHA256: 36105b01631f9fc03b1eca779b44a30a1a5890b9bf8dc07ccb001a07301e01cf ## @@ -3566,3 +3568,99 @@ Y1w8ndYn81LsF7Kpryz3dvgwHQYDVR0OBBYEFJPhQ2NcPJ3WJ/NS7Beyqa8s93b4MA4GA1UdDwEB cFBTApFwhVmpHqTm6iMxoAACMQD94vizrxa5HnPEluPBMBnYfubDl94cT7iJLzPrSA8Z94dGXSaQ pYXFuXqUPoeovQA= -----END CERTIFICATE----- + +TWCA CYBER Root CA +================== +-----BEGIN CERTIFICATE----- +MIIFjTCCA3WgAwIBAgIQQAE0jMIAAAAAAAAAATzyxjANBgkqhkiG9w0BAQwFADBQMQswCQYDVQQG +EwJUVzESMBAGA1UEChMJVEFJV0FOLUNBMRAwDgYDVQQLEwdSb290IENBMRswGQYDVQQDExJUV0NB +IENZQkVSIFJvb3QgQ0EwHhcNMjIxMTIyMDY1NDI5WhcNNDcxMTIyMTU1OTU5WjBQMQswCQYDVQQG +EwJUVzESMBAGA1UEChMJVEFJV0FOLUNBMRAwDgYDVQQLEwdSb290IENBMRswGQYDVQQDExJUV0NB +IENZQkVSIFJvb3QgQ0EwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDG+Moe2Qkgfh1s +Ts6P40czRJzHyWmqOlt47nDSkvgEs1JSHWdyKKHfi12VCv7qze33Kc7wb3+szT3vsxxFavcokPFh +V8UMxKNQXd7UtcsZyoC5dc4pztKFIuwCY8xEMCDa6pFbVuYdHNWdZsc/34bKS1PE2Y2yHer43CdT +o0fhYcx9tbD47nORxc5zb87uEB8aBs/pJ2DFTxnk684iJkXXYJndzk834H/nY62wuFm40AZoNWDT +Nq5xQwTxaWV4fPMf88oon1oglWa0zbfuj3ikRRjpJi+NmykosaS3Om251Bw4ckVYsV7r8Cibt4LK +/c/WMw+f+5eesRycnupfXtuq3VTpMCEobY5583WSjCb+3MX2w7DfRFlDo7YDKPYIMKoNM+HvnKkH +IuNZW0CP2oi3aQiotyMuRAlZN1vH4xfyIutuOVLF3lSnmMlLIJXcRolftBL5hSmO68gnFSDAS9TM +fAxsNAwmmyYxpjyn9tnQS6Jk/zuZQXLB4HCX8SS7K8R0IrGsayIyJNN4KsDAoS/xUgXJP+92ZuJF +2A09rZXIx4kmyA+upwMu+8Ff+iDhcK2wZSA3M2Cw1a/XDBzCkHDXShi8fgGwsOsVHkQGzaRP6AzR +wyAQ4VRlnrZR0Bp2a0JaWHY06rc3Ga4udfmW5cFZ95RXKSWNOkyrTZpB0F8mAwIDAQABo2MwYTAO +BgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBSdhWEUfMFib5do5E83 +QOGt4A1WNzAdBgNVHQ4EFgQUnYVhFHzBYm+XaORPN0DhreANVjcwDQYJKoZIhvcNAQEMBQADggIB +AGSPesRiDrWIzLjHhg6hShbNcAu3p4ULs3a2D6f/CIsLJc+o1IN1KriWiLb73y0ttGlTITVX1olN +c79pj3CjYcya2x6a4CD4bLubIp1dhDGaLIrdaqHXKGnK/nZVekZn68xDiBaiA9a5F/gZbG0jAn/x +X9AKKSM70aoK7akXJlQKTcKlTfjF/biBzysseKNnTKkHmvPfXvt89YnNdJdhEGoHK4Fa0o635yDR +IG4kqIQnoVesqlVYL9zZyvpoBJ7tRCT5dEA7IzOrg1oYJkK2bVS1FmAwbLGg+LhBoF1JSdJlBTrq +/p1hvIbZv97Tujqxf36SNI7JAG7cmL3c7IAFrQI932XtCwP39xaEBDG6k5TY8hL4iuO/Qq+n1M0R +FxbIQh0UqEL20kCGoE8jypZFVmAGzbdVAaYBlGX+bgUJurSkquLvWL69J1bY73NxW0Qz8ppy6rBe +Pm6pUlvscG21h483XjyMnM7k8M4MZ0HMzvaAq07MTFb1wWFZk7Q+ptq4NxKfKjLji7gh7MMrZQzv +It6IKTtM1/r+t+FHvpw+PoP7UV31aPcuIYXcv/Fa4nzXxeSDwWrruoBa3lwtcHb4yOWHh8qgnaHl +IhInD0Q9HWzq1MKLL295q39QpsQZp6F6t5b5wR9iWqJDB0BeJsas7a5wFsWqynKKTbDPAYsDP27X +-----END CERTIFICATE----- + +SecureSign Root CA12 +==================== +-----BEGIN CERTIFICATE----- +MIIDcjCCAlqgAwIBAgIUZvnHwa/swlG07VOX5uaCwysckBYwDQYJKoZIhvcNAQELBQAwUTELMAkG +A1UEBhMCSlAxIzAhBgNVBAoTGkN5YmVydHJ1c3QgSmFwYW4gQ28uLCBMdGQuMR0wGwYDVQQDExRT +ZWN1cmVTaWduIFJvb3QgQ0ExMjAeFw0yMDA0MDgwNTM2NDZaFw00MDA0MDgwNTM2NDZaMFExCzAJ +BgNVBAYTAkpQMSMwIQYDVQQKExpDeWJlcnRydXN0IEphcGFuIENvLiwgTHRkLjEdMBsGA1UEAxMU +U2VjdXJlU2lnbiBSb290IENBMTIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC6OcE3 +emhFKxS06+QT61d1I02PJC0W6K6OyX2kVzsqdiUzg2zqMoqUm048luT9Ub+ZyZN+v/mtp7JIKwcc +J/VMvHASd6SFVLX9kHrko+RRWAPNEHl57muTH2SOa2SroxPjcf59q5zdJ1M3s6oYwlkm7Fsf0uZl +fO+TvdhYXAvA42VvPMfKWeP+bl+sg779XSVOKik71gurFzJ4pOE+lEa+Ym6b3kaosRbnhW70CEBF +EaCeVESE99g2zvVQR9wsMJvuwPWW0v4JhscGWa5Pro4RmHvzC1KqYiaqId+OJTN5lxZJjfU+1Uef +NzFJM3IFTQy2VYzxV4+Kh9GtxRESOaCtAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0P +AQH/BAQDAgEGMB0GA1UdDgQWBBRXNPN0zwRL1SXm8UC2LEzZLemgrTANBgkqhkiG9w0BAQsFAAOC +AQEAPrvbFxbS8hQBICw4g0utvsqFepq2m2um4fylOqyttCg6r9cBg0krY6LdmmQOmFxv3Y67ilQi +LUoT865AQ9tPkbeGGuwAtEGBpE/6aouIs3YIcipJQMPTw4WJmBClnW8Zt7vPemVV2zfrPIpyMpce +mik+rY3moxtt9XUa5rBouVui7mlHJzWhhpmA8zNL4WukJsPvdFlseqJkth5Ew1DgDzk9qTPxpfPS +vWKErI4cqc1avTc7bgoitPQV55FYxTpE05Uo2cBl6XLK0A+9H7MV2anjpEcJnuDLN/v9vZfVvhga +aaI5gdka9at/yOPiZwud9AzqVN/Ssq+xIvEg37xEHA== +-----END CERTIFICATE----- + +SecureSign Root CA14 +==================== +-----BEGIN CERTIFICATE----- +MIIFcjCCA1qgAwIBAgIUZNtaDCBO6Ncpd8hQJ6JaJ90t8sswDQYJKoZIhvcNAQEMBQAwUTELMAkG +A1UEBhMCSlAxIzAhBgNVBAoTGkN5YmVydHJ1c3QgSmFwYW4gQ28uLCBMdGQuMR0wGwYDVQQDExRT +ZWN1cmVTaWduIFJvb3QgQ0ExNDAeFw0yMDA0MDgwNzA2MTlaFw00NTA0MDgwNzA2MTlaMFExCzAJ +BgNVBAYTAkpQMSMwIQYDVQQKExpDeWJlcnRydXN0IEphcGFuIENvLiwgTHRkLjEdMBsGA1UEAxMU +U2VjdXJlU2lnbiBSb290IENBMTQwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDF0nqh +1oq/FjHQmNE6lPxauG4iwWL3pwon71D2LrGeaBLwbCRjOfHw3xDG3rdSINVSW0KZnvOgvlIfX8xn +bacuUKLBl422+JX1sLrcneC+y9/3OPJH9aaakpUqYllQC6KxNedlsmGy6pJxaeQp8E+BgQQ8sqVb +1MWoWWd7VRxJq3qdwudzTe/NCcLEVxLbAQ4jeQkHO6Lo/IrPj8BGJJw4J+CDnRugv3gVEOuGTgpa +/d/aLIJ+7sr2KeH6caH3iGicnPCNvg9JkdjqOvn90Ghx2+m1K06Ckm9mH+Dw3EzsytHqunQG+bOE +kJTRX45zGRBdAuVwpcAQ0BB8b8VYSbSwbprafZX1zNoCr7gsfXmPvkPx+SgojQlD+Ajda8iLLCSx +jVIHvXiby8posqTdDEx5YMaZ0ZPxMBoH064iwurO8YQJzOAUbn8/ftKChazcqRZOhaBgy/ac18iz +ju3Gm5h1DVXoX+WViwKkrkMpKBGk5hIwAUt1ax5mnXkvpXYvHUC0bcl9eQjs0Wq2XSqypWa9a4X0 +dFbD9ed1Uigspf9mR6XU/v6eVL9lfgHWMI+lNpyiUBzuOIABSMbHdPTGrMNASRZhdCyvjG817XsY +AFs2PJxQDcqSMxDxJklt33UkN4Ii1+iW/RVLApY+B3KVfqs9TC7XyvDf4Fg/LS8EmjijAQIDAQAB +o0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUBpOjCl4oaTeq +YR3r6/wtbyPk86AwDQYJKoZIhvcNAQEMBQADggIBAJaAcgkGfpzMkwQWu6A6jZJOtxEaCnFxEM0E +rX+lRVAQZk5KQaID2RFPeje5S+LGjzJmdSX7684/AykmjbgWHfYfM25I5uj4V7Ibed87hwriZLoA +ymzvftAj63iP/2SbNDefNWWipAA9EiOWWF3KY4fGoweITedpdopTzfFP7ELyk+OZpDc8h7hi2/Ds +Hzc/N19DzFGdtfCXwreFamgLRB7lUe6TzktuhsHSDCRZNhqfLJGP4xjblJUK7ZGqDpncllPjYYPG +FrojutzdfhrGe0K22VoF3Jpf1d+42kd92jjbrDnVHmtsKheMYc2xbXIBw8MgAGJoFjHVdqqGuw6q +nsb58Nn4DSEC5MUoFlkRudlpcyqSeLiSV5sI8jrlL5WwWLdrIBRtFO8KvH7YVdiI2i/6GaX7i+B/ +OfVyK4XELKzvGUWSTLNhB9xNH27SgRNcmvMSZ4PPmz+Ln52kuaiWA3rF7iDeM9ovnhp6dB7h7sxa +OgTdsxoEqBRjrLdHEoOabPXm6RUVkRqEGQ6UROcSjiVbgGcZ3GOTEAtlLor6CZpO2oYofaphNdgO +pygau1LgePhsumywbrmHXumZNTfxPWQrqaA0k89jL9WB365jJ6UeTo3cKXhZ+PmhIIynJkBugnLN +eLLIjzwec+fBH7/PzqUqm9tEZDKgu39cJRNItX+S +-----END CERTIFICATE----- + +SecureSign Root CA15 +==================== +-----BEGIN CERTIFICATE----- +MIICIzCCAamgAwIBAgIUFhXHw9hJp75pDIqI7fBw+d23PocwCgYIKoZIzj0EAwMwUTELMAkGA1UE +BhMCSlAxIzAhBgNVBAoTGkN5YmVydHJ1c3QgSmFwYW4gQ28uLCBMdGQuMR0wGwYDVQQDExRTZWN1 +cmVTaWduIFJvb3QgQ0ExNTAeFw0yMDA0MDgwODMyNTZaFw00NTA0MDgwODMyNTZaMFExCzAJBgNV +BAYTAkpQMSMwIQYDVQQKExpDeWJlcnRydXN0IEphcGFuIENvLiwgTHRkLjEdMBsGA1UEAxMUU2Vj +dXJlU2lnbiBSb290IENBMTUwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAQLUHSNZDKZmbPSYAi4Io5G +dCx4wCtELW1fHcmuS1Iggz24FG1Th2CeX2yF2wYUleDHKP+dX+Sq8bOLbe1PL0vJSpSRZHX+AezB +2Ot6lHhWGENfa4HL9rzatAy2KZMIaY+jQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQD +AgEGMB0GA1UdDgQWBBTrQciu/NWeUUj1vYv0hyCTQSvT9DAKBggqhkjOPQQDAwNoADBlAjEA2S6J +fl5OpBEHvVnCB96rMjhTKkZEBhd6zlHp4P9mLQlO4E/0BdGF9jVg3PVys0Z9AjBEmEYagoUeYWmJ +SwdLZrWeqrqgHkHZAXQ6bkU6iYAZezKYVWOr62Nuk22rGwlgMU4= +-----END CERTIFICATE----- From 31d56c98aa1e1fad7b8060e519af53d394950764 Mon Sep 17 00:00:00 2001 From: ernolf Date: Tue, 20 Aug 2024 12:33:28 +0200 Subject: [PATCH 10/21] fix(share): Ensure unique share tokens - check for token collisions and retry up to three times. - throw after 3 attempts without finding a unique token. Signed-off-by: ernolf --- lib/private/Share20/Manager.php | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/lib/private/Share20/Manager.php b/lib/private/Share20/Manager.php index c339d62da14f6..dfca218d54278 100644 --- a/lib/private/Share20/Manager.php +++ b/lib/private/Share20/Manager.php @@ -776,13 +776,25 @@ public function createShare(IShare $share) { $this->linkCreateChecks($share); $this->setLinkParent($share); - // For now ignore a set token. - $share->setToken( - $this->secureRandom->generate( + for ($i = 0; $i <= 3; $i++) { + $token = $this->secureRandom->generate( \OC\Share\Constants::TOKEN_LENGTH, \OCP\Security\ISecureRandom::CHAR_HUMAN_READABLE - ) - ); + ); + + try { + $this->getShareByToken($token); + } catch (\OCP\Share\Exceptions\ShareNotFound $e) { + // Set the unique token + $share->setToken($token); + break; + } + + // Abort after 3 failed attempts + if ($i >= 3) { + throw new \Exception('Unable to generate a unique share token after 3 attempts.'); + } + } // Verify the expiration date $share = $this->validateExpirationDateLink($share); From ef01b1e1cd871b833d8fb228db737b1dc52d448c Mon Sep 17 00:00:00 2001 From: Louis Chemineau Date: Tue, 24 Sep 2024 16:20:04 +0200 Subject: [PATCH 11/21] fix: Use hashed password in files_external settings Signed-off-by: Louis Chemineau --- apps/files_external/lib/Lib/Auth/Password/GlobalAuth.php | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/apps/files_external/lib/Lib/Auth/Password/GlobalAuth.php b/apps/files_external/lib/Lib/Auth/Password/GlobalAuth.php index dff5bf8662533..99fb03007ea86 100644 --- a/apps/files_external/lib/Lib/Auth/Password/GlobalAuth.php +++ b/apps/files_external/lib/Lib/Auth/Password/GlobalAuth.php @@ -37,6 +37,7 @@ */ class GlobalAuth extends AuthMechanism { public const CREDENTIALS_IDENTIFIER = 'password::global'; + private const PWD_PLACEHOLDER = '************************'; /** @var ICredentialsManager */ protected $credentialsManager; @@ -59,11 +60,18 @@ public function getAuth($uid) { 'password' => '' ]; } else { + $auth['password'] = self::PWD_PLACEHOLDER; return $auth; } } public function saveAuth($uid, $user, $password) { + // Use old password if it has not changed. + if ($password === self::PWD_PLACEHOLDER) { + $auth = $this->credentialsManager->retrieve($uid, self::CREDENTIALS_IDENTIFIER); + $password = $auth['password']; + } + $this->credentialsManager->store($uid, self::CREDENTIALS_IDENTIFIER, [ 'user' => $user, 'password' => $password From 4280736ee833273cea1ed87b496e02ac8ea5ae51 Mon Sep 17 00:00:00 2001 From: Louis Chemineau Date: Tue, 1 Oct 2024 11:14:13 +0200 Subject: [PATCH 12/21] chore: Update smb-kerberos workflow Signed-off-by: Louis Chemineau --- .github/workflows/smb-kerberos.yml | 41 +++++++++++++++++------------- 1 file changed, 24 insertions(+), 17 deletions(-) diff --git a/.github/workflows/smb-kerberos.yml b/.github/workflows/smb-kerberos.yml index c069d665a6ae6..064b6f5e635e9 100644 --- a/.github/workflows/smb-kerberos.yml +++ b/.github/workflows/smb-kerberos.yml @@ -1,20 +1,17 @@ name: Samba Kerberos SSO on: - push: - branches: - - master - - stable* - paths: - - 'apps/files_external/**' - - '.github/workflows/smb-kerberos.yml' pull_request: paths: - 'apps/files_external/**' - '.github/workflows/smb-kerberos.yml' +concurrency: + group: smb-kerberos-${{ github.head_ref || github.run_id }} + cancel-in-progress: true + jobs: smb-kerberos-tests: - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 if: ${{ github.repository_owner != 'nextcloud-gmbh' }} @@ -22,21 +19,30 @@ jobs: steps: - name: Checkout server - uses: actions/checkout@v3 + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 with: submodules: true + - name: Checkout user_saml + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + with: + repository: nextcloud/user_saml + path: apps/user_saml + ref: stable-5.2 - name: Pull images run: | - docker pull icewind1991/samba-krb-test-dc - docker pull icewind1991/samba-krb-test-apache - docker pull icewind1991/samba-krb-test-client + docker pull ghcr.io/icewind1991/samba-krb-test-dc + docker pull ghcr.io/icewind1991/samba-krb-test-apache + docker pull ghcr.io/icewind1991/samba-krb-test-client + docker tag ghcr.io/icewind1991/samba-krb-test-dc icewind1991/samba-krb-test-dc + docker tag ghcr.io/icewind1991/samba-krb-test-apache icewind1991/samba-krb-test-apache + docker tag ghcr.io/icewind1991/samba-krb-test-client icewind1991/samba-krb-test-client - name: Setup AD-DC run: | - cp apps/files_external/tests/*.sh . mkdir data sudo chown -R 33 data apps config - DC_IP=$(./start-dc.sh) - ./start-apache.sh $DC_IP $PWD + DC_IP=$(apps/files_external/tests/start-dc.sh) + sleep 1 + apps/files_external/tests/start-apache.sh $DC_IP $PWD echo "DC_IP=$DC_IP" >> $GITHUB_ENV - name: Set up Nextcloud run: | @@ -61,9 +67,9 @@ jobs: chmod 0777 /tmp/shared/cookies echo "SAML login" - ./client-cmd.sh ${{ env.DC_IP }} curl -c /shared/cookies/jar -s --negotiate -u testuser@DOMAIN.TEST: --delegation always http://httpd.domain.test/index.php/apps/user_saml/saml/login + apps/files_external/tests/client-cmd.sh ${{ env.DC_IP }} curl -c /shared/cookies/jar -s --negotiate -u testuser@DOMAIN.TEST: --delegation always http://httpd.domain.test/index.php/apps/user_saml/saml/login echo "Check we are logged in" - CONTENT=$(./client-cmd.sh ${{ env.DC_IP }} curl -b /shared/cookies/jar -s --negotiate -u testuser@DOMAIN.TEST: --delegation always http://httpd.domain.test/remote.php/webdav/smb/test.txt) + CONTENT=$(apps/files_external/tests/client-cmd.sh ${{ env.DC_IP }} curl -b /shared/cookies/jar -s --negotiate -u testuser@DOMAIN.TEST: --delegation always http://httpd.domain.test/remote.php/webdav/smb/test.txt) CONTENT=$(echo $CONTENT | head -n 1 | tr -d '[:space:]') [[ $CONTENT == "testfile" ]] - name: Show logs @@ -71,4 +77,5 @@ jobs: run: | docker exec --user 33 apache ./occ log:file FILEPATH=$(docker exec --user 33 apache ./occ log:file | grep "Log file:" | cut -d' ' -f3) + echo "$FILEPATH:" docker exec --user 33 apache cat $FILEPATH From ce9be3d5cd90d64fb720625a9927566bedc426e1 Mon Sep 17 00:00:00 2001 From: Louis Chemineau Date: Thu, 5 Sep 2024 20:35:26 +0200 Subject: [PATCH 13/21] fix(dav): Always respond custom error page on exceptions Signed-off-by: Louis Chemineau --- .../composer/composer/autoload_classmap.php | 2 +- .../dav/composer/composer/autoload_static.php | 2 +- .../dav/lib/Connector/Sabre/ServerFactory.php | 6 +- ...rrorPagePlugin.php => ErrorPagePlugin.php} | 86 ++++++++++--------- apps/dav/lib/Server.php | 6 +- .../caldavtest/tests/CalDAV/sync-report.xml | 2 +- ...PluginTest.php => ErrorPagePluginTest.php} | 8 +- build/integration/features/caldav.feature | 18 ++-- build/integration/features/carddav.feature | 15 ++-- core/templates/xml_exception.php | 47 ++++++++++ 10 files changed, 115 insertions(+), 77 deletions(-) rename apps/dav/lib/Files/{BrowserErrorPagePlugin.php => ErrorPagePlugin.php} (58%) rename apps/dav/tests/unit/DAV/{BrowserErrorPagePluginTest.php => ErrorPagePluginTest.php} (86%) create mode 100644 core/templates/xml_exception.php diff --git a/apps/dav/composer/composer/autoload_classmap.php b/apps/dav/composer/composer/autoload_classmap.php index bd8f1c0a0e796..7e3f1cc03c2de 100644 --- a/apps/dav/composer/composer/autoload_classmap.php +++ b/apps/dav/composer/composer/autoload_classmap.php @@ -235,7 +235,7 @@ 'OCA\\DAV\\Events\\SubscriptionUpdatedEvent' => $baseDir . '/../lib/Events/SubscriptionUpdatedEvent.php', 'OCA\\DAV\\Exception\\ServerMaintenanceMode' => $baseDir . '/../lib/Exception/ServerMaintenanceMode.php', 'OCA\\DAV\\Exception\\UnsupportedLimitOnInitialSyncException' => $baseDir . '/../lib/Exception/UnsupportedLimitOnInitialSyncException.php', - 'OCA\\DAV\\Files\\BrowserErrorPagePlugin' => $baseDir . '/../lib/Files/BrowserErrorPagePlugin.php', + 'OCA\\DAV\\Files\\ErrorPagePlugin' => $baseDir . '/../lib/Files/ErrorPagePlugin.php', 'OCA\\DAV\\Files\\FileSearchBackend' => $baseDir . '/../lib/Files/FileSearchBackend.php', 'OCA\\DAV\\Files\\FilesHome' => $baseDir . '/../lib/Files/FilesHome.php', 'OCA\\DAV\\Files\\LazySearchBackend' => $baseDir . '/../lib/Files/LazySearchBackend.php', diff --git a/apps/dav/composer/composer/autoload_static.php b/apps/dav/composer/composer/autoload_static.php index c9d2abe059faf..f259de693d87b 100644 --- a/apps/dav/composer/composer/autoload_static.php +++ b/apps/dav/composer/composer/autoload_static.php @@ -250,7 +250,7 @@ class ComposerStaticInitDAV 'OCA\\DAV\\Events\\SubscriptionUpdatedEvent' => __DIR__ . '/..' . '/../lib/Events/SubscriptionUpdatedEvent.php', 'OCA\\DAV\\Exception\\ServerMaintenanceMode' => __DIR__ . '/..' . '/../lib/Exception/ServerMaintenanceMode.php', 'OCA\\DAV\\Exception\\UnsupportedLimitOnInitialSyncException' => __DIR__ . '/..' . '/../lib/Exception/UnsupportedLimitOnInitialSyncException.php', - 'OCA\\DAV\\Files\\BrowserErrorPagePlugin' => __DIR__ . '/..' . '/../lib/Files/BrowserErrorPagePlugin.php', + 'OCA\\DAV\\Files\\ErrorPagePlugin' => __DIR__ . '/..' . '/../lib/Files/ErrorPagePlugin.php', 'OCA\\DAV\\Files\\FileSearchBackend' => __DIR__ . '/..' . '/../lib/Files/FileSearchBackend.php', 'OCA\\DAV\\Files\\FilesHome' => __DIR__ . '/..' . '/../lib/Files/FilesHome.php', 'OCA\\DAV\\Files\\LazySearchBackend' => __DIR__ . '/..' . '/../lib/Files/LazySearchBackend.php', diff --git a/apps/dav/lib/Connector/Sabre/ServerFactory.php b/apps/dav/lib/Connector/Sabre/ServerFactory.php index 755d13f837184..5725e42b04f84 100644 --- a/apps/dav/lib/Connector/Sabre/ServerFactory.php +++ b/apps/dav/lib/Connector/Sabre/ServerFactory.php @@ -34,7 +34,7 @@ use OCP\Files\Folder; use OCA\DAV\AppInfo\PluginManager; use OCA\DAV\DAV\ViewOnlyPlugin; -use OCA\DAV\Files\BrowserErrorPagePlugin; +use OCA\DAV\Files\ErrorPagePlugin; use OCP\Files\Mount\IMountManager; use OCP\IConfig; use OCP\IDBConnection; @@ -120,9 +120,7 @@ public function createServer(string $baseUri, $server->addPlugin(new \OCA\DAV\Connector\Sabre\FakeLockerPlugin()); } - if (BrowserErrorPagePlugin::isBrowserRequest($this->request)) { - $server->addPlugin(new BrowserErrorPagePlugin()); - } + $server->addPlugin(new ErrorPagePlugin($this->request, $this->config)); // wait with registering these until auth is handled and the filesystem is setup $server->on('beforeMethod:*', function () use ($server, $objectTree, $viewCallBack) { diff --git a/apps/dav/lib/Files/BrowserErrorPagePlugin.php b/apps/dav/lib/Files/ErrorPagePlugin.php similarity index 58% rename from apps/dav/lib/Files/BrowserErrorPagePlugin.php rename to apps/dav/lib/Files/ErrorPagePlugin.php index b3ce591bd4a8e..ccf396d551c35 100644 --- a/apps/dav/lib/Files/BrowserErrorPagePlugin.php +++ b/apps/dav/lib/Files/ErrorPagePlugin.php @@ -24,17 +24,22 @@ */ namespace OCA\DAV\Files; -use OC\AppFramework\Http\Request; use OC_Template; use OCP\AppFramework\Http\ContentSecurityPolicy; +use OCP\IConfig; use OCP\IRequest; use Sabre\DAV\Exception; use Sabre\DAV\Server; use Sabre\DAV\ServerPlugin; -class BrowserErrorPagePlugin extends ServerPlugin { - /** @var Server */ - private $server; +class ErrorPagePlugin extends ServerPlugin { + private ?Server $server = null; + + public function __construct( + private IRequest $request, + private IConfig $config, + ) { + } /** * This initializes the plugin. @@ -43,36 +48,13 @@ class BrowserErrorPagePlugin extends ServerPlugin { * addPlugin is called. * * This method should set up the required event subscriptions. - * - * @param Server $server - * @return void */ - public function initialize(Server $server) { + public function initialize(Server $server): void { $this->server = $server; $server->on('exception', [$this, 'logException'], 1000); } - /** - * @param IRequest $request - * @return bool - */ - public static function isBrowserRequest(IRequest $request) { - if ($request->getMethod() !== 'GET') { - return false; - } - return $request->isUserAgent([ - Request::USER_AGENT_IE, - Request::USER_AGENT_MS_EDGE, - Request::USER_AGENT_CHROME, - Request::USER_AGENT_FIREFOX, - Request::USER_AGENT_SAFARI, - ]); - } - - /** - * @param \Exception $ex - */ - public function logException(\Exception $ex) { + public function logException(\Throwable $ex): void { if ($ex instanceof Exception) { $httpCode = $ex->getHTTPCode(); $headers = $ex->getHTTPHeaders($this->server); @@ -82,7 +64,7 @@ public function logException(\Exception $ex) { } $this->server->httpResponse->addHeaders($headers); $this->server->httpResponse->setStatus($httpCode); - $body = $this->generateBody($httpCode); + $body = $this->generateBody($ex, $httpCode); $this->server->httpResponse->setBody($body); $csp = new ContentSecurityPolicy(); $this->server->httpResponse->addHeader('Content-Security-Policy', $csp->buildPolicy()); @@ -91,20 +73,34 @@ public function logException(\Exception $ex) { /** * @codeCoverageIgnore - * @return bool|string + * @return string */ - public function generateBody(int $httpCode) { - $request = \OC::$server->getRequest(); - - $templateName = 'exception'; - if ($httpCode === 403 || $httpCode === 404) { - $templateName = (string)$httpCode; + public function generateBody(\Throwable $ex, int $httpCode): mixed { + if ($this->acceptHtml()) { + $templateName = 'exception'; + $renderAs = 'guest'; + if ($httpCode === 403 || $httpCode === 404) { + $templateName = (string)$httpCode; + } + } else { + $templateName = 'xml_exception'; + $renderAs = null; + $this->server->httpResponse->setHeader('Content-Type', 'application/xml; charset=utf-8'); } - $content = new OC_Template('core', $templateName, 'guest'); + $debug = $this->config->getSystemValueBool('debug', false); + + $content = new OC_Template('core', $templateName, $renderAs); $content->assign('title', $this->server->httpResponse->getStatusText()); - $content->assign('remoteAddr', $request->getRemoteAddress()); - $content->assign('requestID', $request->getId()); + $content->assign('remoteAddr', $this->request->getRemoteAddress()); + $content->assign('requestID', $this->request->getId()); + $content->assign('debugMode', $debug); + $content->assign('errorClass', get_class($ex)); + $content->assign('errorMsg', $ex->getMessage()); + $content->assign('errorCode', $ex->getCode()); + $content->assign('file', $ex->getFile()); + $content->assign('line', $ex->getLine()); + $content->assign('exception', $ex); return $content->fetchPage(); } @@ -115,4 +111,14 @@ public function sendResponse() { $this->server->sapi->sendResponse($this->server->httpResponse); exit(); } + + private function acceptHtml(): bool { + foreach (explode(',', $this->request->getHeader('Accept')) as $part) { + $subparts = explode(';', $part); + if (str_ends_with($subparts[0], '/html')) { + return true; + } + } + return false; + } } diff --git a/apps/dav/lib/Server.php b/apps/dav/lib/Server.php index fe6428361ced2..dac505520d606 100644 --- a/apps/dav/lib/Server.php +++ b/apps/dav/lib/Server.php @@ -67,7 +67,7 @@ use OCA\DAV\DAV\PublicAuth; use OCA\DAV\DAV\ViewOnlyPlugin; use OCA\DAV\Events\SabrePluginAuthInitEvent; -use OCA\DAV\Files\BrowserErrorPagePlugin; +use OCA\DAV\Files\ErrorPagePlugin; use OCA\DAV\Files\LazySearchBackend; use OCA\DAV\Profiler\ProfilerPlugin; use OCA\DAV\Provisioning\Apple\AppleProvisioningPlugin; @@ -242,9 +242,7 @@ public function __construct(IRequest $request, string $baseUri) { $this->server->addPlugin(new FakeLockerPlugin()); } - if (BrowserErrorPagePlugin::isBrowserRequest($request)) { - $this->server->addPlugin(new BrowserErrorPagePlugin()); - } + $this->server->addPlugin(new ErrorPagePlugin($this->request, \OC::$server->getConfig())); $lazySearchBackend = new LazySearchBackend(); $this->server->addPlugin(new SearchPlugin($lazySearchBackend)); diff --git a/apps/dav/tests/travis/caldavtest/tests/CalDAV/sync-report.xml b/apps/dav/tests/travis/caldavtest/tests/CalDAV/sync-report.xml index cf4fcde251f4d..388d9df841383 100644 --- a/apps/dav/tests/travis/caldavtest/tests/CalDAV/sync-report.xml +++ b/apps/dav/tests/travis/caldavtest/tests/CalDAV/sync-report.xml @@ -2712,7 +2712,7 @@ prepostcondition error - {DAV:}valid-sync-token + {http://sabredav.org/ns}exception ignoreextras diff --git a/apps/dav/tests/unit/DAV/BrowserErrorPagePluginTest.php b/apps/dav/tests/unit/DAV/ErrorPagePluginTest.php similarity index 86% rename from apps/dav/tests/unit/DAV/BrowserErrorPagePluginTest.php rename to apps/dav/tests/unit/DAV/ErrorPagePluginTest.php index b6ec05afd7875..3c87574e8d28f 100644 --- a/apps/dav/tests/unit/DAV/BrowserErrorPagePluginTest.php +++ b/apps/dav/tests/unit/DAV/ErrorPagePluginTest.php @@ -23,11 +23,11 @@ */ namespace OCA\DAV\Tests\unit\DAV; -use OCA\DAV\Files\BrowserErrorPagePlugin; +use OCA\DAV\Files\ErrorPagePlugin; use Sabre\DAV\Exception\NotFound; use Sabre\HTTP\Response; -class BrowserErrorPagePluginTest extends \Test\TestCase { +class ErrorPagePluginTest extends \Test\TestCase { /** * @dataProvider providesExceptions @@ -35,8 +35,8 @@ class BrowserErrorPagePluginTest extends \Test\TestCase { * @param $exception */ public function test($expectedCode, $exception): void { - /** @var BrowserErrorPagePlugin | \PHPUnit\Framework\MockObject\MockObject $plugin */ - $plugin = $this->getMockBuilder(BrowserErrorPagePlugin::class)->setMethods(['sendResponse', 'generateBody'])->getMock(); + /** @var ErrorPagePlugin | \PHPUnit\Framework\MockObject\MockObject $plugin */ + $plugin = $this->getMockBuilder(ErrorPagePlugin::class)->disableOriginalConstructor()->setMethods(['sendResponse', 'generateBody'])->getMock(); $plugin->expects($this->once())->method('generateBody')->willReturn(':boom:'); $plugin->expects($this->once())->method('sendResponse'); /** @var \Sabre\DAV\Server | \PHPUnit\Framework\MockObject\MockObject $server */ diff --git a/build/integration/features/caldav.feature b/build/integration/features/caldav.feature index e2cb4f8dc9235..dc0f44af46007 100644 --- a/build/integration/features/caldav.feature +++ b/build/integration/features/caldav.feature @@ -3,8 +3,7 @@ Feature: caldav Given user "user0" exists When "admin" requests calendar "user0/MyCalendar" on the endpoint "/remote.php/dav/calendars/" Then The CalDAV HTTP status code should be "404" - And The exception is "Sabre\DAV\Exception\NotFound" - And The error message is "Node with name 'MyCalendar' could not be found" + And The exception is "Internal Server Error" Scenario: Accessing a not shared calendar of another user Given user "user0" exists @@ -12,8 +11,7 @@ Feature: caldav Given The CalDAV HTTP status code should be "201" When "user0" requests calendar "admin/MyCalendar" on the endpoint "/remote.php/dav/calendars/" Then The CalDAV HTTP status code should be "404" - And The exception is "Sabre\DAV\Exception\NotFound" - And The error message is "Node with name 'MyCalendar' could not be found" + And The exception is "Internal Server Error" Scenario: Accessing a not shared calendar of another user via the legacy endpoint Given user "user0" exists @@ -28,8 +26,7 @@ Feature: caldav Given user "user0" exists When "user0" requests calendar "admin/MyCalendar" on the endpoint "/remote.php/dav/calendars/" Then The CalDAV HTTP status code should be "404" - And The exception is "Sabre\DAV\Exception\NotFound" - And The error message is "Node with name 'MyCalendar' could not be found" + And The exception is "Internal Server Error" Scenario: Accessing a not existing calendar of another user via the legacy endpoint Given user "user0" exists @@ -42,8 +39,7 @@ Feature: caldav Given user "user0" exists When "user0" requests calendar "admin/MyCalendar" on the endpoint "/remote.php/dav/calendars/" Then The CalDAV HTTP status code should be "404" - And The exception is "Sabre\DAV\Exception\NotFound" - And The error message is "Node with name 'MyCalendar' could not be found" + And The exception is "Internal Server Error" Scenario: Creating a new calendar When "admin" creates a calendar named "MyCalendar" @@ -64,8 +60,7 @@ Feature: caldav Given user "user0" exists When "user0" sends a create calendar request to "admin/MyCalendar2" on the endpoint "/remote.php/dav/calendars/" Then The CalDAV HTTP status code should be "404" - And The exception is "Sabre\DAV\Exception\NotFound" - And The error message is "Node with name 'admin' could not be found" + And The exception is "Internal Server Error" Scenario: Create calendar request for existing calendar of another user Given user "user0" exists @@ -73,5 +68,4 @@ Feature: caldav Then The CalDAV HTTP status code should be "201" When "user0" sends a create calendar request to "admin/MyCalendar2" on the endpoint "/remote.php/dav/calendars/" Then The CalDAV HTTP status code should be "404" - And The exception is "Sabre\DAV\Exception\NotFound" - And The error message is "Node with name 'admin' could not be found" + And The exception is "Internal Server Error" diff --git a/build/integration/features/carddav.feature b/build/integration/features/carddav.feature index 9c9df6ddd94be..15f1e95e73770 100644 --- a/build/integration/features/carddav.feature +++ b/build/integration/features/carddav.feature @@ -2,15 +2,13 @@ Feature: carddav Scenario: Accessing a not existing addressbook of another user Given user "user0" exists When "admin" requests addressbook "user0/MyAddressbook" with statuscode "404" on the endpoint "/remote.php/dav/addressbooks/users/" - And The CardDAV exception is "Sabre\DAV\Exception\NotFound" - And The CardDAV error message is "Addressbook with name 'MyAddressbook' could not be found" + And The CardDAV exception is "Internal Server Error" Scenario: Accessing a not shared addressbook of another user Given user "user0" exists Given "admin" creates an addressbook named "MyAddressbook" with statuscode "201" When "user0" requests addressbook "admin/MyAddressbook" with statuscode "404" on the endpoint "/remote.php/dav/addressbooks/users/" - And The CardDAV exception is "Sabre\DAV\Exception\NotFound" - And The CardDAV error message is "Addressbook with name 'MyAddressbook' could not be found" + And The CardDAV exception is "Internal Server Error" Scenario: Accessing a not existing addressbook of another user via legacy endpoint Given user "user0" exists @@ -28,8 +26,7 @@ Feature: carddav Scenario: Accessing a not existing addressbook of myself Given user "user0" exists When "user0" requests addressbook "admin/MyAddressbook" with statuscode "404" on the endpoint "/remote.php/dav/addressbooks/users/" - And The CardDAV exception is "Sabre\DAV\Exception\NotFound" - And The CardDAV error message is "Addressbook with name 'MyAddressbook' could not be found" + And The CardDAV exception is "Internal Server Error" Scenario: Creating a new addressbook When "admin" creates an addressbook named "MyAddressbook" with statuscode "201" @@ -67,13 +64,11 @@ Feature: carddav Given user "user0" exists When "user0" sends a create addressbook request to "admin/MyAddressbook2" on the endpoint "/remote.php/dav/addressbooks/" Then The CardDAV HTTP status code should be "404" - And The CardDAV exception is "Sabre\DAV\Exception\NotFound" - And The CardDAV error message is "File not found: admin in 'addressbooks'" + And The CardDAV exception is "Internal Server Error" Scenario: Create addressbook request for existing addressbook of another user Given user "user0" exists When "admin" creates an addressbook named "MyAddressbook2" with statuscode "201" When "user0" sends a create addressbook request to "admin/MyAddressbook2" on the endpoint "/remote.php/dav/addressbooks/" Then The CardDAV HTTP status code should be "404" - And The CardDAV exception is "Sabre\DAV\Exception\NotFound" - And The CardDAV error message is "File not found: admin in 'addressbooks'" + And The CardDAV exception is "Internal Server Error" diff --git a/core/templates/xml_exception.php b/core/templates/xml_exception.php new file mode 100644 index 0000000000000..d7fb276a7306b --- /dev/null +++ b/core/templates/xml_exception.php @@ -0,0 +1,47 @@ +getTraceAsString()); + + if ($e->getPrevious() !== null) { + print_unescaped(''); + print_exception($e->getPrevious(), $l); + print_unescaped(''); + } +} + +?> + + + t('Internal Server Error')) ?> + + t('The server was unable to complete your request.')) ?> + t('If this happens again, please send the technical details below to the server administrator.')) ?> + t('More details can be found in the server log.')) ?> + + t('For more details see the documentation ↗.'))?>: + + + + + + + + + + + + + + + + + + + + From 8a4394bfd875f1a7d3bb7601dfd422471af7704c Mon Sep 17 00:00:00 2001 From: Ferdinand Thiessen Date: Fri, 6 Sep 2024 16:49:33 +0200 Subject: [PATCH 14/21] fix: Replace conflicting tags in `xml_exception` template The ` --- core/templates/xml_exception.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/templates/xml_exception.php b/core/templates/xml_exception.php index d7fb276a7306b..342238d824bb7 100644 --- a/core/templates/xml_exception.php +++ b/core/templates/xml_exception.php @@ -15,8 +15,8 @@ function print_exception(Throwable $e, \OCP\IL10N $l): void { } } +print_unescaped('' . "\n"); ?> - t('Internal Server Error')) ?> From e20eaa3470aa9769c4529148640fd5a1c9653864 Mon Sep 17 00:00:00 2001 From: Louis Chemineau Date: Tue, 17 Sep 2024 17:32:33 +0200 Subject: [PATCH 15/21] fix: Drop unnecessary exit Signed-off-by: Louis Chemineau --- apps/dav/lib/Files/ErrorPagePlugin.php | 1 - 1 file changed, 1 deletion(-) diff --git a/apps/dav/lib/Files/ErrorPagePlugin.php b/apps/dav/lib/Files/ErrorPagePlugin.php index ccf396d551c35..59d49cb8fda7c 100644 --- a/apps/dav/lib/Files/ErrorPagePlugin.php +++ b/apps/dav/lib/Files/ErrorPagePlugin.php @@ -109,7 +109,6 @@ public function generateBody(\Throwable $ex, int $httpCode): mixed { */ public function sendResponse() { $this->server->sapi->sendResponse($this->server->httpResponse); - exit(); } private function acceptHtml(): bool { From 75d7537b0976301069c83d07b3296d1fad9399d1 Mon Sep 17 00:00:00 2001 From: Louis Chemineau Date: Wed, 18 Sep 2024 17:09:49 +0200 Subject: [PATCH 16/21] fix: Override start method of \Sabre\DAV\Server to remove exception output Signed-off-by: Louis Chemineau --- apps/dav/lib/Connector/Sabre/Server.php | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/apps/dav/lib/Connector/Sabre/Server.php b/apps/dav/lib/Connector/Sabre/Server.php index 6cf6fa954c8db..0831a47b49198 100644 --- a/apps/dav/lib/Connector/Sabre/Server.php +++ b/apps/dav/lib/Connector/Sabre/Server.php @@ -43,4 +43,27 @@ public function __construct($treeOrNode = null) { self::$exposeVersion = false; $this->enablePropfindDepthInfinity = true; } + + // Copied from 3rdparty/sabre/dav/lib/DAV/Server.php + // Should be them exact same without the exception output. + public function start(): void { + try { + // If nginx (pre-1.2) is used as a proxy server, and SabreDAV as an + // origin, we must make sure we send back HTTP/1.0 if this was + // requested. + // This is mainly because nginx doesn't support Chunked Transfer + // Encoding, and this forces the webserver SabreDAV is running on, + // to buffer entire responses to calculate Content-Length. + $this->httpResponse->setHTTPVersion($this->httpRequest->getHTTPVersion()); + + // Setting the base url + $this->httpRequest->setBaseUrl($this->getBaseUri()); + $this->invokeMethod($this->httpRequest, $this->httpResponse); + } catch (\Throwable $e) { + try { + $this->emit('exception', [$e]); + } catch (\Exception $ignore) { + } + } + } } From 2a10014d0f5a1412f16df799668dd6d3b5be0fe3 Mon Sep 17 00:00:00 2001 From: Louis Chemineau Date: Wed, 18 Sep 2024 09:23:19 +0200 Subject: [PATCH 17/21] fix: Prevent duplicate creation of print_exception function Signed-off-by: Louis Chemineau --- core/templates/exception.php | 15 +-------------- core/templates/print_exception.php | 21 +++++++++++++++++++++ core/templates/print_xml_exception.php | 16 ++++++++++++++++ core/templates/xml_exception.php | 10 +--------- 4 files changed, 39 insertions(+), 23 deletions(-) create mode 100644 core/templates/print_exception.php create mode 100644 core/templates/print_xml_exception.php diff --git a/core/templates/exception.php b/core/templates/exception.php index d26e9ff4f94ed..324724e67ace2 100644 --- a/core/templates/exception.php +++ b/core/templates/exception.php @@ -4,20 +4,7 @@ style('core', ['styles', 'header']); -function print_exception(Throwable $e, \OCP\IL10N $l): void { - print_unescaped('
');
-	p($e->getTraceAsString());
-	print_unescaped('
'); - - if ($e->getPrevious() !== null) { - print_unescaped('
'); - print_unescaped('

'); - p($l->t('Previous')); - print_unescaped('

'); - - print_exception($e->getPrevious(), $l); - } -} +require_once __DIR__ . '/print_exception.php'; ?>
diff --git a/core/templates/print_exception.php b/core/templates/print_exception.php new file mode 100644 index 0000000000000..2def6d4e9d904 --- /dev/null +++ b/core/templates/print_exception.php @@ -0,0 +1,21 @@ +'); + p($e->getTraceAsString()); + print_unescaped(''); + + if ($e->getPrevious() !== null) { + print_unescaped('
'); + print_unescaped('

'); + p($l->t('Previous')); + print_unescaped('

'); + + print_exception($e->getPrevious(), $l); + } +} diff --git a/core/templates/print_xml_exception.php b/core/templates/print_xml_exception.php new file mode 100644 index 0000000000000..94452d8ae9d3f --- /dev/null +++ b/core/templates/print_xml_exception.php @@ -0,0 +1,16 @@ +getTraceAsString()); + + if ($e->getPrevious() !== null) { + print_unescaped(''); + print_exception($e->getPrevious(), $l); + print_unescaped(''); + } +} diff --git a/core/templates/xml_exception.php b/core/templates/xml_exception.php index 342238d824bb7..ba808c88595c2 100644 --- a/core/templates/xml_exception.php +++ b/core/templates/xml_exception.php @@ -5,15 +5,7 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -function print_exception(Throwable $e, \OCP\IL10N $l): void { - p($e->getTraceAsString()); - - if ($e->getPrevious() !== null) { - print_unescaped(''); - print_exception($e->getPrevious(), $l); - print_unescaped(''); - } -} +require_once __DIR__ . '/print_xml_exception.php'; print_unescaped('' . "\n"); ?> From 9edc02401aabc4185a00c890aaee470563668c8f Mon Sep 17 00:00:00 2001 From: Anna Larch Date: Tue, 1 Oct 2024 22:52:08 +0200 Subject: [PATCH 18/21] fix(caldav): add missing handlers Signed-off-by: Anna Larch --- .../WebcalCaching/RefreshWebcalService.php | 151 +++++++----------- .../RefreshWebcalServiceTest.php | 31 ++-- 2 files changed, 71 insertions(+), 111 deletions(-) diff --git a/apps/dav/lib/CalDAV/WebcalCaching/RefreshWebcalService.php b/apps/dav/lib/CalDAV/WebcalCaching/RefreshWebcalService.php index 8928d05b93c96..ddee241f52866 100644 --- a/apps/dav/lib/CalDAV/WebcalCaching/RefreshWebcalService.php +++ b/apps/dav/lib/CalDAV/WebcalCaching/RefreshWebcalService.php @@ -31,18 +31,14 @@ namespace OCA\DAV\CalDAV\WebcalCaching; use Exception; -use GuzzleHttp\HandlerStack; -use GuzzleHttp\Middleware; +use GuzzleHttp\RequestOptions; use OCA\DAV\CalDAV\CalDavBackend; use OCP\Http\Client\IClientService; use OCP\Http\Client\LocalServerException; use OCP\IConfig; -use Psr\Http\Message\RequestInterface; -use Psr\Http\Message\ResponseInterface; use Psr\Log\LoggerInterface; use Sabre\DAV\Exception\BadRequest; use Sabre\DAV\PropPatch; -use Sabre\DAV\Xml\Property\Href; use Sabre\VObject\Component; use Sabre\VObject\DateTimeParser; use Sabre\VObject\InvalidDataException; @@ -76,7 +72,7 @@ public function __construct(CalDavBackend $calDavBackend, IClientService $client $this->logger = $logger; } - public function refreshSubscription(string $principalUri, string $uri) { + public function refreshSubscription(string $principalUri, string $uri): void { $subscription = $this->getSubscription($principalUri, $uri); $mutations = []; if (!$subscription) { @@ -84,7 +80,7 @@ public function refreshSubscription(string $principalUri, string $uri) { } $webcalData = $this->queryWebcalFeed($subscription, $mutations); - if (!$webcalData) { + if ($webcalData === null) { return; } @@ -127,7 +123,7 @@ public function refreshSubscription(string $principalUri, string $uri) { $calendarData = $vObject->serialize(); try { $this->calDavBackend->createCalendarObject($subscription['id'], $objectUri, $calendarData, CalDavBackend::CALENDAR_TYPE_SUBSCRIPTION); - } catch (NoInstancesException | BadRequest $ex) { + } catch (NoInstancesException|BadRequest $ex) { $this->logger->error('Unable to create calendar object from subscription {subscriptionId}', ['exception' => $ex, 'subscriptionId' => $subscription['id'], 'source' => $subscription['source']]); } } @@ -139,7 +135,7 @@ public function refreshSubscription(string $principalUri, string $uri) { $this->updateSubscription($subscription, $mutations); } catch (ParseException $ex) { - $this->logger->error("Subscription {subscriptionId} could not be refreshed due to a parsing error", ['exception' => $ex, 'subscriptionId' => $subscription['id']]); + $this->logger->error('Subscription {subscriptionId} could not be refreshed due to a parsing error', ['exception' => $ex, 'subscriptionId' => $subscription['id']]); } } @@ -165,106 +161,81 @@ function ($sub) use ($uri) { * gets webcal feed from remote server */ private function queryWebcalFeed(array $subscription, array &$mutations): ?string { - $client = $this->clientService->newClient(); - - $didBreak301Chain = false; - $latestLocation = null; - - $handlerStack = HandlerStack::create(); - $handlerStack->push(Middleware::mapRequest(function (RequestInterface $request) { - return $request - ->withHeader('Accept', 'text/calendar, application/calendar+json, application/calendar+xml') - ->withHeader('User-Agent', 'Nextcloud Webcal Service'); - })); - $handlerStack->push(Middleware::mapResponse(function (ResponseInterface $response) use (&$didBreak301Chain, &$latestLocation) { - if (!$didBreak301Chain) { - if ($response->getStatusCode() !== 301) { - $didBreak301Chain = true; - } else { - $latestLocation = $response->getHeader('Location'); - } - } - return $response; - })); - - $allowLocalAccess = $this->config->getAppValue('dav', 'webcalAllowLocalAccess', 'no'); $subscriptionId = $subscription['id']; $url = $this->cleanURL($subscription['source']); if ($url === null) { return null; } - try { - $params = [ - 'allow_redirects' => [ - 'redirects' => 10 - ], - 'handler' => $handlerStack, - 'nextcloud' => [ - 'allow_local_address' => $allowLocalAccess === 'yes', - ] - ]; - - $user = parse_url($subscription['source'], PHP_URL_USER); - $pass = parse_url($subscription['source'], PHP_URL_PASS); - if ($user !== null && $pass !== null) { - $params['auth'] = [$user, $pass]; - } - - $response = $client->get($url, $params); - $body = $response->getBody(); + $allowLocalAccess = $this->config->getAppValue('dav', 'webcalAllowLocalAccess', 'no'); - if ($latestLocation) { - $mutations['{http://calendarserver.org/ns/}source'] = new Href($latestLocation); - } + $params = [ + 'nextcloud' => [ + 'allow_local_address' => $allowLocalAccess === 'yes', + ], + RequestOptions::HEADERS => [ + 'User-Agent' => 'Nextcloud Webcal Service', + 'Accept' => 'text/calendar, application/calendar+json, application/calendar+xml', + ], + ]; + + $user = parse_url($subscription['source'], PHP_URL_USER); + $pass = parse_url($subscription['source'], PHP_URL_PASS); + if ($user !== null && $pass !== null) { + $params[RequestOptions::AUTH] = [$user, $pass]; + } - $contentType = $response->getHeader('Content-Type'); - $contentType = explode(';', $contentType, 2)[0]; - switch ($contentType) { - case 'application/calendar+json': - try { - $jCalendar = Reader::readJson($body, Reader::OPTION_FORGIVING); - } catch (Exception $ex) { - // In case of a parsing error return null - $this->logger->warning("Subscription $subscriptionId could not be parsed", ['exception' => $ex]); - return null; - } - return $jCalendar->serialize(); - - case 'application/calendar+xml': - try { - $xCalendar = Reader::readXML($body); - } catch (Exception $ex) { - // In case of a parsing error return null - $this->logger->warning("Subscription $subscriptionId could not be parsed", ['exception' => $ex]); - return null; - } - return $xCalendar->serialize(); - - case 'text/calendar': - default: - try { - $vCalendar = Reader::read($body); - } catch (Exception $ex) { - // In case of a parsing error return null - $this->logger->warning("Subscription $subscriptionId could not be parsed", ['exception' => $ex]); - return null; - } - return $vCalendar->serialize(); - } + try { + $client = $this->clientService->newClient(); + $response = $client->get($url, $params); } catch (LocalServerException $ex) { $this->logger->warning("Subscription $subscriptionId was not refreshed because it violates local access rules", [ 'exception' => $ex, ]); - return null; } catch (Exception $ex) { $this->logger->warning("Subscription $subscriptionId could not be refreshed due to a network error", [ 'exception' => $ex, ]); - return null; } + + $body = $response->getBody(); + + $contentType = $response->getHeader('Content-Type'); + $contentType = explode(';', $contentType, 2)[0]; + switch ($contentType) { + case 'application/calendar+json': + try { + $jCalendar = Reader::readJson($body, Reader::OPTION_FORGIVING); + } catch (Exception $ex) { + // In case of a parsing error return null + $this->logger->warning("Subscription $subscriptionId could not be parsed", ['exception' => $ex]); + return null; + } + return $jCalendar->serialize(); + + case 'application/calendar+xml': + try { + $xCalendar = Reader::readXML($body); + } catch (Exception $ex) { + // In case of a parsing error return null + $this->logger->warning("Subscription $subscriptionId could not be parsed", ['exception' => $ex]); + return null; + } + return $xCalendar->serialize(); + + case 'text/calendar': + default: + try { + $vCalendar = Reader::read($body); + } catch (Exception $ex) { + // In case of a parsing error return null + $this->logger->warning("Subscription $subscriptionId could not be parsed", ['exception' => $ex]); + return null; + } + return $vCalendar->serialize(); + } } /** diff --git a/apps/dav/tests/unit/CalDAV/WebcalCaching/RefreshWebcalServiceTest.php b/apps/dav/tests/unit/CalDAV/WebcalCaching/RefreshWebcalServiceTest.php index 5ae62ea8b74f1..62bfd5f828dec 100644 --- a/apps/dav/tests/unit/CalDAV/WebcalCaching/RefreshWebcalServiceTest.php +++ b/apps/dav/tests/unit/CalDAV/WebcalCaching/RefreshWebcalServiceTest.php @@ -26,7 +26,6 @@ */ namespace OCA\DAV\Tests\unit\CalDAV\WebcalCaching; -use GuzzleHttp\HandlerStack; use OCA\DAV\CalDAV\CalDavBackend; use OCA\DAV\CalDAV\WebcalCaching\RefreshWebcalService; use OCP\Http\Client\IClient; @@ -120,9 +119,7 @@ public function testRun(string $body, string $contentType, string $result): void $client->expects($this->once()) ->method('get') - ->with('https://foo.bar/bla2', $this->callback(function ($obj) { - return $obj['allow_redirects']['redirects'] === 10 && $obj['handler'] instanceof HandlerStack; - })) + ->with('https://foo.bar/bla2') ->willReturn($response); $response->expects($this->once()) @@ -188,9 +185,7 @@ public function testRunCreateCalendarNoException(string $body, string $contentTy $client->expects($this->once()) ->method('get') - ->with('https://foo.bar/bla2', $this->callback(function ($obj) { - return $obj['allow_redirects']['redirects'] === 10 && $obj['handler'] instanceof HandlerStack; - })) + ->with('https://foo.bar/bla2') ->willReturn($response); $response->expects($this->once()) @@ -212,7 +207,7 @@ public function testRunCreateCalendarNoException(string $body, string $contentTy $noInstanceException = new NoInstancesException("can't add calendar object"); $this->caldavBackend->expects($this->once()) - ->method("createCalendarObject") + ->method('createCalendarObject') ->willThrowException($noInstanceException); $this->logger->expects($this->once()) @@ -265,9 +260,7 @@ public function testRunCreateCalendarBadRequest(string $body, string $contentTyp $client->expects($this->once()) ->method('get') - ->with('https://foo.bar/bla2', $this->callback(function ($obj) { - return $obj['allow_redirects']['redirects'] === 10 && $obj['handler'] instanceof HandlerStack; - })) + ->with('https://foo.bar/bla2') ->willReturn($response); $response->expects($this->once()) @@ -289,7 +282,7 @@ public function testRunCreateCalendarBadRequest(string $body, string $contentTyp $badRequestException = new BadRequest("can't add reach calendar url"); $this->caldavBackend->expects($this->once()) - ->method("createCalendarObject") + ->method('createCalendarObject') ->willThrowException($badRequestException); $this->logger->expects($this->once()) @@ -367,7 +360,7 @@ public function testRunLocalURL(string $source): void { $this->logger->expects($this->once()) ->method('warning') - ->with("Subscription 42 was not refreshed because it violates local access rules", ['exception' => $localServerException]); + ->with('Subscription 42 was not refreshed because it violates local access rules', ['exception' => $localServerException]); $refreshWebcalService->refreshSubscription('principals/users/testuser', 'sub123'); } @@ -411,15 +404,11 @@ public function testInvalidUrl(): void { ]); $client = $this->createMock(IClient::class); - $this->clientService->expects($this->once()) - ->method('newClient') - ->with() - ->willReturn($client); + $this->clientService->expects($this->never()) + ->method('newClient'); - $this->config->expects($this->once()) - ->method('getAppValue') - ->with('dav', 'webcalAllowLocalAccess', 'no') - ->willReturn('no'); + $this->config->expects($this->never()) + ->method('getAppValue'); $client->expects($this->never()) ->method('get'); From 80798fca25753a42cf8d46307b276f5eea2207d1 Mon Sep 17 00:00:00 2001 From: nextcloud-command Date: Sat, 19 Oct 2024 02:29:23 +0000 Subject: [PATCH 19/21] fix(security): Update code signing revocation list Signed-off-by: GitHub --- resources/codesigning/root.crl | 47 +++++++++++++++++----------------- 1 file changed, 24 insertions(+), 23 deletions(-) diff --git a/resources/codesigning/root.crl b/resources/codesigning/root.crl index 871a8d8f99409..18ecbd554bf63 100644 --- a/resources/codesigning/root.crl +++ b/resources/codesigning/root.crl @@ -1,8 +1,8 @@ -----BEGIN X509 CRL----- -MIII9DCCB9wCAQEwDQYJKoZIhvcNAQELBQAwezELMAkGA1UEBhMCREUxGzAZBgNV +MIIJHjCCCAYCAQEwDQYJKoZIhvcNAQELBQAwezELMAkGA1UEBhMCREUxGzAZBgNV BAgMEkJhZGVuLVd1ZXJ0dGVtYmVyZzEXMBUGA1UECgwOTmV4dGNsb3VkIEdtYkgx NjA0BgNVBAMMLU5leHRjbG91ZCBDb2RlIFNpZ25pbmcgSW50ZXJtZWRpYXRlIEF1 -dGhvcml0eRcNMjQwNjI2MTYxNzE0WhcNMzQwNTA1MTYxNzE0WjCCBvkwEwICEAIX +dGhvcml0eRcNMjQxMDE4MDcxNzU5WhcNMzQwODI3MDcxNzU5WjCCByMwEwICEAIX DTIxMDQxOTA5NTI0NVowEwICEBAXDTE2MTAxNzEyMDkxOVowEwICEBYXDTE3MTEy MzE3MzUyOVowEwICEBcXDTE3MDIyMDEwMDIzOFowEwICEBgXDTE5MDEzMDEzMDEy NVowEwICEBwXDTE4MDIwMjEwNTIzOVowEwICEB8XDTE5MDEzMDEzMDEzM1owEwIC @@ -26,25 +26,26 @@ MDYwNVowEwICEP8XDTIwMDQyODA2MDYxM1owEwICEQAXDTIwMDQyODA2MDYyMlow EwICEQEXDTIwMDQyNzExMjI1NFowEwICEQIXDTIwMDQyODA2MDY0MFowEwICEQMX DTIwMDQyODA2MDY0N1owEwICEQQXDTIwMDQyODA2MDY1NFowEwICEQUXDTIwMDQy ODA2MDcwMVowEwICEQYXDTIwMDQyODA2MDcwNlowEwICEQcXDTIwMDQyODA2MDcx -M1owEwICESsXDTI0MDIwNTE0NTQ1OFowEwICES0XDTIxMDEwNjEyMjEzMVowEwIC -ES4XDTI0MDYyNjE2MTcxMlowEwICETwXDTIzMDcyODE2MjUxOVowEwICEUcXDTIx -MDIxNTE5MTQwMVowEwICEUgXDTIxMDIxNTE5MTQxM1owEwICEUkXDTIxMDIxNTE5 -MTUyNFowEwICEUoXDTIxMDIxNTE5MTQ0OFowEwICEUsXDTIxMDIxNTE5MTM0Nlow -EwICEUwXDTIxMDIxNTE5MTUwOVowEwICEU0XDTIxMDIxNTE5MTUxNlowEwICEVIX -DTIxMDIxNTE5MTQ1OFowEwICEVMXDTIxMDIxNTE5MTQzOFowEwICEVQXDTIxMDIx -NTE5MTQyMlowEwICEWQXDTIyMDQxMTE0Mjg0M1owEwICEXwXDTIzMDEwNDAyMjc0 -NlowEwICEZ4XDTIzMDQyNDIyMTkzM1owEwICEaIXDTIyMDIyNDA5NTk1NFowEwIC -EaMXDTIxMTAyNzIxNTExNFowEwICEacXDTIyMDMwMzEzMTMzNlowEwICEbQXDTIy -MDIyNDExNTc0NVowEwICEcIXDTIyMDMxODExMzcwMlowEwICEcwXDTIyMDUzMDEy -NTMxM1owEwICEgwXDTIzMDkyMTE0NTE0OFowEwICEhIXDTI0MDEwMzE3MjUzMFow -EwICEiEXDTIzMDcyODExNTc0OVowEwICEicXDTIzMDkwNDA3MzQ0NFowEwICEjoX -DTIzMTIxMTA4MzAxNVowEwICEksXDTI0MDEwMzE3NDkxMFowEwICElAXDTI0MDEy -MzA3NTQ0MVowEwICElgXDTI0MDIwOTA4MzI1OVowEwICEl4XDTI0MDMyNzE3MDU0 -OFqgMDAuMB8GA1UdIwQYMBaAFG3qbqqpNyw8iS0XPv1G7sOeeO10MAsGA1UdFAQE -AgIQPjANBgkqhkiG9w0BAQsFAAOCAQEAkQAruMpo+vYEnKCzIORcptym2IvazN8p -qxMAIMnV+/3Vc9HRXKgo+cw85BJyg/2fl/9yIegKQEYg5H8XQjKY0s90JYCCjhSr -qCd2VZe6yjNCDH0kSaxtDYmTcAar6YmJ1qu4A6SzA/3YSAc6b+kedL2b81CKkIhz -3KZMlL5dnPa6yYi9RkCk3VeVaxQ1DYojReUrCmMpEXACrMzVhyzFqiQHG+tKqwsS -L3lIS8gzHRXdG0wkJHdUzsZ6uSX/UHXbOyRUmbIkcESVPz8daonogYlLiAhf3+kb -fkDAL6x5obcpfvA6zqiIfTV/NxM4HdTKmUioE5mBpL6GNHes5d/oZw== +M1owEwICEQ8XDTI0MTAxNjE1MTcyN1owEwICESsXDTI0MDIwNTE0NTQ1OFowEwIC +ES0XDTIxMDEwNjEyMjEzMVowEwICES4XDTI0MDYyNjE2MTcxMlowEwICETwXDTIz +MDcyODE2MjUxOVowEwICEUcXDTIxMDIxNTE5MTQwMVowEwICEUgXDTIxMDIxNTE5 +MTQxM1owEwICEUkXDTIxMDIxNTE5MTUyNFowEwICEUoXDTIxMDIxNTE5MTQ0OFow +EwICEUsXDTIxMDIxNTE5MTM0NlowEwICEUwXDTIxMDIxNTE5MTUwOVowEwICEU0X +DTIxMDIxNTE5MTUxNlowEwICEVIXDTIxMDIxNTE5MTQ1OFowEwICEVMXDTIxMDIx +NTE5MTQzOFowEwICEVQXDTIxMDIxNTE5MTQyMlowEwICEWQXDTIyMDQxMTE0Mjg0 +M1owEwICEXwXDTIzMDEwNDAyMjc0NlowEwICEZ4XDTIzMDQyNDIyMTkzM1owEwIC +EaIXDTIyMDIyNDA5NTk1NFowEwICEaMXDTIxMTAyNzIxNTExNFowEwICEacXDTIy +MDMwMzEzMTMzNlowEwICEbQXDTIyMDIyNDExNTc0NVowEwICEcIXDTIyMDMxODEx +MzcwMlowEwICEcwXDTIyMDUzMDEyNTMxM1owEwICEgwXDTIzMDkyMTE0NTE0OFow +EwICEhIXDTI0MDEwMzE3MjUzMFowEwICEiEXDTIzMDcyODExNTc0OVowEwICEicX +DTIzMDkwNDA3MzQ0NFowEwICEjoXDTIzMTIxMTA4MzAxNVowEwICEksXDTI0MDEw +MzE3NDkxMFowEwICElAXDTI0MDEyMzA3NTQ0MVowEwICElgXDTI0MDIwOTA4MzI1 +OVowEwICEloXDTI0MDkxODEzMjI1NVowEwICEl4XDTI0MDMyNzE3MDU0OFqgMDAu +MB8GA1UdIwQYMBaAFG3qbqqpNyw8iS0XPv1G7sOeeO10MAsGA1UdFAQEAgIQRjAN +BgkqhkiG9w0BAQsFAAOCAQEAFwF625R9U1XRHg4mejZKhV+deHxNxT88HW0NlB4Y +uEJlL7psDwiOH6lbDxQRimHvyingIO8f0TLFwDBKK9Xl8sEG3BRrjKTOEnpOpi5f +VHoFx9/gCSb2S6cGD9XAhqBIRB0Z1P7ZIZkHWXqnIxHEEg0tkUOyTmfBWteuru3z +reK+IwIu+rkkylWEftIPQbE5oHIp2gsPQOeXavaxPY0S25zZ6J7OQNV+9/65XgLi +j/TOqhPzaX/NPmRfxErb9aAFHvfK+WXqBr15uPgHthExAym1K4MQ6IOU7ijltiJd +lCRSyauNA8yvKwAPUtUOJXZwhasvpQlqLLVjWjLpkUaPYw== -----END X509 CRL----- From ae1d924626999c3ef16ab4fc3b7e45307d691069 Mon Sep 17 00:00:00 2001 From: provokateurin Date: Mon, 21 Oct 2024 12:21:29 +0200 Subject: [PATCH 20/21] fix(dav): Cleanup view-only check Signed-off-by: provokateurin --- apps/dav/lib/DAV/ViewOnlyPlugin.php | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/dav/lib/DAV/ViewOnlyPlugin.php b/apps/dav/lib/DAV/ViewOnlyPlugin.php index 0ae472460beda..035266af5b1cc 100644 --- a/apps/dav/lib/DAV/ViewOnlyPlugin.php +++ b/apps/dav/lib/DAV/ViewOnlyPlugin.php @@ -58,6 +58,7 @@ public function initialize(Server $server): void { //Sabre\DAV\CorePlugin::httpGet $this->server->on('method:GET', [$this, 'checkViewOnly'], 90); $this->server->on('method:COPY', [$this, 'checkViewOnly'], 90); + $this->server->on('method:MOVE', [$this, 'checkViewOnly'], 90); } /** From 87786494f265e7c4ad75092677f90e130197975d Mon Sep 17 00:00:00 2001 From: provokateurin Date: Mon, 28 Oct 2024 09:14:22 +0100 Subject: [PATCH 21/21] ci(psalm): Add missing imagick extension Signed-off-by: provokateurin --- .github/workflows/static-code-analysis.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/static-code-analysis.yml b/.github/workflows/static-code-analysis.yml index 2c979333cd40e..cda55ab4c4c92 100644 --- a/.github/workflows/static-code-analysis.yml +++ b/.github/workflows/static-code-analysis.yml @@ -23,7 +23,7 @@ jobs: uses: shivammathur/setup-php@v2 with: php-version: '8.0' - extensions: apcu,ctype,curl,dom,fileinfo,ftp,gd,intl,json,ldap,mbstring,openssl,pdo_sqlite,posix,sqlite,xml,zip + extensions: apcu,ctype,curl,dom,fileinfo,ftp,gd,imagick,intl,json,ldap,mbstring,openssl,pdo_sqlite,posix,sqlite,xml,zip coverage: none env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} @@ -57,7 +57,7 @@ jobs: uses: shivammathur/setup-php@master with: php-version: '8.0' - extensions: ctype,curl,dom,fileinfo,gd,intl,json,mbstring,openssl,pdo_sqlite,posix,sqlite,xml,zip + extensions: ctype,curl,dom,fileinfo,gd,imagick,intl,json,mbstring,openssl,pdo_sqlite,posix,sqlite,xml,zip coverage: none - name: Composer install @@ -85,7 +85,7 @@ jobs: uses: shivammathur/setup-php@v2 with: php-version: '8.0' - extensions: ctype,curl,dom,fileinfo,gd,intl,json,mbstring,openssl,pdo_sqlite,posix,sqlite,xml,zip + extensions: ctype,curl,dom,fileinfo,gd,imagick,intl,json,mbstring,openssl,pdo_sqlite,posix,sqlite,xml,zip coverage: none env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}