From 33b64868d7b65e751bd8d729ce69d6f46e6c3d8d Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Mon, 10 Nov 2014 16:00:25 +0100 Subject: [PATCH] Add storage and cache wrappers to apply a permissions mask to a storage --- .../cache/wrapper/cachepermissionsmask.php | 32 ++++++ .../files/storage/wrapper/permissionsmask.php | 33 ++++-- .../cache/wrapper/cachepermissionsmask.php | 94 ++++++++++++++++ .../files/storage/wrapper/permissionsmask.php | 105 ++++++++++++++++++ 4 files changed, 252 insertions(+), 12 deletions(-) create mode 100644 lib/private/files/cache/wrapper/cachepermissionsmask.php create mode 100644 tests/lib/files/cache/wrapper/cachepermissionsmask.php create mode 100644 tests/lib/files/storage/wrapper/permissionsmask.php diff --git a/lib/private/files/cache/wrapper/cachepermissionsmask.php b/lib/private/files/cache/wrapper/cachepermissionsmask.php new file mode 100644 index 00000000000..6ce6a4ebc44 --- /dev/null +++ b/lib/private/files/cache/wrapper/cachepermissionsmask.php @@ -0,0 +1,32 @@ + + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +namespace OC\Files\Cache\Wrapper; + +class CachePermissionsMask extends CacheWrapper { + /** + * @var int + */ + protected $mask; + + /** + * @param \OC\Files\Cache\Cache $cache + * @param int $mask + */ + public function __construct($cache, $mask) { + parent::__construct($cache); + $this->mask = $mask; + } + + protected function formatCacheEntry($entry) { + if (isset($entry['permissions'])) { + $entry['permissions'] &= $this->mask; + } + return $entry; + } +} diff --git a/lib/private/files/storage/wrapper/permissionsmask.php b/lib/private/files/storage/wrapper/permissionsmask.php index be5cb6bbaa3..955cb54591b 100644 --- a/lib/private/files/storage/wrapper/permissionsmask.php +++ b/lib/private/files/storage/wrapper/permissionsmask.php @@ -9,18 +9,27 @@ namespace OC\Files\Storage\Wrapper; use OC\Files\Cache\Wrapper\CachePermissionsMask; +use OCP\Constants; /** * Mask the permissions of a storage * + * This can be used to restrict update, create, delete and/or share permissions of a storage + * * Note that the read permissions cant be masked */ class PermissionsMask extends Wrapper { /** - * @var int + * @var int the permissions bits we want to keep */ private $mask; + /** + * @param array $arguments ['storage' => $storage, 'mask' => $mask] + * + * $storage: The storage the permissions mask should be applied on + * $mask: The permission bits that should be kept, a combination of the \OCP\Constant::PERMISSION_ constants + */ public function __construct($arguments) { parent::__construct($arguments); $this->mask = $arguments['mask']; @@ -31,15 +40,15 @@ class PermissionsMask extends Wrapper { } public function isUpdatable($path) { - return $this->checkMask(\OCP\PERMISSION_UPDATE) and parent::isUpdatable($path); + return $this->checkMask(Constants::PERMISSION_UPDATE) and parent::isUpdatable($path); } public function isCreatable($path) { - return $this->checkMask(\OCP\PERMISSION_CREATE) and parent::isCreatable($path); + return $this->checkMask(Constants::PERMISSION_CREATE) and parent::isCreatable($path); } public function isDeletable($path) { - return $this->checkMask(\OCP\PERMISSION_DELETE) and parent::isDeletable($path); + return $this->checkMask(Constants::PERMISSION_DELETE) and parent::isDeletable($path); } public function getPermissions($path) { @@ -47,32 +56,32 @@ class PermissionsMask extends Wrapper { } public function rename($path1, $path2) { - return $this->checkMask(\OCP\PERMISSION_UPDATE) and parent::rename($path1, $path2); + return $this->checkMask(Constants::PERMISSION_UPDATE) and parent::rename($path1, $path2); } public function copy($path1, $path2) { - return $this->checkMask(\OCP\PERMISSION_CREATE) and parent::copy($path1, $path2); + return $this->checkMask(Constants::PERMISSION_CREATE) and parent::copy($path1, $path2); } public function touch($path, $mtime = null) { - $permissions = $this->file_exists($path) ? \OCP\PERMISSION_UPDATE : \OCP\PERMISSION_CREATE; + $permissions = $this->file_exists($path) ? Constants::PERMISSION_UPDATE : Constants::PERMISSION_CREATE; return $this->checkMask($permissions) and parent::touch($path, $mtime); } public function mkdir($path) { - return $this->checkMask(\OCP\PERMISSION_CREATE) and parent::mkdir($path); + return $this->checkMask(Constants::PERMISSION_CREATE) and parent::mkdir($path); } public function rmdir($path) { - return $this->checkMask(\OCP\PERMISSION_DELETE) and parent::rmdir($path); + return $this->checkMask(Constants::PERMISSION_DELETE) and parent::rmdir($path); } public function unlink($path) { - return $this->checkMask(\OCP\PERMISSION_DELETE) and parent::unlink($path); + return $this->checkMask(Constants::PERMISSION_DELETE) and parent::unlink($path); } public function file_put_contents($path, $data) { - $permissions = $this->file_exists($path) ? \OCP\PERMISSION_UPDATE : \OCP\PERMISSION_CREATE; + $permissions = $this->file_exists($path) ? Constants::PERMISSION_UPDATE : Constants::PERMISSION_CREATE; return $this->checkMask($permissions) and parent::file_put_contents($path, $data); } @@ -80,7 +89,7 @@ class PermissionsMask extends Wrapper { if ($mode === 'r' or $mode === 'rb') { return parent::fopen($path, $mode); } else { - $permissions = $this->file_exists($path) ? \OCP\PERMISSION_UPDATE : \OCP\PERMISSION_CREATE; + $permissions = $this->file_exists($path) ? Constants::PERMISSION_UPDATE : Constants::PERMISSION_CREATE; return $this->checkMask($permissions) ? parent::fopen($path, $mode) : false; } } diff --git a/tests/lib/files/cache/wrapper/cachepermissionsmask.php b/tests/lib/files/cache/wrapper/cachepermissionsmask.php new file mode 100644 index 00000000000..72fd22741d3 --- /dev/null +++ b/tests/lib/files/cache/wrapper/cachepermissionsmask.php @@ -0,0 +1,94 @@ + + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +namespace Test\Files\Cache\Wrapper; + +use OCP\Constants; +use Test\Files\Cache\Cache; + +class CachePermissionsMask extends Cache { + /** + * @var \OC\Files\Cache\Cache $sourceCache + */ + protected $sourceCache; + + public function setUp() { + parent::setUp(); + $this->storage->mkdir('foo'); + $this->sourceCache = $this->cache; + $this->cache = $this->getMaskedCached(Constants::PERMISSION_ALL); + } + + protected function getMaskedCached($mask) { + return new \OC\Files\Cache\Wrapper\CachePermissionsMask($this->sourceCache, $mask); + } + + public function maskProvider() { + return array( + array(Constants::PERMISSION_ALL), + array(Constants::PERMISSION_ALL - Constants::PERMISSION_SHARE), + array(Constants::PERMISSION_ALL - Constants::PERMISSION_UPDATE), + array(Constants::PERMISSION_READ) + ); + } + + /** + * @dataProvider maskProvider + * @param int $mask + */ + public function testGetMasked($mask) { + $cache = $this->getMaskedCached($mask); + $data = array('size' => 100, 'mtime' => 50, 'mimetype' => 'text/plain', 'permissions' => Constants::PERMISSION_ALL); + $this->sourceCache->put('foo', $data); + $result = $cache->get('foo'); + $this->assertEquals($mask, $result['permissions']); + + $data = array('size' => 100, 'mtime' => 50, 'mimetype' => 'text/plain', 'permissions' => Constants::PERMISSION_ALL - Constants::PERMISSION_DELETE); + $this->sourceCache->put('bar', $data); + $result = $cache->get('bar'); + $this->assertEquals($mask & ~Constants::PERMISSION_DELETE, $result['permissions']); + } + + /** + * @dataProvider maskProvider + * @param int $mask + */ + public function testGetFolderContentMasked($mask) { + $this->storage->mkdir('foo'); + $this->storage->file_put_contents('foo/bar', 'asd'); + $this->storage->file_put_contents('foo/asd', 'bar'); + $this->storage->getScanner()->scan(''); + + $cache = $this->getMaskedCached($mask); + $files = $cache->getFolderContents('foo'); + $this->assertCount(2, $files); + + foreach ($files as $file) { + $this->assertEquals($mask & ~Constants::PERMISSION_CREATE, $file['permissions']); + } + } + + /** + * @dataProvider maskProvider + * @param int $mask + */ + public function testSearchMasked($mask) { + $this->storage->mkdir('foo'); + $this->storage->file_put_contents('foo/bar', 'asd'); + $this->storage->file_put_contents('foo/foobar', 'bar'); + $this->storage->getScanner()->scan(''); + + $cache = $this->getMaskedCached($mask); + $files = $cache->search('%bar'); + $this->assertCount(2, $files); + + foreach ($files as $file) { + $this->assertEquals($mask & ~Constants::PERMISSION_CREATE, $file['permissions']); + } + } +} diff --git a/tests/lib/files/storage/wrapper/permissionsmask.php b/tests/lib/files/storage/wrapper/permissionsmask.php new file mode 100644 index 00000000000..7e8c387677c --- /dev/null +++ b/tests/lib/files/storage/wrapper/permissionsmask.php @@ -0,0 +1,105 @@ + + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +namespace Test\Files\Storage\Wrapper; + +use OCP\Constants; + +class PermissionsMask extends \Test\Files\Storage\Storage { + + /** + * @var \OC\Files\Storage\Temporary + */ + private $sourceStorage; + + public function setUp() { + parent::setUp(); + $this->sourceStorage = new \OC\Files\Storage\Temporary(array()); + $this->instance = $this->getMaskedStorage(Constants::PERMISSION_ALL); + } + + public function tearDown() { + $this->sourceStorage->cleanUp(); + parent::tearDown(); + } + + protected function getMaskedStorage($mask) { + return new \OC\Files\Storage\Wrapper\PermissionsMask(array( + 'storage' => $this->sourceStorage, + 'mask' => $mask + )); + } + + public function testMkdirNoCreate() { + $storage = $this->getMaskedStorage(Constants::PERMISSION_ALL - Constants::PERMISSION_CREATE); + $this->assertFalse($storage->mkdir('foo')); + $this->assertFalse($storage->file_exists('foo')); + } + + public function testRmdirNoDelete() { + $storage = $this->getMaskedStorage(Constants::PERMISSION_ALL - Constants::PERMISSION_DELETE); + $this->assertTrue($storage->mkdir('foo')); + $this->assertTrue($storage->file_exists('foo')); + $this->assertFalse($storage->rmdir('foo')); + $this->assertTrue($storage->file_exists('foo')); + } + + public function testTouchNewFileNoCreate() { + $storage = $this->getMaskedStorage(Constants::PERMISSION_ALL - Constants::PERMISSION_CREATE); + $this->assertFalse($storage->touch('foo')); + $this->assertFalse($storage->file_exists('foo')); + } + + public function testTouchNewFileNoUpdate() { + $storage = $this->getMaskedStorage(Constants::PERMISSION_ALL - Constants::PERMISSION_UPDATE); + $this->assertTrue($storage->touch('foo')); + $this->assertTrue($storage->file_exists('foo')); + } + + public function testTouchExistingFileNoUpdate() { + $this->sourceStorage->touch('foo'); + $storage = $this->getMaskedStorage(Constants::PERMISSION_ALL - Constants::PERMISSION_UPDATE); + $this->assertFalse($storage->touch('foo')); + } + + public function testUnlinkNoDelete() { + $storage = $this->getMaskedStorage(Constants::PERMISSION_ALL - Constants::PERMISSION_DELETE); + $this->assertTrue($storage->touch('foo')); + $this->assertTrue($storage->file_exists('foo')); + $this->assertFalse($storage->unlink('foo')); + $this->assertTrue($storage->file_exists('foo')); + } + + public function testPutContentsNewFileNoUpdate() { + $storage = $this->getMaskedStorage(Constants::PERMISSION_ALL - Constants::PERMISSION_UPDATE); + $this->assertTrue($storage->file_put_contents('foo', 'bar')); + $this->assertEquals('bar', $storage->file_get_contents('foo')); + } + + public function testPutContentsNewFileNoCreate() { + $storage = $this->getMaskedStorage(Constants::PERMISSION_ALL - Constants::PERMISSION_CREATE); + $this->assertFalse($storage->file_put_contents('foo', 'bar')); + } + + public function testPutContentsExistingFileNoUpdate() { + $this->sourceStorage->touch('foo'); + $storage = $this->getMaskedStorage(Constants::PERMISSION_ALL - Constants::PERMISSION_UPDATE); + $this->assertFalse($storage->file_put_contents('foo', 'bar')); + } + + public function testFopenExistingFileNoUpdate() { + $this->sourceStorage->touch('foo'); + $storage = $this->getMaskedStorage(Constants::PERMISSION_ALL - Constants::PERMISSION_UPDATE); + $this->assertFalse($storage->fopen('foo', 'w')); + } + + public function testFopenNewFileNoCreate() { + $storage = $this->getMaskedStorage(Constants::PERMISSION_ALL - Constants::PERMISSION_CREATE); + $this->assertFalse($storage->fopen('foo', 'w')); + } +}