TokenAccessTests.mjs 40 KB

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