@@ -36,7 +36,7 @@ class File implements \OCP\Encryption\IFile { | |||
* get list of users with access to the file | |||
* | |||
* @param string $path to the file | |||
* @return array | |||
* @return array ['users' => $uniqueUserIds, 'public' => $public] | |||
*/ | |||
public function getAccessList($path) { | |||
@@ -46,7 +46,7 @@ class File implements \OCP\Encryption\IFile { | |||
// always add owner to the list of users with access to the file | |||
$userIds = array($owner); | |||
if (!$this->util->isFile($ownerPath)) { | |||
if (!$this->util->isFile($owner . '/' . $ownerPath)) { | |||
return array('users' => $userIds, 'public' => false); | |||
} | |||
@@ -22,6 +22,7 @@ | |||
namespace OC\Encryption; | |||
use OC\Files\Filesystem; | |||
use OC\Files\Storage\Shared; | |||
use OC\Files\Storage\Wrapper\Encryption; | |||
use OC\Files\View; | |||
@@ -222,7 +223,24 @@ class Manager implements IManager { | |||
$uid = $user ? $user->getUID() : null; | |||
$fileHelper = \OC::$server->getEncryptionFilesHelper(); | |||
$keyStorage = \OC::$server->getEncryptionKeyStorage(); | |||
return new Encryption($parameters, $manager, $util, $logger, $fileHelper, $uid, $keyStorage); | |||
$update = new Update( | |||
new View(), | |||
$util, | |||
Filesystem::getMountManager(), | |||
$manager, | |||
$fileHelper, | |||
$uid | |||
); | |||
return new Encryption( | |||
$parameters, | |||
$manager, | |||
$util, | |||
$logger, | |||
$fileHelper, | |||
$uid, | |||
$keyStorage, | |||
$update | |||
); | |||
} else { | |||
return $storage; | |||
} |
@@ -22,6 +22,7 @@ | |||
namespace OC\Encryption; | |||
use OC\Files\Filesystem; | |||
use \OC\Files\Mount; | |||
use \OC\Files\View; | |||
@@ -74,46 +75,73 @@ class Update { | |||
$this->uid = $uid; | |||
} | |||
/** | |||
* hook after file was shared | |||
* | |||
* @param array $params | |||
*/ | |||
public function postShared($params) { | |||
if ($params['itemType'] === 'file' || $params['itemType'] === 'folder') { | |||
$this->update($params['fileSource']); | |||
$path = Filesystem::getPath($params['fileSource']); | |||
list($owner, $ownerPath) = $this->getOwnerPath($path); | |||
$absPath = '/' . $owner . '/files/' . $ownerPath; | |||
$this->update($absPath); | |||
} | |||
} | |||
/** | |||
* hook after file was unshared | |||
* | |||
* @param array $params | |||
*/ | |||
public function postUnshared($params) { | |||
if ($params['itemType'] === 'file' || $params['itemType'] === 'folder') { | |||
$this->update($params['fileSource']); | |||
$path = Filesystem::getPath($params['fileSource']); | |||
list($owner, $ownerPath) = $this->getOwnerPath($path); | |||
$absPath = '/' . $owner . '/files/' . $ownerPath; | |||
$this->update($absPath); | |||
} | |||
} | |||
/** | |||
* update keyfiles and share keys recursively | |||
* get owner and path relative to data/<owner>/files | |||
* | |||
* @param int $fileSource file source id | |||
* @param string $path path to file for current user | |||
* @return array ['owner' => $owner, 'path' => $path] | |||
* @throw \InvalidArgumentException | |||
*/ | |||
private function update($fileSource) { | |||
$path = \OC\Files\Filesystem::getPath($fileSource); | |||
$info = \OC\Files\Filesystem::getFileInfo($path); | |||
$owner = \OC\Files\Filesystem::getOwner($path); | |||
$view = new \OC\Files\View('/' . $owner . '/files'); | |||
$ownerPath = $view->getPath($info->getId()); | |||
$absPath = '/' . $owner . '/files' . $ownerPath; | |||
private function getOwnerPath($path) { | |||
$info = Filesystem::getFileInfo($path); | |||
$owner = Filesystem::getOwner($path); | |||
$view = new View('/' . $owner . '/files'); | |||
$path = $view->getPath($info->getId()); | |||
if ($path === null) { | |||
throw new \InvalidArgumentException('No file found for ' . $info->getId()); | |||
} | |||
return array($owner, $path); | |||
} | |||
$mount = $this->mountManager->find($path); | |||
$mountPoint = $mount->getMountPoint(); | |||
/** | |||
* notify encryption module about added/removed users from a file/folder | |||
* | |||
* @param string $path relative to data/ | |||
* @throws Exceptions\ModuleDoesNotExistsException | |||
*/ | |||
public function update($path) { | |||
// if a folder was shared, get a list of all (sub-)folders | |||
if ($this->view->is_dir($absPath)) { | |||
$allFiles = $this->util->getAllFiles($absPath, $mountPoint); | |||
if ($this->view->is_dir($path)) { | |||
$allFiles = $this->util->getAllFiles($path); | |||
} else { | |||
$allFiles = array($absPath); | |||
$allFiles = array($path); | |||
} | |||
$encryptionModule = $this->encryptionManager->getDefaultEncryptionModule(); | |||
foreach ($allFiles as $path) { | |||
$usersSharing = $this->file->getAccessList($path); | |||
$encryptionModule->update($path, $this->uid, $usersSharing); | |||
foreach ($allFiles as $file) { | |||
$usersSharing = $this->file->getAccessList($file); | |||
$encryptionModule->update($file, $this->uid, $usersSharing); | |||
} | |||
} | |||
@@ -25,6 +25,7 @@ namespace OC\Encryption; | |||
use OC\Encryption\Exceptions\EncryptionHeaderKeyExistsException; | |||
use OC\Encryption\Exceptions\EncryptionHeaderToLargeException; | |||
use OC\Encryption\Exceptions\ModuleDoesNotExistsException; | |||
use OC\Files\Filesystem; | |||
use OC\Files\View; | |||
use OCP\Encryption\IEncryptionModule; | |||
use OCP\IConfig; | |||
@@ -181,10 +182,9 @@ class Util { | |||
* go recursively through a dir and collect all files and sub files. | |||
* | |||
* @param string $dir relative to the users files folder | |||
* @param string $mountPoint | |||
* @return array with list of files relative to the users files folder | |||
*/ | |||
public function getAllFiles($dir, $mountPoint = '') { | |||
public function getAllFiles($dir) { | |||
$result = array(); | |||
$dirList = array($dir); | |||
@@ -193,13 +193,10 @@ class Util { | |||
$content = $this->view->getDirectoryContent($dir); | |||
foreach ($content as $c) { | |||
// getDirectoryContent() returns the paths relative to the mount points, so we need | |||
// to re-construct the complete path | |||
$path = ($mountPoint !== '') ? $mountPoint . '/' . $c->getPath() : $c->getPath(); | |||
if ($c['type'] === 'dir') { | |||
$dirList[] = \OC\Files\Filesystem::normalizePath($path); | |||
if ($c->getType() === 'dir') { | |||
$dirList[] = $c->getPath(); | |||
} else { | |||
$result[] = \OC\Files\Filesystem::normalizePath($path); | |||
$result[] = $c->getPath(); | |||
} | |||
} | |||
@@ -212,11 +209,12 @@ class Util { | |||
* check if it is a file uploaded by the user stored in data/user/files | |||
* or a metadata file | |||
* | |||
* @param string $path | |||
* @param string $path relative to the data/ folder | |||
* @return boolean | |||
*/ | |||
public function isFile($path) { | |||
if (substr($path, 0, strlen('/files/')) === '/files/') { | |||
$parts = explode('/', Filesystem::normalizePath($path), 4); | |||
if (isset($parts[2]) && $parts[2] === 'files') { | |||
return true; | |||
} | |||
return false; | |||
@@ -262,7 +260,7 @@ class Util { | |||
$ownerPath = implode('/', array_slice($parts, 2)); | |||
return array($uid, \OC\Files\Filesystem::normalizePath($ownerPath)); | |||
return array($uid, Filesystem::normalizePath($ownerPath)); | |||
} | |||
@@ -24,8 +24,13 @@ namespace OC\Files\Storage\Wrapper; | |||
use OC\Encryption\Exceptions\ModuleDoesNotExistsException; | |||
use OC\Encryption\File; | |||
use OC\Encryption\Manager; | |||
use OC\Encryption\Update; | |||
use OC\Encryption\Util; | |||
use OC\Files\Filesystem; | |||
use OC\Files\Storage\LocalTempFileTrait; | |||
use OC\Log; | |||
use OCP\Encryption\Keys\IStorage; | |||
use OCP\Files\Mount\IMountPoint; | |||
class Encryption extends Wrapper { | |||
@@ -59,6 +64,9 @@ class Encryption extends Wrapper { | |||
/** @var \OCP\Encryption\Keys\IStorage */ | |||
private $keyStorage; | |||
/** @var \OC\Encryption\Update */ | |||
private $update; | |||
/** | |||
* @param array $parameters | |||
* @param \OC\Encryption\Manager $encryptionManager | |||
@@ -66,15 +74,18 @@ class Encryption extends Wrapper { | |||
* @param \OC\Log $logger | |||
* @param File $fileHelper | |||
* @param string $uid user who perform the read/write operation (null for public access) | |||
* @param IStorage $keyStorage | |||
* @param Update $update | |||
*/ | |||
public function __construct( | |||
$parameters, | |||
\OC\Encryption\Manager $encryptionManager = null, | |||
\OC\Encryption\Util $util = null, | |||
\OC\Log $logger = null, | |||
Manager $encryptionManager = null, | |||
Util $util = null, | |||
Log $logger = null, | |||
File $fileHelper = null, | |||
$uid = null, | |||
$keyStorage = null | |||
IStorage $keyStorage = null, | |||
Update $update = null | |||
) { | |||
$this->mountPoint = $parameters['mountPoint']; | |||
@@ -86,6 +97,7 @@ class Encryption extends Wrapper { | |||
$this->fileHelper = $fileHelper; | |||
$this->keyStorage = $keyStorage; | |||
$this->unencryptedSize = array(); | |||
$this->update = $update; | |||
parent::__construct($parameters); | |||
} | |||
@@ -207,12 +219,11 @@ class Encryption extends Wrapper { | |||
* @return bool | |||
*/ | |||
public function rename($path1, $path2) { | |||
$fullPath1 = $this->getFullPath($path1); | |||
if ($this->util->isExcluded($fullPath1)) { | |||
$source = $this->getFullPath($path1); | |||
if ($this->util->isExcluded($source)) { | |||
return $this->storage->rename($path1, $path2); | |||
} | |||
$source = $this->getFullPath($path1); | |||
$result = $this->storage->rename($path1, $path2); | |||
if ($result) { | |||
$target = $this->getFullPath($path2); | |||
@@ -220,6 +231,9 @@ class Encryption extends Wrapper { | |||
$this->unencryptedSize[$target] = $this->unencryptedSize[$source]; | |||
} | |||
$this->keyStorage->renameKeys($source, $target); | |||
if (dirname($source) !== dirname($target) && $this->util->isFile($target)) { | |||
$this->update->update($target); | |||
} | |||
} | |||
return $result; |
@@ -0,0 +1,129 @@ | |||
<?php | |||
/** | |||
* @author Björn Schießle <schiessle@owncloud.com> | |||
* | |||
* @copyright Copyright (c) 2015, ownCloud, Inc. | |||
* @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 Test\Encryption; | |||
use OC\Encryption\Update; | |||
use Test\TestCase; | |||
class UpdateTest extends TestCase { | |||
/** @var \OC\Encryption\Update */ | |||
private $update; | |||
/** @var string */ | |||
private $uid; | |||
/** @var \OC\Files\View | \PHPUnit_Framework_MockObject_MockObject */ | |||
private $view; | |||
/** @var \OC\Encryption\Util | \PHPUnit_Framework_MockObject_MockObject */ | |||
private $util; | |||
/** @var \OC\Files\Mount\Manager | \PHPUnit_Framework_MockObject_MockObject */ | |||
private $mountManager; | |||
/** @var \OC\Encryption\Manager | \PHPUnit_Framework_MockObject_MockObject */ | |||
private $encryptionManager; | |||
/** @var \OCP\Encryption\IEncryptionModule | \PHPUnit_Framework_MockObject_MockObject */ | |||
private $encryptionModule; | |||
/** @var \OC\Encryption\File | \PHPUnit_Framework_MockObject_MockObject */ | |||
private $fileHelper; | |||
public function setUp() { | |||
parent::setUp(); | |||
$this->view = $this->getMockBuilder('\OC\Files\View') | |||
->disableOriginalConstructor()->getMock(); | |||
$this->util = $this->getMockBuilder('\OC\Encryption\Util') | |||
->disableOriginalConstructor()->getMock(); | |||
$this->mountManager = $this->getMockBuilder('\OC\Files\Mount\Manager') | |||
->disableOriginalConstructor()->getMock(); | |||
$this->encryptionManager = $this->getMockBuilder('\OC\Encryption\Manager') | |||
->disableOriginalConstructor()->getMock(); | |||
$this->fileHelper = $this->getMockBuilder('\OC\Encryption\File') | |||
->disableOriginalConstructor()->getMock(); | |||
$this->encryptionModule = $this->getMockBuilder('\OCP\Encryption\IEncryptionModule') | |||
->disableOriginalConstructor()->getMock(); | |||
$this->encryptionManager->expects($this->once()) | |||
->method('getDefaultEncryptionModule') | |||
->willReturn($this->encryptionModule); | |||
$this->uid = 'testUser1'; | |||
$this->update = new Update( | |||
$this->view, | |||
$this->util, | |||
$this->mountManager, | |||
$this->encryptionManager, | |||
$this->fileHelper, | |||
$this->uid); | |||
} | |||
/** | |||
* @dataProvider dataTestUpdate | |||
* | |||
* @param string $path | |||
* @param boolean $isDir | |||
* @param array $allFiles | |||
* @param integer $numberOfFiles | |||
*/ | |||
public function testUpdate($path, $isDir, $allFiles, $numberOfFiles) { | |||
$this->view->expects($this->once()) | |||
->method('is_dir') | |||
->willReturn($isDir); | |||
if($isDir) { | |||
$this->util->expects($this->once()) | |||
->method('getAllFiles') | |||
->willReturn($allFiles); | |||
} | |||
$this->fileHelper->expects($this->exactly($numberOfFiles)) | |||
->method('getAccessList') | |||
->willReturn(['users' => [], 'public' => false]); | |||
$this->encryptionModule->expects($this->exactly($numberOfFiles)) | |||
->method('update') | |||
->willReturn(true); | |||
$this->update->update($path); | |||
} | |||
/** | |||
* data provider for testUpdate() | |||
* | |||
* @return array | |||
*/ | |||
public function dataTestUpdate() { | |||
return array( | |||
array('/user/files/foo', true, ['/user/files/foo/file1.txt', '/user/files/foo/file1.txt'], 2), | |||
array('/user/files/test.txt', false, [], 1), | |||
); | |||
} | |||
} |
@@ -152,4 +152,25 @@ class UtilTest extends TestCase { | |||
return false; | |||
} | |||
/** | |||
* @dataProvider dataTestIsFile | |||
*/ | |||
public function testIsFile($path, $expected) { | |||
$this->assertSame($expected, | |||
$this->util->isFile($path) | |||
); | |||
} | |||
public function dataTestIsFile() { | |||
return array( | |||
array('/user/files/test.txt', true), | |||
array('/user/files', true), | |||
array('/user/files_versions/test.txt', false), | |||
array('/user/foo/files/test.txt', false), | |||
array('/files/foo/files/test.txt', false), | |||
array('/user', false), | |||
array('/user/test.txt', false), | |||
); | |||
} | |||
} |
@@ -12,11 +12,26 @@ class Encryption extends \Test\Files\Storage\Storage { | |||
*/ | |||
private $sourceStorage; | |||
/** | |||
* @var \OC\Files\Storage\Wrapper\Encryption | |||
*/ | |||
protected $instance; | |||
/** | |||
* @var \OC\Encryption\Keys\Storage | \PHPUnit_Framework_MockObject_MockObject | |||
*/ | |||
private $keyStore; | |||
/** | |||
* @var \OC\Encryption\Util | \PHPUnit_Framework_MockObject_MockObject | |||
*/ | |||
private $util; | |||
/** | |||
* @var \OC\Encryption\Update | \PHPUnit_Framework_MockObject_MockObject | |||
*/ | |||
private $update; | |||
public function setUp() { | |||
parent::setUp(); | |||
@@ -43,8 +58,8 @@ class Encryption extends \Test\Files\Storage\Storage { | |||
->disableOriginalConstructor() | |||
->getMock(); | |||
$util = $this->getMock('\OC\Encryption\Util', ['getUidAndFilename'], [new View(), new \OC\User\Manager(), $groupManager, $config]); | |||
$util->expects($this->any()) | |||
$this->util = $this->getMock('\OC\Encryption\Util', ['getUidAndFilename', 'isFile'], [new View(), new \OC\User\Manager(), $groupManager, $config]); | |||
$this->util->expects($this->any()) | |||
->method('getUidAndFilename') | |||
->willReturnCallback(function ($path) { | |||
return ['user1', $path]; | |||
@@ -61,6 +76,8 @@ class Encryption extends \Test\Files\Storage\Storage { | |||
$this->sourceStorage = new Temporary(array()); | |||
$this->keyStore = $this->getMockBuilder('\OC\Encryption\Keys\Storage') | |||
->disableOriginalConstructor()->getMock(); | |||
$this->update = $this->getMockBuilder('\OC\Encryption\Update') | |||
->disableOriginalConstructor()->getMock(); | |||
$mount = $this->getMockBuilder('\OC\Files\Mount\MountPoint') | |||
->disableOriginalConstructor() | |||
->setMethods(['getOption']) | |||
@@ -72,7 +89,7 @@ class Encryption extends \Test\Files\Storage\Storage { | |||
'mountPoint' => '/', | |||
'mount' => $mount | |||
], | |||
$encryptionManager, $util, $logger, $file, null, $this->keyStore | |||
$encryptionManager, $this->util, $logger, $file, null, $this->keyStore, $this->update | |||
); | |||
} | |||
@@ -97,11 +114,41 @@ class Encryption extends \Test\Files\Storage\Storage { | |||
return $encryptionModule; | |||
} | |||
public function testRename() { | |||
/** | |||
* @dataProvider dataTestRename | |||
* | |||
* @param string $source | |||
* @param string $target | |||
* @param boolean $shouldUpdate | |||
*/ | |||
public function testRename($source, $target, $shouldUpdate) { | |||
$this->keyStore | |||
->expects($this->once()) | |||
->method('renameKeys'); | |||
$this->instance->mkdir('folder'); | |||
$this->instance->rename('folder', 'flodder'); | |||
$this->util->expects($this->any()) | |||
->method('isFile')->willReturn(true); | |||
if ($shouldUpdate) { | |||
$this->update->expects($this->once()) | |||
->method('update'); | |||
} else { | |||
$this->update->expects($this->never()) | |||
->method('update'); | |||
} | |||
$this->instance->mkdir($source); | |||
$this->instance->mkdir(dirname($target)); | |||
$this->instance->rename($source, $target); | |||
} | |||
/** | |||
* data provider for testRename() | |||
* | |||
* @return array | |||
*/ | |||
public function dataTestRename() { | |||
return array( | |||
array('source', 'target', false), | |||
array('source', '/subFolder/target', true), | |||
); | |||
} | |||
} |