Browse Source

display displayname on federated shares

Signed-off-by: Maxence Lange <maxence@artificial-owl.com>
Maxence Lange 11 months ago
parent
commit
b9a25ce4d5

+ 8 - 5
apps/federatedfilesharing/lib/Notifier.php

@@ -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 = '';
 		}
 

+ 36 - 7
apps/federatedfilesharing/lib/OCM/CloudFederationProviderFiles.php

@@ -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);
+	}
 }

+ 5 - 3
apps/files_sharing/lib/Activity/Providers/Base.php

@@ -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),
 		];
 	}
 

+ 2 - 1
apps/files_sharing/lib/Activity/Providers/RemoteShares.php

@@ -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:

+ 127 - 11
apps/files_sharing/lib/Controller/ShareAPIController.php

@@ -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());

+ 6 - 5
lib/private/Federation/CloudIdManager.php

@@ -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;

+ 10 - 0
lib/public/Federation/ICloudIdManager.php

@@ -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;
 }

+ 2 - 0
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>
@@ -127,6 +128,7 @@
 				<!-- 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>