diff --git a/apps/dav/lib/Upload/RootCollection.php b/apps/dav/lib/Upload/RootCollection.php index 9f722dc98a7e8..08c620e5ce222 100644 --- a/apps/dav/lib/Upload/RootCollection.php +++ b/apps/dav/lib/Upload/RootCollection.php @@ -12,6 +12,8 @@ use OCP\Files\IRootFolder; use OCP\IUserSession; use OCP\Share\IManager; +use Sabre\DAV\Exception\Forbidden; +use Sabre\DAV\INode; use Sabre\DAVACL\AbstractPrincipalCollection; use Sabre\DAVACL\PrincipalBackend; @@ -33,7 +35,14 @@ public function __construct( * @inheritdoc */ #[\Override] - public function getChildForPrincipal(array $principalInfo): UploadHome { + public function getChildForPrincipal(array $principalInfo): INode|UploadHome { + [$prefix, $name] = \Sabre\Uri\split($principalInfo['uri']); + $user = $this->userSession->getUser(); + if ($prefix !== 'principals/shares' && $user?->getUID() !== $name) { + // if the request is not using a share token and the URL does not match the user, error out + throw new Forbidden('Not allowed'); + } + return new UploadHome( $principalInfo, $this->cleanupService, diff --git a/apps/dav/tests/unit/Upload/RootCollectionTest.php b/apps/dav/tests/unit/Upload/RootCollectionTest.php new file mode 100644 index 0000000000000..28d8d315ef797 --- /dev/null +++ b/apps/dav/tests/unit/Upload/RootCollectionTest.php @@ -0,0 +1,95 @@ +principalBackend = $this->createMock(BackendInterface::class); + $this->cleanupService = $this->createMock(CleanupService::class); + $this->rootFolder = $this->createMock(IRootFolder::class); + $this->userSession = $this->createMock(IUserSession::class); + $this->shareManager = $this->createMock(IShareManager::class); + + $this->collection = new RootCollection( + $this->principalBackend, + 'principals/users', + $this->cleanupService, + $this->rootFolder, + $this->userSession, + $this->shareManager, + ); + } + + private function mockUser(string $uid): IUser&MockObject { + $user = $this->createMock(IUser::class); + $user->method('getUID')->willReturn($uid); + return $user; + } + + public function testGetChildForPrincipalReturnsUploadHomeForOwnPrincipal(): void { + $this->userSession->method('getUser')->willReturn($this->mockUser('alice')); + + $node = $this->collection->getChildForPrincipal(['uri' => 'principals/users/alice']); + + $this->assertInstanceOf(UploadHome::class, $node); + } + + public function testGetChildForPrincipalReturnsUploadHomeForShareToken(): void { + $this->userSession->method('getUser')->willReturn($this->mockUser('alice')); + + $share = $this->createMock(IShare::class); + $share->method('getShareOwner')->willReturn('bob'); + $this->shareManager->method('getShareByToken') + ->with('sometoken') + ->willReturn($share); + + $node = $this->collection->getChildForPrincipal(['uri' => 'principals/shares/sometoken']); + + $this->assertInstanceOf(UploadHome::class, $node); + } + + public function testGetChildForPrincipalThrowsWhenPrincipalDoesNotMatchUser(): void { + $this->userSession->method('getUser')->willReturn($this->mockUser('alice')); + + $this->expectException(Forbidden::class); + + $this->collection->getChildForPrincipal(['uri' => 'principals/users/bob']); + } + + public function testGetChildForPrincipalThrowsWhenNotLoggedIn(): void { + $this->userSession->method('getUser')->willReturn(null); + + $this->expectException(Forbidden::class); + + $this->collection->getChildForPrincipal(['uri' => 'principals/users/alice']); + } +} diff --git a/build/integration/dav_features/webdav-related.feature b/build/integration/dav_features/webdav-related.feature index ef5a4fa7d4717..3403caa0a8184 100644 --- a/build/integration/dav_features/webdav-related.feature +++ b/build/integration/dav_features/webdav-related.feature @@ -424,6 +424,14 @@ Feature: webdav-related And Downloading file "/myChunkedFile.txt" Then Downloaded content should be "AAAAABBBBBCCCCC" + Scenario: Cannot create a chunked upload in another user's uploads folder + Given using new dav path + And user "user0" exists + And user "user1" exists + And As an "user1" + When user "user1" creates a new chunking upload with id "chunking-42" in the uploads folder for "user0" + Then the HTTP status code should be "403" + Scenario: A disabled user cannot use webdav Given user "userToBeDisabled" exists And As an "admin" diff --git a/build/integration/features/bootstrap/WebDav.php b/build/integration/features/bootstrap/WebDav.php index 83f9778cee3d8..dc64680d90c04 100644 --- a/build/integration/features/bootstrap/WebDav.php +++ b/build/integration/features/bootstrap/WebDav.php @@ -879,6 +879,18 @@ public function userCreatesANewChunkingUploadWithId($user, $id) { $this->makeDavRequest($user, 'MKCOL', $destination, [], null, 'uploads'); } + /** + * @When user :user creates a new chunking upload with id :id in the uploads folder for :uidOrToken + */ + public function userCreatesANewChunkingUploadWithIdInFolderOf($user, $id, $uidOrToken): void { + $destination = '/uploads/' . $uidOrToken . '/' . $id; + try { + $this->response = $this->makeDavRequest($user, 'MKCOL', $destination, [], null, 'uploads'); + } catch (\GuzzleHttp\Exception\ClientException $e) { + $this->response = $e->getResponse(); + } + } + /** * @Given user :user uploads new chunk file :num with :data to id :id */