Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion css/admin.scss
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
@use "sass:math";
@use 'sass:math';

.rd-settings-documentation {
max-width: 50em;
Expand Down
2 changes: 1 addition & 1 deletion css/templatePicker.scss
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
*
*/

@use "sass:math";
@use 'sass:math';

#template-picker {
.template-container:not(.hidden) {
Expand Down
3 changes: 3 additions & 0 deletions lib/AppInfo/Application.php
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
use OCA\Files_Sharing\Event\ShareLinkAccessedEvent;
use OCA\Richdocuments\AppConfig;
use OCA\Richdocuments\Capabilities;
use OCA\Richdocuments\Listener\BeforeFetchPreviewListener;
use OCA\Richdocuments\Listener\CSPListener;
use OCA\Richdocuments\Listener\LoadViewerListener;
use OCA\Richdocuments\Listener\ShareLinkListener;
Expand All @@ -52,6 +53,7 @@
use OCP\IConfig;
use OCP\IL10N;
use OCP\IPreview;
use OCP\Preview\BeforeFetchPreviewEvent;
use OCP\Security\CSP\AddContentSecurityPolicyEvent;

class Application extends App implements IBootstrap {
Expand All @@ -70,6 +72,7 @@ public function register(IRegistrationContext $context): void {
$context->registerEventListener(AddContentSecurityPolicyEvent::class, CSPListener::class);
$context->registerEventListener(LoadViewer::class, LoadViewerListener::class);
$context->registerEventListener(ShareLinkAccessedEvent::class, ShareLinkListener::class);
$context->registerEventListener(BeforeFetchPreviewEvent::class, BeforeFetchPreviewListener::class);
}

public function boot(IBootContext $context): void {
Expand Down
1 change: 1 addition & 0 deletions lib/Controller/SettingsController.php
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,7 @@ public function updateWatermarkSettings($settings = []) {
'watermark_enabled',
'watermark_shareAll',
'watermark_shareRead',
'watermark_shareDisabledDownload',
'watermark_linkSecure',
'watermark_linkRead',
'watermark_linkAll',
Expand Down
70 changes: 13 additions & 57 deletions lib/Controller/WopiController.php
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@
use OCP\PreConditionNotMetException;
use OCP\Share\Exceptions\ShareNotFound;
use OCP\Share\IManager as IShareManager;
use OCP\Share\IShare;
use Psr\Container\ContainerExceptionInterface;
use Psr\Container\NotFoundExceptionInterface;

Expand Down Expand Up @@ -234,7 +235,8 @@ public function checkFileInfo($fileId, $access_token) {
$response['TemplateSaveAs'] = $file->getName();
}

if ($this->shouldWatermark($isPublic, $wopi->getEditorUid(), $fileId, $wopi)) {
$share = $this->getShareForWopiToken($wopi);
if ($this->permissionManager->shouldWatermark($file, $wopi->getEditorUid(), $share)) {
$email = $user !== null && !$isPublic ? $user->getEMailAddress() : "";
$replacements = [
'userId' => $wopi->getEditorUid(),
Expand Down Expand Up @@ -318,62 +320,6 @@ private function setFederationFileInfo(Wopi $wopi, $response) {
return $response;
}

private function shouldWatermark($isPublic, $userId, $fileId, Wopi $wopi) {
if ($this->config->getAppValue(AppConfig::WATERMARK_APP_NAMESPACE, 'watermark_enabled', 'no') === 'no') {
return false;
}

if ($isPublic) {
if ($this->config->getAppValue(AppConfig::WATERMARK_APP_NAMESPACE, 'watermark_linkAll', 'no') === 'yes') {
return true;
}
if ($this->config->getAppValue(AppConfig::WATERMARK_APP_NAMESPACE, 'watermark_linkRead', 'no') === 'yes' && !$wopi->getCanwrite()) {
return true;
}
if ($this->config->getAppValue(AppConfig::WATERMARK_APP_NAMESPACE, 'watermark_linkSecure', 'no') === 'yes' && $wopi->getHideDownload()) {
return true;
}
if ($this->config->getAppValue(AppConfig::WATERMARK_APP_NAMESPACE, 'watermark_linkTags', 'no') === 'yes') {
$tags = $this->appConfig->getAppValueArray('watermark_linkTagsList');
$fileTags = \OC::$server->getSystemTagObjectMapper()->getTagIdsForObjects([$fileId], 'files')[$fileId];
foreach ($fileTags as $tagId) {
if (in_array($tagId, $tags, true)) {
return true;
}
}
}
} else {
if ($this->config->getAppValue(AppConfig::WATERMARK_APP_NAMESPACE, 'watermark_shareAll', 'no') === 'yes') {
$files = $this->rootFolder->getUserFolder($userId)->getById($fileId);
if (count($files) !== 0 && $files[0]->getOwner()->getUID() !== $userId) {
return true;
}
}
if ($this->config->getAppValue(AppConfig::WATERMARK_APP_NAMESPACE, 'watermark_shareRead', 'no') === 'yes' && !$wopi->getCanwrite()) {
return true;
}
}
if ($userId !== null && $this->config->getAppValue(AppConfig::WATERMARK_APP_NAMESPACE, 'watermark_allGroups', 'no') === 'yes') {
$groups = $this->appConfig->getAppValueArray('watermark_allGroupsList');
foreach ($groups as $group) {
if (\OC::$server->getGroupManager()->isInGroup($userId, $group)) {
return true;
}
}
}
if ($this->config->getAppValue(AppConfig::WATERMARK_APP_NAMESPACE, 'watermark_allTags', 'no') === 'yes') {
$tags = $this->appConfig->getAppValueArray('watermark_allTagsList');
$fileTags = \OC::$server->getSystemTagObjectMapper()->getTagIdsForObjects([$fileId], 'files')[$fileId];
foreach ($fileTags as $tagId) {
if (in_array($tagId, $tags, true)) {
return true;
}
}
}

return false;
}

/**
* Given an access token and a fileId, returns the contents of the file.
* Expects a valid token in access_token parameter.
Expand Down Expand Up @@ -842,6 +788,7 @@ private function retryOperation(callable $operation) {
* @throws ShareNotFound
*/
private function getFileForWopiToken(Wopi $wopi) {
$this->userScopeService->setUserScope($wopi->getEditorUid());
if (!empty($wopi->getShare())) {
$share = $this->shareManager->getShareByToken($wopi->getShare());
$node = $share->getNode();
Expand Down Expand Up @@ -875,6 +822,15 @@ private function getFileForWopiToken(Wopi $wopi) {
return array_shift($files);
}

private function getShareForWopiToken(Wopi $wopi): ?IShare {
try {
return $wopi->getShare() ? $this->shareManager->getShareByToken($wopi->getShare()) : null;
} catch (ShareNotFound $e) {
}

return null;
}

/**
* Endpoint to return the template file that is requested by collabora to create a new document
*
Expand Down
82 changes: 82 additions & 0 deletions lib/Listener/BeforeFetchPreviewListener.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
<?php

declare(strict_types=1);

/**
* @copyright Copyright (c) 2022 Julius Härtl <[email protected]>
*
* @author Julius Härtl <[email protected]>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/


namespace OCA\Richdocuments\Listener;

use OCA\Files_Sharing\SharedStorage;
use OCA\Richdocuments\PermissionManager;
use OCP\EventDispatcher\Event;
use OCP\EventDispatcher\IEventListener;
use OCP\Files\NotFoundException;
use OCP\IRequest;
use OCP\IUserSession;
use OCP\Preview\BeforeFetchPreviewEvent;
use OCP\Share\Exceptions\ShareNotFound;
use OCP\Share\IManager;
use OCP\Share\IShare;

class BeforeFetchPreviewListener implements IEventListener {
private PermissionManager $permissionManager;
private IUserSession $userSession;
private IRequest $request;
private IManager $shareManager;

public function __construct(PermissionManager $permissionManager, IUserSession $userSession, IRequest $request, IManager $shareManager) {
$this->permissionManager = $permissionManager;
$this->userSession = $userSession;
$this->request = $request;
$this->shareManager = $shareManager;
}

public function handle(Event $event): void {
if (!$event instanceof BeforeFetchPreviewEvent) {
return;
}
$shareToken = $this->request->getParam('token');

$share = null;

// Get share for internal shares
$storage = $event->getNode()->getStorage();
if (!$shareToken && $storage->instanceOfStorage(SharedStorage::class)) {
if (method_exists(IShare::class, 'getAttributes')) {
/** @var SharedStorage $storage */
$share = $storage->getShare();
}
}

// Get different share for public previews as the share from the node is only set for mounted shares
try {
$share = $shareToken ? $this->shareManager->getShareByToken($shareToken) : $share;
} catch (ShareNotFound $e) {
}

$userId = $this->userSession->getUser() ? $this->userSession->getUser()->getUID() : null;
if ($this->permissionManager->shouldWatermark($event->getNode(), $userId, $share)) {
throw new NotFoundException();
}
}
}
98 changes: 90 additions & 8 deletions lib/PermissionManager.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,26 +23,38 @@

namespace OCA\Richdocuments;

use OCP\Constants;
use OCP\Files\Node;
use OCP\IConfig;
use OCP\IGroupManager;
use OCP\IUserManager;
use OCP\IUserSession;
use OCP\Share\IAttributes;
use OCP\Share\IShare;
use OCP\SystemTag\ISystemTagObjectMapper;

class PermissionManager {
private AppConfig $config;
private AppConfig $appConfig;
private IConfig $config;
private IGroupManager $groupManager;
private IUserManager $userManager;
private IUserSession $userSession;
private ISystemTagObjectMapper $systemTagObjectMapper;

public function __construct(
AppConfig $config,
IGroupManager $groupManager,
IUserManager $userManager,
IUserSession $userSession
AppConfig $appConfig,
IConfig $config,
IGroupManager $groupManager,
IUserManager $userManager,
IUserSession $userSession,
ISystemTagObjectMapper $systemTagObjectMapper
) {
$this->appConfig = $appConfig;
$this->config = $config;
$this->groupManager = $groupManager;
$this->userManager = $userManager;
$this->userSession = $userSession;
$this->systemTagObjectMapper = $systemTagObjectMapper;
}

private function userMatchesGroupList(?string $userId = null, ?array $groupList = []): bool {
Expand Down Expand Up @@ -75,26 +87,96 @@ private function userMatchesGroupList(?string $userId = null, ?array $groupList
}

public function isEnabledForUser(string $userId = null): bool {
if ($this->userMatchesGroupList($userId, $this->config->getUseGroups())) {
if ($this->userMatchesGroupList($userId, $this->appConfig->getUseGroups())) {
return true;
}

return false;
}

public function userCanEdit(string $userId = null): bool {
if ($this->userMatchesGroupList($userId, $this->config->getEditGroups())) {
if ($this->userMatchesGroupList($userId, $this->appConfig->getEditGroups())) {
return true;
}

return false;
}

public function userIsFeatureLocked(string $userId = null): bool {
if ($this->config->isReadOnlyFeatureLocked() && !$this->userCanEdit($userId)) {
if ($this->appConfig->isReadOnlyFeatureLocked() && !$this->userCanEdit($userId)) {
return true;
}

return false;
}

public function shouldWatermark(Node $node, ?string $userId = null, ?IShare $share = null): bool {
if ($this->config->getAppValue(AppConfig::WATERMARK_APP_NAMESPACE, 'watermark_enabled', 'no') === 'no') {
return false;
}

$fileId = $node->getId();

$isUpdatable = $node->isUpdateable() && (!$share || $share->getPermissions() & Constants::PERMISSION_UPDATE);

$hasShareAttributes = $share && method_exists($share, 'getAttributes') && $share->getAttributes() instanceof IAttributes;
$isDisabledDownload = $hasShareAttributes && $share->getAttributes()->getAttribute('permissions', 'download') === false;
$isHideDownload = $share && $share->getHideDownload();
$isSecureView = $isDisabledDownload || $isHideDownload;

if ($share && $share->getShareType() === IShare::TYPE_LINK) {
if ($this->config->getAppValue(AppConfig::WATERMARK_APP_NAMESPACE, 'watermark_linkAll', 'no') === 'yes') {
return true;
}
if ($this->config->getAppValue(AppConfig::WATERMARK_APP_NAMESPACE, 'watermark_linkRead', 'no') === 'yes' && !$isUpdatable) {
return true;
}
if ($this->config->getAppValue(AppConfig::WATERMARK_APP_NAMESPACE, 'watermark_linkSecure', 'no') === 'yes' && $isSecureView) {
return true;
}
if ($this->config->getAppValue(AppConfig::WATERMARK_APP_NAMESPACE, 'watermark_linkTags', 'no') === 'yes') {
$tags = $this->appConfig->getAppValueArray('watermark_linkTagsList');
$fileTags = $this->systemTagObjectMapper->getTagIdsForObjects([$fileId], 'files')[$fileId];
foreach ($fileTags as $tagId) {
if (in_array($tagId, $tags, true)) {
return true;
}
}
}
}

if ($this->config->getAppValue(AppConfig::WATERMARK_APP_NAMESPACE, 'watermark_shareAll', 'no') === 'yes') {
if ($node->getOwner()->getUID() !== $userId) {
return true;
}
}

if ($this->config->getAppValue(AppConfig::WATERMARK_APP_NAMESPACE, 'watermark_shareRead', 'no') === 'yes' && !$isUpdatable) {
return true;
}

if ($this->config->getAppValue(AppConfig::WATERMARK_APP_NAMESPACE, 'watermark_shareDisabledDownload', 'no') === 'yes' && $isDisabledDownload) {
return true;
}

if ($userId !== null && $this->config->getAppValue(AppConfig::WATERMARK_APP_NAMESPACE, 'watermark_allGroups', 'no') === 'yes') {
$groups = $this->appConfig->getAppValueArray('watermark_allGroupsList');
foreach ($groups as $group) {
if ($this->groupManager->isInGroup($userId, $group)) {
return true;
}
}
}
if ($this->config->getAppValue(AppConfig::WATERMARK_APP_NAMESPACE, 'watermark_allTags', 'no') === 'yes') {
$tags = $this->appConfig->getAppValueArray('watermark_allTagsList');
$fileTags = $this->systemTagObjectMapper->getTagIdsForObjects([$fileId], 'files')[$fileId];
foreach ($fileTags as $tagId) {
if (in_array($tagId, $tags, true)) {
return true;
}
}
}

return false;
}
}
Loading