migrate therinaldos.com data
Build & Deploy to DigitalOcean Space / build (push) Failing after 2m38s

This commit is contained in:
2024-05-05 15:50:45 -04:00
commit ef1ff240d4
23182 changed files with 3801898 additions and 0 deletions
@@ -0,0 +1,294 @@
<?php
/**
* @copyright Copyright (c) 2016, ownCloud, Inc.
*
* @author Bjoern Schiessle <bjoern@schiessle.org>
* @author Björn Schießle <bjoern@schiessle.org>
* @author Christian Jürges <christian@eqipe.ch>
* @author Christoph Wurst <christoph@winzerhof-wurst.at>
* @author Joas Schilling <coding@schilljs.com>
* @author Roeland Jago Douma <roeland@famdouma.nl>
* @author sammo2828 <sammo2828@gmail.com>
* @author Vincent Petry <vincent@nextcloud.com>
*
* @license AGPL-3.0
*
* This code is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License, version 3,
* along with this program. If not, see <http://www.gnu.org/licenses/>
*
*/
namespace OC\Encryption;
use OC\Encryption\Exceptions\DecryptionFailedException;
use OC\Files\View;
use OCP\Encryption\IEncryptionModule;
use OCP\IUserManager;
use Symfony\Component\Console\Helper\ProgressBar;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
class DecryptAll {
/** @var OutputInterface */
protected $output;
/** @var InputInterface */
protected $input;
/** @var Manager */
protected $encryptionManager;
/** @var IUserManager */
protected $userManager;
/** @var View */
protected $rootView;
/** @var array files which couldn't be decrypted */
protected $failed;
/**
* @param Manager $encryptionManager
* @param IUserManager $userManager
* @param View $rootView
*/
public function __construct(
Manager $encryptionManager,
IUserManager $userManager,
View $rootView
) {
$this->encryptionManager = $encryptionManager;
$this->userManager = $userManager;
$this->rootView = $rootView;
$this->failed = [];
}
/**
* start to decrypt all files
*
* @param InputInterface $input
* @param OutputInterface $output
* @param string $user which users data folder should be decrypted, default = all users
* @return bool
* @throws \Exception
*/
public function decryptAll(InputInterface $input, OutputInterface $output, $user = '') {
$this->input = $input;
$this->output = $output;
if ($user !== '' && $this->userManager->userExists($user) === false) {
$this->output->writeln('User "' . $user . '" does not exist. Please check the username and try again');
return false;
}
$this->output->writeln('prepare encryption modules...');
if ($this->prepareEncryptionModules($user) === false) {
return false;
}
$this->output->writeln(' done.');
$this->decryptAllUsersFiles($user);
if (empty($this->failed)) {
$this->output->writeln('all files could be decrypted successfully!');
} else {
$this->output->writeln('Files for following users couldn\'t be decrypted, ');
$this->output->writeln('maybe the user is not set up in a way that supports this operation: ');
foreach ($this->failed as $uid => $paths) {
$this->output->writeln(' ' . $uid);
foreach ($paths as $path) {
$this->output->writeln(' ' . $path);
}
}
$this->output->writeln('');
}
return true;
}
/**
* prepare encryption modules to perform the decrypt all function
*
* @param $user
* @return bool
*/
protected function prepareEncryptionModules($user) {
// prepare all encryption modules for decrypt all
$encryptionModules = $this->encryptionManager->getEncryptionModules();
foreach ($encryptionModules as $moduleDesc) {
/** @var IEncryptionModule $module */
$module = call_user_func($moduleDesc['callback']);
$this->output->writeln('');
$this->output->writeln('Prepare "' . $module->getDisplayName() . '"');
$this->output->writeln('');
if ($module->prepareDecryptAll($this->input, $this->output, $user) === false) {
$this->output->writeln('Module "' . $moduleDesc['displayName'] . '" does not support the functionality to decrypt all files again or the initialization of the module failed!');
return false;
}
}
return true;
}
/**
* iterate over all user and encrypt their files
*
* @param string $user which users files should be decrypted, default = all users
*/
protected function decryptAllUsersFiles($user = '') {
$this->output->writeln("\n");
$userList = [];
if ($user === '') {
$fetchUsersProgress = new ProgressBar($this->output);
$fetchUsersProgress->setFormat(" %message% \n [%bar%]");
$fetchUsersProgress->start();
$fetchUsersProgress->setMessage("Fetch list of users...");
$fetchUsersProgress->advance();
foreach ($this->userManager->getBackends() as $backend) {
$limit = 500;
$offset = 0;
do {
$users = $backend->getUsers('', $limit, $offset);
foreach ($users as $user) {
$userList[] = $user;
}
$offset += $limit;
$fetchUsersProgress->advance();
} while (count($users) >= $limit);
$fetchUsersProgress->setMessage("Fetch list of users... finished");
$fetchUsersProgress->finish();
}
} else {
$userList[] = $user;
}
$this->output->writeln("\n\n");
$progress = new ProgressBar($this->output);
$progress->setFormat(" %message% \n [%bar%]");
$progress->start();
$progress->setMessage("starting to decrypt files...");
$progress->advance();
$numberOfUsers = count($userList);
$userNo = 1;
foreach ($userList as $uid) {
$userCount = "$uid ($userNo of $numberOfUsers)";
$this->decryptUsersFiles($uid, $progress, $userCount);
$userNo++;
}
$progress->setMessage("starting to decrypt files... finished");
$progress->finish();
$this->output->writeln("\n\n");
}
/**
* encrypt files from the given user
*
* @param string $uid
* @param ProgressBar $progress
* @param string $userCount
*/
protected function decryptUsersFiles($uid, ProgressBar $progress, $userCount) {
$this->setupUserFS($uid);
$directories = [];
$directories[] = '/' . $uid . '/files';
while ($root = array_pop($directories)) {
$content = $this->rootView->getDirectoryContent($root);
foreach ($content as $file) {
// only decrypt files owned by the user
if ($file->getStorage()->instanceOfStorage('OCA\Files_Sharing\SharedStorage')) {
continue;
}
$path = $root . '/' . $file['name'];
if ($this->rootView->is_dir($path)) {
$directories[] = $path;
continue;
} else {
try {
$progress->setMessage("decrypt files for user $userCount: $path");
$progress->advance();
if ($file->isEncrypted() === false) {
$progress->setMessage("decrypt files for user $userCount: $path (already decrypted)");
$progress->advance();
} else {
if ($this->decryptFile($path) === false) {
$progress->setMessage("decrypt files for user $userCount: $path (already decrypted)");
$progress->advance();
}
}
} catch (\Exception $e) {
if (isset($this->failed[$uid])) {
$this->failed[$uid][] = $path;
} else {
$this->failed[$uid] = [$path];
}
}
}
}
}
}
/**
* encrypt file
*
* @param string $path
* @return bool
*/
protected function decryptFile($path) {
// skip already decrypted files
$fileInfo = $this->rootView->getFileInfo($path);
if ($fileInfo !== false && !$fileInfo->isEncrypted()) {
return true;
}
$source = $path;
$target = $path . '.decrypted.' . $this->getTimestamp();
try {
$this->rootView->copy($source, $target);
$this->rootView->touch($target, $fileInfo->getMTime());
$this->rootView->rename($target, $source);
} catch (DecryptionFailedException $e) {
if ($this->rootView->file_exists($target)) {
$this->rootView->unlink($target);
}
return false;
}
return true;
}
/**
* get current timestamp
*
* @return int
*/
protected function getTimestamp() {
return time();
}
/**
* setup user file system
*
* @param string $uid
*/
protected function setupUserFS($uid) {
\OC_Util::tearDownFS();
\OC_Util::setupFS($uid);
}
}
@@ -0,0 +1,118 @@
<?php
/**
* @copyright Copyright (c) 2016, ownCloud, Inc.
*
* @author Björn Schießle <bjoern@schiessle.org>
* @author Julius Härtl <jus@bitgrid.net>
* @author Robin Appelman <robin@icewind.nl>
* @author Roeland Jago Douma <roeland@famdouma.nl>
*
* @license AGPL-3.0
*
* This code is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License, version 3,
* along with this program. If not, see <http://www.gnu.org/licenses/>
*
*/
namespace OC\Encryption;
use OC\Files\Filesystem;
use OC\Files\Storage\Wrapper\Encryption;
use OC\Files\View;
use OC\Memcache\ArrayCache;
use OCP\Files\Mount\IMountPoint;
use OCP\Files\Storage\IDisableEncryptionStorage;
use OCP\Files\Storage\IStorage;
use Psr\Log\LoggerInterface;
/**
* Class EncryptionWrapper
*
* applies the encryption storage wrapper
*
* @package OC\Encryption
*/
class EncryptionWrapper {
/** @var ArrayCache */
private $arrayCache;
/** @var Manager */
private $manager;
private LoggerInterface $logger;
/**
* EncryptionWrapper constructor.
*/
public function __construct(ArrayCache $arrayCache,
Manager $manager,
LoggerInterface $logger
) {
$this->arrayCache = $arrayCache;
$this->manager = $manager;
$this->logger = $logger;
}
/**
* Wraps the given storage when it is not a shared storage
*
* @param string $mountPoint
* @param IStorage $storage
* @param IMountPoint $mount
* @param bool $force apply the wrapper even if the storage normally has encryption disabled, helpful for repair steps
* @return Encryption|IStorage
*/
public function wrapStorage(string $mountPoint, IStorage $storage, IMountPoint $mount, bool $force = false) {
$parameters = [
'storage' => $storage,
'mountPoint' => $mountPoint,
'mount' => $mount
];
if ($force || (!$storage->instanceOfStorage(IDisableEncryptionStorage::class) && $mountPoint !== '/')) {
$user = \OC::$server->getUserSession()->getUser();
$mountManager = Filesystem::getMountManager();
$uid = $user ? $user->getUID() : null;
$fileHelper = \OC::$server->getEncryptionFilesHelper();
$keyStorage = \OC::$server->getEncryptionKeyStorage();
$util = new Util(
new View(),
\OC::$server->getUserManager(),
\OC::$server->getGroupManager(),
\OC::$server->getConfig()
);
$update = new Update(
new View(),
$util,
Filesystem::getMountManager(),
$this->manager,
$fileHelper,
$this->logger,
$uid
);
return new Encryption(
$parameters,
$this->manager,
$util,
$this->logger,
$fileHelper,
$uid,
$keyStorage,
$update,
$mountManager,
$this->arrayCache
);
} else {
return $storage;
}
}
}
@@ -0,0 +1,28 @@
<?php
/**
* @copyright Copyright (c) 2016, ownCloud, Inc.
*
* @author Clark Tomlinson <fallen013@gmail.com>
* @author Thomas Müller <thomas.mueller@tmit.eu>
*
* @license AGPL-3.0
*
* This code is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License, version 3,
* along with this program. If not, see <http://www.gnu.org/licenses/>
*
*/
namespace OC\Encryption\Exceptions;
use OCP\Encryption\Exceptions\GenericEncryptionException;
class DecryptionFailedException extends GenericEncryptionException {
}
@@ -0,0 +1,29 @@
<?php
/**
* @copyright Copyright (c) 2016, ownCloud, Inc.
*
* @author Christoph Wurst <christoph@winzerhof-wurst.at>
* @author Clark Tomlinson <fallen013@gmail.com>
* @author Thomas Müller <thomas.mueller@tmit.eu>
*
* @license AGPL-3.0
*
* This code is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License, version 3,
* along with this program. If not, see <http://www.gnu.org/licenses/>
*
*/
namespace OC\Encryption\Exceptions;
use OCP\Encryption\Exceptions\GenericEncryptionException;
class EmptyEncryptionDataException extends GenericEncryptionException {
}
@@ -0,0 +1,29 @@
<?php
/**
* @copyright Copyright (c) 2016, ownCloud, Inc.
*
* @author Christoph Wurst <christoph@winzerhof-wurst.at>
* @author Clark Tomlinson <fallen013@gmail.com>
* @author Thomas Müller <thomas.mueller@tmit.eu>
*
* @license AGPL-3.0
*
* This code is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License, version 3,
* along with this program. If not, see <http://www.gnu.org/licenses/>
*
*/
namespace OC\Encryption\Exceptions;
use OCP\Encryption\Exceptions\GenericEncryptionException;
class EncryptionFailedException extends GenericEncryptionException {
}
@@ -0,0 +1,34 @@
<?php
/**
* @copyright Copyright (c) 2016, ownCloud, Inc.
*
* @author Björn Schießle <bjoern@schiessle.org>
* @author Thomas Müller <thomas.mueller@tmit.eu>
*
* @license AGPL-3.0
*
* This code is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License, version 3,
* along with this program. If not, see <http://www.gnu.org/licenses/>
*
*/
namespace OC\Encryption\Exceptions;
use OCP\Encryption\Exceptions\GenericEncryptionException;
class EncryptionHeaderKeyExistsException extends GenericEncryptionException {
/**
* @param string $key
*/
public function __construct($key) {
parent::__construct('header key "'. $key . '" already reserved by ownCloud');
}
}
@@ -0,0 +1,31 @@
<?php
/**
* @copyright Copyright (c) 2016, ownCloud, Inc.
*
* @author Clark Tomlinson <fallen013@gmail.com>
* @author Thomas Müller <thomas.mueller@tmit.eu>
*
* @license AGPL-3.0
*
* This code is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License, version 3,
* along with this program. If not, see <http://www.gnu.org/licenses/>
*
*/
namespace OC\Encryption\Exceptions;
use OCP\Encryption\Exceptions\GenericEncryptionException;
class EncryptionHeaderToLargeException extends GenericEncryptionException {
public function __construct() {
parent::__construct('max header size exceeded');
}
}
@@ -0,0 +1,35 @@
<?php
/**
* @copyright Copyright (c) 2016, ownCloud, Inc.
*
* @author Björn Schießle <bjoern@schiessle.org>
* @author Thomas Müller <thomas.mueller@tmit.eu>
*
* @license AGPL-3.0
*
* This code is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License, version 3,
* along with this program. If not, see <http://www.gnu.org/licenses/>
*
*/
namespace OC\Encryption\Exceptions;
use OCP\Encryption\Exceptions\GenericEncryptionException;
class ModuleAlreadyExistsException extends GenericEncryptionException {
/**
* @param string $id
* @param string $name
*/
public function __construct($id, $name) {
parent::__construct('Id "' . $id . '" already used by encryption module "' . $name . '"');
}
}
@@ -0,0 +1,28 @@
<?php
/**
* @copyright Copyright (c) 2016, ownCloud, Inc.
*
* @author Björn Schießle <bjoern@schiessle.org>
* @author Thomas Müller <thomas.mueller@tmit.eu>
*
* @license AGPL-3.0
*
* This code is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License, version 3,
* along with this program. If not, see <http://www.gnu.org/licenses/>
*
*/
namespace OC\Encryption\Exceptions;
use OCP\Encryption\Exceptions\GenericEncryptionException;
class ModuleDoesNotExistsException extends GenericEncryptionException {
}
@@ -0,0 +1,28 @@
<?php
/**
* @copyright Copyright (c) 2016, ownCloud, Inc.
*
* @author Clark Tomlinson <fallen013@gmail.com>
* @author Thomas Müller <thomas.mueller@tmit.eu>
*
* @license AGPL-3.0
*
* This code is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License, version 3,
* along with this program. If not, see <http://www.gnu.org/licenses/>
*
*/
namespace OC\Encryption\Exceptions;
use OCP\Encryption\Exceptions\GenericEncryptionException;
class UnknownCipherException extends GenericEncryptionException {
}
@@ -0,0 +1,132 @@
<?php
/**
* @copyright Copyright (c) 2016, ownCloud, Inc.
*
* @author Björn Schießle <bjoern@schiessle.org>
* @author Christoph Wurst <christoph@winzerhof-wurst.at>
* @author Joas Schilling <coding@schilljs.com>
* @author Morris Jobke <hey@morrisjobke.de>
* @author Roeland Jago Douma <roeland@famdouma.nl>
* @author Thomas Müller <thomas.mueller@tmit.eu>
* @author Vincent Petry <vincent@nextcloud.com>
*
* @license AGPL-3.0
*
* This code is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License, version 3,
* along with this program. If not, see <http://www.gnu.org/licenses/>
*
*/
namespace OC\Encryption;
use OCA\Files_External\Service\GlobalStoragesService;
use OCP\App\IAppManager;
use OCP\Cache\CappedMemoryCache;
use OCP\Files\IRootFolder;
use OCP\Files\NotFoundException;
use OCP\Share\IManager;
class File implements \OCP\Encryption\IFile {
protected Util $util;
private IRootFolder $rootFolder;
private IManager $shareManager;
/**
* Cache results of already checked folders
* @var CappedMemoryCache<array>
*/
protected CappedMemoryCache $cache;
private ?IAppManager $appManager = null;
public function __construct(Util $util,
IRootFolder $rootFolder,
IManager $shareManager) {
$this->util = $util;
$this->cache = new CappedMemoryCache();
$this->rootFolder = $rootFolder;
$this->shareManager = $shareManager;
}
public function getAppManager(): IAppManager {
// Lazy evaluate app manager as it initialize the db too early otherwise
if ($this->appManager) {
return $this->appManager;
}
$this->appManager = \OCP\Server::get(IAppManager::class);
return $this->appManager;
}
/**
* Get list of users with access to the file
*
* @param string $path to the file
* @return array{users: string[], public: bool}
*/
public function getAccessList($path) {
// Make sure that a share key is generated for the owner too
[$owner, $ownerPath] = $this->util->getUidAndFilename($path);
// always add owner to the list of users with access to the file
$userIds = [$owner];
if (!$this->util->isFile($owner . '/' . $ownerPath)) {
return ['users' => $userIds, 'public' => false];
}
$ownerPath = substr($ownerPath, strlen('/files'));
$userFolder = $this->rootFolder->getUserFolder($owner);
try {
$file = $userFolder->get($ownerPath);
} catch (NotFoundException $e) {
$file = null;
}
$ownerPath = $this->util->stripPartialFileExtension($ownerPath);
// first get the shares for the parent and cache the result so that we don't
// need to check all parents for every file
$parent = dirname($ownerPath);
$parentNode = $userFolder->get($parent);
if (isset($this->cache[$parent])) {
$resultForParents = $this->cache[$parent];
} else {
$resultForParents = $this->shareManager->getAccessList($parentNode);
$this->cache[$parent] = $resultForParents;
}
$userIds = array_merge($userIds, $resultForParents['users']);
$public = $resultForParents['public'] || $resultForParents['remote'];
// Find out who, if anyone, is sharing the file
if ($file !== null) {
$resultForFile = $this->shareManager->getAccessList($file, false);
$userIds = array_merge($userIds, $resultForFile['users']);
$public = $resultForFile['public'] || $resultForFile['remote'] || $public;
}
// check if it is a group mount
if ($this->getAppManager()->isEnabledForUser("files_external")) {
/** @var GlobalStoragesService $storageService */
$storageService = \OC::$server->get(GlobalStoragesService::class);
$storages = $storageService->getAllStorages();
foreach ($storages as $storage) {
if ($storage->getMountPoint() == substr($ownerPath, 0, strlen($storage->getMountPoint()))) {
$mountedFor = $this->util->getUserWithAccessToMountPoint($storage->getApplicableUsers(), $storage->getApplicableGroups());
$userIds = array_merge($userIds, $mountedFor);
}
}
}
// Remove duplicate UIDs
$uniqueUserIds = array_unique($userIds);
return ['users' => $uniqueUserIds, 'public' => $public];
}
}
@@ -0,0 +1,90 @@
<?php
/**
* @copyright Copyright (c) 2016, ownCloud, Inc.
*
* @author Björn Schießle <bjoern@schiessle.org>
* @author Julius Härtl <jus@bitgrid.net>
* @author Thomas Müller <thomas.mueller@tmit.eu>
*
* @license AGPL-3.0
*
* This code is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License, version 3,
* along with this program. If not, see <http://www.gnu.org/licenses/>
*
*/
namespace OC\Encryption;
use OC\Files\Filesystem;
use OC\Files\SetupManager;
use OC\Files\View;
use Psr\Log\LoggerInterface;
class HookManager {
private static ?Update $updater = null;
public static function postShared($params): void {
self::getUpdate()->postShared($params);
}
public static function postUnshared($params): void {
// In case the unsharing happens in a background job, we don't have
// a session and we load instead the user from the UserManager
$path = Filesystem::getPath($params['fileSource']);
$owner = Filesystem::getOwner($path);
self::getUpdate($owner)->postUnshared($params);
}
public static function postRename($params): void {
self::getUpdate()->postRename($params);
}
public static function postRestore($params): void {
self::getUpdate()->postRestore($params);
}
private static function getUpdate(?string $owner = null): Update {
if (is_null(self::$updater)) {
$user = \OC::$server->getUserSession()->getUser();
if (!$user && $owner) {
$user = \OC::$server->getUserManager()->get($owner);
}
if (!$user) {
throw new \Exception("Inconsistent data, File unshared, but owner not found. Should not happen");
}
$uid = '';
if ($user) {
$uid = $user->getUID();
}
$setupManager = \OC::$server->get(SetupManager::class);
if (!$setupManager->isSetupComplete($user)) {
$setupManager->setupForUser($user);
}
self::$updater = new Update(
new View(),
new Util(
new View(),
\OC::$server->getUserManager(),
\OC::$server->getGroupManager(),
\OC::$server->getConfig()),
Filesystem::getMountManager(),
\OC::$server->getEncryptionManager(),
\OC::$server->getEncryptionFilesHelper(),
\OC::$server->get(LoggerInterface::class),
$uid
);
}
return self::$updater;
}
}
@@ -0,0 +1,488 @@
<?php
/**
* @copyright Copyright (c) 2016, ownCloud, Inc.
*
* @author Bjoern Schiessle <bjoern@schiessle.org>
* @author Björn Schießle <bjoern@schiessle.org>
* @author Christoph Wurst <christoph@winzerhof-wurst.at>
* @author Joas Schilling <coding@schilljs.com>
* @author Roeland Jago Douma <roeland@famdouma.nl>
* @author Thomas Müller <thomas.mueller@tmit.eu>
* @author Vincent Petry <vincent@nextcloud.com>
*
* @license AGPL-3.0
*
* This code is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License, version 3,
* along with this program. If not, see <http://www.gnu.org/licenses/>
*
*/
namespace OC\Encryption\Keys;
use OC\Encryption\Util;
use OC\Files\Filesystem;
use OC\Files\View;
use OC\ServerNotAvailableException;
use OC\User\NoUserException;
use OCP\Encryption\Keys\IStorage;
use OCP\IConfig;
use OCP\Security\ICrypto;
class Storage implements IStorage {
// hidden file which indicate that the folder is a valid key storage
public const KEY_STORAGE_MARKER = '.oc_key_storage';
/** @var View */
private $view;
/** @var Util */
private $util;
// base dir where all the file related keys are stored
/** @var string */
private $keys_base_dir;
// root of the key storage default is empty which means that we use the data folder
/** @var string */
private $root_dir;
/** @var string */
private $encryption_base_dir;
/** @var string */
private $backup_base_dir;
/** @var array */
private $keyCache = [];
/** @var ICrypto */
private $crypto;
/** @var IConfig */
private $config;
/**
* @param View $view
* @param Util $util
*/
public function __construct(View $view, Util $util, ICrypto $crypto, IConfig $config) {
$this->view = $view;
$this->util = $util;
$this->encryption_base_dir = '/files_encryption';
$this->keys_base_dir = $this->encryption_base_dir .'/keys';
$this->backup_base_dir = $this->encryption_base_dir .'/backup';
$this->root_dir = $this->util->getKeyStorageRoot();
$this->crypto = $crypto;
$this->config = $config;
}
/**
* @inheritdoc
*/
public function getUserKey($uid, $keyId, $encryptionModuleId) {
$path = $this->constructUserKeyPath($encryptionModuleId, $keyId, $uid);
return base64_decode($this->getKeyWithUid($path, $uid));
}
/**
* @inheritdoc
*/
public function getFileKey($path, $keyId, $encryptionModuleId) {
$realFile = $this->util->stripPartialFileExtension($path);
$keyDir = $this->getFileKeyDir($encryptionModuleId, $realFile);
$key = $this->getKey($keyDir . $keyId)['key'];
if ($key === '' && $realFile !== $path) {
// Check if the part file has keys and use them, if no normal keys
// exist. This is required to fix copyBetweenStorage() when we
// rename a .part file over storage borders.
$keyDir = $this->getFileKeyDir($encryptionModuleId, $path);
$key = $this->getKey($keyDir . $keyId)['key'];
}
return base64_decode($key);
}
/**
* @inheritdoc
*/
public function getSystemUserKey($keyId, $encryptionModuleId) {
$path = $this->constructUserKeyPath($encryptionModuleId, $keyId, null);
return base64_decode($this->getKeyWithUid($path, null));
}
/**
* @inheritdoc
*/
public function setUserKey($uid, $keyId, $key, $encryptionModuleId) {
$path = $this->constructUserKeyPath($encryptionModuleId, $keyId, $uid);
return $this->setKey($path, [
'key' => base64_encode($key),
'uid' => $uid,
]);
}
/**
* @inheritdoc
*/
public function setFileKey($path, $keyId, $key, $encryptionModuleId) {
$keyDir = $this->getFileKeyDir($encryptionModuleId, $path);
return $this->setKey($keyDir . $keyId, [
'key' => base64_encode($key),
]);
}
/**
* @inheritdoc
*/
public function setSystemUserKey($keyId, $key, $encryptionModuleId) {
$path = $this->constructUserKeyPath($encryptionModuleId, $keyId, null);
return $this->setKey($path, [
'key' => base64_encode($key),
'uid' => null,
]);
}
/**
* @inheritdoc
*/
public function deleteUserKey($uid, $keyId, $encryptionModuleId) {
try {
$path = $this->constructUserKeyPath($encryptionModuleId, $keyId, $uid);
return !$this->view->file_exists($path) || $this->view->unlink($path);
} catch (NoUserException $e) {
// this exception can come from initMountPoints() from setupUserMounts()
// for a deleted user.
//
// It means, that:
// - we are not running in alternative storage mode because we don't call
// initMountPoints() in that mode
// - the keys were in the user's home but since the user was deleted, the
// user's home is gone and so are the keys
//
// So there is nothing to do, just ignore.
}
}
/**
* @inheritdoc
*/
public function deleteFileKey($path, $keyId, $encryptionModuleId) {
$keyDir = $this->getFileKeyDir($encryptionModuleId, $path);
return !$this->view->file_exists($keyDir . $keyId) || $this->view->unlink($keyDir . $keyId);
}
/**
* @inheritdoc
*/
public function deleteAllFileKeys($path) {
$keyDir = $this->getFileKeyDir('', $path);
return !$this->view->file_exists($keyDir) || $this->view->deleteAll($keyDir);
}
/**
* @inheritdoc
*/
public function deleteSystemUserKey($keyId, $encryptionModuleId) {
$path = $this->constructUserKeyPath($encryptionModuleId, $keyId, null);
return !$this->view->file_exists($path) || $this->view->unlink($path);
}
/**
* construct path to users key
*
* @param string $encryptionModuleId
* @param string $keyId
* @param string $uid
* @return string
*/
protected function constructUserKeyPath($encryptionModuleId, $keyId, $uid) {
if ($uid === null) {
$path = $this->root_dir . '/' . $this->encryption_base_dir . '/' . $encryptionModuleId . '/' . $keyId;
} else {
$path = $this->root_dir . '/' . $uid . $this->encryption_base_dir . '/'
. $encryptionModuleId . '/' . $uid . '.' . $keyId;
}
return \OC\Files\Filesystem::normalizePath($path);
}
/**
* @param string $path
* @param string|null $uid
* @return string
* @throws ServerNotAvailableException
*
* Small helper function to fetch the key and verify the value for user and system keys
*/
private function getKeyWithUid(string $path, ?string $uid): string {
$data = $this->getKey($path);
if (!isset($data['key'])) {
throw new ServerNotAvailableException('Key is invalid');
}
if ($data['key'] === '') {
return '';
}
if (!array_key_exists('uid', $data) || $data['uid'] !== $uid) {
// If the migration is done we error out
$versionFromBeforeUpdate = $this->config->getSystemValueString('version', '0.0.0.0');
if (version_compare($versionFromBeforeUpdate, '20.0.0.1', '<=')) {
return $data['key'];
}
if ($this->config->getSystemValueBool('encryption.key_storage_migrated', true)) {
throw new ServerNotAvailableException('Key has been modified');
} else {
//Otherwise we migrate
$data['uid'] = $uid;
$this->setKey($path, $data);
}
}
return $data['key'];
}
/**
* read key from hard disk
*
* @param string $path to key
* @return array containing key as base64encoded key, and possible the uid
*/
private function getKey($path): array {
$key = [
'key' => '',
];
if ($this->view->file_exists($path)) {
if (isset($this->keyCache[$path])) {
$key = $this->keyCache[$path];
} else {
$data = $this->view->file_get_contents($path);
// Version <20.0.0.1 doesn't have this
$versionFromBeforeUpdate = $this->config->getSystemValueString('version', '0.0.0.0');
if (version_compare($versionFromBeforeUpdate, '20.0.0.1', '<=')) {
$key = [
'key' => base64_encode($data),
];
} else {
if ($this->config->getSystemValueBool('encryption.key_storage_migrated', true)) {
try {
$clearData = $this->crypto->decrypt($data);
} catch (\Exception $e) {
throw new ServerNotAvailableException('Could not decrypt key', 0, $e);
}
$dataArray = json_decode($clearData, true);
if ($dataArray === null) {
throw new ServerNotAvailableException('Invalid encryption key');
}
$key = $dataArray;
} else {
/*
* Even if not all keys are migrated we should still try to decrypt it (in case some have moved).
* However it is only a failure now if it is an array and decryption fails
*/
$fallback = false;
try {
$clearData = $this->crypto->decrypt($data);
} catch (\Throwable $e) {
$fallback = true;
}
if (!$fallback) {
$dataArray = json_decode($clearData, true);
if ($dataArray === null) {
throw new ServerNotAvailableException('Invalid encryption key');
}
$key = $dataArray;
} else {
$key = [
'key' => base64_encode($data),
];
}
}
}
$this->keyCache[$path] = $key;
}
}
return $key;
}
/**
* write key to disk
*
*
* @param string $path path to key directory
* @param array $key key
* @return bool
*/
private function setKey($path, $key) {
$this->keySetPreparation(dirname($path));
$versionFromBeforeUpdate = $this->config->getSystemValueString('version', '0.0.0.0');
if (version_compare($versionFromBeforeUpdate, '20.0.0.1', '<=')) {
// Only store old format if this happens during the migration.
// TODO: Remove for 21
$data = base64_decode($key['key']);
} else {
// Wrap the data
$data = $this->crypto->encrypt(json_encode($key));
}
$result = $this->view->file_put_contents($path, $data);
if (is_int($result) && $result > 0) {
$this->keyCache[$path] = $key;
return true;
}
return false;
}
/**
* get path to key folder for a given file
*
* @param string $encryptionModuleId
* @param string $path path to the file, relative to data/
* @return string
*/
private function getFileKeyDir($encryptionModuleId, $path) {
[$owner, $filename] = $this->util->getUidAndFilename($path);
// in case of system wide mount points the keys are stored directly in the data directory
if ($this->util->isSystemWideMountPoint($filename, $owner)) {
$keyPath = $this->root_dir . '/' . $this->keys_base_dir . $filename . '/';
} else {
$keyPath = $this->root_dir . '/' . $owner . $this->keys_base_dir . $filename . '/';
}
return Filesystem::normalizePath($keyPath . $encryptionModuleId . '/', false);
}
/**
* move keys if a file was renamed
*
* @param string $source
* @param string $target
* @return boolean
*/
public function renameKeys($source, $target) {
$sourcePath = $this->getPathToKeys($source);
$targetPath = $this->getPathToKeys($target);
if ($this->view->file_exists($sourcePath)) {
$this->keySetPreparation(dirname($targetPath));
$this->view->rename($sourcePath, $targetPath);
return true;
}
return false;
}
/**
* copy keys if a file was renamed
*
* @param string $source
* @param string $target
* @return boolean
*/
public function copyKeys($source, $target) {
$sourcePath = $this->getPathToKeys($source);
$targetPath = $this->getPathToKeys($target);
if ($this->view->file_exists($sourcePath)) {
$this->keySetPreparation(dirname($targetPath));
$this->view->copy($sourcePath, $targetPath);
return true;
}
return false;
}
/**
* backup keys of a given encryption module
*
* @param string $encryptionModuleId
* @param string $purpose
* @param string $uid
* @return bool
* @since 12.0.0
*/
public function backupUserKeys($encryptionModuleId, $purpose, $uid) {
$source = $uid . $this->encryption_base_dir . '/' . $encryptionModuleId;
$backupDir = $uid . $this->backup_base_dir;
if (!$this->view->file_exists($backupDir)) {
$this->view->mkdir($backupDir);
}
$backupDir = $backupDir . '/' . $purpose . '.' . $encryptionModuleId . '.' . $this->getTimestamp();
$this->view->mkdir($backupDir);
return $this->view->copy($source, $backupDir);
}
/**
* get the current timestamp
*
* @return int
*/
protected function getTimestamp() {
return time();
}
/**
* get system wide path and detect mount points
*
* @param string $path
* @return string
*/
protected function getPathToKeys($path) {
[$owner, $relativePath] = $this->util->getUidAndFilename($path);
$systemWideMountPoint = $this->util->isSystemWideMountPoint($relativePath, $owner);
if ($systemWideMountPoint) {
$systemPath = $this->root_dir . '/' . $this->keys_base_dir . $relativePath . '/';
} else {
$systemPath = $this->root_dir . '/' . $owner . $this->keys_base_dir . $relativePath . '/';
}
return Filesystem::normalizePath($systemPath, false);
}
/**
* Make preparations to filesystem for saving a key file
*
* @param string $path relative to the views root
*/
protected function keySetPreparation($path) {
// If the file resides within a subdirectory, create it
if (!$this->view->file_exists($path)) {
$sub_dirs = explode('/', ltrim($path, '/'));
$dir = '';
foreach ($sub_dirs as $sub_dir) {
$dir .= '/' . $sub_dir;
if (!$this->view->is_dir($dir)) {
$this->view->mkdir($dir);
}
}
}
}
}
@@ -0,0 +1,265 @@
<?php
/**
* @copyright Copyright (c) 2016, ownCloud, Inc.
*
* @author Björn Schießle <bjoern@schiessle.org>
* @author Christoph Wurst <christoph@winzerhof-wurst.at>
* @author Joas Schilling <coding@schilljs.com>
* @author Roeland Jago Douma <roeland@famdouma.nl>
* @author Thomas Müller <thomas.mueller@tmit.eu>
*
* @license AGPL-3.0
*
* This code is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License, version 3,
* along with this program. If not, see <http://www.gnu.org/licenses/>
*
*/
namespace OC\Encryption;
use OC\Encryption\Keys\Storage;
use OC\Files\Filesystem;
use OC\Files\View;
use OC\Memcache\ArrayCache;
use OC\ServiceUnavailableException;
use OCP\Encryption\IEncryptionModule;
use OCP\Encryption\IManager;
use OCP\Files\Mount\IMountPoint;
use OCP\Files\Storage\IStorage;
use OCP\IConfig;
use OCP\IL10N;
use Psr\Log\LoggerInterface;
class Manager implements IManager {
/** @var array */
protected $encryptionModules;
/** @var IConfig */
protected $config;
protected LoggerInterface $logger;
/** @var Il10n */
protected $l;
/** @var View */
protected $rootView;
/** @var Util */
protected $util;
/** @var ArrayCache */
protected $arrayCache;
public function __construct(IConfig $config, LoggerInterface $logger, IL10N $l10n, View $rootView, Util $util, ArrayCache $arrayCache) {
$this->encryptionModules = [];
$this->config = $config;
$this->logger = $logger;
$this->l = $l10n;
$this->rootView = $rootView;
$this->util = $util;
$this->arrayCache = $arrayCache;
}
/**
* Check if encryption is enabled
*
* @return bool true if enabled, false if not
*/
public function isEnabled() {
$installed = $this->config->getSystemValueBool('installed', false);
if (!$installed) {
return false;
}
$enabled = $this->config->getAppValue('core', 'encryption_enabled', 'no');
return $enabled === 'yes';
}
/**
* check if new encryption is ready
*
* @return bool
* @throws ServiceUnavailableException
*/
public function isReady() {
if ($this->isKeyStorageReady() === false) {
throw new ServiceUnavailableException('Key Storage is not ready');
}
return true;
}
/**
* @param string $user
*/
public function isReadyForUser($user) {
if (!$this->isReady()) {
return false;
}
foreach ($this->getEncryptionModules() as $module) {
/** @var IEncryptionModule $m */
$m = call_user_func($module['callback']);
if (!$m->isReadyForUser($user)) {
return false;
}
}
return true;
}
/**
* Registers an callback function which must return an encryption module instance
*
* @param string $id
* @param string $displayName
* @param callable $callback
* @throws Exceptions\ModuleAlreadyExistsException
*/
public function registerEncryptionModule($id, $displayName, callable $callback) {
if (isset($this->encryptionModules[$id])) {
throw new Exceptions\ModuleAlreadyExistsException($id, $displayName);
}
$this->encryptionModules[$id] = [
'id' => $id,
'displayName' => $displayName,
'callback' => $callback,
];
$defaultEncryptionModuleId = $this->getDefaultEncryptionModuleId();
if (empty($defaultEncryptionModuleId)) {
$this->setDefaultEncryptionModule($id);
}
}
/**
* Unregisters an encryption module
*
* @param string $moduleId
*/
public function unregisterEncryptionModule($moduleId) {
unset($this->encryptionModules[$moduleId]);
}
/**
* get a list of all encryption modules
*
* @return array [id => ['id' => $id, 'displayName' => $displayName, 'callback' => callback]]
*/
public function getEncryptionModules() {
return $this->encryptionModules;
}
/**
* get a specific encryption module
*
* @param string $moduleId
* @return IEncryptionModule
* @throws Exceptions\ModuleDoesNotExistsException
*/
public function getEncryptionModule($moduleId = '') {
if (empty($moduleId)) {
return $this->getDefaultEncryptionModule();
}
if (isset($this->encryptionModules[$moduleId])) {
return call_user_func($this->encryptionModules[$moduleId]['callback']);
}
$message = "Module with ID: $moduleId does not exist.";
$hint = $this->l->t('Module with ID: %s does not exist. Please enable it in your apps settings or contact your administrator.', [$moduleId]);
throw new Exceptions\ModuleDoesNotExistsException($message, $hint);
}
/**
* get default encryption module
*
* @return \OCP\Encryption\IEncryptionModule
* @throws Exceptions\ModuleDoesNotExistsException
*/
protected function getDefaultEncryptionModule() {
$defaultModuleId = $this->getDefaultEncryptionModuleId();
if (empty($defaultModuleId)) {
$message = 'No default encryption module defined';
throw new Exceptions\ModuleDoesNotExistsException($message);
}
if (isset($this->encryptionModules[$defaultModuleId])) {
return call_user_func($this->encryptionModules[$defaultModuleId]['callback']);
}
$message = 'Default encryption module not loaded';
throw new Exceptions\ModuleDoesNotExistsException($message);
}
/**
* set default encryption module Id
*
* @param string $moduleId
* @return bool
*/
public function setDefaultEncryptionModule($moduleId) {
try {
$this->getEncryptionModule($moduleId);
} catch (\Exception $e) {
return false;
}
$this->config->setAppValue('core', 'default_encryption_module', $moduleId);
return true;
}
/**
* get default encryption module Id
*
* @return string
*/
public function getDefaultEncryptionModuleId() {
return $this->config->getAppValue('core', 'default_encryption_module');
}
/**
* Add storage wrapper
*/
public function setupStorage() {
// If encryption is disabled and there are no loaded modules it makes no sense to load the wrapper
if (!empty($this->encryptionModules) || $this->isEnabled()) {
$encryptionWrapper = new EncryptionWrapper($this->arrayCache, $this, $this->logger);
Filesystem::addStorageWrapper('oc_encryption', [$encryptionWrapper, 'wrapStorage'], 2);
}
}
public function forceWrapStorage(IMountPoint $mountPoint, IStorage $storage) {
$encryptionWrapper = new EncryptionWrapper($this->arrayCache, $this, $this->logger);
return $encryptionWrapper->wrapStorage($mountPoint->getMountPoint(), $storage, $mountPoint, true);
}
/**
* check if key storage is ready
*
* @return bool
*/
protected function isKeyStorageReady() {
$rootDir = $this->util->getKeyStorageRoot();
// the default root is always valid
if ($rootDir === '') {
return true;
}
// check if key storage is mounted correctly
if ($this->rootView->file_exists($rootDir . '/' . Storage::KEY_STORAGE_MARKER)) {
return true;
}
return false;
}
}
@@ -0,0 +1,199 @@
<?php
/**
* @copyright Copyright (c) 2016, ownCloud, Inc.
*
* @author Bjoern Schiessle <bjoern@schiessle.org>
* @author Björn Schießle <bjoern@schiessle.org>
* @author Christoph Wurst <christoph@winzerhof-wurst.at>
* @author Julius Härtl <jus@bitgrid.net>
* @author Roeland Jago Douma <roeland@famdouma.nl>
* @author Thomas Müller <thomas.mueller@tmit.eu>
*
* @license AGPL-3.0
*
* This code is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License, version 3,
* along with this program. If not, see <http://www.gnu.org/licenses/>
*
*/
namespace OC\Encryption;
use InvalidArgumentException;
use OC\Files\Filesystem;
use OC\Files\Mount;
use OC\Files\View;
use OCP\Encryption\Exceptions\GenericEncryptionException;
use Psr\Log\LoggerInterface;
/**
* update encrypted files, e.g. because a file was shared
*/
class Update {
/** @var View */
protected $view;
/** @var Util */
protected $util;
/** @var \OC\Files\Mount\Manager */
protected $mountManager;
/** @var Manager */
protected $encryptionManager;
/** @var string */
protected $uid;
/** @var File */
protected $file;
/** @var LoggerInterface */
protected $logger;
/**
* @param string $uid
*/
public function __construct(
View $view,
Util $util,
Mount\Manager $mountManager,
Manager $encryptionManager,
File $file,
LoggerInterface $logger,
$uid
) {
$this->view = $view;
$this->util = $util;
$this->mountManager = $mountManager;
$this->encryptionManager = $encryptionManager;
$this->file = $file;
$this->logger = $logger;
$this->uid = $uid;
}
/**
* hook after file was shared
*
* @param array $params
*/
public function postShared($params) {
if ($this->encryptionManager->isEnabled()) {
if ($params['itemType'] === 'file' || $params['itemType'] === 'folder') {
$path = Filesystem::getPath($params['fileSource']);
[$owner, $ownerPath] = $this->getOwnerPath($path);
$absPath = '/' . $owner . '/files/' . $ownerPath;
$this->update($absPath);
}
}
}
/**
* hook after file was unshared
*
* @param array $params
*/
public function postUnshared($params) {
if ($this->encryptionManager->isEnabled()) {
if ($params['itemType'] === 'file' || $params['itemType'] === 'folder') {
$path = Filesystem::getPath($params['fileSource']);
[$owner, $ownerPath] = $this->getOwnerPath($path);
$absPath = '/' . $owner . '/files/' . $ownerPath;
$this->update($absPath);
}
}
}
/**
* inform encryption module that a file was restored from the trash bin,
* e.g. to update the encryption keys
*
* @param array $params
*/
public function postRestore($params) {
if ($this->encryptionManager->isEnabled()) {
$path = Filesystem::normalizePath('/' . $this->uid . '/files/' . $params['filePath']);
$this->update($path);
}
}
/**
* inform encryption module that a file was renamed,
* e.g. to update the encryption keys
*
* @param array $params
*/
public function postRename($params) {
$source = $params['oldpath'];
$target = $params['newpath'];
if (
$this->encryptionManager->isEnabled() &&
dirname($source) !== dirname($target)
) {
[$owner, $ownerPath] = $this->getOwnerPath($target);
$absPath = '/' . $owner . '/files/' . $ownerPath;
$this->update($absPath);
}
}
/**
* get owner and path relative to data/<owner>/files
*
* @param string $path path to file for current user
* @return array ['owner' => $owner, 'path' => $path]
* @throw \InvalidArgumentException
*/
protected function getOwnerPath($path) {
$info = Filesystem::getFileInfo($path);
$owner = Filesystem::getOwner($path);
$view = new View('/' . $owner . '/files');
$path = $view->getPath($info->getId());
if ($path === null) {
throw new InvalidArgumentException('No file found for ' . $info->getId());
}
return [$owner, $path];
}
/**
* notify encryption module about added/removed users from a file/folder
*
* @param string $path relative to data/
* @throws Exceptions\ModuleDoesNotExistsException
*/
public function update($path) {
$encryptionModule = $this->encryptionManager->getEncryptionModule();
// if the encryption module doesn't encrypt the files on a per-user basis
// we have nothing to do here.
if ($encryptionModule->needDetailedAccessList() === false) {
return;
}
// if a folder was shared, get a list of all (sub-)folders
if ($this->view->is_dir($path)) {
$allFiles = $this->util->getAllFiles($path);
} else {
$allFiles = [$path];
}
foreach ($allFiles as $file) {
$usersSharing = $this->file->getAccessList($file);
try {
$encryptionModule->update($file, $this->uid, $usersSharing);
} catch (GenericEncryptionException $e) {
// If the update of an individual file fails e.g. due to a corrupt key we should continue the operation and just log the failure
$this->logger->error('Failed to update encryption module for ' . $this->uid . ' ' . $file, [ 'exception' => $e ]);
}
}
}
}
@@ -0,0 +1,388 @@
<?php
/**
* @copyright Copyright (c) 2016, ownCloud, Inc.
*
* @author Bjoern Schiessle <bjoern@schiessle.org>
* @author Björn Schießle <bjoern@schiessle.org>
* @author Christoph Wurst <christoph@winzerhof-wurst.at>
* @author Jan-Christoph Borchardt <hey@jancborchardt.net>
* @author Morris Jobke <hey@morrisjobke.de>
* @author Roeland Jago Douma <roeland@famdouma.nl>
* @author Thomas Müller <thomas.mueller@tmit.eu>
*
* @license AGPL-3.0
*
* This code is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License, version 3,
* along with this program. If not, see <http://www.gnu.org/licenses/>
*
*/
namespace OC\Encryption;
use OC\Encryption\Exceptions\EncryptionHeaderKeyExistsException;
use OC\Encryption\Exceptions\EncryptionHeaderToLargeException;
use OC\Encryption\Exceptions\ModuleDoesNotExistsException;
use OC\Files\Filesystem;
use OC\Files\View;
use OCP\Encryption\IEncryptionModule;
use OCP\Files\Mount\ISystemMountPoint;
use OCP\IConfig;
use OCP\IGroupManager;
use OCP\IUser;
use OCP\IUserManager;
class Util {
public const HEADER_START = 'HBEGIN';
public const HEADER_END = 'HEND';
public const HEADER_PADDING_CHAR = '-';
public const HEADER_ENCRYPTION_MODULE_KEY = 'oc_encryption_module';
/**
* block size will always be 8192 for a PHP stream
* @see https://bugs.php.net/bug.php?id=21641
* @var integer
*/
protected $headerSize = 8192;
/**
* block size will always be 8192 for a PHP stream
* @see https://bugs.php.net/bug.php?id=21641
* @var integer
*/
protected $blockSize = 8192;
/** @var View */
protected $rootView;
/** @var array */
protected $ocHeaderKeys;
/** @var IConfig */
protected $config;
/** @var array paths excluded from encryption */
protected array $excludedPaths = [];
protected IGroupManager $groupManager;
protected IUserManager $userManager;
/**
*
* @param View $rootView
* @param IConfig $config
*/
public function __construct(
View $rootView,
IUserManager $userManager,
IGroupManager $groupManager,
IConfig $config) {
$this->ocHeaderKeys = [
self::HEADER_ENCRYPTION_MODULE_KEY
];
$this->rootView = $rootView;
$this->userManager = $userManager;
$this->groupManager = $groupManager;
$this->config = $config;
$this->excludedPaths[] = 'files_encryption';
$this->excludedPaths[] = 'appdata_' . $config->getSystemValueString('instanceid');
$this->excludedPaths[] = 'files_external';
}
/**
* read encryption module ID from header
*
* @param array $header
* @return string
* @throws ModuleDoesNotExistsException
*/
public function getEncryptionModuleId(array $header = null) {
$id = '';
$encryptionModuleKey = self::HEADER_ENCRYPTION_MODULE_KEY;
if (isset($header[$encryptionModuleKey])) {
$id = $header[$encryptionModuleKey];
} elseif (isset($header['cipher'])) {
if (class_exists('\OCA\Encryption\Crypto\Encryption')) {
// fall back to default encryption if the user migrated from
// ownCloud <= 8.0 with the old encryption
$id = \OCA\Encryption\Crypto\Encryption::ID;
} else {
throw new ModuleDoesNotExistsException('Default encryption module missing');
}
}
return $id;
}
/**
* create header for encrypted file
*
* @param array $headerData
* @param IEncryptionModule $encryptionModule
* @return string
* @throws EncryptionHeaderToLargeException if header has to many arguments
* @throws EncryptionHeaderKeyExistsException if header key is already in use
*/
public function createHeader(array $headerData, IEncryptionModule $encryptionModule) {
$header = self::HEADER_START . ':' . self::HEADER_ENCRYPTION_MODULE_KEY . ':' . $encryptionModule->getId() . ':';
foreach ($headerData as $key => $value) {
if (in_array($key, $this->ocHeaderKeys)) {
throw new EncryptionHeaderKeyExistsException($key);
}
$header .= $key . ':' . $value . ':';
}
$header .= self::HEADER_END;
if (strlen($header) > $this->getHeaderSize()) {
throw new EncryptionHeaderToLargeException();
}
$paddedHeader = str_pad($header, $this->headerSize, self::HEADER_PADDING_CHAR, STR_PAD_RIGHT);
return $paddedHeader;
}
/**
* go recursively through a dir and collect all files and sub files.
*
* @param string $dir relative to the users files folder
* @return array with list of files relative to the users files folder
*/
public function getAllFiles($dir) {
$result = [];
$dirList = [$dir];
while ($dirList) {
$dir = array_pop($dirList);
$content = $this->rootView->getDirectoryContent($dir);
foreach ($content as $c) {
if ($c->getType() === 'dir') {
$dirList[] = $c->getPath();
} else {
$result[] = $c->getPath();
}
}
}
return $result;
}
/**
* check if it is a file uploaded by the user stored in data/user/files
* or a metadata file
*
* @param string $path relative to the data/ folder
* @return boolean
*/
public function isFile($path) {
$parts = explode('/', Filesystem::normalizePath($path), 4);
if (isset($parts[2]) && $parts[2] === 'files') {
return true;
}
return false;
}
/**
* return size of encryption header
*
* @return integer
*/
public function getHeaderSize() {
return $this->headerSize;
}
/**
* return size of block read by a PHP stream
*
* @return integer
*/
public function getBlockSize() {
return $this->blockSize;
}
/**
* get the owner and the path for the file relative to the owners files folder
*
* @param string $path
* @return array{0: string, 1: string}
* @throws \BadMethodCallException
*/
public function getUidAndFilename($path) {
$parts = explode('/', $path);
$uid = '';
if (count($parts) > 2) {
$uid = $parts[1];
}
if (!$this->userManager->userExists($uid)) {
throw new \BadMethodCallException(
'path needs to be relative to the system wide data folder and point to a user specific file'
);
}
$ownerPath = implode('/', array_slice($parts, 2));
return [$uid, Filesystem::normalizePath($ownerPath)];
}
/**
* 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 function stripPartialFileExtension($path) {
$extension = pathinfo($path, PATHINFO_EXTENSION);
if ($extension === 'part') {
$newLength = strlen($path) - 5; // 5 = strlen(".part")
$fPath = substr($path, 0, $newLength);
// if path also contains a transaction id, we remove it too
$extension = pathinfo($fPath, PATHINFO_EXTENSION);
if (substr($extension, 0, 12) === 'ocTransferId') { // 12 = strlen("ocTransferId")
$newLength = strlen($fPath) - strlen($extension) - 1;
$fPath = substr($fPath, 0, $newLength);
}
return $fPath;
} else {
return $path;
}
}
public function getUserWithAccessToMountPoint($users, $groups) {
$result = [];
if ($users === [] && $groups === []) {
$users = $this->userManager->search('', null, null);
$result = array_map(function (IUser $user) {
return $user->getUID();
}, $users);
} else {
$result = array_merge($result, $users);
$groupManager = $this->groupManager;
foreach ($groups as $group) {
$groupObject = $groupManager->get($group);
if ($groupObject) {
$foundUsers = $groupObject->searchUsers('', -1, 0);
$userIds = [];
foreach ($foundUsers as $user) {
$userIds[] = $user->getUID();
}
$result = array_merge($result, $userIds);
}
}
}
return $result;
}
/**
* check if the file is stored on a system wide mount point
* @param string $path relative to /data/user with leading '/'
* @param string $uid
* @return boolean
*/
public function isSystemWideMountPoint(string $path, string $uid) {
$mount = Filesystem::getMountManager()->find('/' . $uid . $path);
return $mount instanceof ISystemMountPoint;
}
/**
* check if it is a path which is excluded by ownCloud from encryption
*
* @param string $path
* @return boolean
*/
public function isExcluded($path) {
$normalizedPath = Filesystem::normalizePath($path);
$root = explode('/', $normalizedPath, 4);
if (count($root) > 1) {
// detect alternative key storage root
$rootDir = $this->getKeyStorageRoot();
if ($rootDir !== '' &&
str_starts_with(Filesystem::normalizePath($path), Filesystem::normalizePath($rootDir))
) {
return true;
}
//detect system wide folders
if (in_array($root[1], $this->excludedPaths)) {
return true;
}
// detect user specific folders
if ($this->userManager->userExists($root[1])
&& in_array($root[2], $this->excludedPaths)) {
return true;
}
}
return false;
}
/**
* Check if recovery key is enabled for user
*/
public function recoveryEnabled(string $uid): bool {
$enabled = $this->config->getUserValue($uid, 'encryption', 'recovery_enabled', '0');
return $enabled === '1';
}
/**
* Set new key storage root
*
* @param string $root new key store root relative to the data folder
*/
public function setKeyStorageRoot(string $root): void {
$this->config->setAppValue('core', 'encryption_key_storage_root', $root);
}
/**
* Get key storage root
*
* @return string key storage root
*/
public function getKeyStorageRoot(): string {
return $this->config->getAppValue('core', 'encryption_key_storage_root', '');
}
/**
* parse raw header to array
*
* @param string $rawHeader
* @return array
*/
public function parseRawHeader(string $rawHeader) {
$result = [];
if (str_starts_with($rawHeader, Util::HEADER_START)) {
$header = $rawHeader;
$endAt = strpos($header, Util::HEADER_END);
if ($endAt !== false) {
$header = substr($header, 0, $endAt + strlen(Util::HEADER_END));
// +1 to not start with an ':' which would result in empty element at the beginning
$exploded = explode(':', substr($header, strlen(Util::HEADER_START) + 1));
$element = array_shift($exploded);
while ($element !== Util::HEADER_END && $element !== null) {
$result[$element] = array_shift($exploded);
$element = array_shift($exploded);
}
}
}
return $result;
}
}