diff --git a/apps/files_trashbin/lib/Trash/LegacyTrashBackend.php b/apps/files_trashbin/lib/Trash/LegacyTrashBackend.php index c4391c37f8cb5..3fca6c10e0c90 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, @@ -125,4 +127,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 c72d5f2e91442..5d95f6ca7d08a 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; @@ -894,4 +895,9 @@ public function getAvatarUrlDark(string $userId, int $size): string { return $url; } + + #[\Override] + 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 0cd6daa399e49..477ce10f2984d 100644 --- a/lib/public/IUserManager.php +++ b/lib/public/IUserManager.php @@ -282,4 +282,15 @@ public function getAvatarUrlLight(string $userId, int $size): string; * @since 34.0.0 */ public function getAvatarUrlDark(string $userId, int $size): string; + + /** + * 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 acb35bf7cf7c1..ac4a354aa0333 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; @@ -741,4 +742,18 @@ public function testGetAvatarUrlLight(): void { public function testGetAvatarUrlDark(): void { $this->assertEquals('http://localhost/index.php/avatar/userid/64/dark', $this->manager->getAvatarUrlDark('userid', 64)); } + + public function testGetFederatedUser(): void { + $userId = 'test@example.com'; + + $cloudId = $this->createMock(ICloudId::class); + $cloudId->expects($this->exactly(2)) + ->method('getDisplayId') + ->willReturn($userId); + + $user = $this->manager->getFederatedUser($cloudId); + + $this->assertEquals($userId, $user->getUID()); + $this->assertEquals($userId, $user->getDisplayName()); + } }