TokenAccessTests.js 64 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212
  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 SplitTestHandler = require('../../../app/src/Features/SplitTests/SplitTestHandler')
  9. const sinon = require('sinon')
  10. const tryEditorAccess = (user, projectId, test, callback) =>
  11. async.series(
  12. [
  13. cb =>
  14. user.request.get(`/project/${projectId}`, (error, response, body) => {
  15. if (error != null) {
  16. return cb(error)
  17. }
  18. test(response, body)
  19. cb()
  20. }),
  21. cb =>
  22. user.request.get(
  23. `/project/${projectId}/download/zip`,
  24. (error, response, body) => {
  25. if (error != null) {
  26. return cb(error)
  27. }
  28. test(response, body)
  29. cb()
  30. }
  31. ),
  32. ],
  33. callback
  34. )
  35. const tryReadOnlyTokenAccess = (
  36. user,
  37. token,
  38. testPageLoad,
  39. testFormPost,
  40. callback
  41. ) => {
  42. _doTryTokenAccess(
  43. `/read/${token}`,
  44. user,
  45. token,
  46. testPageLoad,
  47. testFormPost,
  48. callback
  49. )
  50. }
  51. const tryReadAndWriteTokenAccess = (
  52. user,
  53. token,
  54. testPageLoad,
  55. testFormPost,
  56. callback
  57. ) => {
  58. _doTryTokenAccess(
  59. `/${token}`,
  60. user,
  61. token,
  62. testPageLoad,
  63. testFormPost,
  64. callback
  65. )
  66. }
  67. const _doTryTokenAccess = (
  68. url,
  69. user,
  70. token,
  71. testPageLoad,
  72. testFormPost,
  73. callback
  74. ) => {
  75. user.request.get(url, (err, response, body) => {
  76. if (err) {
  77. return callback(err)
  78. }
  79. testPageLoad(response, body)
  80. if (!testFormPost) {
  81. return callback()
  82. }
  83. user.request.post(
  84. `${url}/grant`,
  85. { json: { token } },
  86. (err, response, body) => {
  87. if (err) {
  88. return callback(err)
  89. }
  90. testFormPost(response, body)
  91. callback()
  92. }
  93. )
  94. })
  95. }
  96. const tryReadOnlyTokenAccept = (
  97. user,
  98. token,
  99. testPageLoad,
  100. testFormPost,
  101. callback
  102. ) => {
  103. _doTryTokenAccept(
  104. `/read/${token}`,
  105. user,
  106. token,
  107. testPageLoad,
  108. testFormPost,
  109. callback
  110. )
  111. }
  112. const tryReadAndWriteTokenAccept = (
  113. user,
  114. token,
  115. testPageLoad,
  116. testFormPost,
  117. callback
  118. ) => {
  119. _doTryTokenAccept(
  120. `/${token}`,
  121. user,
  122. token,
  123. testPageLoad,
  124. testFormPost,
  125. callback
  126. )
  127. }
  128. const _doTryTokenAccept = (
  129. url,
  130. user,
  131. token,
  132. testPageLoad,
  133. testFormPost,
  134. callback
  135. ) => {
  136. user.request.get(url, (err, response, body) => {
  137. if (err) {
  138. return callback(err)
  139. }
  140. testPageLoad(response, body)
  141. if (!testFormPost) {
  142. return callback()
  143. }
  144. user.request.post(
  145. `${url}/grant`,
  146. { json: { token, confirmedByUser: true } },
  147. (err, response, body) => {
  148. if (err) {
  149. return callback(err)
  150. }
  151. testFormPost(response, body)
  152. callback()
  153. }
  154. )
  155. })
  156. }
  157. const tryContentAccess = (user, projcetId, test, callback) => {
  158. // The real-time service calls this end point to determine the user's
  159. // permissions.
  160. let userId
  161. if (user.id != null) {
  162. userId = user.id
  163. } else {
  164. userId = 'anonymous-user'
  165. }
  166. request.post(
  167. {
  168. url: `/project/${projcetId}/join`,
  169. auth: {
  170. user: settings.apis.web.user,
  171. pass: settings.apis.web.pass,
  172. sendImmediately: true,
  173. },
  174. json: {
  175. userId,
  176. },
  177. jar: false,
  178. },
  179. (error, response, body) => {
  180. if (error != null) {
  181. return callback(error)
  182. }
  183. test(response, body)
  184. callback()
  185. }
  186. )
  187. }
  188. const tryAnonContentAccess = (user, projectId, token, test, callback) => {
  189. // The real-time service calls this end point to determine the user's
  190. // permissions.
  191. let userId
  192. if (user.id != null) {
  193. userId = user.id
  194. } else {
  195. userId = 'anonymous-user'
  196. }
  197. request.post(
  198. {
  199. url: `/project/${projectId}/join`,
  200. auth: {
  201. user: settings.apis.web.user,
  202. pass: settings.apis.web.pass,
  203. sendImmediately: true,
  204. },
  205. json: {
  206. userId,
  207. anonymousAccessToken: token,
  208. },
  209. jar: false,
  210. },
  211. (error, response, body) => {
  212. if (error != null) {
  213. return callback(error)
  214. }
  215. test(response, body)
  216. callback()
  217. }
  218. )
  219. }
  220. const tryFetchProjectTokens = (user, projectId, callback) => {
  221. user.request.get(
  222. { url: `/project/${projectId}/tokens`, json: true },
  223. (error, response, body) => {
  224. if (error) {
  225. return callback(error)
  226. }
  227. callback(null, response, body)
  228. }
  229. )
  230. }
  231. const trySharingUpdatesPage = (user, projectId, test, callback) =>
  232. user.request.get(
  233. `/project/${projectId}/sharing-updates`,
  234. (error, response, body) => {
  235. if (error != null) {
  236. return callback(error)
  237. }
  238. test(response, body, projectId)
  239. callback()
  240. }
  241. )
  242. const trySharingUpdatesJoin = (user, projectId, test, callback) =>
  243. user.request.post(
  244. `/project/${projectId}/sharing-updates/join`,
  245. (error, response, body) => {
  246. if (error != null) {
  247. return callback(error)
  248. }
  249. test(response, body, projectId)
  250. callback()
  251. }
  252. )
  253. const trySharingUpdatesView = (user, projectId, test, callback) =>
  254. user.request.post(
  255. `/project/${projectId}/sharing-updates/view`,
  256. (error, response, body) => {
  257. if (error != null) {
  258. return callback(error)
  259. }
  260. test(response, body, projectId)
  261. callback()
  262. }
  263. )
  264. const expectRedirectToProject = (response, body, projectId) => {
  265. expect(response.statusCode).to.equal(302)
  266. expect(response.headers.location).to.equal(`/project/${projectId}`)
  267. }
  268. describe('TokenAccess', function () {
  269. beforeEach(function (done) {
  270. this.timeout(90000)
  271. this.owner = new User()
  272. this.other1 = new User()
  273. this.other2 = new User()
  274. this.anon = new User()
  275. this.siteAdmin = new User({ email: 'admin@example.com' })
  276. async.parallel(
  277. [
  278. cb =>
  279. this.siteAdmin.login(err => {
  280. if (err) {
  281. return cb(err)
  282. }
  283. this.siteAdmin.ensureAdmin(cb)
  284. }),
  285. cb => this.owner.login(cb),
  286. cb => this.other1.login(cb),
  287. cb => this.other2.login(cb),
  288. cb => this.anon.getCsrfToken(cb),
  289. ],
  290. done
  291. )
  292. })
  293. describe('no token-access', function () {
  294. beforeEach(function (done) {
  295. this.owner.createProject(
  296. `token-ro-test${Math.random()}`,
  297. (err, projectId) => {
  298. if (err != null) {
  299. return done(err)
  300. }
  301. this.projectId = projectId
  302. // Note, never made token-based,
  303. // thus no tokens
  304. done()
  305. }
  306. )
  307. })
  308. it('should deny access ', function (done) {
  309. async.series(
  310. [
  311. cb => {
  312. tryEditorAccess(
  313. this.other1,
  314. this.projectId,
  315. expectErrorResponse.restricted.html,
  316. cb
  317. )
  318. },
  319. cb => {
  320. tryContentAccess(
  321. this.other1,
  322. this.projectId,
  323. (response, body) => {
  324. expect(response.statusCode).to.equal(403)
  325. expect(body).to.equal('Forbidden')
  326. },
  327. cb
  328. )
  329. },
  330. ],
  331. done
  332. )
  333. })
  334. it('should deny access to access tokens', function (done) {
  335. tryFetchProjectTokens(this.other1, this.projectId, (error, response) => {
  336. expect(error).to.equal(null)
  337. expect(response.statusCode).to.equal(403)
  338. done()
  339. })
  340. })
  341. })
  342. describe('owner', function () {
  343. beforeEach(function (done) {
  344. this.projectName = `token-owner-test${Math.random()}`
  345. this.owner.createProject(this.projectName, (err, projectId) => {
  346. if (err != null) {
  347. return done(err)
  348. }
  349. this.projectId = projectId
  350. this.owner.makeTokenBased(this.projectId, err => {
  351. if (err != null) {
  352. return done(err)
  353. }
  354. this.owner.getProject(this.projectId, (err, project) => {
  355. if (err != null) {
  356. return done(err)
  357. }
  358. this.tokens = project.tokens
  359. done()
  360. })
  361. })
  362. })
  363. })
  364. it('should be able to fetch read-only and read-write tokens', function (done) {
  365. tryFetchProjectTokens(
  366. this.owner,
  367. this.projectId,
  368. (error, response, body) => {
  369. expect(error).to.equal(null)
  370. expect(response.statusCode).to.equal(200)
  371. expect(body).to.include({
  372. readOnly: this.tokens.readOnly,
  373. readAndWrite: this.tokens.readAndWrite,
  374. readAndWritePrefix: this.tokens.readAndWritePrefix,
  375. })
  376. expect(body.readOnlyHashPrefix).to.exist
  377. expect(body.readAndWriteHashPrefix).to.exist
  378. expect(Object.keys(body).length).to.equal(5)
  379. done()
  380. }
  381. )
  382. })
  383. })
  384. describe('read-only token', function () {
  385. beforeEach(function (done) {
  386. this.projectName = `token-ro-test${Math.random()}`
  387. this.owner.createProject(this.projectName, (err, projectId) => {
  388. if (err != null) {
  389. return done(err)
  390. }
  391. this.projectId = projectId
  392. this.owner.makeTokenBased(this.projectId, err => {
  393. if (err != null) {
  394. return done(err)
  395. }
  396. this.owner.getProject(this.projectId, (err, project) => {
  397. if (err != null) {
  398. return done(err)
  399. }
  400. this.tokens = project.tokens
  401. done()
  402. })
  403. })
  404. })
  405. })
  406. it('allow the user read-only access to the project', function (done) {
  407. async.series(
  408. [
  409. cb => {
  410. // deny access before token is used
  411. tryEditorAccess(
  412. this.other1,
  413. this.projectId,
  414. expectErrorResponse.restricted.html,
  415. cb
  416. )
  417. },
  418. cb => {
  419. // try token
  420. tryReadOnlyTokenAccess(
  421. this.other1,
  422. this.tokens.readOnly,
  423. (response, body) => {
  424. expect(response.statusCode).to.equal(200)
  425. },
  426. (response, body) => {
  427. expect(response.statusCode).to.equal(200)
  428. expect(body.requireAccept.projectName).to.equal(
  429. this.projectName
  430. )
  431. },
  432. cb
  433. )
  434. },
  435. cb => {
  436. // deny access before token is accepted
  437. tryEditorAccess(
  438. this.other1,
  439. this.projectId,
  440. expectErrorResponse.restricted.html,
  441. cb
  442. )
  443. },
  444. cb => {
  445. // accept token
  446. tryReadOnlyTokenAccept(
  447. this.other1,
  448. this.tokens.readOnly,
  449. (response, body) => {
  450. expect(response.statusCode).to.equal(200)
  451. },
  452. (response, body) => {
  453. expect(response.statusCode).to.equal(200)
  454. expect(body.redirect).to.equal(`/project/${this.projectId}`)
  455. expect(body.tokenAccessGranted).to.equal('readOnly')
  456. },
  457. cb
  458. )
  459. },
  460. cb => {
  461. // allow content access read-only
  462. tryContentAccess(
  463. this.other1,
  464. this.projectId,
  465. (response, body) => {
  466. expect(body.privilegeLevel).to.equal('readOnly')
  467. expect(body.isRestrictedUser).to.equal(true)
  468. expect(body.isTokenMember).to.equal(true)
  469. expect(body.isInvitedMember).to.equal(false)
  470. expect(body.project.owner).to.have.keys('_id')
  471. expect(body.project.owner).to.not.have.any.keys(
  472. 'email',
  473. 'first_name',
  474. 'last_name'
  475. )
  476. },
  477. cb
  478. )
  479. },
  480. cb => {
  481. tryEditorAccess(
  482. this.other1,
  483. this.projectId,
  484. (response, body) => {
  485. expect(response.statusCode).to.equal(200)
  486. },
  487. cb
  488. )
  489. },
  490. ],
  491. done
  492. )
  493. })
  494. it('should allow the user to fetch the read-only token', function (done) {
  495. async.series(
  496. [
  497. cb => {
  498. // accept token
  499. tryReadOnlyTokenAccept(
  500. this.other1,
  501. this.tokens.readOnly,
  502. (response, body) => {
  503. expect(response.statusCode).to.equal(200)
  504. },
  505. (response, body) => {
  506. expect(response.statusCode).to.equal(200)
  507. expect(body.redirect).to.equal(`/project/${this.projectId}`)
  508. expect(body.tokenAccessGranted).to.equal('readOnly')
  509. },
  510. cb
  511. )
  512. },
  513. cb => {
  514. tryFetchProjectTokens(
  515. this.other1,
  516. this.projectId,
  517. (error, response, body) => {
  518. expect(error).to.equal(null)
  519. expect(response.statusCode).to.equal(200)
  520. expect(body).to.include({ readOnly: this.tokens.readOnly })
  521. expect(body.readOnlyHashPrefix).to.exist
  522. cb()
  523. }
  524. )
  525. },
  526. ],
  527. done
  528. )
  529. })
  530. it('should redirect the admin to the project (with rw access)', function (done) {
  531. async.series(
  532. [
  533. cb => {
  534. // use token
  535. tryReadOnlyTokenAccess(
  536. this.siteAdmin,
  537. this.tokens.readOnly,
  538. (response, body) => {
  539. expect(response.statusCode).to.equal(200)
  540. },
  541. (response, body) => {
  542. expect(response.statusCode).to.equal(200)
  543. expect(body.redirect).to.equal(`/project/${this.projectId}`)
  544. },
  545. cb
  546. )
  547. },
  548. cb => {
  549. // allow content access read-and-write
  550. tryContentAccess(
  551. this.siteAdmin,
  552. this.projectId,
  553. (response, body) => {
  554. expect(body.privilegeLevel).to.equal('owner')
  555. expect(body.isRestrictedUser).to.equal(false)
  556. expect(body.isTokenMember).to.equal(false)
  557. expect(body.isInvitedMember).to.equal(false)
  558. },
  559. cb
  560. )
  561. },
  562. ],
  563. done
  564. )
  565. })
  566. describe('made private again', function () {
  567. beforeEach(function (done) {
  568. this.owner.makePrivate(this.projectId, () => setTimeout(done, 1000))
  569. })
  570. it('should not allow the user to access the project', function (done) {
  571. async.series(
  572. [
  573. // no access before token is used
  574. cb =>
  575. tryEditorAccess(
  576. this.other1,
  577. this.projectId,
  578. expectErrorResponse.restricted.html,
  579. cb
  580. ),
  581. // token goes nowhere
  582. cb =>
  583. tryReadOnlyTokenAccess(
  584. this.other1,
  585. this.tokens.readOnly,
  586. (response, body) => {
  587. expect(response.statusCode).to.equal(200)
  588. },
  589. (response, body) => {
  590. expect(response.statusCode).to.equal(404)
  591. },
  592. cb
  593. ),
  594. // still no access
  595. cb =>
  596. tryEditorAccess(
  597. this.other1,
  598. this.projectId,
  599. expectErrorResponse.restricted.html,
  600. cb
  601. ),
  602. cb =>
  603. tryContentAccess(
  604. this.other1,
  605. this.projectId,
  606. (response, body) => {
  607. expect(response.statusCode).to.equal(403)
  608. expect(body).to.equal('Forbidden')
  609. },
  610. cb
  611. ),
  612. ],
  613. done
  614. )
  615. })
  616. })
  617. it('should deny access to access tokens', function (done) {
  618. tryFetchProjectTokens(this.other1, this.projectId, (error, response) => {
  619. expect(error).to.equal(null)
  620. expect(response.statusCode).to.equal(403)
  621. done()
  622. })
  623. })
  624. })
  625. describe('anonymous read-only token', function () {
  626. beforeEach(function (done) {
  627. this.owner.createProject(
  628. `token-anon-ro-test${Math.random()}`,
  629. (err, projectId) => {
  630. if (err != null) {
  631. return done(err)
  632. }
  633. this.projectId = projectId
  634. this.owner.makeTokenBased(this.projectId, err => {
  635. if (err != null) {
  636. return done(err)
  637. }
  638. this.owner.getProject(this.projectId, (err, project) => {
  639. if (err != null) {
  640. return done(err)
  641. }
  642. this.tokens = project.tokens
  643. done()
  644. })
  645. })
  646. }
  647. )
  648. })
  649. it('should allow the user to access project via read-only token url', function (done) {
  650. async.series(
  651. [
  652. cb =>
  653. tryEditorAccess(
  654. this.anon,
  655. this.projectId,
  656. expectErrorResponse.restricted.html,
  657. cb
  658. ),
  659. cb =>
  660. tryReadOnlyTokenAccess(
  661. this.anon,
  662. this.tokens.readOnly,
  663. (response, body) => {
  664. expect(response.statusCode).to.equal(200)
  665. },
  666. (response, body) => {
  667. expect(response.statusCode).to.equal(200)
  668. expect(body.redirect).to.equal(`/project/${this.projectId}`)
  669. expect(body.grantAnonymousAccess).to.equal('readOnly')
  670. },
  671. cb
  672. ),
  673. cb =>
  674. tryEditorAccess(
  675. this.anon,
  676. this.projectId,
  677. (response, body) => {
  678. expect(response.statusCode).to.equal(200)
  679. },
  680. cb
  681. ),
  682. cb =>
  683. tryAnonContentAccess(
  684. this.anon,
  685. this.projectId,
  686. this.tokens.readOnly,
  687. (response, body) => {
  688. expect(body.privilegeLevel).to.equal('readOnly')
  689. expect(body.isRestrictedUser).to.equal(true)
  690. expect(body.isTokenMember).to.equal(false)
  691. expect(body.isInvitedMember).to.equal(false)
  692. expect(body.project.owner).to.have.keys('_id')
  693. expect(body.project.owner).to.not.have.any.keys(
  694. 'email',
  695. 'first_name',
  696. 'last_name'
  697. )
  698. },
  699. cb
  700. ),
  701. ],
  702. done
  703. )
  704. })
  705. it('should allow the anonymous user to fetch the read-only token', function (done) {
  706. async.series(
  707. [
  708. cb =>
  709. tryReadOnlyTokenAccess(
  710. this.anon,
  711. this.tokens.readOnly,
  712. (response, body) => {
  713. expect(response.statusCode).to.equal(200)
  714. },
  715. (response, body) => {
  716. expect(response.statusCode).to.equal(200)
  717. expect(body.redirect).to.equal(`/project/${this.projectId}`)
  718. expect(body.grantAnonymousAccess).to.equal('readOnly')
  719. },
  720. cb
  721. ),
  722. cb => {
  723. tryFetchProjectTokens(
  724. this.anon,
  725. this.projectId,
  726. (error, response, body) => {
  727. expect(error).to.equal(null)
  728. expect(response.statusCode).to.equal(200)
  729. expect(body).to.include({ readOnly: this.tokens.readOnly })
  730. expect(body.readOnlyHashPrefix).to.exist
  731. expect(Object.keys(body).length).to.equal(2)
  732. cb()
  733. }
  734. )
  735. },
  736. ],
  737. done
  738. )
  739. })
  740. describe('made private again', function () {
  741. beforeEach(function (done) {
  742. this.owner.makePrivate(this.projectId, () => setTimeout(done, 1000))
  743. })
  744. it('should deny access to project', function (done) {
  745. async.series(
  746. [
  747. cb =>
  748. tryEditorAccess(
  749. this.anon,
  750. this.projectId,
  751. expectErrorResponse.restricted.html,
  752. cb
  753. ),
  754. // should not allow the user to access read-only token
  755. cb =>
  756. tryReadOnlyTokenAccess(
  757. this.anon,
  758. this.tokens.readOnly,
  759. (response, body) => {
  760. expect(response.statusCode).to.equal(200)
  761. },
  762. (response, body) => {
  763. expect(response.statusCode).to.equal(404)
  764. },
  765. cb
  766. ),
  767. // still no access
  768. cb =>
  769. tryEditorAccess(
  770. this.anon,
  771. this.projectId,
  772. expectErrorResponse.restricted.html,
  773. cb
  774. ),
  775. // should not allow the user to join the project
  776. cb =>
  777. tryAnonContentAccess(
  778. this.anon,
  779. this.projectId,
  780. this.tokens.readOnly,
  781. (response, body) => {
  782. expect(response.statusCode).to.equal(403)
  783. expect(body).to.equal('Forbidden')
  784. },
  785. cb
  786. ),
  787. ],
  788. done
  789. )
  790. })
  791. it('should deny access to access tokens', function (done) {
  792. tryFetchProjectTokens(this.anon, this.projectId, (error, response) => {
  793. expect(error).to.equal(null)
  794. expect(response.statusCode).to.equal(403)
  795. done()
  796. })
  797. })
  798. })
  799. })
  800. describe('read-and-write token', function () {
  801. beforeEach(function (done) {
  802. this.projectName = `token-rw-test${Math.random()}`
  803. this.owner.createProject(this.projectName, (err, projectId) => {
  804. if (err != null) {
  805. return done(err)
  806. }
  807. this.projectId = projectId
  808. this.owner.makeTokenBased(this.projectId, err => {
  809. if (err != null) {
  810. return done(err)
  811. }
  812. this.owner.getProject(this.projectId, (err, project) => {
  813. if (err != null) {
  814. return done(err)
  815. }
  816. this.tokens = project.tokens
  817. done()
  818. })
  819. })
  820. })
  821. })
  822. it('should allow the user to access project via read-and-write token url', function (done) {
  823. async.series(
  824. [
  825. // deny access before the token is used
  826. cb =>
  827. tryEditorAccess(
  828. this.other1,
  829. this.projectId,
  830. expectErrorResponse.restricted.html,
  831. cb
  832. ),
  833. // try token
  834. cb =>
  835. tryReadAndWriteTokenAccess(
  836. this.other1,
  837. this.tokens.readAndWrite,
  838. (response, body) => {
  839. expect(response.statusCode).to.equal(200)
  840. },
  841. (response, body) => {
  842. expect(response.statusCode).to.equal(200)
  843. expect(body.requireAccept.projectName).to.equal(
  844. this.projectName
  845. )
  846. },
  847. cb
  848. ),
  849. // deny access before the token is accepted
  850. cb =>
  851. tryEditorAccess(
  852. this.other1,
  853. this.projectId,
  854. expectErrorResponse.restricted.html,
  855. cb
  856. ),
  857. // accept token
  858. cb =>
  859. tryReadAndWriteTokenAccept(
  860. this.other1,
  861. this.tokens.readAndWrite,
  862. (response, body) => {
  863. expect(response.statusCode).to.equal(200)
  864. },
  865. (response, body) => {
  866. expect(response.statusCode).to.equal(200)
  867. expect(body.redirect).to.equal(`/project/${this.projectId}`)
  868. expect(body.tokenAccessGranted).to.equal('readAndWrite')
  869. },
  870. cb
  871. ),
  872. cb =>
  873. tryEditorAccess(
  874. this.other1,
  875. this.projectId,
  876. (response, body) => {
  877. expect(response.statusCode).to.equal(200)
  878. },
  879. cb
  880. ),
  881. cb =>
  882. tryContentAccess(
  883. this.other1,
  884. this.projectId,
  885. (response, body) => {
  886. expect(body.privilegeLevel).to.equal('readAndWrite')
  887. expect(body.isRestrictedUser).to.equal(false)
  888. expect(body.isTokenMember).to.equal(true)
  889. expect(body.isInvitedMember).to.equal(false)
  890. expect(body.project.owner).to.have.all.keys(
  891. '_id',
  892. 'email',
  893. 'first_name',
  894. 'last_name',
  895. 'privileges',
  896. 'signUpDate'
  897. )
  898. },
  899. cb
  900. ),
  901. ],
  902. done
  903. )
  904. })
  905. it('fetching access tokens returns an empty object', function (done) {
  906. async.series(
  907. [
  908. cb =>
  909. tryReadAndWriteTokenAccept(
  910. this.other1,
  911. this.tokens.readAndWrite,
  912. (response, body) => {
  913. expect(response.statusCode).to.equal(200)
  914. },
  915. (response, body) => {
  916. expect(response.statusCode).to.equal(200)
  917. expect(body.redirect).to.equal(`/project/${this.projectId}`)
  918. expect(body.tokenAccessGranted).to.equal('readAndWrite')
  919. },
  920. cb
  921. ),
  922. cb => {
  923. tryFetchProjectTokens(
  924. this.other1,
  925. this.projectId,
  926. (error, response, body) => {
  927. expect(error).to.equal(null)
  928. expect(response.statusCode).to.equal(200)
  929. expect(body).to.deep.equal({})
  930. cb()
  931. }
  932. )
  933. },
  934. ],
  935. done
  936. )
  937. })
  938. describe('upgrading from a read-only token', function () {
  939. beforeEach(function (done) {
  940. this.owner.createProject(
  941. `token-rw-upgrade-test${Math.random()}`,
  942. (err, projectId) => {
  943. if (err != null) {
  944. return done(err)
  945. }
  946. this.projectId = projectId
  947. this.owner.makeTokenBased(this.projectId, err => {
  948. if (err != null) {
  949. return done(err)
  950. }
  951. this.owner.getProject(this.projectId, (err, project) => {
  952. if (err != null) {
  953. return done(err)
  954. }
  955. this.tokens = project.tokens
  956. done()
  957. })
  958. })
  959. }
  960. )
  961. })
  962. it('should allow user to access project via read-only, then upgrade to read-write', function (done) {
  963. async.series(
  964. [
  965. // deny access before the token is used
  966. cb =>
  967. tryEditorAccess(
  968. this.other1,
  969. this.projectId,
  970. expectErrorResponse.restricted.html,
  971. cb
  972. ),
  973. cb => {
  974. // use read-only token
  975. tryReadOnlyTokenAccept(
  976. this.other1,
  977. this.tokens.readOnly,
  978. (response, body) => {
  979. expect(response.statusCode).to.equal(200)
  980. },
  981. (response, body) => {
  982. expect(response.statusCode).to.equal(200)
  983. expect(body.redirect).to.equal(`/project/${this.projectId}`)
  984. expect(body.tokenAccessGranted).to.equal('readOnly')
  985. },
  986. cb
  987. )
  988. },
  989. cb => {
  990. tryEditorAccess(
  991. this.other1,
  992. this.projectId,
  993. (response, body) => {
  994. expect(response.statusCode).to.equal(200)
  995. },
  996. cb
  997. )
  998. },
  999. cb => {
  1000. // allow content access read-only
  1001. tryContentAccess(
  1002. this.other1,
  1003. this.projectId,
  1004. (response, body) => {
  1005. expect(body.privilegeLevel).to.equal('readOnly')
  1006. expect(body.isRestrictedUser).to.equal(true)
  1007. expect(body.isTokenMember).to.equal(true)
  1008. expect(body.isInvitedMember).to.equal(false)
  1009. expect(body.project.owner).to.have.keys('_id')
  1010. expect(body.project.owner).to.not.have.any.keys(
  1011. 'email',
  1012. 'first_name',
  1013. 'last_name'
  1014. )
  1015. },
  1016. cb
  1017. )
  1018. },
  1019. //
  1020. // Then switch to read-write token
  1021. //
  1022. cb =>
  1023. tryReadAndWriteTokenAccept(
  1024. this.other1,
  1025. this.tokens.readAndWrite,
  1026. (response, body) => {
  1027. expect(response.statusCode).to.equal(200)
  1028. },
  1029. (response, body) => {
  1030. expect(response.statusCode).to.equal(200)
  1031. expect(body.redirect).to.equal(`/project/${this.projectId}`)
  1032. expect(body.tokenAccessGranted).to.equal('readAndWrite')
  1033. },
  1034. cb
  1035. ),
  1036. cb =>
  1037. tryEditorAccess(
  1038. this.other1,
  1039. this.projectId,
  1040. (response, body) => {
  1041. expect(response.statusCode).to.equal(200)
  1042. },
  1043. cb
  1044. ),
  1045. cb =>
  1046. tryContentAccess(
  1047. this.other1,
  1048. this.projectId,
  1049. (response, body) => {
  1050. expect(body.privilegeLevel).to.equal('readAndWrite')
  1051. expect(body.isRestrictedUser).to.equal(false)
  1052. expect(body.isTokenMember).to.equal(true)
  1053. expect(body.isInvitedMember).to.equal(false)
  1054. expect(body.project.owner).to.have.all.keys(
  1055. '_id',
  1056. 'email',
  1057. 'first_name',
  1058. 'last_name',
  1059. 'privileges',
  1060. 'signUpDate'
  1061. )
  1062. },
  1063. cb
  1064. ),
  1065. ],
  1066. done
  1067. )
  1068. })
  1069. })
  1070. describe('made private again', function () {
  1071. beforeEach(function (done) {
  1072. this.owner.makePrivate(this.projectId, () => setTimeout(done, 1000))
  1073. })
  1074. it('should deny access to project', function (done) {
  1075. async.series(
  1076. [
  1077. cb => {
  1078. tryEditorAccess(
  1079. this.other1,
  1080. this.projectId,
  1081. (response, body) => {},
  1082. cb
  1083. )
  1084. },
  1085. cb => {
  1086. tryReadAndWriteTokenAccess(
  1087. this.other1,
  1088. this.tokens.readAndWrite,
  1089. (response, body) => {
  1090. expect(response.statusCode).to.equal(200)
  1091. },
  1092. (response, body) => {
  1093. expect(response.statusCode).to.equal(404)
  1094. },
  1095. cb
  1096. )
  1097. },
  1098. cb => {
  1099. tryEditorAccess(
  1100. this.other1,
  1101. this.projectId,
  1102. expectErrorResponse.restricted.html,
  1103. cb
  1104. )
  1105. },
  1106. cb => {
  1107. tryContentAccess(
  1108. this.other1,
  1109. this.projectId,
  1110. (response, body) => {
  1111. expect(response.statusCode).to.equal(403)
  1112. expect(body).to.equal('Forbidden')
  1113. },
  1114. cb
  1115. )
  1116. },
  1117. ],
  1118. done
  1119. )
  1120. })
  1121. it('should deny access to access tokens', function (done) {
  1122. tryFetchProjectTokens(
  1123. this.other1,
  1124. this.projectId,
  1125. (error, response) => {
  1126. expect(error).to.equal(null)
  1127. expect(response.statusCode).to.equal(403)
  1128. done()
  1129. }
  1130. )
  1131. })
  1132. })
  1133. })
  1134. if (!settings.allowAnonymousReadAndWriteSharing) {
  1135. describe('anonymous read-and-write token, disabled', function () {
  1136. beforeEach(function (done) {
  1137. this.owner.createProject(
  1138. `token-anon-rw-test${Math.random()}`,
  1139. (err, projectId) => {
  1140. if (err != null) {
  1141. return done(err)
  1142. }
  1143. this.projectId = projectId
  1144. this.owner.makeTokenBased(this.projectId, err => {
  1145. if (err != null) {
  1146. return done(err)
  1147. }
  1148. this.owner.getProject(this.projectId, (err, project) => {
  1149. if (err != null) {
  1150. return done(err)
  1151. }
  1152. this.tokens = project.tokens
  1153. done()
  1154. })
  1155. })
  1156. }
  1157. )
  1158. })
  1159. it('should not allow the user to access read-and-write token', function (done) {
  1160. async.series(
  1161. [
  1162. cb =>
  1163. tryEditorAccess(
  1164. this.anon,
  1165. this.projectId,
  1166. expectErrorResponse.restricted.html,
  1167. cb
  1168. ),
  1169. cb =>
  1170. tryReadAndWriteTokenAccess(
  1171. this.anon,
  1172. this.tokens.readAndWrite,
  1173. (response, body) => {
  1174. expect(response.statusCode).to.equal(200)
  1175. },
  1176. (response, body) => {
  1177. expect(response.statusCode).to.equal(200)
  1178. expect(body).to.deep.equal({
  1179. redirect: '/restricted',
  1180. anonWriteAccessDenied: true,
  1181. })
  1182. },
  1183. cb
  1184. ),
  1185. cb =>
  1186. tryAnonContentAccess(
  1187. this.anon,
  1188. this.projectId,
  1189. this.tokens.readAndWrite,
  1190. (response, body) => {
  1191. expect(response.statusCode).to.equal(403)
  1192. expect(body).to.equal('Forbidden')
  1193. },
  1194. cb
  1195. ),
  1196. cb =>
  1197. this.anon.login((err, response, body) => {
  1198. expect(err).to.not.exist
  1199. expect(response.statusCode).to.equal(200)
  1200. expect(body.redir).to.equal(`/${this.tokens.readAndWrite}`)
  1201. cb()
  1202. }),
  1203. ],
  1204. done
  1205. )
  1206. })
  1207. it('should deny access to access tokens', function (done) {
  1208. tryFetchProjectTokens(this.anon, this.projectId, (error, response) => {
  1209. expect(error).to.equal(null)
  1210. expect(response.statusCode).to.equal(403)
  1211. done()
  1212. })
  1213. })
  1214. it('should require login if project does not exist', function (done) {
  1215. async.series(
  1216. [
  1217. // delete project
  1218. cb => {
  1219. this.owner.deleteProject(this.projectId, cb)
  1220. },
  1221. cb =>
  1222. tryReadAndWriteTokenAccess(
  1223. this.anon,
  1224. this.tokens.readAndWrite,
  1225. (response, body) => {
  1226. expect(response.statusCode).to.equal(200)
  1227. },
  1228. (response, body) => {
  1229. expect(response.statusCode).to.equal(200)
  1230. expect(body).to.deep.equal({
  1231. redirect: '/restricted',
  1232. anonWriteAccessDenied: true,
  1233. })
  1234. },
  1235. cb
  1236. ),
  1237. cb =>
  1238. this.anon.login((err, response, body) => {
  1239. expect(err).to.not.exist
  1240. expect(response.statusCode).to.equal(200)
  1241. expect(body.redir).to.equal(`/${this.tokens.readAndWrite}`)
  1242. cb()
  1243. }),
  1244. ],
  1245. done
  1246. )
  1247. })
  1248. it('should save URL hash in redirect', function (done) {
  1249. const urlFragment = '#123456'
  1250. const tokenWithUrlFragment = `${this.tokens.readAndWrite}${urlFragment}`
  1251. async.series(
  1252. [
  1253. cb =>
  1254. tryEditorAccess(
  1255. this.anon,
  1256. this.projectId,
  1257. expectErrorResponse.restricted.html,
  1258. cb
  1259. ),
  1260. cb =>
  1261. this.anon.request.get(
  1262. tokenWithUrlFragment,
  1263. (err, response, body) => {
  1264. if (err) {
  1265. return cb(err)
  1266. }
  1267. expect(response.statusCode).to.equal(200)
  1268. this.anon.request.post(
  1269. `${this.tokens.readAndWrite}/grant`,
  1270. {
  1271. json: {
  1272. token: this.tokens.readAndWrite,
  1273. tokenHashPrefix: urlFragment,
  1274. },
  1275. },
  1276. (err, response, body) => {
  1277. if (err) {
  1278. return cb(err)
  1279. }
  1280. expect(response.statusCode).to.equal(200)
  1281. expect(body).to.deep.equal({
  1282. redirect: '/restricted',
  1283. anonWriteAccessDenied: true,
  1284. })
  1285. cb()
  1286. }
  1287. )
  1288. }
  1289. ),
  1290. cb =>
  1291. tryAnonContentAccess(
  1292. this.anon,
  1293. this.projectId,
  1294. this.tokens.readAndWrite,
  1295. (response, body) => {
  1296. expect(response.statusCode).to.equal(403)
  1297. expect(body).to.equal('Forbidden')
  1298. },
  1299. cb
  1300. ),
  1301. cb =>
  1302. this.anon.login((err, response, body) => {
  1303. expect(err).to.not.exist
  1304. expect(response.statusCode).to.equal(200)
  1305. expect(body.redir).to.equal(`/${tokenWithUrlFragment}`)
  1306. cb()
  1307. }),
  1308. ],
  1309. done
  1310. )
  1311. })
  1312. })
  1313. } else {
  1314. describe('anonymous read-and-write token, enabled', function () {
  1315. beforeEach(function (done) {
  1316. this.owner.createProject(
  1317. `token-anon-rw-test${Math.random()}`,
  1318. (err, projectId) => {
  1319. if (err != null) {
  1320. return done(err)
  1321. }
  1322. this.projectId = projectId
  1323. this.owner.makeTokenBased(this.projectId, err => {
  1324. if (err != null) {
  1325. return done(err)
  1326. }
  1327. this.owner.getProject(this.projectId, (err, project) => {
  1328. if (err != null) {
  1329. return done(err)
  1330. }
  1331. this.tokens = project.tokens
  1332. done()
  1333. })
  1334. })
  1335. }
  1336. )
  1337. })
  1338. it('should allow the user to access project via read-and-write token url', function (done) {
  1339. async.series(
  1340. [
  1341. cb =>
  1342. tryEditorAccess(
  1343. this.anon,
  1344. this.projectId,
  1345. expectErrorResponse.restricted.html,
  1346. cb
  1347. ),
  1348. cb =>
  1349. tryReadAndWriteTokenAccess(
  1350. this.anon,
  1351. this.tokens.readAndWrite,
  1352. (response, body) => {
  1353. expect(response.statusCode).to.equal(200)
  1354. },
  1355. (response, body) => {
  1356. expect(response.statusCode).to.equal(200)
  1357. expect(body.redirect).to.equal(`/project/${this.projectId}`)
  1358. expect(body.grantAnonymousAccess).to.equal('readAndWrite')
  1359. },
  1360. cb
  1361. ),
  1362. cb =>
  1363. tryEditorAccess(
  1364. this.anon,
  1365. this.projectId,
  1366. (response, body) => {
  1367. expect(response.statusCode).to.equal(200)
  1368. },
  1369. cb
  1370. ),
  1371. cb =>
  1372. tryAnonContentAccess(
  1373. this.anon,
  1374. this.projectId,
  1375. this.tokens.readAndWrite,
  1376. (response, body) => {
  1377. expect(body.privilegeLevel).to.equal('readAndWrite')
  1378. },
  1379. cb
  1380. ),
  1381. ],
  1382. done
  1383. )
  1384. })
  1385. describe('made private again', function () {
  1386. beforeEach(function (done) {
  1387. this.owner.makePrivate(this.projectId, () => setTimeout(done, 1000))
  1388. })
  1389. it('should not allow the user to access read-and-write token', function (done) {
  1390. async.series(
  1391. [
  1392. cb =>
  1393. tryEditorAccess(
  1394. this.anon,
  1395. this.projectId,
  1396. expectErrorResponse.restricted.html,
  1397. cb
  1398. ),
  1399. cb =>
  1400. tryReadAndWriteTokenAccess(
  1401. this.anon,
  1402. this.tokens.readAndWrite,
  1403. (response, body) => {
  1404. expect(response.statusCode).to.equal(200)
  1405. },
  1406. (response, body) => {
  1407. expect(response.statusCode).to.equal(404)
  1408. },
  1409. cb
  1410. ),
  1411. cb =>
  1412. tryEditorAccess(
  1413. this.anon,
  1414. this.projectId,
  1415. expectErrorResponse.restricted.html,
  1416. cb
  1417. ),
  1418. cb =>
  1419. tryAnonContentAccess(
  1420. this.anon,
  1421. this.projectId,
  1422. this.tokens.readAndWrite,
  1423. (response, body) => {
  1424. expect(response.statusCode).to.equal(403)
  1425. expect(body).to.equal('Forbidden')
  1426. },
  1427. cb
  1428. ),
  1429. ],
  1430. done
  1431. )
  1432. })
  1433. it('should deny access to access tokens', function (done) {
  1434. tryFetchProjectTokens(
  1435. this.anon,
  1436. this.projectId,
  1437. (error, response) => {
  1438. expect(error).to.equal(null)
  1439. expect(response.statusCode).to.equal(403)
  1440. done()
  1441. }
  1442. )
  1443. })
  1444. })
  1445. it('should 404 if project does not exist', function (done) {
  1446. async.series(
  1447. [
  1448. // delete project
  1449. cb => {
  1450. this.owner.deleteProject(this.projectId, cb)
  1451. },
  1452. cb =>
  1453. tryReadAndWriteTokenAccess(
  1454. this.anon,
  1455. this.tokens.readAndWrite,
  1456. (response, body) => {
  1457. expect(response.statusCode).to.equal(200)
  1458. },
  1459. (response, body) => {
  1460. expect(response.statusCode).to.equal(200)
  1461. expect(body).to.deep.equal({
  1462. v1Import: {
  1463. status: 'mustLogin',
  1464. },
  1465. })
  1466. },
  1467. cb
  1468. ),
  1469. ],
  1470. done
  1471. )
  1472. })
  1473. })
  1474. }
  1475. describe('private overleaf project', function () {
  1476. beforeEach(function (done) {
  1477. this.owner.createProject('overleaf-import', (err, projectId) => {
  1478. expect(err).not.to.exist
  1479. this.projectId = projectId
  1480. this.owner.makeTokenBased(this.projectId, err => {
  1481. expect(err).not.to.exist
  1482. this.owner.getProject(this.projectId, (err, project) => {
  1483. expect(err).not.to.exist
  1484. this.tokens = project.tokens
  1485. this.owner.makePrivate(this.projectId, () => {
  1486. db.projects.updateOne(
  1487. { _id: project._id },
  1488. {
  1489. $set: {
  1490. overleaf: { id: 1234 },
  1491. },
  1492. },
  1493. err => {
  1494. expect(err).not.to.exist
  1495. done()
  1496. }
  1497. )
  1498. })
  1499. })
  1500. })
  1501. })
  1502. })
  1503. it('should only allow the owner access to the project', function (done) {
  1504. async.series(
  1505. [
  1506. // should redirect to canonical path, when owner uses read-write token
  1507. cb =>
  1508. tryReadAndWriteTokenAccess(
  1509. this.owner,
  1510. this.tokens.readAndWrite,
  1511. (response, body) => {
  1512. expect(response.statusCode).to.equal(200)
  1513. },
  1514. (response, body) => {
  1515. expect(response.statusCode).to.equal(200)
  1516. expect(response.body.redirect).to.equal(
  1517. `/project/${this.projectId}`
  1518. )
  1519. expect(response.body.higherAccess).to.equal(true)
  1520. },
  1521. cb
  1522. ),
  1523. cb =>
  1524. tryEditorAccess(
  1525. this.owner,
  1526. this.projectId,
  1527. (response, body) => {
  1528. expect(response.statusCode).to.equal(200)
  1529. },
  1530. cb
  1531. ),
  1532. cb =>
  1533. tryContentAccess(
  1534. this.owner,
  1535. this.projectId,
  1536. (response, body) => {
  1537. expect(body.privilegeLevel).to.equal('owner')
  1538. },
  1539. cb
  1540. ),
  1541. // non-owner should be denied access
  1542. cb =>
  1543. tryContentAccess(
  1544. this.other2,
  1545. this.projectId,
  1546. (response, body) => {
  1547. expect(response.statusCode).to.equal(403)
  1548. expect(body).to.equal('Forbidden')
  1549. },
  1550. cb
  1551. ),
  1552. ],
  1553. done
  1554. )
  1555. })
  1556. })
  1557. describe('private project, with higher access', function () {
  1558. beforeEach(function (done) {
  1559. this.owner.createProject(
  1560. `higher-access-test-${Math.random()}`,
  1561. (err, projectId) => {
  1562. expect(err).not.to.exist
  1563. this.projectId = projectId
  1564. this.owner.addUserToProject(
  1565. this.projectId,
  1566. this.other1,
  1567. 'readAndWrite',
  1568. err => {
  1569. expect(err).not.to.exist
  1570. this.owner.makeTokenBased(this.projectId, err => {
  1571. expect(err).not.to.exist
  1572. this.owner.getProject(this.projectId, (err, project) => {
  1573. expect(err).not.to.exist
  1574. this.tokens = project.tokens
  1575. this.owner.makePrivate(this.projectId, () => {
  1576. setTimeout(done, 1000)
  1577. })
  1578. })
  1579. })
  1580. }
  1581. )
  1582. }
  1583. )
  1584. })
  1585. it('should allow the user access to the project', function (done) {
  1586. async.series(
  1587. [
  1588. // should redirect to canonical path, when user uses read-write token
  1589. cb =>
  1590. tryReadAndWriteTokenAccess(
  1591. this.other1,
  1592. this.tokens.readAndWrite,
  1593. (response, body) => {
  1594. expect(response.statusCode).to.equal(200)
  1595. },
  1596. (response, body) => {
  1597. expect(response.statusCode).to.equal(200)
  1598. expect(response.body.redirect).to.equal(
  1599. `/project/${this.projectId}`
  1600. )
  1601. expect(response.body.higherAccess).to.equal(true)
  1602. },
  1603. cb
  1604. ),
  1605. // should redirect to canonical path, when user uses read-only token
  1606. cb =>
  1607. tryReadOnlyTokenAccess(
  1608. this.other1,
  1609. this.tokens.readOnly,
  1610. (response, body) => {
  1611. expect(response.statusCode).to.equal(200)
  1612. },
  1613. (response, body) => {
  1614. expect(response.statusCode).to.equal(200)
  1615. expect(response.body.redirect).to.equal(
  1616. `/project/${this.projectId}`
  1617. )
  1618. expect(response.body.higherAccess).to.equal(true)
  1619. },
  1620. cb
  1621. ),
  1622. // should allow the user access to the project
  1623. cb =>
  1624. tryEditorAccess(
  1625. this.other1,
  1626. this.projectId,
  1627. (response, body) => {
  1628. expect(response.statusCode).to.equal(200)
  1629. },
  1630. cb
  1631. ),
  1632. // should allow user to join the project
  1633. cb =>
  1634. tryContentAccess(
  1635. this.other1,
  1636. this.projectId,
  1637. (response, body) => {
  1638. expect(body.privilegeLevel).to.equal('readAndWrite')
  1639. expect(body.isRestrictedUser).to.equal(false)
  1640. expect(body.isTokenMember).to.equal(false)
  1641. expect(body.isInvitedMember).to.equal(true)
  1642. },
  1643. cb
  1644. ),
  1645. // should not allow a different user to join the project
  1646. cb =>
  1647. tryEditorAccess(
  1648. this.other2,
  1649. this.projectId,
  1650. expectErrorResponse.restricted.html,
  1651. cb
  1652. ),
  1653. cb =>
  1654. tryContentAccess(
  1655. this.other2,
  1656. this.projectId,
  1657. (response, body) => {
  1658. expect(response.statusCode).to.equal(403)
  1659. expect(body).to.equal('Forbidden')
  1660. },
  1661. cb
  1662. ),
  1663. ],
  1664. done
  1665. )
  1666. })
  1667. })
  1668. describe('deleted project', function () {
  1669. beforeEach(function (done) {
  1670. this.owner.createProject(
  1671. `delete-test${Math.random()}`,
  1672. (err, projectId) => {
  1673. if (err != null) {
  1674. return done(err)
  1675. }
  1676. this.projectId = projectId
  1677. this.owner.makeTokenBased(this.projectId, err => {
  1678. if (err != null) {
  1679. return done(err)
  1680. }
  1681. this.owner.getProject(this.projectId, (err, project) => {
  1682. if (err != null) {
  1683. return done(err)
  1684. }
  1685. this.tokens = project.tokens
  1686. done()
  1687. })
  1688. })
  1689. }
  1690. )
  1691. })
  1692. it('should 404', function (done) {
  1693. async.series(
  1694. [
  1695. // delete project
  1696. cb => {
  1697. this.owner.deleteProject(this.projectId, cb)
  1698. },
  1699. cb => {
  1700. // use read-only token
  1701. tryReadOnlyTokenAccess(
  1702. this.other1,
  1703. this.tokens.readOnly,
  1704. (response, body) => {
  1705. expect(response.statusCode).to.equal(200)
  1706. },
  1707. (response, body) => {
  1708. expect(response.statusCode).to.equal(404)
  1709. },
  1710. cb
  1711. )
  1712. },
  1713. cb => {
  1714. // use read-write token
  1715. tryReadAndWriteTokenAccess(
  1716. this.other1,
  1717. this.tokens.readAndWrite,
  1718. (response, body) => {
  1719. expect(response.statusCode).to.equal(200)
  1720. },
  1721. (response, body) => {
  1722. expect(response.statusCode).to.equal(404)
  1723. },
  1724. cb
  1725. )
  1726. },
  1727. ],
  1728. done
  1729. )
  1730. })
  1731. it('should deny access to access tokens with a 404', function (done) {
  1732. async.series(
  1733. [
  1734. // delete project
  1735. cb => {
  1736. this.owner.deleteProject(this.projectId, cb)
  1737. },
  1738. cb => {
  1739. // use read-only token
  1740. tryReadOnlyTokenAccess(
  1741. this.other1,
  1742. this.tokens.readOnly,
  1743. (response, body) => {
  1744. expect(response.statusCode).to.equal(200)
  1745. },
  1746. (response, body) => {
  1747. expect(response.statusCode).to.equal(404)
  1748. },
  1749. cb
  1750. )
  1751. },
  1752. cb => {
  1753. tryFetchProjectTokens(
  1754. this.other1,
  1755. this.projectId,
  1756. (error, response) => {
  1757. expect(error).to.equal(null)
  1758. expect(response.statusCode).to.equal(404)
  1759. cb()
  1760. }
  1761. )
  1762. },
  1763. ],
  1764. done
  1765. )
  1766. })
  1767. })
  1768. describe('link sharing changes', function () {
  1769. beforeEach(function () {
  1770. this.getAssignmentForUser = sinon.stub(
  1771. SplitTestHandler.promises,
  1772. 'getAssignmentForUser'
  1773. )
  1774. this.getAssignmentForUser.resolves({ variant: 'default' })
  1775. })
  1776. afterEach(function () {
  1777. this.getAssignmentForUser.restore()
  1778. })
  1779. describe('not a member of the project', function () {
  1780. beforeEach(function (done) {
  1781. this.projectName = `token-link-sharing-changes${Math.random()}`
  1782. this.owner.createProject(this.projectName, (err, projectId) => {
  1783. if (err != null) {
  1784. return done(err)
  1785. }
  1786. this.projectId = projectId
  1787. this.owner.makeTokenBased(this.projectId, err => {
  1788. if (err != null) {
  1789. return done(err)
  1790. }
  1791. this.owner.getProject(this.projectId, (err, project) => {
  1792. if (err != null) {
  1793. return done(err)
  1794. }
  1795. this.tokens = project.tokens
  1796. done()
  1797. })
  1798. })
  1799. })
  1800. })
  1801. it('should deny access', function (done) {
  1802. async.series(
  1803. [
  1804. cb => {
  1805. trySharingUpdatesPage(
  1806. this.other1,
  1807. this.projectId,
  1808. expectErrorResponse.restricted.html,
  1809. cb
  1810. )
  1811. },
  1812. cb => {
  1813. trySharingUpdatesJoin(
  1814. this.other1,
  1815. this.projectId,
  1816. expectErrorResponse.restricted.html,
  1817. cb
  1818. )
  1819. },
  1820. cb => {
  1821. trySharingUpdatesView(
  1822. this.other1,
  1823. this.projectId,
  1824. expectErrorResponse.restricted.html,
  1825. cb
  1826. )
  1827. },
  1828. ],
  1829. done
  1830. )
  1831. })
  1832. })
  1833. describe('read and write token member of project', function () {
  1834. beforeEach(function (done) {
  1835. this.projectName = `token-link-sharing-changes${Math.random()}`
  1836. this.owner.createProject(this.projectName, (err, projectId) => {
  1837. if (err != null) {
  1838. return done(err)
  1839. }
  1840. this.projectId = projectId
  1841. this.owner.makeTokenBased(this.projectId, err => {
  1842. if (err != null) {
  1843. return done(err)
  1844. }
  1845. this.owner.getProject(this.projectId, (err, project) => {
  1846. if (err != null) {
  1847. return done(err)
  1848. }
  1849. this.tokens = project.tokens
  1850. // must do token accept before split test enabled
  1851. // otherwise would be automatically added to named collaborators
  1852. tryReadAndWriteTokenAccept(
  1853. this.other1,
  1854. this.tokens.readAndWrite,
  1855. (response, body) => {
  1856. expect(response.statusCode).to.equal(200)
  1857. },
  1858. (response, body) => {
  1859. expect(response.statusCode).to.equal(200)
  1860. expect(body.redirect).to.equal(`/project/${this.projectId}`)
  1861. expect(body.tokenAccessGranted).to.equal('readAndWrite')
  1862. },
  1863. done
  1864. )
  1865. })
  1866. })
  1867. })
  1868. })
  1869. describe('link sharing changes test not active', function () {
  1870. it('should redirect to project, same permissions as before', function (done) {
  1871. async.series(
  1872. [
  1873. cb => {
  1874. trySharingUpdatesPage(
  1875. this.other1,
  1876. this.projectId,
  1877. expectRedirectToProject,
  1878. cb
  1879. )
  1880. },
  1881. cb => {
  1882. trySharingUpdatesJoin(
  1883. this.other1,
  1884. this.projectId,
  1885. expectRedirectToProject,
  1886. cb
  1887. )
  1888. },
  1889. cb => {
  1890. trySharingUpdatesView(
  1891. this.other1,
  1892. this.projectId,
  1893. expectRedirectToProject,
  1894. cb
  1895. )
  1896. },
  1897. cb => {
  1898. tryContentAccess(
  1899. this.other1,
  1900. this.projectId,
  1901. (response, body) => {
  1902. expect(body.privilegeLevel).to.equal('readAndWrite')
  1903. expect(body.isRestrictedUser).to.equal(false)
  1904. expect(body.isTokenMember).to.equal(true)
  1905. expect(body.isInvitedMember).to.equal(false)
  1906. expect(body.project.owner).to.have.all.keys(
  1907. '_id',
  1908. 'email',
  1909. 'first_name',
  1910. 'last_name',
  1911. 'privileges',
  1912. 'signUpDate'
  1913. )
  1914. },
  1915. cb
  1916. )
  1917. },
  1918. cb => {
  1919. tryEditorAccess(
  1920. this.other1,
  1921. this.projectId,
  1922. (response, body) => {
  1923. expect(response.statusCode).to.equal(200)
  1924. },
  1925. cb
  1926. )
  1927. },
  1928. ],
  1929. done
  1930. )
  1931. })
  1932. })
  1933. describe('link sharing changes test is active', function () {
  1934. beforeEach(function () {
  1935. this.getAssignmentForUser.resolves({ variant: 'active' })
  1936. })
  1937. it('should show sharing updates page', function (done) {
  1938. trySharingUpdatesPage(
  1939. this.other1,
  1940. this.projectId,
  1941. (response, body) => {
  1942. expect(response.statusCode).to.equal(200)
  1943. },
  1944. done
  1945. )
  1946. })
  1947. it('should allow join to named collaborator', function (done) {
  1948. async.series(
  1949. [
  1950. cb => {
  1951. trySharingUpdatesJoin(
  1952. this.other1,
  1953. this.projectId,
  1954. (response, body) => {
  1955. expect(response.statusCode).to.equal(204)
  1956. },
  1957. cb
  1958. )
  1959. },
  1960. cb => {
  1961. tryContentAccess(
  1962. this.other1,
  1963. this.projectId,
  1964. (response, body) => {
  1965. expect(body.privilegeLevel).to.equal('readAndWrite')
  1966. expect(body.isRestrictedUser).to.equal(false)
  1967. expect(body.isTokenMember).to.equal(false)
  1968. expect(body.isInvitedMember).to.equal(true) // now collaborator
  1969. expect(body.project.owner).to.have.all.keys(
  1970. '_id',
  1971. 'email',
  1972. 'first_name',
  1973. 'last_name',
  1974. 'privileges',
  1975. 'signUpDate'
  1976. )
  1977. },
  1978. cb
  1979. )
  1980. },
  1981. cb => {
  1982. tryEditorAccess(
  1983. this.other1,
  1984. this.projectId,
  1985. (response, body) => {
  1986. expect(response.statusCode).to.equal(200)
  1987. },
  1988. cb
  1989. )
  1990. },
  1991. ],
  1992. done
  1993. )
  1994. })
  1995. it('should allow move to anonymous viewer', function (done) {
  1996. async.series(
  1997. [
  1998. cb => {
  1999. trySharingUpdatesView(
  2000. this.other1,
  2001. this.projectId,
  2002. (response, body) => {
  2003. expect(response.statusCode).to.equal(204)
  2004. },
  2005. cb
  2006. )
  2007. },
  2008. cb => {
  2009. tryContentAccess(
  2010. this.other1,
  2011. this.projectId,
  2012. (response, body) => {
  2013. expect(body.privilegeLevel).to.equal('readOnly')
  2014. expect(body.isRestrictedUser).to.equal(true)
  2015. expect(body.isTokenMember).to.equal(true)
  2016. expect(body.isInvitedMember).to.equal(false)
  2017. expect(body.project.owner).to.have.keys('_id')
  2018. },
  2019. cb
  2020. )
  2021. },
  2022. cb => {
  2023. tryEditorAccess(
  2024. this.other1,
  2025. this.projectId,
  2026. (response, body) => {
  2027. expect(response.statusCode).to.equal(200)
  2028. },
  2029. cb
  2030. )
  2031. },
  2032. ],
  2033. done
  2034. )
  2035. })
  2036. })
  2037. })
  2038. describe('read-only token member of project', function () {
  2039. beforeEach(function (done) {
  2040. this.projectName = `token-link-sharing-changes${Math.random()}`
  2041. this.owner.createProject(this.projectName, (err, projectId) => {
  2042. if (err != null) {
  2043. return done(err)
  2044. }
  2045. this.projectId = projectId
  2046. this.owner.makeTokenBased(this.projectId, err => {
  2047. if (err != null) {
  2048. return done(err)
  2049. }
  2050. this.owner.getProject(this.projectId, (err, project) => {
  2051. if (err != null) {
  2052. return done(err)
  2053. }
  2054. this.tokens = project.tokens
  2055. tryReadOnlyTokenAccept(
  2056. this.other1,
  2057. this.tokens.readOnly,
  2058. (response, body) => {
  2059. expect(response.statusCode).to.equal(200)
  2060. },
  2061. (response, body) => {
  2062. expect(response.statusCode).to.equal(200)
  2063. expect(body.redirect).to.equal(`/project/${this.projectId}`)
  2064. expect(body.tokenAccessGranted).to.equal('readOnly')
  2065. },
  2066. done
  2067. )
  2068. })
  2069. })
  2070. })
  2071. })
  2072. describe('link sharing changes test is active', function () {
  2073. beforeEach(function () {
  2074. this.getAssignmentForUser.resolves({ variant: 'active' })
  2075. })
  2076. it('should redirect to project, same view permissions as before', function (done) {
  2077. async.series(
  2078. [
  2079. cb => {
  2080. trySharingUpdatesPage(
  2081. this.other1,
  2082. this.projectId,
  2083. expectRedirectToProject,
  2084. cb
  2085. )
  2086. },
  2087. cb => {
  2088. trySharingUpdatesJoin(
  2089. this.other1,
  2090. this.projectId,
  2091. expectRedirectToProject,
  2092. cb
  2093. )
  2094. },
  2095. cb => {
  2096. trySharingUpdatesView(
  2097. this.other1,
  2098. this.projectId,
  2099. expectRedirectToProject,
  2100. cb
  2101. )
  2102. },
  2103. cb => {
  2104. // allow content access read-only
  2105. tryContentAccess(
  2106. this.other1,
  2107. this.projectId,
  2108. (response, body) => {
  2109. expect(body.privilegeLevel).to.equal('readOnly')
  2110. expect(body.isRestrictedUser).to.equal(true)
  2111. expect(body.isTokenMember).to.equal(true)
  2112. expect(body.isInvitedMember).to.equal(false)
  2113. expect(body.project.owner).to.have.keys('_id')
  2114. expect(body.project.owner).to.not.have.any.keys(
  2115. 'email',
  2116. 'first_name',
  2117. 'last_name'
  2118. )
  2119. },
  2120. cb
  2121. )
  2122. },
  2123. cb => {
  2124. tryEditorAccess(
  2125. this.other1,
  2126. this.projectId,
  2127. (response, body) => {
  2128. expect(response.statusCode).to.equal(200)
  2129. },
  2130. cb
  2131. )
  2132. },
  2133. ],
  2134. done
  2135. )
  2136. })
  2137. })
  2138. })
  2139. })
  2140. })