AuthorizationTests.mjs 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639
  1. import { expect } from 'chai'
  2. import UserHelper from './helpers/User.mjs'
  3. import request from './helpers/request.js'
  4. import settings from '@overleaf/settings'
  5. import Features from '../../../app/src/infrastructure/Features.js'
  6. import expectErrorResponse from './helpers/expectErrorResponse.mjs'
  7. import { promisify } from '@overleaf/promise-utils'
  8. const User = UserHelper.promises
  9. async function tryReadAccess(user, projectId, test) {
  10. const projectRequest = await user.doRequest('get', `/project/${projectId}`)
  11. test(projectRequest.response, projectRequest.body)
  12. const zipRequest = await user.doRequest(
  13. 'get',
  14. `/project/${projectId}/download/zip`
  15. )
  16. test(zipRequest.response, zipRequest.body)
  17. }
  18. async function tryRenameProjectAccess(user, projectId, test) {
  19. const { response, body } = await user.doRequest('post', {
  20. url: `/project/${projectId}/settings`,
  21. json: { name: 'new name' },
  22. })
  23. test(response, body)
  24. }
  25. async function trySettingsWriteAccess(user, projectId, test) {
  26. const { response, body } = await user.doRequest('post', {
  27. url: `/project/${projectId}/settings`,
  28. json: { compiler: 'latex' },
  29. })
  30. test(response, body)
  31. }
  32. async function tryProjectAdminAccess(user, projectId, test) {
  33. const renameRequest = await user.doRequest('post', {
  34. url: `/project/${projectId}/rename`,
  35. json: { newProjectName: 'new-name' },
  36. })
  37. test(renameRequest.response, renameRequest.body)
  38. const settingsRequest = await user.doRequest('post', {
  39. url: `/project/${projectId}/settings/admin`,
  40. json: { publicAccessLevel: 'private' },
  41. })
  42. test(settingsRequest.response, settingsRequest.body)
  43. }
  44. async function tryAdminAccess(user, test) {
  45. const { response, body } = await user.doRequest('get', '/admin')
  46. test(response, body)
  47. if (Features.hasFeature('saas')) {
  48. const { response, body } = await user.doRequest(
  49. 'get',
  50. `/admin/user/${user._id}`
  51. )
  52. test(response, body)
  53. }
  54. }
  55. function tryContentAccessCb(user, projectId, test, callback) {
  56. // The real-time service calls this end point to determine the user's
  57. // permissions.
  58. let userId
  59. if (user.id != null) {
  60. userId = user.id
  61. } else {
  62. userId = 'anonymous-user'
  63. }
  64. request.post(
  65. {
  66. url: `/project/${projectId}/join`,
  67. auth: {
  68. user: settings.apis.web.user,
  69. pass: settings.apis.web.pass,
  70. sendImmediately: true,
  71. },
  72. json: { userId },
  73. jar: false,
  74. },
  75. (error, response, body) => {
  76. if (error != null) {
  77. return callback(error)
  78. }
  79. test(response, body)
  80. callback()
  81. }
  82. )
  83. }
  84. const tryContentAccess = promisify(tryContentAccessCb)
  85. async function expectAdminAccess(user) {
  86. await tryAdminAccess(user, response =>
  87. expect(response.statusCode).to.be.oneOf([200, 204])
  88. )
  89. }
  90. async function expectRedirectedAdminAccess(user) {
  91. await tryAdminAccess(user, response => {
  92. expect(response.statusCode).to.equal(302)
  93. expect(response.headers.location).to.equal(
  94. settings.adminUrl + response.request.uri.pathname
  95. )
  96. })
  97. }
  98. async function expectReadAccess(user, projectId) {
  99. await tryReadAccess(user, projectId, response =>
  100. expect(response.statusCode).to.be.oneOf([200, 204])
  101. )
  102. await tryContentAccess(user, projectId, (response, body) =>
  103. expect(body.privilegeLevel).to.be.oneOf([
  104. 'owner',
  105. 'readAndWrite',
  106. 'readOnly',
  107. ])
  108. )
  109. }
  110. async function expectContentWriteAccess(user, projectId) {
  111. await tryContentAccess(user, projectId, (response, body) =>
  112. expect(body.privilegeLevel).to.be.oneOf(['owner', 'readAndWrite'])
  113. )
  114. }
  115. async function expectRenameProjectAccess(user, projectId) {
  116. await tryRenameProjectAccess(user, projectId, response => {
  117. expect(response.statusCode).to.be.oneOf([200, 204])
  118. })
  119. }
  120. async function expectSettingsWriteAccess(user, projectId) {
  121. await trySettingsWriteAccess(user, projectId, response =>
  122. expect(response.statusCode).to.be.oneOf([200, 204])
  123. )
  124. }
  125. async function expectProjectAdminAccess(user, projectId) {
  126. await tryProjectAdminAccess(user, projectId, response =>
  127. expect(response.statusCode).to.be.oneOf([200, 204])
  128. )
  129. }
  130. async function expectNoReadAccess(user, projectId) {
  131. await tryReadAccess(user, projectId, expectErrorResponse.restricted.html)
  132. await tryContentAccess(user, projectId, (response, body) => {
  133. expect(response.statusCode).to.equal(403)
  134. expect(body).to.equal('Forbidden')
  135. })
  136. }
  137. async function expectNoContentWriteAccess(user, projectId) {
  138. await tryContentAccess(user, projectId, (response, body) =>
  139. expect(body.privilegeLevel).to.be.oneOf([undefined, null, 'readOnly'])
  140. )
  141. }
  142. async function expectNoSettingsWriteAccess(user, projectId) {
  143. await trySettingsWriteAccess(
  144. user,
  145. projectId,
  146. expectErrorResponse.restricted.json
  147. )
  148. }
  149. async function expectNoRenameProjectAccess(user, projectId) {
  150. await tryRenameProjectAccess(
  151. user,
  152. projectId,
  153. expectErrorResponse.restricted.json
  154. )
  155. }
  156. async function expectNoProjectAdminAccess(user, projectId) {
  157. await tryProjectAdminAccess(user, projectId, response => {
  158. expect(response.statusCode).to.equal(403)
  159. })
  160. }
  161. async function expectNoAnonymousProjectAdminAccess(user, projectId) {
  162. await tryProjectAdminAccess(
  163. user,
  164. projectId,
  165. expectErrorResponse.requireLogin.json
  166. )
  167. }
  168. async function expectChatAccess(user, projectId) {
  169. const { response } = await user.doRequest(
  170. 'get',
  171. `/project/${projectId}/messages`
  172. )
  173. expect(response.statusCode).to.equal(200)
  174. }
  175. async function expectNoChatAccess(user, projectId) {
  176. const { response } = await user.doRequest(
  177. 'get',
  178. `/project/${projectId}/messages`
  179. )
  180. expect(response.statusCode).to.equal(403)
  181. }
  182. describe('Authorization', function () {
  183. beforeEach(async function () {
  184. this.timeout(90000)
  185. this.owner = new User()
  186. this.other1 = new User()
  187. this.other2 = new User()
  188. this.anon = new User()
  189. this.site_admin = new User({ email: 'admin@example.com' })
  190. await Promise.all([
  191. this.owner.login(),
  192. this.other1.login(),
  193. this.other2.login(),
  194. this.anon.getCsrfToken(),
  195. (async () => {
  196. await this.site_admin.ensureUserExists()
  197. await this.site_admin.ensureAdmin()
  198. await this.site_admin.login()
  199. })(),
  200. ])
  201. })
  202. describe('private project', function () {
  203. beforeEach(async function () {
  204. this.projectId = await this.owner.createProject('private-project')
  205. })
  206. it('should allow the owner read access to it', async function () {
  207. await expectReadAccess(this.owner, this.projectId)
  208. })
  209. it('should allow the owner write access to its content', async function () {
  210. await expectContentWriteAccess(this.owner, this.projectId)
  211. })
  212. it('should allow the owner write access to its settings', async function () {
  213. await expectSettingsWriteAccess(this.owner, this.projectId)
  214. })
  215. it('should allow the owner to rename the project', async function () {
  216. await expectRenameProjectAccess(this.owner, this.projectId)
  217. })
  218. it('should allow the owner project admin access to it', async function () {
  219. await expectProjectAdminAccess(this.owner, this.projectId)
  220. })
  221. it('should allow the owner user chat messages access', async function () {
  222. await expectChatAccess(this.owner, this.projectId)
  223. })
  224. it('should not allow another user read access to the project', async function () {
  225. await expectNoReadAccess(this.other1, this.projectId)
  226. })
  227. it('should not allow another user write access to its content', async function () {
  228. await expectNoContentWriteAccess(this.other1, this.projectId)
  229. })
  230. it('should not allow another user write access to its settings', async function () {
  231. await expectNoSettingsWriteAccess(this.other1, this.projectId)
  232. })
  233. it('should not allow another user to rename the project', async function () {
  234. await expectNoRenameProjectAccess(this.other1, this.projectId)
  235. })
  236. it('should not allow another user project admin access to it', async function () {
  237. await expectNoProjectAdminAccess(this.other1, this.projectId)
  238. })
  239. it('should not allow another user chat messages access', async function () {
  240. await expectNoChatAccess(this.other1, this.projectId)
  241. })
  242. it('should not allow anonymous user read access to it', async function () {
  243. await expectNoReadAccess(this.anon, this.projectId)
  244. })
  245. it('should not allow anonymous user write access to its content', async function () {
  246. await expectNoContentWriteAccess(this.anon, this.projectId)
  247. })
  248. it('should not allow anonymous user write access to its settings', async function () {
  249. await expectNoSettingsWriteAccess(this.anon, this.projectId)
  250. })
  251. it('should not allow anonymous user to rename the project', async function () {
  252. await expectNoRenameProjectAccess(this.anon, this.projectId)
  253. })
  254. it('should not allow anonymous user project admin access to it', async function () {
  255. await expectNoAnonymousProjectAdminAccess(this.anon, this.projectId)
  256. })
  257. it('should not allow anonymous user chat messages access', async function () {
  258. await expectNoChatAccess(this.anon, this.projectId)
  259. })
  260. describe('with admin privilege available', function () {
  261. beforeEach(function () {
  262. settings.adminPrivilegeAvailable = true
  263. })
  264. it('should allow site admin users read access to it', async function () {
  265. await expectReadAccess(this.site_admin, this.projectId)
  266. })
  267. it('should allow site admin users write access to its content', async function () {
  268. await expectContentWriteAccess(this.site_admin, this.projectId)
  269. })
  270. it('should allow site admin users write access to its settings', async function () {
  271. await expectSettingsWriteAccess(this.site_admin, this.projectId)
  272. })
  273. it('should allow site admin users to rename the project', async function () {
  274. await expectRenameProjectAccess(this.site_admin, this.projectId)
  275. })
  276. it('should allow site admin users project admin access to it', async function () {
  277. await expectProjectAdminAccess(this.site_admin, this.projectId)
  278. })
  279. it('should allow site admin users site admin access to site admin endpoints', async function () {
  280. await expectAdminAccess(this.site_admin)
  281. })
  282. })
  283. describe('with admin privilege unavailable', function () {
  284. beforeEach(function () {
  285. settings.adminPrivilegeAvailable = false
  286. })
  287. afterEach(function () {
  288. settings.adminPrivilegeAvailable = true
  289. })
  290. it('should not allow site admin users read access to it', async function () {
  291. await expectNoReadAccess(this.site_admin, this.projectId)
  292. })
  293. it('should not allow site admin users write access to its content', async function () {
  294. await expectNoContentWriteAccess(this.site_admin, this.projectId)
  295. })
  296. it('should not allow site admin users write access to its settings', async function () {
  297. await expectNoSettingsWriteAccess(this.site_admin, this.projectId)
  298. })
  299. it('should not allow site admin users to rename the project', async function () {
  300. await expectNoRenameProjectAccess(this.site_admin, this.projectId)
  301. })
  302. it('should not allow site admin users project admin access to it', async function () {
  303. await expectNoProjectAdminAccess(this.site_admin, this.projectId)
  304. })
  305. it('should redirect site admin users when accessing site admin endpoints', async function () {
  306. await expectRedirectedAdminAccess(this.site_admin)
  307. })
  308. })
  309. describe('with admin roles', function () {
  310. beforeEach(function () {
  311. if (!settings.moduleImportSequence.includes('admin-roles')) {
  312. this.skip()
  313. }
  314. settings.adminRolesEnabled = true
  315. settings.adminPrivilegeAvailable = true
  316. })
  317. afterEach(function () {
  318. settings.adminRolesEnabled = false
  319. settings.adminPrivilegeAvailable = true
  320. this.site_admin.mongoUpdate({
  321. $set: { adminRoles: [] },
  322. })
  323. })
  324. describe('engineering', function () {
  325. beforeEach(function () {
  326. this.site_admin.mongoUpdate({
  327. $set: { adminRoles: ['engineering'] },
  328. })
  329. })
  330. it('should allow site admin users read access to it', async function () {
  331. await expectReadAccess(this.site_admin, this.projectId)
  332. })
  333. it('should not allow site admin users write access to its content', async function () {
  334. await expectNoContentWriteAccess(this.site_admin, this.projectId)
  335. })
  336. it('should allow site admin users write access to its settings', async function () {
  337. await expectSettingsWriteAccess(this.site_admin, this.projectId)
  338. })
  339. it('should allow site admin users to rename the project', async function () {
  340. await expectRenameProjectAccess(this.site_admin, this.projectId)
  341. })
  342. it('should allow site admin users project admin access to it', async function () {
  343. await expectProjectAdminAccess(this.site_admin, this.projectId)
  344. })
  345. it('should allow site admin users site admin access to site admin endpoints', async function () {
  346. await expectAdminAccess(this.site_admin)
  347. })
  348. })
  349. describe('no admin role assigned', function () {
  350. beforeEach(function () {
  351. this.site_admin.mongoUpdate({
  352. $set: { adminRoles: [] },
  353. })
  354. })
  355. it('should not allow site admin users read access to it', async function () {
  356. await expectNoReadAccess(this.site_admin, this.projectId)
  357. })
  358. it('should not allow site admin users write access to its content', async function () {
  359. await expectNoContentWriteAccess(this.site_admin, this.projectId)
  360. })
  361. it('should not allow site admin users write access to its settings', async function () {
  362. await expectNoSettingsWriteAccess(this.site_admin, this.projectId)
  363. })
  364. it('should not allow site admin users to rename the project', async function () {
  365. await expectNoRenameProjectAccess(this.site_admin, this.projectId)
  366. })
  367. it('should not allow site admin users project admin access to it', async function () {
  368. await expectNoProjectAdminAccess(this.site_admin, this.projectId)
  369. })
  370. it('should allow site admin users site admin access to site admin endpoints', async function () {
  371. await expectAdminAccess(this.site_admin)
  372. })
  373. })
  374. })
  375. })
  376. describe('shared project', function () {
  377. beforeEach(async function () {
  378. this.rw_user = this.other1
  379. this.ro_user = this.other2
  380. this.projectId = await this.owner.createProject('private-project')
  381. await this.owner.addUserToProject(
  382. this.projectId,
  383. this.ro_user,
  384. 'readOnly'
  385. )
  386. await this.owner.addUserToProject(
  387. this.projectId,
  388. this.rw_user,
  389. 'readAndWrite'
  390. )
  391. })
  392. it('should allow the read-only user read access to it', async function () {
  393. await expectReadAccess(this.ro_user, this.projectId)
  394. })
  395. it('should allow the read-only user chat messages access', async function () {
  396. await expectChatAccess(this.ro_user, this.projectId)
  397. })
  398. it('should not allow the read-only user write access to its content', async function () {
  399. await expectNoContentWriteAccess(this.ro_user, this.projectId)
  400. })
  401. it('should not allow the read-only user write access to its settings', async function () {
  402. await expectNoSettingsWriteAccess(this.ro_user, this.projectId)
  403. })
  404. it('should not allow the read-only user to rename the project', async function () {
  405. await expectNoRenameProjectAccess(this.ro_user, this.projectId)
  406. })
  407. it('should not allow the read-only user project admin access to it', async function () {
  408. await expectNoProjectAdminAccess(this.ro_user, this.projectId)
  409. })
  410. it('should allow the read-write user read access to it', async function () {
  411. await expectReadAccess(this.rw_user, this.projectId)
  412. })
  413. it('should allow the read-write user write access to its content', async function () {
  414. await expectContentWriteAccess(this.rw_user, this.projectId)
  415. })
  416. it('should allow the read-write user write access to its settings', async function () {
  417. await expectSettingsWriteAccess(this.rw_user, this.projectId)
  418. })
  419. it('should not allow the read-write user to rename the project', async function () {
  420. await expectNoRenameProjectAccess(this.rw_user, this.projectId)
  421. })
  422. it('should not allow the read-write user project admin access to it', async function () {
  423. await expectNoProjectAdminAccess(this.rw_user, this.projectId)
  424. })
  425. it('should allow the read-write user chat messages access', async function () {
  426. await expectChatAccess(this.rw_user, this.projectId)
  427. })
  428. })
  429. describe('public read-write project', function () {
  430. /**
  431. * Note: this is a test for the legacy "public access" feature.
  432. * See documentation comment in `Authorization/PublicAccessLevels`
  433. * */
  434. beforeEach(async function () {
  435. this.projectId = await this.owner.createProject('public-rw-project')
  436. await this.owner.makePublic(this.projectId, 'readAndWrite')
  437. })
  438. it('should allow a user read access to it', async function () {
  439. await expectReadAccess(this.other1, this.projectId)
  440. })
  441. it('should allow a user write access to its content', async function () {
  442. await expectContentWriteAccess(this.other1, this.projectId)
  443. })
  444. it('should allow a user chat messages access', async function () {
  445. await expectChatAccess(this.other1, this.projectId)
  446. })
  447. it('should not allow a user write access to its settings', async function () {
  448. await expectNoSettingsWriteAccess(this.other1, this.projectId)
  449. })
  450. it('should not allow a user to rename the project', async function () {
  451. await expectNoRenameProjectAccess(this.other1, this.projectId)
  452. })
  453. it('should not allow a user project admin access to it', async function () {
  454. await expectNoProjectAdminAccess(this.other1, this.projectId)
  455. })
  456. it('should allow an anonymous user read access to it', async function () {
  457. await expectReadAccess(this.anon, this.projectId)
  458. })
  459. it('should allow an anonymous user write access to its content', async function () {
  460. await expectContentWriteAccess(this.anon, this.projectId)
  461. })
  462. it('should allow an anonymous user chat messages access', async function () {
  463. // chat access for anonymous users is a CE/SP-only feature, although currently broken
  464. // https://github.com/overleaf/internal/issues/10944
  465. if (Features.hasFeature('saas')) {
  466. this.skip()
  467. }
  468. await expectChatAccess(this.anon, this.projectId)
  469. })
  470. it('should not allow an anonymous user write access to its settings', async function () {
  471. await expectNoSettingsWriteAccess(this.anon, this.projectId)
  472. })
  473. it('should not allow an anonymous user to rename the project', async function () {
  474. await expectNoRenameProjectAccess(this.anon, this.projectId)
  475. })
  476. it('should not allow an anonymous user project admin access to it', async function () {
  477. await expectNoAnonymousProjectAdminAccess(this.anon, this.projectId)
  478. })
  479. })
  480. describe('public read-only project', function () {
  481. /**
  482. * Note: this is a test for the legacy "public access" feature.
  483. * See documentation comment in `Authorization/PublicAccessLevels`
  484. * */
  485. beforeEach(async function () {
  486. this.projectId = await this.owner.createProject('public-ro-project')
  487. await this.owner.makePublic(this.projectId, 'readOnly')
  488. })
  489. it('should allow a user read access to it', async function () {
  490. await expectReadAccess(this.other1, this.projectId)
  491. })
  492. it('should not allow a user write access to its content', async function () {
  493. await expectNoContentWriteAccess(this.other1, this.projectId)
  494. })
  495. it('should not allow a user write access to its settings', async function () {
  496. await expectNoSettingsWriteAccess(this.other1, this.projectId)
  497. })
  498. it('should not allow a user to rename the project', async function () {
  499. await expectNoRenameProjectAccess(this.other1, this.projectId)
  500. })
  501. it('should not allow a user project admin access to it', async function () {
  502. await expectNoProjectAdminAccess(this.other1, this.projectId)
  503. })
  504. // NOTE: legacy readOnly access does not count as 'restricted' in the new model
  505. it('should allow a user chat messages access', async function () {
  506. await expectChatAccess(this.other1, this.projectId)
  507. })
  508. it('should allow an anonymous user read access to it', async function () {
  509. await expectReadAccess(this.anon, this.projectId)
  510. })
  511. it('should not allow an anonymous user write access to its content', async function () {
  512. await expectNoContentWriteAccess(this.anon, this.projectId)
  513. })
  514. it('should not allow an anonymous user write access to its settings', async function () {
  515. await expectNoSettingsWriteAccess(this.anon, this.projectId)
  516. })
  517. it('should not allow an anonymous user to rename the project', async function () {
  518. await expectNoRenameProjectAccess(this.anon, this.projectId)
  519. })
  520. it('should not allow an anonymous user project admin access to it', async function () {
  521. await expectNoAnonymousProjectAdminAccess(this.anon, this.projectId)
  522. })
  523. it('should not allow an anonymous user chat messages access', async function () {
  524. await expectNoChatAccess(this.anon, this.projectId)
  525. })
  526. })
  527. })