TokenAccessControllerTests.coffee 32 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861
  1. should = require('chai').should()
  2. SandboxedModule = require('sandboxed-module')
  3. assert = require('assert')
  4. path = require('path')
  5. sinon = require('sinon')
  6. modulePath = path.join __dirname, "../../../../app/js/Features/TokenAccess/TokenAccessController"
  7. expect = require("chai").expect
  8. ObjectId = require("mongojs").ObjectId
  9. MockRequest = require('../helpers/MockRequest')
  10. MockResponse = require('../helpers/MockResponse')
  11. Errors = require "../../../../app/js/Features/Errors/Errors.js"
  12. describe "TokenAccessController", ->
  13. beforeEach ->
  14. @readOnlyToken = 'somereadonlytoken'
  15. @readAndWriteToken = '42somereadandwritetoken'
  16. @projectId = ObjectId()
  17. @ownerId = 'owner'
  18. @project =
  19. _id: @projectId
  20. publicAccesLevel: 'tokenBased'
  21. tokens:
  22. readOnly: @readOnlyToken
  23. readAndWrite: @readAndWriteToken
  24. owner_ref: @ownerId
  25. @userId = ObjectId()
  26. @TokenAccessController = SandboxedModule.require modulePath, requires:
  27. '../Project/ProjectController': @ProjectController = {}
  28. '../Authentication/AuthenticationController': @AuthenticationController = {}
  29. './TokenAccessHandler': @TokenAccessHandler = {
  30. getV1DocInfo: sinon.stub().yields(null, {
  31. allow: true
  32. exists: true
  33. exported: false
  34. })
  35. }
  36. 'logger-sharelatex': {log: sinon.stub(), err: sinon.stub()}
  37. 'settings-sharelatex': {
  38. overleaf:
  39. host: 'http://overleaf.test:5000'
  40. }
  41. '../V1/V1Api': @V1Api = {
  42. request: sinon.stub().callsArgWith(1, null, {}, { allow: true })
  43. }
  44. @AuthenticationController.getLoggedInUserId = sinon.stub().returns(@userId.toString())
  45. describe 'readAndWriteToken', ->
  46. beforeEach ->
  47. describe 'when all goes well', ->
  48. beforeEach ->
  49. @req = new MockRequest()
  50. @res = new MockResponse()
  51. @next = sinon.stub()
  52. @req.params['read_and_write_token'] = @readAndWriteToken
  53. @TokenAccessHandler.findProjectWithReadAndWriteToken = sinon.stub()
  54. .callsArgWith(1, null, @project, true)
  55. @TokenAccessHandler.addReadAndWriteUserToProject = sinon.stub()
  56. .callsArgWith(2, null)
  57. @ProjectController.loadEditor = sinon.stub()
  58. @AuthenticationController._setRedirectInSession = sinon.stub()
  59. @TokenAccessController.readAndWriteToken @req, @res, @next
  60. it 'should try to find a project with this token', (done) ->
  61. expect(@TokenAccessHandler.findProjectWithReadAndWriteToken.callCount)
  62. .to.equal 1
  63. expect(@TokenAccessHandler.findProjectWithReadAndWriteToken.calledWith(@readAndWriteToken))
  64. .to.equal true
  65. done()
  66. it 'should add the user to the project with read-write access', (done) ->
  67. expect(@TokenAccessHandler.addReadAndWriteUserToProject.callCount)
  68. .to.equal 1
  69. expect(@TokenAccessHandler.addReadAndWriteUserToProject.calledWith(
  70. @userId.toString(), @projectId
  71. ))
  72. .to.equal true
  73. done()
  74. it 'should pass control to loadEditor', (done) ->
  75. expect(@req.params.Project_id).to.equal @projectId.toString()
  76. expect(@ProjectController.loadEditor.callCount).to.equal 1
  77. expect(@ProjectController.loadEditor.calledWith(@req, @res, @next)).to.equal true
  78. done()
  79. describe 'when the user is already the owner', ->
  80. beforeEach ->
  81. @req = new MockRequest()
  82. @res = new MockResponse()
  83. @next = sinon.stub()
  84. @req.params['read_and_write_token'] = @readAndWriteToken
  85. @project.owner_ref = @userId
  86. @TokenAccessHandler.findProjectWithReadAndWriteToken = sinon.stub()
  87. .callsArgWith(1, null, @project, true)
  88. @TokenAccessHandler.addReadAndWriteUserToProject = sinon.stub()
  89. .callsArgWith(2, null)
  90. @ProjectController.loadEditor = sinon.stub()
  91. @TokenAccessController.readAndWriteToken @req, @res, @next
  92. it 'should try to find a project with this token', (done) ->
  93. expect(@TokenAccessHandler.findProjectWithReadAndWriteToken.callCount)
  94. .to.equal 1
  95. expect(@TokenAccessHandler.findProjectWithReadAndWriteToken.calledWith(@readAndWriteToken))
  96. .to.equal true
  97. done()
  98. it 'should not add the user to the project with read-write access', (done) ->
  99. expect(@TokenAccessHandler.addReadAndWriteUserToProject.callCount)
  100. .to.equal 0
  101. done()
  102. it 'should pass control to loadEditor', (done) ->
  103. expect(@req.params.Project_id).to.equal @projectId.toString()
  104. expect(@ProjectController.loadEditor.callCount).to.equal 1
  105. expect(@ProjectController.loadEditor.calledWith(@req, @res, @next)).to.equal true
  106. done()
  107. describe 'when there is no user', ->
  108. beforeEach ->
  109. @AuthenticationController.getLoggedInUserId =
  110. sinon.stub().returns(null)
  111. describe 'when anonymous read-write access is enabled', ->
  112. beforeEach ->
  113. @TokenAccessHandler.ANONYMOUS_READ_AND_WRITE_ENABLED = true
  114. @req = new MockRequest()
  115. @res = new MockResponse()
  116. @next = sinon.stub()
  117. @req.params['read_and_write_token'] = @readAndWriteToken
  118. @TokenAccessHandler.findProjectWithReadAndWriteToken = sinon.stub()
  119. .callsArgWith(1, null, @project, true)
  120. @TokenAccessHandler.addReadAndWriteUserToProject = sinon.stub()
  121. .callsArgWith(2, null)
  122. @ProjectController.loadEditor = sinon.stub()
  123. @TokenAccessHandler.grantSessionTokenAccess = sinon.stub()
  124. @TokenAccessController.readAndWriteToken @req, @res, @next
  125. it 'should not add the user to the project with read-write access', (done) ->
  126. expect(@TokenAccessHandler.addReadAndWriteUserToProject.callCount)
  127. .to.equal 0
  128. done()
  129. it 'should give the user session token access', (done) ->
  130. expect(@TokenAccessHandler.grantSessionTokenAccess.callCount)
  131. .to.equal 1
  132. expect(@TokenAccessHandler.grantSessionTokenAccess.calledWith(
  133. @req, @projectId, @readAndWriteToken
  134. ))
  135. .to.equal true
  136. done()
  137. it 'should pass control to loadEditor', (done) ->
  138. expect(@req.params.Project_id).to.equal @projectId.toString()
  139. expect(@ProjectController.loadEditor.callCount).to.equal 1
  140. expect(@ProjectController.loadEditor.calledWith(@req, @res, @next)).to.equal true
  141. done()
  142. describe 'when anonymous read-write access is not enabled', ->
  143. beforeEach ->
  144. @TokenAccessHandler.ANONYMOUS_READ_AND_WRITE_ENABLED = false
  145. @req = new MockRequest()
  146. @res = new MockResponse()
  147. @res.redirect = sinon.stub()
  148. @next = sinon.stub()
  149. @req.params['read_and_write_token'] = @readAndWriteToken
  150. @TokenAccessHandler.findProjectWithReadAndWriteToken = sinon.stub()
  151. .callsArgWith(1, null, @project, true)
  152. @TokenAccessHandler.addReadAndWriteUserToProject = sinon.stub()
  153. .callsArgWith(2, null)
  154. @ProjectController.loadEditor = sinon.stub()
  155. @TokenAccessHandler.grantSessionTokenAccess = sinon.stub()
  156. @AuthenticationController._setRedirectInSession = sinon.stub()
  157. @TokenAccessController.readAndWriteToken @req, @res, @next
  158. it 'should not add the user to the project with read-write access', (done) ->
  159. expect(@TokenAccessHandler.addReadAndWriteUserToProject.callCount)
  160. .to.equal 0
  161. done()
  162. it 'should give the user session token access', (done) ->
  163. expect(@TokenAccessHandler.grantSessionTokenAccess.callCount)
  164. .to.equal 0
  165. done()
  166. it 'should not pass control to loadEditor', (done) ->
  167. expect(@ProjectController.loadEditor.callCount).to.equal 0
  168. expect(@ProjectController.loadEditor.calledWith(@req, @res, @next)).to.equal false
  169. done()
  170. it 'should set redirect in session', (done) ->
  171. expect(@AuthenticationController._setRedirectInSession.callCount).to.equal 1
  172. expect(@AuthenticationController._setRedirectInSession.calledWith(@req)).to.equal true
  173. done()
  174. it 'should redirect to restricted page', (done) ->
  175. expect(@res.redirect.callCount).to.equal 1
  176. expect(@res.redirect.calledWith('/restricted')).to.equal true
  177. done()
  178. describe 'when findProject produces an error', ->
  179. beforeEach ->
  180. @req = new MockRequest()
  181. @res = new MockResponse()
  182. @next = sinon.stub()
  183. @req.params['read_and_write_token'] = @readAndWriteToken
  184. @TokenAccessHandler.findProjectWithReadAndWriteToken = sinon.stub()
  185. .callsArgWith(1, new Error('woops'))
  186. @TokenAccessHandler.addReadAndWriteUserToProject = sinon.stub()
  187. .callsArgWith(2, null)
  188. @ProjectController.loadEditor = sinon.stub()
  189. @TokenAccessController.readAndWriteToken @req, @res, @next
  190. it 'should try to find a project with this token', (done) ->
  191. expect(@TokenAccessHandler.findProjectWithReadAndWriteToken.callCount)
  192. .to.equal 1
  193. expect(@TokenAccessHandler.findProjectWithReadAndWriteToken.calledWith(@readAndWriteToken))
  194. .to.equal true
  195. done()
  196. it 'should not add the user to the project with read-write access', (done) ->
  197. expect(@TokenAccessHandler.addReadAndWriteUserToProject.callCount)
  198. .to.equal 0
  199. done()
  200. it 'should not pass control to loadEditor', (done) ->
  201. expect(@ProjectController.loadEditor.callCount).to.equal 0
  202. expect(@ProjectController.loadEditor.calledWith(@req, @res, @next)).to.equal false
  203. done()
  204. it 'should call next with an error', (done) ->
  205. expect(@next.callCount).to.equal 1
  206. expect(@next.lastCall.args[0]).to.be.instanceof Error
  207. done()
  208. describe 'when findProject does not find a project', ->
  209. beforeEach ->
  210. describe 'when user is present', ->
  211. beforeEach ->
  212. @AuthenticationController.getLoggedInUserId =
  213. sinon.stub().returns(@userId.toString())
  214. describe 'when project does not exist', ->
  215. beforeEach ->
  216. @req = new MockRequest()
  217. @req.url = '/123abc'
  218. @res = new MockResponse()
  219. @res.redirect = sinon.stub()
  220. @next = sinon.stub()
  221. @req.params['read_and_write_token'] = '123abc'
  222. @TokenAccessHandler.findProjectWithReadAndWriteToken = sinon.stub()
  223. .callsArgWith(1, null, null, false)
  224. describe 'when project was not exported from v1', ->
  225. beforeEach ->
  226. @TokenAccessHandler.checkV1ProjectExported = sinon.stub()
  227. .callsArgWith(1, null, false)
  228. @TokenAccessController.readAndWriteToken @req, @res, @next
  229. it 'should redirect to v1', (done) ->
  230. expect(@res.redirect.callCount).to.equal 1
  231. expect(@res.redirect.calledWith(
  232. 302,
  233. '/sign_in_to_v1?return_to=/123abc'
  234. )).to.equal true
  235. done()
  236. describe 'when project was exported from v1', ->
  237. beforeEach ->
  238. @TokenAccessHandler.checkV1ProjectExported = sinon.stub()
  239. .callsArgWith(1, null, false)
  240. @TokenAccessController.readAndWriteToken @req, @res, @next
  241. it 'should call next with a not-found error', (done) ->
  242. expect(@next.callCount).to.equal 0
  243. done()
  244. describe 'when token access is off, but user has higher access anyway', ->
  245. beforeEach ->
  246. @req = new MockRequest()
  247. @res = new MockResponse()
  248. @res.redirect = sinon.stub()
  249. @next = sinon.stub()
  250. @req.params['read_and_write_token'] = @readAndWriteToken
  251. @TokenAccessHandler.findProjectWithReadAndWriteToken = sinon.stub()
  252. .callsArgWith(1, null, null, true)
  253. @TokenAccessHandler.findProjectWithHigherAccess =
  254. sinon.stub()
  255. .callsArgWith(2, null, @project)
  256. @TokenAccessHandler.addReadAndWriteUserToProject = sinon.stub()
  257. .callsArgWith(2, null)
  258. @ProjectController.loadEditor = sinon.stub()
  259. @TokenAccessController.readAndWriteToken @req, @res, @next
  260. it 'should try to find a project with this token', (done) ->
  261. expect(@TokenAccessHandler.findProjectWithReadAndWriteToken.callCount)
  262. .to.equal 1
  263. expect(@TokenAccessHandler.findProjectWithReadAndWriteToken
  264. .calledWith(@readAndWriteToken)
  265. ).to.equal true
  266. done()
  267. it 'should check if user has higher access to the token project', (done) ->
  268. expect(
  269. @TokenAccessHandler.findProjectWithHigherAccess.callCount
  270. ).to.equal 1
  271. done()
  272. it 'should not add the user to the project with read-write access', (done) ->
  273. expect(@TokenAccessHandler.addReadAndWriteUserToProject.callCount)
  274. .to.equal 0
  275. done()
  276. it 'should not pass control to loadEditor', (done) ->
  277. expect(@ProjectController.loadEditor.callCount).to.equal 0
  278. expect(@ProjectController.loadEditor.calledWith(@req, @res, @next)).to.equal false
  279. done()
  280. it 'should not call next with a not-found error', (done) ->
  281. expect(@next.callCount).to.equal 0
  282. done()
  283. it 'should redirect to the canonical project url', (done) ->
  284. expect(@res.redirect.callCount).to.equal 1
  285. expect(@res.redirect.calledWith(302, "/project/#{@project._id}")).to.equal true
  286. done()
  287. describe 'when higher access is not available', ->
  288. beforeEach ->
  289. @req = new MockRequest()
  290. @res = new MockResponse()
  291. @next = sinon.stub()
  292. @req.params['read_and_write_token'] = @readAndWriteToken
  293. @TokenAccessHandler.findProjectWithReadAndWriteToken = sinon.stub()
  294. .callsArgWith(1, null, null, true)
  295. @TokenAccessHandler.findProjectWithHigherAccess =
  296. sinon.stub()
  297. .callsArgWith(2, null, null)
  298. @TokenAccessHandler.addReadAndWriteUserToProject = sinon.stub()
  299. .callsArgWith(2, null)
  300. @ProjectController.loadEditor = sinon.stub()
  301. @TokenAccessController.readAndWriteToken @req, @res, @next
  302. it 'should try to find a project with this token', (done) ->
  303. expect(@TokenAccessHandler.findProjectWithReadAndWriteToken.callCount)
  304. .to.equal 1
  305. expect(@TokenAccessHandler.findProjectWithReadAndWriteToken.calledWith(
  306. @readAndWriteToken
  307. )).to.equal true
  308. done()
  309. it 'should check if user has higher access to the token project', (done) ->
  310. expect(
  311. @TokenAccessHandler.findProjectWithHigherAccess.callCount
  312. ).to.equal 1
  313. done()
  314. it 'should not add the user to the project with read-write access', (done) ->
  315. expect(@TokenAccessHandler.addReadAndWriteUserToProject.callCount)
  316. .to.equal 0
  317. done()
  318. it 'should not pass control to loadEditor', (done) ->
  319. expect(@ProjectController.loadEditor.callCount).to.equal 0
  320. expect(@ProjectController.loadEditor.calledWith(@req, @res, @next)).to.equal false
  321. done()
  322. it 'should call next with a not-found error', (done) ->
  323. expect(@next.callCount).to.equal 1
  324. expect(@next.lastCall.args[0]).to.be.instanceof Error
  325. done()
  326. describe 'when adding user to project produces an error', ->
  327. beforeEach ->
  328. @req = new MockRequest()
  329. @res = new MockResponse()
  330. @next = sinon.stub()
  331. @req.params['read_and_write_token'] = @readAndWriteToken
  332. @TokenAccessHandler.findProjectWithReadAndWriteToken = sinon.stub()
  333. .callsArgWith(1, null, @project, true)
  334. @TokenAccessHandler.addReadAndWriteUserToProject = sinon.stub()
  335. .callsArgWith(2, new Error('woops'))
  336. @ProjectController.loadEditor = sinon.stub()
  337. @TokenAccessController.readAndWriteToken @req, @res, @next
  338. it 'should try to find a project with this token', (done) ->
  339. expect(@TokenAccessHandler.findProjectWithReadAndWriteToken.callCount)
  340. .to.equal 1
  341. expect(@TokenAccessHandler.findProjectWithReadAndWriteToken.calledWith(@readAndWriteToken))
  342. .to.equal true
  343. done()
  344. it 'should add the user to the project with read-write access', (done) ->
  345. expect(@TokenAccessHandler.addReadAndWriteUserToProject.callCount)
  346. .to.equal 1
  347. expect(@TokenAccessHandler.addReadAndWriteUserToProject.calledWith(
  348. @userId.toString(), @projectId
  349. ))
  350. .to.equal true
  351. done()
  352. it 'should not pass control to loadEditor', (done) ->
  353. expect(@ProjectController.loadEditor.callCount).to.equal 0
  354. expect(@ProjectController.loadEditor.calledWith(@req, @res, @next)).to.equal false
  355. done()
  356. it 'should call next with an error', (done) ->
  357. expect(@next.callCount).to.equal 1
  358. expect(@next.lastCall.args[0]).to.be.instanceof Error
  359. done()
  360. describe 'readOnlyToken', ->
  361. beforeEach ->
  362. @TokenAccessHandler.checkV1Access = sinon.stub().callsArgWith(1, null, true)
  363. describe 'when access not allowed by v1 api', ->
  364. beforeEach ->
  365. @req = new MockRequest()
  366. @res = new MockResponse()
  367. @res.redirect = sinon.stub()
  368. @next = sinon.stub()
  369. @TokenAccessHandler.findProjectWithReadOnlyToken = sinon.stub()
  370. .callsArgWith(1, null, @project, true)
  371. @TokenAccessHandler.getV1DocInfo = sinon.stub().yields(null, {
  372. allow: false
  373. exists: true
  374. exported: false
  375. published_path: 'doc-url'
  376. })
  377. @TokenAccessController.readOnlyToken @req, @res, @next
  378. it 'should redirect to doc-url', ->
  379. expect(@res.redirect.calledWith('doc-url')).to.equal true
  380. describe 'with a user', ->
  381. beforeEach ->
  382. @AuthenticationController.getLoggedInUserId = sinon.stub().returns(@userId.toString())
  383. describe 'when all goes well', ->
  384. beforeEach ->
  385. @req = new MockRequest()
  386. @res = new MockResponse()
  387. @next = sinon.stub()
  388. @req.params['read_only_token'] = @readOnlyToken
  389. @TokenAccessHandler.findProjectWithReadOnlyToken = sinon.stub()
  390. .callsArgWith(1, null, @project, true)
  391. @TokenAccessHandler.addReadOnlyUserToProject = sinon.stub()
  392. .callsArgWith(2, null)
  393. @ProjectController.loadEditor = sinon.stub()
  394. @TokenAccessController.readOnlyToken @req, @res, @next
  395. it 'should try to find a project with this token', (done) ->
  396. expect(@TokenAccessHandler.findProjectWithReadOnlyToken.callCount)
  397. .to.equal 1
  398. expect(@TokenAccessHandler.findProjectWithReadOnlyToken.calledWith(@readOnlyToken))
  399. .to.equal true
  400. done()
  401. it 'should add the user to the project with read-only access', (done) ->
  402. expect(@TokenAccessHandler.addReadOnlyUserToProject.callCount)
  403. .to.equal 1
  404. expect(@TokenAccessHandler.addReadOnlyUserToProject.calledWith(
  405. @userId.toString(), @projectId
  406. ))
  407. .to.equal true
  408. done()
  409. it 'should pass control to loadEditor', (done) ->
  410. expect(@req.params.Project_id).to.equal @projectId.toString()
  411. expect(@ProjectController.loadEditor.callCount).to.equal 1
  412. expect(@ProjectController.loadEditor.calledWith(@req, @res, @next)).to.equal true
  413. done()
  414. describe 'when the user is already the owner', ->
  415. beforeEach ->
  416. @req = new MockRequest()
  417. @res = new MockResponse()
  418. @next = sinon.stub()
  419. @req.params['read_only_token'] = @readOnlyToken
  420. @project.owner_ref = @userId
  421. @TokenAccessHandler.findProjectWithReadOnlyToken = sinon.stub()
  422. .callsArgWith(1, null, @project, true)
  423. @TokenAccessHandler.addReadOnlyUserToProject = sinon.stub()
  424. .callsArgWith(2, null)
  425. @ProjectController.loadEditor = sinon.stub()
  426. @TokenAccessController.readOnlyToken @req, @res, @next
  427. it 'should try to find a project with this token', (done) ->
  428. expect(@TokenAccessHandler.findProjectWithReadOnlyToken.callCount)
  429. .to.equal 1
  430. expect(@TokenAccessHandler.findProjectWithReadOnlyToken.calledWith(@readOnlyToken))
  431. .to.equal true
  432. done()
  433. it 'should not add the user to the project with read-only access', (done) ->
  434. expect(@TokenAccessHandler.addReadOnlyUserToProject.callCount)
  435. .to.equal 0
  436. done()
  437. it 'should pass control to loadEditor', (done) ->
  438. expect(@req.params.Project_id).to.equal @projectId.toString()
  439. expect(@ProjectController.loadEditor.callCount).to.equal 1
  440. expect(@ProjectController.loadEditor.calledWith(@req, @res, @next)).to.equal true
  441. done()
  442. describe 'when findProject produces an error', ->
  443. beforeEach ->
  444. @req = new MockRequest()
  445. @res = new MockResponse()
  446. @next = sinon.stub()
  447. @req.params['read_only_token'] = @readOnlyToken
  448. @TokenAccessHandler.findProjectWithReadOnlyToken = sinon.stub()
  449. .callsArgWith(1, new Error('woops'))
  450. @TokenAccessHandler.addReadOnlyUserToProject = sinon.stub()
  451. .callsArgWith(2, null)
  452. @ProjectController.loadEditor = sinon.stub()
  453. @TokenAccessController.readOnlyToken @req, @res, @next
  454. it 'should try to find a project with this token', (done) ->
  455. expect(@TokenAccessHandler.findProjectWithReadOnlyToken.callCount)
  456. .to.equal 1
  457. expect(@TokenAccessHandler.findProjectWithReadOnlyToken.calledWith(@readOnlyToken))
  458. .to.equal true
  459. done()
  460. it 'should not add the user to the project with read-only access', (done) ->
  461. expect(@TokenAccessHandler.addReadOnlyUserToProject.callCount)
  462. .to.equal 0
  463. done()
  464. it 'should not pass control to loadEditor', (done) ->
  465. expect(@ProjectController.loadEditor.callCount).to.equal 0
  466. expect(@ProjectController.loadEditor.calledWith(@req, @res, @next)).to.equal false
  467. done()
  468. it 'should call next with an error', (done) ->
  469. expect(@next.callCount).to.equal 1
  470. expect(@next.lastCall.args[0]).to.be.instanceof Error
  471. done()
  472. describe 'when findProject does not find a project', ->
  473. describe 'when project does not exist', ->
  474. beforeEach ->
  475. @req = new MockRequest()
  476. @res = new MockResponse()
  477. @res.redirect = sinon.stub()
  478. @next = sinon.stub()
  479. @req.params['read_only_token'] = 'abcd'
  480. @TokenAccessHandler.findProjectWithReadOnlyToken = sinon.stub()
  481. .callsArgWith(1, null, null, false)
  482. @TokenAccessHandler.checkV1ProjectExported = sinon.stub()
  483. .callsArgWith(1, null, false)
  484. @TokenAccessController.readOnlyToken @req, @res, @next
  485. it 'should redirect to v1', (done) ->
  486. expect(@res.redirect.callCount).to.equal 1
  487. expect(@res.redirect.calledWith(
  488. 302,
  489. '/sign_in_to_v1?return_to=/read/abcd'
  490. )).to.equal true
  491. done()
  492. describe 'when project was exported from v1', ->
  493. beforeEach ->
  494. @req = new MockRequest()
  495. @res = new MockResponse()
  496. @res.redirect = sinon.stub()
  497. @next = sinon.stub()
  498. @req.params['read_only_token'] = 'abcd'
  499. @TokenAccessHandler.findProjectWithReadOnlyToken = sinon.stub()
  500. .callsArgWith(1, null, null, false)
  501. @TokenAccessHandler.getV1DocInfo = sinon.stub().yields(null, {
  502. allow: true
  503. exists: true
  504. exported: true
  505. })
  506. @TokenAccessController.readOnlyToken @req, @res, @next
  507. it 'should call next with a not-found error', (done) ->
  508. expect(@next.callCount).to.equal 1
  509. done()
  510. describe 'when token access is off, but user has higher access anyway', ->
  511. beforeEach ->
  512. @req = new MockRequest()
  513. @res = new MockResponse()
  514. @res.redirect = sinon.stub()
  515. @next = sinon.stub()
  516. @req.params['read_and_write_token'] = @readAndWriteToken
  517. @TokenAccessHandler.findProjectWithReadAndWriteToken = sinon.stub()
  518. .callsArgWith(1, null, null, true)
  519. @TokenAccessHandler.findProjectWithHigherAccess =
  520. sinon.stub()
  521. .callsArgWith(2, null, @project)
  522. @TokenAccessHandler.addReadAndWriteUserToProject = sinon.stub()
  523. .callsArgWith(2, null)
  524. @ProjectController.loadEditor = sinon.stub()
  525. @TokenAccessController.readAndWriteToken @req, @res, @next
  526. it 'should try to find a project with this token', (done) ->
  527. expect(@TokenAccessHandler.findProjectWithReadAndWriteToken.callCount)
  528. .to.equal 1
  529. expect(@TokenAccessHandler.findProjectWithReadAndWriteToken.calledWith(@readAndWriteToken))
  530. .to.equal true
  531. done()
  532. it 'should check if user has higher access to the token project', (done) ->
  533. expect(
  534. @TokenAccessHandler.findProjectWithHigherAccess.callCount
  535. ).to.equal 1
  536. done()
  537. it 'should not add the user to the project with read-write access', (done) ->
  538. expect(@TokenAccessHandler.addReadAndWriteUserToProject.callCount)
  539. .to.equal 0
  540. done()
  541. it 'should not pass control to loadEditor', (done) ->
  542. expect(@ProjectController.loadEditor.callCount).to.equal 0
  543. expect(@ProjectController.loadEditor.calledWith(@req, @res, @next)).to.equal false
  544. done()
  545. it 'should not call next with a not-found error', (done) ->
  546. expect(@next.callCount).to.equal 0
  547. done()
  548. it 'should redirect to the canonical project url', (done) ->
  549. expect(@res.redirect.callCount).to.equal 1
  550. expect(@res.redirect.calledWith(302, "/project/#{@project._id}")).to.equal true
  551. done()
  552. describe 'when higher access is not available', ->
  553. beforeEach ->
  554. @req = new MockRequest()
  555. @res = new MockResponse()
  556. @next = sinon.stub()
  557. @req.params['read_and_write_token'] = @readAndWriteToken
  558. @TokenAccessHandler.findProjectWithReadAndWriteToken = sinon.stub()
  559. .callsArgWith(1, null, null, true)
  560. @TokenAccessHandler.findProjectWithHigherAccess =
  561. sinon.stub()
  562. .callsArgWith(2, null, null)
  563. @TokenAccessHandler.addReadOnlyUserToProject = sinon.stub()
  564. .callsArgWith(2, null)
  565. @ProjectController.loadEditor = sinon.stub()
  566. @TokenAccessController.readAndWriteToken @req, @res, @next
  567. it 'should try to find a project with this token', (done) ->
  568. expect(@TokenAccessHandler.findProjectWithReadAndWriteToken.callCount)
  569. .to.equal 1
  570. expect(@TokenAccessHandler.findProjectWithReadAndWriteToken.calledWith(
  571. @readAndWriteToken
  572. )).to.equal true
  573. done()
  574. it 'should check if user has higher access to the token project', (done) ->
  575. expect(
  576. @TokenAccessHandler.findProjectWithHigherAccess.callCount
  577. ).to.equal 1
  578. done()
  579. it 'should not add the user to the project with read-write access', (done) ->
  580. expect(@TokenAccessHandler.addReadOnlyUserToProject.callCount)
  581. .to.equal 0
  582. done()
  583. it 'should not pass control to loadEditor', (done) ->
  584. expect(@ProjectController.loadEditor.callCount).to.equal 0
  585. expect(@ProjectController.loadEditor.calledWith(@req, @res, @next)).to.equal false
  586. done()
  587. it 'should call next with a not-found error', (done) ->
  588. expect(@next.callCount).to.equal 1
  589. expect(@next.lastCall.args[0]).to.be.instanceof Error
  590. done()
  591. describe 'when adding user to project produces an error', ->
  592. beforeEach ->
  593. @req = new MockRequest()
  594. @res = new MockResponse()
  595. @next = sinon.stub()
  596. @req.params['read_only_token'] = @readOnlyToken
  597. @TokenAccessHandler.findProjectWithReadOnlyToken = sinon.stub()
  598. .callsArgWith(1, null, @project, true)
  599. @TokenAccessHandler.addReadOnlyUserToProject = sinon.stub()
  600. .callsArgWith(2, new Error('woops'))
  601. @ProjectController.loadEditor = sinon.stub()
  602. @TokenAccessController.readOnlyToken @req, @res, @next
  603. it 'should try to find a project with this token', (done) ->
  604. expect(@TokenAccessHandler.findProjectWithReadOnlyToken.callCount)
  605. .to.equal 1
  606. expect(@TokenAccessHandler.findProjectWithReadOnlyToken.calledWith(@readOnlyToken))
  607. .to.equal true
  608. done()
  609. it 'should add the user to the project with read-only access', (done) ->
  610. expect(@TokenAccessHandler.addReadOnlyUserToProject.callCount)
  611. .to.equal 1
  612. expect(@TokenAccessHandler.addReadOnlyUserToProject.calledWith(
  613. @userId.toString(), @projectId
  614. ))
  615. .to.equal true
  616. done()
  617. it 'should not pass control to loadEditor', (done) ->
  618. expect(@ProjectController.loadEditor.callCount).to.equal 0
  619. expect(@ProjectController.loadEditor.calledWith(@req, @res, @next)).to.equal false
  620. done()
  621. it 'should call next with an error', (done) ->
  622. expect(@next.callCount).to.equal 1
  623. expect(@next.lastCall.args[0]).to.be.instanceof Error
  624. done()
  625. describe 'anonymous', ->
  626. beforeEach ->
  627. @AuthenticationController.getLoggedInUserId = sinon.stub().returns(null)
  628. @TokenAccessHandler.grantSessionTokenAccess = sinon.stub()
  629. describe 'when all goes well', ->
  630. beforeEach ->
  631. @req = new MockRequest()
  632. @res = new MockResponse()
  633. @next = sinon.stub()
  634. @req.params['read_only_token'] = @readOnlyToken
  635. @TokenAccessHandler.findProjectWithReadOnlyToken = sinon.stub()
  636. .callsArgWith(1, null, @project, true)
  637. @TokenAccessHandler.addReadOnlyUserToProject = sinon.stub()
  638. .callsArgWith(2, null)
  639. @ProjectController.loadEditor = sinon.stub()
  640. @TokenAccessController.readOnlyToken @req, @res, @next
  641. it 'should try to find a project with this token', (done) ->
  642. expect(@TokenAccessHandler.findProjectWithReadOnlyToken.callCount)
  643. .to.equal 1
  644. expect(@TokenAccessHandler.findProjectWithReadOnlyToken.calledWith(@readOnlyToken))
  645. .to.equal true
  646. done()
  647. it 'should give the user session read-only access', (done) ->
  648. expect(@TokenAccessHandler.grantSessionTokenAccess.callCount)
  649. .to.equal 1
  650. expect(@TokenAccessHandler.grantSessionTokenAccess.calledWith(
  651. @req, @projectId, @readOnlyToken
  652. ))
  653. .to.equal true
  654. done()
  655. it 'should not add the user to the project with read-only access', (done) ->
  656. expect(@TokenAccessHandler.addReadOnlyUserToProject.callCount)
  657. .to.equal 0
  658. done()
  659. it 'should pass control to loadEditor', (done) ->
  660. expect(@req.params.Project_id).to.equal @projectId.toString()
  661. expect(@req._anonymousAccessToken).to.equal @readOnlyToken
  662. expect(@ProjectController.loadEditor.callCount).to.equal 1
  663. expect(@ProjectController.loadEditor.calledWith(@req, @res, @next)).to.equal true
  664. done()
  665. describe 'when findProject produces an error', ->
  666. beforeEach ->
  667. @req = new MockRequest()
  668. @res = new MockResponse()
  669. @next = sinon.stub()
  670. @req.params['read_only_token'] = @readOnlyToken
  671. @TokenAccessHandler.findProjectWithReadOnlyToken = sinon.stub()
  672. .callsArgWith(1, new Error('woops'))
  673. @TokenAccessHandler.addReadOnlyUserToProject = sinon.stub()
  674. .callsArgWith(2, null)
  675. @ProjectController.loadEditor = sinon.stub()
  676. @TokenAccessController.readOnlyToken @req, @res, @next
  677. it 'should try to find a project with this token', (done) ->
  678. expect(@TokenAccessHandler.findProjectWithReadOnlyToken.callCount)
  679. .to.equal 1
  680. expect(@TokenAccessHandler.findProjectWithReadOnlyToken.calledWith(@readOnlyToken))
  681. .to.equal true
  682. done()
  683. it 'should not give the user session read-only access', (done) ->
  684. expect(@TokenAccessHandler.grantSessionTokenAccess.callCount)
  685. .to.equal 0
  686. done()
  687. it 'should not add the user to the project with read-only access', (done) ->
  688. expect(@TokenAccessHandler.addReadOnlyUserToProject.callCount)
  689. .to.equal 0
  690. done()
  691. it 'should not pass control to loadEditor', (done) ->
  692. expect(@ProjectController.loadEditor.callCount).to.equal 0
  693. expect(@ProjectController.loadEditor.calledWith(@req, @res, @next)).to.equal false
  694. done()
  695. it 'should call next with an error', (done) ->
  696. expect(@next.callCount).to.equal 1
  697. expect(@next.lastCall.args[0]).to.be.instanceof Error
  698. done()
  699. describe 'when findProject does not find a project', ->
  700. beforeEach ->
  701. @req = new MockRequest()
  702. @res = new MockResponse()
  703. @res.redirect = sinon.stub()
  704. @next = sinon.stub()
  705. @req.params['read_only_token'] = @readOnlyToken
  706. @TokenAccessHandler.findProjectWithReadOnlyToken = sinon.stub()
  707. .callsArgWith(1, null, null)
  708. @TokenAccessHandler.checkV1ProjectExported = sinon.stub()
  709. .callsArgWith(1, null, false)
  710. @TokenAccessHandler.addReadOnlyUserToProject = sinon.stub()
  711. .callsArgWith(2, null)
  712. @ProjectController.loadEditor = sinon.stub()
  713. @TokenAccessController.readOnlyToken @req, @res, @next
  714. it 'should try to find a project with this token', (done) ->
  715. expect(@TokenAccessHandler.findProjectWithReadOnlyToken.callCount)
  716. .to.equal 1
  717. expect(@TokenAccessHandler.findProjectWithReadOnlyToken.calledWith(@readOnlyToken))
  718. .to.equal true
  719. done()
  720. it 'should not give the user session read-only access', (done) ->
  721. expect(@TokenAccessHandler.grantSessionTokenAccess.callCount)
  722. .to.equal 0
  723. done()
  724. it 'should not pass control to loadEditor', (done) ->
  725. expect(@ProjectController.loadEditor.callCount).to.equal 0
  726. expect(@ProjectController.loadEditor.calledWith(@req, @res, @next)).to.equal false
  727. done()
  728. it 'should not add the user to the project with read-only access', (done) ->
  729. expect(@TokenAccessHandler.addReadOnlyUserToProject.callCount)
  730. .to.equal 0
  731. done()
  732. describe 'when project was exported to v2', ->
  733. beforeEach ->
  734. @TokenAccessHandler.getV1DocInfo = sinon.stub().yields(null, {
  735. allow: true
  736. exists: true
  737. exported: true
  738. })
  739. @TokenAccessController.readOnlyToken @req, @res, @next
  740. it 'should redirect to v1', (done) ->
  741. expect(@res.redirect.callCount).to.equal 1
  742. expect(@res.redirect.calledWith(
  743. 302,
  744. "/sign_in_to_v1?return_to=/read/#{@readOnlyToken}"
  745. )).to.equal true
  746. done()