AuthorizationMiddlewareTests.js 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461
  1. const sinon = require('sinon')
  2. const { expect } = require('chai')
  3. const SandboxedModule = require('sandboxed-module')
  4. const { ObjectId } = require('mongodb-legacy')
  5. const Errors = require('../../../../app/src/Features/Errors/Errors')
  6. const MODULE_PATH =
  7. '../../../../app/src/Features/Authorization/AuthorizationMiddleware.js'
  8. describe('AuthorizationMiddleware', function () {
  9. beforeEach(function () {
  10. this.userId = new ObjectId().toString()
  11. this.project_id = new ObjectId().toString()
  12. this.doc_id = new ObjectId().toString()
  13. this.thread_id = new ObjectId().toString()
  14. this.token = 'some-token'
  15. this.AuthenticationController = {}
  16. this.SessionManager = {
  17. getSessionUser: sinon.stub().returns(null),
  18. getLoggedInUserId: sinon.stub().returns(this.userId),
  19. isUserLoggedIn: sinon.stub().returns(true),
  20. }
  21. this.AuthorizationManager = {
  22. promises: {
  23. canUserReadProject: sinon.stub(),
  24. canUserWriteProjectSettings: sinon.stub(),
  25. canUserWriteProjectContent: sinon.stub(),
  26. canUserWriteOrReviewProjectContent: sinon.stub(),
  27. canUserDeleteOrResolveThread: sinon.stub(),
  28. canUserAdminProject: sinon.stub(),
  29. canUserRenameProject: sinon.stub(),
  30. isUserSiteAdmin: sinon.stub(),
  31. isRestrictedUserForProject: sinon.stub(),
  32. },
  33. }
  34. this.HttpErrorHandler = {
  35. forbidden: sinon.stub(),
  36. }
  37. this.TokenAccessHandler = {
  38. getRequestToken: sinon.stub().returns(this.token),
  39. }
  40. this.DocumentUpdaterHandler = {
  41. promises: {
  42. getComment: sinon.stub().resolves(),
  43. },
  44. }
  45. this.AuthorizationMiddleware = SandboxedModule.require(MODULE_PATH, {
  46. requires: {
  47. './AuthorizationManager': this.AuthorizationManager,
  48. '../Errors/HttpErrorHandler': this.HttpErrorHandler,
  49. 'mongodb-legacy': { ObjectId },
  50. '../Authentication/AuthenticationController':
  51. this.AuthenticationController,
  52. '../Authentication/SessionManager': this.SessionManager,
  53. '../TokenAccess/TokenAccessHandler': this.TokenAccessHandler,
  54. '../Helpers/AdminAuthorizationHelper': {
  55. canRedirectToAdminDomain: sinon.stub().returns(false),
  56. },
  57. '../DocumentUpdater/DocumentUpdaterHandler':
  58. this.DocumentUpdaterHandler,
  59. },
  60. })
  61. this.req = {
  62. params: {
  63. project_id: this.project_id,
  64. },
  65. body: {},
  66. }
  67. this.res = {
  68. redirect: sinon.stub(),
  69. locals: {
  70. currentUrl: '/current/url',
  71. },
  72. }
  73. this.next = sinon.stub()
  74. })
  75. describe('ensureCanReadProject', function () {
  76. testMiddleware('ensureUserCanReadProject', 'canUserReadProject')
  77. })
  78. describe('ensureUserCanWriteProjectContent', function () {
  79. testMiddleware(
  80. 'ensureUserCanWriteProjectContent',
  81. 'canUserWriteProjectContent'
  82. )
  83. })
  84. describe('ensureUserCanWriteOrReviewProjectContent', function () {
  85. testMiddleware(
  86. 'ensureUserCanWriteOrReviewProjectContent',
  87. 'canUserWriteOrReviewProjectContent'
  88. )
  89. })
  90. describe('ensureUserCanDeleteOrResolveThread', function () {
  91. beforeEach(function () {
  92. this.req.params.doc_id = this.doc_id
  93. this.req.params.thread_id = this.thread_id
  94. })
  95. describe('when user has permission', function () {
  96. beforeEach(function () {
  97. this.AuthorizationManager.promises.canUserDeleteOrResolveThread
  98. .withArgs(
  99. this.userId,
  100. this.project_id,
  101. this.doc_id,
  102. this.thread_id,
  103. this.token
  104. )
  105. .resolves(true)
  106. })
  107. invokeMiddleware('ensureUserCanDeleteOrResolveThread')
  108. expectNext()
  109. })
  110. describe("when user doesn't have permission", function () {
  111. beforeEach(function () {
  112. this.AuthorizationManager.promises.canUserDeleteOrResolveThread
  113. .withArgs(
  114. this.userId,
  115. this.project_id,
  116. this.doc_id,
  117. this.thread_id,
  118. this.token
  119. )
  120. .resolves(false)
  121. })
  122. invokeMiddleware('ensureUserCanDeleteOrResolveThread')
  123. expectForbidden()
  124. })
  125. })
  126. describe('ensureUserCanWriteProjectSettings', function () {
  127. describe('when renaming a project', function () {
  128. beforeEach(function () {
  129. this.req.body.name = 'new project name'
  130. })
  131. testMiddleware(
  132. 'ensureUserCanWriteProjectSettings',
  133. 'canUserRenameProject'
  134. )
  135. })
  136. describe('when setting another parameter', function () {
  137. beforeEach(function () {
  138. this.req.body.compiler = 'texlive-2017'
  139. })
  140. testMiddleware(
  141. 'ensureUserCanWriteProjectSettings',
  142. 'canUserWriteProjectSettings'
  143. )
  144. })
  145. })
  146. describe('ensureUserCanAdminProject', function () {
  147. testMiddleware('ensureUserCanAdminProject', 'canUserAdminProject')
  148. })
  149. describe('ensureUserIsSiteAdmin', function () {
  150. describe('with logged in user', function () {
  151. describe('when user has permission', function () {
  152. setupSiteAdmin(true)
  153. invokeMiddleware('ensureUserIsSiteAdmin')
  154. expectNext()
  155. })
  156. describe("when user doesn't have permission", function () {
  157. setupSiteAdmin(false)
  158. invokeMiddleware('ensureUserIsSiteAdmin')
  159. expectRedirectToRestricted()
  160. })
  161. })
  162. describe('with oauth user', function () {
  163. setupOAuthUser()
  164. describe('when user has permission', function () {
  165. setupSiteAdmin(true)
  166. invokeMiddleware('ensureUserIsSiteAdmin')
  167. expectNext()
  168. })
  169. describe("when user doesn't have permission", function () {
  170. setupSiteAdmin(false)
  171. invokeMiddleware('ensureUserIsSiteAdmin')
  172. expectRedirectToRestricted()
  173. })
  174. })
  175. describe('with anonymous user', function () {
  176. setupAnonymousUser()
  177. invokeMiddleware('ensureUserIsSiteAdmin')
  178. expectRedirectToRestricted()
  179. })
  180. })
  181. describe('blockRestrictedUserFromProject', function () {
  182. describe('for a restricted user', function () {
  183. setupPermission('isRestrictedUserForProject', true)
  184. invokeMiddleware('blockRestrictedUserFromProject')
  185. expectForbidden()
  186. })
  187. describe('for a regular user', function (done) {
  188. setupPermission('isRestrictedUserForProject', false)
  189. invokeMiddleware('blockRestrictedUserFromProject')
  190. expectNext()
  191. })
  192. })
  193. describe('ensureUserCanReadMultipleProjects', function () {
  194. beforeEach(function () {
  195. this.req.query = { project_ids: 'project1,project2' }
  196. })
  197. describe('with logged in user', function () {
  198. describe('when user has permission to access all projects', function () {
  199. beforeEach(function () {
  200. this.AuthorizationManager.promises.canUserReadProject
  201. .withArgs(this.userId, 'project1', this.token)
  202. .resolves(true)
  203. this.AuthorizationManager.promises.canUserReadProject
  204. .withArgs(this.userId, 'project2', this.token)
  205. .resolves(true)
  206. })
  207. invokeMiddleware('ensureUserCanReadMultipleProjects')
  208. expectNext()
  209. })
  210. describe("when user doesn't have permission to access one of the projects", function () {
  211. beforeEach(function () {
  212. this.AuthorizationManager.promises.canUserReadProject
  213. .withArgs(this.userId, 'project1', this.token)
  214. .resolves(true)
  215. this.AuthorizationManager.promises.canUserReadProject
  216. .withArgs(this.userId, 'project2', this.token)
  217. .resolves(false)
  218. })
  219. invokeMiddleware('ensureUserCanReadMultipleProjects')
  220. expectRedirectToRestricted()
  221. })
  222. })
  223. describe('with oauth user', function () {
  224. setupOAuthUser()
  225. beforeEach(function () {
  226. this.AuthorizationManager.promises.canUserReadProject
  227. .withArgs(this.userId, 'project1', this.token)
  228. .resolves(true)
  229. this.AuthorizationManager.promises.canUserReadProject
  230. .withArgs(this.userId, 'project2', this.token)
  231. .resolves(true)
  232. })
  233. invokeMiddleware('ensureUserCanReadMultipleProjects')
  234. expectNext()
  235. })
  236. describe('with anonymous user', function () {
  237. setupAnonymousUser()
  238. describe('when user has permission', function () {
  239. describe('when user has permission to access all projects', function () {
  240. beforeEach(function () {
  241. this.AuthorizationManager.promises.canUserReadProject
  242. .withArgs(null, 'project1', this.token)
  243. .resolves(true)
  244. this.AuthorizationManager.promises.canUserReadProject
  245. .withArgs(null, 'project2', this.token)
  246. .resolves(true)
  247. })
  248. invokeMiddleware('ensureUserCanReadMultipleProjects')
  249. expectNext()
  250. })
  251. describe("when user doesn't have permission to access one of the projects", function () {
  252. beforeEach(function () {
  253. this.AuthorizationManager.promises.canUserReadProject
  254. .withArgs(null, 'project1', this.token)
  255. .resolves(true)
  256. this.AuthorizationManager.promises.canUserReadProject
  257. .withArgs(null, 'project2', this.token)
  258. .resolves(false)
  259. })
  260. invokeMiddleware('ensureUserCanReadMultipleProjects')
  261. expectRedirectToRestricted()
  262. })
  263. })
  264. })
  265. })
  266. })
  267. function testMiddleware(middleware, permission) {
  268. describe(middleware, function () {
  269. describe('with missing project_id', function () {
  270. setupMissingProjectId()
  271. invokeMiddleware(middleware)
  272. expectError()
  273. })
  274. describe('with logged in user', function () {
  275. describe('when user has permission', function () {
  276. setupPermission(permission, true)
  277. invokeMiddleware(middleware)
  278. expectNext()
  279. })
  280. describe("when user doesn't have permission", function () {
  281. setupPermission(permission, false)
  282. invokeMiddleware(middleware)
  283. expectForbidden()
  284. })
  285. })
  286. describe('with oauth user', function () {
  287. setupOAuthUser()
  288. describe('when user has permission', function () {
  289. setupPermission(permission, true)
  290. invokeMiddleware(middleware)
  291. expectNext()
  292. })
  293. describe("when user doesn't have permission", function () {
  294. setupPermission(permission, false)
  295. invokeMiddleware(middleware)
  296. expectForbidden()
  297. })
  298. })
  299. describe('with anonymous user', function () {
  300. setupAnonymousUser()
  301. describe('when user has permission', function () {
  302. setupAnonymousPermission(permission, true)
  303. invokeMiddleware(middleware)
  304. expectNext()
  305. })
  306. describe("when user doesn't have permission", function () {
  307. setupAnonymousPermission(permission, false)
  308. invokeMiddleware(middleware)
  309. expectForbidden()
  310. })
  311. })
  312. describe('with malformed project id', function () {
  313. setupMalformedProjectId()
  314. invokeMiddleware(middleware)
  315. expectNotFound()
  316. })
  317. })
  318. }
  319. function setupAnonymousUser() {
  320. beforeEach('set up anonymous user', function () {
  321. this.SessionManager.getLoggedInUserId.returns(null)
  322. this.SessionManager.isUserLoggedIn.returns(false)
  323. })
  324. }
  325. function setupOAuthUser() {
  326. beforeEach('set up oauth user', function () {
  327. this.SessionManager.getLoggedInUserId.returns(null)
  328. this.req.oauth_user = { _id: this.userId }
  329. })
  330. }
  331. function setupPermission(permission, value) {
  332. beforeEach(`set permission ${permission} to ${value}`, function () {
  333. this.AuthorizationManager.promises[permission]
  334. .withArgs(this.userId, this.project_id, this.token)
  335. .resolves(value)
  336. })
  337. }
  338. function setupAnonymousPermission(permission, value) {
  339. beforeEach(`set anonymous permission ${permission} to ${value}`, function () {
  340. this.AuthorizationManager.promises[permission]
  341. .withArgs(null, this.project_id, this.token)
  342. .resolves(value)
  343. })
  344. }
  345. function setupSiteAdmin(value) {
  346. beforeEach(`set site admin to ${value}`, function () {
  347. this.AuthorizationManager.promises.isUserSiteAdmin
  348. .withArgs(this.userId)
  349. .resolves(value)
  350. })
  351. }
  352. function setupMissingProjectId() {
  353. beforeEach('set up missing project id', function () {
  354. delete this.req.params.project_id
  355. })
  356. }
  357. function setupMalformedProjectId() {
  358. beforeEach('set up malformed project id', function () {
  359. this.req.params = { project_id: 'bad-project-id' }
  360. })
  361. }
  362. function invokeMiddleware(method) {
  363. beforeEach(`invoke ${method}`, function (done) {
  364. this.next.callsFake(() => done())
  365. this.HttpErrorHandler.forbidden.callsFake(() => done())
  366. this.res.redirect.callsFake(() => done())
  367. this.AuthorizationMiddleware[method](this.req, this.res, this.next)
  368. })
  369. }
  370. function expectNext() {
  371. it('calls the next middleware', function () {
  372. expect(this.next).to.have.been.calledWithExactly()
  373. })
  374. }
  375. function expectError() {
  376. it('calls the error middleware', function () {
  377. expect(this.next).to.have.been.calledWith(sinon.match.instanceOf(Error))
  378. })
  379. }
  380. function expectNotFound() {
  381. it('raises a 404', function () {
  382. expect(this.next).to.have.been.calledWith(
  383. sinon.match.instanceOf(Errors.NotFoundError)
  384. )
  385. })
  386. }
  387. function expectForbidden() {
  388. it('raises a 403', function () {
  389. expect(this.HttpErrorHandler.forbidden).to.have.been.calledWith(
  390. this.req,
  391. this.res
  392. )
  393. expect(this.next).not.to.have.been.called
  394. })
  395. }
  396. function expectRedirectToRestricted() {
  397. it('redirects to restricted', function () {
  398. expect(this.res.redirect).to.have.been.calledWith(
  399. '/restricted?from=%2Fcurrent%2Furl'
  400. )
  401. expect(this.next).not.to.have.been.called
  402. })
  403. }