AuthorizationMiddlewareTests.js 12 KB

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