TokenAccessControllerTests.coffee 32 KB

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