summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMorris Jobke <hey@morrisjobke.de>2015-03-27 09:59:12 +0100
committerMorris Jobke <hey@morrisjobke.de>2015-03-27 09:59:12 +0100
commita538f14b4a8af3067c02c0fdcb3fcecac8c91f0e (patch)
tree9862506f3141e6dbe40c9fffb439e6c5267a55c6
parentbaa2e4f99558a49c0237a50cc548ae5b8a787ba4 (diff)
parent48ceaa9502a211b80881fe0fae431af12fcbc7a3 (diff)
downloadnextcloud-server-a538f14b4a8af3067c02c0fdcb3fcecac8c91f0e.tar.gz
nextcloud-server-a538f14b4a8af3067c02c0fdcb3fcecac8c91f0e.zip
Merge pull request #15246 from owncloud/stable8-share-partfilepermissions
[stable8] Fix share permission checks
-rw-r--r--apps/files_sharing/lib/sharedstorage.php60
-rw-r--r--apps/files_sharing/tests/sharedstorage.php152
-rw-r--r--apps/files_trashbin/lib/storage.php16
-rw-r--r--lib/private/connector/sabre/objecttree.php21
-rw-r--r--tests/lib/connector/sabre/objecttree.php4
5 files changed, 225 insertions, 28 deletions
diff --git a/apps/files_sharing/lib/sharedstorage.php b/apps/files_sharing/lib/sharedstorage.php
index d992f8f70b4..dc8b3a2b72c 100644
--- a/apps/files_sharing/lib/sharedstorage.php
+++ b/apps/files_sharing/lib/sharedstorage.php
@@ -293,26 +293,34 @@ class Shared extends \OC\Files\Storage\Common implements ISharedStorage {
// we need the paths relative to data/user/files
$relPath1 = $this->getMountPoint() . '/' . $path1;
$relPath2 = $this->getMountPoint() . '/' . $path2;
-
- // check for update permissions on the share
- if ($this->isUpdatable('')) {
-
- $pathinfo = pathinfo($relPath1);
- // for part files we need to ask for the owner and path from the parent directory because
- // the file cache doesn't return any results for part files
- if (isset($pathinfo['extension']) && $pathinfo['extension'] === 'part') {
- list($user1, $path1) = \OCA\Files_Sharing\Helper::getUidAndFilename($pathinfo['dirname']);
- $path1 = $path1 . '/' . $pathinfo['basename'];
- } else {
- list($user1, $path1) = \OCA\Files_Sharing\Helper::getUidAndFilename($relPath1);
+ $pathinfo = pathinfo($relPath1);
+
+ $isPartFile = (isset($pathinfo['extension']) && $pathinfo['extension'] === 'part');
+ $targetExists = $this->file_exists($path2);
+ $sameFolder = (dirname($relPath1) === dirname($relPath2));
+ if ($targetExists || ($sameFolder && !$isPartFile)) {
+ // note that renaming a share mount point is always allowed
+ if (!$this->isUpdatable('')) {
+ return false;
+ }
+ } else {
+ if (!$this->isCreatable('')) {
+ return false;
}
- $targetFilename = basename($relPath2);
- list($user2, $path2) = \OCA\Files_Sharing\Helper::getUidAndFilename(dirname($relPath2));
- $rootView = new \OC\Files\View('');
- return $rootView->rename('/' . $user1 . '/files/' . $path1, '/' . $user2 . '/files/' . $path2 . '/' . $targetFilename);
}
- return false;
+ // for part files we need to ask for the owner and path from the parent directory because
+ // the file cache doesn't return any results for part files
+ if ($isPartFile) {
+ list($user1, $path1) = \OCA\Files_Sharing\Helper::getUidAndFilename($pathinfo['dirname']);
+ $path1 = $path1 . '/' . $pathinfo['basename'];
+ } else {
+ list($user1, $path1) = \OCA\Files_Sharing\Helper::getUidAndFilename($relPath1);
+ }
+ $targetFilename = basename($relPath2);
+ list($user2, $path2) = \OCA\Files_Sharing\Helper::getUidAndFilename(dirname($relPath2));
+ $rootView = new \OC\Files\View('');
+ return $rootView->rename('/' . $user1 . '/files/' . $path1, '/' . $user2 . '/files/' . $path2 . '/' . $targetFilename);
}
public function copy($path1, $path2) {
@@ -343,13 +351,25 @@ class Shared extends \OC\Files\Storage\Common implements ISharedStorage {
case 'xb':
case 'a':
case 'ab':
- $exists = $this->file_exists($path);
- if ($exists && !$this->isUpdatable($path)) {
+ $creatable = $this->isCreatable($path);
+ $updatable = $this->isUpdatable($path);
+ // if neither permissions given, no need to continue
+ if (!$creatable && !$updatable) {
return false;
}
- if (!$exists && !$this->isCreatable(dirname($path))) {
+
+ $exists = $this->file_exists($path);
+ // if a file exists, updatable permissions are required
+ if ($exists && !$updatable) {
return false;
}
+
+ // part file is allowed if !$creatable but the final file is $updatable
+ if (pathinfo($path, PATHINFO_EXTENSION) !== 'part') {
+ if (!$exists && !$creatable) {
+ return false;
+ }
+ }
}
$info = array(
'target' => $this->getMountPoint() . $path,
diff --git a/apps/files_sharing/tests/sharedstorage.php b/apps/files_sharing/tests/sharedstorage.php
index 2959b9aacfb..f840b9b964f 100644
--- a/apps/files_sharing/tests/sharedstorage.php
+++ b/apps/files_sharing/tests/sharedstorage.php
@@ -199,6 +199,158 @@ class Test_Files_Sharing_Storage extends OCA\Files_sharing\Tests\TestCase {
$this->assertTrue($result);
}
+ public function testFopenWithReadOnlyPermission() {
+ $this->view->file_put_contents($this->folder . '/existing.txt', 'foo');
+ $fileinfoFolder = $this->view->getFileInfo($this->folder);
+ $result = \OCP\Share::shareItem('folder', $fileinfoFolder['fileid'], \OCP\Share::SHARE_TYPE_USER,
+ self::TEST_FILES_SHARING_API_USER2, \OCP\Constants::PERMISSION_READ);
+ $this->assertTrue($result);
+
+ self::loginHelper(self::TEST_FILES_SHARING_API_USER2);
+ $user2View = new \OC\Files\View('/' . self::TEST_FILES_SHARING_API_USER2 . '/files');
+
+ // part file should be forbidden
+ $handle = $user2View->fopen($this->folder . '/test.txt.part', 'w');
+ $this->assertFalse($handle);
+
+ // regular file forbidden
+ $handle = $user2View->fopen($this->folder . '/test.txt', 'w');
+ $this->assertFalse($handle);
+
+ // rename forbidden
+ $this->assertFalse($user2View->rename($this->folder . '/existing.txt', $this->folder . '/existing2.txt'));
+
+ // delete forbidden
+ $this->assertFalse($user2View->unlink($this->folder . '/existing.txt'));
+
+ //cleanup
+ self::loginHelper(self::TEST_FILES_SHARING_API_USER1);
+ $result = \OCP\Share::unshare('folder', $fileinfoFolder['fileid'], \OCP\Share::SHARE_TYPE_USER,
+ self::TEST_FILES_SHARING_API_USER2);
+ $this->assertTrue($result);
+ }
+
+ public function testFopenWithCreateOnlyPermission() {
+ $this->view->file_put_contents($this->folder . '/existing.txt', 'foo');
+ $fileinfoFolder = $this->view->getFileInfo($this->folder);
+ $result = \OCP\Share::shareItem('folder', $fileinfoFolder['fileid'], \OCP\Share::SHARE_TYPE_USER,
+ self::TEST_FILES_SHARING_API_USER2, \OCP\Constants::PERMISSION_READ | \OCP\Constants::PERMISSION_CREATE);
+ $this->assertTrue($result);
+
+ self::loginHelper(self::TEST_FILES_SHARING_API_USER2);
+ $user2View = new \OC\Files\View('/' . self::TEST_FILES_SHARING_API_USER2 . '/files');
+
+ // create part file allowed
+ $handle = $user2View->fopen($this->folder . '/test.txt.part', 'w');
+ $this->assertNotFalse($handle);
+ fclose($handle);
+
+ // create regular file allowed
+ $handle = $user2View->fopen($this->folder . '/test-create.txt', 'w');
+ $this->assertNotFalse($handle);
+ fclose($handle);
+
+ // rename file never allowed
+ $this->assertFalse($user2View->rename($this->folder . '/test-create.txt', $this->folder . '/newtarget.txt'));
+ $this->assertFalse($user2View->file_exists($this->folder . '/newtarget.txt'));
+
+ // rename file not allowed if target exists
+ $this->assertFalse($user2View->rename($this->folder . '/newtarget.txt', $this->folder . '/existing.txt'));
+
+ // overwriting file not allowed
+ $handle = $user2View->fopen($this->folder . '/existing.txt', 'w');
+ $this->assertFalse($handle);
+
+ // overwrite forbidden (no update permission)
+ $this->assertFalse($user2View->rename($this->folder . '/test.txt.part', $this->folder . '/existing.txt'));
+
+ // delete forbidden
+ $this->assertFalse($user2View->unlink($this->folder . '/existing.txt'));
+
+ //cleanup
+ self::loginHelper(self::TEST_FILES_SHARING_API_USER1);
+ $result = \OCP\Share::unshare('folder', $fileinfoFolder['fileid'], \OCP\Share::SHARE_TYPE_USER,
+ self::TEST_FILES_SHARING_API_USER2);
+ $this->assertTrue($result);
+ }
+
+ public function testFopenWithUpdateOnlyPermission() {
+ $this->view->file_put_contents($this->folder . '/existing.txt', 'foo');
+ $fileinfoFolder = $this->view->getFileInfo($this->folder);
+
+ $result = \OCP\Share::shareItem('folder', $fileinfoFolder['fileid'], \OCP\Share::SHARE_TYPE_USER,
+ self::TEST_FILES_SHARING_API_USER2, \OCP\Constants::PERMISSION_READ | \OCP\Constants::PERMISSION_UPDATE);
+ $this->assertTrue($result);
+
+ self::loginHelper(self::TEST_FILES_SHARING_API_USER2);
+ $user2View = new \OC\Files\View('/' . self::TEST_FILES_SHARING_API_USER2 . '/files');
+
+ // create part file allowed
+ $handle = $user2View->fopen($this->folder . '/test.txt.part', 'w');
+ $this->assertNotFalse($handle);
+ fclose($handle);
+
+ // create regular file not allowed
+ $handle = $user2View->fopen($this->folder . '/test-create.txt', 'w');
+ $this->assertFalse($handle);
+
+ // rename part file not allowed to non-existing file
+ $this->assertFalse($user2View->rename($this->folder . '/test.txt.part', $this->folder . '/nonexist.txt'));
+
+ // rename part file allowed to target existing file
+ $this->assertTrue($user2View->rename($this->folder . '/test.txt.part', $this->folder . '/existing.txt'));
+ $this->assertTrue($user2View->file_exists($this->folder . '/existing.txt'));
+
+ // rename regular file allowed
+ $this->assertTrue($user2View->rename($this->folder . '/existing.txt', $this->folder . '/existing-renamed.txt'));
+ $this->assertTrue($user2View->file_exists($this->folder . '/existing-renamed.txt'));
+
+ // overwriting file directly is allowed
+ $handle = $user2View->fopen($this->folder . '/existing-renamed.txt', 'w');
+ $this->assertNotFalse($handle);
+ fclose($handle);
+
+ // delete forbidden
+ $this->assertFalse($user2View->unlink($this->folder . '/existing-renamed.txt'));
+
+ //cleanup
+ self::loginHelper(self::TEST_FILES_SHARING_API_USER1);
+ $result = \OCP\Share::unshare('folder', $fileinfoFolder['fileid'], \OCP\Share::SHARE_TYPE_USER,
+ self::TEST_FILES_SHARING_API_USER2);
+ $this->assertTrue($result);
+ }
+
+ public function testFopenWithDeleteOnlyPermission() {
+ $this->view->file_put_contents($this->folder . '/existing.txt', 'foo');
+ $fileinfoFolder = $this->view->getFileInfo($this->folder);
+ $result = \OCP\Share::shareItem('folder', $fileinfoFolder['fileid'], \OCP\Share::SHARE_TYPE_USER,
+ self::TEST_FILES_SHARING_API_USER2, \OCP\Constants::PERMISSION_READ | \OCP\Constants::PERMISSION_DELETE);
+ $this->assertTrue($result);
+
+ self::loginHelper(self::TEST_FILES_SHARING_API_USER2);
+ $user2View = new \OC\Files\View('/' . self::TEST_FILES_SHARING_API_USER2 . '/files');
+
+ // part file should be forbidden
+ $handle = $user2View->fopen($this->folder . '/test.txt.part', 'w');
+ $this->assertFalse($handle);
+
+ // regular file forbidden
+ $handle = $user2View->fopen($this->folder . '/test.txt', 'w');
+ $this->assertFalse($handle);
+
+ // rename forbidden
+ $this->assertFalse($user2View->rename($this->folder . '/existing.txt', $this->folder . '/existing2.txt'));
+
+ // delete allowed
+ $this->assertTrue($user2View->unlink($this->folder . '/existing.txt'));
+
+ //cleanup
+ self::loginHelper(self::TEST_FILES_SHARING_API_USER1);
+ $result = \OCP\Share::unshare('folder', $fileinfoFolder['fileid'], \OCP\Share::SHARE_TYPE_USER,
+ self::TEST_FILES_SHARING_API_USER2);
+ $this->assertTrue($result);
+ }
+
function testMountSharesOtherUser() {
$folderInfo = $this->view->getFileInfo($this->folder);
$fileInfo = $this->view->getFileInfo($this->filename);
diff --git a/apps/files_trashbin/lib/storage.php b/apps/files_trashbin/lib/storage.php
index 175889ef95d..d15b136924c 100644
--- a/apps/files_trashbin/lib/storage.php
+++ b/apps/files_trashbin/lib/storage.php
@@ -62,6 +62,22 @@ class Storage extends Wrapper {
}
/**
+ * Rename path1 to path2 by calling the wrapped storage.
+ *
+ * @param string $path1 first path
+ * @param string $path2 second path
+ */
+ public function rename($path1, $path2) {
+ $result = $this->storage->rename($path1, $path2);
+ if ($result === false) {
+ // when rename failed, the post_rename hook isn't triggered,
+ // but we still want to reenable the trash logic
+ self::$disableTrash = false;
+ }
+ return $result;
+ }
+
+ /**
* Deletes the given file by moving it into the trashbin.
*
* @param string $path
diff --git a/lib/private/connector/sabre/objecttree.php b/lib/private/connector/sabre/objecttree.php
index 3422ed2d575..11b0abf3bfc 100644
--- a/lib/private/connector/sabre/objecttree.php
+++ b/lib/private/connector/sabre/objecttree.php
@@ -126,8 +126,9 @@ class ObjectTree extends \Sabre\DAV\ObjectTree {
throw new \Sabre\DAV\Exception\ServiceUnavailable('filesystem not setup');
}
+ $targetNodeExists = $this->nodeExists($destinationPath);
$sourceNode = $this->getNodeForPath($sourcePath);
- if ($sourceNode instanceof \Sabre\DAV\ICollection and $this->nodeExists($destinationPath)) {
+ if ($sourceNode instanceof \Sabre\DAV\ICollection && $targetNodeExists) {
throw new \Sabre\DAV\Exception\Forbidden('Could not copy directory ' . $sourceNode . ', target exists');
}
list($sourceDir,) = \Sabre\DAV\URLUtil::splitPath($sourcePath);
@@ -141,14 +142,22 @@ class ObjectTree extends \Sabre\DAV\ObjectTree {
}
try {
- // check update privileges
- if (!$this->fileView->isUpdatable($sourcePath) && !$isMovableMount) {
- throw new \Sabre\DAV\Exception\Forbidden();
- }
- if ($sourceDir !== $destinationDir) {
+ $sameFolder = ($sourceDir === $destinationDir);
+ // if we're overwriting or same folder
+ if ($targetNodeExists || $sameFolder) {
+ // note that renaming a share mount point is always allowed
+ if (!$this->fileView->isUpdatable($destinationDir) && !$isMovableMount) {
+ throw new \Sabre\DAV\Exception\Forbidden();
+ }
+ } else {
if (!$this->fileView->isCreatable($destinationDir)) {
throw new \Sabre\DAV\Exception\Forbidden();
}
+ }
+
+ if (!$sameFolder) {
+ // moving to a different folder, source will be gone, like a deletion
+ // note that moving a share mount point is always allowed
if (!$this->fileView->isDeletable($sourcePath) && !$isMovableMount) {
throw new \Sabre\DAV\Exception\Forbidden();
}
diff --git a/tests/lib/connector/sabre/objecttree.php b/tests/lib/connector/sabre/objecttree.php
index 2548066214b..8c824d1bbc8 100644
--- a/tests/lib/connector/sabre/objecttree.php
+++ b/tests/lib/connector/sabre/objecttree.php
@@ -70,7 +70,7 @@ class ObjectTree extends \Test\TestCase {
function moveFailedInvalidCharsProvider() {
return array(
- array('a/b', 'a/c*', array('a' => false, 'a/b' => true, 'a/c*' => false), array()),
+ array('a/b', 'a/*', array('a' => true, 'a/b' => true, 'a/c*' => false), array()),
);
}
@@ -81,12 +81,12 @@ class ObjectTree extends \Test\TestCase {
array('a/b', 'b/b', array('a' => false, 'a/b' => true, 'b' => false, 'b/b' => false), array()),
array('a/b', 'b/b', array('a' => true, 'a/b' => true, 'b' => false, 'b/b' => false), array()),
array('a/b', 'b/b', array('a' => true, 'a/b' => true, 'b' => true, 'b/b' => false), array('a/b' => false)),
+ array('a/b', 'a/c', array('a' => false, 'a/b' => true, 'a/c' => false), array()),
);
}
function moveSuccessProvider() {
return array(
- array('a/b', 'a/c', array('a' => false, 'a/b' => true, 'a/c' => false), array()),
array('a/b', 'b/b', array('a' => true, 'a/b' => true, 'b' => true, 'b/b' => false), array('a/b' => true)),
// older files with special chars can still be renamed to valid names
array('a/b*', 'b/b', array('a' => true, 'a/b*' => true, 'b' => true, 'b/b' => false), array('a/b*' => true)),