AuthorizationMiddlewareTests.js 12 KB

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