TokenAccessHandlerTests.coffee 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529
  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/TokenAccessHandler"
  7. expect = require("chai").expect
  8. ObjectId = require("mongojs").ObjectId
  9. describe "TokenAccessHandler", ->
  10. beforeEach ->
  11. @token = 'sometokenthing'
  12. @projectId = ObjectId()
  13. @project =
  14. _id: @projectId
  15. publicAccesLevel: 'tokenBased'
  16. @userId = ObjectId()
  17. @req = {}
  18. @TokenAccessHandler = SandboxedModule.require modulePath, requires:
  19. '../../models/Project': {Project: @Project = {}}
  20. 'settings-sharelatex': @settings = {}
  21. '../Collaborators/CollaboratorsHandler': @CollaboratorsHandler = {}
  22. '../V1/V1Api': @V1Api = {
  23. request: sinon.stub()
  24. }
  25. describe 'findProjectWithReadOnlyToken', ->
  26. beforeEach ->
  27. @Project.findOne = sinon.stub().callsArgWith(2, null, @project)
  28. it 'should call Project.findOne', (done) ->
  29. @TokenAccessHandler.findProjectWithReadOnlyToken @token, (err, project) =>
  30. expect(@Project.findOne.callCount).to.equal 1
  31. expect(@Project.findOne.calledWith({
  32. 'tokens.readOnly': @token
  33. })).to.equal true
  34. done()
  35. it 'should produce a project object with no error', (done) ->
  36. @TokenAccessHandler.findProjectWithReadOnlyToken @token, (err, project) =>
  37. expect(err).to.not.exist
  38. expect(project).to.exist
  39. expect(project).to.deep.equal @project
  40. done()
  41. it 'should return projectExists flag as true', (done) ->
  42. @TokenAccessHandler.findProjectWithReadOnlyToken @token, (err, project, projectExists) ->
  43. expect(projectExists).to.equal true
  44. done()
  45. describe 'when Project.findOne produces an error', ->
  46. beforeEach ->
  47. @Project.findOne = sinon.stub().callsArgWith(2, new Error('woops'))
  48. it 'should produce an error', (done) ->
  49. @TokenAccessHandler.findProjectWithReadOnlyToken @token, (err, project) =>
  50. expect(err).to.exist
  51. expect(project).to.not.exist
  52. expect(err).to.be.instanceof Error
  53. done()
  54. describe 'when project does not have tokenBased access level', ->
  55. beforeEach ->
  56. @project.publicAccesLevel = 'private'
  57. @Project.findOne = sinon.stub().callsArgWith(2, null, @project, true)
  58. it 'should not return a project', (done) ->
  59. @TokenAccessHandler.findProjectWithReadOnlyToken @token, (err, project) ->
  60. expect(err).to.not.exist
  61. expect(project).to.not.exist
  62. done()
  63. it 'should return projectExists flag as true', (done) ->
  64. @TokenAccessHandler.findProjectWithReadOnlyToken @token, (err, project, projectExists) ->
  65. expect(projectExists).to.equal true
  66. done()
  67. describe 'when project does not exist', ->
  68. beforeEach ->
  69. @Project.findOne = sinon.stub().callsArgWith(2, null, null)
  70. it 'should not return a project', (done) ->
  71. @TokenAccessHandler.findProjectWithReadOnlyToken @token, (err, project) ->
  72. expect(err).to.not.exist
  73. expect(project).to.not.exist
  74. done()
  75. it 'should return projectExists flag as false', (done) ->
  76. @TokenAccessHandler.findProjectWithReadOnlyToken @token, (err, project, projectExists) ->
  77. expect(projectExists).to.equal false
  78. done()
  79. describe 'findProjectWithReadAndWriteToken', ->
  80. beforeEach ->
  81. @Project.findOne = sinon.stub().callsArgWith(2, null, @project)
  82. it 'should call Project.findOne', (done) ->
  83. @TokenAccessHandler.findProjectWithReadAndWriteToken @token, (err, project) =>
  84. expect(@Project.findOne.callCount).to.equal 1
  85. expect(@Project.findOne.calledWith({
  86. 'tokens.readAndWrite': @token
  87. })).to.equal true
  88. done()
  89. it 'should produce a project object with no error', (done) ->
  90. @TokenAccessHandler.findProjectWithReadAndWriteToken @token, (err, project) =>
  91. expect(err).to.not.exist
  92. expect(project).to.exist
  93. expect(project).to.deep.equal @project
  94. done()
  95. it 'should return projectExists flag as true', (done) ->
  96. @TokenAccessHandler.findProjectWithReadAndWriteToken @token, (err, project, projectExists) ->
  97. expect(projectExists).to.equal true
  98. done()
  99. describe 'when Project.findOne produces an error', ->
  100. beforeEach ->
  101. @Project.findOne = sinon.stub().callsArgWith(2, new Error('woops'))
  102. it 'should produce an error', (done) ->
  103. @TokenAccessHandler.findProjectWithReadAndWriteToken @token, (err, project) =>
  104. expect(err).to.exist
  105. expect(project).to.not.exist
  106. expect(err).to.be.instanceof Error
  107. done()
  108. describe 'when project does not have tokenBased access level', ->
  109. beforeEach ->
  110. @project.publicAccesLevel = 'private'
  111. @Project.findOne = sinon.stub().callsArgWith(2, null, @project, true)
  112. it 'should not return a project', (done) ->
  113. @TokenAccessHandler.findProjectWithReadAndWriteToken @token, (err, project) ->
  114. expect(err).to.not.exist
  115. expect(project).to.not.exist
  116. done()
  117. it 'should return projectExists flag as true', (done) ->
  118. @TokenAccessHandler.findProjectWithReadAndWriteToken @token, (err, project, projectExists) ->
  119. expect(projectExists).to.equal true
  120. done()
  121. describe 'findProjectWithHigherAccess', ->
  122. describe 'when user does have higher access', ->
  123. beforeEach ->
  124. @Project.findOne = sinon.stub().callsArgWith(2, null, @project)
  125. @CollaboratorsHandler.isUserInvitedMemberOfProject = sinon.stub()
  126. .callsArgWith(2, null, true)
  127. it 'should call Project.findOne', (done) ->
  128. @TokenAccessHandler.findProjectWithHigherAccess @token, @userId, (err, project) =>
  129. expect(@Project.findOne.callCount).to.equal 1
  130. expect(@Project.findOne.calledWith($or: [
  131. {'tokens.readAndWrite': @token},
  132. {'tokens.readOnly': @token}
  133. ])).to.equal true
  134. done()
  135. it 'should call isUserInvitedMemberOfProject', (done) ->
  136. @TokenAccessHandler.findProjectWithHigherAccess @token, @userId, (err, project) =>
  137. expect(@CollaboratorsHandler.isUserInvitedMemberOfProject.callCount)
  138. .to.equal 1
  139. expect(@CollaboratorsHandler.isUserInvitedMemberOfProject.calledWith(
  140. @userId, @project._id
  141. )).to.equal true
  142. done()
  143. it 'should produce a project object', (done) ->
  144. @TokenAccessHandler.findProjectWithHigherAccess @token, @userId, (err, project) =>
  145. expect(err).to.not.exist
  146. expect(project).to.exist
  147. expect(project).to.deep.equal @project
  148. done()
  149. describe 'when user does not have higher access', ->
  150. beforeEach ->
  151. @Project.findOne = sinon.stub().callsArgWith(2, null, @project)
  152. @CollaboratorsHandler.isUserInvitedMemberOfProject = sinon.stub()
  153. .callsArgWith(2, null, false)
  154. it 'should call Project.findOne', (done) ->
  155. @TokenAccessHandler.findProjectWithHigherAccess @token, @userId, (err, project) =>
  156. expect(@Project.findOne.callCount).to.equal 1
  157. expect(@Project.findOne.calledWith($or: [
  158. {'tokens.readAndWrite': @token},
  159. {'tokens.readOnly': @token}
  160. ])).to.equal true
  161. done()
  162. it 'should call isUserInvitedMemberOfProject', (done) ->
  163. @TokenAccessHandler.findProjectWithHigherAccess @token, @userId, (err, project) =>
  164. expect(@CollaboratorsHandler.isUserInvitedMemberOfProject.callCount)
  165. .to.equal 1
  166. expect(@CollaboratorsHandler.isUserInvitedMemberOfProject.calledWith(
  167. @userId, @project._id
  168. )).to.equal true
  169. done()
  170. it 'should not produce a project', (done) ->
  171. @TokenAccessHandler.findProjectWithHigherAccess @token, @userId, (err, project) =>
  172. expect(err).to.not.exist
  173. expect(project).to.not.exist
  174. done()
  175. describe 'when Project.findOne produces an error', ->
  176. beforeEach ->
  177. @Project.findOne = sinon.stub().callsArgWith(2, new Error('woops'))
  178. it 'should produce an error', (done) ->
  179. @TokenAccessHandler.findProjectWithHigherAccess @token, @userId, (err, project) =>
  180. expect(err).to.exist
  181. expect(project).to.not.exist
  182. expect(err).to.be.instanceof Error
  183. done()
  184. describe 'when isUserInvitedMemberOfProject produces an error', ->
  185. beforeEach ->
  186. @Project.findOne = sinon.stub().callsArgWith(2, null, @project)
  187. @CollaboratorsHandler.isUserInvitedMemberOfProject = sinon.stub()
  188. .callsArgWith(2, new Error('woops'))
  189. it 'should produce an error', (done) ->
  190. @TokenAccessHandler.findProjectWithHigherAccess @token, @userId, (err, project) =>
  191. expect(err).to.exist
  192. expect(project).to.not.exist
  193. expect(err).to.be.instanceof Error
  194. done()
  195. describe 'addReadOnlyUserToProject', ->
  196. beforeEach ->
  197. @Project.update = sinon.stub().callsArgWith(2, null)
  198. it 'should call Project.update', (done) ->
  199. @TokenAccessHandler.addReadOnlyUserToProject @userId, @projectId, (err) =>
  200. expect(@Project.update.callCount).to.equal 1
  201. expect(@Project.update.calledWith({
  202. _id: @projectId
  203. })).to.equal true
  204. expect(@Project.update.lastCall.args[1]['$addToSet'])
  205. .to.have.keys 'tokenAccessReadOnly_refs'
  206. done()
  207. it 'should not produce an error', (done) ->
  208. @TokenAccessHandler.addReadOnlyUserToProject @userId, @projectId, (err) =>
  209. expect(err).to.not.exist
  210. done()
  211. describe 'when Project.update produces an error', ->
  212. beforeEach ->
  213. @Project.update = sinon.stub().callsArgWith(2, new Error('woops'))
  214. it 'should produce an error', (done) ->
  215. @TokenAccessHandler.addReadOnlyUserToProject @userId, @projectId, (err) =>
  216. expect(err).to.exist
  217. done()
  218. describe 'addReadAndWriteUserToProject', ->
  219. beforeEach ->
  220. @Project.update = sinon.stub().callsArgWith(2, null)
  221. it 'should call Project.update', (done) ->
  222. @TokenAccessHandler.addReadAndWriteUserToProject @userId, @projectId, (err) =>
  223. expect(@Project.update.callCount).to.equal 1
  224. expect(@Project.update.calledWith({
  225. _id: @projectId
  226. })).to.equal true
  227. expect(@Project.update.lastCall.args[1]['$addToSet'])
  228. .to.have.keys 'tokenAccessReadAndWrite_refs'
  229. done()
  230. it 'should not produce an error', (done) ->
  231. @TokenAccessHandler.addReadAndWriteUserToProject @userId, @projectId, (err) =>
  232. expect(err).to.not.exist
  233. done()
  234. describe 'when Project.update produces an error', ->
  235. beforeEach ->
  236. @Project.update = sinon.stub().callsArgWith(2, new Error('woops'))
  237. it 'should produce an error', (done) ->
  238. @TokenAccessHandler.addReadAndWriteUserToProject @userId, @projectId, (err) =>
  239. expect(err).to.exist
  240. done()
  241. describe 'grantSessionTokenAccess', ->
  242. beforeEach ->
  243. @req = {session: {}, headers: {}}
  244. it 'should add the token to the session', (done) ->
  245. @TokenAccessHandler.grantSessionTokenAccess(@req, @projectId, @token)
  246. expect(@req.session.anonTokenAccess[@projectId.toString()])
  247. .to.equal @token
  248. done()
  249. describe 'isValidToken', ->
  250. describe 'when a read-only project is found', ->
  251. beforeEach ->
  252. @TokenAccessHandler.findProjectWithReadAndWriteToken = sinon.stub()
  253. .callsArgWith(1, null, null)
  254. @TokenAccessHandler.findProjectWithReadOnlyToken = sinon.stub()
  255. .callsArgWith(1, null, @project)
  256. it 'should try to find projects with both kinds of token', (done) ->
  257. @TokenAccessHandler.isValidToken @projectId, @token, (err, allowed) =>
  258. expect(@TokenAccessHandler.findProjectWithReadAndWriteToken.callCount)
  259. .to.equal 1
  260. expect(@TokenAccessHandler.findProjectWithReadOnlyToken.callCount)
  261. .to.equal 1
  262. done()
  263. it 'should allow read-only access', (done) ->
  264. @TokenAccessHandler.isValidToken @projectId, @token, (err, rw, ro) =>
  265. expect(err).to.not.exist
  266. expect(rw).to.equal false
  267. expect(ro).to.equal true
  268. done()
  269. describe 'when a read-and-write project is found', ->
  270. beforeEach ->
  271. @TokenAccessHandler.findProjectWithReadAndWriteToken = sinon.stub()
  272. .callsArgWith(1, null, @project)
  273. @TokenAccessHandler.findProjectWithReadOnlyToken = sinon.stub()
  274. .callsArgWith(1, null, null)
  275. it 'should try to find projects with both kinds of token', (done) ->
  276. @TokenAccessHandler.isValidToken @projectId, @token, (err, allowed) =>
  277. expect(@TokenAccessHandler.findProjectWithReadAndWriteToken.callCount)
  278. .to.equal 1
  279. expect(@TokenAccessHandler.findProjectWithReadOnlyToken.callCount)
  280. .to.equal 1
  281. done()
  282. it 'should allow read-and-write access', (done) ->
  283. @TokenAccessHandler.isValidToken @projectId, @token, (err, rw, ro) =>
  284. expect(err).to.not.exist
  285. expect(rw).to.equal true
  286. expect(ro).to.equal false
  287. done()
  288. describe 'when no project is found', ->
  289. beforeEach ->
  290. @TokenAccessHandler.findProjectWithReadAndWriteToken = sinon.stub()
  291. .callsArgWith(1, null, null)
  292. @TokenAccessHandler.findProjectWithReadOnlyToken = sinon.stub()
  293. .callsArgWith(1, null, null)
  294. it 'should try to find projects with both kinds of token', (done) ->
  295. @TokenAccessHandler.isValidToken @projectId, @token, (err, allowed) =>
  296. expect(@TokenAccessHandler.findProjectWithReadAndWriteToken.callCount)
  297. .to.equal 1
  298. expect(@TokenAccessHandler.findProjectWithReadOnlyToken.callCount)
  299. .to.equal 1
  300. done()
  301. it 'should not allow any access', (done) ->
  302. @TokenAccessHandler.isValidToken @projectId, @token, (err, rw, ro) =>
  303. expect(err).to.not.exist
  304. expect(rw).to.equal false
  305. expect(ro).to.equal false
  306. done()
  307. describe 'when findProject produces an error', ->
  308. beforeEach ->
  309. @TokenAccessHandler.findProjectWithReadAndWriteToken = sinon.stub()
  310. .callsArgWith(1, null, null)
  311. @TokenAccessHandler.findProjectWithReadOnlyToken = sinon.stub()
  312. .callsArgWith(1, new Error('woops'))
  313. it 'should try to find projects with both kinds of token', (done) ->
  314. @TokenAccessHandler.isValidToken @projectId, @token, (err, allowed) =>
  315. expect(@TokenAccessHandler.findProjectWithReadAndWriteToken.callCount)
  316. .to.equal 1
  317. expect(@TokenAccessHandler.findProjectWithReadOnlyToken.callCount)
  318. .to.equal 1
  319. done()
  320. it 'should produce an error and not allow access', (done) ->
  321. @TokenAccessHandler.isValidToken @projectId, @token, (err, rw, ro) =>
  322. expect(err).to.exist
  323. expect(err).to.be.instanceof Error
  324. expect(rw).to.equal undefined
  325. expect(ro).to.equal undefined
  326. done()
  327. describe 'when project is not set to token-based access', ->
  328. beforeEach ->
  329. @project.publicAccesLevel = 'private'
  330. describe 'for read-and-write project', ->
  331. beforeEach ->
  332. @TokenAccessHandler.findProjectWithReadAndWriteToken = sinon.stub()
  333. .callsArgWith(1, null, @project)
  334. @TokenAccessHandler.findProjectWithReadOnlyToken = sinon.stub()
  335. .callsArgWith(1, null, null)
  336. it 'should try to find projects with both kinds of token', (done) ->
  337. @TokenAccessHandler.isValidToken @projectId, @token, (err, allowed) =>
  338. expect(@TokenAccessHandler.findProjectWithReadAndWriteToken.callCount)
  339. .to.equal 1
  340. expect(@TokenAccessHandler.findProjectWithReadOnlyToken.callCount)
  341. .to.equal 1
  342. done()
  343. it 'should not allow any access', (done) ->
  344. @TokenAccessHandler.isValidToken @projectId, @token, (err, rw, ro) =>
  345. expect(err).to.not.exist
  346. expect(rw).to.equal false
  347. expect(ro).to.equal false
  348. done()
  349. describe 'for read-only project', ->
  350. beforeEach ->
  351. @TokenAccessHandler.findProjectWithReadAndWriteToken = sinon.stub()
  352. .callsArgWith(1, null, null)
  353. @TokenAccessHandler.findProjectWithReadOnlyToken = sinon.stub()
  354. .callsArgWith(1, null, @project)
  355. it 'should try to find projects with both kinds of token', (done) ->
  356. @TokenAccessHandler.isValidToken @projectId, @token, (err, allowed) =>
  357. expect(@TokenAccessHandler.findProjectWithReadAndWriteToken.callCount)
  358. .to.equal 1
  359. expect(@TokenAccessHandler.findProjectWithReadOnlyToken.callCount)
  360. .to.equal 1
  361. done()
  362. it 'should not allow any access', (done) ->
  363. @TokenAccessHandler.isValidToken @projectId, @token, (err, rw, ro) =>
  364. expect(err).to.not.exist
  365. expect(rw).to.equal false
  366. expect(ro).to.equal false
  367. done()
  368. describe 'with nothing', ->
  369. beforeEach ->
  370. @TokenAccessHandler.findProjectWithReadAndWriteToken = sinon.stub()
  371. .callsArgWith(1, null, @project)
  372. @TokenAccessHandler.findProjectWithReadOnlyToken = sinon.stub()
  373. .callsArgWith(1, null, null)
  374. it 'should not call findProjectWithReadOnlyToken', (done) ->
  375. @TokenAccessHandler.isValidToken @projectId, null, (err, allowed) =>
  376. expect(@TokenAccessHandler.findProjectWithReadOnlyToken.callCount)
  377. .to.equal 0
  378. done()
  379. it 'should try to find projects with both kinds of token', (done) ->
  380. @TokenAccessHandler.isValidToken @projectId, null, (err, allowed) =>
  381. expect(@TokenAccessHandler.findProjectWithReadAndWriteToken.callCount)
  382. .to.equal 0
  383. expect(@TokenAccessHandler.findProjectWithReadOnlyToken.callCount)
  384. .to.equal 0
  385. done()
  386. it 'should not allow any access', (done) ->
  387. @TokenAccessHandler.isValidToken @projectId, null, (err, rw, ro) =>
  388. expect(err).to.not.exist
  389. expect(rw).to.equal false
  390. expect(ro).to.equal false
  391. done()
  392. describe 'protectTokens', ->
  393. beforeEach ->
  394. @project = {tokens: {readAndWrite: 'rw', readOnly: 'ro'}}
  395. it 'should hide write token from read-only user', ->
  396. @TokenAccessHandler.protectTokens(@project, 'readOnly')
  397. expect(@project.tokens.readAndWrite).to.equal ''
  398. expect(@project.tokens.readOnly).to.equal 'ro'
  399. it 'should hide read token from read-write user', ->
  400. @TokenAccessHandler.protectTokens(@project, 'readAndWrite')
  401. expect(@project.tokens.readAndWrite).to.equal 'rw'
  402. expect(@project.tokens.readOnly).to.equal ''
  403. it 'should leave tokens in place for owner', ->
  404. @TokenAccessHandler.protectTokens(@project, 'owner')
  405. expect(@project.tokens.readAndWrite).to.equal 'rw'
  406. expect(@project.tokens.readOnly).to.equal 'ro'
  407. describe 'getV1DocInfo', ->
  408. beforeEach ->
  409. @callback = sinon.stub()
  410. describe 'when v1 api not set', ->
  411. beforeEach ->
  412. @TokenAccessHandler.getV1DocInfo @token, @callback
  413. it 'should not check access and return default info', ->
  414. expect(@V1Api.request.called).to.equal false
  415. expect(@callback.calledWith null, {
  416. allow: true
  417. exists: true
  418. exported: false
  419. }).to.equal true
  420. describe 'when v1 api is set', ->
  421. beforeEach ->
  422. @settings.apis = { v1: 'v1' }
  423. describe 'on success', ->
  424. beforeEach ->
  425. @V1Api.request = sinon.stub().callsArgWith(1, null, null, 'mock-data')
  426. @TokenAccessHandler.getV1DocInfo @token, @callback
  427. it 'should return response body', ->
  428. expect(@V1Api.request.calledWith { url: "/api/v1/sharelatex/docs/#{@token}/info" }).to.equal true
  429. expect(@callback.calledWith null, 'mock-data').to.equal true
  430. describe 'on error', ->
  431. beforeEach ->
  432. @V1Api.request = sinon.stub().callsArgWith(1, 'error')
  433. @TokenAccessHandler.getV1DocInfo @token, @callback
  434. it 'should callback with error', ->
  435. expect(@callback.calledWith 'error').to.equal true