diff --git a/lib/unstable/Sharing/ShareUser.php b/lib/unstable/Sharing/ShareUser.php index 392eb870b122d..c1b1016b2042b 100644 --- a/lib/unstable/Sharing/ShareUser.php +++ b/lib/unstable/Sharing/ShareUser.php @@ -10,6 +10,7 @@ namespace NCU\Sharing; use NCU\Sharing\Icon\ShareIconURL; +use NCU\Sharing\Recipient\TShareRecipientTypeDisplayNameAddressBook; use OCP\AppFramework\Attribute\Consumable; use OCP\IUserManager; use RuntimeException; @@ -20,6 +21,8 @@ */ #[Consumable(since: '35.0.0')] final readonly class ShareUser { + use TShareRecipientTypeDisplayNameAddressBook; + /** * @experimental 35.0.0 */ @@ -46,14 +49,18 @@ public function isCurrentUser(ShareAccessContext $accessContext): bool { * @experimental 35.0.0 */ public function format(IUserManager $userManager): array { - if ($this->instance !== null) { - // TODO: Support federation - throw new RuntimeException('Currently only local users are supported.'); + if ($this->instance === null) { + $displayName = $userManager->getDisplayName($this->userId); + } else { + $displayName = $this->getRecipientDisplayNameFromAddressBook($this->userId . '@' . $this->instance, 'CLOUD'); } - $displayName = $userManager->getDisplayName($this->userId); - if ($displayName === null) { - throw new RuntimeException('No display name for user ' . $this->userId); + $displayName ??= $this->userId; + + $id = $this->userId; + if ($this->instance !== null) { + // The instance could contain https:// or http:// + $id .= '@' . urlencode($this->instance); } return [ @@ -61,8 +68,8 @@ public function format(IUserManager $userManager): array { 'instance' => $this->instance, 'display_name' => $displayName, 'icon' => (new ShareIconURL( - $userManager->getAvatarUrlLight($this->userId, 64), - $userManager->getAvatarUrlDark($this->userId, 64), + $userManager->getAvatarUrlLight($id, 64), + $userManager->getAvatarUrlDark($id, 64), ))->format(), ]; } diff --git a/tests/lib/Sharing/AbstractSharingManagerTests.php b/tests/lib/Sharing/AbstractSharingManagerTests.php index a208b00db66ae..df4ec3fe5cd35 100644 --- a/tests/lib/Sharing/AbstractSharingManagerTests.php +++ b/tests/lib/Sharing/AbstractSharingManagerTests.php @@ -9,6 +9,7 @@ namespace Test\Sharing; +use NCU\Sharing\ISharingBackend; use NCU\Sharing\ISharingManager; use NCU\Sharing\ISharingRegistry; use NCU\Sharing\Permission\SharePermission; @@ -32,6 +33,7 @@ use OCP\IUserManager; use OCP\L10N\IFactory; use OCP\Server; +use OCP\Snowflake\ISnowflakeGenerator; use PHPUnit\Framework\Attributes\DataProvider; use Test\TestCase; @@ -737,6 +739,49 @@ public function testCreateShare(): void { ], $share); } + public function testCreateShareRemoteOwner(): void { + $accessContext = new ShareAccessContext(overrideChecks: true); + + $before = $this->manager->getTime(); + $id = Server::get(ISnowflakeGenerator::class)->nextId(); + $this->dbConnection->beginTransaction(); + Server::get(ISharingBackend::class)->createShare($id, new ShareUser('owner', 'https://example.com'), $this->manager->getTime()); + $this->dbConnection->commit(); + $share = $this->getShare($accessContext, $id); + $after = $this->manager->getTime(); + $this->assertDateBetween($before, $after, $this->parseTime($share['last_updated'])); + unset($share['last_updated']); + $this->assertEquals([ + 'id' => $id, + 'owner' => [ + 'user_id' => 'owner', + 'instance' => 'https://example.com', + 'display_name' => 'owner', + 'icon' => [ + 'light' => 'http://localhost/index.php/avatar/owner@https%253A%2F%2Fexample.com/64', + 'dark' => 'http://localhost/index.php/avatar/owner@https%253A%2F%2Fexample.com/64/dark', + ], + ], + 'state' => ShareState::Draft->value, + 'user_status' => null, + 'sources' => [], + 'recipients' => [], + 'properties' => [], + 'permissions' => [ + [ + 'class' => ReshareSharePermissionType::class, + 'source_class' => null, + 'display_name' => 'Share with others', + 'hint' => null, + 'presets' => [], + 'enabled' => false, + 'priority' => 90, + ], + ], + 'permission_preset' => null, + ], $share); + } + /** * @return list, list, list, list, ?string}> */ @@ -1061,6 +1106,43 @@ public function testAddShareRecipientInteractionRestricted(ShareRecipient $recip $eventDispatcher->removeListener(RestrictInteractionEvent::class, $listener); } + public function testAddShareRecipientByRemoteInitiator(): void { + $accessContext = new ShareAccessContext($this->owner); + + $before = $this->manager->getTime(); + $this->dbConnection->beginTransaction(); + $share = $this->manager->createShare($accessContext); + Server::get(ISharingBackend::class)->addShareRecipient($share->id, new ShareRecipient(TestShareRecipientType1::class, 'recipient1', null, $this->manager->generateSecret(), new ShareUser('initiator', 'https://example.com'))); + $this->dbConnection->commit(); + $after = $this->manager->getTime(); + $formatted = $this->getShare($accessContext, $share->id); + $this->assertDateBetween($before, $after, $this->parseTime($formatted['last_updated'])); + $this->assertEquals([ + [ + 'class' => TestShareRecipientType1::class, + 'value' => 'recipient1', + 'instance' => null, + 'display_name' => 'Recipient 1', + 'icon' => [ + 'svg' => '', + ], + 'secret' => [ + 'updatable' => false, + ], + 'initiator' => [ + 'user_id' => 'initiator', + 'instance' => 'https://example.com', + 'display_name' => 'initiator', + 'icon' => [ + 'light' => 'http://localhost/index.php/avatar/initiator@https%253A%2F%2Fexample.com/64', + 'dark' => 'http://localhost/index.php/avatar/initiator@https%253A%2F%2Fexample.com/64/dark', + ], + ], + 'permissions' => [], + ], + ], $formatted['recipients']); + } + #[DataProvider(methodName: 'dataRecipient2')] public function testAddChildShareRecipientWithoutResharePermission(ShareRecipient $recipient2, string $recipient2DisplayName): void { $accessContext = new ShareAccessContext($this->owner); diff --git a/tests/lib/Sharing/ShareUserTest.php b/tests/lib/Sharing/ShareUserTest.php new file mode 100644 index 0000000000000..28becef903a61 --- /dev/null +++ b/tests/lib/Sharing/ShareUserTest.php @@ -0,0 +1,91 @@ +registry = Server::get(ISharingRegistry::class); + $this->registry->clear(); + + $this->user = $this->createUser('user', 'password'); + $this->userBackend->setDisplayName($this->user->getUID(), 'Local User'); + } + + #[\Override] + public function tearDown(): void { + $this->registry->clear(); + + parent::tearDown(); + } + + public function testFormatLocalUser(): void { + $user = new ShareUser($this->user->getUID(), null); + + $this->assertEquals([ + 'user_id' => 'user', + 'instance' => null, + 'display_name' => 'Local User', + 'icon' => [ + 'light' => 'http://localhost/index.php/avatar/user/64', + 'dark' => 'http://localhost/index.php/avatar/user/64/dark', + ], + ], $user->format(Server::get(IUserManager::class))); + } + + public function testFormatRemoteUser(): void { + $cardDavBackend = Server::get(CardDavBackend::class); + $contactsManager = Server::get(IManager::class); + $addressBookId = $cardDavBackend->createAddressBook('principals/users/' . $this->user->getUID(), 'Personal', []); + Server::get(ContactsManager::class)->setupContactsProvider($contactsManager, $this->user->getUID(), Server::get(IURLGenerator::class)); + + // Some other tests keeps a stray user in the session, which triggers the address book loader from the dav app. That one then fails, because the user actually doesn't exist anymore. + Server::get(IUserSession::class)->logout(); + + $contactsManager->createOrUpdate(['CLOUD' => $this->user->getUID() . '@https://example.com', 'FN' => 'Remote User'], (string)$addressBookId); + + $user = new ShareUser($this->user->getUID(), 'https://example.com'); + + $this->assertEquals([ + 'user_id' => 'user', + 'instance' => 'https://example.com', + 'display_name' => 'Remote User', + 'icon' => [ + 'light' => 'http://localhost/index.php/avatar/user@https%253A%2F%2Fexample.com/64', + 'dark' => 'http://localhost/index.php/avatar/user@https%253A%2F%2Fexample.com/64/dark', + ], + ], $user->format(Server::get(IUserManager::class))); + + $cardDavBackend->deleteAddressBook($addressBookId); + } +}