. * */ 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 // the login password /** * @brief Startup encryption backend upon user login * @note This method should never be called for users using client side encryption */ public static function login( $params ) { // Manually initialise Filesystem{} singleton with correct // fake root path, in order to avoid fatal webdav errors // NOTE: disabled because this give errors on webdav! //\OC\Files\Filesystem::init( $params['uid'], '/' . 'files' . '/' ); $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 ); if(!$util->setupServerSide( $params['password'] )) { return false; } } \OC_FileProxy::$enabled = false; $encryptedKey = Keymanager::getPrivateKey( $view, $params['uid'] ); \OC_FileProxy::$enabled = true; $privateKey = Crypt::symmetricDecryptFileContent( $encryptedKey, $params['password'] ); $session = new Session( $view ); $session->setPrivateKey( $privateKey, $params['uid'] ); // Check if first-run file migration has already been performed $migrationCompleted = $util->getMigrationStatus(); // If migration not yet done if ( ! $migrationCompleted ) { $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' ) ) { $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 ); } return true; } /** * @brief Change a user's encryption passphrase * @param array $params keys: uid, password */ public static function setPassphrase( $params ) { // Only attempt to change passphrase if server-side encryption // is in use (client-side encryption does not have access to // the necessary keys) if ( Crypt::mode() == 'server' ) { $view = new \OC_FilesystemView( '/' ); $session = new Session($view); // Get existing decrypted private key $privateKey = $session->getPrivateKey(); // Encrypt private key with new user pwd as passphrase $encryptedPrivateKey = Crypt::symmetricEncryptFileContent( $privateKey, $params['password'] ); // Save private key Keymanager::setPrivateKey( $encryptedPrivateKey ); // NOTE: Session does not need to be updated as the // private key has not changed, only the passphrase // used to decrypt it has changed } } /** * @brief update the encryption key of the file uploaded by the client */ public static function updateKeyfile( $params ) { if ( Crypt::mode() == 'client' ) { if ( isset( $params['properties']['key'] ) ) { $view = new \OC_FilesystemView( '/' ); $userId = \OCP\User::getUser(); Keymanager::setFileKey( $view, $params['path'], $userId, $params['properties']['key'] ); } else { \OC_Log::write( 'Encryption library', "Client side encryption is enabled but the client doesn't provide a encryption key for the file!" , \OC_Log::ERROR ); } } } /** * @brief */ public static function preShared( $params ) { // NOTE: $params has keys: // [itemType] => file // itemSource -> int, filecache file ID // [parent] => // [itemTarget] => /13 // shareWith -> string, uid of user being shared to // fileTarget -> path of file being shared // uidOwner -> owner of the original file being shared // [shareType] => 0 // [shareWith] => test1 // [uidOwner] => admin // [permissions] => 17 // [fileSource] => 13 // [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( '/' ); $session = new Session($view); $userId = \OCP\User::getUser(); $util = new Util($view, $userId); $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(); // if a folder was shared, get a list if all (sub-)folders if ( $params['itemType'] === 'folder' ) { $allFiles = $util->getAllFiles( $path ); } else { $allFiles = array( $path ); } // Set array for collecting paths which can't be shared $failed = array(); foreach ( $allFiles as $path ) { $usersSharing = $util->getSharingUsersArray( $sharingEnabled, $path ); // check if we share to a group if($params['shareType'] === \OCP\Share::SHARE_TYPE_GROUP) { $usersSharing[] = reset(\OC_Group::usersInGroup($params['shareWith'])); } else { // Because this is a pre_share hook, the user // being shared to is not yet included; add them $usersSharing[] = $params['shareWith']; } // Attempt to set shareKey if ( ! $util->setSharedFileKeyfiles( $session, $usersSharing, $path ) ) { $failed[] = $path; } } // If some attempts to set keyfiles failed if ( ! empty( $failed ) ) { // 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 } } } /** * @brief */ public static function postUnshare( $params ) { // NOTE: $params has keys: // [itemType] => file // [itemSource] => 13 // [shareType] => 0 // [shareWith] => test1 // [itemParent] => if ( $params['itemType'] === 'file' || $params['itemType'] === 'folder' ) { $view = new \OC_FilesystemView( '/' ); $userId = \OCP\User::getUser(); $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 ) { $userIds = \OC_Group::usersInGroup($params['shareWith']); } else { $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 ); } else { $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 ); // Unshare every user who no longer has access to the file $delUsers = array_diff( $userIds, $sharingUsers); if ( !Keymanager::delShareKey( $view, $delUsers, $path ) ) { $failed[] = $path; } } // If no attempts to set keyfiles failed if ( empty( $failed ) ) { return true; } else { 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 = $userId . '/' . 'files_encryption' . '/' . 'keyfiles' . '/' . $params['oldpath']; $newKeyfilePath = $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 = str_replace($params['oldpath'], $params['newpath'], $src); rename($src, $dst); } } else { // handle share-keys folders $oldShareKeyfilePath = $userId . '/' . 'files_encryption' . '/' . 'share-keys' . '/' . $params['oldpath']; $newShareKeyfilePath = $userId . '/' . 'files_encryption' . '/' . 'share-keys' . '/' . $params['newpath']; $view->rename($oldShareKeyfilePath, $newShareKeyfilePath); } // Rename keyfile so it isn't orphaned if($view->file_exists($oldKeyfilePath)) { $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; } }