TokenAccessTests.js 43 KB

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