TokenAccessHandlerTests.coffee 15 KB

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