diff --git a/apps/files/lib/Service/OwnershipTransferService.php b/apps/files/lib/Service/OwnershipTransferService.php index 3499d809f2c..da60c12b49f 100644 --- a/apps/files/lib/Service/OwnershipTransferService.php +++ b/apps/files/lib/Service/OwnershipTransferService.php @@ -12,6 +12,7 @@ declare(strict_types=1); * @author Roeland Jago Douma * @author Sascha Wiswedel * @author Tobia De Koninck + * @author Ferdinand Thiessen * * @license GNU AGPL version 3 or any later version * @@ -33,6 +34,7 @@ declare(strict_types=1); namespace OCA\Files\Service; use Closure; +use OC\Encryption\Manager as EncryptionManager; use OC\Files\Filesystem; use OC\Files\View; use OCA\Files\Exception\TransferOwnershipException; @@ -41,6 +43,7 @@ use OCP\Files\Config\IUserMountCache; use OCP\Files\FileInfo; use OCP\Files\IHomeStorage; use OCP\Files\InvalidPathException; +use OCP\Files\IRootFolder; use OCP\Files\Mount\IMountManager; use OCP\IUser; use OCP\IUserManager; @@ -58,31 +61,16 @@ use function rtrim; class OwnershipTransferService { - /** @var IEncryptionManager */ - private $encryptionManager; + private IEncryptionManager|EncryptionManager $encryptionManager; - /** @var IShareManager */ - private $shareManager; - - /** @var IMountManager */ - private $mountManager; - - /** @var IUserMountCache */ - private $userMountCache; - - /** @var IUserManager */ - private $userManager; - - public function __construct(IEncryptionManager $manager, - IShareManager $shareManager, - IMountManager $mountManager, - IUserMountCache $userMountCache, - IUserManager $userManager) { - $this->encryptionManager = $manager; - $this->shareManager = $shareManager; - $this->mountManager = $mountManager; - $this->userMountCache = $userMountCache; - $this->userManager = $userManager; + public function __construct( + IEncryptionManager $encryptionManager, + private IShareManager $shareManager, + private IMountManager $mountManager, + private IUserMountCache $userMountCache, + private IUserManager $userManager, + ) { + $this->encryptionManager = $encryptionManager; } /** @@ -95,13 +83,15 @@ class OwnershipTransferService { * @throws TransferOwnershipException * @throws \OC\User\NoUserException */ - public function transfer(IUser $sourceUser, + public function transfer( + IUser $sourceUser, IUser $destinationUser, string $path, ?OutputInterface $output = null, bool $move = false, bool $firstLogin = false, - bool $transferIncomingShares = false): void { + bool $transferIncomingShares = false, + ): void { $output = $output ?? new NullOutput(); $sourceUid = $sourceUser->getUID(); $destinationUid = $destinationUser->getUID(); @@ -183,10 +173,12 @@ class OwnershipTransferService { $output ); + $destinationPath = $finalTarget . '/' . $path; // restore the shares $this->restoreShares( $sourceUid, $destinationUid, + $destinationPath, $shares, $output ); @@ -280,16 +272,35 @@ class OwnershipTransferService { } } - private function collectUsersShares(string $sourceUid, + /** + * @return array + */ + private function collectUsersShares( + string $sourceUid, OutputInterface $output, View $view, - string $path): array { + string $path, + ): array { $output->writeln("Collecting all share information for files and folders of $sourceUid ..."); $shares = []; $progress = new ProgressBar($output); - foreach ([IShare::TYPE_GROUP, IShare::TYPE_USER, IShare::TYPE_LINK, IShare::TYPE_REMOTE, IShare::TYPE_ROOM, IShare::TYPE_EMAIL, IShare::TYPE_CIRCLE, IShare::TYPE_DECK, IShare::TYPE_SCIENCEMESH] as $shareType) { + $normalizedPath = Filesystem::normalizePath($path); + + $supportedShareTypes = [ + IShare::TYPE_GROUP, + IShare::TYPE_USER, + IShare::TYPE_LINK, + IShare::TYPE_REMOTE, + IShare::TYPE_ROOM, + IShare::TYPE_EMAIL, + IShare::TYPE_CIRCLE, + IShare::TYPE_DECK, + IShare::TYPE_SCIENCEMESH, + ]; + + foreach ($supportedShareTypes as $shareType) { $offset = 0; while (true) { $sharePage = $this->shareManager->getSharesBy($sourceUid, $shareType, null, true, 50, $offset); @@ -298,17 +309,17 @@ class OwnershipTransferService { break; } if ($path !== "$sourceUid/files") { - $sharePage = array_filter($sharePage, function (IShare $share) use ($view, $path) { + $sharePage = array_filter($sharePage, function (IShare $share) use ($view, $normalizedPath) { try { $relativePath = $view->getPath($share->getNodeId()); - $singleFileTranfer = $view->is_file($path); + $singleFileTranfer = $view->is_file($normalizedPath); if ($singleFileTranfer) { - return Filesystem::normalizePath($relativePath) === Filesystem::normalizePath($path); + return Filesystem::normalizePath($relativePath) === $normalizedPath; } return mb_strpos( Filesystem::normalizePath($relativePath . '/', false), - Filesystem::normalizePath($path . '/', false)) === 0; + $normalizedPath . '/') === 0; } catch (\Exception $e) { return false; } @@ -321,7 +332,11 @@ class OwnershipTransferService { $progress->finish(); $output->writeln(''); - return $shares; + + return array_map(fn (IShare $share) => [ + 'share' => $share, + 'suffix' => substr(Filesystem::normalizePath($view->getPath($share->getNodeId())), strlen($normalizedPath)), + ], $shares); } private function collectIncomingShares(string $sourceUid, @@ -384,14 +399,22 @@ class OwnershipTransferService { } } - private function restoreShares(string $sourceUid, + /** + * @param string $targetLocation New location of the transfered node + * @param array $shares previously collected share information + */ + private function restoreShares( + string $sourceUid, string $destinationUid, + string $targetLocation, array $shares, - OutputInterface $output) { + OutputInterface $output, + ):void { $output->writeln("Restoring shares ..."); $progress = new ProgressBar($output, count($shares)); + $rootFolder = \OCP\Server::get(IRootFolder::class); - foreach ($shares as $share) { + foreach ($shares as ['share' => $share, 'suffix' => $suffix]) { try { if ($share->getShareType() === IShare::TYPE_USER && $share->getSharedWith() === $destinationUid) { @@ -419,7 +442,19 @@ class OwnershipTransferService { // trigger refetching of the node so that the new owner and mountpoint are taken into account // otherwise the checks on the share update will fail due to the original node not being available in the new user scope $this->userMountCache->clear(); - $share->setNodeId($share->getNode()->getId()); + + try { + // Try to get the "old" id. + // Normally the ID is preserved, + // but for transferes between different storages the ID might change + $newNodeId = $share->getNode()->getId(); + } catch (\OCP\Files\NotFoundException) { + // ID has changed due to transfer between different storages + // Try to get the new ID from the target path and suffix of the share + $node = $rootFolder->get(Filesystem::normalizePath($targetLocation . '/' . $suffix)); + $newNodeId = $node->getId(); + } + $share->setNodeId($newNodeId); $this->shareManager->updateShare($share); }