Skip to content

Commit ac39a0f

Browse files
authored
Merge pull request #4772 from nextcloud/feat/secure-view-talk
feat: Add option to set secure view for public talk shares
2 parents f50d1f0 + 37b3729 commit ac39a0f

19 files changed

+547
-89
lines changed

.github/workflows/cypress-e2e.yml

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,13 +58,25 @@ jobs:
5858
git submodule sync --recursive
5959
git -c "http.extraheader=$auth_header" -c protocol.version=2 submodule update --init --force --recursive --depth=1
6060
61+
- name: Register main git reference
62+
run: |
63+
main_app_ref="$(if [ "${{ matrix.server-versions }}" = "master" ]; then echo -n "main"; else echo -n "${{ matrix.server-versions }}"; fi)"
64+
echo "main_app_ref=$main_app_ref" >> $GITHUB_ENV
65+
6166
- name: Checkout viewer
6267
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
6368
with:
6469
repository: nextcloud/viewer
6570
ref: ${{ matrix.server-versions }}
6671
path: apps/viewer
6772

73+
- name: Checkout spreed
74+
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
75+
with:
76+
repository: nextcloud/spreed
77+
ref: ${{ env.main_app_ref }}
78+
path: apps/spreed
79+
6880
- name: Checkout files_pdfviewer
6981
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
7082
with:
@@ -106,6 +118,13 @@ jobs:
106118
run: |
107119
composer install
108120
121+
- name: Build talk
122+
working-directory: apps/spreed
123+
run: |
124+
composer install --no-dev
125+
npm ci
126+
npm run dev
127+
109128
- name: Set up Nextcloud
110129
env:
111130
DB_PORT: 4444
@@ -125,6 +144,7 @@ jobs:
125144
php occ app:enable --force viewer
126145
php occ app:enable --force files_pdfviewer
127146
php occ app:enable --force richdocuments
147+
php occ app:enable --force spreed
128148
php occ app:list
129149
php occ config:system:set trusted_domains 1 --value="172.17.0.1"
130150

appinfo/info.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ You can also edit your documents off-line with the Collabora Office app from the
1515
<licence>agpl</licence>
1616
<author>Collabora Productivity based on work of Frank Karlitschek, Victor Dubiniuk</author>
1717
<types>
18+
<filesystem />
1819
<prevent_group_restriction/>
1920
</types>
2021
<documentation>

cypress/e2e/integration.spec.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -180,4 +180,4 @@ describe('Nextcloud integration', function() {
180180
})
181181
})
182182
})
183-
})
183+
})

cypress/e2e/talk.spec.js

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
/**
2+
* SPDX-FileCopyrightText: 2023 Julius Härtl <[email protected]>
3+
* SPDX-License-Identifier: AGPL-3.0-or-later
4+
*/
5+
6+
describe('Talk integraiton integration', function() {
7+
let randUser
8+
9+
const resetConfig = () => {
10+
cy.nextcloudTestingAppConfigSet('files', 'watermark_enabled', 'no')
11+
cy.nextcloudTestingAppConfigSet('files', 'watermark_text', '{userId}')
12+
cy.nextcloudTestingAppConfigSet('files', 'watermark_shareTalkPublic', 'no')
13+
cy.nextcloudTestingAppConfigSet('richdocuments', 'uiDefaults-UIMode', 'notebookbar')
14+
}
15+
16+
before(function() {
17+
resetConfig()
18+
cy.createRandomUser().then(user => {
19+
randUser = user
20+
cy.login(user)
21+
cy.uploadFile(user, 'document.odt', 'application/vnd.oasis.opendocument.text', '/document.odt')
22+
})
23+
})
24+
25+
afterEach(() => {
26+
resetConfig()
27+
})
28+
29+
const filename = 'document.odt'
30+
31+
beforeEach(function() {
32+
cy.login(randUser)
33+
})
34+
35+
it('Can share a file to a talk room and open it', function() {
36+
cy.createTalkRoom(randUser, {
37+
roomName: 'Test room',
38+
}).then(room => {
39+
cy.log(`Created talk room "${room.name}"`, room)
40+
cy.shareFileToTalkRoom(randUser, filename, room.token)
41+
cy.visit(`/call/${room.token}`)
42+
cy.get('.file-preview')
43+
.should('be.visible')
44+
.should('contain.text', filename)
45+
.click()
46+
47+
cy.waitForViewer()
48+
cy.waitForCollabora()
49+
})
50+
})
51+
52+
it('See that the file is shared without download', function() {
53+
cy.nextcloudTestingAppConfigSet('files', 'watermark_enabled', 'yes')
54+
cy.nextcloudTestingAppConfigSet('files', 'watermark_shareTalkPublic', 'yes')
55+
cy.nextcloudTestingAppConfigSet('files', 'watermark_text', 'TestingWatermark')
56+
57+
cy.createTalkRoom(randUser, {
58+
roomName: 'Secure room',
59+
}).then(room => {
60+
cy.log(`Created talk room "${room.name}"`, room)
61+
cy.shareFileToTalkRoom(randUser, filename, room.token, { permission: 1 })
62+
cy.makeTalkRoomPublic(randUser, room.token)
63+
64+
cy.logout()
65+
cy.clearAllLocalStorage()
66+
cy.visit(`/call/${room.token}`)
67+
cy.get('.username-form__input input[type="text"]')
68+
.should('be.visible')
69+
.type('Test user{enter}')
70+
71+
// Assert that the download button is hidden in talk
72+
cy.get('.messages:contains("document.odt")')
73+
.trigger('mouseover')
74+
75+
cy.get('.file-preview')
76+
.closest('.message')
77+
.find('button[aria-label="Actions"]')
78+
.should('be.visible')
79+
.click()
80+
81+
cy.get('.action:contains("Download")')
82+
.should('not.exist')
83+
84+
// Assert the file is still opening
85+
cy.get('.file-preview')
86+
.should('be.visible')
87+
.should('contain.text', filename)
88+
// We need to get the href to work around how cypress works with windows
89+
.invoke('attr', 'href')
90+
.then((href) => {
91+
cy.visit(href)
92+
93+
cy.get('[data-cy="guestNameModal"]').should('be.visible')
94+
cy.inputCollaboraGuestName('A guest')
95+
96+
cy.waitForCollabora()
97+
98+
cy.url().then(url => {
99+
const baseUrl = url.split('?')[0]
100+
cy.request({
101+
url: baseUrl + '/download',
102+
failOnStatusCode: false,
103+
}).then((response) => {
104+
expect(response.status).to.eq(403)
105+
})
106+
})
107+
})
108+
})
109+
})
110+
})

cypress/support/commands.js

Lines changed: 58 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ Cypress.Commands.add('uploadFile', (user, fixture, mimeType, target = `/${fixtur
8282
})
8383

8484
Cypress.Commands.add('ocsRequest', (user, options) => {
85-
const auth = { user: user.userId, password: user.password }
85+
const auth = user ? { user: user.userId, password: user.password } : null
8686
return cy.request({
8787
form: true,
8888
auth,
@@ -109,6 +109,22 @@ Cypress.Commands.add('shareFileToUser', (user, path, targetUser, shareData = {})
109109
})
110110
})
111111

112+
Cypress.Commands.add('shareFileToTalkRoom', (user, path, roomId, shareData = {}) => {
113+
cy.login(user)
114+
cy.ocsRequest(user, {
115+
method: 'POST',
116+
url: `${url}/ocs/v2.php/apps/files_sharing/api/v1/shares?format=json`,
117+
body: {
118+
path,
119+
shareType: 10,
120+
shareWith: roomId,
121+
...shareData,
122+
},
123+
}).then(response => {
124+
cy.log(`${user.userId} shared ${path} with talk room ${roomId}`, response.status)
125+
})
126+
})
127+
112128
Cypress.Commands.add('shareFileToRemoteUser', (user, path, targetUser, shareData = {}) => {
113129
cy.login(user)
114130
const federatedId = `${targetUser.userId}@${url}`
@@ -396,10 +412,45 @@ Cypress.Commands.add('verifyTemplateFields', (fields, fileId) => {
396412
})
397413

398414
Cypress.Commands.add('pickFile', (filename) => {
399-
cy.get('.office-target-picker')
400-
.find(`tr[data-filename="${filename}"]`)
401-
.click()
402-
cy.get('.office-target-picker')
403-
.find('button[aria-label="Select file"]')
404-
.click()
415+
cy.get('.office-target-picker')
416+
.find(`tr[data-filename="${filename}"]`)
417+
.click()
418+
cy.get('.office-target-picker')
419+
.find('button[aria-label="Select file"]')
420+
.click()
405421
})
422+
423+
Cypress.Commands.add('createTalkRoom', (user, options = {}) => {
424+
cy.login(user)
425+
return cy.ocsRequest(user, {
426+
method: 'POST',
427+
url: `${url}/ocs/v2.php/apps/spreed/api/v4/room?format=json`,
428+
body: {
429+
roomType: options.roomType || 3, // Default to group conversation
430+
roomName: options.roomName,
431+
invite: options.invite || '',
432+
source: options.source || '',
433+
objectType: options.objectType || '',
434+
objectId: options.objectId || '',
435+
password: options.password || '',
436+
}
437+
}).then(response => {
438+
cy.log(`Created talk room "${options.roomName}"`, response.status)
439+
return cy.wrap(response.body.ocs.data)
440+
})
441+
})
442+
443+
Cypress.Commands.add('makeTalkRoomPublic', (user, token, password = '') => {
444+
cy.login(user)
445+
return cy.ocsRequest(user, {
446+
method: 'POST',
447+
url: `${url}/ocs/v2.php/apps/spreed/api/v4/room/${token}/public?format=json`,
448+
body: {
449+
password: password,
450+
}
451+
}).then(response => {
452+
cy.log(`Made talk room public`, response.status)
453+
return cy.wrap(response.body.ocs.data)
454+
})
455+
})
456+

lib/AppConfig.php

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,13 @@ public function useSecureViewAdditionalMimes(): bool {
204204
return $this->config->getAppValue(Application::APPNAME, self::USE_SECURE_VIEW_ADDITIONAL_MIMES, 'no') === 'yes';
205205
}
206206

207+
public function getMimeTypes(): array {
208+
return array_merge(
209+
Capabilities::MIMETYPES,
210+
Capabilities::MIMETYPES_MSOFFICE,
211+
);
212+
}
213+
207214
public function getDomainList(): array {
208215
$urls = array_merge(
209216
[ $this->domainOnly($this->getCollaboraUrlPublic()) ],

lib/AppInfo/Application.php

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
namespace OCA\Richdocuments\AppInfo;
1010

1111
use OCA\Files_Sharing\Event\ShareLinkAccessedEvent;
12+
use OCA\Richdocuments\AppConfig;
1213
use OCA\Richdocuments\Capabilities;
1314
use OCA\Richdocuments\Conversion\ConversionProvider;
1415
use OCA\Richdocuments\Db\WopiMapper;
@@ -20,6 +21,7 @@
2021
use OCA\Richdocuments\Listener\FileCreatedFromTemplateListener;
2122
use OCA\Richdocuments\Listener\LoadAdditionalListener;
2223
use OCA\Richdocuments\Listener\LoadViewerListener;
24+
use OCA\Richdocuments\Listener\OverwritePublicSharePropertiesListener;
2325
use OCA\Richdocuments\Listener\ReferenceListener;
2426
use OCA\Richdocuments\Listener\RegisterTemplateFileCreatorListener;
2527
use OCA\Richdocuments\Listener\ShareLinkListener;
@@ -32,13 +34,15 @@
3234
use OCA\Richdocuments\Preview\OpenDocument;
3335
use OCA\Richdocuments\Preview\Pdf;
3436
use OCA\Richdocuments\Reference\OfficeTargetReferenceProvider;
37+
use OCA\Richdocuments\Storage\SecureViewWrapper;
3538
use OCA\Richdocuments\TaskProcessing\SlideDeckGenerationProvider;
3639
use OCA\Richdocuments\TaskProcessing\SlideDeckGenerationTaskType;
3740
use OCA\Richdocuments\TaskProcessing\TextToDocumentProvider;
3841
use OCA\Richdocuments\TaskProcessing\TextToDocumentTaskType;
3942
use OCA\Richdocuments\TaskProcessing\TextToSpreadsheetProvider;
4043
use OCA\Richdocuments\TaskProcessing\TextToSpreadsheetTaskType;
4144
use OCA\Richdocuments\Template\CollaboraTemplateProvider;
45+
use OCA\Talk\Events\OverwritePublicSharePropertiesEvent;
4246
use OCA\Viewer\Event\LoadViewer;
4347
use OCP\AppFramework\App;
4448
use OCP\AppFramework\Bootstrap\IBootContext;
@@ -47,12 +51,15 @@
4751
use OCP\AppFramework\Http\Events\BeforeTemplateRenderedEvent;
4852
use OCP\Collaboration\Reference\RenderReferenceEvent;
4953
use OCP\Collaboration\Resources\LoadAdditionalScriptsEvent;
54+
use OCP\Files\Storage\IStorage;
5055
use OCP\Files\Template\BeforeGetTemplatesEvent;
5156
use OCP\Files\Template\FileCreatedFromTemplateEvent;
5257
use OCP\Files\Template\RegisterTemplateCreatorEvent;
58+
use OCP\IAppConfig;
5359
use OCP\Preview\BeforePreviewFetchedEvent;
5460
use OCP\Security\CSP\AddContentSecurityPolicyEvent;
5561
use OCP\Security\FeaturePolicy\AddFeaturePolicyEvent;
62+
use OCP\Server;
5663

5764
class Application extends App implements IBootstrap {
5865
public const APPNAME = 'richdocuments';
@@ -62,6 +69,8 @@ public function __construct(array $urlParams = []) {
6269
}
6370

6471
public function register(IRegistrationContext $context): void {
72+
\OCP\Util::connectHook('OC_Filesystem', 'preSetup', $this, 'addStorageWrapper');
73+
6574
$context->registerTemplateProvider(CollaboraTemplateProvider::class);
6675
$context->registerCapability(Capabilities::class);
6776
$context->registerMiddleWare(WOPIMiddleware::class);
@@ -76,6 +85,7 @@ public function register(IRegistrationContext $context): void {
7685
$context->registerEventListener(RenderReferenceEvent::class, ReferenceListener::class);
7786
$context->registerEventListener(BeforeTemplateRenderedEvent::class, BeforeTemplateRenderedListener::class);
7887
$context->registerEventListener(BeforeGetTemplatesEvent::class, BeforeGetTemplatesListener::class);
88+
$context->registerEventListener(OverwritePublicSharePropertiesEvent::class, OverwritePublicSharePropertiesListener::class);
7989
$context->registerReferenceProvider(OfficeTargetReferenceProvider::class);
8090
$context->registerSensitiveMethods(WopiMapper::class, [
8191
'getPathForToken',
@@ -101,4 +111,32 @@ public function register(IRegistrationContext $context): void {
101111

102112
public function boot(IBootContext $context): void {
103113
}
114+
115+
/**
116+
* @internal
117+
*/
118+
public function addStorageWrapper(): void {
119+
if (Server::get(IAppConfig::class)->getValueString(AppConfig::WATERMARK_APP_NAMESPACE, 'watermark_enabled', 'no') === 'no') {
120+
return;
121+
}
122+
123+
\OC\Files\Filesystem::addStorageWrapper('richdocuments', [$this, 'addStorageWrapperCallback'], -10);
124+
}
125+
126+
/**
127+
* @param $mountPoint
128+
* @param IStorage $storage
129+
* @return SecureViewWrapper|IStorage
130+
*@internal
131+
*/
132+
public function addStorageWrapperCallback($mountPoint, IStorage $storage) {
133+
if (!\OC::$CLI && $mountPoint !== '/') {
134+
return new SecureViewWrapper([
135+
'storage' => $storage,
136+
'mountPoint' => $mountPoint,
137+
]);
138+
}
139+
140+
return $storage;
141+
}
104142
}

lib/Controller/SettingsController.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -213,6 +213,7 @@ public function updateWatermarkSettings($settings = []): JSONResponse {
213213
'watermark_shareAll',
214214
'watermark_shareRead',
215215
'watermark_shareDisabledDownload',
216+
'watermark_shareTalkPublic',
216217
'watermark_linkSecure',
217218
'watermark_linkRead',
218219
'watermark_linkAll',

0 commit comments

Comments
 (0)