follow me slideshow: use wopi check file info for presentation leader

to communicate more securely who is leading the presentation,
communicate the leader via check file info, currently we restrict
file owner to be the presentation leader

Signed-off-by: Pranam Lashkari <lpranam@collabora.com>
This commit is contained in:
Pranam Lashkari 2025-11-24 18:34:02 +00:00
parent 13a1fb6659
commit 1a034174d2
5 changed files with 50 additions and 8 deletions

View file

@ -395,7 +395,8 @@ class DocumentController extends Controller {
$file = $this->getFileForUser($fileId);
$this->session->set(self::SESSION_FILE_TARGET, [
'fileId' => $file->getId()
'fileId' => $file->getId(),
'PresentationLeader' => $file->getFileInfo()->getOwner()->getUid()
]);
$filePath = $file->getPath();
@ -425,7 +426,7 @@ class DocumentController extends Controller {
}
$isGuest = $guestName || !$this->userId;
$wopi = $this->getToken($file, $share, null, $isGuest);
$wopi = $this->getToken($file, $share, null, $isGuest, presentationLeader: $this->session->get(self::SESSION_FILE_TARGET)['PresentationLeader']);
$this->tokenManager->setGuestName($wopi, $guestName);
@ -520,7 +521,7 @@ class DocumentController extends Controller {
throw new NotFoundException();
}
private function getToken(File $file, ?IShare $share = null, ?int $version = null, bool $isGuest = false): Wopi {
private function getToken(File $file, ?IShare $share = null, ?int $version = null, bool $isGuest = false, ?string $presentationLeader = null): Wopi {
// Pass through $version
$templateFile = $this->templateManager->getTemplateSource($file->getId());
if ($templateFile) {
@ -540,7 +541,7 @@ class DocumentController extends Controller {
}
return $this->tokenManager->generateWopiToken($this->getWopiFileId($file->getId(), $version), $share?->getToken(), $this->userId);
return $this->tokenManager->generateWopiToken($this->getWopiFileId($file->getId(), $version), $share?->getToken(), $this->userId, presentationLeader: $presentationLeader);
}
private function getWopiFileId(int $fileId, ?int $version = null): string {

View file

@ -64,6 +64,8 @@ use OCP\Share\IShare;
use Psr\Container\ContainerExceptionInterface;
use Psr\Container\NotFoundExceptionInterface;
use Psr\Log\LoggerInterface;
use OCP\ICacheFactory;
use OCP\ICache;
#[RestrictToWopiServer]
class WopiController extends Controller {
@ -96,6 +98,7 @@ class WopiController extends Controller {
private SettingsService $settingsService,
private CapabilitiesService $capabilitiesService,
private Helper $helper,
private ICacheFactory $cacheFactory,
) {
parent::__construct($appName, $request);
}
@ -149,6 +152,17 @@ class WopiController extends Controller {
$userId = !$isPublic ? $wopi->getEditorUid() : $guestUserId;
$cache = $this->cacheFactory->createLocal('richdocuments_wopi');
$extraJson = $cache->get('wopi_extra_' . $wopi->getToken());
if ($extraJson !== false && $extraJson !== '') {
$decoded = json_decode($extraJson, true);
if (is_array($decoded) && isset($decoded['PresentationLeader'])) {
$wopi->setPresentationLeader($decoded['PresentationLeader']);
}
$cache->remove('wopi_extra_' . $wopi->getToken());
}
$response = [
'BaseFileName' => $file->getName(),
'Size' => $file->getSize(),
@ -181,6 +195,7 @@ class WopiController extends Controller {
'EnableRemoteAIContent' => $isTaskProcessingEnabled,
'HasContentRange' => true,
'ServerPrivateInfo' => [],
'PresentationLeader' => $wopi->getPresentationLeader()
];
if ($this->capabilitiesService->hasSettingIframeSupport()) {

View file

@ -124,6 +124,8 @@ class Wopi extends Entity implements \JsonSerializable {
/** @var int */
protected $tokenType = 0;
protected ?string $presentationLeader = null;
public function __construct() {
$this->addType('ownerUid', Types::STRING);
$this->addType('editorUid', Types::STRING);
@ -139,6 +141,7 @@ class Wopi extends Entity implements \JsonSerializable {
$this->addType('hideDownload', Types::BOOLEAN);
$this->addType('direct', Types::BOOLEAN);
$this->addType('tokenType', Types::INTEGER);
$this->addType('presentationLeader', Types::STRING);
}
public function hasTemplateId() {
@ -168,6 +171,14 @@ class Wopi extends Entity implements \JsonSerializable {
return (bool)$this->direct;
}
public function setPresentationLeader(?string $val): void {
$this->presentationLeader = $val;
}
public function getPresentationLeader(): ?string {
return $this->presentationLeader;
}
#[\ReturnTypeWillChange]
public function jsonSerialize() {
$properties = get_object_vars($this);

View file

@ -14,6 +14,8 @@ use OCP\DB\QueryBuilder\IQueryBuilder;
use OCP\IDBConnection;
use OCP\Security\ISecureRandom;
use Psr\Log\LoggerInterface;
use OCP\ICacheFactory;
use OCP\ICache;
/** @template-extends QBMapper<Wopi> */
class WopiMapper extends QBMapper {
@ -23,6 +25,7 @@ class WopiMapper extends QBMapper {
private LoggerInterface $logger,
private ITimeFactory $timeFactory,
private AppConfig $appConfig,
private ICacheFactory $cacheFactory,
) {
parent::__construct($db, 'richdocuments_wopi', Wopi::class);
}
@ -38,7 +41,7 @@ class WopiMapper extends QBMapper {
* @param int $templateDestination
* @return Wopi
*/
public function generateFileToken($fileId, $owner, $editor, $version, $updatable, $serverHost, ?string $guestDisplayname = null, $hideDownload = false, $direct = false, $templateId = 0, $share = null) {
public function generateFileToken($fileId, $owner, $editor, $version, $updatable, $serverHost, ?string $guestDisplayname = null, $hideDownload = false, $direct = false, $templateId = 0, $share = null, ?string $presentationLeader = null) {
$token = $this->random->generate(32, ISecureRandom::CHAR_LOWER . ISecureRandom::CHAR_UPPER . ISecureRandom::CHAR_DIGITS);
$wopi = Wopi::fromParams([
@ -57,9 +60,21 @@ class WopiMapper extends QBMapper {
'remoteServer' => '',
'remoteServerToken' => '',
'share' => $share,
'tokenType' => $guestDisplayname === null ? Wopi::TOKEN_TYPE_USER : Wopi::TOKEN_TYPE_GUEST
'tokenType' => $guestDisplayname === null ? Wopi::TOKEN_TYPE_USER : Wopi::TOKEN_TYPE_GUEST,
'presentationLeader' => $presentationLeader
]);
// store transient value in memcache keyed by token with TTL = token expiry - now
if ($presentationLeader !== null) {
/** @var ICache $cache */
$cache = $this->cacheFactory->createLocal('richdocuments_wopi');
$ttl = $this->calculateNewTokenExpiry() - $this->timeFactory->getTime();
if ($ttl <= 0) {
$ttl = 60;
}
$cache->set('wopi_extra_' . $token, json_encode(['PresentationLeader' => $presentationLeader]), (int)$ttl);
}
/** @var Wopi $wopi */
$wopi = $this->insert($wopi);

View file

@ -45,7 +45,7 @@ class TokenManager {
/**
* @throws Exception
*/
public function generateWopiToken(string $fileId, ?string $shareToken = null, ?string $editoruid = null, bool $direct = false): Wopi {
public function generateWopiToken(string $fileId, ?string $shareToken = null, ?string $editoruid = null, bool $direct = false, ?string $presentationLeader = null): Wopi {
[$fileId, , $version] = Helper::parseFileId($fileId);
$owneruid = null;
$hideDownload = false;
@ -121,7 +121,7 @@ class TokenManager {
$serverHost = $this->urlGenerator->getAbsoluteURL('/');
$guestName = $editoruid === null ? $this->prepareGuestName($this->helper->getGuestNameFromCookie()) : null;
return $this->wopiMapper->generateFileToken($fileId, $owneruid, $editoruid, $version, $updatable, $serverHost, $guestName, $hideDownload, $direct, 0, $shareToken);
return $this->wopiMapper->generateFileToken($fileId, $owneruid, $editoruid, $version, $updatable, $serverHost, $guestName, $hideDownload, $direct, 0, $shareToken, $presentationLeader);
}
/**