]> source.dussan.org Git - nextcloud-server.git/commitdiff
display displayname on federated shares 39270/head
authorMaxence Lange <maxence@artificial-owl.com>
Mon, 10 Jul 2023 11:57:19 +0000 (10:57 -0100)
committerMaxence Lange <maxence@artificial-owl.com>
Mon, 10 Jul 2023 11:57:26 +0000 (10:57 -0100)
Signed-off-by: Maxence Lange <maxence@artificial-owl.com>
apps/federatedfilesharing/lib/Notifier.php
apps/federatedfilesharing/lib/OCM/CloudFederationProviderFiles.php
apps/files_sharing/lib/Activity/Providers/Base.php
apps/files_sharing/lib/Activity/Providers/RemoteShares.php
apps/files_sharing/lib/Controller/ShareAPIController.php
lib/private/Federation/CloudIdManager.php
lib/public/Federation/ICloudIdManager.php
psalm.xml

index 87c05e19e4bd1abe19230793fdfe57a8c92e3f63..563b121ce5bd8e9bfebf21793e9aa8fc35532efd 100644 (file)
@@ -102,8 +102,9 @@ class Notifier implements INotifier {
                                $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 @@ class Notifier implements INotifier {
                                                ]
                                        );
                                } else {
-                                       $remoteOwner = $this->createRemoteUser($params[0]);
+                                       $remoteOwner = $this->createRemoteUser($params[0], $displayName);
                                        $params[3] = $remoteOwner['name'] . '@' . $remoteOwner['server'];
 
                                        $notification->setRichSubject(
@@ -166,19 +167,21 @@ class Notifier implements INotifier {
 
        /**
         * @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 = '';
                }
 
index 370ef8dc32a56f49af123fae9695efe4f6a72313..5e2cafe0ce047826c65faac53e294fdf0c645e0c 100644 (file)
@@ -55,10 +55,13 @@ use OCP\ILogger;
 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 @@ class CloudFederationProviderFiles implements ICloudFederationProvider {
                                $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 @@ class CloudFederationProviderFiles implements ICloudFederationProvider {
                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 @@ class CloudFederationProviderFiles implements ICloudFederationProvider {
                                ->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 @@ class CloudFederationProviderFiles implements ICloudFederationProvider {
                                $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 @@ class CloudFederationProviderFiles implements ICloudFederationProvider {
        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);
+       }
 }
index 4a2c6ac919e72df554e0c6900c914b7176cd9055..e9e1d870f9a5be0218050fb7094218581f0835fa 100644 (file)
@@ -157,9 +157,11 @@ abstract class Base implements IProvider {
 
        /**
         * @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 @@ abstract class Base implements IProvider {
                        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 @@ abstract class Base implements IProvider {
                return [
                        'type' => 'user',
                        'id' => $uid,
-                       'name' => $uid,
+                       'name' => (($overwriteDisplayName !== '') ? $overwriteDisplayName : $uid),
                ];
        }
 
index f1cc90f5e655f4a4b0032adffaa5c2980b38bb92..e24645f8a26ce8626481e869e9446673ba16e74f 100644 (file)
@@ -115,13 +115,14 @@ class RemoteShares extends Base {
                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:
index 2de5f7a530537edb3e683155c05390d485e57f02..cc3afefd32144bd562a86d0577c814dc713896c4 100644 (file)
@@ -44,12 +44,13 @@ declare(strict_types=1);
  */
 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\OCS\OCSNotFoundException;
 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\IURLGenerator;
 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 @@ class ShareAPIController extends OCSController {
 
                        $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 @@ class ShareAPIController extends OCSController {
 
        /**
         * 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 @@ class ShareAPIController extends OCSController {
         */
        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 @@ class ShareAPIController extends OCSController {
                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
         *
@@ -646,6 +758,8 @@ class ShareAPIController extends OCSController {
                                        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]));
@@ -793,7 +907,6 @@ class ShareAPIController extends OCSController {
                // filter out duplicate shares
                $known = [];
 
-
                $formatted = $miniFormatted = [];
                $resharingRight = false;
                $known = [];
@@ -957,6 +1070,9 @@ class ShareAPIController extends OCSController {
                        $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());
index 85aae8e5ec5293987040db0df809eb254372d592..82b711bd422dcd9fd233e5f79babd1c39cdc24d2 100644 (file)
@@ -209,11 +209,12 @@ class CloudIdManager implements ICloudIdManager {
         * @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;
index 1612c03ba4af001ba043277e24f0a0777b563bad..5292075173992881476b79a1db5065dd60ddc8e5 100644 (file)
@@ -62,4 +62,14 @@ interface ICloudIdManager {
         * @since 12.0.0
         */
        public function isValidCloudId(string $cloudId): bool;
+
+       /**
+        * remove scheme/protocol from an url
+        *
+        * @param string $url
+        *
+        * @return string
+        * @since 28.0.0
+        */
+       public function removeProtocolFromUrl(string $url): string;
 }
index 009c59c785747f9538dce4bddc56d62528b8ed5a..8eb599b30d75d33f2ec517159cc8bbc18674355e 100644 (file)
--- a/psalm.xml
+++ b/psalm.xml
@@ -84,6 +84,7 @@
                        <errorLevel type="suppress">
                                <referencedClass name="OCA\GroupFolders\Mount\GroupFolderStorage"/>
                                <referencedClass name="OCA\TwoFactorNextcloudNotification\Controller\APIController"/>
+                               <referencedClass name="OCA\GlobalSiteSelector\Service\SlaveService"/>
                        </errorLevel>
                </UndefinedClass>
                <UndefinedFunction>
                                <!-- Helper classes for sharing API integration from other apps -->
                                <referencedClass name="OCA\Deck\Sharing\ShareAPIHelper" />
                                <referencedClass name="OCA\Talk\Share\Helper\DeletedShareAPIController" />
+                               <referencedClass name="OCA\GlobalSiteSelector\Service\SlaveService"/>
                        </errorLevel>
                </UndefinedDocblockClass>
        </issueHandlers>