diff --git a/apps/files_encryption/ajax/adminrecovery.php b/apps/files_encryption/ajax/adminrecovery.php index cec0cd4ddd..6a056dc7b3 100644 --- a/apps/files_encryption/ajax/adminrecovery.php +++ b/apps/files_encryption/ajax/adminrecovery.php @@ -1,6 +1,5 @@ -setValue( $app, $key, $value ) - * This file is licensed under the Affero General Public License version 3 or later. @@ -8,69 +7,79 @@ setValue( $app, $key, $value ) * * @brief Script to handle admin settings for encrypted key recovery */ - use OCA\Encryption; \OCP\JSON::checkAdminUser(); -\OCP\JSON::checkAppEnabled( 'files_encryption' ); +\OCP\JSON::checkAppEnabled('files_encryption'); \OCP\JSON::callCheck(); -$return = $doSetup = false; +$return = false; -if ( - isset( $_POST['adminEnableRecovery'] ) - && $_POST['adminEnableRecovery'] == 1 - && isset( $_POST['recoveryPassword'] ) - && ! empty ( $_POST['recoveryPassword'] ) +// Enable recoveryAdmin + +if ( + isset($_POST['adminEnableRecovery']) + && 1 == $_POST['adminEnableRecovery'] ) { - // TODO: Let the admin set this themselves - $recoveryAdminUid = 'recoveryAdmin'; - - // If desired recoveryAdmin UID is already in use - if ( ! \OC_User::userExists( $recoveryAdminUid ) ) { - - // Create new recoveryAdmin user - \OC_User::createUser( $recoveryAdminUid, $_POST['recoveryPassword'] ); - - $doSetup = true; - - } else { - - // Get list of admin users - $admins = OC_Group::usersInGroup( 'admin' ); - - // If the existing recoveryAdmin UID is an admin - if ( in_array( $recoveryAdminUid, $admins ) ) { - - // The desired recoveryAdmi UID pre-exists and can be used - $doSetup = true; - - // If the recoveryAdmin UID exists but doesn't have admin rights - } else { - - $return = false; - + $view = new \OC\Files\View('/'); + + $recoveryKeyId = OC_Appconfig::getValue('files_encryption', 'recoveryKeyId'); + + if ($recoveryKeyId === null) { + $recoveryKeyId = 'recovery_' . substr(md5(time()), 0, 8); + \OC_Appconfig::setValue('files_encryption', 'recoveryKeyId', $recoveryKeyId); + } + + if (!$view->is_dir('/owncloud_private_key')) { + $view->mkdir('/owncloud_private_key'); + } + + if ( + (!$view->file_exists("/public-keys/" . $recoveryKeyId . ".public.key") + || !$view->file_exists("/owncloud_private_key/" . $recoveryKeyId . ".private.key")) + && isset($_POST['recoveryPassword']) + && !empty($_POST['recoveryPassword']) + ) { + + $keypair = \OCA\Encryption\Crypt::createKeypair(); + + \OC_FileProxy::$enabled = false; + + // Save public key + + if (!$view->is_dir('/public-keys')) { + $view->mkdir('/public-keys'); } - + + $view->file_put_contents('/public-keys/' . $recoveryKeyId . '.public.key', $keypair['publicKey']); + + // Encrypt private key empthy passphrase + $encryptedPrivateKey = \OCA\Encryption\Crypt::symmetricEncryptFileContent($keypair['privateKey'], $_POST['recoveryPassword']); + + // Save private key + $view->file_put_contents('/owncloud_private_key/' . $recoveryKeyId . '.private.key', $encryptedPrivateKey); + + \OC_FileProxy::$enabled = true; + } - - // If recoveryAdmin has passed other checks - if ( $doSetup ) { - - $view = new \OC_FilesystemView( '/' ); - $util = new Util( $view, $recoveryAdminUid ); - - // Ensure recoveryAdmin is ready for encryption (has usable keypair etc.) - $util->setupServerSide( $_POST['recoveryPassword'] ); - - // Store the UID in the DB - OC_Appconfig::setValue( 'files_encryption', 'recoveryAdminUid', $recoveryAdminUid ); - - $return = true; - - } - + + // Set recoveryAdmin as enabled + OC_Appconfig::setValue('files_encryption', 'recoveryAdminEnabled', 1); + + $return = true; + +// Disable recoveryAdmin +} elseif ( + isset($_POST['adminEnableRecovery']) + && 0 == $_POST['adminEnableRecovery'] +) { + + // Set recoveryAdmin as enabled + OC_Appconfig::setValue('files_encryption', 'recoveryAdminEnabled', 0); + + $return = true; } -($return) ? OC_JSON::success() : OC_JSON::error(); \ No newline at end of file +// Return success or failure +( $return ) ? \OCP\JSON::success() : \OCP\JSON::error(); \ No newline at end of file diff --git a/apps/files_encryption/ajax/encryptall.php b/apps/files_encryption/ajax/encryptall.php new file mode 100644 index 0000000000..ce613ca443 --- /dev/null +++ b/apps/files_encryption/ajax/encryptall.php @@ -0,0 +1,40 @@ + + * This file is licensed under the Affero General Public License version 3 or later. + * See the COPYING-README file. + * + * @brief Script to handle manual trigger of \OCA\Encryption\Util{}->encryptAll() + */ + +use OCA\Encryption; + +\OCP\JSON::checkAppEnabled( 'files_encryption' ); +\OCP\JSON::callCheck(); + +$return = false; + +if ( + isset( $_POST['encryptAll'] ) + && ! empty( $_POST['userPassword'] ) +) { + + $view = new \OC_FilesystemView( '' ); + $userId = \OCP\User::getUser(); + $util = new \OCA\Encryption\Util( $view, $userId ); + $session = new \OCA\Encryption\Session( $view ); + $publicKey = \OCA\Encryption\Keymanager::getPublicKey( $view, $userId ); + $path = '/' . $userId . '/' . 'files'; + + $util->encryptAll( $publicKey, $path, $session->getLegacyKey(), $_POST['userPassword'] ); + + $return = true; + +} else { + + $return = false; + +} + +// Return success or failure +( $return ) ? \OCP\JSON::success() : \OCP\JSON::error(); \ No newline at end of file diff --git a/apps/files_encryption/ajax/userrecovery.php b/apps/files_encryption/ajax/userrecovery.php index 56c18f7ad5..85a799011d 100644 --- a/apps/files_encryption/ajax/userrecovery.php +++ b/apps/files_encryption/ajax/userrecovery.php @@ -1,5 +1,3 @@ -setValue( $app, $key, $value ) - @@ -17,26 +15,21 @@ use OCA\Encryption; if ( isset( $_POST['userEnableRecovery'] ) + && ( 0 == $_POST['userEnableRecovery'] || 1 == $_POST['userEnableRecovery'] ) ) { - // Ensure preference is an integer - $recoveryEnabled = intval( $_POST['userEnableRecovery'] ); - $userId = \OCP\USER::getUser(); $view = new \OC_FilesystemView( '/' ); - $util = new Util( $view, $userId ); + $util = new \OCA\Encryption\Util( $view, $userId ); // Save recovery preference to DB - $result = $util->setRecovery( $recoveryEnabled ); + $return = $util->setRecoveryForUser( $_POST['userEnableRecovery'] ); - if ( $result ) { +} else { + + $return = false; - \OCP\JSON::success(); - - } else { - - \OCP\JSON::error(); - - } - -} \ No newline at end of file +} + +// Return success or failure +( $return ) ? \OCP\JSON::success() : \OCP\JSON::error(); \ No newline at end of file diff --git a/apps/files_encryption/appinfo/app.php b/apps/files_encryption/appinfo/app.php index c2de9d0b44..b611eb798f 100644 --- a/apps/files_encryption/appinfo/app.php +++ b/apps/files_encryption/appinfo/app.php @@ -8,24 +8,25 @@ OC::$CLASSPATH['OCA\Encryption\Stream'] = 'files_encryption/lib/stream.php'; OC::$CLASSPATH['OCA\Encryption\Proxy'] = 'files_encryption/lib/proxy.php'; OC::$CLASSPATH['OCA\Encryption\Session'] = 'files_encryption/lib/session.php'; OC::$CLASSPATH['OCA\Encryption\Capabilities'] = 'files_encryption/lib/capabilities.php'; +OC::$CLASSPATH['OCA\Encryption\Helper'] = 'files_encryption/lib/helper.php'; OC_FileProxy::register( new OCA\Encryption\Proxy() ); -// User-related hooks -OCP\Util::connectHook( 'OC_User', 'post_login', 'OCA\Encryption\Hooks', 'login' ); -OCP\Util::connectHook( 'OC_User', 'pre_setPassword', 'OCA\Encryption\Hooks', 'setPassphrase' ); +// User related hooks +OCA\Encryption\Helper::registerUserHooks(); -// Sharing-related hooks -OCP\Util::connectHook( 'OCP\Share', 'post_shared', 'OCA\Encryption\Hooks', 'postShared' ); -OCP\Util::connectHook( 'OCP\Share', 'post_unshare', 'OCA\Encryption\Hooks', 'postUnshare' ); -OCP\Util::connectHook( 'OCP\Share', 'post_unshareAll', 'OCA\Encryption\Hooks', 'postUnshareAll' ); +// Sharing related hooks +OCA\Encryption\Helper::registerShareHooks(); -// Webdav-related hooks -OCP\Util::connectHook( 'OC_Webdav_Properties', 'update', 'OCA\Encryption\Hooks', 'updateKeyfileFromClient' ); +// Webdav related hooks +OCA\Encryption\Helper::registerWebdavHooks(); + +// Filesystem related hooks +OCA\Encryption\Helper::registerFilesystemHooks(); stream_wrapper_register( 'crypt', 'OCA\Encryption\Stream' ); -$view = new OC_FilesystemView( '/' ); +$view = new \OC\Files\View( '/' ); $session = new OCA\Encryption\Session( $view ); @@ -47,5 +48,6 @@ if ( } // Register settings scripts -OCP\App::registerAdmin( 'files_encryption', 'settings' ); +OCP\App::registerAdmin( 'files_encryption', 'settings-admin' ); OCP\App::registerPersonal( 'files_encryption', 'settings-personal' ); + diff --git a/apps/files_encryption/appinfo/database.xml b/apps/files_encryption/appinfo/database.xml index b144b6cb2a..64c9ef65fa 100644 --- a/apps/files_encryption/appinfo/database.xml +++ b/apps/files_encryption/appinfo/database.xml @@ -27,6 +27,13 @@ 0 Whether encryption key recovery is enabled + + migrationStatus + boolean + true + 0 + Whether encryption migration has been performed + \ No newline at end of file diff --git a/apps/files_encryption/css/settings-personal.css b/apps/files_encryption/css/settings-personal.css new file mode 100644 index 0000000000..4ee0acc976 --- /dev/null +++ b/apps/files_encryption/css/settings-personal.css @@ -0,0 +1,10 @@ +/* Copyright (c) 2013, Sam Tuke, + This file is licensed under the Affero General Public License version 3 or later. + See the COPYING-README file. */ + +#encryptAllError +, #encryptAllSuccess +, #recoveryEnabledError +, #recoveryEnabledSuccess { + display: none; +} \ No newline at end of file diff --git a/apps/files_encryption/hooks/hooks.php b/apps/files_encryption/hooks/hooks.php index 2ac74ad6c4..31175d1c34 100644 --- a/apps/files_encryption/hooks/hooks.php +++ b/apps/files_encryption/hooks/hooks.php @@ -23,10 +23,11 @@ namespace OCA\Encryption; +use OC\Files\Filesystem; + /** * Class for hook specific logic */ - class Hooks { // TODO: use passphrase for encrypting private key that is separate to @@ -46,15 +47,11 @@ class Hooks { $view = new \OC_FilesystemView( '/' ); $util = new Util( $view, $params['uid'] ); - - // Check files_encryption infrastructure is ready for action - if ( ! $util->ready() ) { - - \OC_Log::write( 'Encryption library', 'User account "' . $params['uid'] . '" is not ready for encryption; configuration started', \OC_Log::DEBUG ); - - return $util->setupServerSide( $params['password'] ); - } + // setup user, if user not ready force relogin + if(Helper::setupUser($util, $params['password']) === false) { + return false; + } \OC_FileProxy::$enabled = false; @@ -67,49 +64,89 @@ class Hooks { $session = new Session( $view ); $session->setPrivateKey( $privateKey, $params['uid'] ); - - //FIXME: disabled because it gets called each time a user do an operation on iPhone - //FIXME: we need a better place doing this and maybe only one time or by user - /*$view1 = new \OC_FilesystemView( '/' . $params['uid'] ); - // Set legacy encryption key if it exists, to support - // depreciated encryption system - if ( - $view1->file_exists( 'encryption.key' ) - && $encLegacyKey = $view1->file_get_contents( 'encryption.key' ) - ) { + // Check if first-run file migration has already been performed + $migrationCompleted = $util->getMigrationStatus(); - $plainLegacyKey = Crypt::legacyDecrypt( $encLegacyKey, $params['password'] ); + // If migration not yet done + if ( ! $migrationCompleted ) { + + $view1 = new \OC_FilesystemView( '/' . $params['uid'] ); - $session->setLegacyKey( $plainLegacyKey ); + // Set legacy encryption key if it exists, to support + // depreciated encryption system + if ( + $view1->file_exists( 'encryption.key' ) + && $encLegacyKey = $view1->file_get_contents( 'encryption.key' ) + ) { + + $plainLegacyKey = Crypt::legacyDecrypt( $encLegacyKey, $params['password'] ); + + $session->setLegacyKey( $plainLegacyKey ); + + } + + \OC_FileProxy::$enabled = false; + + $publicKey = Keymanager::getPublicKey( $view, $params['uid'] ); + + \OC_FileProxy::$enabled = false; + + // Encrypt existing user files: + // This serves to upgrade old versions of the encryption + // app (see appinfo/spec.txt) + if ( + $util->encryptAll( $publicKey, '/' . $params['uid'] . '/' . 'files', $session->getLegacyKey(), $params['password'] ) + ) { + + \OC_Log::write( + 'Encryption library', 'Encryption of existing files belonging to "' . $params['uid'] . '" completed' + , \OC_Log::INFO + ); + + } + + // Register successful migration in DB + $util->setMigrationStatus( 1 ); } - - \OC_FileProxy::$enabled = false; - - $publicKey = Keymanager::getPublicKey( $view, $params['uid'] ); - - \OC_FileProxy::$enabled = false;*/ - - // Encrypt existing user files: - // This serves to upgrade old versions of the encryption - // app (see appinfo/spec.txt) - /*if ( - $util->encryptAll( $publicKey, '/' . $params['uid'] . '/' . 'files', $session->getLegacyKey(), $params['password'] ) - ) { - - \OC_Log::write( - 'Encryption library', 'Encryption of existing files belonging to "' . $params['uid'] . '" started at login' - , \OC_Log::INFO - ); - - }*/ return true; } - - /** + + /** + * @brief setup encryption backend upon user created + * @note This method should never be called for users using client side encryption + */ + public static function postCreateUser( $params ) { + $view = new \OC_FilesystemView( '/' ); + + $util = new Util( $view, $params['uid'] ); + + Helper::setupUser($util, $params['password']); + } + + /** + * @brief cleanup encryption backend upon user deleted + * @note This method should never be called for users using client side encryption + */ + public static function postDeleteUser( $params ) { + $view = new \OC_FilesystemView( '/' ); + + // cleanup public key + $publicKey = '/public-keys/' . $params['uid'] . '.public.key'; + + // Disable encryption proxy to prevent recursive calls + $proxyStatus = \OC_FileProxy::$enabled; + \OC_FileProxy::$enabled = false; + + $view->unlink($publicKey); + + \OC_FileProxy::$enabled = $proxyStatus; + } + + /** * @brief Change a user's encryption passphrase * @param array $params keys: uid, password */ @@ -119,8 +156,10 @@ class Hooks { // is in use (client-side encryption does not have access to // the necessary keys) if ( Crypt::mode() == 'server' ) { - - $session = new Session(); + + $view = new \OC_FilesystemView( '/' ); + + $session = new Session($view); // Get existing decrypted private key $privateKey = $session->getPrivateKey(); @@ -165,7 +204,36 @@ class Hooks { } } - + + /* + * @brief check if files can be encrypted to every user. + */ + public static function preShared($params) { + + $users = array(); + $view = new \OC\Files\View('/public-keys/'); + + switch ($params['shareType']) { + case \OCP\Share::SHARE_TYPE_USER: + $users[] = $params['shareWith']; + break; + case \OCP\Share::SHARE_TYPE_GROUP: + $users = \OC_Group::usersInGroup($params['shareWith']); + break; + } + + foreach ($users as $user) { + if (!$view->file_exists($user . '.public.key')) { + // Set flag var 'run' to notify emitting + // script that hook execution failed + $params['run']->run = false; + // TODO: Make sure files_sharing provides user + // feedback on failed share + break; + } + } + } + /** * @brief */ @@ -187,7 +255,9 @@ class Hooks { // [fileTarget] => /test8 // [id] => 10 // [token] => + // [run] => whether emitting script should continue to run // TODO: Should other kinds of item be encrypted too? + if ($params['itemType'] === 'file' || $params['itemType'] === 'folder') { $view = new \OC_FilesystemView('/'); @@ -196,13 +266,55 @@ class Hooks { $util = new Util($view, $userId); $path = $util->fileIdToPath($params['itemSource']); - //check if this is a reshare action, that's true if the item source is already shared with me - $sharedItem = \OCP\Share::getItemSharedWithBySource($params['itemType'], $params['itemSource']); - if ($sharedItem) { - // if it is a re-share than the file is located in my Shared folder - $path = '/Shared'.$sharedItem['file_target']; - } else { - $path = $util->fileIdToPath($params['itemSource']); + //if parent is set, then this is a re-share action + if ($params['parent']) { + + // get the parent from current share + $parent = $util->getShareParent($params['parent']); + + // if parent is file the it is an 1:1 share + if ($parent['item_type'] === 'file') { + + // prefix path with Shared + $path = '/Shared' . $parent['file_target']; + } else { + + // NOTE: parent is folder but shared was a file! + // we try to rebuild the missing path + // some examples we face here + // user1 share folder1 with user2 folder1 has + // the following structure + // /folder1/subfolder1/subsubfolder1/somefile.txt + // user2 re-share subfolder2 with user3 + // user3 re-share somefile.txt user4 + // so our path should be + // /Shared/subfolder1/subsubfolder1/somefile.txt + // while user3 is sharing + + if ($params['itemType'] === 'file') { + // get target path + $targetPath = $util->fileIdToPath($params['fileSource']); + $targetPathSplit = array_reverse(explode('/', $targetPath)); + + // init values + $path = ''; + $sharedPart = ltrim($parent['file_target'], '/'); + + // rebuild path + foreach ($targetPathSplit as $pathPart) { + if ($pathPart !== $sharedPart) { + $path = '/' . $pathPart . $path; + } else { + break; + } + } + // prefix path with Shared + $path = '/Shared' . $parent['file_target'] . $path; + } else { + // prefix path with Shared + $path = '/Shared' . $parent['file_target'] . $params['fileTarget']; + } + } } $sharingEnabled = \OCP\Share::isEnabled(); @@ -216,23 +328,7 @@ class Hooks { foreach ($allFiles as $path) { $usersSharing = $util->getSharingUsersArray($sharingEnabled, $path); - - $failed = array(); - - // Attempt to set shareKey - if (!$util->setSharedFileKeyfiles($session, $usersSharing, $path)) { - - $failed[] = $path; - } - } - - // If no attempts to set keyfiles failed - if (empty($failed)) { - - return true; - } else { - - return false; + $util->setSharedFileKeyfiles( $session, $usersSharing, $path ); } } } @@ -241,51 +337,89 @@ class Hooks { * @brief */ public static function postUnshare( $params ) { - + // NOTE: $params has keys: // [itemType] => file // [itemSource] => 13 // [shareType] => 0 // [shareWith] => test1 - - if ( $params['itemType'] === 'file' || $params['itemType'] === 'folder' ) { - + // [itemParent] => + + if ( $params['itemType'] === 'file' || $params['itemType'] === 'folder' ) { + $view = new \OC_FilesystemView( '/' ); - $session = new Session($view); $userId = \OCP\User::getUser(); - $util = new Util( $view, $userId ); + $util = new Util( $view, $userId); $path = $util->fileIdToPath( $params['itemSource'] ); + // check if this is a re-share + if ( $params['itemParent'] ) { + + // get the parent from current share + $parent = $util->getShareParent( $params['itemParent'] ); + + // get target path + $targetPath = $util->fileIdToPath( $params['itemSource'] ); + $targetPathSplit = array_reverse( explode( '/', $targetPath ) ); + + // init values + $path = ''; + $sharedPart = ltrim( $parent['file_target'], '/' ); + + // rebuild path + foreach ( $targetPathSplit as $pathPart ) { + + if ( $pathPart !== $sharedPart ) { + + $path = '/' . $pathPart . $path; + + } else { + + break; + + } + + } + + // prefix path with Shared + $path = '/Shared' . $parent['file_target'] . $path; + } + // for group shares get a list of the group members - if ($params['shareType'] == \OCP\Share::SHARE_TYPE_GROUP) { + if ( $params['shareType'] == \OCP\Share::SHARE_TYPE_GROUP ) { $userIds = \OC_Group::usersInGroup($params['shareWith']); + } else if ( $params['shareType'] == \OCP\Share::SHARE_TYPE_LINK ){ + $userIds = array( $util->getPublicShareKeyId() ); } else { - $userIds = array($params['shareWith']); + $userIds = array( $params['shareWith'] ); } // if we unshare a folder we need a list of all (sub-)files - if ($params['itemType'] === 'folder') { - $allFiles = $util->getAllFiles($path); + if ( $params['itemType'] === 'folder' ) { + + $allFiles = $util->getAllFiles( $path ); + } else { - $allFiles = array($path); + + $allFiles = array( $path ); } - foreach ( $allFiles as $path ) { // check if the user still has access to the file, otherwise delete share key - $sharingUsers = $util->getSharingUsersArray(true, $path); + $sharingUsers = $util->getSharingUsersArray( true, $path ); // Unshare every user who no longer has access to the file - $delUsers = array_diff($userIds, $sharingUsers); - if ( ! Keymanager::delShareKey( $view, $delUsers, $path ) ) { + $delUsers = array_diff( $userIds, $sharingUsers); + + if ( !Keymanager::delShareKey( $view, $delUsers, $path ) ) { $failed[] = $path; - - } + } + } - + // If no attempts to set keyfiles failed if ( empty( $failed ) ) { @@ -296,19 +430,99 @@ class Hooks { return false; } - } - } /** * @brief */ public static function postUnshareAll( $params ) { - + // NOTE: It appears that this is never called for files, so // we may not need to implement it } - + + + /** + * @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 + * + * This function is connected to the rename signal of OC_Filesystem and adjust the name and location + * of the stored versions along the actual file + */ + public static function postRename($params) { + // Disable encryption proxy to prevent recursive calls + $proxyStatus = \OC_FileProxy::$enabled; + \OC_FileProxy::$enabled = false; + + $view = new \OC_FilesystemView('/'); + $session = new Session($view); + $userId = \OCP\User::getUser(); + $util = new Util( $view, $userId ); + + // Format paths to be relative to user files dir + $oldKeyfilePath = \OC\Files\Filesystem::normalizePath($userId . '/' . 'files_encryption' . '/' . 'keyfiles' . '/' . $params['oldpath']); + $newKeyfilePath = \OC\Files\Filesystem::normalizePath($userId . '/' . 'files_encryption' . '/' . 'keyfiles' . '/' . $params['newpath']); + + // add key ext if this is not an folder + if (!$view->is_dir($oldKeyfilePath)) { + $oldKeyfilePath .= '.key'; + $newKeyfilePath .= '.key'; + + // handle share-keys + $localKeyPath = $view->getLocalFile($userId.'/files_encryption/share-keys/'.$params['oldpath']); + $matches = glob(preg_quote($localKeyPath).'*.shareKey'); + foreach ($matches as $src) { + $dst = \OC\Files\Filesystem::normalizePath(str_replace($params['oldpath'], $params['newpath'], $src)); + + // create destination folder if not exists + if(!file_exists(dirname($dst))) { + mkdir(dirname($dst), 0750, true); + } + + rename($src, $dst); + } + + } else { + // handle share-keys folders + $oldShareKeyfilePath = \OC\Files\Filesystem::normalizePath($userId . '/' . 'files_encryption' . '/' . 'share-keys' . '/' . $params['oldpath']); + $newShareKeyfilePath = \OC\Files\Filesystem::normalizePath($userId . '/' . 'files_encryption' . '/' . 'share-keys' . '/' . $params['newpath']); + + // create destination folder if not exists + if(!$view->file_exists(dirname($newShareKeyfilePath))) { + $view->mkdir(dirname($newShareKeyfilePath), 0750, true); + } + + $view->rename($oldShareKeyfilePath, $newShareKeyfilePath); + } + + // Rename keyfile so it isn't orphaned + if($view->file_exists($oldKeyfilePath)) { + + // create destination folder if not exists + if(!$view->file_exists(dirname($newKeyfilePath))) { + $view->mkdir(dirname($newKeyfilePath), 0750, true); + } + + $view->rename($oldKeyfilePath, $newKeyfilePath); + } + + // build the path to the file + $newPath = '/' . $userId . '/files' .$params['newpath']; + $newPathRelative = $params['newpath']; + + if($util->fixFileSize($newPath)) { + // get sharing app state + $sharingEnabled = \OCP\Share::isEnabled(); + + // get users + $usersSharing = $util->getSharingUsersArray($sharingEnabled, $newPathRelative); + + // update sharing-keys + $util->setSharedFileKeyfiles($session, $usersSharing, $newPathRelative); + } + + \OC_FileProxy::$enabled = $proxyStatus; + } } diff --git a/apps/files_encryption/js/settings-admin.js b/apps/files_encryption/js/settings-admin.js new file mode 100644 index 0000000000..9cdb7aca68 --- /dev/null +++ b/apps/files_encryption/js/settings-admin.js @@ -0,0 +1,40 @@ +/** + * Copyright (c) 2013, Sam Tuke , Robin Appelman + * + * This file is licensed under the Affero General Public License version 3 or later. + * See the COPYING-README file. + */ + + +$(document).ready(function(){ + // Trigger ajax on recoveryAdmin status change + $( 'input:radio[name="adminEnableRecovery"]' ).change( + function() { + + var recoveryStatus = $( this ).val(); + var recoveryPassword = $( '#recoveryPassword' ).val(); + + if ( '' == recoveryPassword ) { + + // FIXME: add proper OC notification + alert( 'You must set a recovery account password first' ); + + } else { + + $.post( + OC.filePath( 'files_encryption', 'ajax', 'adminrecovery.php' ) + , { adminEnableRecovery: recoveryStatus, recoveryPassword: recoveryPassword } + , function( data ) { + alert( data ); + } + ); + + } + } + ); + + function blackListChange(){ + var blackList=$( '#encryption_blacklist' ).val().join( ',' ); + OC.AppConfig.setValue( 'files_encryption', 'type_blacklist', blackList ); + } +}) \ No newline at end of file diff --git a/apps/files_encryption/js/settings-personal.js b/apps/files_encryption/js/settings-personal.js new file mode 100644 index 0000000000..3b9b00dc79 --- /dev/null +++ b/apps/files_encryption/js/settings-personal.js @@ -0,0 +1,60 @@ +/** + * Copyright (c) 2013, Sam Tuke + * This file is licensed under the Affero General Public License version 3 or later. + * See the COPYING-README file. + */ + +$(document).ready(function(){ + // Trigger ajax on recoveryAdmin status change + $( 'input:radio[name="userEnableRecovery"]' ).change( + function() { + + // Hide feedback messages in case they're already visible + $('#recoveryEnabledSuccess').hide(); + $('#recoveryEnabledError').hide(); + + var recoveryStatus = $( this ).val(); + + $.post( + OC.filePath( 'files_encryption', 'ajax', 'userrecovery.php' ) + , { userEnableRecovery: recoveryStatus } + , function( data ) { + if ( data.status == "success" ) { + $('#recoveryEnabledSuccess').show(); + } else { + $('#recoveryEnabledError').show(); + } + } + ); + // Ensure page is not reloaded on form submit + return false; + } + ); + + $("#encryptAll").click( + function(){ + + // Hide feedback messages in case they're already visible + $('#encryptAllSuccess').hide(); + $('#encryptAllError').hide(); + + var userPassword = $( '#userPassword' ).val(); + var encryptAll = $( '#encryptAll' ).val(); + + $.post( + OC.filePath( 'files_encryption', 'ajax', 'encryptall.php' ) + , { encryptAll: encryptAll, userPassword: userPassword } + , function( data ) { + if ( data.status == "success" ) { + $('#encryptAllSuccess').show(); + } else { + $('#encryptAllError').show(); + } + } + ); + // Ensure page is not reloaded on form submit + return false; + } + + ); +}) \ No newline at end of file diff --git a/apps/files_encryption/js/settings.js b/apps/files_encryption/js/settings.js deleted file mode 100644 index 9a0bebf247..0000000000 --- a/apps/files_encryption/js/settings.js +++ /dev/null @@ -1,36 +0,0 @@ -/** - * Copyright (c) 2011, Robin Appelman - * This file is licensed under the Affero General Public License version 3 or later. - * See the COPYING-README file. - */ - - -$(document).ready(function(){ - // Trigger ajax on filetype blacklist change - $('#encryption_blacklist').multiSelect({ - oncheck:blackListChange, - onuncheck:blackListChange, - createText:'...' - }); - - // Trigger ajax on recoveryAdmin status change - $( 'input:radio[name="adminEnableRecovery"]' ).change( - function() { - - var foo = $( this ).val(); - - $.post( - OC.filePath('files_encryption', 'ajax', 'adminrecovery.php') - , { adminEnableRecovery: foo, recoveryPassword: 'password' } - , function( data ) { - alert( data ); - } - ); - } - ); - - function blackListChange(){ - var blackList=$('#encryption_blacklist').val().join(','); - OC.AppConfig.setValue('files_encryption','type_blacklist',blackList); - } -}) \ No newline at end of file diff --git a/apps/files_encryption/l10n/de.php b/apps/files_encryption/l10n/de.php index bcf0ca5ad6..cdcd8a40b2 100644 --- a/apps/files_encryption/l10n/de.php +++ b/apps/files_encryption/l10n/de.php @@ -3,5 +3,5 @@ "File encryption is enabled." => "Dateiverschlüsselung ist aktiviert", "The following file types will not be encrypted:" => "Die folgenden Dateitypen werden nicht verschlüsselt:", "Exclude the following file types from encryption:" => "Schließe die folgenden Dateitypen von der Verschlüsselung aus:", -"None" => "Nichts" +"None" => "Keine" ); diff --git a/apps/files_encryption/l10n/de_DE.php b/apps/files_encryption/l10n/de_DE.php index 71fd7d9671..4f08b98eb2 100644 --- a/apps/files_encryption/l10n/de_DE.php +++ b/apps/files_encryption/l10n/de_DE.php @@ -3,5 +3,5 @@ "File encryption is enabled." => "Datei-Verschlüsselung ist aktiviert", "The following file types will not be encrypted:" => "Die folgenden Dateitypen werden nicht verschlüsselt:", "Exclude the following file types from encryption:" => "Die folgenden Dateitypen von der Verschlüsselung ausnehmen:", -"None" => "Nichts" +"None" => "Keine" ); diff --git a/apps/files_encryption/l10n/el.php b/apps/files_encryption/l10n/el.php index 82a4c92ec2..0031a73194 100644 --- a/apps/files_encryption/l10n/el.php +++ b/apps/files_encryption/l10n/el.php @@ -3,5 +3,5 @@ "File encryption is enabled." => "Η κρυπτογράφηση αρχείων είναι ενεργή.", "The following file types will not be encrypted:" => "Οι παρακάτω τύποι αρχείων δεν θα κρυπτογραφηθούν:", "Exclude the following file types from encryption:" => "Εξαίρεση των παρακάτω τύπων αρχείων από την κρυπτογράφηση:", -"None" => "Τίποτα" +"None" => "Καμία" ); diff --git a/apps/files_encryption/l10n/eu.php b/apps/files_encryption/l10n/eu.php index 7e3b7611ff..5a22b65728 100644 --- a/apps/files_encryption/l10n/eu.php +++ b/apps/files_encryption/l10n/eu.php @@ -3,5 +3,5 @@ "File encryption is enabled." => "Fitxategien enkriptazioa gaituta dago.", "The following file types will not be encrypted:" => "Hurrengo fitxategi motak ez dira enkriptatuko:", "Exclude the following file types from encryption:" => "Baztertu hurrengo fitxategi motak enkriptatzetik:", -"None" => "Ezer" +"None" => "Bat ere ez" ); diff --git a/apps/files_encryption/l10n/it.php b/apps/files_encryption/l10n/it.php index c717134526..9ab9bc492a 100644 --- a/apps/files_encryption/l10n/it.php +++ b/apps/files_encryption/l10n/it.php @@ -3,5 +3,5 @@ "File encryption is enabled." => "La cifratura dei file è abilitata.", "The following file types will not be encrypted:" => "I seguenti tipi di file non saranno cifrati:", "Exclude the following file types from encryption:" => "Escludi i seguenti tipi di file dalla cifratura:", -"None" => "Nessuno" +"None" => "Nessuna" ); diff --git a/apps/files_encryption/l10n/pl.php b/apps/files_encryption/l10n/pl.php index 836f545359..2fa86f454f 100644 --- a/apps/files_encryption/l10n/pl.php +++ b/apps/files_encryption/l10n/pl.php @@ -3,5 +3,5 @@ "File encryption is enabled." => "Szyfrowanie plików jest włączone", "The following file types will not be encrypted:" => "Poniższe typy plików nie będą szyfrowane:", "Exclude the following file types from encryption:" => "Wyłącz poniższe typy plików z szyfrowania:", -"None" => "Nic" +"None" => "Brak" ); diff --git a/apps/files_encryption/l10n/pt_BR.php b/apps/files_encryption/l10n/pt_BR.php index b41c6ed315..28807db72c 100644 --- a/apps/files_encryption/l10n/pt_BR.php +++ b/apps/files_encryption/l10n/pt_BR.php @@ -3,5 +3,5 @@ "File encryption is enabled." => "A criptografia de arquivos está ativada.", "The following file types will not be encrypted:" => "Os seguintes tipos de arquivo não serão criptografados:", "Exclude the following file types from encryption:" => "Excluir os seguintes tipos de arquivo da criptografia:", -"None" => "Nada" +"None" => "Nenhuma" ); diff --git a/apps/files_encryption/l10n/ru.php b/apps/files_encryption/l10n/ru.php index f07dec621d..22c1e3da37 100644 --- a/apps/files_encryption/l10n/ru.php +++ b/apps/files_encryption/l10n/ru.php @@ -3,5 +3,5 @@ "File encryption is enabled." => "Шифрование файла включено.", "The following file types will not be encrypted:" => "Следующие типы файлов не будут зашифрованы:", "Exclude the following file types from encryption:" => "Исключить следующие типы файлов из шифрованных:", -"None" => "Нет новостей" +"None" => "Ничего" ); diff --git a/apps/files_encryption/l10n/sk_SK.php b/apps/files_encryption/l10n/sk_SK.php index aaea9da21b..bebb623471 100644 --- a/apps/files_encryption/l10n/sk_SK.php +++ b/apps/files_encryption/l10n/sk_SK.php @@ -3,5 +3,5 @@ "File encryption is enabled." => "Šifrovanie súborov nastavené.", "The following file types will not be encrypted:" => "Uvedené typy súborov nebudú šifrované:", "Exclude the following file types from encryption:" => "Nešifrovať uvedené typy súborov", -"None" => "Žiadny" +"None" => "Žiadne" ); diff --git a/apps/files_encryption/l10n/th_TH.php b/apps/files_encryption/l10n/th_TH.php index 30c0324a98..e46d249118 100644 --- a/apps/files_encryption/l10n/th_TH.php +++ b/apps/files_encryption/l10n/th_TH.php @@ -1,4 +1,4 @@ "การเข้ารหัส", -"None" => "ไม่มี" +"None" => "ไม่ต้อง" ); diff --git a/apps/files_encryption/l10n/vi.php b/apps/files_encryption/l10n/vi.php index 40d4b1d0fe..0a88d1b2db 100644 --- a/apps/files_encryption/l10n/vi.php +++ b/apps/files_encryption/l10n/vi.php @@ -3,5 +3,5 @@ "File encryption is enabled." => "Mã hóa file đã mở", "The following file types will not be encrypted:" => "Loại file sau sẽ không được mã hóa", "Exclude the following file types from encryption:" => "Việc mã hóa không bao gồm loại file sau", -"None" => "Không gì cả" +"None" => "Không có gì hết" ); diff --git a/apps/files_encryption/lib/helper.php b/apps/files_encryption/lib/helper.php new file mode 100755 index 0000000000..783cebeee5 --- /dev/null +++ b/apps/files_encryption/lib/helper.php @@ -0,0 +1,93 @@ + + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + * License as published by the Free Software Foundation; either + * version 3 of the License, or any later version. + * + * This library 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 along with this library. If not, see . + * + */ + +namespace OCA\Encryption; + +/** + * @brief Class to manage registration of hooks an various helper methods + */ +class Helper { + + /** + * @brief register share related hooks + * + */ + public static function registerShareHooks() { + + \OCP\Util::connectHook( 'OCP\Share', 'pre_shared', 'OCA\Encryption\Hooks', 'preShared' ); + \OCP\Util::connectHook( 'OCP\Share', 'post_shared', 'OCA\Encryption\Hooks', 'postShared' ); + \OCP\Util::connectHook( 'OCP\Share', 'post_unshare', 'OCA\Encryption\Hooks', 'postUnshare' ); + \OCP\Util::connectHook( 'OCP\Share', 'post_unshareAll', 'OCA\Encryption\Hooks', 'postUnshareAll' ); + } + + /** + * @brief register user related hooks + * + */ + public static function registerUserHooks() { + + \OCP\Util::connectHook( 'OC_User', 'post_login', 'OCA\Encryption\Hooks', 'login' ); + \OCP\Util::connectHook( 'OC_User', 'pre_setPassword', 'OCA\Encryption\Hooks', 'setPassphrase' ); + \OCP\Util::connectHook( 'OC_User', 'post_createUser', 'OCA\Encryption\Hooks', 'postCreateUser' ); + \OCP\Util::connectHook( 'OC_User', 'post_deleteUser', 'OCA\Encryption\Hooks', 'postDeleteUser' ); + } + + /** + * @brief register webdav related hooks + * + */ + public static function registerWebdavHooks() { + + \OCP\Util::connectHook( 'OC_Webdav_Properties', 'update', 'OCA\Encryption\Hooks', 'updateKeyfileFromClient' ); + } + + /** + * @brief register filesystem related hooks + * + */ + public static function registerFilesystemHooks() { + + \OCP\Util::connectHook('OC_Filesystem', 'post_rename', 'OCA\Encryption\Hooks', 'postRename'); + } + + /** + * @brief setup user for files_encryption + * + * @param Util $util + * @param string $password + * @return bool + */ + public static function setupUser($util, $password) { + // Check files_encryption infrastructure is ready for action + if ( ! $util->ready() ) { + + \OC_Log::write( 'Encryption library', 'User account "' . $util->getUserId() . '" is not ready for encryption; configuration started', \OC_Log::DEBUG ); + + if(!$util->setupServerSide( $password )) { + return false; + } + } + + return true; + } +} \ No newline at end of file diff --git a/apps/files_encryption/lib/keymanager.php b/apps/files_encryption/lib/keymanager.php index cfc13ee132..74462a0d1e 100755 --- a/apps/files_encryption/lib/keymanager.php +++ b/apps/files_encryption/lib/keymanager.php @@ -32,15 +32,20 @@ class Keymanager { /** * @brief retrieve the ENCRYPTED private key from a user * - * @return string private key or false + * @return string private key or false (hopefully) * @note the key returned by this method must be decrypted before use */ public static function getPrivateKey( \OC_FilesystemView $view, $user ) { $path = '/' . $user . '/' . 'files_encryption' . '/' . $user.'.private.key'; - + + $proxyStatus = \OC_FileProxy::$enabled; + \OC_FileProxy::$enabled = false; + $key = $view->file_get_contents( $path ); - + + \OC_FileProxy::$enabled = $proxyStatus; + return $key; } @@ -113,8 +118,8 @@ class Keymanager { \OC_FileProxy::$enabled = false; //here we need the currently logged in user, while userId can be a different user - $util = new Util($view, \OCP\User::getUser()); - list($owner, $filename) = $util->getUidAndFilename($path); + $util = new Util( $view, \OCP\User::getUser() ); + list( $owner, $filename ) = $util->getUidAndFilename( $path ); $basePath = '/' . $owner . '/files_encryption/keyfiles'; @@ -123,22 +128,74 @@ class Keymanager { if ( !$view->is_dir( $basePath . '/' . $targetPath ) ) { // create all parent folders - $info=pathinfo($basePath . '/' . $targetPath); - $keyfileFolderName=$view->getLocalFolder($info['dirname']); - if(!file_exists($keyfileFolderName)) { - mkdir($keyfileFolderName, 0750, true); + $info = pathinfo( $basePath . '/' . $targetPath ); + $keyfileFolderName = $view->getLocalFolder( $info['dirname'] ); + + if ( ! file_exists( $keyfileFolderName ) ) { + + mkdir( $keyfileFolderName, 0750, true ); + } } + + // try reusing key file if part file + if ( self::isPartialFilePath( $targetPath ) ) { - $result = $view->file_put_contents( $basePath . '/' . $targetPath . '.key', $catfile ); + $result = $view->file_put_contents( $basePath . '/' . self::fixPartialFilePath( $targetPath ) . '.key', $catfile ); + + } else { + $result = $view->file_put_contents( $basePath . '/' . $targetPath . '.key', $catfile ); + + } \OC_FileProxy::$enabled = $proxyStatus; return $result; } + + /** + * @brief Remove .path extension from a file path + * @param string $path Path that may identify a .part file + * @return string File path without .part extension + * @note this is needed for reusing keys + */ + public static function fixPartialFilePath( $path ) { + if (preg_match('/\.part$/', $path)) { + + $newLength = strlen($path) - 5; + $fPath = substr($path, 0, $newLength); + + return $fPath; + + } else { + + return $path; + + } + + } + + /** + * @brief Check if a path is a .part file + * @param string $path Path that may identify a .part file + * @return bool + */ + public static function isPartialFilePath( $path ) { + + if ( preg_match('/\.part$/', $path ) ) { + + return true; + + } else { + + return false; + + } + + } /** * @brief retrieve keyfile for an encrypted file * @param \OC_FilesystemView $view @@ -150,14 +207,27 @@ class Keymanager { * of the keyfile must be performed by client code */ public static function getFileKey( \OC_FilesystemView $view, $userId, $filePath ) { + + // try reusing key file if part file + if ( self::isPartialFilePath( $filePath ) ) { + $result = self::getFileKey( $view, $userId, self::fixPartialFilePath( $filePath ) ); + + if ( $result ) { + + return $result; + + } + + } + $util = new Util($view, \OCP\User::getUser()); list($owner, $filename) = $util->getUidAndFilename($filePath); $filePath_f = ltrim( $filename, '/' ); - + $keyfilePath = '/' . $owner . '/files_encryption/keyfiles/' . $filePath_f . '.key'; - $proxyStatus = \OC_FileProxy::$enabled; + $proxyStatus = \OC_FileProxy::$enabled; \OC_FileProxy::$enabled = false; if ( $view->file_exists( $keyfilePath ) ) { @@ -226,7 +296,7 @@ class Keymanager { $view = new \OC_FilesystemView( '/' . $user . '/files_encryption' ); - $proxyStatus = \OC_FileProxy::$enabled; + $proxyStatus = \OC_FileProxy::$enabled; \OC_FileProxy::$enabled = false; if ( !$view->file_exists( '' ) ) $view->mkdir( '' ); @@ -235,7 +305,8 @@ class Keymanager { \OC_FileProxy::$enabled = $proxyStatus; - return $result; + return $result; + } /** @@ -261,7 +332,7 @@ class Keymanager { $view = new \OC_FilesystemView( '/public-keys' ); - $proxyStatus = \OC_FileProxy::$enabled; + $proxyStatus = \OC_FileProxy::$enabled; \OC_FileProxy::$enabled = false; if ( !$view->file_exists( '' ) ) $view->mkdir( '' ); @@ -270,7 +341,7 @@ class Keymanager { \OC_FileProxy::$enabled = $proxyStatus; - return $result; + return $result; } @@ -287,23 +358,32 @@ class Keymanager { */ public static function setShareKey( \OC_FilesystemView $view, $path, $userId, $shareKey ) { - //here we need the currently logged in user, while userId can be a different user + // Here we need the currently logged in user, while userId can be a different user $util = new Util( $view, \OCP\User::getUser() ); - list($owner, $filename) = $util->getUidAndFilename($path); + list( $owner, $filename ) = $util->getUidAndFilename( $path ); $basePath = '/' . $owner . '/files_encryption/share-keys'; $shareKeyPath = self::keySetPreparation( $view, $filename, $basePath, $owner ); - - $writePath = $basePath . '/' . $shareKeyPath . '.' . $userId . '.shareKey'; - $proxyStatus = \OC_FileProxy::$enabled; + // try reusing key file if part file + if(self::isPartialFilePath($shareKeyPath)) { + + $writePath = $basePath . '/' . self::fixPartialFilePath($shareKeyPath) . '.' . $userId . '.shareKey'; + + } else { + + $writePath = $basePath . '/' . $shareKeyPath . '.' . $userId . '.shareKey'; + + } + + $proxyStatus = \OC_FileProxy::$enabled; \OC_FileProxy::$enabled = false; $result = $view->file_put_contents( $writePath, $shareKey ); - \OC_FileProxy::$enabled = $proxyStatus; + \OC_FileProxy::$enabled = $proxyStatus; if ( is_int( $result ) @@ -359,7 +439,20 @@ class Keymanager { */ public static function getShareKey( \OC_FilesystemView $view, $userId, $filePath ) { - $proxyStatus = \OC_FileProxy::$enabled; + // try reusing key file if part file + if(self::isPartialFilePath($filePath)) { + + $result = self::getShareKey($view, $userId, self::fixPartialFilePath($filePath)); + + if($result) { + + return $result; + + } + + } + + $proxyStatus = \OC_FileProxy::$enabled; \OC_FileProxy::$enabled = false; //here we need the currently logged in user, while userId can be a different user @@ -367,7 +460,7 @@ class Keymanager { list($owner, $filename) = $util->getUidAndFilename($filePath); - $shareKeyPath = '/' . $owner . '/files_encryption/share-keys/' . $filename . '.' . $userId . '.shareKey'; + $shareKeyPath = \OC\Files\Filesystem::normalizePath('/' . $owner . '/files_encryption/share-keys/' . $filename . '.' . $userId . '.shareKey'); if ( $view->file_exists( $shareKeyPath ) ) { $result = $view->file_get_contents( $shareKeyPath ); diff --git a/apps/files_encryption/lib/proxy.php b/apps/files_encryption/lib/proxy.php index 73f72a9e23..36d05d7e0f 100644 --- a/apps/files_encryption/lib/proxy.php +++ b/apps/files_encryption/lib/proxy.php @@ -93,29 +93,29 @@ class Proxy extends \OC_FileProxy { public function preFile_put_contents( $path, &$data ) { - if ( self::shouldEncrypt( $path ) ) { + if ( self::shouldEncrypt( $path ) ) { - // Stream put contents should have been converted to fopen + // Stream put contents should have been converted to fopen if ( !is_resource( $data ) ) { - $userId = \OCP\USER::getUser(); - $rootView = new \OC_FilesystemView( '/' ); - $util = new Util( $rootView, $userId ); - $session = new Session( $rootView ); + $userId = \OCP\USER::getUser(); + $view = new \OC_FilesystemView( '/' ); + $util = new Util( $view, $userId ); + $session = new Session( $view ); $privateKey = $session->getPrivateKey(); $filePath = $util->stripUserFilesPath( $path ); // Set the filesize for userland, before encrypting $size = strlen( $data ); - + // Disable encryption proxy to prevent recursive calls - $proxyStatus = \OC_FileProxy::$enabled; - \OC_FileProxy::$enabled = false; - + $proxyStatus = \OC_FileProxy::$enabled; + \OC_FileProxy::$enabled = false; + // Check if there is an existing key we can reuse - if ( $encKeyfile = Keymanager::getFileKey( $rootView, $userId, $filePath ) ) { + if ( $encKeyfile = Keymanager::getFileKey( $view, $userId, $filePath ) ) { // Fetch shareKey - $shareKey = Keymanager::getShareKey( $rootView, $userId, $filePath ); + $shareKey = Keymanager::getShareKey( $view, $userId, $filePath ); // Decrypt the keyfile $plainKey = Crypt::multiKeyDecrypt( $encKeyfile, $shareKey, $privateKey ); @@ -124,7 +124,7 @@ class Proxy extends \OC_FileProxy { // Make a new key $plainKey = Crypt::generateKey(); - + } // Encrypt data @@ -134,36 +134,37 @@ class Proxy extends \OC_FileProxy { $uniqueUserIds = $util->getSharingUsersArray( $sharingEnabled, $filePath, $userId ); - // Fetch public keys for all users who will share the file - $publicKeys = Keymanager::getPublicKeys( $rootView, $uniqueUserIds ); + // Fetch public keys for all users who will share the file + $publicKeys = Keymanager::getPublicKeys( $view, $uniqueUserIds ); - // Encrypt plain keyfile to multiple sharefiles + // Encrypt plain keyfile to multiple sharefiles $multiEncrypted = Crypt::multiKeyEncrypt( $plainKey, $publicKeys ); // Save sharekeys to user folders - Keymanager::setShareKeys( $rootView, $filePath, $multiEncrypted['keys'] ); + Keymanager::setShareKeys( $view, $filePath, $multiEncrypted['keys'] ); // Set encrypted keyfile as common varname $encKey = $multiEncrypted['data']; // Save keyfile for newly encrypted file in parallel directory tree - Keymanager::setFileKey( $rootView, $filePath, $userId, $encKey ); + Keymanager::setFileKey( $view, $filePath, $userId, $encKey ); // Replace plain content with encrypted content by reference $data = $encData; - + // Update the file cache with file info - \OC\Files\Filesystem::putFileInfo( $path, array( 'encrypted'=>true, 'size' => $size ), '' ); - + \OC\Files\Filesystem::putFileInfo( $filePath, array( 'encrypted'=>true, 'size' => strlen($size), 'unencrypted_size' => $size), '' ); + // Re-enable proxy - our work is done \OC_FileProxy::$enabled = $proxyStatus; } } - return true; + return true; + } - + /** * @param string $path Path of file from which has been read * @param string $data Data that has been read from file @@ -273,136 +274,22 @@ class Proxy extends \OC_FileProxy { } /** - * @brief When a file is renamed, rename its keyfile also - * @return bool Result of rename() - * @note This is pre rather than post because using post didn't work - */ - public function preRename( $oldPath, $newPath ) - { - - // Disable encryption proxy to prevent recursive calls - $proxyStatus = \OC_FileProxy::$enabled; - \OC_FileProxy::$enabled = false; - - $view = new \OC_FilesystemView('/'); - - $userId = \OCP\USER::getUser(); - - // Format paths to be relative to user files dir - $oldTrimmed = ltrim($oldPath, '/'); - $oldSplit = explode('/', $oldTrimmed); - $oldSliced = array_slice($oldSplit, 2); - $oldRelPath = implode('/', $oldSliced); - $oldKeyfilePath = $userId . '/' . 'files_encryption' . '/' . 'keyfiles' . '/' . $oldRelPath; - - $newTrimmed = ltrim($newPath, '/'); - $newSplit = explode('/', $newTrimmed); - $newSliced = array_slice($newSplit, 2); - $newRelPath = implode('/', $newSliced); - $newKeyfilePath = $userId . '/' . 'files_encryption' . '/' . 'keyfiles' . '/' . $newRelPath; - - // add key ext if this is not an folder - if (!$view->is_dir($oldKeyfilePath)) { - $oldKeyfilePath .= '.key'; - $newKeyfilePath .= '.key'; - - // handle share-keys - $localKeyPath = $view->getLocalFile($userId.'/files_encryption/share-keys/'.$oldRelPath); - $matches = glob(preg_quote($localKeyPath).'*.shareKey'); - foreach ($matches as $src) { - $dst = str_replace($oldRelPath, $newRelPath, $src); - rename($src, $dst); - } - - } else { - // handle share-keys folders - $oldShareKeyfilePath = $userId . '/' . 'files_encryption' . '/' . 'share-keys' . '/' . $oldRelPath; - $newShareKeyfilePath = $userId . '/' . 'files_encryption' . '/' . 'share-keys' . '/' . $newRelPath; - $view->rename($oldShareKeyfilePath, $newShareKeyfilePath); - } - - // Rename keyfile so it isn't orphaned - $result = $view->rename($oldKeyfilePath, $newKeyfilePath); - - \OC_FileProxy::$enabled = $proxyStatus; - - return $result; - - } - - /** * @brief When a file is renamed, rename its keyfile also * @return bool Result of rename() * @note This is pre rather than post because using post didn't work */ - public function postRename( $oldPath, $newPath ) + public function postWrite( $path ) { - - // Disable encryption proxy to prevent recursive calls - $proxyStatus = \OC_FileProxy::$enabled; - \OC_FileProxy::$enabled = false; - - $view = new \OC_FilesystemView('/'); - $session = new Session($view); - $userId = \OCP\User::getUser(); - $util = new Util( $view, $userId ); - - // Reformat path for use with OC_FSV - $newPathSplit = explode( '/', $newPath ); - $newPathRelative = implode( '/', array_slice( $newPathSplit, 3 ) ); - - // get file info from database/cache - //$newFileInfo = \OC\Files\Filesystem::getFileInfo($newPathRelative); - - if ($util->isEncryptedPath($newPath)) { - $cached = $view->getFileInfo($newPath); - $cached['encrypted'] = 1; - - // get the size from filesystem - $size = $view->filesize($newPath); - - // calculate last chunk nr - $lastChunckNr = floor($size / 8192); - - // open stream - $result = fopen('crypt://' . $newPathRelative, "r"); - - if(is_resource($result)) { - // calculate last chunk position - $lastChunckPos = ($lastChunckNr * 8192); - - // seek to end - fseek($result, $lastChunckPos); - - // get the content of the last chunck - $lastChunkContent = fread($result, 8192); - - // calc the real file size with the size of the last chunk - $realSize = (($lastChunckNr * 6126) + strlen($lastChunkContent)); - - // set the size - $cached['unencrypted_size'] = $realSize; - } - - $view->putFileInfo( $newPath, $cached ); - - // get sharing app state - $sharingEnabled = \OCP\Share::isEnabled(); - - // get users - $usersSharing = $util->getSharingUsersArray($sharingEnabled, $newPathRelative); - - // update sharing-keys - $util->setSharedFileKeyfiles($session, $usersSharing, $newPathRelative); - } - - - - - \OC_FileProxy::$enabled = $proxyStatus; + $this->handleFile($path); return true; + } + public function postTouch( $path ) + { + $this->handleFile($path); + + return true; } public function postFopen( $path, &$result ){ @@ -518,25 +405,28 @@ class Proxy extends \OC_FileProxy { return $data; } - public function postStat( $path, $data ) { + public function postStat($path, $data) + { + // check if file is encrypted + if (Crypt::isCatfileContent($path)) { - if ( Crypt::isCatfileContent( $path ) ) { - - $cached = \OC\Files\Filesystem::getFileInfo( $path, '' ); - - $data['size'] = $cached['unencrypted_size']; - - } - - return $data; - } + // get file info from cache + $cached = \OC\Files\Filesystem::getFileInfo($path, ''); - public function postFileSize( $path, $size ) { + // set the real file size + $data['size'] = $cached['unencrypted_size']; + } - $view = new \OC_FilesystemView( '/' ); + return $data; + } + + public function postFileSize($path, $size) + { + + $view = new \OC_FilesystemView('/'); // if path is a folder do nothing - if($view->is_dir($path)) { + if ($view->is_dir($path)) { return $size; } @@ -544,14 +434,65 @@ class Proxy extends \OC_FileProxy { $path_split = explode('/', $path); $path_f = implode('/', array_slice($path_split, 3)); + // if path is empty we cannot resolve anything + if(empty($path_f)) { + return $size; + } + // get file info from database/cache $fileInfo = \OC\Files\Filesystem::getFileInfo($path_f); // if file is encrypted return real file size - if(is_array($fileInfo) && $fileInfo['encrypted'] == 1) { - return $fileInfo['unencrypted_size']; + if (is_array($fileInfo) && $fileInfo['encrypted'] === true) { + $size = $fileInfo['unencrypted_size']; } else { - return $size; + // self healing if file was removed from file cache + if(is_array($fileInfo)) { + $userId = \OCP\User::getUser(); + $util = new Util( $view, $userId ); + $fixSize = $util->getFileSize($path); + if($fixSize > 0) { + $size = $fixSize; + + $fileInfo['encrypted'] = true; + $fileInfo['unencrypted_size'] = $size; + + // put file info + $view->putFileInfo( $path_f, $fileInfo ); + } + } } - } -} + return $size; + } + + public function handleFile($path) { + + // Disable encryption proxy to prevent recursive calls + $proxyStatus = \OC_FileProxy::$enabled; + \OC_FileProxy::$enabled = false; + + $view = new \OC_FilesystemView('/'); + $session = new Session($view); + $userId = \OCP\User::getUser(); + $util = new Util( $view, $userId ); + + // Reformat path for use with OC_FSV + $path_split = explode( '/', $path ); + $path_f = implode( '/', array_slice( $path_split, 3 ) ); + + // only if file is on 'files' folder fix file size and sharing + if($path_split[2] == 'files' && $util->fixFileSize($path)) { + + // get sharing app state + $sharingEnabled = \OCP\Share::isEnabled(); + + // get users + $usersSharing = $util->getSharingUsersArray($sharingEnabled, $path_f); + + // update sharing-keys + $util->setSharedFileKeyfiles($session, $usersSharing, $path_f); + } + + \OC_FileProxy::$enabled = $proxyStatus; + } + } diff --git a/apps/files_encryption/lib/session.php b/apps/files_encryption/lib/session.php index 0c6a7131fd..f02315f95d 100644 --- a/apps/files_encryption/lib/session.php +++ b/apps/files_encryption/lib/session.php @@ -35,43 +35,64 @@ class Session { * * The ownCloud key pair is used to allow public link sharing even if encryption is enabled */ - public function __construct( \OC_FilesystemView $view ) { + public function __construct( $view ) { $this->view = $view; if ( ! $this->view->is_dir( 'owncloud_private_key' ) ) { - $this->view->mkdir('owncloud_private_key'); + $this->view->mkdir( 'owncloud_private_key' ); + + } + + $publicShareKeyId = \OC_Appconfig::getValue('files_encryption', 'publicShareKeyId'); + + if ($publicShareKeyId === null) { + $publicShareKeyId = 'pubShare_'.substr(md5(time()),0,8); + \OC_Appconfig::setValue('files_encryption', 'publicShareKeyId', $publicShareKeyId); } - if ( - ! $this->view->file_exists("/public-keys/owncloud.public.key") - || ! $this->view->file_exists("/owncloud_private_key/owncloud.private.key" ) + ! $this->view->file_exists( "/public-keys/".$publicShareKeyId.".public.key" ) + || ! $this->view->file_exists( "/owncloud_private_key/".$publicShareKeyId.".private.key" ) ) { + + $keypair = Crypt::createKeypair(); - $keypair = Crypt::createKeypair(); - - \OC_FileProxy::$enabled = false; - - // Save public key + // Disable encryption proxy to prevent recursive calls + $proxyStatus = \OC_FileProxy::$enabled; + \OC_FileProxy::$enabled = false; - if (!$view->is_dir('/public-keys')) { - $view->mkdir('/public-keys'); - } - - $this->view->file_put_contents( '/public-keys/owncloud.public.key', $keypair['publicKey'] ); + // Save public key + + if (!$view->is_dir('/public-keys')) { + $view->mkdir('/public-keys'); + } + + $this->view->file_put_contents( '/public-keys/'.$publicShareKeyId.'.public.key', $keypair['publicKey'] ); + + // Encrypt private key empthy passphrase + $encryptedPrivateKey = Crypt::symmetricEncryptFileContent( $keypair['privateKey'], '' ); + + // Save private key + $this->view->file_put_contents( '/owncloud_private_key/'.$publicShareKeyId.'.private.key', $encryptedPrivateKey ); - // Encrypt private key empthy passphrase - $encryptedPrivateKey = Crypt::symmetricEncryptFileContent( $keypair['privateKey'], '' ); - - // Save private key - $this->view->file_put_contents( '/owncloud_private_key/owncloud.private.key', $encryptedPrivateKey ); - - \OC_FileProxy::$enabled = true; + \OC_FileProxy::$enabled = $proxyStatus; } + + if(\OCP\USER::getUser() === false) { + // Disable encryption proxy to prevent recursive calls + $proxyStatus = \OC_FileProxy::$enabled; + \OC_FileProxy::$enabled = false; + + $encryptedKey = $this->view->file_get_contents( '/owncloud_private_key/'.$publicShareKeyId.'.private.key' ); + $privateKey = Crypt::symmetricDecryptFileContent( $encryptedKey, '' ); + $this->setPrivateKey($privateKey); + + \OC_FileProxy::$enabled = $proxyStatus; + } } /** diff --git a/apps/files_encryption/lib/stream.php b/apps/files_encryption/lib/stream.php index 411bcdac92..ab96783508 100644 --- a/apps/files_encryption/lib/stream.php +++ b/apps/files_encryption/lib/stream.php @@ -72,20 +72,20 @@ class Stream { private $rootView; // a fsview object set to '/' public function stream_open( $path, $mode, $options, &$opened_path ) { - - $this->userId = \OCP\User::getUser(); - + if ( ! isset( $this->rootView ) ) { - $this->rootView = new \OC_FilesystemView( '/' ); - } - // Strip identifier text from path, this gives us the path relative to data//files - $this->relPath = str_replace( 'crypt://', '', $path ); + $util = new Util( $this->rootView, \OCP\USER::getUser()); + + $this->userId = $util->getUserId(); + + // Strip identifier text from path, this gives us the path relative to data//files + $this->relPath = \OC\Files\Filesystem::normalizePath(str_replace( 'crypt://', '', $path )); // rawPath is relative to the data directory - $this->rawPath = $this->userId . '/files/' . $this->relPath; + $this->rawPath = $util->getUserFilesDir() . $this->relPath; if ( dirname( $this->rawPath ) == 'streams' @@ -298,7 +298,8 @@ class Stream { // automatically attempted when the file is written to disk - // we are handling that separately here and we don't want to // get into an infinite loop - //\OC_FileProxy::$enabled = false; + $proxyStatus = \OC_FileProxy::$enabled; + \OC_FileProxy::$enabled = false; // Get the length of the unencrypted data that we are handling $length = strlen( $data ); @@ -322,30 +323,7 @@ class Stream { } - // Fetch user's public key - $this->publicKey = Keymanager::getPublicKey( $this->rootView, $this->userId ); - - // Check if OC sharing api is enabled - $sharingEnabled = \OCP\Share::isEnabled(); - - $util = new Util( $this->rootView, $this->userId ); - - // Get all users sharing the file includes current user - $uniqueUserIds = $util->getSharingUsersArray( $sharingEnabled, $this->relPath, $this->userId); - // Fetch public keys for all sharing users - $publicKeys = Keymanager::getPublicKeys( $this->rootView, $uniqueUserIds ); - - // Encrypt enc key for all sharing users - $this->encKeyfiles = Crypt::multiKeyEncrypt( $this->plainKey, $publicKeys ); - - $view = new \OC_FilesystemView( '/' ); - - // Save the new encrypted file key - Keymanager::setFileKey( $this->rootView, $this->relPath, $this->userId, $this->encKeyfiles['data'] ); - - // Save the sharekeys - Keymanager::setShareKeys( $view, $this->relPath, $this->encKeyfiles['keys'] ); // If extra data is left over from the last round, make sure it // is integrated into the next 6126 / 8192 block @@ -437,6 +415,8 @@ class Stream { $this->size = max( $this->size, $pointer + $length ); $this->unencryptedSize += $length; + \OC_FileProxy::$enabled = $proxyStatus; + return $length; } @@ -492,14 +472,59 @@ class Stream { } public function stream_close() { - - $this->flush(); + + $this->flush(); if ( $this->meta['mode']!='r' - and $this->meta['mode']!='rb' + and $this->meta['mode']!='rb' + and $this->size > 0 ) { - \OC\Files\Filesystem::putFileInfo( $this->relPath, array( 'encrypted' => 1, 'size' => $this->size, 'unencrypted_size' => $this->unencryptedSize ), '' ); + // Disable encryption proxy to prevent recursive calls + $proxyStatus = \OC_FileProxy::$enabled; + \OC_FileProxy::$enabled = false; + + // Fetch user's public key + $this->publicKey = Keymanager::getPublicKey( $this->rootView, $this->userId ); + + // Check if OC sharing api is enabled + $sharingEnabled = \OCP\Share::isEnabled(); + + $util = new Util( $this->rootView, $this->userId ); + + // Get all users sharing the file includes current user + $uniqueUserIds = $util->getSharingUsersArray( $sharingEnabled, $this->relPath, $this->userId); + + // Fetch public keys for all sharing users + $publicKeys = Keymanager::getPublicKeys( $this->rootView, $uniqueUserIds ); + + // Encrypt enc key for all sharing users + $this->encKeyfiles = Crypt::multiKeyEncrypt( $this->plainKey, $publicKeys ); + + $view = new \OC_FilesystemView( '/' ); + + // Save the new encrypted file key + Keymanager::setFileKey( $this->rootView, $this->relPath, $this->userId, $this->encKeyfiles['data'] ); + + // Save the sharekeys + Keymanager::setShareKeys( $view, $this->relPath, $this->encKeyfiles['keys'] ); + + // get file info + $fileInfo = $view->getFileInfo($this->rawPath); + if(!is_array($fileInfo)) { + $fileInfo = array(); + } + + // Re-enable proxy - our work is done + \OC_FileProxy::$enabled = $proxyStatus; + + // set encryption data + $fileInfo['encrypted'] = true; + $fileInfo['size'] = $this->size; + $fileInfo['unencrypted_size'] = $this->unencryptedSize; + + // set fileinfo + $view->putFileInfo( $this->rawPath, $fileInfo); } return fclose( $this->handle ); diff --git a/apps/files_encryption/lib/util.php b/apps/files_encryption/lib/util.php index 2198963ce1..19c9cd72a1 100644 --- a/apps/files_encryption/lib/util.php +++ b/apps/files_encryption/lib/util.php @@ -3,8 +3,8 @@ * ownCloud * * @author Sam Tuke, Frank Karlitschek - * @copyright 2012 Sam Tuke samtuke@owncloud.com, - * Frank Karlitschek frank@owncloud.org + * @copyright 2012 Sam Tuke , + * Frank Karlitschek * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE @@ -24,11 +24,8 @@ # Bugs # ---- # Sharing a file to a user without encryption set up will not provide them with access but won't notify the sharer -# Timeouts on first login due to encryption of very large files (fix in progress, as a result streaming is currently broken) # Sharing all files to admin for recovery purposes still in progress # Possibly public links are broken (not tested since last merge of master) -# encryptAll during login mangles paths: /files/files/ -# encryptAll is accessing files via encryption proxy - perhaps proxies should be disabled? # Missing features @@ -91,20 +88,13 @@ class Util { //// TODO: add support for optional recovery in case of lost passphrase / keys //// TODO: add admin optional required long passphrase for users - //// TODO: add UI buttons for encrypt / decrypt everything //// TODO: implement flag system to allow user to specify encryption by folder, subfolder, etc. - // Sharing: - - //// TODO: add support for encrypting to multiple public keys - //// TODO: add support for decrypting to multiple private keys - - // Integration testing: //// TODO: test new encryption with versioning - //// TODO: test new encryption with sharing + //// DONE: test new encryption with sharing //// TODO: test new encryption with proxies @@ -118,38 +108,65 @@ class Util { private $shareKeysPath; // Dir containing env keys for shared files private $publicKeyPath; // Path to user's public key private $privateKeyPath; // Path to user's private key + private $publicShareKeyId; + private $recoveryKeyId; + private $isPublic; public function __construct( \OC_FilesystemView $view, $userId, $client = false ) { - + $this->view = $view; $this->userId = $userId; $this->client = $client; - $this->userDir = '/' . $this->userId; - $this->fileFolderName = 'files'; - $this->userFilesDir = '/' . $this->userId . '/' . $this->fileFolderName; // TODO: Does this need to be user configurable? - $this->publicKeyDir = '/' . 'public-keys'; - $this->encryptionDir = '/' . $this->userId . '/' . 'files_encryption'; - $this->keyfilesPath = $this->encryptionDir . '/' . 'keyfiles'; - $this->shareKeysPath = $this->encryptionDir . '/' . 'share-keys'; - $this->publicKeyPath = $this->publicKeyDir . '/' . $this->userId . '.public.key'; // e.g. data/public-keys/admin.public.key - $this->privateKeyPath = $this->encryptionDir . '/' . $this->userId . '.private.key'; // e.g. data/admin/admin.private.key - + $this->isPublic = false; + + $this->publicShareKeyId = \OC_Appconfig::getValue('files_encryption', 'publicShareKeyId'); + $this->recoveryKeyId = \OC_Appconfig::getValue('files_encryption', 'recoveryKeyId'); + + // if we are anonymous/public + if($this->userId === false) { + $this->userId = $this->publicShareKeyId; + + // only handle for files_sharing app + if($GLOBALS['app'] === 'files_sharing') { + $this->userDir = '/' . $GLOBALS['fileOwner']; + $this->fileFolderName = 'files'; + $this->userFilesDir = '/' . $GLOBALS['fileOwner'] . '/' . $this->fileFolderName; // TODO: Does this need to be user configurable? + $this->publicKeyDir = '/' . 'public-keys'; + $this->encryptionDir = '/' . $GLOBALS['fileOwner'] . '/' . 'files_encryption'; + $this->keyfilesPath = $this->encryptionDir . '/' . 'keyfiles'; + $this->shareKeysPath = $this->encryptionDir . '/' . 'share-keys'; + $this->publicKeyPath = $this->publicKeyDir . '/' . $this->userId . '.public.key'; // e.g. data/public-keys/admin.public.key + $this->privateKeyPath = '/owncloud_private_key/' . $this->userId . '.private.key'; // e.g. data/admin/admin.private.key + $this->isPublic = true; + } + + } else { + $this->userDir = '/' . $this->userId; + $this->fileFolderName = 'files'; + $this->userFilesDir = '/' . $this->userId . '/' . $this->fileFolderName; // TODO: Does this need to be user configurable? + $this->publicKeyDir = '/' . 'public-keys'; + $this->encryptionDir = '/' . $this->userId . '/' . 'files_encryption'; + $this->keyfilesPath = $this->encryptionDir . '/' . 'keyfiles'; + $this->shareKeysPath = $this->encryptionDir . '/' . 'share-keys'; + $this->publicKeyPath = $this->publicKeyDir . '/' . $this->userId . '.public.key'; // e.g. data/public-keys/admin.public.key + $this->privateKeyPath = $this->encryptionDir . '/' . $this->userId . '.private.key'; // e.g. data/admin/admin.private.key + } } public function ready() { if( - !$this->view->file_exists( $this->encryptionDir ) - or !$this->view->file_exists( $this->keyfilesPath ) - or !$this->view->file_exists( $this->shareKeysPath ) - or !$this->view->file_exists( $this->publicKeyPath ) - or !$this->view->file_exists( $this->privateKeyPath ) + ! $this->view->file_exists( $this->encryptionDir ) + or ! $this->view->file_exists( $this->keyfilesPath ) + or ! $this->view->file_exists( $this->shareKeysPath ) + or ! $this->view->file_exists( $this->publicKeyPath ) + or ! $this->view->file_exists( $this->privateKeyPath ) ) { return false; } else { - + return true; } @@ -207,17 +224,32 @@ class Util { } + // If there's no record for this user's encryption preferences + if ( false === $this->recoveryEnabledForUser() ) { + + // create database configuration + $sql = 'INSERT INTO `*PREFIX*encryption` (`uid`,`mode`,`recovery`) VALUES (?,?,?)'; + $args = array( $this->userId, 'server-side', 0); + $query = \OCP\DB::prepare( $sql ); + $query->execute( $args ); + + } + return true; } + + public function getPublicShareKeyId() { + return $this->publicShareKeyId; + } /** * @brief Check whether pwd recovery is enabled for a given user - * @return bool + * @return 1 = yes, 0 = no, false = no record * @note If records are not being returned, check for a hidden space * at the start of the uid in db */ - public function recoveryEnabled() { + public function recoveryEnabledForUser() { $sql = 'SELECT recovery @@ -232,16 +264,25 @@ class Util { $result = $query->execute( $args ); - // Set default in case no records found - $recoveryEnabled = 0; + $recoveryEnabled = array(); while( $row = $result->fetchRow() ) { - $recoveryEnabled = $row['recovery']; + $recoveryEnabled[] = $row['recovery']; } - return $recoveryEnabled; + // If no record is found + if ( empty( $recoveryEnabled ) ) { + + return false; + + // If a record is found + } else { + + return $recoveryEnabled[0]; + + } } @@ -250,20 +291,33 @@ class Util { * @param bool $enabled Whether to enable or disable recovery * @return bool */ - public function setRecovery( $enabled ) { + public function setRecoveryForUser( $enabled ) { - $sql = 'UPDATE - *PREFIX*encryption - SET - recovery = ? - WHERE - uid = ?'; + $recoveryStatus = $this->recoveryEnabledForUser(); + + // If a record for this user already exists, update it + if ( false === $recoveryStatus ) { - // Ensure value is an integer - $enabled = intval( $enabled ); + $sql = 'INSERT INTO `*PREFIX*encryption` + (`uid`,`mode`,`recovery`) + VALUES (?,?,?)'; + + $args = array( $this->userId, 'server-side', $enabled ); - $args = array( $enabled, $this->userId ); - + // Create a new record instead + } else { + + $sql = 'UPDATE + *PREFIX*encryption + SET + recovery = ? + WHERE + uid = ?'; + + $args = array( $enabled, $this->userId ); + + } + $query = \OCP\DB::prepare( $sql ); if ( $query->execute( $args ) ) { @@ -282,7 +336,6 @@ class Util { * @brief Find all files and their encryption status within a directory * @param string $directory The path of the parent directory to search * @return mixed false if 0 found, array on success. Keys: name, path - * @note $directory needs to be a path relative to OC data dir. e.g. * /admin/files NOT /backup OR /home/www/oc/data/admin/files */ @@ -421,26 +474,123 @@ class Util { return $text; } - /** - * @brief Check if a given path identifies an encrypted file - * @return true / false - */ + /** + * @brief Check if a given path identifies an encrypted file + * @return true / false + */ public function isEncryptedPath( $path ) { - // Disable encryption proxy so data retreived is in its + // Disable encryption proxy so data retrieved is in its // original form + $proxyStatus = \OC_FileProxy::$enabled; \OC_FileProxy::$enabled = false; - - $data = $this->view->file_get_contents( $path ); - - \OC_FileProxy::$enabled = true; + + // we only need 24 byte from the last chunk + $data = ''; + $handle = $this->view->fopen( $path, 'r' ); + if(!fseek($handle, -24, SEEK_END)) { + $data = fgets($handle); + } + + // re-enable proxy + \OC_FileProxy::$enabled = $proxyStatus; return Crypt::isCatfileContent( $data ); } + + /** + * @brief get the file size of the unencrypted file + * @param $path absolute path + * @return bool + */ + + public function getFileSize( $path ) { + $result = 0; + + // Disable encryption proxy to prevent recursive calls + $proxyStatus = \OC_FileProxy::$enabled; + \OC_FileProxy::$enabled = false; + + // Reformat path for use with OC_FSV + $pathSplit = explode( '/', $path ); + $pathRelative = implode( '/', array_slice( $pathSplit, 3 ) ); + + if ($pathSplit[2] == 'files' && $this->view->file_exists($path) && $this->isEncryptedPath($path)) { + + // get the size from filesystem + $fullPath = $this->view->getLocalFile($path); + $size = filesize($fullPath); + + // calculate last chunk nr + $lastChunckNr = floor($size / 8192); + + // open stream + $stream = fopen('crypt://' . $pathRelative, "r"); + + if(is_resource($stream)) { + // calculate last chunk position + $lastChunckPos = ($lastChunckNr * 8192); + + // seek to end + fseek($stream, $lastChunckPos); + + // get the content of the last chunk + $lastChunkContent = fread($stream, 8192); + + // calc the real file size with the size of the last chunk + $realSize = (($lastChunckNr * 6126) + strlen($lastChunkContent)); + + // store file size + $result = $realSize; + } + } + + \OC_FileProxy::$enabled = $proxyStatus; + + return $result; + } + + /** + * @brief fix the file size of the encrypted file + * @param $path absolute path + * @return true / false if file is encrypted + */ + + public function fixFileSize( $path ) { + + $result = false; + + // Disable encryption proxy to prevent recursive calls + $proxyStatus = \OC_FileProxy::$enabled; + \OC_FileProxy::$enabled = false; + + $realSize = $this->getFileSize( $path ); + + if ( $realSize > 0 ) { + + $cached = $this->view->getFileInfo( $path ); + $cached['encrypted'] = true; + + // set the size + $cached['unencrypted_size'] = $realSize; + + // put file info + $this->view->putFileInfo( $path, $cached ); + + $result = true; + + } + + \OC_FileProxy::$enabled = $proxyStatus; + + return $result; + } + /** * @brief Format a path to be relative to the /user/files/ directory + * @note e.g. turns '/admin/files/test.txt' into 'test.txt' */ public function stripUserFilesPath( $path ) { @@ -453,6 +603,21 @@ class Util { } + /** + * @brief Format a path to be relative to the /user directory + * @note e.g. turns '/admin/files/test.txt' into 'files/test.txt' + */ + public function stripFilesPath( $path ) { + + $trimmed = ltrim( $path, '/' ); + $split = explode( '/', $trimmed ); + $sliced = array_slice( $split, 1 ); + $relPath = implode( '/', $sliced ); + + return $relPath; + + } + /** * @brief Format a shared path to be relative to the /user/files/ directory * @note Expects a path like /uid/files/Shared/filepath @@ -517,7 +682,7 @@ class Util { stream_copy_to_stream( $plainHandle1, $plainHandle2 ); // Close access to original file -// $this->view->fclose( $plainHandle1 ); // not implemented in view{} + // $this->view->fclose( $plainHandle1 ); // not implemented in view{} // Delete original plain file so we can rename enc file later $this->view->unlink( $rawPath ); @@ -653,7 +818,7 @@ class Util { * @return multi-dimensional array. keys: ready, unready */ public function filterShareReadyUsers( $unfilteredUsers ) { - + // This array will collect the filtered IDs $readyIds = $unreadyIds = array(); @@ -665,8 +830,9 @@ class Util { // Check that the user is encryption capable, or is the // public system user 'ownCloud' (for public shares) if ( - $util->ready() - or $user == 'owncloud' + $user == $this->publicShareKeyId + or $user == $this->recoveryKeyId + or $util->ready() ) { // Construct array of ready UIDs for Keymanager{} @@ -738,25 +904,27 @@ class Util { * @brief Encrypt keyfile to multiple users * @param array $users list of users which should be able to access the file * @param string $filePath path of the file to be shared + * @return bool */ public function setSharedFileKeyfiles( Session $session, array $users, $filePath ) { - + // Make sure users are capable of sharing $filteredUids = $this->filterShareReadyUsers( $users ); -// trigger_error( print_r($filteredUids, 1) ); - + // If we're attempting to share to unready users if ( ! empty( $filteredUids['unready'] ) ) { - - // Notify user of unready userDir - // TODO: Move this out of here; it belongs somewhere else - \OCP\JSON::error(); + + \OC_Log::write( 'Encryption library', 'Sharing to these user(s) failed as they are unready for encryption:"'.print_r( $filteredUids['unready'], 1 ), \OC_Log::WARN ); + + return false; } // Get public keys for each user, ready for generating sharekeys $userPubKeys = Keymanager::getPublicKeys( $this->view, $filteredUids['ready'] ); - + + // Note proxy status then disable it + $proxyStatus = \OC_FileProxy::$enabled; \OC_FileProxy::$enabled = false; // Get the current users's private key for decrypting existing keyfile @@ -772,21 +940,19 @@ class Util { // Save the recrypted key to it's owner's keyfiles directory // Save new sharekeys to all necessary user directory - // TODO: Reuse the keyfile, it it exists, instead of making a new one if ( ! Keymanager::setFileKey( $this->view, $filePath, $fileOwner, $multiEncKey['data'] ) || ! Keymanager::setShareKeys( $this->view, $filePath, $multiEncKey['keys'] ) ) { - trigger_error( "SET Share keys failed" ); + \OC_Log::write( 'Encryption library', 'Keyfiles could not be saved for users sharing ' . $filePath, \OC_Log::ERROR ); + + return false; } - - // Delete existing keyfile - // Do this last to ensure file is recoverable in case of error - // Keymanager::deleteFileKey( $this->view, $this->userId, $params['fileTarget'] ); - - \OC_FileProxy::$enabled = true; + + // Return proxy to original status + \OC_FileProxy::$enabled = $proxyStatus; return true; } @@ -798,33 +964,51 @@ class Util { public function getSharingUsersArray( $sharingEnabled, $filePath, $currentUserId = false ) { // Check if key recovery is enabled - $recoveryEnabled = $this->recoveryEnabled(); + if ( + \OC_Appconfig::getValue( 'files_encryption', 'recoveryAdminEnabled' ) + && $this->recoveryEnabledForUser() + ) { + + $recoveryEnabled = true; + + } else { + + $recoveryEnabled = false; + + } // Make sure that a share key is generated for the owner too - list($owner, $ownerPath) = $this->getUidAndFilename($filePath); + list( $owner, $ownerPath ) = $this->getUidAndFilename( $filePath ); if ( $sharingEnabled ) { // Find out who, if anyone, is sharing the file - $userIds = \OCP\Share::getUsersSharingFile( $ownerPath, $owner,true, true, true ); + $result = \OCP\Share::getUsersSharingFile( $ownerPath, $owner,true, true, true ); + $userIds = $result['users']; + if ( $result['public'] ) { + $userIds[] = $this->publicShareKeyId; + } } // If recovery is enabled, add the // Admin UID to list of users to share to if ( $recoveryEnabled ) { - - // FIXME: Create a separate admin user purely for recovery, and create method in util for fetching this id from DB? - $adminUid = 'recoveryAdmin'; - - $userIds[] = $adminUid; + + // Find recoveryAdmin user ID + $recoveryKeyId = \OC_Appconfig::getValue( 'files_encryption', 'recoveryKeyId' ); + + // Add recoveryAdmin to list of users sharing + $userIds[] = $recoveryKeyId; } - // add current user if given - if($currentUserId != false) { - $userIds[] = $currentUserId; - } + // add current user if given + if ( $currentUserId != false ) { + + $userIds[] = $currentUserId; + + } // Remove duplicate UIDs $uniqueUserIds = array_unique ( $userIds ); @@ -832,6 +1016,78 @@ class Util { return $uniqueUserIds; } + + /** + * @brief Set file migration status for user + * @return bool + */ + public function setMigrationStatus( $status ) { + + $sql = 'UPDATE + *PREFIX*encryption + SET + migrationStatus = ? + WHERE + uid = ?'; + + $args = array( $status, $this->userId ); + + $query = \OCP\DB::prepare( $sql ); + + if ( $query->execute( $args ) ) { + + return true; + + } else { + + return false; + + } + + } + + /** + * @brief Check whether pwd recovery is enabled for a given user + * @return 1 = yes, 0 = no, false = no record + * @note If records are not being returned, check for a hidden space + * at the start of the uid in db + */ + public function getMigrationStatus() { + + $sql = 'SELECT + migrationStatus + FROM + `*PREFIX*encryption` + WHERE + uid = ?'; + + $args = array( $this->userId ); + + $query = \OCP\DB::prepare( $sql ); + + $result = $query->execute( $args ); + + $migrationStatus = array(); + + while( $row = $result->fetchRow() ) { + + $migrationStatus[] = $row['migrationStatus']; + + } + + // If no record is found + if ( empty( $migrationStatus ) ) { + + return false; + + // If a record is found + } else { + + return $migrationStatus[0]; + + } + + } /** * @brief get uid of the owners of the file and the path to the file @@ -842,47 +1098,56 @@ class Util { */ public function getUidAndFilename( $path ) { - $fileOwnerUid = \OC\Files\Filesystem::getOwner( $path ); - - // Check that UID is valid - if ( ! \OCP\User::userExists( $fileOwnerUid ) ) { - - throw new \Exception( 'Could not find owner (UID = "' . var_export( $fileOwnerUid, 1 ) . '") of file "' . $path . '"' ); - - } + $view = new \OC\Files\View($this->userFilesDir); + $fileOwnerUid = $view->getOwner( $path ); + + // handle public access + if($fileOwnerUid === false && $this->isPublic) { + $filename = $path; + $fileOwnerUid = $GLOBALS['fileOwner']; + + return array ( $fileOwnerUid, $filename ); + } else { + + // Check that UID is valid + if ( ! \OCP\User::userExists( $fileOwnerUid ) ) { + throw new \Exception( 'Could not find owner (UID = "' . var_export( $fileOwnerUid, 1 ) . '") of file "' . $path . '"' ); + } + + // NOTE: Bah, this dependency should be elsewhere + \OC\Files\Filesystem::initMountPoints( $fileOwnerUid ); + + // If the file owner is the currently logged in user + if ( $fileOwnerUid == $this->userId ) { + + // Assume the path supplied is correct + $filename = $path; + + } else { + + $info = $view->getFileInfo( $path ); + $ownerView = new \OC\Files\View( '/' . $fileOwnerUid . '/files' ); + + // Fetch real file path from DB + $filename = $ownerView->getPath( $info['fileid'] ); // TODO: Check that this returns a path without including the user data dir + + } + + // Make path relative for use by $view + $relpath = \OC\Files\Filesystem::normalizePath($fileOwnerUid . '/' . $this->fileFolderName . '/' . $filename); + + // Check that the filename we're using is working + if ( $this->view->file_exists( $relpath ) ) { + + return array ( $fileOwnerUid, $filename ); + + } else { + + return false; + + } + } - // NOTE: Bah, this dependency should be elsewhere - \OC\Files\Filesystem::initMountPoints( $fileOwnerUid ); - - // If the file owner is the currently logged in user - if ( $fileOwnerUid == $this->userId ) { - - // Assume the path supplied is correct - $filename = $path; - - } else { - - $info = \OC\Files\Filesystem::getFileInfo( $path ); - $ownerView = new \OC\Files\View( '/' . $fileOwnerUid . '/files' ); - - // Fetch real file path from DB - $filename = $ownerView->getPath( $info['fileid'] ); // TODO: Check that this returns a path without including the user data dir - - } - - // Make path relative for use by $view - $relpath = $fileOwnerUid . '/' . $this->fileFolderName . '/' . $filename; - - // Check that the filename we're using is working - if ( $this->view->file_exists( $relpath ) ) { - - return array ( $fileOwnerUid, $filename ); - - } else { - - return false; - - } } @@ -891,19 +1156,130 @@ class Util { * @param type $dir relative to the users files folder * @return array with list of files relative to the users files folder */ - public function getAllFiles($dir) { + public function getAllFiles( $dir ) { + $result = array(); - - $content = $this->view->getDirectoryContent($this->userFilesDir.$dir); - foreach ($content as $c) { + $content = $this->view->getDirectoryContent( $this->userFilesDir . $dir ); + + // handling for re shared folders + $path_split = explode( '/', $dir ); + $shared = ''; + + if( $path_split[1] === 'Shared' ) { + + $shared = '/Shared'; + + } + + foreach ( $content as $c ) { + + $sharedPart = $path_split[sizeof( $path_split )-1]; + $targetPathSplit = array_reverse( explode( '/', $c['path'] ) ); + + $path = ''; + + // rebuild path + foreach ( $targetPathSplit as $pathPart ) { + + if ( $pathPart !== $sharedPart ) { + + $path = '/' . $pathPart . $path; + + } else { + + break; + + } + + } + + $path = $dir.$path; + if ($c['type'] === "dir" ) { - $result = array_merge($result, $this->getAllFiles(substr($c['path'],5))); + + $result = array_merge( $result, $this->getAllFiles( $path ) ); + } else { - $result[] = substr($c['path'], 5); + + $result[] = $path; + } } + return $result; + } + /** + * @brief get shares parent. + * @param int $id of the current share + * @return array of the parent + */ + public static function getShareParent( $id ) { + + $query = \OC_DB::prepare( 'SELECT `file_target`, `item_type`' + .' FROM `*PREFIX*share`' + .' WHERE `id` = ?' ); + + $result = $query->execute( array( $id ) ); + + $row = $result->fetchRow(); + + return $row; + + } + + /** + * @brief get owner of the shared files. + * @param int $Id of a share + * @return owner + */ + public function getOwnerFromSharedFile( $id ) { + + $query = \OC_DB::prepare( 'SELECT `parent`, `uid_owner` FROM `*PREFIX*share` WHERE `id` = ?', 1 ); + $source = $query->execute( array( $id ) )->fetchRow(); + + if ( isset($source['parent'] ) ) { + + $parent = $source['parent']; + + while ( isset( $parent ) ) { + + $query = \OC_DB::prepare( 'SELECT `parent`, `uid_owner` FROM `*PREFIX*share` WHERE `id` = ?', 1 ); + $item = $query->execute( array( $parent ) )->fetchRow(); + + if ( isset( $item['parent'] ) ) { + + $parent = $item['parent']; + + } else { + + $fileOwner = $item['uid_owner']; + + break; + + } + } + + } else { + + $fileOwner = $source['uid_owner']; + + } + + return $fileOwner; + + } + + public function getUserId() + { + return $this->userId; + } + + public function getUserFilesDir() + { + return $this->userFilesDir; + } + } diff --git a/apps/files_encryption/settings.php b/apps/files_encryption/settings-admin.php similarity index 61% rename from apps/files_encryption/settings.php rename to apps/files_encryption/settings-admin.php index 71d47f061a..ae9a85643e 100644 --- a/apps/files_encryption/settings.php +++ b/apps/files_encryption/settings-admin.php @@ -8,20 +8,22 @@ \OC_Util::checkAdminUser(); -$tmpl = new OCP\Template( 'files_encryption', 'settings' ); +$tmpl = new OCP\Template( 'files_encryption', 'settings-admin' ); $blackList = explode( ',', \OCP\Config::getAppValue( 'files_encryption', 'type_blacklist', '' ) ); // Check if an adminRecovery account is enabled for recovering files after lost pwd $view = new OC_FilesystemView( '' ); -$util = new \OCA\Encryption\Util( $view, \OCP\USER::getUser() ); -$recoveryEnabled = $util->recoveryEnabled(); + +$recoveryAdminEnabled = OC_Appconfig::getValue( 'files_encryption', 'recoveryAdminEnabled' ); +$recoveryAdminUid = OC_Appconfig::getValue( 'files_encryption', 'recoveryAdminUid' ); $tmpl->assign( 'blacklist', $blackList ); $tmpl->assign( 'encryption_mode', \OC_Appconfig::getValue( 'files_encryption', 'mode', 'none' ) ); -$tmpl->assign( 'recoveryEnabled', $recoveryEnabled ); +$tmpl->assign( 'recoveryEnabled', $recoveryAdminEnabled ); +$tmpl->assign( 'recoveryAdminUid', $recoveryAdminUid ); -\OCP\Util::addscript( 'files_encryption', 'settings' ); +\OCP\Util::addscript( 'files_encryption', 'settings-admin' ); \OCP\Util::addscript( 'core', 'multiselect' ); return $tmpl->fetchPage(); diff --git a/apps/files_encryption/settings-personal.php b/apps/files_encryption/settings-personal.php index c001bb0d72..46efb61b02 100644 --- a/apps/files_encryption/settings-personal.php +++ b/apps/files_encryption/settings-personal.php @@ -6,10 +6,35 @@ * See the COPYING-README file. */ +// Add CSS stylesheet +\OC_Util::addStyle( 'files_encryption', 'settings-personal' ); + $tmpl = new OCP\Template( 'files_encryption', 'settings-personal'); $blackList = explode( ',', \OCP\Config::getAppValue( 'files_encryption', 'type_blacklist', '' ) ); +// Add human readable message in case nothing is blacklisted +if ( + 1 == count( $blackList ) + && $blackList[0] == '' +) { + + // FIXME: Make this string translatable + $blackList[0] = "(None - all filetypes will be encrypted)"; + +} + +$user = \OCP\USER::getUser(); +$view = new \OC_FilesystemView( '/' ); +$util = new \OCA\Encryption\Util( $view, $user ); + +$recoveryAdminEnabled = OC_Appconfig::getValue( 'files_encryption', 'recoveryAdminEnabled' ); +$recoveryEnabledForUser = $util->recoveryEnabledForUser(); + +\OCP\Util::addscript( 'files_encryption', 'settings-personal' ); + +$tmpl->assign( 'recoveryEnabled', $recoveryAdminEnabled ); +$tmpl->assign( 'recoveryEnabledForUser', $recoveryEnabledForUser ); $tmpl->assign( 'blacklist', $blackList ); return $tmpl->fetchPage(); diff --git a/apps/files_encryption/templates/settings.php b/apps/files_encryption/templates/settings-admin.php similarity index 54% rename from apps/files_encryption/templates/settings.php rename to apps/files_encryption/templates/settings-admin.php index 6499d0c8e8..be7beecf69 100644 --- a/apps/files_encryption/templates/settings.php +++ b/apps/files_encryption/templates/settings-admin.php @@ -4,22 +4,16 @@

t( 'Encryption' )); ?>
- - t( "Exclude the following file types from encryption:" )); ?> -
- -

- t( "Enable encryption passwords recovery account (allow sharing to recovery account):" )); ?> + t( "Enable encryption passwords recovery key (allow sharing to recovery key):" )); ?>
+
+ + + +
+

- t( 'Encryption' )); ?> + t( 'Encryption' ) ); ?> +

- t( 'File encryption is enabled.' )); ?> +

- t( 'The following file types will not be encrypted:' )); ?> + File types +
+ t( 'The following file types will not be encrypted:' ) ); ?>

+
  • @@ -18,5 +22,43 @@
+
+ +

+ +
+ t( "Enabling this option will allow you to reobtain access to your encrypted files if your password is lost" ) ); ?> +
+ /> + t( "Enabled" ) ); ?> +
+ + /> + t( "Disabled" ) ); ?> +

t( 'File recovery settings updated' ) ); ?>
+
t( 'Could not update file recovery' ) ); ?>
+

+ +
+

+ +
+ t( "Use this if you suspect that you still have files which are unencrypted, or encrypted using ownCloud 4 or older." ) ); ?> +
+ + + +

t( 'Scan complete' ) );?>
+
t( 'Unable to scan and encrypt files' ) );?>
+

+
diff --git a/apps/files_encryption/test/binary b/apps/files_encryption/tests/binary similarity index 100% rename from apps/files_encryption/test/binary rename to apps/files_encryption/tests/binary diff --git a/apps/files_encryption/test/crypt.php b/apps/files_encryption/tests/crypt.php similarity index 75% rename from apps/files_encryption/test/crypt.php rename to apps/files_encryption/tests/crypt.php index b02e63b2ff..de7ae38b17 100755 --- a/apps/files_encryption/test/crypt.php +++ b/apps/files_encryption/tests/crypt.php @@ -15,13 +15,13 @@ require_once realpath( dirname(__FILE__).'/../lib/keymanager.php' ); require_once realpath( dirname(__FILE__).'/../lib/proxy.php' ); require_once realpath( dirname(__FILE__).'/../lib/stream.php' ); require_once realpath( dirname(__FILE__).'/../lib/util.php' ); +require_once realpath( dirname(__FILE__).'/../lib/helper.php' ); require_once realpath( dirname(__FILE__).'/../appinfo/app.php' ); use OCA\Encryption; // This has to go here because otherwise session errors arise, and the private // encryption key needs to be saved in the session -\OC_User::login( 'admin', 'admin' ); /** * @note It would be better to use Mockery here for mocking out the session @@ -34,8 +34,11 @@ use OCA\Encryption; class Test_Crypt extends \PHPUnit_Framework_TestCase { function setUp() { - - // set content for encrypting / decrypting in tests + // reset backend + \OC_User::clearBackends(); + \OC_User::useBackend('database'); + + // set content for encrypting / decrypting in tests $this->dataLong = file_get_contents( realpath( dirname(__FILE__).'/../lib/crypt.php' ) ); $this->dataShort = 'hats'; $this->dataUrl = realpath( dirname(__FILE__).'/../lib/crypt.php' ); @@ -52,17 +55,32 @@ class Test_Crypt extends \PHPUnit_Framework_TestCase { \OC_User::setUserId( 'admin' ); $this->userId = 'admin'; $this->pass = 'admin'; - - \OC_Filesystem::init( '/' ); - \OC_Filesystem::mount( 'OC_Filestorage_Local', array('datadir' => \OC_User::getHome($this->userId)), '/' ); - + + $userHome = \OC_User::getHome($this->userId); + $this->dataDir = str_replace('/'.$this->userId, '', $userHome); + + // Filesystem related hooks + \OCA\Encryption\Helper::registerFilesystemHooks(); + + \OC_FileProxy::register(new OCA\Encryption\Proxy()); + + \OC_Util::tearDownFS(); + \OC_User::setUserId(''); + \OC\Files\Filesystem::setView(false); + \OC_Util::setupFS($this->userId); + \OC_User::setUserId($this->userId); + + $params['uid'] = $this->userId; + $params['password'] = $this->pass; + OCA\Encryption\Hooks::login($params); + } function tearDown() { - - } - function testGenerateKey() { + } + + function testGenerateKey() { # TODO: use more accurate (larger) string length for test confirmation @@ -220,40 +238,53 @@ class Test_Crypt extends \PHPUnit_Framework_TestCase { // // } - function testSymmetricStreamEncryptShortFileContent() { - - $filename = 'tmp-'.time(); + function testSymmetricStreamEncryptShortFileContent() { + $filename = 'tmp-'.time().'.test'; + $cryptedFile = file_put_contents( 'crypt://' . $filename, $this->dataShort ); // Test that data was successfully written $this->assertTrue( is_int( $cryptedFile ) ); - - - // Get file contents without using any wrapper to get it's actual contents on disk - $retreivedCryptedFile = $this->view->file_get_contents( $this->userId . '/files/' . $filename ); - + + // Disable encryption proxy to prevent recursive calls + $proxyStatus = \OC_FileProxy::$enabled; + \OC_FileProxy::$enabled = false; + + // Get file contents without using any wrapper to get it's actual contents on disk + $retreivedCryptedFile = $this->view->file_get_contents($this->userId . '/files/' . $filename); + + // Re-enable proxy - our work is done + \OC_FileProxy::$enabled = $proxyStatus; + // Check that the file was encrypted before being written to disk $this->assertNotEquals( $this->dataShort, $retreivedCryptedFile ); - - // Get private key - $encryptedPrivateKey = Encryption\Keymanager::getPrivateKey( $this->view, $this->userId ); - - $decryptedPrivateKey = Encryption\Crypt::symmetricDecryptFileContent( $encryptedPrivateKey, $this->pass ); - - - // Get keyfile - $encryptedKeyfile = Encryption\Keymanager::getFileKey( $this->view, $this->userId, $filename ); - - $decryptedKeyfile = Encryption\Crypt::keyDecrypt( $encryptedKeyfile, $decryptedPrivateKey ); - - - // Manually decrypt - $manualDecrypt = Encryption\Crypt::symmetricBlockDecryptFileContent( $retreivedCryptedFile, $decryptedKeyfile ); - + + // Get the encrypted keyfile + $encKeyfile = Encryption\Keymanager::getFileKey( $this->view, $this->userId, $filename ); + + // Attempt to fetch the user's shareKey + $shareKey = Encryption\Keymanager::getShareKey( $this->view, $this->userId, $filename ); + + // get session + $session = new Encryption\Session( $this->view ); + + // get private key + $privateKey = $session->getPrivateKey( $this->userId ); + + // Decrypt keyfile with shareKey + $plainKeyfile = Encryption\Crypt::multiKeyDecrypt( $encKeyfile, $shareKey, $privateKey ); + + // Manually decrypt + $manualDecrypt = Encryption\Crypt::symmetricDecryptFileContent( $retreivedCryptedFile, $plainKeyfile ); + // Check that decrypted data matches $this->assertEquals( $this->dataShort, $manualDecrypt ); - + + // Teardown + $this->view->unlink( $this->userId . '/files/' . $filename ); + + Encryption\Keymanager::deleteFileKey( $this->view, $this->userId, $filename ); } /** @@ -265,7 +296,7 @@ class Test_Crypt extends \PHPUnit_Framework_TestCase { function testSymmetricStreamEncryptLongFileContent() { // Generate a a random filename - $filename = 'tmp-'.time(); + $filename = 'tmp-'.time().'.test'; // Save long data as encrypted file using stream wrapper $cryptedFile = file_put_contents( 'crypt://' . $filename, $this->dataLong.$this->dataLong ); @@ -273,12 +304,18 @@ class Test_Crypt extends \PHPUnit_Framework_TestCase { // Test that data was successfully written $this->assertTrue( is_int( $cryptedFile ) ); - // Get file contents without using any wrapper to get it's actual contents on disk - $retreivedCryptedFile = $this->view->file_get_contents( $this->userId . '/files/' . $filename ); - -// echo "\n\n\$retreivedCryptedFile = $retreivedCryptedFile\n\n"; - - // Check that the file was encrypted before being written to disk + // Disable encryption proxy to prevent recursive calls + $proxyStatus = \OC_FileProxy::$enabled; + \OC_FileProxy::$enabled = false; + + // Get file contents without using any wrapper to get it's actual contents on disk + $retreivedCryptedFile = $this->view->file_get_contents($this->userId . '/files/' . $filename); + + // Re-enable proxy - our work is done + \OC_FileProxy::$enabled = $proxyStatus; + + + // Check that the file was encrypted before being written to disk $this->assertNotEquals( $this->dataLong.$this->dataLong, $retreivedCryptedFile ); // Manuallly split saved file into separate IVs and encrypted chunks @@ -290,46 +327,42 @@ class Test_Crypt extends \PHPUnit_Framework_TestCase { $e = array( $r[0].$r[1], $r[2].$r[3], $r[4].$r[5], $r[6].$r[7], $r[8].$r[9], $r[10].$r[11], $r[12].$r[13] );//.$r[11], $r[12].$r[13], $r[14] ); //print_r($e); - - - // Get private key - $encryptedPrivateKey = Encryption\Keymanager::getPrivateKey( $this->view, $this->userId ); - - $decryptedPrivateKey = Encryption\Crypt::symmetricDecryptFileContent( $encryptedPrivateKey, $this->pass ); - - - // Get keyfile - $encryptedKeyfile = Encryption\Keymanager::getFileKey( $this->view, $this->userId, $filename ); - - $decryptedKeyfile = Encryption\Crypt::keyDecrypt( $encryptedKeyfile, $decryptedPrivateKey ); - - + + // Get the encrypted keyfile + $encKeyfile = Encryption\Keymanager::getFileKey( $this->view, $this->userId, $filename ); + + // Attempt to fetch the user's shareKey + $shareKey = Encryption\Keymanager::getShareKey( $this->view, $this->userId, $filename ); + + // get session + $session = new Encryption\Session( $this->view ); + + // get private key + $privateKey = $session->getPrivateKey( $this->userId ); + + // Decrypt keyfile with shareKey + $plainKeyfile = Encryption\Crypt::multiKeyDecrypt( $encKeyfile, $shareKey, $privateKey ); + // Set var for reassembling decrypted content $decrypt = ''; // Manually decrypt chunk foreach ($e as $e) { - -// echo "\n\$e = $e"; - $chunkDecrypt = Encryption\Crypt::symmetricDecryptFileContent( $e, $decryptedKeyfile ); + $chunkDecrypt = Encryption\Crypt::symmetricDecryptFileContent( $e, $plainKeyfile ); // Assemble decrypted chunks $decrypt .= $chunkDecrypt; -// echo "\n\$chunkDecrypt = $chunkDecrypt"; - } -// echo "\n\$decrypt = $decrypt"; - $this->assertEquals( $this->dataLong.$this->dataLong, $decrypt ); // Teardown - $this->view->unlink( $filename ); + $this->view->unlink( $this->userId . '/files/' . $filename ); - Encryption\Keymanager::deleteFileKey( $filename ); + Encryption\Keymanager::deleteFileKey( $this->view, $this->userId, $filename ); } @@ -345,15 +378,14 @@ class Test_Crypt extends \PHPUnit_Framework_TestCase { // Test that data was successfully written $this->assertTrue( is_int( $cryptedFile ) ); - - - // Get file contents without using any wrapper to get it's actual contents on disk - $retreivedCryptedFile = $this->view->file_get_contents( $this->userId . '/files/' . $filename ); - - $decrypt = file_get_contents( 'crypt://' . $filename ); + + // Get file decrypted contents + $decrypt = file_get_contents( 'crypt://' . $filename ); $this->assertEquals( $this->dataShort, $decrypt ); - + + // tear down + $this->view->unlink( $this->userId . '/files/' . $filename ); } function testSymmetricStreamDecryptLongFileContent() { @@ -365,15 +397,14 @@ class Test_Crypt extends \PHPUnit_Framework_TestCase { // Test that data was successfully written $this->assertTrue( is_int( $cryptedFile ) ); - - - // Get file contents without using any wrapper to get it's actual contents on disk - $retreivedCryptedFile = $this->view->file_get_contents( $this->userId . '/files/' . $filename ); - + + // Get file decrypted contents $decrypt = file_get_contents( 'crypt://' . $filename ); - + $this->assertEquals( $this->dataLong, $decrypt ); - + + // tear down + $this->view->unlink( $this->userId . '/files/' . $filename ); } // Is this test still necessary? @@ -600,6 +631,65 @@ class Test_Crypt extends \PHPUnit_Framework_TestCase { } + function testRenameFile() { + + $filename = 'tmp-'.time(); + + // Save long data as encrypted file using stream wrapper + $cryptedFile = file_put_contents( 'crypt://' . $filename, $this->dataLong ); + + // Test that data was successfully written + $this->assertTrue( is_int( $cryptedFile ) ); + + // Get file decrypted contents + $decrypt = file_get_contents( 'crypt://' . $filename ); + + $this->assertEquals( $this->dataLong, $decrypt ); + + $newFilename = 'tmp-new-'.time(); + $view = new \OC\Files\View('/' . $this->userId . '/files'); + $view->rename( $filename, $newFilename ); + + // Get file decrypted contents + $newDecrypt = file_get_contents( 'crypt://' . $newFilename ); + + $this->assertEquals( $this->dataLong, $newDecrypt ); + + // tear down + $view->unlink( $newFilename ); + } + + function testMoveFileIntoFolder() { + + $filename = 'tmp-'.time(); + + // Save long data as encrypted file using stream wrapper + $cryptedFile = file_put_contents( 'crypt://' . $filename, $this->dataLong ); + + // Test that data was successfully written + $this->assertTrue( is_int( $cryptedFile ) ); + + // Get file decrypted contents + $decrypt = file_get_contents( 'crypt://' . $filename ); + + $this->assertEquals( $this->dataLong, $decrypt ); + + $newFolder = '/newfolder1'; + $newFilename = 'tmp-new-'.time(); + $view = new \OC\Files\View('/' . $this->userId . '/files'); + $view->mkdir($newFolder); + $view->rename( $filename, $newFolder . '/' . $newFilename ); + + // Get file decrypted contents + $newDecrypt = file_get_contents( 'crypt://' . $newFolder . '/' . $newFilename ); + + $this->assertEquals( $this->dataLong, $newDecrypt ); + + // tear down + $view->unlink( $newFolder . '/' . $newFilename ); + $view->unlink( $newFolder ); + } + // function testEncryption(){ // // $key=uniqid(); diff --git a/apps/files_encryption/test/keymanager.php b/apps/files_encryption/tests/keymanager.php similarity index 61% rename from apps/files_encryption/test/keymanager.php rename to apps/files_encryption/tests/keymanager.php index bf453fe316..d24dcaa036 100644 --- a/apps/files_encryption/test/keymanager.php +++ b/apps/files_encryption/tests/keymanager.php @@ -13,18 +13,22 @@ require_once realpath( dirname(__FILE__).'/../lib/keymanager.php' ); require_once realpath( dirname(__FILE__).'/../lib/proxy.php' ); require_once realpath( dirname(__FILE__).'/../lib/stream.php' ); require_once realpath( dirname(__FILE__).'/../lib/util.php' ); +require_once realpath( dirname(__FILE__).'/../lib/helper.php' ); require_once realpath( dirname(__FILE__).'/../appinfo/app.php' ); use OCA\Encryption; // This has to go here because otherwise session errors arise, and the private // encryption key needs to be saved in the session -\OC_User::login( 'admin', 'admin' ); +//\OC_User::login( 'admin', 'admin' ); class Test_Keymanager extends \PHPUnit_Framework_TestCase { function setUp() { - + // reset backend + \OC_User::clearBackends(); + \OC_User::useBackend('database'); + \OC_FileProxy::$enabled = false; // set content for encrypting / decrypting in tests @@ -38,16 +42,30 @@ class Test_Keymanager extends \PHPUnit_Framework_TestCase { $keypair = Encryption\Crypt::createKeypair(); $this->genPublicKey = $keypair['publicKey']; $this->genPrivateKey = $keypair['privateKey']; - - $this->view = new \OC_FilesystemView( '/' ); - - \OC_User::setUserId( 'admin' ); - $this->userId = 'admin'; - $this->pass = 'admin'; - - \OC_Filesystem::init( '/' ); - \OC_Filesystem::mount( 'OC_Filestorage_Local', array('datadir' => \OC_User::getHome($this->userId)), '/' ); - + + $this->view = new \OC_FilesystemView( '/' ); + + \OC_User::setUserId( 'admin' ); + $this->userId = 'admin'; + $this->pass = 'admin'; + + $userHome = \OC_User::getHome($this->userId); + $this->dataDir = str_replace('/'.$this->userId, '', $userHome); + + // Filesystem related hooks + \OCA\Encryption\Helper::registerFilesystemHooks(); + + \OC_FileProxy::register(new OCA\Encryption\Proxy()); + + \OC_Util::tearDownFS(); + \OC_User::setUserId(''); + \OC\Files\Filesystem::setView(false); + \OC_Util::setupFS($this->userId); + \OC_User::setUserId($this->userId); + + $params['uid'] = $this->userId; + $params['password'] = $this->pass; + OCA\Encryption\Hooks::login($params); } function tearDown(){ @@ -59,9 +77,13 @@ class Test_Keymanager extends \PHPUnit_Framework_TestCase { function testGetPrivateKey() { $key = Encryption\Keymanager::getPrivateKey( $this->view, $this->userId ); - + + $privateKey = Encryption\Crypt::symmetricDecryptFileContent( $key, $this->pass); + // Will this length vary? Perhaps we should use a range instead - $this->assertEquals( 2296, strlen( $key ) ); + $this->assertGreaterThan( 27, strlen( $privateKey ) ); + + $this->assertEquals( '-----BEGIN PRIVATE KEY-----', substr( $privateKey, 0, 27 ) ); } @@ -69,7 +91,7 @@ class Test_Keymanager extends \PHPUnit_Framework_TestCase { $key = Encryption\Keymanager::getPublicKey( $this->view, $this->userId ); - $this->assertEquals( 451, strlen( $key ) ); + $this->assertGreaterThan( 26, strlen( $key ) ); $this->assertEquals( '-----BEGIN PUBLIC KEY-----', substr( $key, 0, 26 ) ); } @@ -81,11 +103,19 @@ class Test_Keymanager extends \PHPUnit_Framework_TestCase { $key = Encryption\Crypt::symmetricEncryptFileContentKeyfile( $this->randomKey, 'hat' ); - $path = 'unittest-'.time().'txt'; - + $file = 'unittest-'.time().'.txt'; + + // Disable encryption proxy to prevent recursive calls + $proxyStatus = \OC_FileProxy::$enabled; + \OC_FileProxy::$enabled = false; + + $this->view->file_put_contents($this->userId . '/files/' . $file, $key['encrypted']); + + // Re-enable proxy - our work is done + \OC_FileProxy::$enabled = $proxyStatus; + //$view = new \OC_FilesystemView( '/' . $this->userId . '/files_encryption/keyfiles' ); - - Encryption\Keymanager::setFileKey( $this->view, $path, $this->userId, $key['key'] ); + Encryption\Keymanager::setFileKey( $this->view, $file, $this->userId, $key['key'] ); } @@ -109,9 +139,15 @@ class Test_Keymanager extends \PHPUnit_Framework_TestCase { $keys = Encryption\Keymanager::getUserKeys( $this->view, $this->userId ); - $this->assertEquals( 451, strlen( $keys['publicKey'] ) ); + $this->assertGreaterThan( 26, strlen( $keys['publicKey'] ) ); + $this->assertEquals( '-----BEGIN PUBLIC KEY-----', substr( $keys['publicKey'], 0, 26 ) ); - $this->assertEquals( 2296, strlen( $keys['privateKey'] ) ); + + $privateKey = Encryption\Crypt::symmetricDecryptFileContent( $keys['privateKey'], $this->pass); + + $this->assertGreaterThan( 27, strlen( $keys['privateKey'] ) ); + + $this->assertEquals( '-----BEGIN PRIVATE KEY-----', substr( $privateKey, 0, 27 ) ); } diff --git a/apps/files_encryption/test/legacy-encrypted-text.txt b/apps/files_encryption/tests/legacy-encrypted-text.txt similarity index 100% rename from apps/files_encryption/test/legacy-encrypted-text.txt rename to apps/files_encryption/tests/legacy-encrypted-text.txt diff --git a/apps/files_encryption/test/proxy.php b/apps/files_encryption/tests/proxy.php similarity index 100% rename from apps/files_encryption/test/proxy.php rename to apps/files_encryption/tests/proxy.php diff --git a/apps/files_encryption/tests/share.php b/apps/files_encryption/tests/share.php new file mode 100755 index 0000000000..e2e26aa75b --- /dev/null +++ b/apps/files_encryption/tests/share.php @@ -0,0 +1,473 @@ + + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + * License as published by the Free Software Foundation; either + * version 3 of the License, or any later version. + * + * This library 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 along with this library. If not, see . + * + */ + +require_once realpath(dirname(__FILE__) . '/../../../3rdparty/Crypt_Blowfish/Blowfish.php'); +require_once realpath(dirname(__FILE__) . '/../../../lib/base.php'); +require_once realpath(dirname(__FILE__) . '/../lib/crypt.php'); +require_once realpath(dirname(__FILE__) . '/../lib/keymanager.php'); +require_once realpath(dirname(__FILE__) . '/../lib/proxy.php'); +require_once realpath(dirname(__FILE__) . '/../lib/stream.php'); +require_once realpath(dirname(__FILE__) . '/../lib/util.php'); +require_once realpath(dirname(__FILE__) . '/../lib/helper.php'); +require_once realpath(dirname(__FILE__) . '/../appinfo/app.php'); + +use OCA\Encryption; + +class Test_Encryption_Share extends \PHPUnit_Framework_TestCase +{ + + function setUp() + { + // reset backend + \OC_User::clearBackends(); + \OC_User::useBackend('database'); + + $this->dataShort = 'hats'; + $this->view = new \OC_FilesystemView('/'); + + $userHome = \OC_User::getHome('admin'); + $this->dataDir = str_replace('/admin', '', $userHome); + + $this->folder1 = '/folder1'; + $this->subfolder = '/subfolder1'; + $this->subsubfolder = '/subsubfolder1'; + + $this->filename = 'share-tmp.test'; + + // enable resharing + \OC_Appconfig::setValue('core', 'shareapi_allow_resharing', 'yes'); + + // clear share hooks + \OC_Hook::clear('OCP\\Share'); + \OC::registerShareHooks(); + \OCP\Util::connectHook('OC_Filesystem', 'setup', '\OC\Files\Storage\Shared', 'setup'); + + // Sharing related hooks + \OCA\Encryption\Helper::registerShareHooks(); + + // Filesystem related hooks + \OCA\Encryption\Helper::registerFilesystemHooks(); + + \OC_FileProxy::register(new OCA\Encryption\Proxy()); + + // remember files_trashbin state + $this->stateFilesTrashbin = OC_App::isEnabled('files_trashbin'); + + // we don't want to tests with app files_trashbin enabled + \OC_App::disable('files_trashbin'); + + // create users + $this->loginHelper('user1', true); + $this->loginHelper('user2', true); + $this->loginHelper('user3', true); + } + + function tearDown() + { + // reset app files_trashbin + if ($this->stateFilesTrashbin) { + OC_App::enable('files_trashbin'); + } else { + OC_App::disable('files_trashbin'); + } + + // cleanup users + \OC_User::deleteUser('user1'); + \OC_User::deleteUser('user2'); + \OC_User::deleteUser('user3'); + } + + function testShareFile($withTeardown = true) + { + // login as admin + $this->loginHelper('admin'); + + // save file with content + $cryptedFile = file_put_contents('crypt://' . $this->filename, $this->dataShort); + + // test that data was successfully written + $this->assertTrue(is_int($cryptedFile)); + + // disable encryption proxy to prevent recursive calls + $proxyStatus = \OC_FileProxy::$enabled; + \OC_FileProxy::$enabled = false; + + // get the file info from previous created file + $fileInfo = $this->view->getFileInfo('/admin/files/' . $this->filename); + + // check if we have a valid file info + $this->assertTrue(is_array($fileInfo)); + + // check if the unencrypted file size is stored + $this->assertGreaterThan(0, $fileInfo['unencrypted_size']); + + // re-enable the file proxy + \OC_FileProxy::$enabled = $proxyStatus; + + // share the file + \OCP\Share::shareItem('file', $fileInfo['fileid'], \OCP\Share::SHARE_TYPE_USER, 'user1', OCP\PERMISSION_ALL); + + // login as admin + $this->loginHelper('admin'); + + // check if share key for user1 exists + $this->assertTrue($this->view->file_exists('/admin/files_encryption/share-keys/' . $this->filename . '.user1.shareKey')); + + // login as user1 + $this->loginHelper('user1'); + + // get file contents + $retrievedCryptedFile = $this->view->file_get_contents('/user1/files/Shared/' . $this->filename); + + // check if data is the same as we previously written + $this->assertEquals($this->dataShort, $retrievedCryptedFile); + + // cleanup + if ($withTeardown) { + + // login as admin + $this->loginHelper('admin'); + + // unshare the file + \OCP\Share::unshare('file', $fileInfo['fileid'], \OCP\Share::SHARE_TYPE_USER, 'user1'); + + // check if share key not exists + $this->assertFalse($this->view->file_exists('/admin/files_encryption/share-keys/' . $this->filename . '.user1.shareKey')); + + // cleanup + $this->view->unlink('/admin/files/' . $this->filename); + + // check if share key not exists + $this->assertFalse($this->view->file_exists('/admin/files_encryption/share-keys/' . $this->filename . '.admin.shareKey')); + } + } + + function testReShareFile($withTeardown = true) + { + $this->testShareFile(false); + + // login as user1 + $this->loginHelper('user1'); + + // get the file info + $fileInfo = $this->view->getFileInfo('/user1/files/Shared/' . $this->filename); + + // share the file with user2 + \OCP\Share::shareItem('file', $fileInfo['fileid'], \OCP\Share::SHARE_TYPE_USER, 'user2', OCP\PERMISSION_ALL); + + // login as admin + $this->loginHelper('admin'); + + // check if share key for user2 exists + $this->assertTrue($this->view->file_exists('/admin/files_encryption/share-keys/' . $this->filename . '.user2.shareKey')); + + // login as user2 + $this->loginHelper('user2'); + + // get file contents + $retrievedCryptedFile = $this->view->file_get_contents('/user2/files/Shared/' . $this->filename); + + // check if data is the same as previously written + $this->assertEquals($this->dataShort, $retrievedCryptedFile); + + // cleanup + if ($withTeardown) { + + // login as user1 + $this->loginHelper('user1'); + + // unshare the file with user2 + \OCP\Share::unshare('file', $fileInfo['fileid'], \OCP\Share::SHARE_TYPE_USER, 'user2'); + + // login as admin + $this->loginHelper('admin'); + + // check if share key not exists + $this->assertFalse($this->view->file_exists('/admin/files_encryption/share-keys/' . $this->filename . '.user2.shareKey')); + + // unshare the file with user1 + \OCP\Share::unshare('file', $fileInfo['fileid'], \OCP\Share::SHARE_TYPE_USER, 'user1'); + + // check if share key not exists + $this->assertFalse($this->view->file_exists('/admin/files_encryption/share-keys/' . $this->filename . '.user1.shareKey')); + + // cleanup + $this->view->unlink('/admin/files/' . $this->filename); + + // check if share key not exists + $this->assertFalse($this->view->file_exists('/admin/files_encryption/share-keys/' . $this->filename . '.admin.shareKey')); + } + } + + function testShareFolder($withTeardown = true) + { + // login as admin + $this->loginHelper('admin'); + + // create folder structure + $this->view->mkdir('/admin/files' . $this->folder1); + $this->view->mkdir('/admin/files' . $this->folder1 . $this->subfolder); + $this->view->mkdir('/admin/files' . $this->folder1 . $this->subfolder . $this->subsubfolder); + + // save file with content + $cryptedFile = file_put_contents('crypt://' . $this->folder1 . $this->subfolder . $this->subsubfolder . '/' . $this->filename, $this->dataShort); + + // test that data was successfully written + $this->assertTrue(is_int($cryptedFile)); + + // disable encryption proxy to prevent recursive calls + $proxyStatus = \OC_FileProxy::$enabled; + \OC_FileProxy::$enabled = false; + + // get the file info from previous created folder + $fileInfo = $this->view->getFileInfo('/admin/files' . $this->folder1); + + // check if we have a valid file info + $this->assertTrue(is_array($fileInfo)); + + // re-enable the file proxy + \OC_FileProxy::$enabled = $proxyStatus; + + // share the folder with user1 + \OCP\Share::shareItem('folder', $fileInfo['fileid'], \OCP\Share::SHARE_TYPE_USER, 'user1', OCP\PERMISSION_ALL); + + // login as admin + $this->loginHelper('admin'); + + // check if share key for user1 exists + $this->assertTrue($this->view->file_exists('/admin/files_encryption/share-keys' . $this->folder1 . $this->subfolder . $this->subsubfolder . '/' . $this->filename . '.user1.shareKey')); + + // login as user1 + $this->loginHelper('user1'); + + // get file contents + $retrievedCryptedFile = $this->view->file_get_contents('/user1/files/Shared' . $this->folder1 . $this->subfolder . $this->subsubfolder . '/' . $this->filename); + + // check if data is the same + $this->assertEquals($this->dataShort, $retrievedCryptedFile); + + // cleanup + if ($withTeardown) { + + // login as admin + $this->loginHelper('admin'); + + // unshare the folder with user1 + \OCP\Share::unshare('folder', $fileInfo['fileid'], \OCP\Share::SHARE_TYPE_USER, 'user1'); + + // check if share key not exists + $this->assertFalse($this->view->file_exists('/admin/files_encryption/share-keys' . $this->folder1 . $this->subfolder . $this->subsubfolder . '/' . $this->filename . '.user1.shareKey')); + + // cleanup + $this->view->unlink('/admin/files' . $this->folder1 . $this->subfolder . $this->subsubfolder . '/' . $this->filename); + + // check if share key not exists + $this->assertFalse($this->view->file_exists('/admin/files_encryption/share-keys' . $this->folder1 . $this->subfolder . $this->subsubfolder . '/' . $this->filename . '.admin.shareKey')); + } + + return $fileInfo; + } + + function testReShareFolder($withTeardown = true) + { + $fileInfoFolder1 = $this->testShareFolder(false); + + // login as user1 + $this->loginHelper('user1'); + + // disable encryption proxy to prevent recursive calls + $proxyStatus = \OC_FileProxy::$enabled; + \OC_FileProxy::$enabled = false; + + // get the file info from previous created folder + $fileInfoSubFolder = $this->view->getFileInfo('/user1/files/Shared' . $this->folder1 . $this->subfolder); + + // check if we have a valid file info + $this->assertTrue(is_array($fileInfoSubFolder)); + + // re-enable the file proxy + \OC_FileProxy::$enabled = $proxyStatus; + + // share the file with user2 + \OCP\Share::shareItem('folder', $fileInfoSubFolder['fileid'], \OCP\Share::SHARE_TYPE_USER, 'user2', OCP\PERMISSION_ALL); + + // login as admin + $this->loginHelper('admin'); + + // check if share key for user2 exists + $this->assertTrue($this->view->file_exists('/admin/files_encryption/share-keys' . $this->folder1 . $this->subfolder . $this->subsubfolder . '/' . $this->filename . '.user2.shareKey')); + + // login as user2 + $this->loginHelper('user2'); + + // get file contents + $retrievedCryptedFile = $this->view->file_get_contents('/user2/files/Shared' . $this->subfolder . $this->subsubfolder . '/' . $this->filename); + + // check if data is the same + $this->assertEquals($this->dataShort, $retrievedCryptedFile); + + // get the file info + $fileInfo = $this->view->getFileInfo('/user2/files/Shared' . $this->subfolder . $this->subsubfolder . '/' . $this->filename); + + // check if we have fileInfos + $this->assertTrue(is_array($fileInfo)); + + // share the file with user3 + \OCP\Share::shareItem('file', $fileInfo['fileid'], \OCP\Share::SHARE_TYPE_USER, 'user3', OCP\PERMISSION_ALL); + + // login as admin + $this->loginHelper('admin'); + + // check if share key for user3 exists + $this->assertTrue($this->view->file_exists('/admin/files_encryption/share-keys' . $this->folder1 . $this->subfolder . $this->subsubfolder . '/' . $this->filename . '.user3.shareKey')); + + // login as user3 + $this->loginHelper('user3'); + + // get file contents + $retrievedCryptedFile = $this->view->file_get_contents('/user3/files/Shared/' . $this->filename); + + // check if data is the same + $this->assertEquals($this->dataShort, $retrievedCryptedFile); + + // cleanup + if ($withTeardown) { + + // login as user2 + $this->loginHelper('user2'); + + // unshare the file with user3 + \OCP\Share::unshare('file', $fileInfo['fileid'], \OCP\Share::SHARE_TYPE_USER, 'user3'); + + // check if share key not exists + $this->assertFalse($this->view->file_exists('/admin/files_encryption/share-keys' . $this->folder1 . $this->subfolder . $this->subsubfolder . '/' . $this->filename . '.user3.shareKey')); + + // login as user1 + $this->loginHelper('user1'); + + // unshare the folder with user2 + \OCP\Share::unshare('folder', $fileInfoSubFolder['fileid'], \OCP\Share::SHARE_TYPE_USER, 'user2'); + + // check if share key not exists + $this->assertFalse($this->view->file_exists('/admin/files_encryption/share-keys' . $this->folder1 . $this->subfolder . $this->subsubfolder . '/' . $this->filename . '.user2.shareKey')); + + // login as admin + $this->loginHelper('admin'); + + // unshare the folder1 with user1 + \OCP\Share::unshare('folder', $fileInfoFolder1['fileid'], \OCP\Share::SHARE_TYPE_USER, 'user1'); + + // check if share key not exists + $this->assertFalse($this->view->file_exists('/admin/files_encryption/share-keys' . $this->folder1 . $this->subfolder . $this->subsubfolder . '/' . $this->filename . '.user1.shareKey')); + + // cleanup + $this->view->unlink('/admin/files' . $this->folder1 . $this->subfolder . $this->subsubfolder . '/' . $this->filename); + + // check if share key not exists + $this->assertFalse($this->view->file_exists('/admin/files_encryption/share-keys' . $this->folder1 . $this->subfolder . $this->subsubfolder . '/' . $this->filename . '.admin.shareKey')); + } + } + + function testPublicShareFile() + { + // login as admin + $this->loginHelper('admin'); + + // save file with content + $cryptedFile = file_put_contents('crypt://' . $this->filename, $this->dataShort); + + // test that data was successfully written + $this->assertTrue(is_int($cryptedFile)); + + // disable encryption proxy to prevent recursive calls + $proxyStatus = \OC_FileProxy::$enabled; + \OC_FileProxy::$enabled = false; + + // get the file info from previous created file + $fileInfo = $this->view->getFileInfo('/admin/files/' . $this->filename); + + // check if we have a valid file info + $this->assertTrue(is_array($fileInfo)); + + // check if the unencrypted file size is stored + $this->assertGreaterThan(0, $fileInfo['unencrypted_size']); + + // re-enable the file proxy + \OC_FileProxy::$enabled = $proxyStatus; + + // share the file + \OCP\Share::shareItem('file', $fileInfo['fileid'], \OCP\Share::SHARE_TYPE_LINK, null, false); + + // login as admin + $this->loginHelper('admin'); + + $publicShareKeyId = \OC_Appconfig::getValue('files_encryption', 'publicShareKeyId'); + + // check if share key for public exists + $this->assertTrue($this->view->file_exists('/admin/files_encryption/share-keys/' . $this->filename . '.' . $publicShareKeyId . '.shareKey')); + + // some hacking to simulate public link + $GLOBALS['app'] = 'files_sharing'; + $GLOBALS['fileOwner'] = 'admin'; + \OC_User::setUserId(''); + + // get file contents + $retrievedCryptedFile = file_get_contents('crypt://' . $this->filename); + + // check if data is the same as we previously written + $this->assertEquals($this->dataShort, $retrievedCryptedFile); + + // tear down + + // login as admin + $this->loginHelper('admin'); + + // unshare the file + \OCP\Share::unshare('file', $fileInfo['fileid'], \OCP\Share::SHARE_TYPE_LINK, null); + + // check if share key not exists + $this->assertFalse($this->view->file_exists('/admin/files_encryption/share-keys/' . $this->filename . '.' . $publicShareKeyId . '.shareKey')); + + // cleanup + $this->view->unlink('/admin/files/' . $this->filename); + + // check if share key not exists + $this->assertFalse($this->view->file_exists('/admin/files_encryption/share-keys/' . $this->filename . '.admin.shareKey')); + } + + function loginHelper($user, $create = false) + { + if ($create) { + \OC_User::createUser($user, $user); + } + + \OC_Util::tearDownFS(); + \OC_User::setUserId(''); + \OC\Files\Filesystem::setView(false); + \OC_Util::setupFS($user); + \OC_User::setUserId($user); + + $params['uid'] = $user; + $params['password'] = $user; + OCA\Encryption\Hooks::login($params); + } +} diff --git a/apps/files_encryption/test/stream.php b/apps/files_encryption/tests/stream.php similarity index 99% rename from apps/files_encryption/test/stream.php rename to apps/files_encryption/tests/stream.php index ba82ac80ea..633cc9e4fc 100644 --- a/apps/files_encryption/test/stream.php +++ b/apps/files_encryption/tests/stream.php @@ -1,4 +1,4 @@ -// // * This file is licensed under the Affero General Public License version 3 or diff --git a/apps/files_encryption/test/util.php b/apps/files_encryption/tests/util.php similarity index 74% rename from apps/files_encryption/test/util.php rename to apps/files_encryption/tests/util.php index 3ebc484809..2abf409690 100755 --- a/apps/files_encryption/test/util.php +++ b/apps/files_encryption/tests/util.php @@ -24,24 +24,23 @@ $loader->register(); use \Mockery as m; use OCA\Encryption; -\OC_User::login( 'admin', 'admin' ); - class Test_Enc_Util extends \PHPUnit_Framework_TestCase { function setUp() { - - \OC_Filesystem::mount( 'OC_Filestorage_Local', array(), '/' ); - - // set content for encrypting / decrypting in tests + // reset backend + \OC_User::useBackend('database'); + + \OC_User::setUserId( 'admin' ); + $this->userId = 'admin'; + $this->pass = 'admin'; + + // set content for encrypting / decrypting in tests $this->dataUrl = realpath( dirname(__FILE__).'/../lib/crypt.php' ); $this->dataShort = 'hats'; $this->dataLong = file_get_contents( realpath( dirname(__FILE__).'/../lib/crypt.php' ) ); $this->legacyData = realpath( dirname(__FILE__).'/legacy-text.txt' ); $this->legacyEncryptedData = realpath( dirname(__FILE__).'/legacy-encrypted-text.txt' ); - - $this->userId = 'admin'; - $this->pass = 'admin'; - + $keypair = Encryption\Crypt::createKeypair(); $this->genPublicKey = $keypair['publicKey']; @@ -52,11 +51,29 @@ class Test_Enc_Util extends \PHPUnit_Framework_TestCase { $this->keyfilesPath = $this->encryptionDir . '/' . 'keyfiles'; $this->publicKeyPath = $this->publicKeyDir . '/' . $this->userId . '.public.key'; // e.g. data/public-keys/admin.public.key $this->privateKeyPath = $this->encryptionDir . '/' . $this->userId . '.private.key'; // e.g. data/admin/admin.private.key - - $this->view = new \OC_FilesystemView( '/' ); - - $this->mockView = m::mock('OC_FilesystemView'); - $this->util = new Encryption\Util( $this->mockView, $this->userId ); + + $this->view = new \OC_FilesystemView( '/' ); + + $userHome = \OC_User::getHome($this->userId); + $this->dataDir = str_replace('/'.$this->userId, '', $userHome); + + // Filesystem related hooks + \OCA\Encryption\Helper::registerFilesystemHooks(); + + \OC_FileProxy::register(new OCA\Encryption\Proxy()); + + \OC_Util::tearDownFS(); + \OC_User::setUserId(''); + \OC\Files\Filesystem::setView(false); + \OC_Util::setupFS($this->userId); + \OC_User::setUserId($this->userId); + + $params['uid'] = $this->userId; + $params['password'] = $this->pass; + OCA\Encryption\Hooks::login($params); + + $mockView = m::mock('OC_FilesystemView'); + $this->util = new Encryption\Util( $mockView, $this->userId ); } @@ -68,6 +85,9 @@ class Test_Enc_Util extends \PHPUnit_Framework_TestCase { /** * @brief test that paths set during User construction are correct + * + * + * */ function testKeyPaths() { @@ -90,8 +110,8 @@ class Test_Enc_Util extends \PHPUnit_Framework_TestCase { $mockView = m::mock('OC_FilesystemView'); - $mockView->shouldReceive( 'file_exists' )->times(5)->andReturn( false ); - $mockView->shouldReceive( 'mkdir' )->times(4)->andReturn( true ); + $mockView->shouldReceive( 'file_exists' )->times(7)->andReturn( false ); + $mockView->shouldReceive( 'mkdir' )->times(6)->andReturn( true ); $mockView->shouldReceive( 'file_put_contents' )->withAnyArgs(); $util = new Encryption\Util( $mockView, $this->userId ); @@ -107,7 +127,7 @@ class Test_Enc_Util extends \PHPUnit_Framework_TestCase { $mockView = m::mock('OC_FilesystemView'); - $mockView->shouldReceive( 'file_exists' )->times(6)->andReturn( true ); + $mockView->shouldReceive( 'file_exists' )->times(8)->andReturn( true ); $mockView->shouldReceive( 'file_put_contents' )->withAnyArgs(); $util = new Encryption\Util( $mockView, $this->userId ); @@ -141,7 +161,7 @@ class Test_Enc_Util extends \PHPUnit_Framework_TestCase { $mockView = m::mock('OC_FilesystemView'); - $mockView->shouldReceive( 'file_exists' )->times(3)->andReturn( true ); + $mockView->shouldReceive( 'file_exists' )->times(5)->andReturn( true ); $util = new Encryption\Util( $mockView, $this->userId ); @@ -158,43 +178,57 @@ class Test_Enc_Util extends \PHPUnit_Framework_TestCase { $util = new Encryption\Util( $this->view, $this->userId ); - $files = $util->findEncFiles( '/', 'encrypted' ); + $files = $util->findEncFiles( '/'.$this->userId.'/'); - var_dump( $files ); + //var_dump( $files ); # TODO: Add more tests here to check that if any of the dirs are # then false will be returned. Use strict ordering? } - function testRecoveryEnabled() { + function testRecoveryEnabledForUser() { $util = new Encryption\Util( $this->view, $this->userId ); // Record the value so we can return it to it's original state later - $enabled = $util->recoveryEnabled(); + $enabled = $util->recoveryEnabledForUser(); - $this->assertTrue( $util->setRecovery( 1 ) ); + $this->assertTrue( $util->setRecoveryForUser( 1 ) ); - $this->assertEquals( 1, $util->recoveryEnabled() ); + $this->assertEquals( 1, $util->recoveryEnabledForUser() ); - $this->assertTrue( $util->setRecovery( 0 ) ); + $this->assertTrue( $util->setRecoveryForUser( 0 ) ); - $this->assertEquals( 0, $util->recoveryEnabled() ); + $this->assertEquals( 0, $util->recoveryEnabledForUser() ); // Return the setting to it's previous state - $this->assertTrue( $util->setRecovery( $enabled ) ); + $this->assertTrue( $util->setRecoveryForUser( $enabled ) ); } function testGetUidAndFilename() { \OC_User::setUserId( 'admin' ); - - $this->util->getUidAndFilename( 'test1.txt' ); - - - + + $filename = 'tmp-'.time().'.test'; + + // Disable encryption proxy to prevent recursive calls + $proxyStatus = \OC_FileProxy::$enabled; + \OC_FileProxy::$enabled = false; + + $this->view->file_put_contents($this->userId . '/files/' . $filename, $this->dataShort); + + // Re-enable proxy - our work is done + \OC_FileProxy::$enabled = $proxyStatus; + + $util = new Encryption\Util( $this->view, $this->userId ); + + list($fileOwnerUid, $file) = $util->getUidAndFilename( $filename ); + + $this->assertEquals('admin', $fileOwnerUid); + + $this->assertEquals($file, $filename); } // /** diff --git a/apps/files_encryption/test/zeros b/apps/files_encryption/tests/zeros similarity index 100% rename from apps/files_encryption/test/zeros rename to apps/files_encryption/tests/zeros diff --git a/apps/files_trashbin/l10n/bg_BG.php b/apps/files_trashbin/l10n/bg_BG.php index 288518e1a4..31c5dcb4ef 100644 --- a/apps/files_trashbin/l10n/bg_BG.php +++ b/apps/files_trashbin/l10n/bg_BG.php @@ -13,6 +13,5 @@ "{count} files" => "{count} файла", "Nothing in here. Your trash bin is empty!" => "Няма нищо. Кофата е празна!", "Restore" => "Възтановяване", -"Delete" => "Изтриване", -"Deleted Files" => "Изтрити файлове" +"Delete" => "Изтриване" ); diff --git a/apps/files_trashbin/l10n/id.php b/apps/files_trashbin/l10n/id.php index 62a63d515a..e06c66784f 100644 --- a/apps/files_trashbin/l10n/id.php +++ b/apps/files_trashbin/l10n/id.php @@ -2,13 +2,13 @@ "Couldn't delete %s permanently" => "Tidak dapat menghapus permanen %s", "Couldn't restore %s" => "Tidak dapat memulihkan %s", "perform restore operation" => "jalankan operasi pemulihan", -"Error" => "Galat", +"Error" => "kesalahan", "delete file permanently" => "hapus berkas secara permanen", -"Delete permanently" => "Hapus secara permanen", +"Delete permanently" => "hapus secara permanen", "Name" => "Nama", "Deleted" => "Dihapus", -"1 folder" => "1 folder", -"{count} folders" => "{count} folder", +"1 folder" => "1 map", +"{count} folders" => "{count} map", "1 file" => "1 berkas", "{count} files" => "{count} berkas", "Nothing in here. Your trash bin is empty!" => "Tempat sampah anda kosong!", diff --git a/apps/files_trashbin/l10n/nn_NO.php b/apps/files_trashbin/l10n/nn_NO.php index 8166a024e5..14345ddcc4 100644 --- a/apps/files_trashbin/l10n/nn_NO.php +++ b/apps/files_trashbin/l10n/nn_NO.php @@ -1,10 +1,5 @@ "Feil", -"Delete permanently" => "Slett for godt", "Name" => "Namn", -"1 folder" => "1 mappe", -"{count} folders" => "{count} mapper", -"1 file" => "1 fil", -"{count} files" => "{count} filer", "Delete" => "Slett" ); diff --git a/apps/files_trashbin/l10n/pl.php b/apps/files_trashbin/l10n/pl.php index 5c9f558f11..7fd1ab21ec 100644 --- a/apps/files_trashbin/l10n/pl.php +++ b/apps/files_trashbin/l10n/pl.php @@ -8,9 +8,9 @@ "Name" => "Nazwa", "Deleted" => "Usunięte", "1 folder" => "1 folder", -"{count} folders" => "Ilość folderów: {count}", +"{count} folders" => "{count} foldery", "1 file" => "1 plik", -"{count} files" => "Ilość plików: {count}", +"{count} files" => "{count} pliki", "Nothing in here. Your trash bin is empty!" => "Nic tu nie ma. Twój kosz jest pusty!", "Restore" => "Przywróć", "Delete" => "Usuń", diff --git a/apps/files_trashbin/l10n/pt_PT.php b/apps/files_trashbin/l10n/pt_PT.php index ba85158b70..7dfe610466 100644 --- a/apps/files_trashbin/l10n/pt_PT.php +++ b/apps/files_trashbin/l10n/pt_PT.php @@ -13,6 +13,6 @@ "{count} files" => "{count} ficheiros", "Nothing in here. Your trash bin is empty!" => "Não hà ficheiros. O lixo está vazio!", "Restore" => "Restaurar", -"Delete" => "Eliminar", +"Delete" => "Apagar", "Deleted Files" => "Ficheiros Apagados" ); diff --git a/apps/files_trashbin/l10n/ro.php b/apps/files_trashbin/l10n/ro.php index 3af21b7e3f..c03ef600f3 100644 --- a/apps/files_trashbin/l10n/ro.php +++ b/apps/files_trashbin/l10n/ro.php @@ -1,6 +1,5 @@ "Eroare", -"Delete permanently" => "Stergere permanenta", "Name" => "Nume", "1 folder" => "1 folder", "{count} folders" => "{count} foldare", diff --git a/apps/files_trashbin/l10n/sk_SK.php b/apps/files_trashbin/l10n/sk_SK.php index 7cef36ef1c..7203f4c75f 100644 --- a/apps/files_trashbin/l10n/sk_SK.php +++ b/apps/files_trashbin/l10n/sk_SK.php @@ -5,7 +5,7 @@ "Error" => "Chyba", "delete file permanently" => "trvalo zmazať súbor", "Delete permanently" => "Zmazať trvalo", -"Name" => "Názov", +"Name" => "Meno", "Deleted" => "Zmazané", "1 folder" => "1 priečinok", "{count} folders" => "{count} priečinkov", diff --git a/apps/files_trashbin/lib/trash.php b/apps/files_trashbin/lib/trash.php index 7fda855d0c..70df9e2426 100644 --- a/apps/files_trashbin/lib/trash.php +++ b/apps/files_trashbin/lib/trash.php @@ -29,6 +29,17 @@ class Trashbin { // unit: percentage; 50% of available disk space/quota const DEFAULTMAXSIZE=50; + public static function getUidAndFilename($filename) { + $uid = \OC\Files\Filesystem::getOwner($filename); + \OC\Files\Filesystem::initMountPoints($uid); + if ( $uid != \OCP\User::getUser() ) { + $info = \OC\Files\Filesystem::getFileInfo($filename); + $ownerView = new \OC\Files\View('/'.$uid.'/files'); + $filename = $ownerView->getPath($info['fileid']); + } + return array($uid, $filename); + } + /** * move file to the trash bin * @@ -62,8 +73,12 @@ class Trashbin { if ( $trashbinSize === false || $trashbinSize < 0 ) { $trashbinSize = self::calculateSize(new \OC\Files\View('/'. $user.'/files_trashbin')); } - + + // disable proxy to prevent recursive calls + $proxyStatus = \OC_FileProxy::$enabled; + \OC_FileProxy::$enabled = false; $sizeOfAddedFiles = self::copy_recursive($file_path, 'files_trashbin/files/'.$filename.'.d'.$timestamp, $view); + \OC_FileProxy::$enabled = $proxyStatus; if ( $view->file_exists('files_trashbin/files/'.$filename.'.d'.$timestamp) ) { $trashbinSize += $sizeOfAddedFiles; @@ -110,13 +125,17 @@ class Trashbin { \OC_FileProxy::$enabled = false; $user = \OCP\User::getUser(); - if ($view->is_dir('files_versions/' . $file_path)) { - $size += self::calculateSize(new \OC\Files\View('/' . $user . '/files_versions/' . $file_path)); - $view->rename('files_versions/' . $file_path, 'files_trashbin/versions/' . $filename . '.d' . $timestamp); - } else if ($versions = \OCA\Files_Versions\Storage::getVersions($user, $file_path)) { + $rootView = new \OC\Files\View('/'); + + list($owner, $ownerPath) = self::getUidAndFilename($file_path); + + if ($rootView->is_dir($owner.'/files_versions/' . $ownerPath)) { + $size += self::calculateSize(new \OC\Files\View('/' . $owner . '/files_versions/' . $ownerPath)); + $rootView->rename($owner.'/files_versions/' . $ownerPath, $user.'/files_trashbin/versions/' . $filename . '.d' . $timestamp); + } else if ($versions = \OCA\Files_Versions\Storage::getVersions($owner, $ownerPath)) { foreach ($versions as $v) { - $size += $view->filesize('files_versions' . $v['path'] . '.v' . $v['version']); - $view->rename('files_versions' . $v['path'] . '.v' . $v['version'], 'files_trashbin/versions/' . $filename . '.v' . $v['version'] . '.d' . $timestamp); + $size += $rootView->filesize($owner.'/files_versions' . $v['path'] . '.v' . $v['version']); + $rootView->rename($owner.'/files_versions' . $v['path'] . '.v' . $v['version'], $user.'/files_trashbin/versions/' . $filename . '.v' . $v['version'] . '.d' . $timestamp); } } @@ -143,35 +162,38 @@ class Trashbin { if (\OCP\App::isEnabled('files_encryption')) { $user = \OCP\User::getUser(); + $rootView = new \OC\Files\View('/'); + + list($owner, $ownerPath) = self::getUidAndFilename($file_path); + // disable proxy to prevent recursive calls $proxyStatus = \OC_FileProxy::$enabled; \OC_FileProxy::$enabled = false; // retain key files - $keyfile = \OC\Files\Filesystem::normalizePath('files_encryption/keyfiles/' . $file_path); + $keyfile = \OC\Files\Filesystem::normalizePath($owner.'/files_encryption/keyfiles/' . $ownerPath); - if ($view->is_dir($keyfile) || $view->file_exists($keyfile . '.key')) { - $user = \OCP\User::getUser(); + if ($rootView->is_dir($keyfile) || $rootView->file_exists($keyfile . '.key')) { // move keyfiles - if ($view->is_dir($keyfile)) { - $size += self::calculateSize(new \OC\Files\View('/' . $user . '/' . $keyfile)); - $view->rename($keyfile, 'files_trashbin/keyfiles/' . $filename . '.d' . $timestamp); + if ($rootView->is_dir($keyfile)) { + $size += self::calculateSize(new \OC\Files\View($keyfile)); + $rootView->rename($keyfile, $user.'/files_trashbin/keyfiles/' . $filename . '.d' . $timestamp); } else { - $size += $view->filesize($keyfile . '.key'); - $view->rename($keyfile . '.key', 'files_trashbin/keyfiles/' . $filename . '.key.d' . $timestamp); + $size += $rootView->filesize($keyfile . '.key'); + $rootView->rename($keyfile . '.key', $user.'/files_trashbin/keyfiles/' . $filename . '.key.d' . $timestamp); } } // retain share keys - $sharekeys = \OC\Files\Filesystem::normalizePath('files_encryption/share-keys/' . $file_path); + $sharekeys = \OC\Files\Filesystem::normalizePath($owner.'/files_encryption/share-keys/' . $ownerPath); - if ($view->is_dir($sharekeys)) { - $size += self::calculateSize(new \OC\Files\View('/' . $user . '/' . $sharekeys)); - $view->rename($sharekeys, 'files_trashbin/share-keys/' . $filename . '.d' . $timestamp); + if ($rootView->is_dir($sharekeys)) { + $size += self::calculateSize(new \OC\Files\View($sharekeys)); + $rootView->rename($sharekeys, $user.'/files_trashbin/share-keys/' . $filename . '.d' . $timestamp); } else { // get local path to share-keys - $localShareKeysPath = $view->getLocalFile($sharekeys); + $localShareKeysPath = $rootView->getLocalFile($sharekeys); // handle share-keys $matches = glob(preg_quote($localShareKeysPath).'*.shareKey'); @@ -186,10 +208,10 @@ class Trashbin { if($pathinfo['basename'] == $ownerShareKey) { // calculate size - $size += $view->filesize($sharekeys. '.' . $user. '.shareKey'); + $size += $rootView->filesize($sharekeys. '.' . $user. '.shareKey'); // move file - $view->rename($sharekeys. '.' . $user. '.shareKey', 'files_trashbin/share-keys/' . $ownerShareKey . '.d' . $timestamp); + $rootView->rename($sharekeys. '.' . $user. '.shareKey', $user.'/files_trashbin/share-keys/' . $ownerShareKey . '.d' . $timestamp); } else { // calculate size @@ -321,6 +343,12 @@ class Trashbin { \OC_FileProxy::$enabled = false; $user = \OCP\User::getUser(); + $rootView = new \OC\Files\View('/'); + + $target = \OC\Files\Filesystem::normalizePath('/'.$location.'/'.$filename.$ext); + + list($owner, $ownerPath) = self::getUidAndFilename($target); + if ($timestamp) { $versionedFile = $filename; } else { @@ -329,15 +357,15 @@ class Trashbin { if ($view->is_dir('/files_trashbin/versions/'.$file)) { $size += self::calculateSize(new \OC\Files\View('/' . $user . '/' . 'files_trashbin/versions/' . $file)); - $view->rename(\OC\Files\Filesystem::normalizePath('files_trashbin/versions/' . $file), \OC\Files\Filesystem::normalizePath('files_versions/' . $location . '/' . $filename . $ext)); + $rootView->rename(\OC\Files\Filesystem::normalizePath($user.'/files_trashbin/versions/' . $file), \OC\Files\Filesystem::normalizePath($owner.'/files_versions/' . $ownerPath)); } else if ($versions = self::getVersionsFromTrash($versionedFile, $timestamp)) { foreach ($versions as $v) { if ($timestamp) { $size += $view->filesize('files_trashbin/versions/' . $versionedFile . '.v' . $v . '.d' . $timestamp); - $view->rename('files_trashbin/versions/' . $versionedFile . '.v' . $v . '.d' . $timestamp, 'files_versions/' . $location . '/' . $filename . $ext . '.v' . $v); + $rootView->rename($user.'/files_trashbin/versions/' . $versionedFile . '.v' . $v . '.d' . $timestamp, $owner.'/files_versions/' . $ownerPath . '.v' . $v); } else { $size += $view->filesize('files_trashbin/versions/' . $versionedFile . '.v' . $v); - $view->rename('files_trashbin/versions/' . $versionedFile . '.v' . $v, 'files_versions/' . $location . '/' . $filename . $ext . '.v' . $v); + $rootView->rename($user.'/files_trashbin/versions/' . $versionedFile . '.v' . $v, $owner.'/files_versions/' . $ownerPath . '.v' . $v); } } } @@ -356,7 +384,7 @@ class Trashbin { * @param $file complete path to file * @param $filename name of file * @param $ext file extension in case a file with the same $filename already exists - * @param $location location if file + * @param $location location of file * @param $timestamp deleteion time * * @return size of restored encrypted file @@ -366,20 +394,25 @@ class Trashbin { $size = 0; if (\OCP\App::isEnabled('files_encryption')) { $user = \OCP\User::getUser(); + $rootView = new \OC\Files\View('/'); + + $target = \OC\Files\Filesystem::normalizePath('/'.$location.'/'.$filename.$ext); + + list($owner, $ownerPath) = self::getUidAndFilename($target); $path_parts = pathinfo($file); $source_location = $path_parts['dirname']; if ($view->is_dir('/files_trashbin/keyfiles/'.$file)) { if($source_location != '.') { - $keyfile = \OC\Files\Filesystem::normalizePath('files_trashbin/keyfiles/' . $source_location . '/' . $filename); - $sharekey = \OC\Files\Filesystem::normalizePath('files_trashbin/share-keys/' . $source_location . '/' . $filename); + $keyfile = \OC\Files\Filesystem::normalizePath($user.'/files_trashbin/keyfiles/' . $source_location . '/' . $filename); + $sharekey = \OC\Files\Filesystem::normalizePath($user.'/files_trashbin/share-keys/' . $source_location . '/' . $filename); } else { - $keyfile = \OC\Files\Filesystem::normalizePath('files_trashbin/keyfiles/' . $filename); - $sharekey = \OC\Files\Filesystem::normalizePath('files_trashbin/share-keys/' . $filename); + $keyfile = \OC\Files\Filesystem::normalizePath($user.'/files_trashbin/keyfiles/' . $filename); + $sharekey = \OC\Files\Filesystem::normalizePath($user.'/files_trashbin/share-keys/' . $filename); } } else { - $keyfile = \OC\Files\Filesystem::normalizePath('files_trashbin/keyfiles/' . $source_location . '/' . $filename . '.key'); + $keyfile = \OC\Files\Filesystem::normalizePath($user.'/files_trashbin/keyfiles/' . $source_location . '/' . $filename . '.key'); } if ($timestamp) { @@ -390,35 +423,36 @@ class Trashbin { $proxyStatus = \OC_FileProxy::$enabled; \OC_FileProxy::$enabled = false; - if ($view->file_exists($keyfile)) { + if ($rootView->file_exists($keyfile)) { // handle directory - if ($view->is_dir($keyfile)) { + if ($rootView->is_dir($keyfile)) { // handle keyfiles - $size += self::calculateSize(new \OC\Files\View('/' . $user . '/' . $keyfile)); - $view->rename($keyfile, 'files_encryption/keyfiles/' . $location . '/' . $filename . $ext); + $size += self::calculateSize(new \OC\Files\View($keyfile)); + $rootView->rename($keyfile, $owner.'/files_encryption/keyfiles/' . $ownerPath); // handle share-keys if ($timestamp) { $sharekey .= '.d' . $timestamp; } - $view->rename($sharekey, 'files_encryption/share-keys/' . $location . '/' . $filename . $ext); + $size += self::calculateSize(new \OC\Files\View($sharekey)); + $rootView->rename($sharekey, $owner.'/files_encryption/share-keys/' . $ownerPath); } else { // handle keyfiles - $size += $view->filesize($keyfile); - $view->rename($keyfile, 'files_encryption/keyfiles/' . $location . '/' . $filename . $ext . '.key'); + $size += $rootView->filesize($keyfile); + $rootView->rename($keyfile, $owner.'/files_encryption/keyfiles/' . $ownerPath . '.key'); // handle share-keys - $ownerShareKey = \OC\Files\Filesystem::normalizePath('files_trashbin/share-keys/' . $source_location . '/' . $filename . '.' . $user. '.shareKey'); + $ownerShareKey = \OC\Files\Filesystem::normalizePath($user.'/files_trashbin/share-keys/' . $source_location . '/' . $filename . '.' . $user. '.shareKey'); if ($timestamp) { $ownerShareKey .= '.d' . $timestamp; } - $size += $view->filesize($ownerShareKey); + $size += $rootView->filesize($ownerShareKey); // move only owners key - $view->rename($ownerShareKey, 'files_encryption/share-keys/' . $location . '/' . $filename . $ext . '.' . $user. '.shareKey'); + $rootView->rename($ownerShareKey, $owner.'/files_encryption/share-keys/' . $ownerPath . '.' . $user. '.shareKey'); // try to re-share if file is shared $filesystemView = new \OC_FilesystemView('/'); @@ -426,7 +460,7 @@ class Trashbin { $util = new \OCA\Encryption\Util($filesystemView, $user); // fix the file size - $absolutePath = \OC\Files\Filesystem::normalizePath('/' . $user . '/files/'. $location. '/' .$filename); + $absolutePath = \OC\Files\Filesystem::normalizePath('/' . $owner . '/files/'. $ownerPath); $util->fixFileSize($absolutePath); // get current sharing state @@ -475,7 +509,25 @@ class Trashbin { $file = $filename; } + $size += self::deleteVersions($view, $file, $filename, $timestamp); + $size += self::deleteEncryptionKeys($view, $file, $filename, $timestamp); + + if ($view->is_dir('/files_trashbin/files/'.$file)) { + $size += self::calculateSize(new \OC\Files\View('/'.$user.'/files_trashbin/files/'.$file)); + } else { + $size += $view->filesize('/files_trashbin/files/'.$file); + } + $view->unlink('/files_trashbin/files/'.$file); + $trashbinSize -= $size; + self::setTrashbinSize($user, $trashbinSize); + + return $size; + } + + private static function deleteVersions($view, $file, $filename, $timestamp) { + $size = 0; if ( \OCP\App::isEnabled('files_versions') ) { + $user = \OCP\User::getUser(); if ($view->is_dir('files_trashbin/versions/'.$file)) { $size += self::calculateSize(new \OC\Files\view('/'.$user.'/files_trashbin/versions/'.$file)); $view->unlink('files_trashbin/versions/'.$file); @@ -491,35 +543,37 @@ class Trashbin { } } } - - // Take care of encryption keys - $parts = pathinfo($file); - if ( $view->is_dir('/files_trashbin/files/'.$file) ) { - $keyfile = \OC\Files\Filesystem::normalizePath('files_trashbin/keyfiles/'.$filename); - } else { - $keyfile = \OC\Files\Filesystem::normalizePath('files_trashbin/keyfiles/'.$filename.'.key'); - } - if ($timestamp) { - $keyfile .= '.d'.$timestamp; - } - if ( \OCP\App::isEnabled('files_encryption') && $view->file_exists($keyfile) ) { - if ( $view->is_dir($keyfile) ) { - $size += self::calculateSize(new \OC\Files\View('/'.$user.'/'.$keyfile)); + return $size; + } + + private static function deleteEncryptionKeys($view, $file, $filename, $timestamp) { + $size = 0; + if (\OCP\App::isEnabled('files_encryption')) { + $user = \OCP\User::getUser(); + + if ($view->is_dir('/files_trashbin/files/' . $file)) { + $keyfile = \OC\Files\Filesystem::normalizePath('files_trashbin/keyfiles/' . $filename); + $sharekeys = \OC\Files\Filesystem::normalizePath('files_trashbin/share-keys/' . $filename); } else { - $size += $view->filesize($keyfile); + $keyfile = \OC\Files\Filesystem::normalizePath('files_trashbin/keyfiles/' . $filename . '.key'); + $sharekeys = \OC\Files\Filesystem::normalizePath('files_trashbin/share-keys/' . $filename . '.' . $user . '.shareKey'); + } + if ($timestamp) { + $keyfile .= '.d' . $timestamp; + $sharekeys .= '.d' . $timestamp; + } + if ($view->file_exists($keyfile)) { + if ($view->is_dir($keyfile)) { + $size += self::calculateSize(new \OC\Files\View('/' . $user . '/' . $keyfile)); + $size += self::calculateSize(new \OC\Files\View('/' . $user . '/' . $sharekeys)); + } else { + $size += $view->filesize($keyfile); + $size += $view->filesize($sharekeys); + } + $view->unlink($keyfile); + $view->unlink($sharekeys); } - $view->unlink($keyfile); } - - if ($view->is_dir('/files_trashbin/files/'.$file)) { - $size += self::calculateSize(new \OC\Files\View('/'.$user.'/files_trashbin/files/'.$file)); - } else { - $size += $view->filesize('/files_trashbin/files/'.$file); - } - $view->unlink('/files_trashbin/files/'.$file); - $trashbinSize -= $size; - self::setTrashbinSize($user, $trashbinSize); - return $size; } diff --git a/tests/enable_all.php b/tests/enable_all.php index 44af011565..111ed0e135 100644 --- a/tests/enable_all.php +++ b/tests/enable_all.php @@ -8,6 +8,7 @@ require_once __DIR__.'/../lib/base.php'; +OC_App::enable('files_encryption'); OC_App::enable('calendar'); OC_App::enable('contacts'); OC_App::enable('apptemplateadvanced');