TokenAccessControllerTests.coffee 38 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079
  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. getV1DocPublishedInfo: sinon.stub().yields(null, {
  31. allow: true
  32. })
  33. getV1DocInfo: sinon.stub().yields(null, {
  34. exists: true
  35. exported: false
  36. })
  37. }
  38. '../../infrastructure/Features': @Features = {
  39. hasFeature: sinon.stub().returns(false)
  40. }
  41. 'logger-sharelatex': {log: sinon.stub(), err: sinon.stub()}
  42. 'settings-sharelatex': {
  43. overleaf:
  44. host: 'http://overleaf.test:5000'
  45. }
  46. '../V1/V1Api': @V1Api = {
  47. request: sinon.stub().callsArgWith(1, null, {}, { allow: true })
  48. }
  49. @AuthenticationController.getLoggedInUserId = sinon.stub().returns(@userId.toString())
  50. describe 'readAndWriteToken', ->
  51. beforeEach ->
  52. describe 'when all goes well', ->
  53. beforeEach ->
  54. @req = new MockRequest()
  55. @res = new MockResponse()
  56. @next = sinon.stub()
  57. @req.params['read_and_write_token'] = @readAndWriteToken
  58. @TokenAccessHandler.findProjectWithReadAndWriteToken = sinon.stub()
  59. .callsArgWith(1, null, @project, true)
  60. @TokenAccessHandler.addReadAndWriteUserToProject = sinon.stub()
  61. .callsArgWith(2, null)
  62. @ProjectController.loadEditor = sinon.stub()
  63. @AuthenticationController.setRedirectInSession = sinon.stub()
  64. @TokenAccessController.readAndWriteToken @req, @res, @next
  65. it 'should try to find a project with this token', (done) ->
  66. expect(@TokenAccessHandler.findProjectWithReadAndWriteToken.callCount)
  67. .to.equal 1
  68. expect(@TokenAccessHandler.findProjectWithReadAndWriteToken.calledWith(@readAndWriteToken))
  69. .to.equal true
  70. done()
  71. it 'should add the user to the project with read-write access', (done) ->
  72. expect(@TokenAccessHandler.addReadAndWriteUserToProject.callCount)
  73. .to.equal 1
  74. expect(@TokenAccessHandler.addReadAndWriteUserToProject.calledWith(
  75. @userId.toString(), @projectId
  76. ))
  77. .to.equal true
  78. done()
  79. it 'should pass control to loadEditor', (done) ->
  80. expect(@req.params.Project_id).to.equal @projectId.toString()
  81. expect(@ProjectController.loadEditor.callCount).to.equal 1
  82. expect(@ProjectController.loadEditor.calledWith(@req, @res, @next)).to.equal true
  83. done()
  84. describe 'when the user is already the owner', ->
  85. beforeEach ->
  86. @req = new MockRequest()
  87. @res = new MockResponse()
  88. @next = sinon.stub()
  89. @req.params['read_and_write_token'] = @readAndWriteToken
  90. @project.owner_ref = @userId
  91. @TokenAccessHandler.findProjectWithReadAndWriteToken = sinon.stub()
  92. .callsArgWith(1, null, @project, true)
  93. @TokenAccessHandler.addReadAndWriteUserToProject = sinon.stub()
  94. .callsArgWith(2, null)
  95. @ProjectController.loadEditor = sinon.stub()
  96. @TokenAccessController.readAndWriteToken @req, @res, @next
  97. it 'should try to find a project with this token', (done) ->
  98. expect(@TokenAccessHandler.findProjectWithReadAndWriteToken.callCount)
  99. .to.equal 1
  100. expect(@TokenAccessHandler.findProjectWithReadAndWriteToken.calledWith(@readAndWriteToken))
  101. .to.equal true
  102. done()
  103. it 'should not add the user to the project with read-write access', (done) ->
  104. expect(@TokenAccessHandler.addReadAndWriteUserToProject.callCount)
  105. .to.equal 0
  106. done()
  107. it 'should pass control to loadEditor', (done) ->
  108. expect(@req.params.Project_id).to.equal @projectId.toString()
  109. expect(@ProjectController.loadEditor.callCount).to.equal 1
  110. expect(@ProjectController.loadEditor.calledWith(@req, @res, @next)).to.equal true
  111. done()
  112. describe 'when there is no user', ->
  113. beforeEach ->
  114. @AuthenticationController.getLoggedInUserId =
  115. sinon.stub().returns(null)
  116. describe 'when anonymous read-write access is enabled', ->
  117. beforeEach ->
  118. @TokenAccessHandler.ANONYMOUS_READ_AND_WRITE_ENABLED = true
  119. @req = new MockRequest()
  120. @res = new MockResponse()
  121. @next = sinon.stub()
  122. @req.params['read_and_write_token'] = @readAndWriteToken
  123. @TokenAccessHandler.findProjectWithReadAndWriteToken = sinon.stub()
  124. .callsArgWith(1, null, @project, true)
  125. @TokenAccessHandler.addReadAndWriteUserToProject = sinon.stub()
  126. .callsArgWith(2, null)
  127. @ProjectController.loadEditor = sinon.stub()
  128. @TokenAccessHandler.grantSessionTokenAccess = sinon.stub()
  129. @TokenAccessController.readAndWriteToken @req, @res, @next
  130. it 'should not add the user to the project with read-write access', (done) ->
  131. expect(@TokenAccessHandler.addReadAndWriteUserToProject.callCount)
  132. .to.equal 0
  133. done()
  134. it 'should give the user session token access', (done) ->
  135. expect(@TokenAccessHandler.grantSessionTokenAccess.callCount)
  136. .to.equal 1
  137. expect(@TokenAccessHandler.grantSessionTokenAccess.calledWith(
  138. @req, @projectId, @readAndWriteToken
  139. ))
  140. .to.equal true
  141. done()
  142. it 'should pass control to loadEditor', (done) ->
  143. expect(@req.params.Project_id).to.equal @projectId.toString()
  144. expect(@ProjectController.loadEditor.callCount).to.equal 1
  145. expect(@ProjectController.loadEditor.calledWith(@req, @res, @next)).to.equal true
  146. done()
  147. describe 'when anonymous read-write access is not enabled', ->
  148. beforeEach ->
  149. @TokenAccessHandler.ANONYMOUS_READ_AND_WRITE_ENABLED = false
  150. @req = new MockRequest()
  151. @res = new MockResponse()
  152. @res.redirect = sinon.stub()
  153. @next = sinon.stub()
  154. @req.params['read_and_write_token'] = @readAndWriteToken
  155. @TokenAccessHandler.findProjectWithReadAndWriteToken = sinon.stub()
  156. .callsArgWith(1, null, @project, true)
  157. @TokenAccessHandler.addReadAndWriteUserToProject = sinon.stub()
  158. .callsArgWith(2, null)
  159. @ProjectController.loadEditor = sinon.stub()
  160. @TokenAccessHandler.grantSessionTokenAccess = sinon.stub()
  161. @AuthenticationController.setRedirectInSession = sinon.stub()
  162. @TokenAccessController.readAndWriteToken @req, @res, @next
  163. it 'should not add the user to the project with read-write access', (done) ->
  164. expect(@TokenAccessHandler.addReadAndWriteUserToProject.callCount)
  165. .to.equal 0
  166. done()
  167. it 'should give the user session token access', (done) ->
  168. expect(@TokenAccessHandler.grantSessionTokenAccess.callCount)
  169. .to.equal 0
  170. done()
  171. it 'should not pass control to loadEditor', (done) ->
  172. expect(@ProjectController.loadEditor.callCount).to.equal 0
  173. expect(@ProjectController.loadEditor.calledWith(@req, @res, @next)).to.equal false
  174. done()
  175. it 'should set redirect in session', (done) ->
  176. expect(@AuthenticationController.setRedirectInSession.callCount).to.equal 1
  177. expect(@AuthenticationController.setRedirectInSession.calledWith(@req)).to.equal true
  178. done()
  179. it 'should redirect to restricted page', (done) ->
  180. expect(@res.redirect.callCount).to.equal 1
  181. expect(@res.redirect.calledWith('/restricted')).to.equal true
  182. done()
  183. describe 'when findProject produces an error', ->
  184. beforeEach ->
  185. @req = new MockRequest()
  186. @res = new MockResponse()
  187. @next = sinon.stub()
  188. @req.params['read_and_write_token'] = @readAndWriteToken
  189. @TokenAccessHandler.findProjectWithReadAndWriteToken = sinon.stub()
  190. .callsArgWith(1, new Error('woops'))
  191. @TokenAccessHandler.addReadAndWriteUserToProject = sinon.stub()
  192. .callsArgWith(2, null)
  193. @ProjectController.loadEditor = sinon.stub()
  194. @TokenAccessController.readAndWriteToken @req, @res, @next
  195. it 'should try to find a project with this token', (done) ->
  196. expect(@TokenAccessHandler.findProjectWithReadAndWriteToken.callCount)
  197. .to.equal 1
  198. expect(@TokenAccessHandler.findProjectWithReadAndWriteToken.calledWith(@readAndWriteToken))
  199. .to.equal true
  200. done()
  201. it 'should not add the user to the project with read-write access', (done) ->
  202. expect(@TokenAccessHandler.addReadAndWriteUserToProject.callCount)
  203. .to.equal 0
  204. done()
  205. it 'should not pass control to loadEditor', (done) ->
  206. expect(@ProjectController.loadEditor.callCount).to.equal 0
  207. expect(@ProjectController.loadEditor.calledWith(@req, @res, @next)).to.equal false
  208. done()
  209. it 'should call next with an error', (done) ->
  210. expect(@next.callCount).to.equal 1
  211. expect(@next.lastCall.args[0]).to.be.instanceof Error
  212. done()
  213. describe 'when findProject does not find a project', ->
  214. beforeEach ->
  215. describe 'when user is present', ->
  216. beforeEach ->
  217. @AuthenticationController.getLoggedInUserId =
  218. sinon.stub().returns(@userId.toString())
  219. describe 'when project does not exist', ->
  220. beforeEach ->
  221. @req = new MockRequest()
  222. @req.url = '/123abc'
  223. @res = new MockResponse()
  224. @res.redirect = sinon.stub()
  225. @res.render = sinon.stub()
  226. @next = sinon.stub()
  227. @req.params['read_and_write_token'] = '123abc'
  228. @TokenAccessHandler.findProjectWithReadAndWriteToken = sinon.stub()
  229. .callsArgWith(1, null, null, false)
  230. describe 'when project was not exported from v1', ->
  231. beforeEach ->
  232. @TokenAccessHandler.getV1DocInfo = sinon.stub().yields(null, {
  233. allow: true
  234. exists: true
  235. exported: false
  236. })
  237. @TokenAccessController.readAndWriteToken @req, @res, @next
  238. it 'should redirect to v1', (done) ->
  239. expect(@res.redirect.callCount).to.equal 1
  240. expect(@res.redirect.calledWith(
  241. 302,
  242. '/sign_in_to_v1?return_to=/123abc'
  243. )).to.equal true
  244. done()
  245. describe 'when project was not exported from v1 but forcing import to v2', ->
  246. beforeEach ->
  247. @Features.hasFeature.returns(true)
  248. describe 'with project name', ->
  249. beforeEach ->
  250. @TokenAccessHandler.getV1DocInfo = sinon.stub().yields(null, {
  251. allow: true
  252. exists: true
  253. exported: false
  254. has_owner: true
  255. name: 'A title'
  256. })
  257. @TokenAccessController.readAndWriteToken @req, @res, @next
  258. it 'should render v2-import page with name', (done) ->
  259. expect(@res.render.calledWith(
  260. 'project/v2-import',
  261. {
  262. projectId: '123abc'
  263. name: 'A title'
  264. hasOwner: true
  265. }
  266. )).to.equal true
  267. done()
  268. describe 'with project owner', ->
  269. beforeEach ->
  270. @TokenAccessHandler.getV1DocInfo = sinon.stub().yields(null, {
  271. allow: true
  272. exists: true
  273. exported: false
  274. has_owner: true
  275. name: 'A title'
  276. })
  277. @TokenAccessController.readAndWriteToken @req, @res, @next
  278. it 'should render v2-import page', (done) ->
  279. expect(@res.render.calledWith(
  280. 'project/v2-import',
  281. {
  282. projectId: '123abc',
  283. hasOwner: true
  284. name: 'A title'
  285. }
  286. )).to.equal true
  287. done()
  288. describe 'without project owner', ->
  289. beforeEach ->
  290. @TokenAccessHandler.getV1DocInfo = sinon.stub().yields(null, {
  291. allow: true
  292. exists: true
  293. exported: false
  294. has_owner: false
  295. name: 'A title'
  296. })
  297. @TokenAccessController.readAndWriteToken @req, @res, @next
  298. it 'should render v2-import page', (done) ->
  299. expect(@res.render.calledWith(
  300. 'project/v2-import',
  301. {
  302. projectId: '123abc',
  303. hasOwner: false
  304. name: 'A title'
  305. }
  306. )).to.equal true
  307. done()
  308. describe 'with anonymous user', ->
  309. beforeEach ->
  310. @AuthenticationController.getLoggedInUserId = sinon.stub().returns(null)
  311. @TokenAccessController.readAndWriteToken @req, @res, @next
  312. it 'should render anonymous import status page', (done) ->
  313. expect(@res.render.callCount).to.equal 1
  314. expect(@res.render.calledWith(
  315. 'project/v2-import',
  316. { loginRedirect: '/123abc' }
  317. )).to.equal true
  318. done()
  319. describe 'when project was exported from v1', ->
  320. beforeEach ->
  321. @TokenAccessHandler.getV1DocInfo = sinon.stub().yields(null, {
  322. allow: true
  323. exists: true
  324. exported: true
  325. })
  326. @TokenAccessController.readAndWriteToken @req, @res, @next
  327. it 'should call next with a not-found error', (done) ->
  328. expect(@next.callCount).to.equal 1
  329. done()
  330. describe 'when token access is off, but user has higher access anyway', ->
  331. beforeEach ->
  332. @req = new MockRequest()
  333. @res = new MockResponse()
  334. @res.redirect = sinon.stub()
  335. @next = sinon.stub()
  336. @req.params['read_and_write_token'] = @readAndWriteToken
  337. @TokenAccessHandler.findProjectWithReadAndWriteToken = sinon.stub()
  338. .callsArgWith(1, null, null, true)
  339. @TokenAccessHandler.findProjectWithHigherAccess =
  340. sinon.stub()
  341. .callsArgWith(2, null, @project)
  342. @TokenAccessHandler.addReadAndWriteUserToProject = sinon.stub()
  343. .callsArgWith(2, null)
  344. @ProjectController.loadEditor = sinon.stub()
  345. @TokenAccessController.readAndWriteToken @req, @res, @next
  346. it 'should try to find a project with this token', (done) ->
  347. expect(@TokenAccessHandler.findProjectWithReadAndWriteToken.callCount)
  348. .to.equal 1
  349. expect(@TokenAccessHandler.findProjectWithReadAndWriteToken
  350. .calledWith(@readAndWriteToken)
  351. ).to.equal true
  352. done()
  353. it 'should check if user has higher access to the token project', (done) ->
  354. expect(
  355. @TokenAccessHandler.findProjectWithHigherAccess.callCount
  356. ).to.equal 1
  357. done()
  358. it 'should not add the user to the project with read-write access', (done) ->
  359. expect(@TokenAccessHandler.addReadAndWriteUserToProject.callCount)
  360. .to.equal 0
  361. done()
  362. it 'should not pass control to loadEditor', (done) ->
  363. expect(@ProjectController.loadEditor.callCount).to.equal 0
  364. expect(@ProjectController.loadEditor.calledWith(@req, @res, @next)).to.equal false
  365. done()
  366. it 'should not call next with a not-found error', (done) ->
  367. expect(@next.callCount).to.equal 0
  368. done()
  369. it 'should redirect to the canonical project url', (done) ->
  370. expect(@res.redirect.callCount).to.equal 1
  371. expect(@res.redirect.calledWith(302, "/project/#{@project._id}")).to.equal true
  372. done()
  373. describe 'when higher access is not available', ->
  374. beforeEach ->
  375. @req = new MockRequest()
  376. @res = new MockResponse()
  377. @next = sinon.stub()
  378. @req.params['read_and_write_token'] = @readAndWriteToken
  379. @TokenAccessHandler.findProjectWithReadAndWriteToken = sinon.stub()
  380. .callsArgWith(1, null, null, true)
  381. @TokenAccessHandler.findProjectWithHigherAccess =
  382. sinon.stub()
  383. .callsArgWith(2, null, null)
  384. @TokenAccessHandler.addReadAndWriteUserToProject = sinon.stub()
  385. .callsArgWith(2, null)
  386. @ProjectController.loadEditor = sinon.stub()
  387. @TokenAccessController.readAndWriteToken @req, @res, @next
  388. it 'should try to find a project with this token', (done) ->
  389. expect(@TokenAccessHandler.findProjectWithReadAndWriteToken.callCount)
  390. .to.equal 1
  391. expect(@TokenAccessHandler.findProjectWithReadAndWriteToken.calledWith(
  392. @readAndWriteToken
  393. )).to.equal true
  394. done()
  395. it 'should check if user has higher access to the token project', (done) ->
  396. expect(
  397. @TokenAccessHandler.findProjectWithHigherAccess.callCount
  398. ).to.equal 1
  399. done()
  400. it 'should not add the user to the project with read-write access', (done) ->
  401. expect(@TokenAccessHandler.addReadAndWriteUserToProject.callCount)
  402. .to.equal 0
  403. done()
  404. it 'should not pass control to loadEditor', (done) ->
  405. expect(@ProjectController.loadEditor.callCount).to.equal 0
  406. expect(@ProjectController.loadEditor.calledWith(@req, @res, @next)).to.equal false
  407. done()
  408. it 'should call next with a not-found error', (done) ->
  409. expect(@next.callCount).to.equal 1
  410. expect(@next.lastCall.args[0]).to.be.instanceof Error
  411. done()
  412. describe 'when adding user to project produces an error', ->
  413. beforeEach ->
  414. @req = new MockRequest()
  415. @res = new MockResponse()
  416. @next = sinon.stub()
  417. @req.params['read_and_write_token'] = @readAndWriteToken
  418. @TokenAccessHandler.findProjectWithReadAndWriteToken = sinon.stub()
  419. .callsArgWith(1, null, @project, true)
  420. @TokenAccessHandler.addReadAndWriteUserToProject = sinon.stub()
  421. .callsArgWith(2, new Error('woops'))
  422. @ProjectController.loadEditor = sinon.stub()
  423. @TokenAccessController.readAndWriteToken @req, @res, @next
  424. it 'should try to find a project with this token', (done) ->
  425. expect(@TokenAccessHandler.findProjectWithReadAndWriteToken.callCount)
  426. .to.equal 1
  427. expect(@TokenAccessHandler.findProjectWithReadAndWriteToken.calledWith(@readAndWriteToken))
  428. .to.equal true
  429. done()
  430. it 'should add the user to the project with read-write access', (done) ->
  431. expect(@TokenAccessHandler.addReadAndWriteUserToProject.callCount)
  432. .to.equal 1
  433. expect(@TokenAccessHandler.addReadAndWriteUserToProject.calledWith(
  434. @userId.toString(), @projectId
  435. ))
  436. .to.equal true
  437. done()
  438. it 'should not pass control to loadEditor', (done) ->
  439. expect(@ProjectController.loadEditor.callCount).to.equal 0
  440. expect(@ProjectController.loadEditor.calledWith(@req, @res, @next)).to.equal false
  441. done()
  442. it 'should call next with an error', (done) ->
  443. expect(@next.callCount).to.equal 1
  444. expect(@next.lastCall.args[0]).to.be.instanceof Error
  445. done()
  446. describe 'readOnlyToken', ->
  447. beforeEach ->
  448. @TokenAccessHandler.checkV1Access = sinon.stub().callsArgWith(1, null, true)
  449. describe 'when access not allowed by v1 api', ->
  450. beforeEach ->
  451. @req = new MockRequest()
  452. @res = new MockResponse()
  453. @res.redirect = sinon.stub()
  454. @next = sinon.stub()
  455. @TokenAccessHandler.findProjectWithReadOnlyToken = sinon.stub()
  456. .callsArgWith(1, null, @project, true)
  457. @TokenAccessHandler.getV1DocPublishedInfo = sinon.stub().yields(null, {
  458. allow: false
  459. published_path: 'doc-url'
  460. })
  461. @TokenAccessController.readOnlyToken @req, @res, @next
  462. it 'should redirect to doc-url', ->
  463. expect(@res.redirect.calledWith('doc-url')).to.equal true
  464. describe 'with a user', ->
  465. beforeEach ->
  466. @AuthenticationController.getLoggedInUserId = sinon.stub().returns(@userId.toString())
  467. describe 'when all goes well', ->
  468. beforeEach ->
  469. @req = new MockRequest()
  470. @res = new MockResponse()
  471. @next = sinon.stub()
  472. @req.params['read_only_token'] = @readOnlyToken
  473. @TokenAccessHandler.findProjectWithReadOnlyToken = sinon.stub()
  474. .callsArgWith(1, null, @project, true)
  475. @TokenAccessHandler.addReadOnlyUserToProject = sinon.stub()
  476. .callsArgWith(2, null)
  477. @ProjectController.loadEditor = sinon.stub()
  478. @TokenAccessController.readOnlyToken @req, @res, @next
  479. it 'should try to find a project with this token', (done) ->
  480. expect(@TokenAccessHandler.findProjectWithReadOnlyToken.callCount)
  481. .to.equal 1
  482. expect(@TokenAccessHandler.findProjectWithReadOnlyToken.calledWith(@readOnlyToken))
  483. .to.equal true
  484. done()
  485. it 'should add the user to the project with read-only access', (done) ->
  486. expect(@TokenAccessHandler.addReadOnlyUserToProject.callCount)
  487. .to.equal 1
  488. expect(@TokenAccessHandler.addReadOnlyUserToProject.calledWith(
  489. @userId.toString(), @projectId
  490. ))
  491. .to.equal true
  492. done()
  493. it 'should pass control to loadEditor', (done) ->
  494. expect(@req.params.Project_id).to.equal @projectId.toString()
  495. expect(@ProjectController.loadEditor.callCount).to.equal 1
  496. expect(@ProjectController.loadEditor.calledWith(@req, @res, @next)).to.equal true
  497. done()
  498. describe 'when the user is already the owner', ->
  499. beforeEach ->
  500. @req = new MockRequest()
  501. @res = new MockResponse()
  502. @next = sinon.stub()
  503. @req.params['read_only_token'] = @readOnlyToken
  504. @project.owner_ref = @userId
  505. @TokenAccessHandler.findProjectWithReadOnlyToken = sinon.stub()
  506. .callsArgWith(1, null, @project, true)
  507. @TokenAccessHandler.addReadOnlyUserToProject = sinon.stub()
  508. .callsArgWith(2, null)
  509. @ProjectController.loadEditor = sinon.stub()
  510. @TokenAccessController.readOnlyToken @req, @res, @next
  511. it 'should try to find a project with this token', (done) ->
  512. expect(@TokenAccessHandler.findProjectWithReadOnlyToken.callCount)
  513. .to.equal 1
  514. expect(@TokenAccessHandler.findProjectWithReadOnlyToken.calledWith(@readOnlyToken))
  515. .to.equal true
  516. done()
  517. it 'should not add the user to the project with read-only access', (done) ->
  518. expect(@TokenAccessHandler.addReadOnlyUserToProject.callCount)
  519. .to.equal 0
  520. done()
  521. it 'should pass control to loadEditor', (done) ->
  522. expect(@req.params.Project_id).to.equal @projectId.toString()
  523. expect(@ProjectController.loadEditor.callCount).to.equal 1
  524. expect(@ProjectController.loadEditor.calledWith(@req, @res, @next)).to.equal true
  525. done()
  526. describe 'when findProject produces an error', ->
  527. beforeEach ->
  528. @req = new MockRequest()
  529. @res = new MockResponse()
  530. @next = sinon.stub()
  531. @req.params['read_only_token'] = @readOnlyToken
  532. @TokenAccessHandler.findProjectWithReadOnlyToken = sinon.stub()
  533. .callsArgWith(1, new Error('woops'))
  534. @TokenAccessHandler.addReadOnlyUserToProject = sinon.stub()
  535. .callsArgWith(2, null)
  536. @ProjectController.loadEditor = sinon.stub()
  537. @TokenAccessController.readOnlyToken @req, @res, @next
  538. it 'should try to find a project with this token', (done) ->
  539. expect(@TokenAccessHandler.findProjectWithReadOnlyToken.callCount)
  540. .to.equal 1
  541. expect(@TokenAccessHandler.findProjectWithReadOnlyToken.calledWith(@readOnlyToken))
  542. .to.equal true
  543. done()
  544. it 'should not add the user to the project with read-only access', (done) ->
  545. expect(@TokenAccessHandler.addReadOnlyUserToProject.callCount)
  546. .to.equal 0
  547. done()
  548. it 'should not pass control to loadEditor', (done) ->
  549. expect(@ProjectController.loadEditor.callCount).to.equal 0
  550. expect(@ProjectController.loadEditor.calledWith(@req, @res, @next)).to.equal false
  551. done()
  552. it 'should call next with an error', (done) ->
  553. expect(@next.callCount).to.equal 1
  554. expect(@next.lastCall.args[0]).to.be.instanceof Error
  555. done()
  556. describe 'when findProject does not find a project', ->
  557. describe 'when project does not exist', ->
  558. beforeEach ->
  559. @req = new MockRequest()
  560. @res = new MockResponse()
  561. @res.redirect = sinon.stub()
  562. @next = sinon.stub()
  563. @req.params['read_only_token'] = 'abcd'
  564. @TokenAccessHandler.findProjectWithReadOnlyToken = sinon.stub()
  565. .callsArgWith(1, null, null, false)
  566. @TokenAccessHandler.checkV1ProjectExported = sinon.stub()
  567. .callsArgWith(1, null, false)
  568. @TokenAccessController.readOnlyToken @req, @res, @next
  569. it 'should redirect to v1', (done) ->
  570. expect(@res.redirect.callCount).to.equal 1
  571. expect(@res.redirect.calledWith(
  572. 302,
  573. '/sign_in_to_v1?return_to=/read/abcd'
  574. )).to.equal true
  575. done()
  576. describe 'when project was not exported from v1 but forcing import to v2', ->
  577. beforeEach ->
  578. @Features.hasFeature.returns(true)
  579. @req = new MockRequest()
  580. @res = new MockResponse()
  581. @res.render = sinon.stub()
  582. @next = sinon.stub()
  583. @req.params['read_only_token'] = 'abcd'
  584. @TokenAccessHandler.findProjectWithReadOnlyToken = sinon.stub()
  585. .callsArgWith(1, null, null, false)
  586. describe 'with project name', ->
  587. beforeEach ->
  588. @TokenAccessHandler.getV1DocInfo = sinon.stub().yields(null, {
  589. allow: true
  590. exists: true
  591. exported: false
  592. has_owner: true
  593. name: 'A title'
  594. })
  595. @TokenAccessController.readOnlyToken @req, @res, @next
  596. it 'should render v2-import page with name', (done) ->
  597. expect(@res.render.calledWith(
  598. 'project/v2-import',
  599. {
  600. projectId: 'abcd'
  601. name: 'A title'
  602. hasOwner: true
  603. }
  604. )).to.equal true
  605. done()
  606. describe 'with project owner', ->
  607. beforeEach ->
  608. @TokenAccessHandler.getV1DocInfo = sinon.stub().yields(null, {
  609. allow: true
  610. exists: true
  611. exported: false
  612. has_owner: true
  613. name: 'A title'
  614. })
  615. @TokenAccessController.readOnlyToken @req, @res, @next
  616. it 'should render v2-import page', (done) ->
  617. expect(@res.render.calledWith(
  618. 'project/v2-import',
  619. {
  620. projectId: 'abcd',
  621. hasOwner: true
  622. name: 'A title'
  623. }
  624. )).to.equal true
  625. done()
  626. describe 'without project owner', ->
  627. beforeEach ->
  628. @TokenAccessHandler.getV1DocInfo = sinon.stub().yields(null, {
  629. allow: true
  630. exists: true
  631. exported: false
  632. has_owner: false
  633. name: 'A title'
  634. })
  635. @TokenAccessController.readOnlyToken @req, @res, @next
  636. it 'should render v2-import page', (done) ->
  637. expect(@res.render.calledWith(
  638. 'project/v2-import',
  639. {
  640. projectId: 'abcd',
  641. hasOwner: false
  642. name: 'A title'
  643. }
  644. )).to.equal true
  645. done()
  646. describe 'when project was exported from v1', ->
  647. beforeEach ->
  648. @req = new MockRequest()
  649. @res = new MockResponse()
  650. @res.redirect = sinon.stub()
  651. @next = sinon.stub()
  652. @req.params['read_only_token'] = 'abcd'
  653. @TokenAccessHandler.findProjectWithReadOnlyToken = sinon.stub()
  654. .callsArgWith(1, null, null, false)
  655. @TokenAccessHandler.getV1DocInfo = sinon.stub().yields(null, {
  656. allow: true
  657. exists: true
  658. exported: true
  659. })
  660. @TokenAccessController.readOnlyToken @req, @res, @next
  661. it 'should call next with a not-found error', (done) ->
  662. expect(@next.callCount).to.equal 1
  663. done()
  664. describe 'when token access is off, but user has higher access anyway', ->
  665. beforeEach ->
  666. @req = new MockRequest()
  667. @res = new MockResponse()
  668. @res.redirect = sinon.stub()
  669. @next = sinon.stub()
  670. @req.params['read_and_write_token'] = @readAndWriteToken
  671. @TokenAccessHandler.findProjectWithReadAndWriteToken = sinon.stub()
  672. .callsArgWith(1, null, null, true)
  673. @TokenAccessHandler.findProjectWithHigherAccess =
  674. sinon.stub()
  675. .callsArgWith(2, null, @project)
  676. @TokenAccessHandler.addReadAndWriteUserToProject = sinon.stub()
  677. .callsArgWith(2, null)
  678. @ProjectController.loadEditor = sinon.stub()
  679. @TokenAccessController.readAndWriteToken @req, @res, @next
  680. it 'should try to find a project with this token', (done) ->
  681. expect(@TokenAccessHandler.findProjectWithReadAndWriteToken.callCount)
  682. .to.equal 1
  683. expect(@TokenAccessHandler.findProjectWithReadAndWriteToken.calledWith(@readAndWriteToken))
  684. .to.equal true
  685. done()
  686. it 'should check if user has higher access to the token project', (done) ->
  687. expect(
  688. @TokenAccessHandler.findProjectWithHigherAccess.callCount
  689. ).to.equal 1
  690. done()
  691. it 'should not add the user to the project with read-write access', (done) ->
  692. expect(@TokenAccessHandler.addReadAndWriteUserToProject.callCount)
  693. .to.equal 0
  694. done()
  695. it 'should not pass control to loadEditor', (done) ->
  696. expect(@ProjectController.loadEditor.callCount).to.equal 0
  697. expect(@ProjectController.loadEditor.calledWith(@req, @res, @next)).to.equal false
  698. done()
  699. it 'should not call next with a not-found error', (done) ->
  700. expect(@next.callCount).to.equal 0
  701. done()
  702. it 'should redirect to the canonical project url', (done) ->
  703. expect(@res.redirect.callCount).to.equal 1
  704. expect(@res.redirect.calledWith(302, "/project/#{@project._id}")).to.equal true
  705. done()
  706. describe 'when higher access is not available', ->
  707. beforeEach ->
  708. @req = new MockRequest()
  709. @res = new MockResponse()
  710. @next = sinon.stub()
  711. @req.params['read_and_write_token'] = @readAndWriteToken
  712. @TokenAccessHandler.findProjectWithReadAndWriteToken = sinon.stub()
  713. .callsArgWith(1, null, null, true)
  714. @TokenAccessHandler.findProjectWithHigherAccess =
  715. sinon.stub()
  716. .callsArgWith(2, null, null)
  717. @TokenAccessHandler.addReadOnlyUserToProject = sinon.stub()
  718. .callsArgWith(2, null)
  719. @ProjectController.loadEditor = sinon.stub()
  720. @TokenAccessController.readAndWriteToken @req, @res, @next
  721. it 'should try to find a project with this token', (done) ->
  722. expect(@TokenAccessHandler.findProjectWithReadAndWriteToken.callCount)
  723. .to.equal 1
  724. expect(@TokenAccessHandler.findProjectWithReadAndWriteToken.calledWith(
  725. @readAndWriteToken
  726. )).to.equal true
  727. done()
  728. it 'should check if user has higher access to the token project', (done) ->
  729. expect(
  730. @TokenAccessHandler.findProjectWithHigherAccess.callCount
  731. ).to.equal 1
  732. done()
  733. it 'should not add the user to the project with read-write access', (done) ->
  734. expect(@TokenAccessHandler.addReadOnlyUserToProject.callCount)
  735. .to.equal 0
  736. done()
  737. it 'should not pass control to loadEditor', (done) ->
  738. expect(@ProjectController.loadEditor.callCount).to.equal 0
  739. expect(@ProjectController.loadEditor.calledWith(@req, @res, @next)).to.equal false
  740. done()
  741. it 'should call next with a not-found error', (done) ->
  742. expect(@next.callCount).to.equal 1
  743. expect(@next.lastCall.args[0]).to.be.instanceof Error
  744. done()
  745. describe 'when adding user to project produces an error', ->
  746. beforeEach ->
  747. @req = new MockRequest()
  748. @res = new MockResponse()
  749. @next = sinon.stub()
  750. @req.params['read_only_token'] = @readOnlyToken
  751. @TokenAccessHandler.findProjectWithReadOnlyToken = sinon.stub()
  752. .callsArgWith(1, null, @project, true)
  753. @TokenAccessHandler.addReadOnlyUserToProject = sinon.stub()
  754. .callsArgWith(2, new Error('woops'))
  755. @ProjectController.loadEditor = sinon.stub()
  756. @TokenAccessController.readOnlyToken @req, @res, @next
  757. it 'should try to find a project with this token', (done) ->
  758. expect(@TokenAccessHandler.findProjectWithReadOnlyToken.callCount)
  759. .to.equal 1
  760. expect(@TokenAccessHandler.findProjectWithReadOnlyToken.calledWith(@readOnlyToken))
  761. .to.equal true
  762. done()
  763. it 'should add the user to the project with read-only access', (done) ->
  764. expect(@TokenAccessHandler.addReadOnlyUserToProject.callCount)
  765. .to.equal 1
  766. expect(@TokenAccessHandler.addReadOnlyUserToProject.calledWith(
  767. @userId.toString(), @projectId
  768. ))
  769. .to.equal true
  770. done()
  771. it 'should not pass control to loadEditor', (done) ->
  772. expect(@ProjectController.loadEditor.callCount).to.equal 0
  773. expect(@ProjectController.loadEditor.calledWith(@req, @res, @next)).to.equal false
  774. done()
  775. it 'should call next with an error', (done) ->
  776. expect(@next.callCount).to.equal 1
  777. expect(@next.lastCall.args[0]).to.be.instanceof Error
  778. done()
  779. describe 'anonymous', ->
  780. beforeEach ->
  781. @AuthenticationController.getLoggedInUserId = sinon.stub().returns(null)
  782. @TokenAccessHandler.grantSessionTokenAccess = sinon.stub()
  783. describe 'when all goes well', ->
  784. beforeEach ->
  785. @req = new MockRequest()
  786. @res = new MockResponse()
  787. @next = sinon.stub()
  788. @req.params['read_only_token'] = @readOnlyToken
  789. @TokenAccessHandler.findProjectWithReadOnlyToken = sinon.stub()
  790. .callsArgWith(1, null, @project, true)
  791. @TokenAccessHandler.addReadOnlyUserToProject = sinon.stub()
  792. .callsArgWith(2, null)
  793. @ProjectController.loadEditor = sinon.stub()
  794. @TokenAccessController.readOnlyToken @req, @res, @next
  795. it 'should try to find a project with this token', (done) ->
  796. expect(@TokenAccessHandler.findProjectWithReadOnlyToken.callCount)
  797. .to.equal 1
  798. expect(@TokenAccessHandler.findProjectWithReadOnlyToken.calledWith(@readOnlyToken))
  799. .to.equal true
  800. done()
  801. it 'should give the user session read-only access', (done) ->
  802. expect(@TokenAccessHandler.grantSessionTokenAccess.callCount)
  803. .to.equal 1
  804. expect(@TokenAccessHandler.grantSessionTokenAccess.calledWith(
  805. @req, @projectId, @readOnlyToken
  806. ))
  807. .to.equal true
  808. done()
  809. it 'should not add the user to the project with read-only access', (done) ->
  810. expect(@TokenAccessHandler.addReadOnlyUserToProject.callCount)
  811. .to.equal 0
  812. done()
  813. it 'should pass control to loadEditor', (done) ->
  814. expect(@req.params.Project_id).to.equal @projectId.toString()
  815. expect(@req._anonymousAccessToken).to.equal @readOnlyToken
  816. expect(@ProjectController.loadEditor.callCount).to.equal 1
  817. expect(@ProjectController.loadEditor.calledWith(@req, @res, @next)).to.equal true
  818. done()
  819. describe 'when findProject produces an error', ->
  820. beforeEach ->
  821. @req = new MockRequest()
  822. @res = new MockResponse()
  823. @next = sinon.stub()
  824. @req.params['read_only_token'] = @readOnlyToken
  825. @TokenAccessHandler.findProjectWithReadOnlyToken = sinon.stub()
  826. .callsArgWith(1, new Error('woops'))
  827. @TokenAccessHandler.addReadOnlyUserToProject = sinon.stub()
  828. .callsArgWith(2, null)
  829. @ProjectController.loadEditor = sinon.stub()
  830. @TokenAccessController.readOnlyToken @req, @res, @next
  831. it 'should try to find a project with this token', (done) ->
  832. expect(@TokenAccessHandler.findProjectWithReadOnlyToken.callCount)
  833. .to.equal 1
  834. expect(@TokenAccessHandler.findProjectWithReadOnlyToken.calledWith(@readOnlyToken))
  835. .to.equal true
  836. done()
  837. it 'should not give the user session read-only access', (done) ->
  838. expect(@TokenAccessHandler.grantSessionTokenAccess.callCount)
  839. .to.equal 0
  840. done()
  841. it 'should not add the user to the project with read-only access', (done) ->
  842. expect(@TokenAccessHandler.addReadOnlyUserToProject.callCount)
  843. .to.equal 0
  844. done()
  845. it 'should not pass control to loadEditor', (done) ->
  846. expect(@ProjectController.loadEditor.callCount).to.equal 0
  847. expect(@ProjectController.loadEditor.calledWith(@req, @res, @next)).to.equal false
  848. done()
  849. it 'should call next with an error', (done) ->
  850. expect(@next.callCount).to.equal 1
  851. expect(@next.lastCall.args[0]).to.be.instanceof Error
  852. done()
  853. describe 'when findProject does not find a project', ->
  854. beforeEach ->
  855. @req = new MockRequest()
  856. @res = new MockResponse()
  857. @res.redirect = sinon.stub()
  858. @next = sinon.stub()
  859. @req.params['read_only_token'] = @readOnlyToken
  860. @AuthenticationController.getLoggedInUserId = sinon.stub().returns(@userId.toString())
  861. @TokenAccessHandler.findProjectWithReadOnlyToken = sinon.stub()
  862. .callsArgWith(1, null, false)
  863. @TokenAccessHandler.addReadOnlyUserToProject = sinon.stub()
  864. describe 'when project does not exist', ->
  865. beforeEach ->
  866. @TokenAccessController.readOnlyToken @req, @res, @next
  867. it 'should try to find a project with this token', (done) ->
  868. expect(@TokenAccessHandler.findProjectWithReadOnlyToken.callCount)
  869. .to.equal 1
  870. expect(@TokenAccessHandler.findProjectWithReadOnlyToken.calledWith(@readOnlyToken))
  871. .to.equal true
  872. done()
  873. it 'should not give the user session read-only access', (done) ->
  874. expect(@TokenAccessHandler.grantSessionTokenAccess.callCount)
  875. .to.equal 0
  876. done()
  877. it 'should not add the user to the project with read-only access', (done) ->
  878. expect(@TokenAccessHandler.addReadOnlyUserToProject.callCount)
  879. .to.equal 0
  880. done()
  881. describe 'when project was exported to v2', ->
  882. beforeEach ->
  883. @TokenAccessHandler.getV1DocInfo = sinon.stub().yields(null, {
  884. exists: true
  885. exported: true
  886. })
  887. @TokenAccessController.readOnlyToken @req, @res, @next
  888. it 'should call next with not found error', (done) ->
  889. expect(@next.callCount).to.equal 1
  890. expect(@next.calledWith(new Errors.NotFoundError())).to.equal true
  891. done()
  892. describe 'when project was not exported to v2', ->
  893. beforeEach ->
  894. @TokenAccessHandler.getV1DocInfo = sinon.stub().yields(null, {
  895. exists: true
  896. exported: false
  897. })
  898. @TokenAccessController.readOnlyToken @req, @res, @next
  899. it 'should redirect to v1', (done) ->
  900. expect(@res.redirect.callCount).to.equal 1
  901. expect(@res.redirect.calledWith(
  902. 302,
  903. "/sign_in_to_v1?return_to=/read/#{@readOnlyToken}"
  904. )).to.equal true
  905. done()
  906. describe 'anonymous user', ->
  907. beforeEach ->
  908. @AuthenticationController.getLoggedInUserId = sinon.stub().returns(null)
  909. describe 'when project was not exported to v2', ->
  910. beforeEach ->
  911. @TokenAccessHandler.getV1DocInfo = sinon.stub().yields(null, {
  912. exists: true
  913. exported: false
  914. })
  915. @TokenAccessController.readOnlyToken @req, @res, @next
  916. it 'should redirect to v1', (done) ->
  917. expect(@res.redirect.callCount).to.equal 1
  918. expect(@res.redirect.calledWith(
  919. 302,
  920. "/sign_in_to_v1?return_to=/read/#{@readOnlyToken}"
  921. )).to.equal true
  922. done()
  923. describe 'force-import-to-v2 flag is on', ->
  924. beforeEach ->
  925. @res.render = sinon.stub()
  926. @Features.hasFeature.returns(true)
  927. describe 'when project was not exported to v2', ->
  928. beforeEach ->
  929. @TokenAccessHandler.getV1DocInfo = sinon.stub().yields(null, {
  930. exists: true
  931. exported: false
  932. })
  933. @TokenAccessController.readOnlyToken @req, @res, @next
  934. it 'should render anonymous import status page', (done) ->
  935. expect(@res.render.callCount).to.equal 1
  936. expect(@res.render.calledWith(
  937. 'project/v2-import',
  938. { loginRedirect: "/read/#{@readOnlyToken}" }
  939. )).to.equal true
  940. done()