diff --git a/lib/AppInfo/Application.php b/lib/AppInfo/Application.php index e95ac03cf..d0b793b96 100644 --- a/lib/AppInfo/Application.php +++ b/lib/AppInfo/Application.php @@ -54,6 +54,7 @@ use OCP\IGroupManager; use OCP\IRequest; use OCP\ISession; +use OCP\IUserManager; use OCP\IUserSession; use Psr\Container\ContainerInterface; use Psr\Log\LoggerInterface; @@ -126,7 +127,9 @@ public function register(IRegistrationContext $context): void { $c->get(MountProvider::class), $c->get(ACLManagerFactory::class), $c->get(IRootFolder::class), - $c->get(LoggerInterface::class) + $c->get(LoggerInterface::class), + $c->get(IUserManager::class), + $c->get(IUserSession::class), ); $hasVersionApp = interface_exists(\OCA\Files_Versions\Versions\IVersionBackend::class); if ($hasVersionApp) { diff --git a/lib/Migration/Version300000Date20240905185515.php b/lib/Migration/Version300000Date20240905185515.php new file mode 100644 index 000000000..029a39ea6 --- /dev/null +++ b/lib/Migration/Version300000Date20240905185515.php @@ -0,0 +1,47 @@ +hasTable('group_folders_trash')) { + return null; + } + + $table = $schema->getTable('group_folders_trash'); + + if ($table->hasColumn('deleted_by')) { + return null; + } + + $table->addColumn('deleted_by', Types::STRING, [ + 'notnull' => false, + 'length' => 64, + ]); + + return $schema; + } +} diff --git a/lib/Trash/GroupTrashItem.php b/lib/Trash/GroupTrashItem.php index f19e4695c..7371f0487 100644 --- a/lib/Trash/GroupTrashItem.php +++ b/lib/Trash/GroupTrashItem.php @@ -22,9 +22,10 @@ public function __construct( string $trashPath, FileInfo $fileInfo, IUser $user, - string $mountPoint + string $mountPoint, + private ?IUser $deletedBy, ) { - parent::__construct($backend, $originalLocation, $deletedTime, $trashPath, $fileInfo, $user, null); + parent::__construct($backend, $originalLocation, $deletedTime, $trashPath, $fileInfo, $user, $deletedBy); $this->mountPoint = $mountPoint; } diff --git a/lib/Trash/TrashBackend.php b/lib/Trash/TrashBackend.php index b113dac22..5566eeb14 100644 --- a/lib/Trash/TrashBackend.php +++ b/lib/Trash/TrashBackend.php @@ -24,35 +24,25 @@ use OCP\Files\NotPermittedException; use OCP\Files\Storage\IStorage; use OCP\IUser; +use OCP\IUserManager; +use OCP\IUserSession; use Psr\Log\LoggerInterface; class TrashBackend implements ITrashBackend { - private FolderManager $folderManager; - private TrashManager $trashManager; - private Folder $appFolder; - private MountProvider $mountProvider; - private ACLManagerFactory $aclManagerFactory; /** @var ?VersionsBackend */ private $versionsBackend = null; - private IRootFolder $rootFolder; - private LoggerInterface $logger; public function __construct( - FolderManager $folderManager, - TrashManager $trashManager, - Folder $appFolder, - MountProvider $mountProvider, - ACLManagerFactory $aclManagerFactory, - IRootFolder $rootFolder, - LoggerInterface $logger + private FolderManager $folderManager, + private TrashManager $trashManager, + private Folder $appFolder, + private MountProvider $mountProvider, + private ACLManagerFactory $aclManagerFactory, + private IRootFolder $rootFolder, + private LoggerInterface $logger, + private IUserManager $userManager, + private IUserSession $userSession, ) { - $this->folderManager = $folderManager; - $this->trashManager = $trashManager; - $this->appFolder = $appFolder; - $this->mountProvider = $mountProvider; - $this->aclManagerFactory = $aclManagerFactory; - $this->rootFolder = $rootFolder; - $this->logger = $logger; } public function setVersionsBackend(VersionsBackend $versionsBackend): void { @@ -212,7 +202,7 @@ public function moveToTrash(IStorage $storage, string $internalPath): bool { [$unJailedStorage, $unJailedInternalPath] = $this->unwrapJails($storage, $internalPath); $targetInternalPath = $trashFolder->getInternalPath() . '/' . $trashName; if ($trashStorage->moveFromStorage($unJailedStorage, $unJailedInternalPath, $targetInternalPath)) { - $this->trashManager->addTrashItem($folderId, $name, $time, $internalPath, $fileEntry->getId()); + $this->trashManager->addTrashItem($folderId, $name, $time, $internalPath, $fileEntry->getId(), $this->userSession->getUser()->getUID()); if ($trashStorage->getCache()->getId($targetInternalPath) !== $fileEntry->getId()) { $trashStorage->getCache()->moveFromCache($unJailedStorage->getCache(), $unJailedInternalPath, $targetInternalPath); } @@ -328,6 +318,7 @@ private function getTrashForFolders(IUser $user, array $folders): array { $key = $folderId . '/' . $name . '/' . $timestamp; $originalLocation = isset($indexedRows[$key]) ? $indexedRows[$key]['original_location'] : ''; + $deletedBy = isset($indexedRows[$key]) ? $indexedRows[$key]['deleted_by'] : ''; if ($folderHasAcl) { // if we for any reason lost track of the original location, hide the item for non-managers as a fail-safe @@ -357,7 +348,8 @@ private function getTrashForFolders(IUser $user, array $folders): array { '/' . $folderId . '/' . $item->getName(), $info, $user, - $mountPoint + $mountPoint, + $this->userManager->get($deletedBy), ); } } diff --git a/lib/Trash/TrashManager.php b/lib/Trash/TrashManager.php index 885498b4b..b44834387 100644 --- a/lib/Trash/TrashManager.php +++ b/lib/Trash/TrashManager.php @@ -22,14 +22,14 @@ public function __construct( */ public function listTrashForFolders(array $folderIds): array { $query = $this->connection->getQueryBuilder(); - $query->select(['trash_id', 'name', 'deleted_time', 'original_location', 'folder_id', 'file_id']) + $query->select(['trash_id', 'name', 'deleted_time', 'original_location', 'folder_id', 'file_id', 'deleted_by']) ->from('group_folders_trash') ->orderBy('deleted_time') ->where($query->expr()->in('folder_id', $query->createNamedParameter($folderIds, IQueryBuilder::PARAM_INT_ARRAY))); return $query->executeQuery()->fetchAll(); } - public function addTrashItem(int $folderId, string $name, int $deletedTime, string $originalLocation, int $fileId): void { + public function addTrashItem(int $folderId, string $name, int $deletedTime, string $originalLocation, int $fileId, string $deletedBy): void { $query = $this->connection->getQueryBuilder(); $query->insert('group_folders_trash') ->values([ @@ -37,14 +37,15 @@ public function addTrashItem(int $folderId, string $name, int $deletedTime, stri 'name' => $query->createNamedParameter($name), 'deleted_time' => $query->createNamedParameter($deletedTime, IQueryBuilder::PARAM_INT), 'original_location' => $query->createNamedParameter($originalLocation), - 'file_id' => $query->createNamedParameter($fileId, IQueryBuilder::PARAM_INT) + 'file_id' => $query->createNamedParameter($fileId, IQueryBuilder::PARAM_INT), + 'deleted_by' => $query->createNamedParameter($deletedBy), ]); $query->executeStatement(); } public function getTrashItemByFileId(int $fileId): ?array { $query = $this->connection->getQueryBuilder(); - $query->select(['trash_id', 'name', 'deleted_time', 'original_location', 'folder_id']) + $query->select(['trash_id', 'name', 'deleted_time', 'original_location', 'folder_id', 'deleted_by']) ->from('group_folders_trash') ->where($query->expr()->eq('file_id', $query->createNamedParameter($fileId, IQueryBuilder::PARAM_INT))); return $query->executeQuery()->fetch() ?: null; @@ -52,7 +53,7 @@ public function getTrashItemByFileId(int $fileId): ?array { public function getTrashItemByFileName(int $folderId, string $name, int $deletedTime): ?array { $query = $this->connection->getQueryBuilder(); - $query->select(['trash_id', 'name', 'deleted_time', 'original_location', 'folder_id']) + $query->select(['trash_id', 'name', 'deleted_time', 'original_location', 'folder_id', 'deleted_by']) ->from('group_folders_trash') ->where($query->expr()->eq('folder_id', $query->createNamedParameter($folderId, IQueryBuilder::PARAM_INT))) ->andWhere($query->expr()->eq('name', $query->createNamedParameter($name))) diff --git a/tests/Trash/TrashBackendTest.php b/tests/Trash/TrashBackendTest.php index 0bed10a63..addaa2062 100644 --- a/tests/Trash/TrashBackendTest.php +++ b/tests/Trash/TrashBackendTest.php @@ -106,6 +106,8 @@ private function createNoReadRule(string $userId, int $fileId): Rule { } public function testHideTrashItemAcl() { + $this->loginAsUser('manager'); + $restricted = $this->managerUserFolder->newFile("{$this->folderName}/restricted.txt", "content"); $this->ruleManager->saveRule($this->createNoReadRule("normal", $restricted->getId())); @@ -120,9 +122,13 @@ public function testHideTrashItemAcl() { // only the manager can see the deleted file $this->assertCount(1, $this->trashBackend->listTrashRoot($this->managerUser)); $this->assertCount(0, $this->trashBackend->listTrashRoot($this->normalUser)); + + $this->logout(); } public function testHideItemInDeletedFolderAcl() { + $this->loginAsUser('manager'); + $folder = $this->managerUserFolder->newFolder("{$this->folderName}/folder"); $folder->newFile("file.txt", "content1"); $restrictedChild = $folder->newFile("restricted.txt", "content2"); @@ -145,9 +151,13 @@ public function testHideItemInDeletedFolderAcl() { // only the manager can see the restricted child, both can see the un-restricted child $this->assertCount(2, $this->trashBackend->listTrashFolder($managerTrashFolder)); $this->assertCount(1, $this->trashBackend->listTrashFolder($normalTrashFolder)); + + $this->logout(); } public function testHideDeletedTrashItemInDeletedFolderAcl() { + $this->loginAsUser('manager'); + $folder = $this->managerUserFolder->newFolder("{$this->folderName}/restricted"); $child = $folder->newFile("file.txt", "content1"); $this->ruleManager->saveRule($this->createNoReadRule("normal", $folder->getId())); @@ -169,9 +179,13 @@ public function testHideDeletedTrashItemInDeletedFolderAcl() { // only the manager can see the deleted items $this->assertCount(2, $this->trashBackend->listTrashRoot($this->managerUser)); $this->assertCount(0, $this->trashBackend->listTrashRoot($this->normalUser)); + + $this->logout(); } public function testHideDeletedTrashItemInDeletedParentFolderAcl() { + $this->loginAsUser('manager'); + $parent = $this->managerUserFolder->newFolder("{$this->folderName}/parent"); $folder = $parent->newFolder("restricted"); $child = $folder->newFile("file.txt", "content1"); @@ -194,5 +208,7 @@ public function testHideDeletedTrashItemInDeletedParentFolderAcl() { // only the manager can see the deleted child, both can see the deleted parent $this->assertCount(2, $this->trashBackend->listTrashRoot($this->managerUser)); $this->assertCount(1, $this->trashBackend->listTrashRoot($this->normalUser)); + + $this->logout(); } }