Skip to content

Commit

Permalink
Merge pull request #3939 from nextcloud/backport/3669/stable29
Browse files Browse the repository at this point in the history
[stable29] fix: Bring back federated editing in viewer iframe
  • Loading branch information
juliushaertl committed Aug 21, 2024
2 parents 61528f2 + bc99a77 commit ba2f01f
Show file tree
Hide file tree
Showing 10 changed files with 153 additions and 15 deletions.
1 change: 1 addition & 0 deletions .github/workflows/cypress.yml
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,7 @@ jobs:
CYPRESS_RECORD_KEY: ${{ secrets.CYPRESS_RECORD_KEY }}
SPLIT: ${{ strategy.job-total }}
SPLIT_INDEX: ${{ strategy.job-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 @@ -30,6 +30,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 @@ -26,7 +26,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 @@ -103,4 +103,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)
})
})
})
})

})
6 changes: 3 additions & 3 deletions cypress/e2e/integration.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -76,8 +76,8 @@ 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('#saveas').click()
Expand All @@ -87,7 +87,7 @@ describe('Nextcloud integration', function() {
cy.get('.saveas-dialog').should('be.visible')
cy.get('.saveas-dialog input[type=text]')
.should('be.visible')
.should('have.value', '/document.rtf')
.should('have.value', `/${exportFilename}`)

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

Expand All @@ -99,7 +99,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.rtf')
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 @@ -124,6 +124,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 @@ -205,9 +243,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 @@ -222,7 +261,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 @@ -288,7 +288,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 @@ -300,6 +302,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 @@ -381,6 +393,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 @@ -214,6 +214,7 @@ public function getRemoteRedirectURL(File $item, ?Direct $direct = null, ?IShare
return null;
}


$remote = $item->getStorage()->getRemote();
$remoteCollabora = $this->getRemoteCollaboraURL($remote);
if ($remoteCollabora !== '') {
Expand All @@ -231,6 +232,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 @@ -30,18 +30,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 @@ -50,6 +44,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 @@ -313,6 +313,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

0 comments on commit ba2f01f

Please sign in to comment.