summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--apps/files_encryption/hooks/hooks.php63
-rwxr-xr-xapps/files_encryption/lib/helper.php25
-rw-r--r--apps/files_encryption/lib/stream.php38
-rw-r--r--apps/files_encryption/lib/util.php79
-rw-r--r--apps/files_encryption/tests/stream.php39
-rwxr-xr-xapps/files_encryption/tests/util.php35
-rw-r--r--apps/files_sharing/tests/base.php4
-rw-r--r--lib/private/router.php6
8 files changed, 227 insertions, 62 deletions
diff --git a/apps/files_encryption/hooks/hooks.php b/apps/files_encryption/hooks/hooks.php
index 7b13ae2a1d0..09d5687e226 100644
--- a/apps/files_encryption/hooks/hooks.php
+++ b/apps/files_encryption/hooks/hooks.php
@@ -30,6 +30,9 @@ use OC\Files\Filesystem;
*/
class Hooks {
+ // file for which we want to rename the keys after the rename operation was successful
+ private static $renamedFiles = array();
+
/**
* @brief Startup encryption backend upon user login
* @note This method should never be called for users using client side encryption
@@ -480,6 +483,18 @@ class Hooks {
}
/**
+ * @brief mark file as renamed so that we know the original source after the file was renamed
+ * @param array $params with the old path and the new path
+ */
+ public static function preRename($params) {
+ $util = new Util(new \OC_FilesystemView('/'), \OCP\User::getUser());
+ list($ownerOld, $pathOld) = $util->getUidAndFilename($params['oldpath']);
+ self::$renamedFiles[$params['oldpath']] = array(
+ 'uid' => $ownerOld,
+ 'path' => $pathOld);
+ }
+
+ /**
* @brief after a file is renamed, rename its keyfile and share-keys also fix the file size and fix also the sharing
* @param array with oldpath and newpath
*
@@ -501,19 +516,32 @@ class Hooks {
$userId = \OCP\User::getUser();
$util = new Util($view, $userId);
+ if (isset(self::$renamedFiles[$params['oldpath']]['uid']) &&
+ isset(self::$renamedFiles[$params['oldpath']]['path'])) {
+ $ownerOld = self::$renamedFiles[$params['oldpath']]['uid'];
+ $pathOld = self::$renamedFiles[$params['oldpath']]['path'];
+ } else {
+ \OCP\Util::writeLog('Encryption library', "can't get path and owner from the file before it was renamed", \OCP\Util::ERROR);
+ return false;
+ }
+
+ list($ownerNew, $pathNew) = $util->getUidAndFilename($params['newpath']);
+
// Format paths to be relative to user files dir
- if ($util->isSystemWideMountPoint($params['oldpath'])) {
- $baseDir = 'files_encryption/';
- $oldKeyfilePath = $baseDir . 'keyfiles/' . $params['oldpath'];
+ if ($util->isSystemWideMountPoint($pathOld)) {
+ $oldKeyfilePath = 'files_encryption/keyfiles/' . $pathOld;
+ $oldShareKeyPath = 'files_encryption/share-keys/' . $pathOld;
} else {
- $baseDir = $userId . '/' . 'files_encryption/';
- $oldKeyfilePath = $baseDir . 'keyfiles/' . $params['oldpath'];
+ $oldKeyfilePath = $ownerOld . '/' . 'files_encryption/keyfiles/' . $pathOld;
+ $oldShareKeyPath = $ownerOld . '/' . 'files_encryption/share-keys/' . $pathOld;
}
- if ($util->isSystemWideMountPoint($params['newpath'])) {
- $newKeyfilePath = $baseDir . 'keyfiles/' . $params['newpath'];
+ if ($util->isSystemWideMountPoint($pathNew)) {
+ $newKeyfilePath = 'files_encryption/keyfiles/' . $pathNew;
+ $newShareKeyPath = 'files_encryption/share-keys/' . $pathNew;
} else {
- $newKeyfilePath = $baseDir . 'keyfiles/' . $params['newpath'];
+ $newKeyfilePath = $ownerNew . '/files_encryption/keyfiles/' . $pathNew;
+ $newShareKeyPath = $ownerNew . '/files_encryption/share-keys/' . $pathNew;
}
// add key ext if this is not an folder
@@ -522,11 +550,11 @@ class Hooks {
$newKeyfilePath .= '.key';
// handle share-keys
- $localKeyPath = $view->getLocalFile($baseDir . 'share-keys/' . $params['oldpath']);
+ $localKeyPath = $view->getLocalFile($oldShareKeyPath);
$escapedPath = Helper::escapeGlobPattern($localKeyPath);
$matches = glob($escapedPath . '*.shareKey');
foreach ($matches as $src) {
- $dst = \OC\Files\Filesystem::normalizePath(str_replace($params['oldpath'], $params['newpath'], $src));
+ $dst = \OC\Files\Filesystem::normalizePath(str_replace($pathOld, $pathNew, $src));
// create destination folder if not exists
if (!file_exists(dirname($dst))) {
@@ -538,15 +566,13 @@ class Hooks {
} else {
// handle share-keys folders
- $oldShareKeyfilePath = $baseDir . 'share-keys/' . $params['oldpath'];
- $newShareKeyfilePath = $baseDir . 'share-keys/' . $params['newpath'];
// create destination folder if not exists
- if (!$view->file_exists(dirname($newShareKeyfilePath))) {
- $view->mkdir(dirname($newShareKeyfilePath), 0750, true);
+ if (!$view->file_exists(dirname($newShareKeyPath))) {
+ $view->mkdir(dirname($newShareKeyPath), 0750, true);
}
- $view->rename($oldShareKeyfilePath, $newShareKeyfilePath);
+ $view->rename($oldShareKeyPath, $newShareKeyPath);
}
// Rename keyfile so it isn't orphaned
@@ -561,18 +587,17 @@ class Hooks {
}
// build the path to the file
- $newPath = '/' . $userId . '/files' . $params['newpath'];
- $newPathRelative = $params['newpath'];
+ $newPath = '/' . $ownerNew . '/files' . $pathNew;
if ($util->fixFileSize($newPath)) {
// get sharing app state
$sharingEnabled = \OCP\Share::isEnabled();
// get users
- $usersSharing = $util->getSharingUsersArray($sharingEnabled, $newPathRelative);
+ $usersSharing = $util->getSharingUsersArray($sharingEnabled, $pathNew);
// update sharing-keys
- $util->setSharedFileKeyfiles($session, $usersSharing, $newPathRelative);
+ $util->setSharedFileKeyfiles($session, $usersSharing, $pathNew);
}
\OC_FileProxy::$enabled = $proxyStatus;
diff --git a/apps/files_encryption/lib/helper.php b/apps/files_encryption/lib/helper.php
index 08941077258..5dcb05fa196 100755
--- a/apps/files_encryption/lib/helper.php
+++ b/apps/files_encryption/lib/helper.php
@@ -29,6 +29,8 @@ namespace OCA\Encryption;
*/
class Helper {
+ private static $tmpFileMapping; // Map tmp files to files in data/user/files
+
/**
* @brief register share related hooks
*
@@ -59,6 +61,7 @@ class Helper {
*/
public static function registerFilesystemHooks() {
+ \OCP\Util::connectHook('OC_Filesystem', 'rename', 'OCA\Encryption\Hooks', 'preRename');
\OCP\Util::connectHook('OC_Filesystem', 'post_rename', 'OCA\Encryption\Hooks', 'postRename');
}
@@ -423,5 +426,27 @@ class Helper {
public static function escapeGlobPattern($path) {
return preg_replace('/(\*|\?|\[)/', '[$1]', $path);
}
+
+ /**
+ * @brief remember from which file the tmp file (getLocalFile() call) was created
+ * @param string $tmpFile path of tmp file
+ * @param string $originalFile path of the original file relative to data/
+ */
+ public static function addTmpFileToMapper($tmpFile, $originalFile) {
+ self::$tmpFileMapping[$tmpFile] = $originalFile;
+ }
+
+ /**
+ * @brief get the path of the original file
+ * @param string $tmpFile path of the tmp file
+ * @return mixed path of the original file or false
+ */
+ public static function getPathFromTmpFile($tmpFile) {
+ if (isset(self::$tmpFileMapping[$tmpFile])) {
+ return self::$tmpFileMapping[$tmpFile];
+ }
+
+ return false;
+ }
}
diff --git a/apps/files_encryption/lib/stream.php b/apps/files_encryption/lib/stream.php
index 7a37d2200a4..b3bf34ddb82 100644
--- a/apps/files_encryption/lib/stream.php
+++ b/apps/files_encryption/lib/stream.php
@@ -64,6 +64,9 @@ class Stream {
private $publicKey;
private $encKeyfile;
private $newFile; // helper var, we only need to write the keyfile for new files
+ private $isLocalTmpFile = false; // do we operate on a local tmp file
+ private $localTmpFile; // path of local tmp file
+
/**
* @var \OC\Files\View
*/
@@ -91,13 +94,18 @@ class Stream {
$this->rootView = new \OC_FilesystemView('/');
}
-
$this->session = new \OCA\Encryption\Session($this->rootView);
$this->privateKey = $this->session->getPrivateKey();
- // rawPath is relative to the data directory
- $this->rawPath = \OC\Files\Filesystem::normalizePath(str_replace('crypt://', '', $path));
+ $normalizedPath = \OC\Files\Filesystem::normalizePath(str_replace('crypt://', '', $path));
+ if ($originalFile = Helper::getPathFromTmpFile($normalizedPath)) {
+ $this->rawPath = $originalFile;
+ $this->isLocalTmpFile = true;
+ $this->localTmpFile = $normalizedPath;
+ } else {
+ $this->rawPath = $normalizedPath;
+ }
$this->userId = Helper::getUser($this->rawPath);
@@ -141,10 +149,14 @@ class Stream {
\OCA\Encryption\Helper::redirectToErrorPage($this->session);
}
- $this->size = $this->rootView->filesize($this->rawPath, $mode);
+ $this->size = $this->rootView->filesize($this->rawPath);
}
- $this->handle = $this->rootView->fopen($this->rawPath, $mode);
+ if ($this->isLocalTmpFile) {
+ $this->handle = fopen($this->localTmpFile, $mode);
+ } else {
+ $this->handle = $this->rootView->fopen($this->rawPath, $mode);
+ }
\OC_FileProxy::$enabled = $proxyStatus;
@@ -164,14 +176,25 @@ class Stream {
}
/**
+ * @brief Returns the current position of the file pointer
+ * @return int position of the file pointer
+ */
+ public function stream_tell() {
+ return ftell($this->handle);
+ }
+
+ /**
* @param $offset
* @param int $whence
+ * @return bool true if fseek was successful, otherwise false
*/
public function stream_seek($offset, $whence = SEEK_SET) {
$this->flush();
- fseek($this->handle, $offset, $whence);
+ // this wrapper needs to return "true" for success.
+ // the fseek call itself returns 0 on succeess
+ return !fseek($this->handle, $offset, $whence);
}
@@ -477,7 +500,7 @@ class Stream {
if ($this->privateKey === false) {
// cleanup
- if ($this->meta['mode'] !== 'r' && $this->meta['mode'] !== 'rb') {
+ if ($this->meta['mode'] !== 'r' && $this->meta['mode'] !== 'rb' && !$this->isLocalTmpFile) {
// Disable encryption proxy to prevent recursive calls
$proxyStatus = \OC_FileProxy::$enabled;
@@ -498,6 +521,7 @@ class Stream {
if (
$this->meta['mode'] !== 'r' &&
$this->meta['mode'] !== 'rb' &&
+ $this->isLocalTmpFile === false &&
$this->size > 0 &&
$this->unencryptedSize > 0
) {
diff --git a/apps/files_encryption/lib/util.php b/apps/files_encryption/lib/util.php
index a3852312200..8a5dfabeec1 100644
--- a/apps/files_encryption/lib/util.php
+++ b/apps/files_encryption/lib/util.php
@@ -241,11 +241,9 @@ class Util {
if (\OCP\DB::isError($result)) {
\OCP\Util::writeLog('Encryption library', \OC_DB::getErrorMessage($result), \OCP\Util::ERROR);
} else {
- if ($result->numRows() > 0) {
- $row = $result->fetchRow();
- if (isset($row['recovery_enabled'])) {
- $recoveryEnabled[] = $row['recovery_enabled'];
- }
+ $row = $result->fetchRow();
+ if ($row && isset($row['recovery_enabled'])) {
+ $recoveryEnabled[] = $row['recovery_enabled'];
}
}
@@ -289,7 +287,7 @@ class Util {
$sql = 'UPDATE `*PREFIX*encryption` SET `recovery_enabled` = ? WHERE `uid` = ?';
$args = array(
- $enabled,
+ $enabled ? '1' : '0',
$this->userId
);
@@ -429,8 +427,22 @@ class Util {
// we only need 24 byte from the last chunk
$data = '';
$handle = $this->view->fopen($path, 'r');
- if (is_resource($handle) && !fseek($handle, -24, SEEK_END)) {
- $data = fgets($handle);
+ if (is_resource($handle)) {
+ // suppress fseek warining, we handle the case that fseek doesn't
+ // work in the else branch
+ if (@fseek($handle, -24, SEEK_END) === 0) {
+ $data = fgets($handle);
+ } else {
+ // if fseek failed on the storage we create a local copy from the file
+ // and read this one
+ fclose($handle);
+ $localFile = $this->view->getLocalFile($path);
+ $handle = fopen($localFile, 'r');
+ if (is_resource($handle) && fseek($handle, -24, SEEK_END) === 0) {
+ $data = fgets($handle);
+ }
+ }
+ fclose($handle);
}
// re-enable proxy
@@ -482,7 +494,20 @@ class Util {
$lastChunckPos = ($lastChunkNr * 8192);
// seek to end
- fseek($stream, $lastChunckPos);
+ if (@fseek($stream, $lastChunckPos) === -1) {
+ // storage doesn't support fseek, we need a local copy
+ fclose($stream);
+ $localFile = $this->view->getLocalFile($path);
+ Helper::addTmpFileToMapper($localFile, $path);
+ $stream = fopen('crypt://' . $localFile, "r");
+ if (fseek($stream, $lastChunckPos) === -1) {
+ // if fseek also fails on the local storage, than
+ // there is nothing we can do
+ fclose($stream);
+ \OCP\Util::writeLog('Encryption library', 'couldn\'t determine size of "' . $path, \OCP\Util::ERROR);
+ return $result;
+ }
+ }
// get the content of the last chunk
$lastChunkContent = fread($stream, $lastChunkSize);
@@ -944,8 +969,8 @@ class Util {
if (\OCP\DB::isError($result)) {
\OCP\Util::writeLog('Encryption library', \OC_DB::getErrorMessage($result), \OCP\Util::ERROR);
} else {
- if ($result->numRows() > 0) {
- $row = $result->fetchRow();
+ $row = $result->fetchRow();
+ if ($row) {
$path = substr($row['path'], strlen('files'));
}
}
@@ -1225,11 +1250,9 @@ class Util {
if (\OCP\DB::isError($result)) {
\OCP\Util::writeLog('Encryption library', \OC_DB::getErrorMessage($result), \OCP\Util::ERROR);
} else {
- if ($result->numRows() > 0) {
- $row = $result->fetchRow();
- if (isset($row['migration_status'])) {
- $migrationStatus[] = $row['migration_status'];
- }
+ $row = $result->fetchRow();
+ if ($row && isset($row['migration_status'])) {
+ $migrationStatus[] = $row['migration_status'];
}
}
@@ -1409,9 +1432,7 @@ class Util {
if (\OCP\DB::isError($result)) {
\OCP\Util::writeLog('Encryption library', \OC_DB::getErrorMessage($result), \OCP\Util::ERROR);
} else {
- if ($result->numRows() > 0) {
- $row = $result->fetchRow();
- }
+ $row = $result->fetchRow();
}
return $row;
@@ -1435,9 +1456,7 @@ class Util {
if (\OCP\DB::isError($result)) {
\OCP\Util::writeLog('Encryption library', \OC_DB::getErrorMessage($result), \OCP\Util::ERROR);
} else {
- if ($result->numRows() > 0) {
- $row = $result->fetchRow();
- }
+ $row = $result->fetchRow();
}
return $row;
@@ -1456,18 +1475,16 @@ class Util {
$result = $query->execute(array($id));
- $source = array();
+ $source = null;
if (\OCP\DB::isError($result)) {
\OCP\Util::writeLog('Encryption library', \OC_DB::getErrorMessage($result), \OCP\Util::ERROR);
} else {
- if ($result->numRows() > 0) {
- $source = $result->fetchRow();
- }
+ $source = $result->fetchRow();
}
$fileOwner = false;
- if (isset($source['parent'])) {
+ if ($source && isset($source['parent'])) {
$parent = $source['parent'];
@@ -1477,16 +1494,14 @@ class Util {
$result = $query->execute(array($parent));
- $item = array();
+ $item = null;
if (\OCP\DB::isError($result)) {
\OCP\Util::writeLog('Encryption library', \OC_DB::getErrorMessage($result), \OCP\Util::ERROR);
} else {
- if ($result->numRows() > 0) {
- $item = $result->fetchRow();
- }
+ $item = $result->fetchRow();
}
- if (isset($item['parent'])) {
+ if ($item && isset($item['parent'])) {
$parent = $item['parent'];
diff --git a/apps/files_encryption/tests/stream.php b/apps/files_encryption/tests/stream.php
index 530ee3a7b2d..2767bbe512b 100644
--- a/apps/files_encryption/tests/stream.php
+++ b/apps/files_encryption/tests/stream.php
@@ -180,4 +180,43 @@ class Test_Encryption_Stream extends \PHPUnit_Framework_TestCase {
// tear down
$view->unlink($filename);
}
+
+ /**
+ * @medium
+ * @brief test if stream wrapper can read files outside from the data folder
+ */
+ function testStreamFromLocalFile() {
+
+ $filename = '/' . $this->userId . '/files/' . 'tmp-' . time().'.txt';
+
+ $tmpFilename = "/tmp/" . time() . ".txt";
+
+ // write an encrypted file
+ $cryptedFile = $this->view->file_put_contents($filename, $this->dataShort);
+
+ // Test that data was successfully written
+ $this->assertTrue(is_int($cryptedFile));
+
+ // create a copy outside of the data folder in /tmp
+ $proxyStatus = \OC_FileProxy::$enabled;
+ \OC_FileProxy::$enabled = false;
+ $encryptedContent = $this->view->file_get_contents($filename);
+ \OC_FileProxy::$enabled = $proxyStatus;
+
+ file_put_contents($tmpFilename, $encryptedContent);
+
+ \OCA\Encryption\Helper::addTmpFileToMapper($tmpFilename, $filename);
+
+ // try to read the file from /tmp
+ $handle = fopen("crypt://".$tmpFilename, "r");
+ $contentFromTmpFile = stream_get_contents($handle);
+
+ // check if it was successful
+ $this->assertEquals($this->dataShort, $contentFromTmpFile);
+
+ // clean up
+ unlink($tmpFilename);
+ $this->view->unlink($filename);
+
+ }
}
diff --git a/apps/files_encryption/tests/util.php b/apps/files_encryption/tests/util.php
index c83c3836157..b1904cbadc7 100755
--- a/apps/files_encryption/tests/util.php
+++ b/apps/files_encryption/tests/util.php
@@ -134,6 +134,41 @@ class Test_Encryption_Util extends \PHPUnit_Framework_TestCase {
/**
* @medium
+ * @brief test detection of encrypted files
+ */
+ function testIsEncryptedPath() {
+
+ $util = new Encryption\Util($this->view, $this->userId);
+
+ self::loginHelper($this->userId);
+
+ $unencryptedFile = '/tmpUnencrypted-' . time() . '.txt';
+ $encryptedFile = '/tmpEncrypted-' . time() . '.txt';
+
+ // Disable encryption proxy to write a unencrypted file
+ $proxyStatus = \OC_FileProxy::$enabled;
+ \OC_FileProxy::$enabled = false;
+
+ $this->view->file_put_contents($this->userId . '/files/' . $unencryptedFile, $this->dataShort);
+
+ // Re-enable proxy - our work is done
+ \OC_FileProxy::$enabled = $proxyStatus;
+
+ // write a encrypted file
+ $this->view->file_put_contents($this->userId . '/files/' . $encryptedFile, $this->dataShort);
+
+ // test if both files are detected correctly
+ $this->assertFalse($util->isEncryptedPath($this->userId . '/files/' . $unencryptedFile));
+ $this->assertTrue($util->isEncryptedPath($this->userId . '/files/' . $encryptedFile));
+
+ // cleanup
+ $this->view->unlink($this->userId . '/files/' . $unencryptedFile, $this->dataShort);
+ $this->view->unlink($this->userId . '/files/' . $encryptedFile, $this->dataShort);
+
+ }
+
+ /**
+ * @medium
* @brief test setup of encryption directories
*/
function testSetupServerSide() {
diff --git a/apps/files_sharing/tests/base.php b/apps/files_sharing/tests/base.php
index 689c80cb9e6..3e283271f5d 100644
--- a/apps/files_sharing/tests/base.php
+++ b/apps/files_sharing/tests/base.php
@@ -132,8 +132,8 @@ abstract class Test_Files_Sharing_Base extends \PHPUnit_Framework_TestCase {
$share = Null;
- if ($result && $result->numRows() > 0) {
- $share = $result->fetchRow();
+ if ($result) {
+ $share = $result->fetchRow();
}
return $share;
diff --git a/lib/private/router.php b/lib/private/router.php
index dbaca9e0d5d..19c1e4473ec 100644
--- a/lib/private/router.php
+++ b/lib/private/router.php
@@ -67,7 +67,8 @@ class OC_Router {
$this->useCollection($app);
require_once $file;
$collection = $this->getCollection($app);
- $this->root->addCollection($collection, '/apps/'.$app);
+ $collection->addPrefix('/apps/'.$app);
+ $this->root->addCollection($collection);
}
$this->useCollection('root');
require_once 'settings/routes.php';
@@ -76,7 +77,8 @@ class OC_Router {
// include ocs routes
require_once 'ocs/routes.php';
$collection = $this->getCollection('ocs');
- $this->root->addCollection($collection, '/ocs');
+ $collection->addPrefix('/ocs');
+ $this->root->addCollection($collection);
}
protected function getCollection($name) {