Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[stable30] fix: Bring back federated editing in viewer iframe #3940

Merged
merged 3 commits into from
Aug 21, 2024
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
1 change: 1 addition & 0 deletions .github/workflows/cypress-e2e.yml
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,7 @@ jobs:
CYPRESS_RECORD_KEY: ${{ secrets.CYPRESS_RECORD_KEY }}
SPLIT: 3
SPLIT_INDEX: ${{ env.container_index }}
CODE_RELEASE: ${{ matrix.code-image }}

- name: Upload test failure screenshots
uses: actions/upload-artifact@v2
Expand Down
1 change: 1 addition & 0 deletions appinfo/routes.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
// Page rendering of documents
['name' => 'document#index', 'url' => 'index', 'verb' => 'GET'],
['name' => 'document#remote', 'url' => 'remote', 'verb' => 'GET'],
['name' => 'document#remotePost', 'url' => 'remote', 'verb' => 'POST'],
['name' => 'document#createFromTemplate', 'url' => 'indexTemplate', 'verb' => 'GET'],
['name' => 'document#publicPage', 'url' => '/public', 'verb' => 'GET'],
['name' => 'document#token', 'url' => '/token', 'verb' => 'POST'],
Expand Down
17 changes: 16 additions & 1 deletion cypress/e2e/direct.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ const createDirectEditingLink = (user, fileId) => {
body: {
fileId,
},
auth: { user: user.userId, pass: user.password },
// auth: { user: user.userId, pass: user.password },
headers: {
'OCS-ApiRequest': 'true',
'Content-Type': 'application/x-www-form-urlencoded',
Expand Down Expand Up @@ -88,4 +88,19 @@ describe('Direct editing (legacy)', function() {
})
})

it('Open a remotely shared file', () => {
cy.createRandomUser().then(shareRecipient => {
cy.login(randUser)
cy.shareFileToRemoteUser(randUser, '/document.odt', shareRecipient)
.then(incomingFileId => {
createDirectEditingLink(shareRecipient, incomingFileId)
.then((token) => {
cy.logout()
cy.visit(token)
cy.waitForCollabora(false)
})
})
})
})

})
12 changes: 6 additions & 6 deletions cypress/e2e/integration.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
* SPDX-FileCopyrightText: 2023 Julius Härtl <[email protected]>
* SPDX-License-Identifier: AGPL-3.0-or-later
*/

describe('Nextcloud integration', function() {
let randUser

Expand Down Expand Up @@ -61,19 +62,18 @@ describe('Nextcloud integration', function() {
cy.get('#tab-version_vue .version__info__label').contains('Current version')
})

// Currently it seems that Collabora is missing the save as button
it('Save as', function() {
const exportFilename = 'document.rtf'
cy.get('@loleafletframe').within(() => {
cy.get('#File-tab-label').click()
cy.get('#file-saveas').click()
// FIXME: Seems currently broken, so let's skip this step
// cy.get('#saveas-entries #saveas-entry-1').click()
cy.get('#saveas').click()
cy.get('#saveas-entries #saveas-entry-1').click()
})

cy.get('.saveas-dialog').should('be.visible')
cy.get('.saveas-dialog input[type=text]')
.should('be.visible')
.should('have.value', '/document.odt')
.should('have.value', `/${exportFilename}`)

cy.get('.saveas-dialog button.button-vue--vue-primary').click()

Expand All @@ -85,7 +85,7 @@ describe('Nextcloud integration', function() {
// FIXME: We should not need to reload
cy.get('.breadcrumb__crumbs a').eq(0).click({ force: true })

cy.openFile('document.odt')
cy.openFile(exportFilename)
})

it('Open locally', function() {
Expand Down
40 changes: 40 additions & 0 deletions cypress/e2e/share-federated.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/**
* SPDX-FileCopyrightText: 2023 Julius Härtl <[email protected]>
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
import { User } from '@nextcloud/cypress'
import { randHash } from '../utils/index.js'
const shareOwner = new User(randHash(), randHash())
const shareRecipient = new User(randHash(), randHash())

describe('Federated sharing of office documents', function() {

before(function() {
cy.nextcloudEnableApp('testing')
cy.nextcloudTestingAppConfigSet('richdocuments', 'uiDefaults-UIMode', 'notebookbar')
cy.createUser(shareRecipient)
cy.createUser(shareOwner)

cy.uploadFile(shareOwner, 'document.odt', 'application/vnd.oasis.opendocument.text', '/document.odt')
})

it('Open a remotely shared file', function() {
const filename = 'document.odt'

cy.login(shareOwner)
cy.shareFileToRemoteUser(shareOwner, '/document.odt', shareRecipient)
cy.login(shareRecipient)

cy.visit('/apps/files', {
onBeforeLoad(win) {
cy.spy(win, 'postMessage').as('postMessage')
},
})
cy.openFile(filename)
cy.waitForViewer()
cy.waitForCollabora(true, true).within(() => {
cy.get('#closebutton').click()
cy.get('#viewer', { timeout: 5000 }).should('not.exist')
})
})
})
51 changes: 48 additions & 3 deletions cypress/support/commands.js
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,44 @@ Cypress.Commands.add('shareFileToUser', (user, path, targetUser, shareData = {})
})
})

Cypress.Commands.add('shareFileToRemoteUser', (user, path, targetUser, shareData = {}) => {
cy.login(user)
const federatedId = `${targetUser.userId}@${url}`
return cy.ocsRequest(user, {
method: 'POST',
url: `${url}/ocs/v2.php/apps/files_sharing/api/v1/shares?format=json`,
body: {
path,
shareType: 6,
shareWith: federatedId,
...shareData,
},
}).then(response => {
cy.log(`${user.userId} shared ${path} with ${federatedId}`, response.status)
cy.login(targetUser)
return cy.ocsRequest(targetUser, {
method: 'GET',
url: `${url}/ocs/v2.php/apps/files_sharing/api/v1/remote_shares/pending?format=json`,
})
}).then(({ body }) => {
for (const index in body.ocs.data) {
cy.ocsRequest(targetUser, {
method: 'POST',
url: `${url}/ocs/v2.php/apps/files_sharing/api/v1/remote_shares/pending/${body.ocs.data[index].id}`,
})
return cy.wrap(body.ocs.data[index].id)
}
}).then((shareId) => {
cy.ocsRequest(targetUser, {
method: 'GET',
url: `${url}/ocs/v2.php/apps/files_sharing/api/v1/remote_shares/${shareId}?format=json`,
}).then((response) => {
cy.login(user)
return cy.wrap(response.body.ocs.data['file_id'])
})
})
})

Cypress.Commands.add('shareLink', (user, path, shareData = {}) => {
cy.login(user)
cy.ocsRequest(user, {
Expand Down Expand Up @@ -190,9 +228,10 @@ Cypress.Commands.add('waitForViewer', () => {
.and('not.have.class', 'icon-loading')
})

Cypress.Commands.add('waitForCollabora', (wrapped = false) => {
Cypress.Commands.add('waitForCollabora', (wrapped = false, federated = false) => {
const wrappedFrameIdentifier = federated ? 'coolframe' : 'documentframe'
if (wrapped) {
cy.get('[data-cy="documentframe"]', { timeout: 30000 })
cy.get(`[data-cy="${wrappedFrameIdentifier}"]`, { timeout: 30000 })
.its('0.contentDocument')
.its('body').should('not.be.empty')
.should('be.visible').should('not.be.empty')
Expand All @@ -207,7 +246,13 @@ Cypress.Commands.add('waitForCollabora', (wrapped = false) => {
.its('0.contentDocument')
.its('body').should('not.be.empty')
.as('loleafletframe')
cy.get('@loleafletframe').find('#main-document-content').should('be.visible')

cy.get('@loleafletframe')
.within(() => {
cy.get('#main-document-content').should('be.visible')
})

return cy.get('@loleafletframe')
})

Cypress.Commands.add('waitForPostMessage', (messageId, values = undefined) => {
Expand Down
21 changes: 20 additions & 1 deletion lib/Controller/DocumentController.php
Original file line number Diff line number Diff line change
Expand Up @@ -282,7 +282,9 @@ public function remote(string $shareToken, string $remoteServer, string $remoteS
'userId' => $remoteWopi->getEditorUid() ? ($remoteWopi->getEditorUid() . '@' . $remoteServer) : null,
];

return $this->documentTemplateResponse($wopi, $params);
$response = $this->documentTemplateResponse($wopi, $params);
$response->addHeader('X-Frame-Options', 'ALLOW');
return $response;
}
} catch (ShareNotFound $e) {
return new TemplateResponse('core', '404', [], 'guest');
Expand All @@ -294,6 +296,16 @@ public function remote(string $shareToken, string $remoteServer, string $remoteS
return new TemplateResponse('core', '403', [], 'guest');
}

/**
* Open file on Source instance with token from Initiator instance
*/
#[PublicPage]
#[NoCSRFRequired]
public function remotePost(string $shareToken, string $remoteServer, string $remoteServerToken, ?string $filePath = null): TemplateResponse {
return $this->remote($shareToken, $remoteServer, $remoteServerToken, $filePath);
}


private function renderErrorPage(string $message, int $status = Http::STATUS_INTERNAL_SERVER_ERROR): TemplateResponse {
$params = [
'errors' => [['error' => $message]]
Expand Down Expand Up @@ -375,6 +387,13 @@ public function token(int $fileId, ?string $shareToken = null, ?string $path = n
$share = $shareToken ? $this->shareManager->getShareByToken($shareToken) : null;
$file = $shareToken ? $this->getFileForShare($share, $fileId, $path) : $this->getFileForUser($fileId, $path);

$federatedUrl = $this->federationService->getRemoteRedirectURL($file);
if ($federatedUrl) {
return new DataResponse([
'federatedUrl' => $federatedUrl,
]);
}

$wopi = $this->getToken($file, $share);

$this->tokenManager->setGuestName($wopi, $guestName);
Expand Down
2 changes: 2 additions & 0 deletions lib/Service/FederationService.php
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,7 @@ public function getRemoteRedirectURL(File $item, ?Direct $direct = null, ?IShare
return null;
}


$remote = $item->getStorage()->getRemote();
$remoteCollabora = $this->getRemoteCollaboraURL($remote);
if ($remoteCollabora !== '') {
Expand All @@ -214,6 +215,7 @@ public function getRemoteRedirectURL(File $item, ?Direct $direct = null, ?IShare
$this->tokenManager->extendWithInitiatorUserToken($wopi, $direct->getInitiatorHost(), $direct->getInitiatorToken());
}


$url = rtrim($remote, '/') . '/index.php/apps/richdocuments/remote?shareToken=' . $item->getStorage()->getToken() .
'&remoteServer=' . $initiatorServer .
'&remoteServerToken=' . $initiatorToken;
Expand Down
21 changes: 14 additions & 7 deletions src/helpers/coolParameters.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,18 +13,12 @@ const getUIDefaults = () => {
const saveAsMode = 'group'
const uiMode = defaults.UIMode ?? 'notebookbar'

const systemDarkMode = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches
const dataset = (document.body.dataset.themes ? document.body.dataset : parent?.document.body.dataset) ?? {}
const nextcloudDarkMode = dataset?.themeDark === '' || dataset?.themeDarkHighcontrast === ''
const matchedDarkMode = (!dataset?.themes || dataset?.themes === '' || dataset?.themeDefault === '') ? systemDarkMode : nextcloudDarkMode
const uiTheme = matchedDarkMode ? 'dark' : 'light'

let uiDefaults = 'TextRuler=' + textRuler + ';'
uiDefaults += 'TextSidebar=' + sidebar + ';TextStatusbar=' + statusBar + ';'
uiDefaults += 'PresentationSidebar=' + sidebar + ';PresentationStatusbar=' + statusBar + ';'
uiDefaults += 'SpreadsheetSidebar=' + sidebar + ';SpreadsheetStatusbar=true;'
uiDefaults += 'UIMode=' + uiMode + ';'
uiDefaults += 'UITheme=' + uiTheme + ';'
uiDefaults += 'UITheme=' + getUITheme() + ';'
uiDefaults += 'SaveAsMode=' + saveAsMode + ';'
return uiDefaults
}
Expand All @@ -33,6 +27,19 @@ const getCollaboraTheme = () => {
return loadState('richdocuments', 'theme', 'nextcloud')
}

const getUITheme = () => {
const systemDarkMode = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches
let dataset = {}
try {
dataset = (document.body.dataset.themes ? document.body.dataset : parent?.document.body.dataset) ?? {}
} catch (e) {
// Ignore errors here in case we run into cross-origin domains
}
const nextcloudDarkMode = dataset?.themeDark === '' || dataset?.themeDarkHighcontrast === ''
const matchedDarkMode = (!dataset?.themes || dataset?.themes === '' || dataset?.themeDefault === '') ? systemDarkMode : nextcloudDarkMode
return matchedDarkMode ? 'dark' : 'light'
}

const createDataThemeDiv = (elementType, theme) => {
const element = document.createElement(elementType)
element.setAttribute('id', 'cool-var-source-' + theme)
Expand Down
8 changes: 8 additions & 0 deletions src/view/Office.vue
Original file line number Diff line number Diff line change
Expand Up @@ -306,6 +306,14 @@ export default {
const { data } = await axios.post(generateUrl('/apps/richdocuments/token'), {
fileId: fileid, shareToken: this.shareToken, version, guestName: getGuestNickname(),
})

if (data.federatedUrl) {
this.$set(this.formData, 'action', data.federatedUrl)
this.$nextTick(() => this.$refs.form.submit())
this.loading = LOADING_STATE.DOCUMENT_READY
return
}

Config.update('urlsrc', data.urlSrc)
Config.update('wopi_callback_url', loadState('richdocuments', 'wopi_callback_url', ''))

Expand Down
Loading