TokenAccessHandlerTests.coffee 17 KB

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