From 9e1914ce15d1d6ab53c9567a40bad6e57371c85b Mon Sep 17 00:00:00 2001 From: Maxence Lange Date: Thu, 26 Jan 2023 15:21:30 -0100 Subject: [PATCH] displayname on federated shares Signed-off-by: Maxence Lange --- apps/federatedfilesharing/lib/Notifier.php | 13 +- .../lib/OCM/CloudFederationProviderFiles.php | 39 ++++- .../lib/Activity/Providers/Base.php | 8 +- .../lib/Activity/Providers/RemoteShares.php | 3 +- .../lib/Controller/ShareAPIController.php | 134 +++++++++++++++--- 5 files changed, 162 insertions(+), 35 deletions(-) diff --git a/apps/federatedfilesharing/lib/Notifier.php b/apps/federatedfilesharing/lib/Notifier.php index 87c05e19e4bd1..563b121ce5bd8 100644 --- a/apps/federatedfilesharing/lib/Notifier.php +++ b/apps/federatedfilesharing/lib/Notifier.php @@ -102,8 +102,9 @@ public function prepare(INotification $notification, string $languageCode): INot $notification->setIcon($this->url->getAbsoluteURL($this->url->imagePath('core', 'actions/share.svg'))); $params = $notification->getSubjectParameters(); + $displayName = (count($params) > 3) ? $params[3] : ''; if ($params[0] !== $params[1] && $params[1] !== null) { - $remoteInitiator = $this->createRemoteUser($params[0]); + $remoteInitiator = $this->createRemoteUser($params[0], $displayName); $remoteOwner = $this->createRemoteUser($params[1]); $params[3] = $remoteInitiator['name'] . '@' . $remoteInitiator['server']; $params[4] = $remoteOwner['name'] . '@' . $remoteOwner['server']; @@ -121,7 +122,7 @@ public function prepare(INotification $notification, string $languageCode): INot ] ); } else { - $remoteOwner = $this->createRemoteUser($params[0]); + $remoteOwner = $this->createRemoteUser($params[0], $displayName); $params[3] = $remoteOwner['name'] . '@' . $remoteOwner['server']; $notification->setRichSubject( @@ -166,19 +167,21 @@ public function prepare(INotification $notification, string $languageCode): INot /** * @param string $cloudId + * @param string $displayName - overwrite display name + * * @return array */ - protected function createRemoteUser($cloudId, $displayName = null) { + protected function createRemoteUser(string $cloudId, string $displayName = '') { try { $resolvedId = $this->cloudIdManager->resolveCloudId($cloudId); - if ($displayName === null) { + if ($displayName === '') { $displayName = $this->getDisplayName($resolvedId); } $user = $resolvedId->getUser(); $server = $resolvedId->getRemote(); } catch (HintException $e) { $user = $cloudId; - $displayName = $cloudId; + $displayName = ($displayName !== '') ? $displayName : $cloudId; $server = ''; } diff --git a/apps/federatedfilesharing/lib/OCM/CloudFederationProviderFiles.php b/apps/federatedfilesharing/lib/OCM/CloudFederationProviderFiles.php index 370ef8dc32a56..477943b97caf9 100644 --- a/apps/federatedfilesharing/lib/OCM/CloudFederationProviderFiles.php +++ b/apps/federatedfilesharing/lib/OCM/CloudFederationProviderFiles.php @@ -55,10 +55,12 @@ use OCP\IURLGenerator; use OCP\IUserManager; use OCP\Notification\IManager as INotificationManager; +use OCP\Server; use OCP\Share\Exceptions\ShareNotFound; use OCP\Share\IManager; use OCP\Share\IShare; use OCP\Util; +use Psr\Container\ContainerExceptionInterface; class CloudFederationProviderFiles implements ICloudFederationProvider { @@ -250,26 +252,29 @@ public function shareReceived(ICloudFederationShare $share) { $this->externalShareManager->addShare($remote, $token, '', $name, $owner, $shareType,false, $shareWith, $remoteId); $shareId = \OC::$server->getDatabaseConnection()->lastInsertId('*PREFIX*share_external'); + // get DisplayName about the owner of the share + $ownerDisplayName = $this->getUserDisplayName($ownerFederatedId); + if ($shareType === IShare::TYPE_USER) { $event = $this->activityManager->generateEvent(); $event->setApp('files_sharing') ->setType('remote_share') - ->setSubject(RemoteShares::SUBJECT_REMOTE_SHARE_RECEIVED, [$ownerFederatedId, trim($name, '/')]) + ->setSubject(RemoteShares::SUBJECT_REMOTE_SHARE_RECEIVED, [$ownerFederatedId, trim($name, '/'), $ownerDisplayName]) ->setAffectedUser($shareWith) ->setObject('remote_share', $shareId, $name); \OC::$server->getActivityManager()->publish($event); - $this->notifyAboutNewShare($shareWith, $shareId, $ownerFederatedId, $sharedByFederatedId, $name); + $this->notifyAboutNewShare($shareWith, $shareId, $ownerFederatedId, $sharedByFederatedId, $name, $ownerDisplayName); } else { $groupMembers = $this->groupManager->get($shareWith)->getUsers(); foreach ($groupMembers as $user) { $event = $this->activityManager->generateEvent(); $event->setApp('files_sharing') ->setType('remote_share') - ->setSubject(RemoteShares::SUBJECT_REMOTE_SHARE_RECEIVED, [$ownerFederatedId, trim($name, '/')]) + ->setSubject(RemoteShares::SUBJECT_REMOTE_SHARE_RECEIVED, [$ownerFederatedId, trim($name, '/'), $ownerDisplayName]) ->setAffectedUser($user->getUID()) ->setObject('remote_share', $shareId, $name); \OC::$server->getActivityManager()->publish($event); - $this->notifyAboutNewShare($user->getUID(), $shareId, $ownerFederatedId, $sharedByFederatedId, $name); + $this->notifyAboutNewShare($user->getUID(), $shareId, $ownerFederatedId, $sharedByFederatedId, $name, $ownerDisplayName); } } return $shareId; @@ -335,13 +340,13 @@ private function mapShareTypeToNextcloud($shareType) { return $result; } - private function notifyAboutNewShare($shareWith, $shareId, $ownerFederatedId, $sharedByFederatedId, $name): void { + private function notifyAboutNewShare($shareWith, $shareId, $ownerFederatedId, $sharedByFederatedId, $name, $displayName): void { $notification = $this->notificationManager->createNotification(); $notification->setApp('files_sharing') ->setUser($shareWith) ->setDateTime(new \DateTime()) ->setObject('remote_share', $shareId) - ->setSubject('remote_share', [$ownerFederatedId, $sharedByFederatedId, trim($name, '/')]); + ->setSubject('remote_share', [$ownerFederatedId, $sharedByFederatedId, trim($name, '/'), $displayName]); $declineAction = $notification->createAction(); $declineAction->setLabel('decline') @@ -579,6 +584,8 @@ private function unshare($id, array $notification) { ->where($qb->expr()->eq('parent', $qb->createNamedParameter((int)$share['id']))); $qb->execute(); + $ownerDisplayName = $this->getUserDisplayName($owner->getId()); + if ((int)$share['share_type'] === IShare::TYPE_USER) { if ($share['accepted']) { $path = trim($mountpoint, '/'); @@ -594,7 +601,7 @@ private function unshare($id, array $notification) { $event = $this->activityManager->generateEvent(); $event->setApp('files_sharing') ->setType('remote_share') - ->setSubject(RemoteShares::SUBJECT_REMOTE_SHARE_UNSHARED, [$owner->getId(), $path]) + ->setSubject(RemoteShares::SUBJECT_REMOTE_SHARE_UNSHARED, [$owner->getId(), $path, $ownerDisplayName]) ->setAffectedUser($user) ->setObject('remote_share', (int)$share['id'], $path); \OC::$server->getActivityManager()->publish($event); @@ -824,4 +831,22 @@ private function isS2SEnabled($incoming = false) { public function getSupportedShareTypes() { return ['user', 'group']; } + + + private function getUserDisplayName(string $userId): string { + // check if gss is enabled and available + if (!$this->appManager->isInstalled('globalsiteselector') + || !class_exists('\OCA\GlobalSiteSelector\Service\SlaveService')) { + return ''; + } + + try { + $slaveService = Server::get(\OCA\GlobalSiteSelector\Service\SlaveService::class); + } catch (ContainerExceptionInterface $e) { + \OC::$server->getLogger()->logException($e); + return ''; + } + + return $slaveService->getUserDisplayName($userId, false); + } } diff --git a/apps/files_sharing/lib/Activity/Providers/Base.php b/apps/files_sharing/lib/Activity/Providers/Base.php index 4a2c6ac919e72..e9e1d870f9a5b 100644 --- a/apps/files_sharing/lib/Activity/Providers/Base.php +++ b/apps/files_sharing/lib/Activity/Providers/Base.php @@ -157,9 +157,11 @@ protected function getFile($parameter, IEvent $event = null) { /** * @param string $uid + * @param string $overwriteDisplayName - overwrite display name, only if user is not local + * * @return array */ - protected function getUser($uid) { + protected function getUser(string $uid, string $overwriteDisplayName = '') { // First try local user $displayName = $this->userManager->getDisplayName($uid); if ($displayName !== null) { @@ -176,7 +178,7 @@ protected function getUser($uid) { return [ 'type' => 'user', 'id' => $cloudId->getUser(), - 'name' => $this->getDisplayNameFromAddressBook($cloudId->getDisplayId()), + 'name' => (($overwriteDisplayName !== '') ? $overwriteDisplayName : $this->getDisplayNameFromAddressBook($cloudId->getDisplayId())), 'server' => $cloudId->getRemote(), ]; } @@ -185,7 +187,7 @@ protected function getUser($uid) { return [ 'type' => 'user', 'id' => $uid, - 'name' => $uid, + 'name' => (($overwriteDisplayName !== '') ? $overwriteDisplayName : $uid), ]; } diff --git a/apps/files_sharing/lib/Activity/Providers/RemoteShares.php b/apps/files_sharing/lib/Activity/Providers/RemoteShares.php index f1cc90f5e655f..e24645f8a26ce 100644 --- a/apps/files_sharing/lib/Activity/Providers/RemoteShares.php +++ b/apps/files_sharing/lib/Activity/Providers/RemoteShares.php @@ -115,13 +115,14 @@ protected function getParsedParameters(IEvent $event) { switch ($subject) { case self::SUBJECT_REMOTE_SHARE_RECEIVED: case self::SUBJECT_REMOTE_SHARE_UNSHARED: + $displayName = (count($parameters) > 2) ? $parameters[2] : ''; return [ 'file' => [ 'type' => 'pending-federated-share', 'id' => $parameters[1], 'name' => $parameters[1], ], - 'user' => $this->getUser($parameters[0]), + 'user' => $this->getUser($parameters[0], $displayName) ]; case self::SUBJECT_REMOTE_SHARE_ACCEPTED: case self::SUBJECT_REMOTE_SHARE_DECLINED: diff --git a/apps/files_sharing/lib/Controller/ShareAPIController.php b/apps/files_sharing/lib/Controller/ShareAPIController.php index ab318a81fc232..2daa31a6886bc 100644 --- a/apps/files_sharing/lib/Controller/ShareAPIController.php +++ b/apps/files_sharing/lib/Controller/ShareAPIController.php @@ -44,12 +44,13 @@ */ namespace OCA\Files_Sharing\Controller; +use Exception; use OC\Files\FileInfo; use OC\Files\Storage\Wrapper\Wrapper; +use OCA\Files\Helper; use OCA\Files_Sharing\Exceptions\SharingRightsException; use OCA\Files_Sharing\External\Storage; use OCA\Files_Sharing\SharedStorage; -use OCA\Files\Helper; use OCP\App\IAppManager; use OCP\AppFramework\Http\DataResponse; use OCP\AppFramework\OCS\OCSBadRequestException; @@ -59,9 +60,9 @@ use OCP\AppFramework\OCSController; use OCP\AppFramework\QueryException; use OCP\Constants; +use OCP\Files\Folder; use OCP\Files\InvalidPathException; use OCP\Files\IRootFolder; -use OCP\Files\Folder; use OCP\Files\Node; use OCP\Files\NotFoundException; use OCP\IConfig; @@ -74,7 +75,7 @@ use OCP\IUserManager; use OCP\Lock\ILockingProvider; use OCP\Lock\LockedException; -use OCP\Share; +use OCP\Server; use OCP\Share\Exceptions\GenericShareException; use OCP\Share\Exceptions\ShareNotFound; use OCP\Share\IManager; @@ -276,7 +277,7 @@ protected function formatShare(IShare $share, Node $recipientNode = null): array $result['url'] = $this->urlGenerator->linkToRouteAbsolute('files_sharing.sharecontroller.showShare', ['token' => $share->getToken()]); } elseif ($share->getShareType() === IShare::TYPE_REMOTE || $share->getShareType() === IShare::TYPE_REMOTE_GROUP) { $result['share_with'] = $share->getSharedWith(); - $result['share_with_displayname'] = $this->getDisplayNameFromAddressBook($share->getSharedWith(), 'CLOUD'); + $result['share_with_displayname'] = $this->getCachedFederatedDisplayName($share->getSharedWith()); $result['token'] = $share->getToken(); } elseif ($share->getShareType() === IShare::TYPE_EMAIL) { $result['share_with'] = $share->getSharedWith(); @@ -336,7 +337,7 @@ protected function formatShare(IShare $share, Node $recipientNode = null): array /** * Check if one of the users address books knows the exact property, if - * yes we return the full name. + * yes we return empty string. * * @param string $query * @param string $property @@ -357,9 +358,100 @@ private function getDisplayNameFromAddressBook(string $query, string $property): } } - return $query; + return ''; + } + + + /** + * @param array $shares + * @param array|null $updatedDisplayName + * + * @return array + */ + private function fixMissingDisplayName(array $shares, ?array $updatedDisplayName = null): array { + $userIds = $updated = []; + foreach ($shares as $share) { + // share is federated and share have no display name yet + if ($share['share_type'] === IShare::TYPE_REMOTE && $share['share_with_displayname'] === '') { + $userIds[] = $userId = $share['share_with']; + + if ($updatedDisplayName !== null && array_key_exists($userId, $updatedDisplayName)) { + $share['share_with_displayname'] = $updatedDisplayName[$userId]; + } + } + + // prepping userIds with displayName to be updated + $updated[] = $share; + } + + // if $updatedDisplayName is not null, it means we should have already fixed displayNames of the shares + if ($updatedDisplayName !== null) { + return $updated; + } + + // get displayName for the generated list of userId with no displayName + $displayNames = $this->retrieveFederatedDisplayName($userIds); + + // if no displayName are updated, we exit + if (empty($displayNames)) { + return $updated; + } + + // let's fix missing display name and returns all shares + return $this->fixMissingDisplayName($shares, $displayNames); + } + + + /** + * get displayName of a list of userIds from the lookup-server; through the globalsiteselector app. + * returns an array with userIds as keys and displayName as values. + * + * @param array $userIds + * @param bool $cacheOnly - do not reach LUS, get data from cache. + * + * @return array + * @psalm-suppress DeprecatedMethod + * @psalm-suppress UndefinedClass + */ + private function retrieveFederatedDisplayName(array $userIds, bool $cacheOnly = false): array { + // check if gss is enabled and available + if (count($userIds) === 0 + || !$this->appManager->isInstalled('globalsiteselector') + || !class_exists('\OCA\GlobalSiteSelector\Service\SlaveService')) { + return []; + } + + try { + $slaveService = Server::get(\OCA\GlobalSiteSelector\Service\SlaveService::class); + } catch (Exception $e) { + \OC::$server->getLogger()->logException($e); + return []; + } + + return $slaveService->getUsersDisplayName($userIds, $cacheOnly); } + + /** + * retrieve displayName from cache if available (should be used on federated shares) + * if not available in cache/lus, try for get from address-book, else returns empty string. + * + * @param string $userId + * @param bool $cacheOnly if true will not reach the lus but will only get data from cache + * + * @return string + */ + private function getCachedFederatedDisplayName(string $userId, bool $cacheOnly = true): string { + $details = $this->retrieveFederatedDisplayName([$userId], $cacheOnly); + if (array_key_exists($userId, $details)) { + return $details[$userId]; + } + + return $this->getDisplayNameFromAddressBook($userId, 'CLOUD'); + } + + + /** * Get a specific share by id * @@ -625,7 +717,7 @@ public function createShare( try { $expireDate = $this->parseDate($expireDate); $share->setExpirationDate($expireDate); - } catch (\Exception $e) { + } catch (Exception $e) { throw new OCSNotFoundException($this->l->t('Invalid date, date format must be YYYY-MM-DD')); } } @@ -644,10 +736,12 @@ public function createShare( try { $expireDate = $this->parseDate($expireDate); $share->setExpirationDate($expireDate); - } catch (\Exception $e) { + } catch (Exception $e) { throw new OCSNotFoundException($this->l->t('Invalid date, date format must be YYYY-MM-DD')); } } + + $share->setSharedWithDisplayName($this->getCachedFederatedDisplayName($shareWith, false)); } elseif ($shareType === IShare::TYPE_REMOTE_GROUP) { if (!$this->shareManager->outgoingServer2ServerGroupSharesAllowed()) { throw new OCSForbiddenException($this->l->t('Sharing %1$s failed because the back end does not allow shares from type %2$s', [$node->getPath(), $shareType])); @@ -663,7 +757,7 @@ public function createShare( try { $expireDate = $this->parseDate($expireDate); $share->setExpirationDate($expireDate); - } catch (\Exception $e) { + } catch (Exception $e) { throw new OCSNotFoundException($this->l->t('Invalid date, date format must be YYYY-MM-DD')); } } @@ -708,7 +802,7 @@ public function createShare( \OC::$server->getLogger()->logException($e); $code = $e->getCode() === 0 ? 403 : $e->getCode(); throw new OCSException($e->getHint(), $code); - } catch (\Exception $e) { + } catch (Exception $e) { \OC::$server->getLogger()->logException($e); throw new OCSForbiddenException($e->getMessage(), $e); } @@ -778,7 +872,6 @@ private function getSharesInDir(Node $folder): array { // filter out duplicate shares $known = []; - $formatted = $miniFormatted = []; $resharingRight = false; $known = []; @@ -798,7 +891,7 @@ private function getSharesInDir(Node $folder): array { if (!$resharingRight && $this->shareProviderResharingRights($this->currentUser, $share, $folder)) { $resharingRight = true; } - } catch (\Exception $e) { + } catch (Exception $e) { //Ignore this share } } @@ -942,6 +1035,9 @@ private function getFormattedShares( $formatted = $miniFormatted; } + // fix eventual missing display name from federated shares + $formatted = $this->fixMissingDisplayName($formatted); + if ($includeTags) { $formatted = Helper::populateTags($formatted, 'file_source', \OC::$server->getTagManager()); @@ -1206,7 +1302,7 @@ public function updateShare( } elseif ($expireDate !== null) { try { $expireDate = $this->parseDate($expireDate); - } catch (\Exception $e) { + } catch (Exception $e) { throw new OCSBadRequestException($e->getMessage(), $e); } $share->setExpirationDate($expireDate); @@ -1247,7 +1343,7 @@ public function updateShare( } elseif ($expireDate !== null) { try { $expireDate = $this->parseDate($expireDate); - } catch (\Exception $e) { + } catch (Exception $e) { throw new OCSBadRequestException($e->getMessage(), $e); } $share->setExpirationDate($expireDate); @@ -1259,7 +1355,7 @@ public function updateShare( } catch (GenericShareException $e) { $code = $e->getCode() === 0 ? 403 : $e->getCode(); throw new OCSException($e->getHint(), (int)$code); - } catch (\Exception $e) { + } catch (Exception $e) { throw new OCSBadRequestException($e->getMessage(), $e); } @@ -1341,7 +1437,7 @@ public function acceptShare(string $id): DataResponse { } catch (GenericShareException $e) { $code = $e->getCode() === 0 ? 403 : $e->getCode(); throw new OCSException($e->getHint(), (int)$code); - } catch (\Exception $e) { + } catch (Exception $e) { throw new OCSBadRequestException($e->getMessage(), $e); } @@ -1537,14 +1633,14 @@ protected function canDeleteShareFromSelf(\OCP\Share\IShare $share): bool { * * @param string $expireDate * - * @throws \Exception + * @throws Exception * @return \DateTime */ private function parseDate(string $expireDate): \DateTime { try { $date = new \DateTime(trim($expireDate, "\"")); - } catch (\Exception $e) { - throw new \Exception('Invalid date. Format must be YYYY-MM-DD'); + } catch (Exception $e) { + throw new Exception('Invalid date. Format must be YYYY-MM-DD'); } $date->setTime(0, 0, 0);