diff --git a/apps/dav/lib/Connector/Sabre/FilesPlugin.php b/apps/dav/lib/Connector/Sabre/FilesPlugin.php index 6b6f622a5a755..e3e38b4b1ff3b 100644 --- a/apps/dav/lib/Connector/Sabre/FilesPlugin.php +++ b/apps/dav/lib/Connector/Sabre/FilesPlugin.php @@ -343,7 +343,7 @@ public function handleGetProperties(PropFind $propFind, \Sabre\DAV\INode $node) if (!$owner) { return null; } else { - return $owner->getDisplayName(); + return $this->getUserDisplayName($owner); } }); @@ -598,4 +598,35 @@ public function sendFileIdHeader($filePath, \Sabre\DAV\INode $node = null) { } } } + + /** + * returns displayName based on IUser: + * - if user exists locally, get user display name + * - if federatedfilesharing is not available, returns set display name + * - else get display name from LUS (globalscale) + * + * @param IUser $owner + * + * @return string + */ + private function getUserDisplayName(IUser $owner): string { + try { + $userManager = \OCP\Server::get(IUserManager::class); + $user = $userManager->get($owner->getUID()); + if ($user !== null) { + return $user->getDisplayName(); + } + + $appManager = \OCP\Server::get(IAppManager::class); + if (!$appManager->isInstalled('federatedfilesharing')) { + return $owner->getDisplayName(); + } + + $cloudFederatedProviderFiles = \OCP\Server::get(CloudFederationProviderFiles::class); + } catch (ContainerExceptionInterface $e) { + return $owner->getDisplayName(); + } + + return $cloudFederatedProviderFiles->getUserDisplayName($owner->getDisplayName()); + } } 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..5e2cafe0ce047 100644 --- a/apps/federatedfilesharing/lib/OCM/CloudFederationProviderFiles.php +++ b/apps/federatedfilesharing/lib/OCM/CloudFederationProviderFiles.php @@ -55,10 +55,13 @@ 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; +use Psr\Log\LoggerInterface; class CloudFederationProviderFiles implements ICloudFederationProvider { @@ -250,26 +253,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 +341,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 +585,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 +602,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 +832,25 @@ private function isS2SEnabled($incoming = false) { public function getSupportedShareTypes() { return ['user', 'group']; } + + + public 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 (\Throwable $e) { + Server::get(LoggerInterface::class)->error( + $e->getMessage(), + ['exception' => $e] + ); + return ''; + } + + return $slaveService->getUserDisplayName($this->cloudIdManager->removeProtocolFromUrl($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 e1595a98ea31a..97df20976a10e 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,12 +75,14 @@ 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; use OCP\Share\IShare; use OCP\UserStatus\IManager as IUserStatusManager; +use Psr\Container\ContainerExceptionInterface; +use Psr\Log\LoggerInterface; /** * Class Share20OCS @@ -274,7 +277,11 @@ protected function formatShare(IShare $share, Node $recipientNode = null): array $result['token'] = $share->getToken(); $result['url'] = $this->urlGenerator->linkToRouteAbsolute('files_sharing.sharecontroller.showShare', ['token' => $share->getToken()]); - } elseif ($share->getShareType() === IShare::TYPE_REMOTE || $share->getShareType() === IShare::TYPE_REMOTE_GROUP) { + } elseif ($share->getShareType() === IShare::TYPE_REMOTE) { + $result['share_with'] = $share->getSharedWith(); + $result['share_with_displayname'] = $this->getCachedFederatedDisplayName($share->getSharedWith()); + $result['token'] = $share->getToken(); + } elseif ($share->getShareType() === IShare::TYPE_REMOTE_GROUP) { $result['share_with'] = $share->getSharedWith(); $result['share_with_displayname'] = $this->getDisplayNameFromAddressBook($share->getSharedWith(), 'CLOUD'); $result['token'] = $share->getToken(); @@ -344,7 +351,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. + * not we return the full name. * * @param string $query * @param string $property @@ -352,11 +359,20 @@ protected function formatShare(IShare $share, Node $recipientNode = null): array */ private function getDisplayNameFromAddressBook(string $query, string $property): string { // FIXME: If we inject the contacts manager it gets initialized before any address books are registered - $result = \OC::$server->getContactsManager()->search($query, [$property], [ - 'limit' => 1, - 'enumeration' => false, - 'strict_search' => true, - ]); + try { + $result = \OC::$server->getContactsManager()->search($query, [$property], [ + 'limit' => 1, + 'enumeration' => false, + 'strict_search' => true, + ]); + } catch (Exception $e) { + Server::get(LoggerInterface::class)->error( + $e->getMessage(), + ['exception' => $e] + ); + return $query; + } + foreach ($result as $r) { foreach ($r[$property] as $value) { if ($value === $query && $r['FN']) { @@ -368,6 +384,102 @@ private function getDisplayNameFromAddressBook(string $query, string $property): return $query; } + + /** + * @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'] ?? '') !== '' + && ($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 + * @throws ContainerExceptionInterface + */ + 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 (\Throwable $e) { + Server::get(LoggerInterface::class)->error( + $e->getMessage(), + ['exception' => $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]; + } + + $displayName = $this->getDisplayNameFromAddressBook($userId, 'CLOUD'); + return ($displayName === $userId) ? '' : $displayName; + } + + + /** * Get a specific share by id * @@ -633,7 +745,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')); } } @@ -652,10 +764,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])); @@ -671,7 +785,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')); } } @@ -722,7 +836,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); } @@ -793,7 +907,6 @@ private function getSharesInDir(Node $folder): array { // filter out duplicate shares $known = []; - $formatted = $miniFormatted = []; $resharingRight = false; $known = []; @@ -813,7 +926,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 } } @@ -957,6 +1070,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()); @@ -1221,7 +1337,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); @@ -1262,7 +1378,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); @@ -1274,7 +1390,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); } @@ -1356,7 +1472,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); } @@ -1569,14 +1685,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); diff --git a/lib/private/Federation/CloudIdManager.php b/lib/private/Federation/CloudIdManager.php index 85aae8e5ec529..82b711bd422dc 100644 --- a/lib/private/Federation/CloudIdManager.php +++ b/lib/private/Federation/CloudIdManager.php @@ -209,11 +209,12 @@ public function getCloudId(string $user, ?string $remote): ICloudId { * @param string $url * @return string */ - private function removeProtocolFromUrl($url) { - if (strpos($url, 'https://') === 0) { - return substr($url, strlen('https://')); - } elseif (strpos($url, 'http://') === 0) { - return substr($url, strlen('http://')); + public function removeProtocolFromUrl(string $url): string { + if (str_starts_with($url, 'https://')) { + return substr($url, 8); + } + if (str_starts_with($url, 'http://')) { + return substr($url, 7); } return $url; diff --git a/psalm.xml b/psalm.xml index cd50a2722dabe..786e0d28bfbe7 100644 --- a/psalm.xml +++ b/psalm.xml @@ -87,6 +87,7 @@ + @@ -130,6 +131,7 @@ +