diff options
author | Ferdinand Thiessen <opensource@fthiessen.de> | 2025-01-26 16:56:00 +0100 |
---|---|---|
committer | Ferdinand Thiessen <opensource@fthiessen.de> | 2025-01-26 16:56:31 +0100 |
commit | da32af01859db12d5e54727eae703ac819ea2d9b (patch) | |
tree | a5df7401c6535186a637732aba29a442b9c7f88e | |
parent | 823782b44dcdc99a8cf7d772d5bd32305cb64faf (diff) | |
download | nextcloud-server-backport/50430/stable30.tar.gz nextcloud-server-backport/50430/stable30.zip |
fix(files): Harden thumbnail endpointbackport/50430/stable30
- Catch all thrown exceptions and handle in such a way you do not get
information about forbidden files.
- Resepect download permissions of shares.
Signed-off-by: Ferdinand Thiessen <opensource@fthiessen.de>
-rw-r--r-- | apps/files/lib/Controller/ApiController.php | 23 | ||||
-rw-r--r-- | apps/files/tests/Controller/ApiControllerTest.php | 89 |
2 files changed, 104 insertions, 8 deletions
diff --git a/apps/files/lib/Controller/ApiController.php b/apps/files/lib/Controller/ApiController.php index 62ae4e6b0f0..eb30601a937 100644 --- a/apps/files/lib/Controller/ApiController.php +++ b/apps/files/lib/Controller/ApiController.php @@ -28,8 +28,11 @@ use OCP\AppFramework\Http\Response; use OCP\AppFramework\Http\StreamResponse; use OCP\Files\File; use OCP\Files\Folder; +use OCP\Files\InvalidPathException; use OCP\Files\IRootFolder; use OCP\Files\NotFoundException; +use OCP\Files\NotPermittedException; +use OCP\Files\Storage\ISharedStorage; use OCP\IConfig; use OCP\IL10N; use OCP\IPreview; @@ -87,20 +90,28 @@ class ApiController extends Controller { } try { - $file = $this->userFolder->get($file); - if ($file instanceof Folder) { + $file = $this->userFolder?->get($file); + if ($file === null + || !($file instanceof File) + || ($file->getId() <= 0) + ) { throw new NotFoundException(); } - if ($file->getId() <= 0) { - return new DataResponse(['message' => 'File not found.'], Http::STATUS_NOT_FOUND); + // Validate the user is allowed to download the file (preview is some kind of download) + $storage = $file->getStorage(); + if ($storage->instanceOfStorage(ISharedStorage::class)) { + /** @var ISharedStorage $storage */ + $attributes = $storage->getShare()->getAttributes(); + if ($attributes !== null && $attributes->getAttribute('permissions', 'download') === false) { + throw new NotFoundException(); + } } - /** @var File $file */ $preview = $this->previewManager->getPreview($file, $x, $y, true); return new FileDisplayResponse($preview, Http::STATUS_OK, ['Content-Type' => $preview->getMimeType()]); - } catch (NotFoundException $e) { + } catch (NotFoundException|NotPermittedException|InvalidPathException) { return new DataResponse(['message' => 'File not found.'], Http::STATUS_NOT_FOUND); } catch (\Exception $e) { return new DataResponse([], Http::STATUS_BAD_REQUEST); diff --git a/apps/files/tests/Controller/ApiControllerTest.php b/apps/files/tests/Controller/ApiControllerTest.php index f79a5c7bb64..e94124ad356 100644 --- a/apps/files/tests/Controller/ApiControllerTest.php +++ b/apps/files/tests/Controller/ApiControllerTest.php @@ -17,6 +17,8 @@ use OCP\Files\Folder; use OCP\Files\IRootFolder; use OCP\Files\NotFoundException; use OCP\Files\SimpleFS\ISimpleFile; +use OCP\Files\Storage\ISharedStorage; +use OCP\Files\Storage\IStorage; use OCP\Files\StorageNotAvailableException; use OCP\IConfig; use OCP\IL10N; @@ -24,7 +26,9 @@ use OCP\IPreview; use OCP\IRequest; use OCP\IUser; use OCP\IUserSession; +use OCP\Share\IAttributes; use OCP\Share\IManager; +use OCP\Share\IShare; use Psr\Log\LoggerInterface; use Test\TestCase; @@ -170,9 +174,13 @@ class ApiControllerTest extends TestCase { $this->assertEquals($expected, $this->apiController->getThumbnail(0, 0, '')); } - public function testGetThumbnailInvalidImage() { + public function testGetThumbnailInvalidImage(): void { + $storage = $this->createMock(IStorage::class); + $storage->method('instanceOfStorage')->with(ISharedStorage::class)->willReturn(false); + $file = $this->createMock(File::class); $file->method('getId')->willReturn(123); + $file->method('getStorage')->willReturn($storage); $this->userFolder->method('get') ->with($this->equalTo('unknown.jpg')) ->willReturn($file); @@ -194,9 +202,86 @@ class ApiControllerTest extends TestCase { $this->assertEquals($expected, $this->apiController->getThumbnail(10, 10, 'unknown.jpg')); } - public function testGetThumbnail() { + public function testGetThumbnailSharedNoDownload(): void { + $attributes = $this->createMock(IAttributes::class); + $attributes->expects(self::once()) + ->method('getAttribute') + ->with('permissions', 'download') + ->willReturn(false); + + $share = $this->createMock(IShare::class); + $share->expects(self::once()) + ->method('getAttributes') + ->willReturn($attributes); + + $storage = $this->createMock(ISharedStorage::class); + $storage->expects(self::once()) + ->method('instanceOfStorage') + ->with(ISharedStorage::class) + ->willReturn(true); + $storage->expects(self::once()) + ->method('getShare') + ->willReturn($share); + + $file = $this->createMock(File::class); + $file->method('getId')->willReturn(123); + $file->method('getStorage')->willReturn($storage); + + $this->userFolder->method('get') + ->with('unknown.jpg') + ->willReturn($file); + + $this->preview->expects($this->never()) + ->method('getPreview'); + + $expected = new DataResponse(['message' => 'File not found.'], Http::STATUS_NOT_FOUND); + $this->assertEquals($expected, $this->apiController->getThumbnail(10, 10, 'unknown.jpg')); + } + + public function testGetThumbnailShared(): void { + $share = $this->createMock(IShare::class); + $share->expects(self::once()) + ->method('getAttributes') + ->willReturn(null); + + $storage = $this->createMock(ISharedStorage::class); + $storage->expects(self::once()) + ->method('instanceOfStorage') + ->with(ISharedStorage::class) + ->willReturn(true); + $storage->expects(self::once()) + ->method('getShare') + ->willReturn($share); + $file = $this->createMock(File::class); $file->method('getId')->willReturn(123); + $file->method('getStorage')->willReturn($storage); + + $this->userFolder->method('get') + ->with($this->equalTo('known.jpg')) + ->willReturn($file); + $preview = $this->createMock(ISimpleFile::class); + $preview->method('getName')->willReturn('my name'); + $preview->method('getMTime')->willReturn(42); + $this->preview->expects($this->once()) + ->method('getPreview') + ->with($this->equalTo($file), 10, 10, true) + ->willReturn($preview); + + $ret = $this->apiController->getThumbnail(10, 10, 'known.jpg'); + + $this->assertEquals(Http::STATUS_OK, $ret->getStatus()); + $this->assertInstanceOf(Http\FileDisplayResponse::class, $ret); + } + + public function testGetThumbnail(): void { + $storage = $this->createMock(IStorage::class); + $storage->method('instanceOfStorage')->with(ISharedStorage::class)->willReturn(false); + + $file = $this->createMock(File::class); + $file->method('getId')->willReturn(123); + $file->method('getStorage')->willReturn($storage); + $this->userFolder->method('get') ->with($this->equalTo('known.jpg')) ->willReturn($file); |