diff options
-rw-r--r-- | appinfo/routes.php | 1 | ||||
-rw-r--r-- | lib/Controller/FederationController.php | 56 | ||||
-rw-r--r-- | lib/Controller/OCSController.php | 2 | ||||
-rw-r--r-- | lib/Controller/WopiController.php | 34 | ||||
-rw-r--r-- | lib/Service/FederationService.php | 19 | ||||
-rw-r--r-- | lib/TokenManager.php | 15 | ||||
-rw-r--r-- | tests/features/direct.feature | 4 |
7 files changed, 87 insertions, 44 deletions
diff --git a/appinfo/routes.php b/appinfo/routes.php index 6fd22f90..02c0baf8 100644 --- a/appinfo/routes.php +++ b/appinfo/routes.php @@ -75,5 +75,6 @@ return [ ['name' => 'Federation#index', 'url' => '/api/v1/federation', 'verb' => 'GET'], ['name' => 'Federation#remoteWopiToken', 'url' => '/api/v1/federation', 'verb' => 'POST'], + ['name' => 'Federation#initiatorUser', 'url' => '/api/v1/federation/user', 'verb' => 'POST'], ], ]; diff --git a/lib/Controller/FederationController.php b/lib/Controller/FederationController.php index 8a0f9277..08fd39c7 100644 --- a/lib/Controller/FederationController.php +++ b/lib/Controller/FederationController.php @@ -30,6 +30,8 @@ use OCP\AppFramework\Http\DataResponse; use OCP\IConfig; use OCP\ILogger; use OCP\IRequest; +use OCP\IURLGenerator; +use OCP\IUserManager; class FederationController extends OCSController { @@ -42,17 +44,27 @@ class FederationController extends OCSController { /** @var WopiMapper */ private $wopiMapper; + /** @var IUserManager */ + private $userManager; + + /** @var IURLGenerator */ + private $urlGenerator; + public function __construct( string $appName, IRequest $request, IConfig $config, ILogger $logger, - WopiMapper $wopiMapper + WopiMapper $wopiMapper, + IUserManager $userManager, + IURLGenerator $urlGenerator ) { parent::__construct($appName, $request); $this->config = $config; $this->logger = $logger; $this->wopiMapper = $wopiMapper; + $this->userManager = $userManager; + $this->urlGenerator = $urlGenerator; } /** @@ -84,32 +96,54 @@ class FederationController extends OCSController { */ public function remoteWopiToken($token): DataResponse { try { - $wopi = $this->wopiMapper->getWopiForToken($token); - $user = \OC::$server->getUserManager()->get($wopi->getEditorUid()); - if($user !== null) { - $wopi->setGuestDisplayname($user->getDisplayName()); + $initiatorWopi = $this->wopiMapper->getWopiForToken($token); + if (empty($initiatorWopi->getEditorUid()) && !empty($initiatorWopi->getRemoteServer()) && !empty($initiatorWopi->getRemoteServerToken())) { + $client = \OC::$server->getHTTPClientService()->newClient(); + $response = $client->post( + rtrim($initiatorWopi->getRemoteServer(), '/') . '/ocs/v2.php/apps/richdocuments/api/v1/federation/user?format=json', + [ 'body' => [ 'token' => $initiatorWopi->getRemoteServerToken() ], 'timeout' => 10 ] + ); + $initiatorUser = \json_decode($response->getBody(), true)['ocs']['data']; + $initiatorWopi->setGuestDisplayname($initiatorUser['displayName']); + } else { + $user = $this->userManager->get($initiatorWopi->getEditorUid()); + if($user !== null) { + $initiatorWopi->setGuestDisplayname($user->getDisplayName()); + } } $this->logger->debug('COOL-Federation-Initiator: Token ' . $token . ' returned'); - return new DataResponse($wopi); + return new DataResponse($initiatorWopi); } catch (DoesNotExistException $e) { $this->logger->debug('COOL-Federation-Initiator: Token ' . $token . 'not found'); throw new OCSNotFoundException(); } } + /** + * @PublicPage + * @NoCSRFRequired + * @OCSRoute POST /api/v1/federation/user + * + * Return user details for a initiator user that will be used by remote instances + * to provide Collabora with the users avatar/displayname for guests on share links + * if the session was created through a direct link of a public share + * + * @param $token + * @return DataResponse + * @throws DoesNotExistException + */ public function initiatorUser($token): DataResponse { try { $wopi = $this->wopiMapper->getWopiForToken($token); - $user = \OC::$server->getUserManager()->get($wopi->getEditorUid()); + $user = $this->userManager->get($wopi->getEditorUid()); if($user !== null) { $wopi->setGuestDisplayname($user->getDisplayName()); } $this->logger->debug('COOL-Federation-Initiator-User: Token ' . $token . ' returned'); return new DataResponse([ - 'initiatorHost' => '', - 'userId' => '', - 'displayName' => '', - 'avatar' => '' + 'userId' => $user->getUID(), + 'displayName' => $user->getDisplayName(), + 'avatar' => $this->urlGenerator->linkToRouteAbsolute('core.avatar.getAvatar', ['userId' => $wopi->getEditorUid(), 'size' => WopiController::WOPI_AVATAR_SIZE]) ]); } catch (DoesNotExistException $e) { $this->logger->debug('COOL-Federation-Initiator-User: Token ' . $token . 'not found'); diff --git a/lib/Controller/OCSController.php b/lib/Controller/OCSController.php index 23188a3c..c7cf7520 100644 --- a/lib/Controller/OCSController.php +++ b/lib/Controller/OCSController.php @@ -156,7 +156,7 @@ class OCSController extends \OCP\AppFramework\OCSController { 'path' => $path, 'password' => $password ], - 'timeout' => 5 + 'timeout' => 30 ]); $url = \json_decode($response->getBody(), true)['ocs']['data']['url']; diff --git a/lib/Controller/WopiController.php b/lib/Controller/WopiController.php index 5ee267d2..4634b0b2 100644 --- a/lib/Controller/WopiController.php +++ b/lib/Controller/WopiController.php @@ -85,6 +85,8 @@ class WopiController extends Controller { // Signifies LOOL that document has been changed externally in this storage const LOOL_STATUS_DOC_CHANGED = 1010; + const WOPI_AVATAR_SIZE = 32; + /** * @param string $appName * @param IRequest $request @@ -230,7 +232,7 @@ class WopiController extends Controller { $user = $this->userManager->get($wopi->getEditorUid()); if($user !== null) { - $response['UserExtraInfo']['avatar'] = $this->urlGenerator->linkToRouteAbsolute('core.avatar.getAvatar', ['userId' => $wopi->getEditorUid(), 'size' => 32]); + $response['UserExtraInfo']['avatar'] = $this->urlGenerator->linkToRouteAbsolute('core.avatar.getAvatar', ['userId' => $wopi->getEditorUid(), 'size' => self::WOPI_AVATAR_SIZE]); } if ($wopi->isRemoteToken()) { @@ -242,12 +244,14 @@ class WopiController extends Controller { private function setFederationFileInfo(Wopi $wopi, $response) { + $response['UserId'] = 'Guest-' . \OC::$server->getSecureRandom()->generate(8); + if ($wopi->getTokenType() === Wopi::TOKEN_TYPE_REMOTE_USER) { $remoteUserId = $wopi->getGuestDisplayname(); $cloudID = \OC::$server->getCloudIdManager()->resolveCloudId($remoteUserId); $response['UserId'] = $cloudID->getDisplayId(); $response['UserFriendlyName'] = $cloudID->getDisplayId(); - $response['UserExtraInfo']['avatar'] = $this->urlGenerator->linkToRouteAbsolute('core.avatar.getAvatar', ['userId' => explode('@', $remoteUserId)[0], 'size' => 32]); + $response['UserExtraInfo']['avatar'] = $this->urlGenerator->linkToRouteAbsolute('core.avatar.getAvatar', ['userId' => explode('@', $remoteUserId)[0], 'size' => self::WOPI_AVATAR_SIZE]); $cleanCloudId = str_replace(['http://', 'https://'], '', $cloudID->getId()); $addressBookEntries = \OC::$server->getContactsManager()->search($cleanCloudId, ['CLOUD']); foreach ($addressBookEntries as $entry) { @@ -260,24 +264,20 @@ class WopiController extends Controller { } } } - } else if ($wopi->getTokenType() === Wopi::TOKEN_TYPE_REMOTE_GUEST) { - $response['UserId'] = 'Guest-' . \OC::$server->getSecureRandom()->generate(8); } - $initiator = $this->federationService->getRemoteFileDetails($wopi->getRemoteServer(), $wopi->getRemoteServerToken()); - if ($initiator !== null) { - if ($initiator->hasTemplateId()) { - $templateUrl = $wopi->getRemoteServer() . '/index.php/apps/richdocuments/wopi/template/' . $initiator->getTemplateId() . '?access_token=' . $initiator->getToken(); - $response['TemplateSource'] = $templateUrl; - } - if ($wopi->getTokenType() === Wopi::TOKEN_TYPE_REMOTE_USER) { - $response['UserExtraInfo']['avatar'] = $wopi->getRemoteServer() . '/index.php/avatar/' . $initiator->getEditorUid() . '/32'; - } - if ($wopi->getTokenType() === Wopi::TOKEN_TYPE_REMOTE_GUEST && $initiator->getEditorUid()) { - $response['UserFriendlyName'] = $initiator->getGuestDisplayname() . ' (Guest)'; - $response['UserExtraInfo']['avatar'] = $wopi->getRemoteServer() . '/index.php/avatar/' . $initiator->getEditorUid() . '/32'; - } + if ($initiator === null) { + return $response; + } + + $response['UserFriendlyName'] = $initiator->getGuestDisplayname() . ' (Guest)'; + if ($initiator->hasTemplateId()) { + $templateUrl = $wopi->getRemoteServer() . '/index.php/apps/richdocuments/wopi/template/' . $initiator->getTemplateId() . '?access_token=' . $initiator->getToken(); + $response['TemplateSource'] = $templateUrl; + } + if ($wopi->getTokenType() === Wopi::TOKEN_TYPE_REMOTE_USER || ($wopi->getTokenType() === Wopi::TOKEN_TYPE_REMOTE_GUEST && $initiator->getEditorUid())) { + $response['UserExtraInfo']['avatar'] = $wopi->getRemoteServer() . '/index.php/avatar/' . $initiator->getEditorUid() . '/'. self::WOPI_AVATAR_SIZE; } return $response; diff --git a/lib/Service/FederationService.php b/lib/Service/FederationService.php index fa555c49..190c9c1e 100644 --- a/lib/Service/FederationService.php +++ b/lib/Service/FederationService.php @@ -28,6 +28,7 @@ use OCA\Federation\TrustedServers; use OCA\Files_Sharing\External\Storage as SharingExternalStorage; use OCA\Richdocuments\Db\Direct; use OCA\Richdocuments\Db\Wopi; +use OCA\Richdocuments\Db\WopiMapper; use OCA\Richdocuments\TokenManager; use OCP\AppFramework\Http\RedirectResponse; use OCP\AppFramework\QueryException; @@ -91,7 +92,7 @@ class FederationService { } try { $client = $this->clientService->newClient(); - $response = $client->get($remote . '/ocs/v2.php/apps/richdocuments/api/v1/federation?format=json', ['timeout' => 5]); + $response = $client->get($remote . '/ocs/v2.php/apps/richdocuments/api/v1/federation?format=json', ['timeout' => 30]); $data = \json_decode($response->getBody(), true); $remoteCollabora = $data['ocs']['data']['wopi_url']; $this->cache->set('richdocuments_remote/' . $remote, $remoteCollabora, 3600); @@ -168,7 +169,7 @@ class FederationService { $this->logger->debug('COOL-Federation-Source: Fetching remote file details from ' . $remote . ' for token ' . $remoteToken); $client = $this->clientService->newClient(); $response = $client->post($remote . '/ocs/v2.php/apps/richdocuments/api/v1/federation?format=json', [ - 'timeout' => 5, + 'timeout' => 30, 'body' => [ 'token' => $remoteToken ] @@ -179,7 +180,7 @@ class FederationService { $this->cache->set($cacheKey, $data['ocs']['data']); return Wopi::fromParams($data['ocs']['data']); } catch (\Throwable $e) { - $this->logger->error('COOL-Federation-Source: Unable to fetch remote file details for ' . $remoteToken . ' from ' . $remote, ['exception' => $e]); + $this->logger->logException($e, ['message' => 'COOL-Federation-Source: Unable to fetch remote file details for ' . $remoteToken . ' from ' . $remote ]); } return null; } @@ -204,12 +205,12 @@ class FederationService { $initiatorServer = $wopi->getServerHost(); $initiatorToken = $wopi->getToken(); - if ($direct) { - //$wopi->setRemoteServer($direct->getInitiatorHost()); - //$wopi->setRemoteServerToken($direct->getInitiatorToken()); - // FIXME: the direct token might have a different originator when a share link originating on a federated stoarge is opened - // editor uid is null since we don't fetch it from the initiator of direct so @{link remoteWopiToken()} fails - // Currently there is no mapping from the initiator user data to the source then + /** + * If the request to open a file originates from a direct token we might need to fetch the initiator user details when the initiator wopi token is accessed + * as the user might origin on a 3rd instance + */ + if ($direct && !empty($direct->getInitiatorHost()) && !empty($direct->getInitiatorToken())) { + $this->tokenManager->extendWithInitiatorUserToken($wopi, $direct->getInitiatorHost(), $direct->getInitiatorToken()); } $url = rtrim($remote, '/') . '/index.php/apps/richdocuments/remote?shareToken=' . $item->getStorage()->getToken() . diff --git a/lib/TokenManager.php b/lib/TokenManager.php index 4a7bebd7..90eb420a 100644 --- a/lib/TokenManager.php +++ b/lib/TokenManager.php @@ -225,9 +225,11 @@ class TokenManager { $remoteTokenType = $remoteWopi->getEditorUid() !== null ? Wopi::TOKEN_TYPE_REMOTE_USER : Wopi::TOKEN_TYPE_REMOTE_GUEST; $wopi->setTokenType($remoteTokenType); - if ($remoteTokenType === Wopi::TOKEN_TYPE_REMOTE_USER) { - $wopi->setGuestDisplayname($remoteWopi->getEditorUid() . '@' . $remoteServer); - } + $wopi->setGuestDisplayname( + $remoteTokenType === Wopi::TOKEN_TYPE_REMOTE_USER ? + $remoteWopi->getEditorUid() . '@' . $remoteServer : + $remoteWopi->getGuestDisplayname() + ); $wopi->setShare($shareToken); $wopi->setCanwrite($wopi->getCanwrite() && $remoteWopi->getCanwrite()); $wopi->setHideDownload($wopi->getHideDownload() || $remoteWopi->getHideDownload()); @@ -302,4 +304,11 @@ class TokenManager { return $this->wopiMapper->generateInitiatorToken($this->userId, $sourceServer); } + public function extendWithInitiatorUserToken(Wopi $wopi, string $initiatorUserHost, string $initiatorUserToken): Wopi { + $wopi->setRemoteServer($initiatorUserHost); + $wopi->setRemoteServerToken($initiatorUserToken); + $this->wopiMapper->update($wopi); + return $wopi; + } + } diff --git a/tests/features/direct.feature b/tests/features/direct.feature index 26695dd7..8359b020 100644 --- a/tests/features/direct.feature +++ b/tests/features/direct.feature @@ -207,9 +207,7 @@ Feature: Direct editing And Collabora fetches checkFileInfo And Collabora fetches and receives the following in the checkFileInfo response | BaseFileName | document-reshare-fed-link.odt | - | UserFriendlyName | Anonymous guest | - # FIXME: Known issue as of right now as we do not fetch the initiator user yet - # | UserFriendlyName | user3-displayname (Guest) | + | UserFriendlyName | user3-displayname (Guest) | And checkFileInfo "UserId" matches "/Guest-/" And checkFileInfo "UserCanWrite" is false And both Collabora files used the same file id |