Commit 296d2dbf authored by Vincent Petry's avatar Vincent Petry Committed by GitHub

Merge pull request #27286 from owncloud/1_move_jailing_code_to_core_2

#1 move jailing code to core
parents ac842f66 ccd0c593
......@@ -7,7 +7,7 @@ OC.Share = _.extend(OC.Share || {}, {
SHARE_TYPE_USER:0,
SHARE_TYPE_GROUP:1,
SHARE_TYPE_LINK:3,
SHARE_TYPE_EMAIL:4,
SHARE_TYPE_GUEST:4,
SHARE_TYPE_REMOTE:6,
/**
......
......@@ -124,7 +124,7 @@
'onShareWithFieldChanged'
);
OC.Plugins.attach('OCA.Share.ShareDialogView', this);
OC.Plugins.attach('OC.Share.ShareDialogView', this);
},
_onClickTabHeader: function(ev) {
......
<?php
/**
* @author Jörn Friedrich Dreyer <jfd@butonic.de>
*
* @copyright Copyright (c) 2017, ownCloud GmbH
* @license AGPL-3.0
*
* This code is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* 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, version 3,
* along with this program. If not, see <http://www.gnu.org/licenses/>
*
*/
namespace OC\Files\Storage\Wrapper;
/**
* While PermissionMask can mask a whole storage this can
* mask a certain directory inside a storage
*
* @package OC\Files\Storage\Wrapper
*/
class DirMask extends PermissionsMask {
/**
* @var string the dir that should be masked
*/
private $path;
/**
* @var int remember length
*/
private $pathLength;
/**
* @param array $arguments ['storage' => $storage, 'mask' => $mask, 'path' => $path]
*
* $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
* $path: The path relative to the storage root that should be masked
*/
public function __construct($arguments) {
parent::__construct($arguments);
$this->path = $arguments['path'];
$this->pathLength = strlen($arguments['path']);
}
protected function checkPath($path) {
return substr($path, 0, $this->pathLength) === $this->path;
}
public function isUpdatable($path) {
if ($this->checkPath($path)) {
return parent::isUpdatable($path);
} else {
return $this->storage->isUpdatable($path);
}
}
public function isCreatable($path) {
if ($this->checkPath($path)) {
return parent::isCreatable($path);
} else {
return $this->storage->isCreatable($path);
}
}
public function isDeletable($path) {
if ($this->checkPath($path)) {
return parent::isDeletable($path);
} else {
return $this->storage->isDeletable($path);
}
}
public function isSharable($path) {
if ($this->checkPath($path)) {
return parent::isSharable($path);
} else {
return $this->storage->isSharable($path);
}
}
public function getPermissions($path) {
if ($this->checkPath($path)) {
return parent::getPermissions($path);
} else {
return $this->storage->getPermissions($path);
}
}
public function rename($path1, $path2) {
if (!$this->isReadable($path1)) {
return false;
}
if ($this->file_exists($path2)) {
if ($this->isUpdatable($path2)) {
return $this->storage->rename($path1, $path2);
}
} else {
if ($this->isCreatable($path2)) {
return $this->storage->rename($path1, $path2);
}
}
return false;
}
public function copy($path1, $path2) {
if (!$this->isReadable($path1)) {
return false;
}
if ($this->file_exists($path2)) {
if ($this->isUpdatable($path2)) {
return $this->storage->copy($path1, $path2);
}
} else {
if ($this->isCreatable($path2)) {
return $this->storage->copy($path1, $path2);
}
}
return false;
}
public function touch($path, $mtime = null) {
if ($this->checkPath($path)) {
return parent::touch($path);
} else {
return $this->storage->touch($path);
}
}
public function mkdir($path) {
if ($this->checkPath($path)) {
return parent::mkdir($path);
} else {
return $this->storage->mkdir($path);
}
}
public function rmdir($path) {
if ($this->checkPath($path)) {
return parent::rmdir($path);
} else {
return $this->storage->rmdir($path);
}
}
public function unlink($path) {
if ($this->checkPath($path)) {
return parent::unlink($path);
} else {
return $this->storage->unlink($path);
}
}
public function file_put_contents($path, $data) {
if ($this->checkPath($path)) {
return parent::file_put_contents($path, $data);
} else {
return $this->storage->file_put_contents($path, $data);
}
}
public function fopen($path, $mode) {
if ($this->checkPath($path)) {
return parent::fopen($path, $mode);
} else {
return $this->storage->fopen($path, $mode);
}
}
}
\ No newline at end of file
......@@ -40,7 +40,7 @@ class PermissionsMask extends Wrapper {
/**
* @var int the permissions bits we want to keep
*/
private $mask;
protected $mask;
/**
* @param array $arguments ['storage' => $storage, 'mask' => $mask]
......
<?php
/**
* @author Ilja Neumann <ineumann@owncloud.com>
*
* @copyright Copyright (c) 2017, ownCloud GmbH
* @license AGPL-3.0
*
* This code is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* 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, version 3,
* along with this program. If not, see <http://www.gnu.org/licenses/>
*
*/
namespace OC\Files\Storage\Wrapper;
use OCP\Constants;
class ReadOnlyJail extends DirMask {
/**
* @param $path
* @return bool
*/
protected function checkPath($path) {
if ($path === 'files') {
return true;
}
return parent::checkPath($path);
}
/**
* @param string $path
* @return bool
*/
public function isDeletable($path) {
if (pathinfo($path, PATHINFO_EXTENSION) === 'part') {
return true;
}
return $this->getWrapperStorage()->isDeletable($path);
}
/**
* @param string $path
* @return bool
*/
public function mkdir($path) {
// Lift restrictions if files dir is created (at first login)
if ($path === 'files') {
$this->mask = Constants::PERMISSION_CREATE;
};
$result = parent::mkdir($path);
$this->mask = Constants::PERMISSION_READ;
return $result;
}
}
\ No newline at end of file
......@@ -29,7 +29,7 @@ class Constants {
const SHARE_TYPE_USER = 0;
const SHARE_TYPE_GROUP = 1;
const SHARE_TYPE_LINK = 3;
const SHARE_TYPE_EMAIL = 4; // ToDo Check if it is still in use otherwise remove it
const SHARE_TYPE_GUEST = 4;
const SHARE_TYPE_CONTACT = 5; // ToDo Check if it is still in use otherwise remove it
const SHARE_TYPE_REMOTE = 6; // ToDo Check if it is still in use otherwise remove it
......
......@@ -47,6 +47,7 @@ use OCP\App\IAppManager;
use OCP\AppFramework\QueryException;
use OCP\AppFramework\Utility\ITimeFactory;
use OCP\Authentication\IAuthModule;
use OCP\Files\NotPermittedException;
use OCP\IConfig;
use OCP\IRequest;
use OCP\ISession;
......@@ -398,8 +399,15 @@ class Session implements IUserSession, Emitter {
//trigger creation of user home and /files folder
$userFolder = \OC::$server->getUserFolder($user);
// copy skeleton
\OC_Util::copySkeleton($user, $userFolder);
try {
// copy skeleton
\OC_Util::copySkeleton($user, $userFolder);
} catch (NotPermittedException $ex) {
// possible if files directory is in an readonly jail
\OC::$server->getLogger()->warning(
'Skeleton not created due to missing write permission'
);
}
// trigger any other initialization
\OC::$server->getEventDispatcher()->dispatch(IUser::class . '::firstLogin', new GenericEvent($this->getUser()));
......
......@@ -224,6 +224,57 @@ class OC_Util {
OC_Hook::emit('OC_Filesystem', 'preSetup', ['user' => $user]);
\OC\Files\Filesystem::logWarningWhenAddingStorageWrapper(true);
// Make users storage readonly if he is a guest or in a read_only group
$isGuest = \OC::$server->getConfig()->getUserValue(
$user,
'owncloud',
'isGuest',
false
);
if (!$isGuest) {
$readOnlyGroups = json_decode(\OC::$server->getConfig()->getAppValue(
'core',
'read_only_groups',
'[]'
), true);
if (!is_array($readOnlyGroups)) {
$readOnlyGroups = [];
}
$userGroups = array_keys(
\OC::$server->getGroupManager()->getUserIdGroups($user)
);
$readOnlyGroupMemberships = array_intersect(
$readOnlyGroups,
$userGroups
);
}
if ($isGuest === '1' || !empty($readOnlyGroupMemberships)) {
\OC\Files\Filesystem::addStorageWrapper(
'oc_readonly',
function ($mountPoint, $storage) use ($user) {
if ($mountPoint === '/' || $mountPoint === "/$user/") {
return new \OC\Files\Storage\Wrapper\ReadOnlyJail(
[
'storage' => $storage,
'mask' => \OCP\Constants::PERMISSION_READ,
'path' => 'files'
]
);
}
return $storage;
}
);
}
//check if we are using an object storage
$objectStore = \OC::$server->getSystemConfig()->getValue('objectstore', null);
if (isset($objectStore)) {
......
<?php
namespace Test\Files\Storage\Wrapper;
use OC\Files\Storage\Temporary;
use OC\Files\Storage\Wrapper\DirMask;
use OCP\Constants;
class DirMaskTest extends \PHPUnit_Framework_TestCase {
/** @var Temporary */
private $sourceStorage;
/**
* @param $mask
* @return DirMask
*/
private function getStorage($mask) {
$this->sourceStorage->mkdir('masked');
$this->sourceStorage->mkdir('masked/dir');
$this->sourceStorage->file_put_contents('masked/test.txt', 'something');
return new DirMask(
[
'storage' => $this->sourceStorage,
'path' => 'masked',
'mask' => $mask
]
);
}
public function setUp() {
parent::setUp();
$this->sourceStorage = new Temporary([]);
}
public function testIsUpdatable() {
$readOnlyStorage = $this->getStorage(Constants::PERMISSION_READ);
$this->assertFalse(
$readOnlyStorage->isUpdatable('masked/test.txt')
);
$writableStorage = $this->getStorage(Constants::PERMISSION_ALL);
$this->assertTrue(
$writableStorage->isUpdatable('masked/test.txt')
);
}
public function testIsDeletable() {
$readOnlyStorage = $this->getStorage(Constants::PERMISSION_READ);
$this->assertFalse(
$readOnlyStorage->isDeletable('masked/test.txt')
);
$writeOnlyStorage = $this->getStorage(Constants::PERMISSION_ALL);
$this->assertTrue(
$writeOnlyStorage->isDeletable('masked/test.txt')
);
}
public function testIsSharable() {
$readOnlyStorage = $this->getStorage(Constants::PERMISSION_READ);
$this->assertFalse(
$readOnlyStorage->isSharable('masked/test.txt')
);
$sharableStorage = $this->getStorage(Constants::PERMISSION_SHARE);
$this->assertTrue(
$sharableStorage->isSharable('masked/test.txt')
);
}
public function testMkdir() {
$storage = $this->getStorage(Constants::PERMISSION_CREATE);
$this->assertTrue($storage->mkdir('masked/dir2'));
$storage = $this->getStorage(Constants::PERMISSION_READ);
$this->assertFalse($storage->mkdir('masked/dir2'));
}
public function testRmdir() {
$storage = $this->getStorage(Constants::PERMISSION_ALL);
$this->assertTrue($storage->rmdir('masked/dir'));
$this->sourceStorage->mkdir('masked/dir2');
$storage = $this->getStorage(Constants::PERMISSION_READ);
$this->assertFalse($storage->rmdir('masked/dir2'));
}
public function testUnlink() {
$storage = $this->getStorage(Constants::PERMISSION_ALL);
$this->assertTrue($storage->unlink('masked/test.txt'));
$this->assertFalse($this->sourceStorage->file_exists('masked/test.txt'));
$storage = $this->getStorage(Constants::PERMISSION_READ);
$this->assertFalse($storage->unlink('masked/test.txt'));
$this->assertTrue($this->sourceStorage->file_exists('masked/test.txt'));
}
public function testFilePutContents() {
$storage = $this->getStorage(Constants::PERMISSION_ALL);
$this->assertTrue($storage->file_put_contents(
'masked/barbaz.txt', 'something'
));
$content = $this->sourceStorage->file_get_contents('masked/barbaz.txt');
$this->assertTrue($content == 'something');
$storage = $this->getStorage(Constants::PERMISSION_READ);
$this->assertFalse($storage->file_put_contents(
'masked/barbaz.txt', 'something'
));
}
public function testFopen() {
$storage = $this->getStorage(Constants::PERMISSION_ALL);
$this->assertTrue(
is_resource($storage->fopen('masked/test.txt', 'r+'))
);
$storage = $this->getStorage(Constants::PERMISSION_READ);
$this->assertTrue(
is_resource($storage->fopen('masked/test.txt', 'r'))
);
$storage = $this->getStorage(Constants::PERMISSION_READ);
$this->assertFalse(
$storage->fopen('masked/test.txt', 'r+')
);
}
}
<?php
namespace Test\Files\Storage\Wrapper;
use OC\Files\Storage\Temporary;
use OC\Files\Storage\Wrapper\ReadOnlyJail;
use OCP\Constants;
class ReadOnlyJailTest extends \PHPUnit_Framework_TestCase {
/** @var ReadOnlyJail */
private $sut;
/** @var Temporary */
private $sourceStorage;
public function setUp() {
parent::setUp();
$this->sourceStorage = new Temporary([]);
$this->sut = new ReadOnlyJail(
[
'storage' => $this->sourceStorage,
'path' => 'files',
'mask' => Constants::PERMISSION_READ
]
);
}
public function testMkdirCanOnlyCreateFilesDirectoryInsideRoot() {
$this->assertTrue($this->sut->mkdir('files'));
$this->assertFalse($this->sut->mkdir('files_other'));
}
public function testOnlyPartFilesAreDeletable() {
$this->assertTrue($this->sut->isDeletable('foo.part'));
$this->assertFalse($this->sut->isDeletable('cant_delete_me.txt'));
}
}
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment