Skip to content

Commit 1a52114

Browse files
authored
Merge pull request #2586 from nextcloud/backport/2579/stable25
[stable25] Listen for event during preview rendering and apply secure view options
2 parents 618d904 + cd81056 commit 1a52114

11 files changed

Lines changed: 260 additions & 104 deletions

css/admin.scss

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
@use "sass:math";
1+
@use 'sass:math';
22

33
.rd-settings-documentation {
44
max-width: 50em;

css/templatePicker.scss

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
*
2121
*/
2222

23-
@use "sass:math";
23+
@use 'sass:math';
2424

2525
#template-picker {
2626
.template-container:not(.hidden) {

lib/AppInfo/Application.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
use OCA\Files_Sharing\Event\ShareLinkAccessedEvent;
2828
use OCA\Richdocuments\AppConfig;
2929
use OCA\Richdocuments\Capabilities;
30+
use OCA\Richdocuments\Listener\BeforeFetchPreviewListener;
3031
use OCA\Richdocuments\Listener\CSPListener;
3132
use OCA\Richdocuments\Listener\LoadViewerListener;
3233
use OCA\Richdocuments\Listener\ShareLinkListener;
@@ -52,6 +53,7 @@
5253
use OCP\IConfig;
5354
use OCP\IL10N;
5455
use OCP\IPreview;
56+
use OCP\Preview\BeforePreviewFetchedEvent;
5557
use OCP\Security\CSP\AddContentSecurityPolicyEvent;
5658

5759
class Application extends App implements IBootstrap {
@@ -70,6 +72,7 @@ public function register(IRegistrationContext $context): void {
7072
$context->registerEventListener(AddContentSecurityPolicyEvent::class, CSPListener::class);
7173
$context->registerEventListener(LoadViewer::class, LoadViewerListener::class);
7274
$context->registerEventListener(ShareLinkAccessedEvent::class, ShareLinkListener::class);
75+
$context->registerEventListener(BeforePreviewFetchedEvent::class, BeforeFetchPreviewListener::class);
7376
}
7477

7578
public function boot(IBootContext $context): void {

lib/Controller/SettingsController.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -237,6 +237,7 @@ public function updateWatermarkSettings($settings = []) {
237237
'watermark_enabled',
238238
'watermark_shareAll',
239239
'watermark_shareRead',
240+
'watermark_shareDisabledDownload',
240241
'watermark_linkSecure',
241242
'watermark_linkRead',
242243
'watermark_linkAll',

lib/Controller/WopiController.php

Lines changed: 13 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@
6767
use OCP\PreConditionNotMetException;
6868
use OCP\Share\Exceptions\ShareNotFound;
6969
use OCP\Share\IManager as IShareManager;
70+
use OCP\Share\IShare;
7071
use Psr\Container\ContainerExceptionInterface;
7172
use Psr\Container\NotFoundExceptionInterface;
7273

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

237-
if ($this->shouldWatermark($isPublic, $wopi->getEditorUid(), $fileId, $wopi)) {
238+
$share = $this->getShareForWopiToken($wopi);
239+
if ($this->permissionManager->shouldWatermark($file, $wopi->getEditorUid(), $share)) {
238240
$email = $user !== null && !$isPublic ? $user->getEMailAddress() : "";
239241
$replacements = [
240242
'userId' => $wopi->getEditorUid(),
@@ -318,62 +320,6 @@ private function setFederationFileInfo(Wopi $wopi, $response) {
318320
return $response;
319321
}
320322

321-
private function shouldWatermark($isPublic, $userId, $fileId, Wopi $wopi) {
322-
if ($this->config->getAppValue(AppConfig::WATERMARK_APP_NAMESPACE, 'watermark_enabled', 'no') === 'no') {
323-
return false;
324-
}
325-
326-
if ($isPublic) {
327-
if ($this->config->getAppValue(AppConfig::WATERMARK_APP_NAMESPACE, 'watermark_linkAll', 'no') === 'yes') {
328-
return true;
329-
}
330-
if ($this->config->getAppValue(AppConfig::WATERMARK_APP_NAMESPACE, 'watermark_linkRead', 'no') === 'yes' && !$wopi->getCanwrite()) {
331-
return true;
332-
}
333-
if ($this->config->getAppValue(AppConfig::WATERMARK_APP_NAMESPACE, 'watermark_linkSecure', 'no') === 'yes' && $wopi->getHideDownload()) {
334-
return true;
335-
}
336-
if ($this->config->getAppValue(AppConfig::WATERMARK_APP_NAMESPACE, 'watermark_linkTags', 'no') === 'yes') {
337-
$tags = $this->appConfig->getAppValueArray('watermark_linkTagsList');
338-
$fileTags = \OC::$server->getSystemTagObjectMapper()->getTagIdsForObjects([$fileId], 'files')[$fileId];
339-
foreach ($fileTags as $tagId) {
340-
if (in_array($tagId, $tags, true)) {
341-
return true;
342-
}
343-
}
344-
}
345-
} else {
346-
if ($this->config->getAppValue(AppConfig::WATERMARK_APP_NAMESPACE, 'watermark_shareAll', 'no') === 'yes') {
347-
$files = $this->rootFolder->getUserFolder($userId)->getById($fileId);
348-
if (count($files) !== 0 && $files[0]->getOwner()->getUID() !== $userId) {
349-
return true;
350-
}
351-
}
352-
if ($this->config->getAppValue(AppConfig::WATERMARK_APP_NAMESPACE, 'watermark_shareRead', 'no') === 'yes' && !$wopi->getCanwrite()) {
353-
return true;
354-
}
355-
}
356-
if ($userId !== null && $this->config->getAppValue(AppConfig::WATERMARK_APP_NAMESPACE, 'watermark_allGroups', 'no') === 'yes') {
357-
$groups = $this->appConfig->getAppValueArray('watermark_allGroupsList');
358-
foreach ($groups as $group) {
359-
if (\OC::$server->getGroupManager()->isInGroup($userId, $group)) {
360-
return true;
361-
}
362-
}
363-
}
364-
if ($this->config->getAppValue(AppConfig::WATERMARK_APP_NAMESPACE, 'watermark_allTags', 'no') === 'yes') {
365-
$tags = $this->appConfig->getAppValueArray('watermark_allTagsList');
366-
$fileTags = \OC::$server->getSystemTagObjectMapper()->getTagIdsForObjects([$fileId], 'files')[$fileId];
367-
foreach ($fileTags as $tagId) {
368-
if (in_array($tagId, $tags, true)) {
369-
return true;
370-
}
371-
}
372-
}
373-
374-
return false;
375-
}
376-
377323
/**
378324
* Given an access token and a fileId, returns the contents of the file.
379325
* Expects a valid token in access_token parameter.
@@ -842,6 +788,7 @@ private function retryOperation(callable $operation) {
842788
* @throws ShareNotFound
843789
*/
844790
private function getFileForWopiToken(Wopi $wopi) {
791+
$this->userScopeService->setUserScope($wopi->getEditorUid());
845792
if (!empty($wopi->getShare())) {
846793
$share = $this->shareManager->getShareByToken($wopi->getShare());
847794
$node = $share->getNode();
@@ -875,6 +822,15 @@ private function getFileForWopiToken(Wopi $wopi) {
875822
return array_shift($files);
876823
}
877824

825+
private function getShareForWopiToken(Wopi $wopi): ?IShare {
826+
try {
827+
return $wopi->getShare() ? $this->shareManager->getShareByToken($wopi->getShare()) : null;
828+
} catch (ShareNotFound $e) {
829+
}
830+
831+
return null;
832+
}
833+
878834
/**
879835
* Endpoint to return the template file that is requested by collabora to create a new document
880836
*
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/**
6+
* @copyright Copyright (c) 2022 Julius Härtl <[email protected]>
7+
*
8+
* @author Julius Härtl <[email protected]>
9+
*
10+
* @license GNU AGPL version 3 or any later version
11+
*
12+
* This program is free software: you can redistribute it and/or modify
13+
* it under the terms of the GNU Affero General Public License as
14+
* published by the Free Software Foundation, either version 3 of the
15+
* License, or (at your option) any later version.
16+
*
17+
* This program is distributed in the hope that it will be useful,
18+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
19+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20+
* GNU Affero General Public License for more details.
21+
*
22+
* You should have received a copy of the GNU Affero General Public License
23+
* along with this program. If not, see <http://www.gnu.org/licenses/>.
24+
*/
25+
26+
27+
namespace OCA\Richdocuments\Listener;
28+
29+
use OCA\Files_Sharing\SharedStorage;
30+
use OCA\Richdocuments\PermissionManager;
31+
use OCP\EventDispatcher\Event;
32+
use OCP\EventDispatcher\IEventListener;
33+
use OCP\Files\NotFoundException;
34+
use OCP\IRequest;
35+
use OCP\IUserSession;
36+
use OCP\Preview\BeforePreviewFetchedEvent;
37+
use OCP\Share\Exceptions\ShareNotFound;
38+
use OCP\Share\IManager;
39+
use OCP\Share\IShare;
40+
41+
class BeforeFetchPreviewListener implements IEventListener {
42+
private PermissionManager $permissionManager;
43+
private IUserSession $userSession;
44+
private IRequest $request;
45+
private IManager $shareManager;
46+
47+
public function __construct(PermissionManager $permissionManager, IUserSession $userSession, IRequest $request, IManager $shareManager) {
48+
$this->permissionManager = $permissionManager;
49+
$this->userSession = $userSession;
50+
$this->request = $request;
51+
$this->shareManager = $shareManager;
52+
}
53+
54+
public function handle(Event $event): void {
55+
if (!$event instanceof BeforePreviewFetchedEvent) {
56+
return;
57+
}
58+
$shareToken = $this->request->getParam('token');
59+
60+
$share = null;
61+
62+
// Get share for internal shares
63+
$storage = $event->getNode()->getStorage();
64+
if (!$shareToken && $storage->instanceOfStorage(SharedStorage::class)) {
65+
if (method_exists(IShare::class, 'getAttributes')) {
66+
/** @var SharedStorage $storage */
67+
$share = $storage->getShare();
68+
}
69+
}
70+
71+
// Get different share for public previews as the share from the node is only set for mounted shares
72+
try {
73+
$share = $shareToken ? $this->shareManager->getShareByToken($shareToken) : $share;
74+
} catch (ShareNotFound $e) {
75+
}
76+
77+
$userId = $this->userSession->getUser() ? $this->userSession->getUser()->getUID() : null;
78+
if ($this->permissionManager->shouldWatermark($event->getNode(), $userId, $share)) {
79+
throw new NotFoundException();
80+
}
81+
}
82+
}

lib/PermissionManager.php

Lines changed: 91 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -23,30 +23,44 @@
2323

2424
namespace OCA\Richdocuments;
2525

26+
use OCP\Constants;
27+
use OCP\Files\Node;
28+
use OCP\IConfig;
2629
use OCP\IGroupManager;
2730
use OCP\IUserManager;
2831
use OCP\IUserSession;
32+
use OCP\Share\IAttributes;
33+
use OCP\Share\IShare;
34+
use OCP\SystemTag\ISystemTagObjectMapper;
2935

3036
class PermissionManager {
3137
/** @var AppConfig */
38+
private $appConfig;
39+
/** @var IConfig */
3240
private $config;
3341
/** @var IGroupManager */
3442
private $groupManager;
3543
/** @var IUserManager */
3644
private $userManager;
3745
/** @var IUserSession */
3846
private $userSession;
47+
/** @var ISystemTagObjectMapper */
48+
private $systemTagObjectMapper;
3949

4050
public function __construct(
41-
AppConfig $config,
42-
IGroupManager $groupManager,
43-
IUserManager $userManager,
44-
IUserSession $userSession
51+
AppConfig $appConfig,
52+
IConfig $config,
53+
IGroupManager $groupManager,
54+
IUserManager $userManager,
55+
IUserSession $userSession,
56+
ISystemTagObjectMapper $systemTagObjectMapper
4557
) {
58+
$this->appConfig = $appConfig;
4659
$this->config = $config;
4760
$this->groupManager = $groupManager;
4861
$this->userManager = $userManager;
4962
$this->userSession = $userSession;
63+
$this->systemTagObjectMapper = $systemTagObjectMapper;
5064
}
5165

5266
private function userMatchesGroupList(?string $userId = null, ?array $groupList = []): bool {
@@ -79,26 +93,96 @@ private function userMatchesGroupList(?string $userId = null, ?array $groupList
7993
}
8094

8195
public function isEnabledForUser(string $userId = null): bool {
82-
if ($this->userMatchesGroupList($userId, $this->config->getUseGroups())) {
96+
if ($this->userMatchesGroupList($userId, $this->appConfig->getUseGroups())) {
8397
return true;
8498
}
8599

86100
return false;
87101
}
88102

89103
public function userCanEdit(string $userId = null): bool {
90-
if ($this->userMatchesGroupList($userId, $this->config->getEditGroups())) {
104+
if ($this->userMatchesGroupList($userId, $this->appConfig->getEditGroups())) {
91105
return true;
92106
}
93107

94108
return false;
95109
}
96110

97111
public function userIsFeatureLocked(string $userId = null): bool {
98-
if ($this->config->isReadOnlyFeatureLocked() && !$this->userCanEdit($userId)) {
112+
if ($this->appConfig->isReadOnlyFeatureLocked() && !$this->userCanEdit($userId)) {
99113
return true;
100114
}
101115

102116
return false;
103117
}
118+
119+
public function shouldWatermark(Node $node, ?string $userId = null, ?IShare $share = null): bool {
120+
if ($this->config->getAppValue(AppConfig::WATERMARK_APP_NAMESPACE, 'watermark_enabled', 'no') === 'no') {
121+
return false;
122+
}
123+
124+
$fileId = $node->getId();
125+
126+
$isUpdatable = $node->isUpdateable() && (!$share || $share->getPermissions() & Constants::PERMISSION_UPDATE);
127+
128+
$hasShareAttributes = $share && method_exists($share, 'getAttributes') && $share->getAttributes() instanceof IAttributes;
129+
$isDisabledDownload = $hasShareAttributes && $share->getAttributes()->getAttribute('permissions', 'download') === false;
130+
$isHideDownload = $share && $share->getHideDownload();
131+
$isSecureView = $isDisabledDownload || $isHideDownload;
132+
133+
if ($share && $share->getShareType() === IShare::TYPE_LINK) {
134+
if ($this->config->getAppValue(AppConfig::WATERMARK_APP_NAMESPACE, 'watermark_linkAll', 'no') === 'yes') {
135+
return true;
136+
}
137+
if ($this->config->getAppValue(AppConfig::WATERMARK_APP_NAMESPACE, 'watermark_linkRead', 'no') === 'yes' && !$isUpdatable) {
138+
return true;
139+
}
140+
if ($this->config->getAppValue(AppConfig::WATERMARK_APP_NAMESPACE, 'watermark_linkSecure', 'no') === 'yes' && $isSecureView) {
141+
return true;
142+
}
143+
if ($this->config->getAppValue(AppConfig::WATERMARK_APP_NAMESPACE, 'watermark_linkTags', 'no') === 'yes') {
144+
$tags = $this->appConfig->getAppValueArray('watermark_linkTagsList');
145+
$fileTags = $this->systemTagObjectMapper->getTagIdsForObjects([$fileId], 'files')[$fileId];
146+
foreach ($fileTags as $tagId) {
147+
if (in_array($tagId, $tags, true)) {
148+
return true;
149+
}
150+
}
151+
}
152+
}
153+
154+
if ($this->config->getAppValue(AppConfig::WATERMARK_APP_NAMESPACE, 'watermark_shareAll', 'no') === 'yes') {
155+
if ($node->getOwner()->getUID() !== $userId) {
156+
return true;
157+
}
158+
}
159+
160+
if ($this->config->getAppValue(AppConfig::WATERMARK_APP_NAMESPACE, 'watermark_shareRead', 'no') === 'yes' && !$isUpdatable) {
161+
return true;
162+
}
163+
164+
if ($this->config->getAppValue(AppConfig::WATERMARK_APP_NAMESPACE, 'watermark_shareDisabledDownload', 'no') === 'yes' && $isDisabledDownload) {
165+
return true;
166+
}
167+
168+
if ($userId !== null && $this->config->getAppValue(AppConfig::WATERMARK_APP_NAMESPACE, 'watermark_allGroups', 'no') === 'yes') {
169+
$groups = $this->appConfig->getAppValueArray('watermark_allGroupsList');
170+
foreach ($groups as $group) {
171+
if ($this->groupManager->isInGroup($userId, $group)) {
172+
return true;
173+
}
174+
}
175+
}
176+
if ($this->config->getAppValue(AppConfig::WATERMARK_APP_NAMESPACE, 'watermark_allTags', 'no') === 'yes') {
177+
$tags = $this->appConfig->getAppValueArray('watermark_allTagsList');
178+
$fileTags = $this->systemTagObjectMapper->getTagIdsForObjects([$fileId], 'files')[$fileId];
179+
foreach ($fileTags as $tagId) {
180+
if (in_array($tagId, $tags, true)) {
181+
return true;
182+
}
183+
}
184+
}
185+
186+
return false;
187+
}
104188
}

0 commit comments

Comments
 (0)