diff --git a/apps/files_trashbin/lib/Trash/LegacyTrashBackend.php b/apps/files_trashbin/lib/Trash/LegacyTrashBackend.php index 2117596eda802..b39f50b0323a1 100644 --- a/apps/files_trashbin/lib/Trash/LegacyTrashBackend.php +++ b/apps/files_trashbin/lib/Trash/LegacyTrashBackend.php @@ -11,6 +11,7 @@ use OCA\Files_Trashbin\Helper; use OCA\Files_Trashbin\Storage; use OCA\Files_Trashbin\Trashbin; +use OCP\Federation\ICloudIdManager; use OCP\Files\FileInfo; use OCP\Files\Folder; use OCP\Files\IRootFolder; @@ -26,6 +27,7 @@ class LegacyTrashBackend implements ITrashBackend { public function __construct( private IRootFolder $rootFolder, private IUserManager $userManager, + private ICloudIdManager $cloudIdManager, ) { } @@ -38,7 +40,7 @@ private function mapTrashItem(FileInfo $file, IUser $user, ?ITrashItem $parent = $originalLocation = $file->getName(); } /** @psalm-suppress UndefinedInterfaceMethod */ - $deletedBy = $this->userManager->get($file['deletedBy']) ?? $parent?->getDeletedBy(); + $deletedBy = $this->resolveDeletedBy($file['deletedBy']) ?? $parent?->getDeletedBy(); $trashFilename = Trashbin::getTrashFilename($file->getName(), $file->getMtime()); return new TrashItem( $this, @@ -119,4 +121,29 @@ public function getTrashNodeById(IUser $user, int $fileId) { return null; } } + + /** + * Resolve the user that deleted a trash item. Files deleted by a federated share + * recipient only carry the recipient's remote cloud ID, which no local IUserManager + * backend can resolve, so fall back to a display-only user for the cloud ID in that + * case instead of leaving the item without an "Unknown" deleted by user. + */ + private function resolveDeletedBy(?string $uid): ?IUser { + if (!$uid) { + return null; + } + + $user = $this->userManager->get($uid); + if ($user !== null) { + return $user; + } + + try { + $cloudId = $this->cloudIdManager->resolveCloudId($uid); + } catch (\InvalidArgumentException $e) { + return null; + } + + return $this->userManager->getFederatedUser($cloudId); + } } diff --git a/lib/private/User/Manager.php b/lib/private/User/Manager.php index c24e4b8be7ba7..5560fd2cb5ffa 100644 --- a/lib/private/User/Manager.php +++ b/lib/private/User/Manager.php @@ -12,6 +12,7 @@ use OCP\Config\IUserConfig; use OCP\DB\QueryBuilder\IQueryBuilder; use OCP\EventDispatcher\IEventDispatcher; +use OCP\Federation\ICloudId; use OCP\HintException; use OCP\ICache; use OCP\ICacheFactory; @@ -851,4 +852,8 @@ public function getSeenUsers(int $offset = 0, ?int $limit = null): \Iterator { public function getExistingUser(string $userId, ?string $displayName = null): IUser { return new LazyUser($userId, $this, $displayName); } + + public function getFederatedUser(ICloudId $cloudId): IUser { + return new LazyUser($cloudId->getDisplayId(), $this, $cloudId->getDisplayId()); + } } diff --git a/lib/public/IUserManager.php b/lib/public/IUserManager.php index e1ffa34fe927d..71d11467c1cd4 100644 --- a/lib/public/IUserManager.php +++ b/lib/public/IUserManager.php @@ -267,4 +267,15 @@ public function getSeenUsers(int $offset = 0, ?int $limit = null): \Iterator; * @since 33.0.0 */ public function getExistingUser(string $userId, ?string $displayName = null): IUser; + + /** + * Get a read-only user from a cloud ID for showing the display name of a remote + * federation user (e.g. the "deleted by" user of a federated share) that has no + * local account. + * + * @param \OCP\Federation\ICloudId $federatedUserId A cloud ID of the federated user + * @return IUser + * @since 35.0.0 + */ + public function getFederatedUser(\OCP\Federation\ICloudId $cloudId): IUser; } diff --git a/tests/lib/User/ManagerTest.php b/tests/lib/User/ManagerTest.php index 17007aa8a24ed..b1585dd3259f7 100644 --- a/tests/lib/User/ManagerTest.php +++ b/tests/lib/User/ManagerTest.php @@ -15,6 +15,7 @@ use OC\User\User; use OCP\Config\IUserConfig; use OCP\EventDispatcher\IEventDispatcher; +use OCP\Federation\ICloudId; use OCP\ICache; use OCP\ICacheFactory; use OCP\IConfig; @@ -762,4 +763,19 @@ public function testGetExistingUser() { $this->assertEquals('nobody', $user->getUID()); $this->assertEquals('None', $user->getDisplayName()); } + + public function testGetFederatedUser(): void { + $userId = 'test@example.com'; + + $cloudId = $this->createMock(ICloudId::class); + $cloudId->expects($this->exactly(2)) + ->method('getDisplayId') + ->willReturn($userId); + + $manager = new Manager($this->config, $this->cacheFactory, $this->eventDispatcher, $this->logger); + $user = $manager->getFederatedUser($cloudId); + + $this->assertEquals($userId, $user->getUID()); + $this->assertEquals($userId, $user->getDisplayName()); + } }