|
|
@@ -1,518 +0,0 @@
|
|
|
-import { v4 as uuid } from 'uuid'
|
|
|
-import {
|
|
|
- isExcludedBySharding,
|
|
|
- startWith,
|
|
|
- reloadWith,
|
|
|
- STARTUP_TIMEOUT,
|
|
|
-} from './helpers/config'
|
|
|
-import { ensureUserExists, login } from './helpers/login'
|
|
|
-import {
|
|
|
- createProject,
|
|
|
- enableLinkSharing,
|
|
|
- getSpamSafeProjectName,
|
|
|
- openProjectByName,
|
|
|
- openProjectViaLinkSharingAsAnon,
|
|
|
- openProjectViaLinkSharingAsUser,
|
|
|
- shareProjectByEmailAndAcceptInviteViaDash,
|
|
|
- shareProjectByEmailAndAcceptInviteViaEmail,
|
|
|
-} from './helpers/project'
|
|
|
-import { prepareWaitForNextCompileSlot } from './helpers/compile'
|
|
|
-import { beforeWithReRunOnTestRetry } from './helpers/beforeWithReRunOnTestRetry'
|
|
|
-
|
|
|
-describe('Project Sharing', function () {
|
|
|
- if (isExcludedBySharding('PRO_CUSTOM_4')) return
|
|
|
- ensureUserExists({ email: 'user@example.com' })
|
|
|
- startWith({ withDataDir: true, pro: true })
|
|
|
-
|
|
|
- let projectName: string
|
|
|
- let recompile: () => void
|
|
|
- let waitForCompile: (triggerCompile: () => void) => void
|
|
|
- beforeWithReRunOnTestRetry(() => {
|
|
|
- projectName = getSpamSafeProjectName()
|
|
|
- ;({ recompile, waitForCompile } = prepareWaitForNextCompileSlot())
|
|
|
- setupTestProject()
|
|
|
- })
|
|
|
-
|
|
|
- beforeEach(function () {
|
|
|
- // Always start with a fresh session
|
|
|
- cy.session([uuid()], () => {})
|
|
|
- })
|
|
|
-
|
|
|
- let linkSharingReadOnly: string
|
|
|
- let linkSharingReadAndWrite: string
|
|
|
-
|
|
|
- function setupTestProject() {
|
|
|
- login('user@example.com')
|
|
|
- waitForCompile(() => {
|
|
|
- createProject(projectName)
|
|
|
- })
|
|
|
-
|
|
|
- // Add chat message
|
|
|
- cy.findByRole('button', { name: 'Chat' }).click()
|
|
|
- // wait for lazy loading of the chat pane
|
|
|
- cy.findByRole('complementary', { name: 'Chat' }).findByText(
|
|
|
- 'Send your first message to your collaborators'
|
|
|
- )
|
|
|
- cy.findByLabelText('Send a message to your collaborators…').type(
|
|
|
- 'New Chat Message{enter}'
|
|
|
- )
|
|
|
-
|
|
|
- // Get link sharing links
|
|
|
- enableLinkSharing().then(
|
|
|
- ({ linkSharingReadOnly: ro, linkSharingReadAndWrite: rw }) => {
|
|
|
- linkSharingReadAndWrite = rw
|
|
|
- linkSharingReadOnly = ro
|
|
|
- }
|
|
|
- )
|
|
|
- }
|
|
|
-
|
|
|
- function expectContentReadOnlyAccess() {
|
|
|
- cy.url().should('match', /\/project\/[a-fA-F0-9]{24}/)
|
|
|
- cy.findByRole('textbox', { name: 'Source Editor editing' }).should(
|
|
|
- 'contain.text',
|
|
|
- '\\maketitle'
|
|
|
- )
|
|
|
- cy.findByRole('textbox', { name: 'Source Editor editing' }).should(
|
|
|
- 'have.attr',
|
|
|
- 'contenteditable',
|
|
|
- 'false'
|
|
|
- )
|
|
|
- }
|
|
|
-
|
|
|
- function expectContentWriteAccess() {
|
|
|
- const section = `Test Section ${uuid()}`
|
|
|
- cy.url().should('match', /\/project\/[a-fA-F0-9]{24}/)
|
|
|
- // wait for the editor to finish loading
|
|
|
- cy.findByRole('textbox', { name: 'Source Editor editing' }).should(
|
|
|
- 'contain.text',
|
|
|
- '\\maketitle'
|
|
|
- )
|
|
|
- // the editor should be writable
|
|
|
- cy.findByRole('textbox', { name: 'Source Editor editing' }).should(
|
|
|
- 'have.attr',
|
|
|
- 'contenteditable',
|
|
|
- 'true'
|
|
|
- )
|
|
|
- cy.findByRole('textbox', { name: 'Source Editor editing' }).within(() => {
|
|
|
- cy.findByText('\\maketitle').parent().click()
|
|
|
- cy.findByText('\\maketitle').parent().type(`\n\\section{{}${section}}`)
|
|
|
- })
|
|
|
- // should have written
|
|
|
- cy.findByRole('textbox', { name: 'Source Editor editing' }).should(
|
|
|
- 'contain.text',
|
|
|
- `\\section{${section}}`
|
|
|
- )
|
|
|
- // check PDF
|
|
|
- recompile()
|
|
|
- cy.findByRole('region', { name: 'PDF preview and logs' }).within(() => {
|
|
|
- cy.findByLabelText(/Page.*1/i).should('be.visible')
|
|
|
- cy.findByText(projectName).should('be.visible')
|
|
|
- })
|
|
|
- cy.findByRole('region', { name: 'PDF preview and logs' }).within(() => {
|
|
|
- cy.findByLabelText(/Page.*1/i).should('be.visible')
|
|
|
- cy.contains(section)
|
|
|
- })
|
|
|
- }
|
|
|
-
|
|
|
- function expectNoAccess() {
|
|
|
- // try read only access link
|
|
|
- cy.visit(linkSharingReadOnly)
|
|
|
- cy.url().should('match', /\/login/)
|
|
|
-
|
|
|
- // Cypress bugs: cypress resolves the link-sharing link outside the browser, and it carries over the hash of the link-sharing link to the login page redirect (bug 1).
|
|
|
- // Effectively, cypress then instructs the browser to change the page from /login#read-only-hash to /login#read-and-write-hash.
|
|
|
- // This is turn does not trigger a "page load", but rather just "scrolling", which in turn trips up the "page loaded" detection in cypress (bug 2).
|
|
|
- // Work around this by navigating away from the /login page in between checks.
|
|
|
- cy.visit('/user/password/reset')
|
|
|
-
|
|
|
- // try read and write access link
|
|
|
- cy.visit(linkSharingReadAndWrite)
|
|
|
- cy.url().should('match', /\/login/)
|
|
|
- }
|
|
|
-
|
|
|
- function expectChatAccess() {
|
|
|
- cy.findByRole('button', { name: 'Chat' }).click()
|
|
|
- cy.findByRole('complementary', { name: 'Chat' }).findByText(
|
|
|
- 'New Chat Message'
|
|
|
- )
|
|
|
- }
|
|
|
-
|
|
|
- function expectHistoryAccess() {
|
|
|
- cy.findByRole('button', { name: 'History' }).click()
|
|
|
- // The input is not clickable due to being visually hidden, click its label instead
|
|
|
- cy.findByRole('complementary', {
|
|
|
- name: 'Project history and labels',
|
|
|
- }).within(() => {
|
|
|
- cy.findByRole('group', {
|
|
|
- name: 'Show all of the project history or only labelled versions.',
|
|
|
- }).within(() => {
|
|
|
- cy.findByText('All history').click()
|
|
|
- })
|
|
|
- cy.findByRole('radio', { name: 'Labels' }).should('not.be.checked')
|
|
|
- cy.findByRole('radio', { name: 'All history' }).should('be.checked')
|
|
|
- })
|
|
|
- cy.findByText(/\\begin\{document}/)
|
|
|
- cy.findByRole('complementary', {
|
|
|
- name: 'Project history and labels',
|
|
|
- }).within(() => {
|
|
|
- cy.findAllByTestId('history-version-metadata-users')
|
|
|
- .last()
|
|
|
- .should('have.text', 'user')
|
|
|
- })
|
|
|
- cy.findByRole('button', { name: 'Back to editor' }).click()
|
|
|
- }
|
|
|
-
|
|
|
- function expectNoChatAccess() {
|
|
|
- cy.findByRole('button', { name: 'Layout' }) // wait for lazy loading
|
|
|
- cy.findByRole('button', { name: 'Chat' }).should('not.exist')
|
|
|
- }
|
|
|
-
|
|
|
- function expectNoHistoryAccess() {
|
|
|
- cy.findByRole('button', { name: 'Layout' }) // wait for lazy loading
|
|
|
- cy.findByRole('button', { name: 'History' }).should('not.exist')
|
|
|
- }
|
|
|
-
|
|
|
- function expectCommentAccess() {
|
|
|
- cy.findByRole('textbox', { name: 'Source Editor editing' }).should(
|
|
|
- 'contain.text',
|
|
|
- '\\maketitle'
|
|
|
- )
|
|
|
-
|
|
|
- cy.findByText('\\maketitle').parent().dblclick()
|
|
|
-
|
|
|
- cy.findByRole('button', { name: 'Add comment' }).should('be.visible')
|
|
|
-
|
|
|
- cy.findByRole('textbox', { name: 'Source Editor editing' }).click()
|
|
|
- }
|
|
|
-
|
|
|
- function expectNoCommentAccess() {
|
|
|
- cy.findByRole('textbox', { name: 'Source Editor editing' }).should(
|
|
|
- 'contain.text',
|
|
|
- '\\maketitle'
|
|
|
- )
|
|
|
-
|
|
|
- cy.findByText('\\maketitle').parent().dblclick()
|
|
|
-
|
|
|
- cy.findByRole('button', { name: 'Add comment' }).should('not.exist')
|
|
|
- cy.findByRole('textbox', { name: 'Source Editor editing' }).click()
|
|
|
- }
|
|
|
-
|
|
|
- function expectFullReadOnlyAccess() {
|
|
|
- expectContentReadOnlyAccess()
|
|
|
- expectChatAccess()
|
|
|
- expectHistoryAccess()
|
|
|
- expectNoCommentAccess()
|
|
|
- }
|
|
|
-
|
|
|
- function expectRestrictedReadOnlyAccess() {
|
|
|
- expectContentReadOnlyAccess()
|
|
|
- expectNoChatAccess()
|
|
|
- expectNoHistoryAccess()
|
|
|
- expectNoCommentAccess()
|
|
|
- }
|
|
|
-
|
|
|
- function expectFullReadAndWriteAccess() {
|
|
|
- expectContentWriteAccess()
|
|
|
- expectChatAccess()
|
|
|
- expectHistoryAccess()
|
|
|
- expectCommentAccess()
|
|
|
- }
|
|
|
-
|
|
|
- function expectAnonymousReadAndWriteAccess() {
|
|
|
- expectContentWriteAccess()
|
|
|
- expectChatAccess()
|
|
|
- expectHistoryAccess()
|
|
|
- expectNoCommentAccess()
|
|
|
- }
|
|
|
-
|
|
|
- function expectProjectDashboardEntry() {
|
|
|
- cy.visit('/project')
|
|
|
- cy.findByText(projectName)
|
|
|
- }
|
|
|
-
|
|
|
- function expectEditAuthoredAs(author: string) {
|
|
|
- cy.findByRole('button', { name: 'History' }).click()
|
|
|
- cy.findByRole('complementary', {
|
|
|
- name: 'Project history and labels',
|
|
|
- }).within(() => {
|
|
|
- cy.findAllByTestId('history-version-metadata-users')
|
|
|
- .first()
|
|
|
- .should('contain.text', author) // might have other edits in the same group
|
|
|
- })
|
|
|
- }
|
|
|
- describe('via email', function () {
|
|
|
- const email = 'collaborator-email@example.com'
|
|
|
- ensureUserExists({ email })
|
|
|
-
|
|
|
- beforeEach(function () {
|
|
|
- login('user@example.com')
|
|
|
- shareProjectByEmailAndAcceptInviteViaEmail(projectName, email, 'Viewer')
|
|
|
- })
|
|
|
-
|
|
|
- it('should grant the collaborator read access', function () {
|
|
|
- expectFullReadOnlyAccess()
|
|
|
- expectProjectDashboardEntry()
|
|
|
- })
|
|
|
- })
|
|
|
-
|
|
|
- describe('read only', function () {
|
|
|
- const email = 'collaborator-ro@example.com'
|
|
|
- ensureUserExists({ email })
|
|
|
-
|
|
|
- beforeWithReRunOnTestRetry(() => {
|
|
|
- login('user@example.com')
|
|
|
- shareProjectByEmailAndAcceptInviteViaDash(projectName, email, 'Viewer')
|
|
|
- })
|
|
|
-
|
|
|
- it('should grant the collaborator read access', function () {
|
|
|
- login(email)
|
|
|
- openProjectByName(projectName)
|
|
|
- expectFullReadOnlyAccess()
|
|
|
- expectProjectDashboardEntry()
|
|
|
- })
|
|
|
- })
|
|
|
-
|
|
|
- describe('read and write', function () {
|
|
|
- const email = 'collaborator-rw@example.com'
|
|
|
- ensureUserExists({ email })
|
|
|
-
|
|
|
- beforeWithReRunOnTestRetry(() => {
|
|
|
- login('user@example.com')
|
|
|
- shareProjectByEmailAndAcceptInviteViaDash(projectName, email, 'Editor')
|
|
|
- })
|
|
|
-
|
|
|
- it('should grant the collaborator write access', function () {
|
|
|
- login(email)
|
|
|
- openProjectByName(projectName)
|
|
|
- expectFullReadAndWriteAccess()
|
|
|
- expectEditAuthoredAs('You')
|
|
|
- expectProjectDashboardEntry()
|
|
|
- })
|
|
|
- })
|
|
|
-
|
|
|
- describe('token access', function () {
|
|
|
- describe('logged in', function () {
|
|
|
- describe('read only', function () {
|
|
|
- const email = 'collaborator-link-ro@example.com'
|
|
|
- ensureUserExists({ email })
|
|
|
-
|
|
|
- it('should grant restricted read access', function () {
|
|
|
- login(email)
|
|
|
- openProjectViaLinkSharingAsUser(
|
|
|
- linkSharingReadOnly,
|
|
|
- projectName,
|
|
|
- email
|
|
|
- )
|
|
|
- expectRestrictedReadOnlyAccess()
|
|
|
- expectProjectDashboardEntry()
|
|
|
- })
|
|
|
- })
|
|
|
-
|
|
|
- describe('read and write', function () {
|
|
|
- const email = 'collaborator-link-rw@example.com'
|
|
|
- ensureUserExists({ email })
|
|
|
-
|
|
|
- it('should grant full write access', function () {
|
|
|
- login(email)
|
|
|
- openProjectViaLinkSharingAsUser(
|
|
|
- linkSharingReadAndWrite,
|
|
|
- projectName,
|
|
|
- email
|
|
|
- )
|
|
|
- expectFullReadAndWriteAccess()
|
|
|
- expectEditAuthoredAs('You')
|
|
|
- expectProjectDashboardEntry()
|
|
|
- })
|
|
|
- })
|
|
|
- })
|
|
|
-
|
|
|
- describe('with OVERLEAF_ALLOW_PUBLIC_ACCESS=false', function () {
|
|
|
- describe('wrap startup', function () {
|
|
|
- startWith({
|
|
|
- pro: true,
|
|
|
- vars: {
|
|
|
- OVERLEAF_ALLOW_PUBLIC_ACCESS: 'false',
|
|
|
- },
|
|
|
- withDataDir: true,
|
|
|
- })
|
|
|
- it('should block access', function () {
|
|
|
- expectNoAccess()
|
|
|
- })
|
|
|
- })
|
|
|
-
|
|
|
- describe('with OVERLEAF_ALLOW_ANONYMOUS_READ_AND_WRITE_SHARING=true', function () {
|
|
|
- startWith({
|
|
|
- pro: true,
|
|
|
- vars: {
|
|
|
- OVERLEAF_ALLOW_PUBLIC_ACCESS: 'false',
|
|
|
- OVERLEAF_ALLOW_ANONYMOUS_READ_AND_WRITE_SHARING: 'true',
|
|
|
- },
|
|
|
- withDataDir: true,
|
|
|
- })
|
|
|
- it('should block access', function () {
|
|
|
- expectNoAccess()
|
|
|
- })
|
|
|
- })
|
|
|
- })
|
|
|
-
|
|
|
- describe('with OVERLEAF_ALLOW_PUBLIC_ACCESS=true', function () {
|
|
|
- describe('wrap startup', function () {
|
|
|
- startWith({
|
|
|
- pro: true,
|
|
|
- vars: {
|
|
|
- OVERLEAF_ALLOW_PUBLIC_ACCESS: 'true',
|
|
|
- },
|
|
|
- withDataDir: true,
|
|
|
- })
|
|
|
- it('should grant read access with read link', function () {
|
|
|
- openProjectViaLinkSharingAsAnon(linkSharingReadOnly)
|
|
|
- expectRestrictedReadOnlyAccess()
|
|
|
- })
|
|
|
-
|
|
|
- it('should prompt for login with write link', function () {
|
|
|
- cy.visit(linkSharingReadAndWrite)
|
|
|
- cy.url().should('match', /\/login/)
|
|
|
- })
|
|
|
- })
|
|
|
-
|
|
|
- describe('with OVERLEAF_ALLOW_ANONYMOUS_READ_AND_WRITE_SHARING=true', function () {
|
|
|
- startWith({
|
|
|
- pro: true,
|
|
|
- vars: {
|
|
|
- OVERLEAF_ALLOW_PUBLIC_ACCESS: 'true',
|
|
|
- OVERLEAF_ALLOW_ANONYMOUS_READ_AND_WRITE_SHARING: 'true',
|
|
|
- },
|
|
|
- withDataDir: true,
|
|
|
- })
|
|
|
-
|
|
|
- it('should grant read access with read link', function () {
|
|
|
- openProjectViaLinkSharingAsAnon(linkSharingReadOnly)
|
|
|
- expectRestrictedReadOnlyAccess()
|
|
|
- })
|
|
|
-
|
|
|
- it('should grant write access with write link', function () {
|
|
|
- openProjectViaLinkSharingAsAnon(linkSharingReadAndWrite)
|
|
|
- expectAnonymousReadAndWriteAccess()
|
|
|
- expectEditAuthoredAs('Anonymous')
|
|
|
- })
|
|
|
- })
|
|
|
- })
|
|
|
-
|
|
|
- describe('with OVERLEAF_DISABLE_LINK_SHARING=true', function () {
|
|
|
- const email = 'collaborator-email@example.com'
|
|
|
- ensureUserExists({ email })
|
|
|
-
|
|
|
- const invitedEmail = 'invited-email@example.com'
|
|
|
- ensureUserExists({ email: invitedEmail })
|
|
|
-
|
|
|
- const retainedViewerEmail = 'collaborator-retained-viewer@example.com'
|
|
|
- ensureUserExists({ email: retainedViewerEmail })
|
|
|
-
|
|
|
- const retainedEditorEmail = 'collaborator-retained-editor@example.com'
|
|
|
- ensureUserExists({ email: retainedEditorEmail })
|
|
|
-
|
|
|
- // Link-sharing urls have to be created before disabling link sharing.
|
|
|
- // We use the `beforeEach` hook to reload the server with link sharing
|
|
|
- // disabled **after** the initial setup which happens in the `before`
|
|
|
- // block. The `before` hook always runs prior to the `beforeEach` hook.
|
|
|
-
|
|
|
- // Set up retained access before disabling link sharing
|
|
|
- before(function () {
|
|
|
- // Set up retained viewer access
|
|
|
- login(retainedViewerEmail)
|
|
|
- openProjectViaLinkSharingAsUser(
|
|
|
- linkSharingReadOnly,
|
|
|
- projectName,
|
|
|
- retainedViewerEmail
|
|
|
- )
|
|
|
-
|
|
|
- // Set up retained editor access
|
|
|
- login(retainedEditorEmail)
|
|
|
- openProjectViaLinkSharingAsUser(
|
|
|
- linkSharingReadAndWrite,
|
|
|
- projectName,
|
|
|
- retainedEditorEmail
|
|
|
- )
|
|
|
- })
|
|
|
-
|
|
|
- beforeEach(function () {
|
|
|
- this.timeout(STARTUP_TIMEOUT) // Increase timeout for server reload
|
|
|
-
|
|
|
- return cy.wrap(
|
|
|
- reloadWith({
|
|
|
- pro: true,
|
|
|
- vars: {
|
|
|
- OVERLEAF_ALLOW_PUBLIC_ACCESS: 'true',
|
|
|
- OVERLEAF_ALLOW_ANONYMOUS_READ_AND_WRITE_SHARING: 'true',
|
|
|
- OVERLEAF_DISABLE_LINK_SHARING: 'true',
|
|
|
- },
|
|
|
- withDataDir: true,
|
|
|
- }),
|
|
|
- { timeout: STARTUP_TIMEOUT }
|
|
|
- )
|
|
|
- })
|
|
|
-
|
|
|
- it('should not display link sharing in the sharing modal', function () {
|
|
|
- login('user@example.com')
|
|
|
- openProjectByName(projectName)
|
|
|
- cy.findByRole('navigation', {
|
|
|
- name: 'Project actions',
|
|
|
- })
|
|
|
- .findByRole('button', { name: 'Share' })
|
|
|
- .click()
|
|
|
- cy.findByRole('button', { name: 'Turn on link sharing' }).should(
|
|
|
- 'not.exist'
|
|
|
- )
|
|
|
- })
|
|
|
-
|
|
|
- it('should block new access to read-only link shared projects', function () {
|
|
|
- login(email)
|
|
|
-
|
|
|
- // Test read-only link returns 404
|
|
|
- cy.request({
|
|
|
- url: linkSharingReadOnly,
|
|
|
- failOnStatusCode: false,
|
|
|
- }).then(response => {
|
|
|
- expect(response.status).to.eq(404)
|
|
|
- })
|
|
|
- })
|
|
|
-
|
|
|
- it('should block new access to read-write link shared projects', function () {
|
|
|
- login(email)
|
|
|
-
|
|
|
- // Test read-write link returns 404
|
|
|
- cy.request({
|
|
|
- url: linkSharingReadAndWrite,
|
|
|
- failOnStatusCode: false,
|
|
|
- }).then(response => {
|
|
|
- expect(response.status).to.eq(404)
|
|
|
- })
|
|
|
- })
|
|
|
-
|
|
|
- it('should continue to allow email sharing', function () {
|
|
|
- login('user@example.com')
|
|
|
- shareProjectByEmailAndAcceptInviteViaEmail(
|
|
|
- projectName,
|
|
|
- invitedEmail,
|
|
|
- 'Viewer'
|
|
|
- )
|
|
|
- expectFullReadOnlyAccess()
|
|
|
- expectProjectDashboardEntry()
|
|
|
- })
|
|
|
-
|
|
|
- it('should retain read-only access when project was joined via link before link sharing was turned off', function () {
|
|
|
- login(retainedViewerEmail)
|
|
|
- openProjectByName(projectName)
|
|
|
- expectRestrictedReadOnlyAccess()
|
|
|
- expectProjectDashboardEntry()
|
|
|
- })
|
|
|
-
|
|
|
- it('should retain read-write access when project was joined via link before link sharing was turned off', function () {
|
|
|
- login(retainedEditorEmail)
|
|
|
- openProjectByName(projectName)
|
|
|
- expectFullReadAndWriteAccess()
|
|
|
- expectProjectDashboardEntry()
|
|
|
- })
|
|
|
- })
|
|
|
- })
|
|
|
-})
|