diff options
author | Julius Härtl <jus@bitgrid.net> | 2021-04-23 15:27:56 +0300 |
---|---|---|
committer | Julius Härtl <jus@bitgrid.net> | 2021-04-28 11:40:58 +0300 |
commit | 9ecf2aafc64f8e366c7bae5a73c37da8452bb604 (patch) | |
tree | fd6d5d479c5c2f15e044823fe5de3c9d66fff292 | |
parent | 3067440843cd86d10f99ee6ed06c90882f87e7aa (diff) |
Implement token types
Signed-off-by: Julius Härtl <jus@bitgrid.net>
-rw-r--r-- | appinfo/routes.php | 8 | ||||
-rw-r--r-- | lib/Controller/DirectViewController.php | 77 | ||||
-rw-r--r-- | lib/Controller/DocumentController.php | 124 | ||||
-rw-r--r-- | lib/Controller/FederationController.php | 68 | ||||
-rw-r--r-- | lib/Controller/OCSController.php | 122 | ||||
-rw-r--r-- | lib/Controller/WopiController.php | 87 | ||||
-rw-r--r-- | lib/Db/Direct.php | 12 | ||||
-rw-r--r-- | lib/Db/DirectMapper.php | 7 | ||||
-rw-r--r-- | lib/Db/Wopi.php | 44 | ||||
-rw-r--r-- | lib/Db/WopiMapper.php | 22 | ||||
-rw-r--r-- | lib/Migration/Version30717Date20210310164901.php | 21 | ||||
-rw-r--r-- | lib/Service/FederationService.php | 89 | ||||
-rw-r--r-- | lib/TokenManager.php | 100 |
13 files changed, 509 insertions, 272 deletions
diff --git a/appinfo/routes.php b/appinfo/routes.php index f04ce9e8..6fd22f90 100644 --- a/appinfo/routes.php +++ b/appinfo/routes.php @@ -30,7 +30,7 @@ return [ //documents ['name' => 'document#index', 'url' => 'index', 'verb' => 'GET'], ['name' => 'document#remote', 'url' => 'remote', 'verb' => 'GET'], - ['name' => 'document#open', 'url' => 'open', 'verb' => 'GET'], + ['name' => 'document#openRemoteFile', 'url' => 'open', 'verb' => 'GET'], ['name' => 'document#createFromTemplate', 'url' => 'indexTemplate', 'verb' => 'GET'], ['name' => 'document#publicPage', 'url' => '/public', 'verb' => 'GET'], @@ -67,13 +67,13 @@ return [ ['name' => 'templates#delete', 'url' => '/template/{fileId}', 'verb' => 'DELETE'], ], 'ocs' => [ - ['name' => 'OCS#create', 'url' => '/api/v1/document', 'verb' => 'POST'], + ['name' => 'OCS#createDirect', 'url' => '/api/v1/document', 'verb' => 'POST'], + ['name' => 'OCS#createPublic', 'url' => '/api/v1/share', 'verb' => 'POST'], + ['name' => 'OCS#createPublicFromInitiator', 'url' => '/api/v1/direct/share/initiator', 'verb' => 'POST'], ['name' => 'OCS#getTemplates', 'url' => '/api/v1/templates/{type}', 'verb' => 'GET'], ['name' => 'OCS#createFromTemplate', 'url' => '/api/v1/templates/new', 'verb' => 'POST'], ['name' => 'Federation#index', 'url' => '/api/v1/federation', 'verb' => 'GET'], ['name' => 'Federation#remoteWopiToken', 'url' => '/api/v1/federation', 'verb' => 'POST'], - ['name' => 'Federation#remoteDirectToken', 'url' => '/api/v1/federation/direct', 'verb' => 'POST'], - ], ]; diff --git a/lib/Controller/DirectViewController.php b/lib/Controller/DirectViewController.php index b6616770..7b5cac06 100644 --- a/lib/Controller/DirectViewController.php +++ b/lib/Controller/DirectViewController.php @@ -23,6 +23,7 @@ namespace OCA\Richdocuments\Controller; use OCA\Richdocuments\AppConfig; +use OCA\Richdocuments\Db\Direct; use OCA\Richdocuments\Db\DirectMapper; use OCA\Richdocuments\Service\FederationService; use OCA\Richdocuments\TemplateManager; @@ -34,10 +35,12 @@ use OCP\AppFramework\Http\ContentSecurityPolicy; use OCP\AppFramework\Http\JSONResponse; use OCP\AppFramework\Http\RedirectResponse; use OCP\AppFramework\Http\TemplateResponse; +use OCP\Files\Folder; use OCP\Files\IRootFolder; use OCP\Files\Node; use OCP\Files\NotFoundException; use OCP\IConfig; +use OCP\ILogger; use OCP\IRequest; class DirectViewController extends Controller { @@ -62,6 +65,9 @@ class DirectViewController extends Controller { /** @var FederationService */ private $federationService; + /** @var ILogger */ + private $logger; + public function __construct( $appName, IRequest $request, @@ -71,7 +77,8 @@ class DirectViewController extends Controller { IConfig $config, AppConfig $appConfig, TemplateManager $templateManager, - FederationService $federationService + FederationService $federationService, + ILogger $logger ) { parent::__construct($appName, $request); @@ -82,6 +89,7 @@ class DirectViewController extends Controller { $this->appConfig = $appConfig; $this->templateManager = $templateManager; $this->federationService = $federationService; + $this->logger = $logger; } /** @@ -108,6 +116,12 @@ class DirectViewController extends Controller { // Delete the token. They are for 1 time use only $this->directMapper->delete($direct); + // Direct token for share link + if (!empty($direct->getShare())) { + return $this->showPublicShare($direct); + } + + $folder = $this->rootFolder->getUserFolder($direct->getUid()); if ($this->templateManager->isTemplate($direct->getFileid())) { $item = $this->templateManager->get($direct->getFileid()); @@ -116,7 +130,6 @@ class DirectViewController extends Controller { } try { - list($urlSrc, $wopi) = $this->tokenManager->getTokenForTemplate($item, $direct->getUid(), $direct->getTemplateDestination(), true); } catch (\Exception $e) { return new JSONResponse([], Http::STATUS_BAD_REQUEST); @@ -177,4 +190,64 @@ class DirectViewController extends Controller { } } + + public function showPublicShare(Direct $direct) { + try { + $share = \OC::$server->getShareManager()->getShareByToken($direct->getShare()); + + $node = $share->getNode(); + if ($node instanceof Folder) { + $nodes = $node->getById($direct->getFileid()); + $node = array_shift($nodes); + if ($node === null) { + throw new NotFoundException(); + } + } + + // Handle opening a share link that originates from a remote instance + $federatedUrl = $this->federationService->getRemoteRedirectURL($node, $direct, $share); + if ($federatedUrl !== null) { + $response = new RedirectResponse($federatedUrl); + $response->addHeader('X-Frame-Options', 'ALLOW'); + return $response; + } + + $this->settings = \OC::$server->getConfig(); + if ($node instanceof Node) { + $params = [ + 'permissions' => $share->getPermissions(), + 'title' => $node->getName(), + 'fileId' => $node->getId() . '_' . $this->settings->getSystemValue('instanceid'), + 'path' => '/', + 'instanceId' => $this->settings->getSystemValue('instanceid'), + 'canonical_webroot' => $this->appConfig->getAppValue('canonical_webroot'), + 'userId' => null, + 'direct' => true + ]; + + list($urlSrc, $token, $wopi) = $this->tokenManager->getToken($node->getId(), $direct->getShare(), $direct->getUid(), true); + if (!empty($direct->getInitiatorHost())) { + $this->tokenManager->upgradeFromDirectInitiator($direct, $wopi); + } + $params['token'] = $token; + $params['urlsrc'] = $urlSrc; + + $response = new TemplateResponse('richdocuments', 'documents', $params, 'base'); + $policy = new ContentSecurityPolicy(); + $policy->allowInlineScript(true); + $policy->addAllowedFrameDomain($this->appConfig->getAppValue('public_wopi_url')); + $response->setContentSecurityPolicy($policy); + return $response; + } + } catch (\Exception $e) { + $this->logger->logException($e, ['app'=>'richdocuments']); + $params = [ + 'errors' => [['error' => $e->getMessage()]] + ]; + return new TemplateResponse('core', 'error', $params, 'guest'); + } + + return new TemplateResponse('core', '403', [], 'guest'); + + } } diff --git a/lib/Controller/DocumentController.php b/lib/Controller/DocumentController.php index 0f6b969a..f315ccdd 100644 --- a/lib/Controller/DocumentController.php +++ b/lib/Controller/DocumentController.php @@ -192,56 +192,6 @@ class DocumentController extends Controller { } /** - * Redirect to the files app with proper CSP headers set for federated editing - * This is a workaround since we cannot set a nonce for allowing dynamic URLs in the richdocument iframe - * - * @NoAdminRequired - * @NoCSRFRequired - */ - public function open($fileId) { - try { - $folder = $this->rootFolder->getUserFolder($this->uid); - $item = $folder->getById($fileId)[0]; - if (!($item instanceof File)) { - throw new \Exception('Node is not a file'); - } - - if ($item->getStorage()->instanceOfStorage(\OCA\Files_Sharing\External\Storage::class)) { - $remote = $item->getStorage()->getRemote(); - $remoteCollabora = $this->federationService->getRemoteCollaboraURL($remote); - if ($remoteCollabora !== '') { - $absolute = $item->getParent()->getPath(); - $relativeFolderPath = $folder->getRelativePath($absolute); - $relativeFilePath = $folder->getRelativePath($item->getPath()); - $url = '/index.php/apps/files/?dir=' . $relativeFolderPath . - '&richdocuments_open=' . $relativeFilePath . - '&richdocuments_fileId=' . $fileId . - '&richdocuments_remote_access=' . $remote; - - $event = new BeforeFederationRedirectEvent( - $item, $relativeFolderPath, $remote - ); - $eventDispatcher = \OC::$server->getEventDispatcher(); - $eventDispatcher->dispatch(BeforeFederationRedirectEvent::class, $event); - if ($event->getRedirectUrl()) { - $url = $event->getRedirectUrl(); - } - return new RedirectResponse($url); - } - $this->logger->warning('Failed to connect to remote collabora instance for ' . $fileId); - } - } catch (\Exception $e) { - $this->logger->logException($e, ['app'=>'richdocuments']); - $params = [ - 'errors' => [['error' => $e->getMessage()]] - ]; - return new TemplateResponse('core', 'error', $params, 'guest'); - } - - return new TemplateResponse('core', '403', [], 'guest'); - } - - /** * @NoAdminRequired * * @param string $fileId @@ -262,7 +212,10 @@ class DocumentController extends Controller { throw new \Exception(); } - /** Open file from remote collabora */ + /** + * Open file on source instance if it is originating from a federated share + * The generated url will result in {@link remote()} + */ $federatedUrl = $this->federationService->getRemoteRedirectURL($item); if ($federatedUrl !== null) { $response = new RedirectResponse($federatedUrl); @@ -304,8 +257,6 @@ class DocumentController extends Controller { ]; return new TemplateResponse('core', 'error', $params, 'guest'); } - - return new TemplateResponse('core', '403', [], 'guest'); } /** @@ -342,7 +293,6 @@ class DocumentController extends Controller { $template = $this->templateManager->get($templateId); list($urlSrc, $wopi) = $this->tokenManager->getTokenForTemplate($template, $this->uid, $file->getId()); - $wopiFileId = $template->getId() . '-' . $file->getId() . '_' . $this->settings->getSystemValue('instanceid'); $wopiFileId = $wopi->getFileid() . '_' . $this->settings->getSystemValue('instanceid'); $params = [ @@ -368,7 +318,7 @@ class DocumentController extends Controller { * * @param string $shareToken * @param string $fileName - * @return TemplateResponse + * @return TemplateResponse|RedirectResponse * @throws \Exception */ public function publicPage($shareToken, $fileName, $fileId) { @@ -389,6 +339,12 @@ class DocumentController extends Controller { } else { $item = $node; } + $federatedUrl = $this->federationService->getRemoteRedirectURL($item, null, $share); + if ($federatedUrl !== null) { + $response = new RedirectResponse($federatedUrl); + $response->addHeader('X-Frame-Options', 'ALLOW'); + return $response; + } if ($item instanceof Node) { $params = [ 'permissions' => $share->getPermissions(), @@ -422,6 +378,58 @@ class DocumentController extends Controller { } /** + * Redirect to the files app with proper CSP headers set for federated editing + * This is a workaround since we cannot set a nonce for allowing dynamic URLs in the richdocument iframe + * + * @NoAdminRequired + * @NoCSRFRequired + */ + public function openRemoteFile($fileId) { + try { + $folder = $this->rootFolder->getUserFolder($this->uid); + $item = $folder->getById($fileId)[0]; + if (!($item instanceof File)) { + throw new \Exception('Node is not a file'); + } + + if ($item->getStorage()->instanceOfStorage(\OCA\Files_Sharing\External\Storage::class)) { + $remote = $item->getStorage()->getRemote(); + $remoteCollabora = $this->federationService->getRemoteCollaboraURL($remote); + if ($remoteCollabora !== '') { + $absolute = $item->getParent()->getPath(); + $relativeFolderPath = $folder->getRelativePath($absolute); + $relativeFilePath = $folder->getRelativePath($item->getPath()); + $url = '/index.php/apps/files/?dir=' . $relativeFolderPath . + '&richdocuments_open=' . $relativeFilePath . + '&richdocuments_fileId=' . $fileId . + '&richdocuments_remote_access=' . $remote; + + $event = new BeforeFederationRedirectEvent( + $item, $relativeFolderPath, $remote + ); + $eventDispatcher = \OC::$server->getEventDispatcher(); + $eventDispatcher->dispatch(BeforeFederationRedirectEvent::class, $event); + if ($event->getRedirectUrl()) { + $url = $event->getRedirectUrl(); + } + return new RedirectResponse($url); + } + $this->logger->warning('Failed to connect to remote collabora instance for ' . $fileId); + } + } catch (\Exception $e) { + $this->logger->logException($e, ['app'=>'richdocuments']); + $params = [ + 'errors' => [['error' => $e->getMessage()]] + ]; + return new TemplateResponse('core', 'error', $params, 'guest'); + } + + return new TemplateResponse('core', '403', [], 'guest'); + } + + /** + * Open file on Source instance with token from Initiator instance + * * @PublicPage * @NoCSRFRequired * @@ -455,10 +463,10 @@ class DocumentController extends Controller { if ($remoteWopi === null) { throw new \Exception('Invalid remote file details for ' . $remoteServerToken); } - $this->tokenManager->updateToFederationToken($wopi, $shareToken, $remoteServer, $remoteServerToken, $remoteWopi); + $this->tokenManager->upgradeToRemoteToken($wopi, $remoteWopi, $shareToken, $remoteServer, $remoteServerToken); $permissions = $share->getPermissions(); - if (!$remoteWopi['canwrite']) { + if (!$remoteWopi->getCanwrite()) { $permissions = $permissions & ~ Constants::PERMISSION_UPDATE; } @@ -471,7 +479,7 @@ class DocumentController extends Controller { 'path' => '/', 'instanceId' => $this->settings->getSystemValue('instanceid'), 'canonical_webroot' => $this->appConfig->getAppValue('canonical_webroot'), - 'userId' => $remoteWopi['editorUid'] . '@' . $remoteServer + 'userId' => $remoteWopi->getEditorUid() ? ($remoteWopi->getEditorUid() . '@' . $remoteServer) : null, ]; $response = new TemplateResponse('richdocuments', 'documents', $params, 'base'); diff --git a/lib/Controller/FederationController.php b/lib/Controller/FederationController.php index 6ec1409d..8a0f9277 100644 --- a/lib/Controller/FederationController.php +++ b/lib/Controller/FederationController.php @@ -23,45 +23,44 @@ namespace OCA\Richdocuments\Controller; use OCP\AppFramework\Db\DoesNotExistException; -use \OCP\AppFramework\OCSController; +use OCP\AppFramework\OCS\OCSNotFoundException; +use OCP\AppFramework\OCSController; use OCA\Richdocuments\Db\WopiMapper; use OCP\AppFramework\Http\DataResponse; -use OCP\AppFramework\OCS\OCSNotFoundException; -use OCP\Files\NotFoundException; use OCP\IConfig; +use OCP\ILogger; use OCP\IRequest; -use OCP\Share\Exceptions\ShareNotFound; -use OCP\Share\IManager; class FederationController extends OCSController { /** @var IConfig */ private $config; + /** @var ILogger */ + private $logger; + /** @var WopiMapper */ private $wopiMapper; - /** @var IManager */ - private $shareManager; - public function __construct( string $appName, IRequest $request, IConfig $config, - WopiMapper $wopiMapper, - IManager $shareManager + ILogger $logger, + WopiMapper $wopiMapper ) { parent::__construct($appName, $request); $this->config = $config; + $this->logger = $logger; $this->wopiMapper = $wopiMapper; - $this->shareManager = $shareManager; } /** * @PublicPage * @NoCSRFRequired + * @OCSRoute GET /api/v1/federation */ - public function index() { + public function index(): DataResponse { $response = new DataResponse([ 'wopi_url' => $this->config->getAppValue('richdocuments', 'wopi_url') ]); @@ -72,6 +71,7 @@ class FederationController extends OCSController { /** * @PublicPage * @NoCSRFRequired + * @OCSRoute POST /api/v1/federation * * Check the file info of a remote accessing a file * @@ -82,17 +82,39 @@ class FederationController extends OCSController { * @return DataResponse * @throws DoesNotExistException */ - public function remoteWopiToken($token) { - $wopi = $this->wopiMapper->getWopiForToken($token); - return new DataResponse([ - 'ownerUid' => $wopi->getOwnerUid(), - 'editorUid' => $wopi->getEditorUid(), - 'canwrite' => $wopi->getCanwrite(), - 'hideDownload' => $wopi->getHideDownload(), - 'direct' => $wopi->getDirect(), - 'serverHost' => $wopi->getServerHost(), - 'guestDisplayname' => $wopi->getGuestDisplayname() - ]); + 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()); + } + $this->logger->debug('COOL-Federation-Initiator: Token ' . $token . ' returned'); + return new DataResponse($wopi); + } catch (DoesNotExistException $e) { + $this->logger->debug('COOL-Federation-Initiator: Token ' . $token . 'not found'); + throw new OCSNotFoundException(); + } + } + + public function initiatorUser($token): DataResponse { + try { + $wopi = $this->wopiMapper->getWopiForToken($token); + $user = \OC::$server->getUserManager()->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' => '' + ]); + } catch (DoesNotExistException $e) { + $this->logger->debug('COOL-Federation-Initiator-User: Token ' . $token . 'not found'); + throw new OCSNotFoundException(); + } } } diff --git a/lib/Controller/OCSController.php b/lib/Controller/OCSController.php index ea79e0c5..23188a3c 100644 --- a/lib/Controller/OCSController.php +++ b/lib/Controller/OCSController.php @@ -26,6 +26,7 @@ namespace OCA\Richdocuments\Controller; use OCA\Richdocuments\Db\DirectMapper; use OCA\Richdocuments\Service\FederationService; use OCA\Richdocuments\TemplateManager; +use OCA\Richdocuments\TokenManager; use OCP\AppFramework\Http; use OCP\AppFramework\Http\DataResponse; use OCP\AppFramework\OCS\OCSBadRequestException; @@ -38,6 +39,7 @@ use OCP\Files\Node; use OCP\Files\NotFoundException; use OCP\IRequest; use OCP\IURLGenerator; +use OCP\Share\IManager; class OCSController extends \OCP\AppFramework\OCSController { @@ -56,20 +58,15 @@ class OCSController extends \OCP\AppFramework\OCSController { /** @var TemplateManager */ private $manager; + /** @var TokenManager */ + private $tokenManager; + + /** @var IManager */ + private $shareManager; + /** @var FederationService */ private $federationService; - /** - * OCS controller - * - * @param string $appName - * @param IRequest $request - * @param IRootFolder $rootFolder - * @param string $userId - * @param DirectMapper $directMapper - * @param IURLGenerator $urlGenerator - * @param TemplateManager $manager - */ public function __construct(string $appName, IRequest $request, IRootFolder $rootFolder, @@ -77,6 +74,8 @@ class OCSController extends \OCP\AppFramework\OCSController { DirectMapper $directMapper, IURLGenerator $urlGenerator, TemplateManager $manager, + TokenManager $tokenManager, + IManager $shareManager, FederationService $federationService ) { parent::__construct($appName, $request); @@ -86,19 +85,21 @@ class OCSController extends \OCP\AppFramework\OCSController { $this->directMapper = $directMapper; $this->urlGenerator = $urlGenerator; $this->manager = $manager; + $this->tokenManager = $tokenManager; + $this->shareManager = $shareManager; $this->federationService = $federationService; } /** * @NoAdminRequired * - * Init an editing session + * Init a direct editing session * * @param int $fileId * @return DataResponse * @throws OCSNotFoundException|OCSBadRequestException */ - public function create($fileId) { + public function createDirect($fileId) { try { $userFolder = $this->rootFolder->getUserFolder($this->userId); $nodes = $userFolder->getById($fileId); @@ -125,41 +126,86 @@ class OCSController extends \OCP\AppFramework\OCSController { } /** + * Generate a direct editing link for a file in a public share to open with the current user + * + * If + * * @NoAdminRequired - * @param $shareToken - * @param $path - * @param $password - * @param $guestName - * @param null $remoteUserHost - * @param null $remoteUserToken - * @return DataResponse - * @throws NotFoundException * @throws OCSForbiddenException - * @throws \OCP\Share\Exceptions\ShareNotFound */ - public function createPublic(string $shareToken, string $path = '', string $password = null, string $guestName = null, $remoteUserHost = null, $remoteUserToken = null) { - if ($remoteUserHost && $remoteUserToken) { - // may be optional depending on the requirements - // fetch user details from remote host + public function createPublic( + string $shareTokenSourceInstance = null, + string $shareToken, + string $path = '', + string $password = null + ): DataResponse { + if ($shareTokenSourceInstance) { + $remoteCollabora = $this->federationService->getRemoteCollaboraURL($shareTokenSourceInstance); + if ($remoteCollabora === '') { + throw new OCSNotFoundException('Failed to connect to remote collabora instance.'); + } + + $wopi = $this->tokenManager->newInitiatorToken($shareTokenSourceInstance, null, $shareToken, true, $this->userId); + + $client = \OC::$server->getHTTPClientService()->newClient(); + $response = $client->post(rtrim($shareTokenSourceInstance, '/') . '/ocs/v2.php/apps/richdocuments/api/v1/direct/share/initiator?format=json', [ + 'body' => [ + 'initiatorServer' => \OC::$server->getURLGenerator()->getAbsoluteURL(''), + 'initiatorToken' => $wopi->getToken(), + 'shareToken' => $shareToken, + 'path' => $path, + 'password' => $password + ], + 'timeout' => 5 + ]); + $url = \json_decode($response->getBody(), true)['ocs']['data']['url']; + + return new DataResponse([ + 'url' => $url, + ]); } - $this->shareManager = \OC::$server->getShareManager(); + $share = $this->shareManager->getShareByToken($shareToken); - if ($share->getPassword()) { - if (!$this->shareManager->checkPassword($share, $password)) { - throw new OCSForbiddenException(); - } + if ($share->getPassword() && !$this->shareManager->checkPassword($share, $password)) { + throw new OCSForbiddenException(); + } + + $node = $share->getNode(); + if ($node instanceof Folder) { + $node = $node->get($path); + } + $direct = $this->directMapper->newDirect($this->userId, $node->getId(), 0, $shareToken); + + return new DataResponse([ + 'url' => $this->urlGenerator->linkToRouteAbsolute('richdocuments.directView.show', [ + 'token' => $direct->getToken() + ]) + ]); + } + + /** + * @PublicPage + * @NoCSRFRequired + * @throws OCSForbiddenException + */ + public function createPublicFromInitiator( + string $initiatorServer, + string $initiatorToken, + string $shareToken, + string $path = '', + string $password = null + ): DataResponse { + $share = $this->shareManager->getShareByToken($shareToken); + if ($share->getPassword() && !$this->shareManager->checkPassword($share, $password)) { + throw new OCSForbiddenException(); } + $node = $share->getNode(); if ($node instanceof Folder) { - try { - $node = $node->get($path); - } catch (NotFoundException $e) { - throw new OCSForbiddenException(); - } + $node = $node->get($path); } - // create new direct token and link with either $guestName or remote user info - $direct = $this->directMapper->newDirect( - ); + $direct = $this->directMapper->newDirect(null, $node->getId(), null, $shareToken, $initiatorServer, $initiatorToken); return new DataResponse([ 'url' => $this->urlGenerator->linkToRouteAbsolute('richdocuments.directView.show', [ diff --git a/lib/Controller/WopiController.php b/lib/Controller/WopiController.php index 1449844e..5ee267d2 100644 --- a/lib/Controller/WopiController.php +++ b/lib/Controller/WopiController.php @@ -26,6 +26,7 @@ use OCA\Richdocuments\AppConfig; use OCA\Richdocuments\Db\Wopi; use OCA\Richdocuments\Db\WopiMapper; use OCA\Richdocuments\Helper; +use OCA\Richdocuments\Service\FederationService; use OCA\Richdocuments\Service\UserScopeService; use OCA\Richdocuments\TemplateManager; use OCA\Richdocuments\TokenManager; @@ -76,6 +77,8 @@ class WopiController extends Controller { private $shareManager; /** @var UserScopeService */ private $userScopeService; + /** @var FederationService */ + private $federationService; /** @var IEncryptionManager */ private $encryptionManager; @@ -112,6 +115,7 @@ class WopiController extends Controller { TemplateManager $templateManager, IShareManager $shareManager, UserScopeService $userScopeService, + FederationService $federationService, IEncryptionManager $encryptionManager ) { parent::__construct($appName, $request); @@ -126,6 +130,7 @@ class WopiController extends Controller { $this->templateManager = $templateManager; $this->shareManager = $shareManager; $this->userScopeService = $userScopeService; + $this->federationService = $federationService; $this->encryptionManager = $encryptionManager; } @@ -157,10 +162,10 @@ class WopiController extends Controller { throw new NotFoundException('No valid file found for ' . $fileId); } } catch (NotFoundException $e) { - $this->logger->debug($e->getMessage(), ['app' => 'richdocuments', '']); + $this->logger->debug($e->getMessage(), ['app' => 'richdocuments']); return new JSONResponse([], Http::STATUS_FORBIDDEN); } catch (DoesNotExistException $e) { - $this->logger->debug($e->getMessage(), ['app' => 'richdocuments', '']); + $this->logger->debug($e->getMessage(), ['app' => 'richdocuments']); return new JSONResponse([], Http::STATUS_FORBIDDEN); } catch (\Exception $e) { $this->logger->logException($e, ['app' => 'richdocuments']); @@ -179,8 +184,7 @@ class WopiController extends Controller { 'UserId' => !$isPublic ? $wopi->getEditorUid() : $guestUserId, 'OwnerId' => $wopi->getOwnerUid(), 'UserFriendlyName' => $userDisplayName, - 'UserExtraInfo' => [ - ], + 'UserExtraInfo' => [], 'UserCanWrite' => (bool)$wopi->getCanwrite(), 'UserCanNotWriteRelative' => $this->encryptionManager->isEnabled() || $isPublic, 'PostMessageOrigin' => $wopi->getServerHost(), @@ -198,7 +202,12 @@ class WopiController extends Controller { 'DownloadAsPostMessage' => $wopi->getDirect(), ]; - if ($wopi->isTemplateToken()) { + if ($wopi->hasTemplateId()) { + $templateUrl = 'index.php/apps/richdocuments/wopi/template/' . $wopi->getTemplateId() . '?access_token=' . $wopi->getToken(); + $templateUrl = $this->urlGenerator->getAbsoluteURL($templateUrl); + $response['TemplateSource'] = $templateUrl; + } elseif ($wopi->isTemplateToken()) { + // FIXME: Remove backward compatibility layer once TemplateSource is available in all supported Collabora versions $userFolder = $this->rootFolder->getUserFolder($wopi->getOwnerUid()); $file = $userFolder->getById($wopi->getTemplateDestination())[0]; $response['TemplateSaveAs'] = $file->getName(); @@ -212,7 +221,6 @@ class WopiController extends Controller { 'themingName' => \OC::$server->getThemingDefaults()->getName(), 'userDisplayName' => $userDisplayName, 'email' => $email, - ]; $watermarkTemplate = $this->appConfig->getAppValue('watermark_text'); $response['WatermarkText'] = preg_replace_callback('/{(.+?)}/', function ($matches) use ($replacements) { @@ -220,45 +228,58 @@ class WopiController extends Controller { }, $watermarkTemplate); } - /** - * New approach for generating files from templates by creating an empty file - * and providing an URL which returns the actual template - */ - if ($wopi->hasTemplateId()) { - $templateUrl = 'index.php/apps/richdocuments/wopi/template/' . $wopi->getTemplateId() . '?access_token=' . $wopi->getToken(); - $templateUrl = $this->urlGenerator->getAbsoluteURL($templateUrl); - $response['TemplateSource'] = $templateUrl; - } - $user = $this->userManager->get($wopi->getEditorUid()); if($user !== null) { $response['UserExtraInfo']['avatar'] = $this->urlGenerator->linkToRouteAbsolute('core.avatar.getAvatar', ['userId' => $wopi->getEditorUid(), 'size' => 32]); } - if (!empty($wopi->getRemoteServer())) { + if ($wopi->isRemoteToken()) { $response = $this->setFederationFileInfo($wopi, $response); } return new JSONResponse($response); } - private function setFederationFileInfo($wopi, $response) { - $remoteUserId = $wopi->getGuestDisplayname(); - $cloudID = \OC::$server->getCloudIdManager()->resolveCloudId($remoteUserId); - $response['UserFriendlyName'] = $cloudID->getDisplayId(); - $response['UserExtraInfo']['avatar'] = $this->urlGenerator->linkToRouteAbsolute('core.avatar.getAvatar', ['userId' => explode('@', $remoteUserId)[0], 'size' => 32]); - $cleanCloudId = str_replace(['http://', 'https://'], '', $cloudID->getId()); - $addressBookEntries = \OC::$server->getContactsManager()->search($cleanCloudId, ['CLOUD']); - foreach ($addressBookEntries as $entry) { - if (isset($entry['CLOUD'])) { - foreach ($entry['CLOUD'] as $cloudID) { - if ($cloudID === $cleanCloudId) { - $response['UserFriendlyName'] = $entry['FN']; - break; + + private function setFederationFileInfo(Wopi $wopi, $response) { + 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]); + $cleanCloudId = str_replace(['http://', 'https://'], '', $cloudID->getId()); + $addressBookEntries = \OC::$server->getContactsManager()->search($cleanCloudId, ['CLOUD']); + foreach ($addressBookEntries as $entry) { + if (isset($entry['CLOUD'])) { + foreach ($entry['CLOUD'] as $cloudID) { + if ($cloudID === $cleanCloudId) { + $response['UserFriendlyName'] = $entry['FN']; + break; + } } } } + } 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'; + } } + return $response; } @@ -534,7 +555,7 @@ class WopiController extends Controller { // Unless the editor is empty (public link) we modify the files as the current editor $editor = $wopi->getEditorUid(); - if ($editor === null || !empty($wopi->getRemoteServer())) { + if ($editor === null || !$wopi->isRemoteToken()) { $editor = $wopi->getOwnerUid(); } @@ -673,8 +694,8 @@ class WopiController extends Controller { private function getFileForWopiToken(Wopi $wopi) { $file = null; - if (!empty($wopi->getRemoteServer())) { - $share = $this->shareManager->getShareByToken($wopi->getEditorUid()); + if (!empty($wopi->getShare())) { + $share = $this->shareManager->getShareByToken($wopi->getShare()); $node = $share->getNode(); if ($node instanceof Folder) { $file = $node->getById($wopi->getFileid())[0]; diff --git a/lib/Db/Direct.php b/lib/Db/Direct.php index 92dcbe05..addd77ff 100644 --- a/lib/Db/Direct.php +++ b/lib/Db/Direct.php @@ -58,6 +58,15 @@ class Direct extends Entity { /** @var int */ protected $templateId; + /** @var string */ + protected $share; + + /** @var string */ + protected $initiatorHost; + + /** @var string */ + protected $initiatorToken; + public function __construct() { $this->addType('token', 'string'); $this->addType('uid', 'string'); @@ -65,5 +74,8 @@ class Direct extends Entity { $this->addType('timestamp', 'int'); $this->addType('template_destination', 'int'); $this->addType('template_id', 'int'); + $this->addType('share', 'string'); + $this->addType('initiator_host', 'string'); + $this->addType('initiator_token', 'string'); } } diff --git a/lib/Db/DirectMapper.php b/lib/Db/DirectMapper.php index e3fa8841..f98b2a40 100644 --- a/lib/Db/DirectMapper.php +++ b/lib/Db/DirectMapper.php @@ -51,18 +51,21 @@ class DirectMapper extends Mapper { } /** - * @param string $uid + * @param string|null $uid * @param int $fileid * @param int $destination * @return Direct */ - public function newDirect($uid, $fileid, $destination = null) { + public function newDirect($uid, $fileid, $destination = null, $share = null, $initiatorHost = null, $initiatorToken = null) { $direct = new Direct(); $direct->setUid($uid); $direct->setFileid($fileid); $direct->setToken($this->random->generate(64, ISecureRandom::CHAR_DIGITS . ISecureRandom::CHAR_LOWER . ISecureRandom::CHAR_UPPER)); $direct->setTimestamp($this->timeFactory->getTime()); $direct->setTemplateDestination($destination); + $direct->setShare($share); + $direct->setInitiatorHost($initiatorHost); + $direct->setInitiatorToken($initiatorToken); $direct = $this->insert($direct); return $direct; diff --git a/lib/Db/Wopi.php b/lib/Db/Wopi.php index 06763687..11d7d4ed 100644 --- a/lib/Db/Wopi.php +++ b/lib/Db/Wopi.php @@ -43,6 +43,12 @@ use OCP\AppFramework\Db\Entity; * @method string getServerHost() * @method void setToken(string $token) * @method string getToken() + * @method void setTokenType(int $tokenType) + * @method int getTokenType() + * @method void setRemoteServer(string $remoteServer) + * @method string getRemoteServer() + * @method void setRemoteServerToken(string $remoteToken) + * @method string getRemoteServerToken() * @method void setExpiry(int $expiry) * @method int getExpiry() * @method void setGuestDisplayname(string $token) @@ -52,8 +58,10 @@ use OCP\AppFramework\Db\Entity; * @method void setTemplateId(int $fileId) * @method int getTemplateId() * @method void setShare(string $token) + * @method string getShare() + * @method static Wopi fromParams(array $params) */ -class Wopi extends Entity { +class Wopi extends Entity implements \JsonSerializable { /** * WOPI token to open a file as a user on the current instance @@ -66,19 +74,19 @@ class Wopi extends Entity { const TOKEN_TYPE_GUEST = 1; /** - * WOPI token to open a file as a user from a federated instane + * WOPI token to open a file as a user from a federated instance */ const TOKEN_TYPE_REMOTE_USER = 2; /** - * WOPI token to open a file as a guest from a federated instane + * WOPI token to open a file as a guest from a federated instance */ const TOKEN_TYPE_REMOTE_GUEST = 3; /* - * Temporary token that is used to share the opener details to a federated instance + * Temporary token that is used to share the initiator details to the source instance */ - const TOKEN_TYPE_FEDERATION = 4; + const TOKEN_TYPE_INITIATOR = 4; /** @var string */ protected $ownerUid; @@ -157,20 +165,20 @@ class Wopi extends Entity { } public function isGuest() { - return $this->getTokenType() === Wopi::TOKEN_TYPE_GUEST || Wopi::TOKEN_TYPE_REMOTE_GUEST; + return $this->getTokenType() === Wopi::TOKEN_TYPE_GUEST || $this->getTokenType() === Wopi::TOKEN_TYPE_REMOTE_GUEST; + } + + public function isRemoteToken() { + return $this->getTokenType() === Wopi::TOKEN_TYPE_REMOTE_USER || $this->getTokenType() === Wopi::TOKEN_TYPE_REMOTE_GUEST; } public function getUserForFileAccess() { - if ($this->share !== null) { + if ($this->share !== null || $this->tokenType === self::TOKEN_TYPE_REMOTE_USER || $this->tokenType === self::TOKEN_TYPE_REMOTE_GUEST) { return $this->getOwnerUid(); } return $this->isGuest() ? $this->getOwnerUid() : $this->getEditorUid(); } - public function getCanwrite() { - return (bool)$this->canwrite; - } - public function getHideDownload() { return (bool)$this->hideDownload; } @@ -179,4 +187,18 @@ class Wopi extends Entity { return (bool)$this->direct; } + public function jsonSerialize() { + $properties = get_object_vars($this); + $reflection = new \ReflectionClass($this); + $json = []; + foreach ($properties as $property => $value) { + if (strpos($property, '_') !== 0 && $reflection->hasProperty($property)) { + $propertyReflection = $reflection->getProperty($property); + if (!$propertyReflection->isPrivate()) { + $json[$property] = $this->getter($property); + } + } + } + return $json; + } } diff --git a/lib/Db/WopiMapper.php b/lib/Db/WopiMapper.php index 056c6078..eeea8041 100644 --- a/lib/Db/WopiMapper.php +++ b/lib/Db/WopiMapper.php @@ -64,7 +64,7 @@ class WopiMapper extends Mapper { * @param int $templateDestination * @return Wopi */ - public function generateFileToken($fileId, $owner, $editor, $version, $updatable, $serverHost, $guestDisplayname, $templateDestination = 0, $hideDownload = false, $direct = false, $templateId = 0, $share = null, $tokenType = Wopi::TOKEN_TYPE_USER) { + public function generateFileToken($fileId, $owner, $editor, $version, $updatable, $serverHost, $guestDisplayname = null, $templateDestination = 0, $hideDownload = false, $direct = false, $templateId = 0, $share = null) { $token = $this->random->generate(32, ISecureRandom::CHAR_LOWER . ISecureRandom::CHAR_UPPER . ISecureRandom::CHAR_DIGITS); $wopi = Wopi::fromParams([ @@ -84,7 +84,25 @@ class WopiMapper extends Mapper { 'remoteServer' => '', 'remoteServerToken' => '', 'share' => $share, - 'tokenType' => $tokenType + 'tokenType' => $guestDisplayname === null ? Wopi::TOKEN_TYPE_USER : Wopi::TOKEN_TYPE_GUEST + ]); + + /** @var Wopi $wopi */ + $wopi = $this->insert($wopi); + + return $wopi; + } + + public function generateInitiatorToken($uid, $remoteServer) { + $token = $this->random->generate(32, ISecureRandom::CHAR_LOWER . ISecureRandom::CHAR_UPPER . ISecureRandom::CHAR_DIGITS); + + $wopi = Wopi::fromParams([ + 'fileid' => 0, + 'editorUid' => $uid, + 'token' => $token, + 'expiry' => $this->timeFactory->getTime() + self::TOKEN_LIFETIME_SECONDS, + 'remoteServer' => $remoteServer, + 'tokenType' => Wopi::TOKEN_TYPE_INITIATOR ]); /** @var Wopi $wopi */ diff --git a/lib/Migration/Version30717Date20210310164901.php b/lib/Migration/Version30717Date20210310164901.php index 21bb289b..8f4347f1 100644 --- a/lib/Migration/Version30717Date20210310164901.php +++ b/lib/Migration/Version30717Date20210310164901.php @@ -28,6 +28,27 @@ class Version30717Date20210310164901 extends SimpleMigrationStep { $table->dropColumn('is_remote_token'); } + $table = $schema->getTable('richdocuments_direct'); + + if (!$table->hasColumn('share')) { + $table->addColumn('share', 'string', [ + 'notnull' => false, + 'length' => 64 + ]); + } + if (!$table->hasColumn('initiator_host')) { + $table->addColumn('initiator_host', 'string', [ + 'notnull' => false, + 'length' => 255 + ]); + } + if (!$table->hasColumn('initiator_token')) { + $table->addColumn('initiator_token', 'string', [ + 'notnull' => false, + 'length' => 64 + ]); + } + return $schema; } diff --git a/lib/Service/FederationService.php b/lib/Service/FederationService.php index d07c61f9..fa555c49 100644 --- a/lib/Service/FederationService.php +++ b/lib/Service/FederationService.php @@ -26,6 +26,8 @@ namespace OCA\Richdocuments\Service; 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\TokenManager; use OCP\AppFramework\Http\RedirectResponse; use OCP\AppFramework\QueryException; @@ -38,6 +40,7 @@ use OCP\ICacheFactory; use OCP\IConfig; use OCP\ILogger; use OCP\IRequest; +use OCP\Share\IShare; class FederationService { @@ -148,33 +151,21 @@ class FederationService { return $host; } - public function getRemoteDirectUrl($remote, $shareToken, $filePath) { - if ($this->getRemoteCollaboraURL() === '') { - return ''; + /** @return Wopi|null */ + public function getRemoteFileDetails(string $remote, string $remoteToken) { + $cacheKey = md5($remote . $remoteToken); + $remoteWopi = $this->cache->get($cacheKey); + if ($remoteWopi !== null) { + return Wopi::fromParams($remoteWopi); } - try { - $client = $this->clientService->newClient(); - $response = $client->post($remote . '/ocs/v2.php/apps/richdocuments/api/v1/federation/direct?format=json', [ - 'timeout' => 5, - 'body' => [ - 'shareToken' => $shareToken, - 'filePath' => $filePath - ] - ]); - $data = \json_decode($response->getBody(), true); - return $data['ocs']['data']; - } catch (\Throwable $e) { - $this->logger->info('Unable to determine collabora URL of remote server ' . $remote, ['exception' => $e]); - } - return null; - } - public function getRemoteFileDetails($remote, $remoteToken) { if (!$this->isTrustedRemote($remote)) { - $this->logger->info('Unable to determine collabora URL of remote server ' . $remote . ' - Remote is not a trusted server'); + $this->logger->info('COOL-Federation-Source: Unable to determine collabora URL of remote server ' . $remote . ' for token ' . $remoteToken . ' - Remote is not a trusted server'); return null; } + try { + $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, @@ -184,10 +175,11 @@ class FederationService { ]); $responseBody = $response->getBody(); $data = \json_decode($responseBody, true, 512); - $this->logger->debug('Reveived remote file details for ' . $remoteToken . ' from ' . $remote . ': ' . $responseBody); - return $data['ocs']['data']; + $this->logger->debug('COOL-Federation-Source: Received remote file details for ' . $remoteToken . ' from ' . $remote . ': ' . json_encode($data['ocs']['data'])); + $this->cache->set($cacheKey, $data['ocs']['data']); + return Wopi::fromParams($data['ocs']['data']); } catch (\Throwable $e) { - $this->logger->error('Unable to fetch remote file details for ' . $remoteToken . ' from ' . $remote, ['exception' => $e]); + $this->logger->error('COOL-Federation-Source: Unable to fetch remote file details for ' . $remoteToken . ' from ' . $remote, ['exception' => $e]); } return null; } @@ -198,26 +190,37 @@ class FederationService { * @throws NotFoundException * @throws InvalidPathException */ - public function getRemoteRedirectURL(File $item, $direct = null) { - if ($item->getStorage()->instanceOfStorage(SharingExternalStorage::class)) { - $remote = $item->getStorage()->getRemote(); - $remoteCollabora = $this->getRemoteCollaboraURL($remote); - if ($remoteCollabora !== '') { - if ($direct === null) { - $wopi = $this->tokenManager->getRemoteToken($item); - } else { - $wopi = $this->tokenManager->getRemoteTokenFromDirect($item, $direct->getUid()); - } - $url = rtrim($remote, '/') . '/index.php/apps/richdocuments/remote?shareToken=' . $item->getStorage()->getToken() . - '&remoteServer=' . $wopi->getServerHost() . - '&remoteServerToken=' . $wopi->getToken(); - if ($item->getInternalPath() !== '') { - $url .= '&filePath=' . $item->getInternalPath(); - } - return $url; + public function getRemoteRedirectURL(File $item, Direct $direct = null, IShare $share = null) { + if (!$item->getStorage()->instanceOfStorage(SharingExternalStorage::class)) { + return null; + } + + $remote = $item->getStorage()->getRemote(); + $remoteCollabora = $this->getRemoteCollaboraURL($remote); + if ($remoteCollabora !== '') { + $shareToken = $share ? $share->getToken() : null; + + $wopi = $this->tokenManager->newInitiatorToken($remote, $item, $shareToken, ($direct !== null), ($direct ? $direct->getUid() : null)); + $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 + } + + $url = rtrim($remote, '/') . '/index.php/apps/richdocuments/remote?shareToken=' . $item->getStorage()->getToken() . + '&remoteServer=' . $initiatorServer . + '&remoteServerToken=' . $initiatorToken; + if ($item->getInternalPath() !== '') { + $url .= '&filePath=' . $item->getInternalPath(); } - throw new NotFoundException('Failed to connect to remote collabora instance for ' . $item->getId()); + return $url; } - return null; + + throw new NotFoundException('Failed to connect to remote collabora instance for ' . $item->getId()); } } diff --git a/lib/TokenManager.php b/lib/TokenManager.php index 5da5c92c..4a7bebd7 100644 --- a/lib/TokenManager.php +++ b/lib/TokenManager.php @@ -21,6 +21,7 @@ namespace OCA\Richdocuments; +use OCA\Richdocuments\Db\Direct; use OCA\Richdocuments\Db\WopiMapper; use OCA\Richdocuments\Db\Wopi; use OCA\Richdocuments\Service\CapabilitiesService; @@ -33,6 +34,7 @@ use OCP\IURLGenerator; use OCP\IUserManager; use OCP\Share\IManager; use OCP\IL10N; +use OCP\Share\IShare; use OCP\Util; class TokenManager { @@ -61,16 +63,6 @@ class TokenManager { /** @var Helper */ private $helper; - /** - * @param IRootFolder $rootFolder - * @param IManager $shareManager - * @param IURLGenerator $urlGenerator - * @param Parser $wopiParser - * @param AppConfig $appConfig - * @param string $UserId - * @param WopiMapper $wopiMapper - * @param IL10N $trans - */ public function __construct( IRootFolder $rootFolder, IManager $shareManager, @@ -191,25 +183,24 @@ class TokenManager { $fp = $file->fopen('r'); fclose($fp); - $serverHost = $this->urlGenerator->getAbsoluteURL('/');//$this->request->getServerProtocol() . '://' . $this->request->getServerHost(); + $serverHost = $this->urlGenerator->getAbsoluteURL('/'); - $guest_name = null; + $guestName = null; if ($this->userId === null) { - if ($guest_name = $this->helper->getGuestName()) { - $guest_name = $this->trans->t('%s (Guest)', Util::sanitizeHTML($guest_name)); + if ($guestName = $this->helper->getGuestName()) { + $guestName = $this->trans->t('%s (Guest)', Util::sanitizeHTML($guestName)); $cut = 56; - while (mb_strlen($guest_name) >= 64) { - $guest_name = $this->trans->t('%s (Guest)', Util::sanitizeHTML( - mb_substr($guest_name, 0, $cut) + while (mb_strlen($guestName) >= 64) { + $guestName = $this->trans->t('%s (Guest)', Util::sanitizeHTML( + mb_substr($guestName, 0, $cut) )); $cut -= 5; } } else { - $guest_name = $this->trans->t('Anonymous guest'); + $guestName = $this->trans->t('Anonymous guest'); } } - - $wopi = $this->wopiMapper->generateFileToken($fileId, $owneruid, $editoruid, $version, $updatable, $serverHost, $guest_name, 0, $hideDownload, $direct, 0, $shareToken); + $wopi = $this->wopiMapper->generateFileToken($fileId, $owneruid, $editoruid, $version, $updatable, $serverHost, $guestName, 0, $hideDownload, $direct, 0, $shareToken); try { @@ -224,21 +215,33 @@ class TokenManager { } /** - * @param Wopi $wopi - * @param $shareToken - * @param $remoteServer - * @param $remoteServerToken - * @param $remoteWopi - * @return Wopi + * This method is receiving the results from the TOKEN_TYPE_FEDERATION generated on the opener server + * that is created in {@link newInitiatorToken} */ - public function updateToFederationToken(Wopi $wopi, $shareToken, $remoteServer, $remoteServerToken, $remoteWopi) { - // $wopi->setTokenType(Wopi::TOKEN_TYPE_REMOTE_*); - $uid = $remoteWopi['editorUid'] ? ($remoteWopi['editorUid'] . '@' . $remoteServer) : null; - $wopi->setEditorUid($shareToken); - $wopi->setCanwrite($wopi->getCanwrite() && $remoteWopi['canwrite']); + public function upgradeToRemoteToken(Wopi $wopi, Wopi $remoteWopi, string $shareToken, string $remoteServer, string $remoteServerToken): Wopi { + if ($remoteWopi->getTokenType() !== Wopi::TOKEN_TYPE_INITIATOR) { + return $wopi; + } + + $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->setShare($shareToken); + $wopi->setCanwrite($wopi->getCanwrite() && $remoteWopi->getCanwrite()); + $wopi->setHideDownload($wopi->getHideDownload() || $remoteWopi->getHideDownload()); $wopi->setRemoteServer($remoteServer); $wopi->setRemoteServerToken($remoteServerToken); - $wopi->setGuestDisplayname($uid); + $this->wopiMapper->update($wopi); + return $wopi; + } + + public function upgradeFromDirectInitiator(Direct $direct, Wopi $wopi) { + $wopi->setTokenType(Wopi::TOKEN_TYPE_REMOTE_GUEST); + $wopi->setEditorUid(null); + $wopi->setRemoteServer($direct->getInitiatorHost()); + $wopi->setRemoteServerToken($direct->getInitiatorToken()); $this->wopiMapper->update($wopi); return $wopi; } @@ -287,31 +290,16 @@ class TokenManager { ]; } - /** - * @param Node $node - * @return Wopi - */ - public function getRemoteToken(Node $node) { - list($urlSrc, $token, $wopi) = $this->getToken($node->getId(), null, null, false); - $wopi->setIsRemoteToken(true); - $wopi->setRemoteServer($node->getStorage()->getRemote()); - $wopi->setTokenType(Wopi::TOKEN_TYPE_REMOTE_USER); - $this->wopiMapper->update($wopi); - return $wopi; - } + public function newInitiatorToken($sourceServer, Node $node = null, $shareToken = null, bool $direct = false, $userId = null): Wopi { + if ($node !== null) { + list($urlSrc, $token, $wopi) = $this->getToken($node->getId(), $shareToken, $userId, $direct); + $wopi->setServerHost($sourceServer); + $wopi->setTokenType(Wopi::TOKEN_TYPE_INITIATOR); + $this->wopiMapper->update($wopi); + return $wopi; + } - /** - * @param Node $node - * @return Wopi - */ - public function getRemoteTokenFromDirect(Node $node, $editorUid) { - list($urlSrc, $token, $wopi) = $this->getToken($node->getId(), null, $editorUid, true); - $wopi->setIsRemoteToken(true); - $wopi->setRemoteServer($node->getStorage()->getRemote()); - $wopi->setTokenType(Wopi::TOKEN_TYPE_REMOTE_USER); - $this->wopiMapper->update($wopi); - return $wopi; + return $this->wopiMapper->generateInitiatorToken($this->userId, $sourceServer); } - } |