project-sharing.spec.ts 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308
  1. import { v4 as uuid } from 'uuid'
  2. import { isExcludedBySharding, startWith } from './helpers/config'
  3. import { ensureUserExists, login } from './helpers/login'
  4. import {
  5. createProject,
  6. enableLinkSharing,
  7. shareProjectByEmailAndAcceptInviteViaDash,
  8. shareProjectByEmailAndAcceptInviteViaEmail,
  9. } from './helpers/project'
  10. import { throttledRecompile } from './helpers/compile'
  11. import { beforeWithReRunOnTestRetry } from './helpers/beforeWithReRunOnTestRetry'
  12. describe('Project Sharing', function () {
  13. if (isExcludedBySharding('CE_CUSTOM_2')) return
  14. ensureUserExists({ email: 'user@example.com' })
  15. startWith({ withDataDir: true })
  16. let projectName: string
  17. beforeWithReRunOnTestRetry(function () {
  18. projectName = `Project ${uuid()}`
  19. setupTestProject()
  20. })
  21. beforeEach(() => {
  22. // Always start with a fresh session
  23. cy.session([uuid()], () => {})
  24. })
  25. let linkSharingReadOnly: string
  26. let linkSharingReadAndWrite: string
  27. function setupTestProject() {
  28. login('user@example.com')
  29. cy.visit('/project')
  30. createProject(projectName)
  31. // Add chat message
  32. cy.findByText('Chat').click()
  33. // wait for lazy loading of the chat pane
  34. cy.findByText('Send your first message to your collaborators')
  35. cy.get(
  36. 'textarea[placeholder="Send a message to your collaborators…"]'
  37. ).type('New Chat Message{enter}')
  38. // Get link sharing links
  39. enableLinkSharing().then(
  40. ({ linkSharingReadOnly: ro, linkSharingReadAndWrite: rw }) => {
  41. linkSharingReadAndWrite = rw
  42. linkSharingReadOnly = ro
  43. }
  44. )
  45. }
  46. function expectContentReadOnlyAccess() {
  47. cy.url().should('match', /\/project\/[a-fA-F0-9]{24}/)
  48. cy.get('.cm-content').should('contain.text', '\\maketitle')
  49. cy.get('.cm-content').should('have.attr', 'contenteditable', 'false')
  50. }
  51. function expectContentWriteAccess() {
  52. const section = `Test Section ${uuid()}`
  53. cy.url().should('match', /\/project\/[a-fA-F0-9]{24}/)
  54. const recompile = throttledRecompile()
  55. // wait for the editor to finish loading
  56. cy.get('.cm-content').should('contain.text', '\\maketitle')
  57. // the editor should be writable
  58. cy.get('.cm-content').should('have.attr', 'contenteditable', 'true')
  59. cy.findByText('\\maketitle').parent().click()
  60. cy.findByText('\\maketitle').parent().type(`\n\\section{{}${section}}`)
  61. // should have written
  62. cy.get('.cm-content').should('contain.text', `\\section{${section}}`)
  63. // check PDF
  64. recompile()
  65. cy.get('.pdf-viewer').should('contain.text', projectName)
  66. cy.get('.pdf-viewer').should('contain.text', section)
  67. }
  68. function expectNoAccess() {
  69. // try read only access link
  70. cy.visit(linkSharingReadOnly)
  71. cy.url().should('match', /\/login/)
  72. // 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).
  73. // Effectively, cypress then instructs the browser to change the page from /login#read-only-hash to /login#read-and-write-hash.
  74. // 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).
  75. // Work around this by navigating away from the /login page in between checks.
  76. cy.visit('/user/password/reset')
  77. // try read and write access link
  78. cy.visit(linkSharingReadAndWrite)
  79. cy.url().should('match', /\/login/)
  80. }
  81. function expectChatAccess() {
  82. cy.findByText('Chat').click()
  83. cy.findByText('New Chat Message')
  84. }
  85. function expectHistoryAccess() {
  86. cy.findByText('History').click()
  87. cy.findByText('Labels')
  88. cy.findByText(/\\begin\{document}/)
  89. cy.findAllByTestId('history-version-metadata-users')
  90. .last()
  91. .should('have.text', 'user')
  92. cy.findByText('Back to editor').click()
  93. }
  94. function expectNoChatAccess() {
  95. cy.findByText('Layout') // wait for lazy loading
  96. cy.findByText('Chat').should('not.exist')
  97. }
  98. function expectNoHistoryAccess() {
  99. cy.findByText('Layout') // wait for lazy loading
  100. cy.findByText('History').should('not.exist')
  101. }
  102. function expectFullReadOnlyAccess() {
  103. expectContentReadOnlyAccess()
  104. expectChatAccess()
  105. expectHistoryAccess()
  106. }
  107. function expectRestrictedReadOnlyAccess() {
  108. expectContentReadOnlyAccess()
  109. expectNoChatAccess()
  110. expectNoHistoryAccess()
  111. }
  112. function expectReadAndWriteAccess() {
  113. expectContentWriteAccess()
  114. expectChatAccess()
  115. expectHistoryAccess()
  116. }
  117. function expectProjectDashboardEntry() {
  118. cy.visit('/project')
  119. cy.findByText(projectName)
  120. }
  121. function expectEditAuthoredAs(author: string) {
  122. cy.findByText('History').click()
  123. cy.findAllByTestId('history-version-metadata-users')
  124. .first()
  125. .should('contain.text', author) // might have other edits in the same group
  126. }
  127. describe('via email', function () {
  128. const email = 'collaborator-email@example.com'
  129. ensureUserExists({ email })
  130. beforeEach(function () {
  131. login('user@example.com')
  132. shareProjectByEmailAndAcceptInviteViaEmail(
  133. projectName,
  134. email,
  135. 'Read only'
  136. )
  137. })
  138. it('should grant the collaborator read access', () => {
  139. cy.visit('/project')
  140. cy.findByText(projectName).click()
  141. expectFullReadOnlyAccess()
  142. expectProjectDashboardEntry()
  143. })
  144. })
  145. describe('read only', () => {
  146. const email = 'collaborator-ro@example.com'
  147. ensureUserExists({ email })
  148. beforeWithReRunOnTestRetry(function () {
  149. login('user@example.com')
  150. shareProjectByEmailAndAcceptInviteViaDash(projectName, email, 'Read only')
  151. })
  152. it('should grant the collaborator read access', () => {
  153. login(email)
  154. cy.visit('/project')
  155. cy.findByText(projectName).click()
  156. expectFullReadOnlyAccess()
  157. expectProjectDashboardEntry()
  158. })
  159. })
  160. describe('read and write', () => {
  161. const email = 'collaborator-rw@example.com'
  162. ensureUserExists({ email })
  163. beforeWithReRunOnTestRetry(function () {
  164. login('user@example.com')
  165. shareProjectByEmailAndAcceptInviteViaDash(projectName, email, 'Can edit')
  166. })
  167. it('should grant the collaborator write access', () => {
  168. login(email)
  169. cy.visit('/project')
  170. cy.findByText(projectName).click()
  171. expectReadAndWriteAccess()
  172. expectEditAuthoredAs('You')
  173. expectProjectDashboardEntry()
  174. })
  175. })
  176. describe('token access', () => {
  177. describe('logged in', () => {
  178. describe('read only', () => {
  179. const email = 'collaborator-link-ro@example.com'
  180. ensureUserExists({ email })
  181. it('should grant restricted read access', () => {
  182. login(email)
  183. cy.visit(linkSharingReadOnly)
  184. cy.findByText(projectName) // wait for lazy loading
  185. cy.findByText('Join Project').click()
  186. expectRestrictedReadOnlyAccess()
  187. expectProjectDashboardEntry()
  188. })
  189. })
  190. describe('read and write', () => {
  191. const email = 'collaborator-link-rw@example.com'
  192. ensureUserExists({ email })
  193. it('should grant full write access', () => {
  194. login(email)
  195. cy.visit(linkSharingReadAndWrite)
  196. cy.findByText(projectName) // wait for lazy loading
  197. cy.findByText('Join Project').click()
  198. expectReadAndWriteAccess()
  199. expectEditAuthoredAs('You')
  200. expectProjectDashboardEntry()
  201. })
  202. })
  203. })
  204. describe('with OVERLEAF_ALLOW_PUBLIC_ACCESS=false', () => {
  205. describe('wrap startup', () => {
  206. startWith({
  207. vars: {
  208. OVERLEAF_ALLOW_PUBLIC_ACCESS: 'false',
  209. },
  210. withDataDir: true,
  211. })
  212. it('should block access', () => {
  213. expectNoAccess()
  214. })
  215. })
  216. describe('with OVERLEAF_ALLOW_ANONYMOUS_READ_AND_WRITE_SHARING=true', () => {
  217. startWith({
  218. vars: {
  219. OVERLEAF_ALLOW_PUBLIC_ACCESS: 'false',
  220. OVERLEAF_ALLOW_ANONYMOUS_READ_AND_WRITE_SHARING: 'true',
  221. },
  222. withDataDir: true,
  223. })
  224. it('should block access', () => {
  225. expectNoAccess()
  226. })
  227. })
  228. })
  229. describe('with OVERLEAF_ALLOW_PUBLIC_ACCESS=true', () => {
  230. describe('wrap startup', () => {
  231. startWith({
  232. vars: {
  233. OVERLEAF_ALLOW_PUBLIC_ACCESS: 'true',
  234. },
  235. withDataDir: true,
  236. })
  237. it('should grant read access with read link', () => {
  238. cy.visit(linkSharingReadOnly)
  239. expectRestrictedReadOnlyAccess()
  240. })
  241. it('should prompt for login with write link', () => {
  242. cy.visit(linkSharingReadAndWrite)
  243. cy.url().should('match', /\/login/)
  244. })
  245. })
  246. describe('with OVERLEAF_ALLOW_ANONYMOUS_READ_AND_WRITE_SHARING=true', () => {
  247. startWith({
  248. vars: {
  249. OVERLEAF_ALLOW_PUBLIC_ACCESS: 'true',
  250. OVERLEAF_ALLOW_ANONYMOUS_READ_AND_WRITE_SHARING: 'true',
  251. },
  252. withDataDir: true,
  253. })
  254. it('should grant read access with read link', () => {
  255. cy.visit(linkSharingReadOnly)
  256. expectRestrictedReadOnlyAccess()
  257. })
  258. // eslint-disable-next-line mocha/no-skipped-tests
  259. it.skip('should grant write access with write link', () => {
  260. cy.visit(linkSharingReadAndWrite)
  261. expectReadAndWriteAccess()
  262. expectEditAuthoredAs('Anonymous')
  263. })
  264. })
  265. })
  266. })
  267. })