project-sharing.spec.ts 9.2 KB

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