AuthorizationMiddlewareTests.js 12 KB

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