TokenAccessTests.js 40 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447
  1. const { expect } = require('chai')
  2. const async = require('async')
  3. const User = require('./helpers/User')
  4. const request = require('./helpers/request')
  5. const settings = require('@overleaf/settings')
  6. const { db } = require('../../../app/src/infrastructure/mongodb')
  7. const expectErrorResponse = require('./helpers/expectErrorResponse')
  8. const tryEditorAccess = (user, projectId, test, callback) =>
  9. async.series(
  10. [
  11. cb =>
  12. user.request.get(`/project/${projectId}`, (error, response, body) => {
  13. if (error != null) {
  14. return cb(error)
  15. }
  16. test(response, body)
  17. cb()
  18. }),
  19. cb =>
  20. user.request.get(
  21. `/project/${projectId}/download/zip`,
  22. (error, response, body) => {
  23. if (error != null) {
  24. return cb(error)
  25. }
  26. test(response, body)
  27. cb()
  28. }
  29. ),
  30. ],
  31. callback
  32. )
  33. const tryReadOnlyTokenAccess = (
  34. user,
  35. token,
  36. testPageLoad,
  37. testFormPost,
  38. callback
  39. ) => {
  40. _doTryTokenAccess(
  41. `/read/${token}`,
  42. user,
  43. token,
  44. testPageLoad,
  45. testFormPost,
  46. callback
  47. )
  48. }
  49. const tryReadAndWriteTokenAccess = (
  50. user,
  51. token,
  52. testPageLoad,
  53. testFormPost,
  54. callback
  55. ) => {
  56. _doTryTokenAccess(
  57. `/${token}`,
  58. user,
  59. token,
  60. testPageLoad,
  61. testFormPost,
  62. callback
  63. )
  64. }
  65. const _doTryTokenAccess = (
  66. url,
  67. user,
  68. token,
  69. testPageLoad,
  70. testFormPost,
  71. callback
  72. ) => {
  73. user.request.get(url, (err, response, body) => {
  74. if (err) {
  75. return callback(err)
  76. }
  77. testPageLoad(response, body)
  78. if (!testFormPost) {
  79. return callback()
  80. }
  81. user.request.post(
  82. `${url}/grant`,
  83. { json: { token } },
  84. (err, response, body) => {
  85. if (err) {
  86. return callback(err)
  87. }
  88. testFormPost(response, body)
  89. callback()
  90. }
  91. )
  92. })
  93. }
  94. const tryReadOnlyTokenAccept = (
  95. user,
  96. token,
  97. testPageLoad,
  98. testFormPost,
  99. callback
  100. ) => {
  101. _doTryTokenAccept(
  102. `/read/${token}`,
  103. user,
  104. token,
  105. testPageLoad,
  106. testFormPost,
  107. callback
  108. )
  109. }
  110. const tryReadAndWriteTokenAccept = (
  111. user,
  112. token,
  113. testPageLoad,
  114. testFormPost,
  115. callback
  116. ) => {
  117. _doTryTokenAccept(
  118. `/${token}`,
  119. user,
  120. token,
  121. testPageLoad,
  122. testFormPost,
  123. callback
  124. )
  125. }
  126. const _doTryTokenAccept = (
  127. url,
  128. user,
  129. token,
  130. testPageLoad,
  131. testFormPost,
  132. callback
  133. ) => {
  134. user.request.get(url, (err, response, body) => {
  135. if (err) {
  136. return callback(err)
  137. }
  138. testPageLoad(response, body)
  139. if (!testFormPost) {
  140. return callback()
  141. }
  142. user.request.post(
  143. `${url}/grant`,
  144. { json: { token, confirmedByUser: true } },
  145. (err, response, body) => {
  146. if (err) {
  147. return callback(err)
  148. }
  149. testFormPost(response, body)
  150. callback()
  151. }
  152. )
  153. })
  154. }
  155. const tryContentAccess = (user, projcetId, test, callback) => {
  156. // The real-time service calls this end point to determine the user's
  157. // permissions.
  158. let userId
  159. if (user.id != null) {
  160. userId = user.id
  161. } else {
  162. userId = 'anonymous-user'
  163. }
  164. request.post(
  165. {
  166. url: `/project/${projcetId}/join`,
  167. qs: { user_id: userId },
  168. auth: {
  169. user: settings.apis.web.user,
  170. pass: settings.apis.web.pass,
  171. sendImmediately: true,
  172. },
  173. json: true,
  174. jar: false,
  175. },
  176. (error, response, body) => {
  177. if (error != null) {
  178. return callback(error)
  179. }
  180. test(response, body)
  181. callback()
  182. }
  183. )
  184. }
  185. const tryAnonContentAccess = (user, projectId, token, test, callback) => {
  186. // The real-time service calls this end point to determine the user's
  187. // permissions.
  188. let userId
  189. if (user.id != null) {
  190. userId = user.id
  191. } else {
  192. userId = 'anonymous-user'
  193. }
  194. request.post(
  195. {
  196. url: `/project/${projectId}/join`,
  197. qs: { user_id: userId },
  198. auth: {
  199. user: settings.apis.web.user,
  200. pass: settings.apis.web.pass,
  201. sendImmediately: true,
  202. },
  203. headers: {
  204. 'x-sl-anonymous-access-token': token,
  205. },
  206. json: true,
  207. jar: false,
  208. },
  209. (error, response, body) => {
  210. if (error != null) {
  211. return callback(error)
  212. }
  213. test(response, body)
  214. callback()
  215. }
  216. )
  217. }
  218. describe('TokenAccess', function () {
  219. beforeEach(function (done) {
  220. this.timeout(90000)
  221. this.owner = new User()
  222. this.other1 = new User()
  223. this.other2 = new User()
  224. this.anon = new User()
  225. this.siteAdmin = new User({ email: 'admin@example.com' })
  226. async.parallel(
  227. [
  228. cb =>
  229. this.siteAdmin.login(err => {
  230. if (err) {
  231. return cb(err)
  232. }
  233. this.siteAdmin.ensureAdmin(cb)
  234. }),
  235. cb => this.owner.login(cb),
  236. cb => this.other1.login(cb),
  237. cb => this.other2.login(cb),
  238. cb => this.anon.getCsrfToken(cb),
  239. ],
  240. done
  241. )
  242. })
  243. describe('no token-access', function () {
  244. beforeEach(function (done) {
  245. this.owner.createProject(
  246. `token-ro-test${Math.random()}`,
  247. (err, projectId) => {
  248. if (err != null) {
  249. return done(err)
  250. }
  251. this.projectId = projectId
  252. // Note, never made token-based,
  253. // thus no tokens
  254. done()
  255. }
  256. )
  257. })
  258. it('should deny access ', function (done) {
  259. async.series(
  260. [
  261. cb => {
  262. tryEditorAccess(
  263. this.other1,
  264. this.projectId,
  265. expectErrorResponse.restricted.html,
  266. cb
  267. )
  268. },
  269. cb => {
  270. tryContentAccess(
  271. this.other1,
  272. this.projectId,
  273. (response, body) => {
  274. expect(response.statusCode).to.equal(403)
  275. expect(body).to.equal('Forbidden')
  276. },
  277. cb
  278. )
  279. },
  280. ],
  281. done
  282. )
  283. })
  284. })
  285. describe('read-only token', function () {
  286. beforeEach(function (done) {
  287. this.projectName = `token-ro-test${Math.random()}`
  288. this.owner.createProject(this.projectName, (err, projectId) => {
  289. if (err != null) {
  290. return done(err)
  291. }
  292. this.projectId = projectId
  293. this.owner.makeTokenBased(this.projectId, err => {
  294. if (err != null) {
  295. return done(err)
  296. }
  297. this.owner.getProject(this.projectId, (err, project) => {
  298. if (err != null) {
  299. return done(err)
  300. }
  301. this.tokens = project.tokens
  302. done()
  303. })
  304. })
  305. })
  306. })
  307. it('allow the user read-only access to the project', function (done) {
  308. async.series(
  309. [
  310. cb => {
  311. // deny access before token is used
  312. tryEditorAccess(
  313. this.other1,
  314. this.projectId,
  315. expectErrorResponse.restricted.html,
  316. cb
  317. )
  318. },
  319. cb => {
  320. // try token
  321. tryReadOnlyTokenAccess(
  322. this.other1,
  323. this.tokens.readOnly,
  324. (response, body) => {
  325. expect(response.statusCode).to.equal(200)
  326. },
  327. (response, body) => {
  328. expect(response.statusCode).to.equal(200)
  329. expect(body.requireAccept.projectName).to.equal(
  330. this.projectName
  331. )
  332. },
  333. cb
  334. )
  335. },
  336. cb => {
  337. // deny access before token is accepted
  338. tryEditorAccess(
  339. this.other1,
  340. this.projectId,
  341. expectErrorResponse.restricted.html,
  342. cb
  343. )
  344. },
  345. cb => {
  346. // accept token
  347. tryReadOnlyTokenAccept(
  348. this.other1,
  349. this.tokens.readOnly,
  350. (response, body) => {
  351. expect(response.statusCode).to.equal(200)
  352. },
  353. (response, body) => {
  354. expect(response.statusCode).to.equal(200)
  355. expect(body.redirect).to.equal(`/project/${this.projectId}`)
  356. expect(body.tokenAccessGranted).to.equal('readOnly')
  357. },
  358. cb
  359. )
  360. },
  361. cb => {
  362. // allow content access read-only
  363. tryContentAccess(
  364. this.other1,
  365. this.projectId,
  366. (response, body) => {
  367. expect(body.privilegeLevel).to.equal('readOnly')
  368. expect(body.isRestrictedUser).to.equal(true)
  369. expect(body.project.owner).to.have.keys('_id')
  370. expect(body.project.owner).to.not.have.any.keys(
  371. 'email',
  372. 'first_name',
  373. 'last_name'
  374. )
  375. },
  376. cb
  377. )
  378. },
  379. cb => {
  380. tryEditorAccess(
  381. this.other1,
  382. this.projectId,
  383. (response, body) => {
  384. expect(response.statusCode).to.equal(200)
  385. },
  386. cb
  387. )
  388. },
  389. ],
  390. done
  391. )
  392. })
  393. it('should redirect the admin to the project (with rw access)', function (done) {
  394. async.series(
  395. [
  396. cb => {
  397. // use token
  398. tryReadOnlyTokenAccess(
  399. this.siteAdmin,
  400. this.tokens.readOnly,
  401. (response, body) => {
  402. expect(response.statusCode).to.equal(200)
  403. },
  404. (response, body) => {
  405. expect(response.statusCode).to.equal(200)
  406. expect(body.redirect).to.equal(`/project/${this.projectId}`)
  407. },
  408. cb
  409. )
  410. },
  411. cb => {
  412. // allow content access read-and-write
  413. tryContentAccess(
  414. this.siteAdmin,
  415. this.projectId,
  416. (response, body) => {
  417. expect(body.privilegeLevel).to.equal('owner')
  418. expect(body.isRestrictedUser).to.equal(false)
  419. },
  420. cb
  421. )
  422. },
  423. ],
  424. done
  425. )
  426. })
  427. describe('made private again', function () {
  428. beforeEach(function (done) {
  429. this.owner.makePrivate(this.projectId, () => setTimeout(done, 1000))
  430. })
  431. it('should not allow the user to access the project', function (done) {
  432. async.series(
  433. [
  434. // no access before token is used
  435. cb =>
  436. tryEditorAccess(
  437. this.other1,
  438. this.projectId,
  439. expectErrorResponse.restricted.html,
  440. cb
  441. ),
  442. // token goes nowhere
  443. cb =>
  444. tryReadOnlyTokenAccess(
  445. this.other1,
  446. this.tokens.readOnly,
  447. (response, body) => {
  448. expect(response.statusCode).to.equal(200)
  449. },
  450. (response, body) => {
  451. expect(response.statusCode).to.equal(404)
  452. },
  453. cb
  454. ),
  455. // still no access
  456. cb =>
  457. tryEditorAccess(
  458. this.other1,
  459. this.projectId,
  460. expectErrorResponse.restricted.html,
  461. cb
  462. ),
  463. cb =>
  464. tryContentAccess(
  465. this.other1,
  466. this.projectId,
  467. (response, body) => {
  468. expect(response.statusCode).to.equal(403)
  469. expect(body).to.equal('Forbidden')
  470. },
  471. cb
  472. ),
  473. ],
  474. done
  475. )
  476. })
  477. })
  478. })
  479. describe('anonymous read-only token', function () {
  480. beforeEach(function (done) {
  481. this.owner.createProject(
  482. `token-anon-ro-test${Math.random()}`,
  483. (err, projectId) => {
  484. if (err != null) {
  485. return done(err)
  486. }
  487. this.projectId = projectId
  488. this.owner.makeTokenBased(this.projectId, err => {
  489. if (err != null) {
  490. return done(err)
  491. }
  492. this.owner.getProject(this.projectId, (err, project) => {
  493. if (err != null) {
  494. return done(err)
  495. }
  496. this.tokens = project.tokens
  497. done()
  498. })
  499. })
  500. }
  501. )
  502. })
  503. it('should allow the user to access project via read-only token url', function (done) {
  504. async.series(
  505. [
  506. cb =>
  507. tryEditorAccess(
  508. this.anon,
  509. this.projectId,
  510. expectErrorResponse.restricted.html,
  511. cb
  512. ),
  513. cb =>
  514. tryReadOnlyTokenAccess(
  515. this.anon,
  516. this.tokens.readOnly,
  517. (response, body) => {
  518. expect(response.statusCode).to.equal(200)
  519. },
  520. (response, body) => {
  521. expect(response.statusCode).to.equal(200)
  522. expect(body.redirect).to.equal(`/project/${this.projectId}`)
  523. expect(body.grantAnonymousAccess).to.equal('readOnly')
  524. },
  525. cb
  526. ),
  527. cb =>
  528. tryEditorAccess(
  529. this.anon,
  530. this.projectId,
  531. (response, body) => {
  532. expect(response.statusCode).to.equal(200)
  533. },
  534. cb
  535. ),
  536. cb =>
  537. tryAnonContentAccess(
  538. this.anon,
  539. this.projectId,
  540. this.tokens.readOnly,
  541. (response, body) => {
  542. expect(body.privilegeLevel).to.equal('readOnly')
  543. expect(body.isRestrictedUser).to.equal(true)
  544. expect(body.project.owner).to.have.keys('_id')
  545. expect(body.project.owner).to.not.have.any.keys(
  546. 'email',
  547. 'first_name',
  548. 'last_name'
  549. )
  550. },
  551. cb
  552. ),
  553. ],
  554. done
  555. )
  556. })
  557. describe('made private again', function () {
  558. beforeEach(function (done) {
  559. this.owner.makePrivate(this.projectId, () => setTimeout(done, 1000))
  560. })
  561. it('should deny access to project', function (done) {
  562. async.series(
  563. [
  564. cb =>
  565. tryEditorAccess(
  566. this.anon,
  567. this.projectId,
  568. expectErrorResponse.restricted.html,
  569. cb
  570. ),
  571. // should not allow the user to access read-only token
  572. cb =>
  573. tryReadOnlyTokenAccess(
  574. this.anon,
  575. this.tokens.readOnly,
  576. (response, body) => {
  577. expect(response.statusCode).to.equal(200)
  578. },
  579. (response, body) => {
  580. expect(response.statusCode).to.equal(404)
  581. },
  582. cb
  583. ),
  584. // still no access
  585. cb =>
  586. tryEditorAccess(
  587. this.anon,
  588. this.projectId,
  589. expectErrorResponse.restricted.html,
  590. cb
  591. ),
  592. // should not allow the user to join the project
  593. cb =>
  594. tryAnonContentAccess(
  595. this.anon,
  596. this.projectId,
  597. this.tokens.readOnly,
  598. (response, body) => {
  599. expect(response.statusCode).to.equal(403)
  600. expect(body).to.equal('Forbidden')
  601. },
  602. cb
  603. ),
  604. ],
  605. done
  606. )
  607. })
  608. })
  609. })
  610. describe('read-and-write token', function () {
  611. beforeEach(function (done) {
  612. this.projectName = `token-rw-test${Math.random()}`
  613. this.owner.createProject(this.projectName, (err, projectId) => {
  614. if (err != null) {
  615. return done(err)
  616. }
  617. this.projectId = projectId
  618. this.owner.makeTokenBased(this.projectId, err => {
  619. if (err != null) {
  620. return done(err)
  621. }
  622. this.owner.getProject(this.projectId, (err, project) => {
  623. if (err != null) {
  624. return done(err)
  625. }
  626. this.tokens = project.tokens
  627. done()
  628. })
  629. })
  630. })
  631. })
  632. it('should allow the user to access project via read-and-write token url', function (done) {
  633. async.series(
  634. [
  635. // deny access before the token is used
  636. cb =>
  637. tryEditorAccess(
  638. this.other1,
  639. this.projectId,
  640. expectErrorResponse.restricted.html,
  641. cb
  642. ),
  643. // try token
  644. cb =>
  645. tryReadAndWriteTokenAccess(
  646. this.other1,
  647. this.tokens.readAndWrite,
  648. (response, body) => {
  649. expect(response.statusCode).to.equal(200)
  650. },
  651. (response, body) => {
  652. expect(response.statusCode).to.equal(200)
  653. expect(body.requireAccept.projectName).to.equal(
  654. this.projectName
  655. )
  656. },
  657. cb
  658. ),
  659. // deny access before the token is accepted
  660. cb =>
  661. tryEditorAccess(
  662. this.other1,
  663. this.projectId,
  664. expectErrorResponse.restricted.html,
  665. cb
  666. ),
  667. // accept token
  668. cb =>
  669. tryReadAndWriteTokenAccept(
  670. this.other1,
  671. this.tokens.readAndWrite,
  672. (response, body) => {
  673. expect(response.statusCode).to.equal(200)
  674. },
  675. (response, body) => {
  676. expect(response.statusCode).to.equal(200)
  677. expect(body.redirect).to.equal(`/project/${this.projectId}`)
  678. expect(body.tokenAccessGranted).to.equal('readAndWrite')
  679. },
  680. cb
  681. ),
  682. cb =>
  683. tryEditorAccess(
  684. this.other1,
  685. this.projectId,
  686. (response, body) => {
  687. expect(response.statusCode).to.equal(200)
  688. },
  689. cb
  690. ),
  691. cb =>
  692. tryContentAccess(
  693. this.other1,
  694. this.projectId,
  695. (response, body) => {
  696. expect(body.privilegeLevel).to.equal('readAndWrite')
  697. expect(body.isRestrictedUser).to.equal(false)
  698. expect(body.project.owner).to.have.all.keys(
  699. '_id',
  700. 'email',
  701. 'first_name',
  702. 'last_name',
  703. 'privileges',
  704. 'signUpDate'
  705. )
  706. },
  707. cb
  708. ),
  709. ],
  710. done
  711. )
  712. })
  713. describe('upgrading from a read-only token', function () {
  714. beforeEach(function (done) {
  715. this.owner.createProject(
  716. `token-rw-upgrade-test${Math.random()}`,
  717. (err, projectId) => {
  718. if (err != null) {
  719. return done(err)
  720. }
  721. this.projectId = projectId
  722. this.owner.makeTokenBased(this.projectId, err => {
  723. if (err != null) {
  724. return done(err)
  725. }
  726. this.owner.getProject(this.projectId, (err, project) => {
  727. if (err != null) {
  728. return done(err)
  729. }
  730. this.tokens = project.tokens
  731. done()
  732. })
  733. })
  734. }
  735. )
  736. })
  737. it('should allow user to access project via read-only, then upgrade to read-write', function (done) {
  738. async.series(
  739. [
  740. // deny access before the token is used
  741. cb =>
  742. tryEditorAccess(
  743. this.other1,
  744. this.projectId,
  745. expectErrorResponse.restricted.html,
  746. cb
  747. ),
  748. cb => {
  749. // use read-only token
  750. tryReadOnlyTokenAccept(
  751. this.other1,
  752. this.tokens.readOnly,
  753. (response, body) => {
  754. expect(response.statusCode).to.equal(200)
  755. },
  756. (response, body) => {
  757. expect(response.statusCode).to.equal(200)
  758. expect(body.redirect).to.equal(`/project/${this.projectId}`)
  759. expect(body.tokenAccessGranted).to.equal('readOnly')
  760. },
  761. cb
  762. )
  763. },
  764. cb => {
  765. tryEditorAccess(
  766. this.other1,
  767. this.projectId,
  768. (response, body) => {
  769. expect(response.statusCode).to.equal(200)
  770. },
  771. cb
  772. )
  773. },
  774. cb => {
  775. // allow content access read-only
  776. tryContentAccess(
  777. this.other1,
  778. this.projectId,
  779. (response, body) => {
  780. expect(body.privilegeLevel).to.equal('readOnly')
  781. expect(body.isRestrictedUser).to.equal(true)
  782. expect(body.project.owner).to.have.keys('_id')
  783. expect(body.project.owner).to.not.have.any.keys(
  784. 'email',
  785. 'first_name',
  786. 'last_name'
  787. )
  788. },
  789. cb
  790. )
  791. },
  792. //
  793. // Then switch to read-write token
  794. //
  795. cb =>
  796. tryReadAndWriteTokenAccept(
  797. this.other1,
  798. this.tokens.readAndWrite,
  799. (response, body) => {
  800. expect(response.statusCode).to.equal(200)
  801. },
  802. (response, body) => {
  803. expect(response.statusCode).to.equal(200)
  804. expect(body.redirect).to.equal(`/project/${this.projectId}`)
  805. expect(body.tokenAccessGranted).to.equal('readAndWrite')
  806. },
  807. cb
  808. ),
  809. cb =>
  810. tryEditorAccess(
  811. this.other1,
  812. this.projectId,
  813. (response, body) => {
  814. expect(response.statusCode).to.equal(200)
  815. },
  816. cb
  817. ),
  818. cb =>
  819. tryContentAccess(
  820. this.other1,
  821. this.projectId,
  822. (response, body) => {
  823. expect(body.privilegeLevel).to.equal('readAndWrite')
  824. expect(body.isRestrictedUser).to.equal(false)
  825. expect(body.project.owner).to.have.all.keys(
  826. '_id',
  827. 'email',
  828. 'first_name',
  829. 'last_name',
  830. 'privileges',
  831. 'signUpDate'
  832. )
  833. },
  834. cb
  835. ),
  836. ],
  837. done
  838. )
  839. })
  840. })
  841. describe('made private again', function () {
  842. beforeEach(function (done) {
  843. this.owner.makePrivate(this.projectId, () => setTimeout(done, 1000))
  844. })
  845. it('should deny access to project', function (done) {
  846. async.series(
  847. [
  848. cb => {
  849. tryEditorAccess(
  850. this.other1,
  851. this.projectId,
  852. (response, body) => {},
  853. cb
  854. )
  855. },
  856. cb => {
  857. tryReadAndWriteTokenAccess(
  858. this.other1,
  859. this.tokens.readAndWrite,
  860. (response, body) => {
  861. expect(response.statusCode).to.equal(200)
  862. },
  863. (response, body) => {
  864. expect(response.statusCode).to.equal(404)
  865. },
  866. cb
  867. )
  868. },
  869. cb => {
  870. tryEditorAccess(
  871. this.other1,
  872. this.projectId,
  873. expectErrorResponse.restricted.html,
  874. cb
  875. )
  876. },
  877. cb => {
  878. tryContentAccess(
  879. this.other1,
  880. this.projectId,
  881. (response, body) => {
  882. expect(response.statusCode).to.equal(403)
  883. expect(body).to.equal('Forbidden')
  884. },
  885. cb
  886. )
  887. },
  888. ],
  889. done
  890. )
  891. })
  892. })
  893. })
  894. if (!settings.allowAnonymousReadAndWriteSharing) {
  895. describe('anonymous read-and-write token, disabled', function () {
  896. beforeEach(function (done) {
  897. this.owner.createProject(
  898. `token-anon-rw-test${Math.random()}`,
  899. (err, projectId) => {
  900. if (err != null) {
  901. return done(err)
  902. }
  903. this.projectId = projectId
  904. this.owner.makeTokenBased(this.projectId, err => {
  905. if (err != null) {
  906. return done(err)
  907. }
  908. this.owner.getProject(this.projectId, (err, project) => {
  909. if (err != null) {
  910. return done(err)
  911. }
  912. this.tokens = project.tokens
  913. done()
  914. })
  915. })
  916. }
  917. )
  918. })
  919. it('should not allow the user to access read-and-write token', function (done) {
  920. async.series(
  921. [
  922. cb =>
  923. tryEditorAccess(
  924. this.anon,
  925. this.projectId,
  926. expectErrorResponse.restricted.html,
  927. cb
  928. ),
  929. cb =>
  930. tryReadAndWriteTokenAccess(
  931. this.anon,
  932. this.tokens.readAndWrite,
  933. (response, body) => {
  934. expect(response.statusCode).to.equal(200)
  935. },
  936. (response, body) => {
  937. expect(response.statusCode).to.equal(200)
  938. expect(body).to.deep.equal({
  939. redirect: '/restricted',
  940. anonWriteAccessDenied: true,
  941. })
  942. },
  943. cb
  944. ),
  945. cb =>
  946. tryAnonContentAccess(
  947. this.anon,
  948. this.projectId,
  949. this.tokens.readAndWrite,
  950. (response, body) => {
  951. expect(response.statusCode).to.equal(403)
  952. expect(body).to.equal('Forbidden')
  953. },
  954. cb
  955. ),
  956. cb =>
  957. this.anon.login((err, response, body) => {
  958. expect(err).to.not.exist
  959. expect(response.statusCode).to.equal(200)
  960. expect(body.redir).to.equal(`/${this.tokens.readAndWrite}`)
  961. cb()
  962. }),
  963. ],
  964. done
  965. )
  966. })
  967. it('should require login if project does not exist', function (done) {
  968. async.series(
  969. [
  970. // delete project
  971. cb => {
  972. this.owner.deleteProject(this.projectId, cb)
  973. },
  974. cb =>
  975. tryReadAndWriteTokenAccess(
  976. this.anon,
  977. this.tokens.readAndWrite,
  978. (response, body) => {
  979. expect(response.statusCode).to.equal(200)
  980. },
  981. (response, body) => {
  982. expect(response.statusCode).to.equal(200)
  983. expect(body).to.deep.equal({
  984. redirect: '/restricted',
  985. anonWriteAccessDenied: true,
  986. })
  987. },
  988. cb
  989. ),
  990. cb =>
  991. this.anon.login((err, response, body) => {
  992. expect(err).to.not.exist
  993. expect(response.statusCode).to.equal(200)
  994. expect(body.redir).to.equal(`/${this.tokens.readAndWrite}`)
  995. cb()
  996. }),
  997. ],
  998. done
  999. )
  1000. })
  1001. })
  1002. } else {
  1003. describe('anonymous read-and-write token, enabled', function () {
  1004. beforeEach(function (done) {
  1005. this.owner.createProject(
  1006. `token-anon-rw-test${Math.random()}`,
  1007. (err, projectId) => {
  1008. if (err != null) {
  1009. return done(err)
  1010. }
  1011. this.projectId = projectId
  1012. this.owner.makeTokenBased(this.projectId, err => {
  1013. if (err != null) {
  1014. return done(err)
  1015. }
  1016. this.owner.getProject(this.projectId, (err, project) => {
  1017. if (err != null) {
  1018. return done(err)
  1019. }
  1020. this.tokens = project.tokens
  1021. done()
  1022. })
  1023. })
  1024. }
  1025. )
  1026. })
  1027. it('should allow the user to access project via read-and-write token url', function (done) {
  1028. async.series(
  1029. [
  1030. cb =>
  1031. tryEditorAccess(
  1032. this.anon,
  1033. this.projectId,
  1034. expectErrorResponse.restricted.html,
  1035. cb
  1036. ),
  1037. cb =>
  1038. tryReadAndWriteTokenAccess(
  1039. this.anon,
  1040. this.tokens.readAndWrite,
  1041. (response, body) => {
  1042. expect(response.statusCode).to.equal(200)
  1043. },
  1044. (response, body) => {
  1045. expect(response.statusCode).to.equal(200)
  1046. expect(body.redirect).to.equal(`/project/${this.projectId}`)
  1047. expect(body.grantAnonymousAccess).to.equal('readAndWrite')
  1048. },
  1049. cb
  1050. ),
  1051. cb =>
  1052. tryEditorAccess(
  1053. this.anon,
  1054. this.projectId,
  1055. (response, body) => {
  1056. expect(response.statusCode).to.equal(200)
  1057. },
  1058. cb
  1059. ),
  1060. cb =>
  1061. tryAnonContentAccess(
  1062. this.anon,
  1063. this.projectId,
  1064. this.tokens.readAndWrite,
  1065. (response, body) => {
  1066. expect(body.privilegeLevel).to.equal('readAndWrite')
  1067. },
  1068. cb
  1069. ),
  1070. ],
  1071. done
  1072. )
  1073. })
  1074. describe('made private again', function () {
  1075. beforeEach(function (done) {
  1076. this.owner.makePrivate(this.projectId, () => setTimeout(done, 1000))
  1077. })
  1078. it('should not allow the user to access read-and-write token', function (done) {
  1079. async.series(
  1080. [
  1081. cb =>
  1082. tryEditorAccess(
  1083. this.anon,
  1084. this.projectId,
  1085. expectErrorResponse.restricted.html,
  1086. cb
  1087. ),
  1088. cb =>
  1089. tryReadAndWriteTokenAccess(
  1090. this.anon,
  1091. this.tokens.readAndWrite,
  1092. (response, body) => {
  1093. expect(response.statusCode).to.equal(200)
  1094. },
  1095. (response, body) => {
  1096. expect(response.statusCode).to.equal(404)
  1097. },
  1098. cb
  1099. ),
  1100. cb =>
  1101. tryEditorAccess(
  1102. this.anon,
  1103. this.projectId,
  1104. expectErrorResponse.restricted.html,
  1105. cb
  1106. ),
  1107. cb =>
  1108. tryAnonContentAccess(
  1109. this.anon,
  1110. this.projectId,
  1111. this.tokens.readAndWrite,
  1112. (response, body) => {
  1113. expect(response.statusCode).to.equal(403)
  1114. expect(body).to.equal('Forbidden')
  1115. },
  1116. cb
  1117. ),
  1118. ],
  1119. done
  1120. )
  1121. })
  1122. })
  1123. it('should 404 if project does not exist', function (done) {
  1124. async.series(
  1125. [
  1126. // delete project
  1127. cb => {
  1128. this.owner.deleteProject(this.projectId, cb)
  1129. },
  1130. cb =>
  1131. tryReadAndWriteTokenAccess(
  1132. this.anon,
  1133. this.tokens.readAndWrite,
  1134. (response, body) => {
  1135. expect(response.statusCode).to.equal(200)
  1136. },
  1137. (response, body) => {
  1138. expect(response.statusCode).to.equal(200)
  1139. expect(body).to.deep.equal({
  1140. v1Import: {
  1141. status: 'mustLogin',
  1142. },
  1143. })
  1144. },
  1145. cb
  1146. ),
  1147. ],
  1148. done
  1149. )
  1150. })
  1151. })
  1152. }
  1153. describe('private overleaf project', function () {
  1154. beforeEach(function (done) {
  1155. this.owner.createProject('overleaf-import', (err, projectId) => {
  1156. expect(err).not.to.exist
  1157. this.projectId = projectId
  1158. this.owner.makeTokenBased(this.projectId, err => {
  1159. expect(err).not.to.exist
  1160. this.owner.getProject(this.projectId, (err, project) => {
  1161. expect(err).not.to.exist
  1162. this.tokens = project.tokens
  1163. this.owner.makePrivate(this.projectId, () => {
  1164. db.projects.updateOne(
  1165. { _id: project._id },
  1166. {
  1167. $set: {
  1168. overleaf: { id: 1234 },
  1169. },
  1170. },
  1171. err => {
  1172. expect(err).not.to.exist
  1173. done()
  1174. }
  1175. )
  1176. })
  1177. })
  1178. })
  1179. })
  1180. })
  1181. it('should only allow the owner access to the project', function (done) {
  1182. async.series(
  1183. [
  1184. // should redirect to canonical path, when owner uses read-write token
  1185. cb =>
  1186. tryReadAndWriteTokenAccess(
  1187. this.owner,
  1188. this.tokens.readAndWrite,
  1189. (response, body) => {
  1190. expect(response.statusCode).to.equal(200)
  1191. },
  1192. (response, body) => {
  1193. expect(response.statusCode).to.equal(200)
  1194. expect(response.body.redirect).to.equal(
  1195. `/project/${this.projectId}`
  1196. )
  1197. expect(response.body.higherAccess).to.equal(true)
  1198. },
  1199. cb
  1200. ),
  1201. cb =>
  1202. tryEditorAccess(
  1203. this.owner,
  1204. this.projectId,
  1205. (response, body) => {
  1206. expect(response.statusCode).to.equal(200)
  1207. },
  1208. cb
  1209. ),
  1210. cb =>
  1211. tryContentAccess(
  1212. this.owner,
  1213. this.projectId,
  1214. (response, body) => {
  1215. expect(body.privilegeLevel).to.equal('owner')
  1216. },
  1217. cb
  1218. ),
  1219. // non-owner should be denied access
  1220. cb =>
  1221. tryContentAccess(
  1222. this.other2,
  1223. this.projectId,
  1224. (response, body) => {
  1225. expect(response.statusCode).to.equal(403)
  1226. expect(body).to.equal('Forbidden')
  1227. },
  1228. cb
  1229. ),
  1230. ],
  1231. done
  1232. )
  1233. })
  1234. })
  1235. describe('private project, with higher access', function () {
  1236. beforeEach(function (done) {
  1237. this.owner.createProject(
  1238. `higher-access-test-${Math.random()}`,
  1239. (err, projectId) => {
  1240. expect(err).not.to.exist
  1241. this.projectId = projectId
  1242. this.owner.addUserToProject(
  1243. this.projectId,
  1244. this.other1,
  1245. 'readAndWrite',
  1246. err => {
  1247. expect(err).not.to.exist
  1248. this.owner.makeTokenBased(this.projectId, err => {
  1249. expect(err).not.to.exist
  1250. this.owner.getProject(this.projectId, (err, project) => {
  1251. expect(err).not.to.exist
  1252. this.tokens = project.tokens
  1253. this.owner.makePrivate(this.projectId, () => {
  1254. setTimeout(done, 1000)
  1255. })
  1256. })
  1257. })
  1258. }
  1259. )
  1260. }
  1261. )
  1262. })
  1263. it('should allow the user access to the project', function (done) {
  1264. async.series(
  1265. [
  1266. // should redirect to canonical path, when user uses read-write token
  1267. cb =>
  1268. tryReadAndWriteTokenAccess(
  1269. this.other1,
  1270. this.tokens.readAndWrite,
  1271. (response, body) => {
  1272. expect(response.statusCode).to.equal(200)
  1273. },
  1274. (response, body) => {
  1275. expect(response.statusCode).to.equal(200)
  1276. expect(response.body.redirect).to.equal(
  1277. `/project/${this.projectId}`
  1278. )
  1279. expect(response.body.higherAccess).to.equal(true)
  1280. },
  1281. cb
  1282. ),
  1283. // should redirect to canonical path, when user uses read-only token
  1284. cb =>
  1285. tryReadOnlyTokenAccess(
  1286. this.other1,
  1287. this.tokens.readOnly,
  1288. (response, body) => {
  1289. expect(response.statusCode).to.equal(200)
  1290. },
  1291. (response, body) => {
  1292. expect(response.statusCode).to.equal(200)
  1293. expect(response.body.redirect).to.equal(
  1294. `/project/${this.projectId}`
  1295. )
  1296. expect(response.body.higherAccess).to.equal(true)
  1297. },
  1298. cb
  1299. ),
  1300. // should allow the user access to the project
  1301. cb =>
  1302. tryEditorAccess(
  1303. this.other1,
  1304. this.projectId,
  1305. (response, body) => {
  1306. expect(response.statusCode).to.equal(200)
  1307. },
  1308. cb
  1309. ),
  1310. // should allow user to join the project
  1311. cb =>
  1312. tryContentAccess(
  1313. this.other1,
  1314. this.projectId,
  1315. (response, body) => {
  1316. expect(body.privilegeLevel).to.equal('readAndWrite')
  1317. },
  1318. cb
  1319. ),
  1320. // should not allow a different user to join the project
  1321. cb =>
  1322. tryEditorAccess(
  1323. this.other2,
  1324. this.projectId,
  1325. expectErrorResponse.restricted.html,
  1326. cb
  1327. ),
  1328. cb =>
  1329. tryContentAccess(
  1330. this.other2,
  1331. this.projectId,
  1332. (response, body) => {
  1333. expect(response.statusCode).to.equal(403)
  1334. expect(body).to.equal('Forbidden')
  1335. },
  1336. cb
  1337. ),
  1338. ],
  1339. done
  1340. )
  1341. })
  1342. })
  1343. describe('deleted project', function () {
  1344. beforeEach(function (done) {
  1345. this.owner.createProject(
  1346. `delete-test${Math.random()}`,
  1347. (err, projectId) => {
  1348. if (err != null) {
  1349. return done(err)
  1350. }
  1351. this.projectId = projectId
  1352. this.owner.makeTokenBased(this.projectId, err => {
  1353. if (err != null) {
  1354. return done(err)
  1355. }
  1356. this.owner.getProject(this.projectId, (err, project) => {
  1357. if (err != null) {
  1358. return done(err)
  1359. }
  1360. this.tokens = project.tokens
  1361. done()
  1362. })
  1363. })
  1364. }
  1365. )
  1366. })
  1367. it('should 404', function (done) {
  1368. async.series(
  1369. [
  1370. // delete project
  1371. cb => {
  1372. this.owner.deleteProject(this.projectId, cb)
  1373. },
  1374. cb => {
  1375. // use read-only token
  1376. tryReadOnlyTokenAccess(
  1377. this.other1,
  1378. this.tokens.readOnly,
  1379. (response, body) => {
  1380. expect(response.statusCode).to.equal(200)
  1381. },
  1382. (response, body) => {
  1383. expect(response.statusCode).to.equal(404)
  1384. },
  1385. cb
  1386. )
  1387. },
  1388. cb => {
  1389. // use read-write token
  1390. tryReadAndWriteTokenAccess(
  1391. this.other1,
  1392. this.tokens.readAndWrite,
  1393. (response, body) => {
  1394. expect(response.statusCode).to.equal(200)
  1395. },
  1396. (response, body) => {
  1397. expect(response.statusCode).to.equal(404)
  1398. },
  1399. cb
  1400. )
  1401. },
  1402. ],
  1403. done
  1404. )
  1405. })
  1406. })
  1407. })