AuthorizationMiddlewareTests.js 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546
  1. const sinon = require('sinon')
  2. const { expect } = require('chai')
  3. const SandboxedModule = require('sandboxed-module')
  4. const HttpErrors = require('@overleaf/o-error/http')
  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 = 'user-id-123'
  11. this.project_id = 'project-id-123'
  12. this.token = 'some-token'
  13. this.AuthenticationController = {
  14. getLoggedInUserId: sinon.stub().returns(this.userId),
  15. isUserLoggedIn: sinon.stub().returns(true)
  16. }
  17. this.AuthorizationManager = {}
  18. this.TokenAccessHandler = {
  19. getRequestToken: sinon.stub().returns(this.token)
  20. }
  21. this.ObjectId = {
  22. isValid: sinon
  23. .stub()
  24. .withArgs(this.project_id)
  25. .returns(true)
  26. }
  27. this.AuthorizationManager = {}
  28. this.AuthorizationMiddleware = SandboxedModule.require(MODULE_PATH, {
  29. globals: {
  30. console: console
  31. },
  32. requires: {
  33. './AuthorizationManager': this.AuthorizationManager,
  34. 'logger-sharelatex': { log() {} },
  35. mongojs: {
  36. ObjectId: this.ObjectId
  37. },
  38. '@overleaf/o-error/http': HttpErrors,
  39. '../Errors/Errors': Errors,
  40. '../Authentication/AuthenticationController': this
  41. .AuthenticationController,
  42. '../TokenAccess/TokenAccessHandler': this.TokenAccessHandler
  43. }
  44. })
  45. this.req = {}
  46. this.res = {}
  47. this.next = sinon.stub()
  48. })
  49. describe('_getUserId', function() {
  50. beforeEach(function() {
  51. this.req = {}
  52. })
  53. it('should get the user from session', function(done) {
  54. this.AuthenticationController.getLoggedInUserId = sinon
  55. .stub()
  56. .returns('1234')
  57. this.AuthorizationMiddleware._getUserId(this.req, (err, userId) => {
  58. expect(err).to.not.exist
  59. expect(userId).to.equal('1234')
  60. done()
  61. })
  62. })
  63. it('should get oauth_user from request', function(done) {
  64. this.AuthenticationController.getLoggedInUserId = sinon
  65. .stub()
  66. .returns(null)
  67. this.req.oauth_user = { _id: '5678' }
  68. this.AuthorizationMiddleware._getUserId(this.req, (err, userId) => {
  69. expect(err).to.not.exist
  70. expect(userId).to.equal('5678')
  71. done()
  72. })
  73. })
  74. it('should fall back to null', function(done) {
  75. this.AuthenticationController.getLoggedInUserId = sinon
  76. .stub()
  77. .returns(null)
  78. this.req.oauth_user = undefined
  79. this.AuthorizationMiddleware._getUserId(this.req, (err, userId) => {
  80. expect(err).to.not.exist
  81. expect(userId).to.equal(null)
  82. done()
  83. })
  84. })
  85. })
  86. const METHODS_TO_TEST = {
  87. ensureUserCanReadProject: 'canUserReadProject',
  88. ensureUserCanWriteProjectSettings: 'canUserWriteProjectSettings',
  89. ensureUserCanWriteProjectContent: 'canUserWriteProjectContent'
  90. }
  91. Object.entries(METHODS_TO_TEST).forEach(
  92. ([middlewareMethod, managerMethod]) => {
  93. describe(middlewareMethod, function() {
  94. beforeEach(function() {
  95. this.req.params = { project_id: this.project_id }
  96. this.AuthorizationManager[managerMethod] = sinon.stub()
  97. this.AuthorizationMiddleware.redirectToRestricted = sinon.stub()
  98. })
  99. describe('with missing project_id', function() {
  100. beforeEach(function() {
  101. this.req.params = {}
  102. })
  103. it('should return an error to next', function() {
  104. this.AuthorizationMiddleware[middlewareMethod](
  105. this.req,
  106. this.res,
  107. this.next
  108. )
  109. this.next.calledWith(new Error()).should.equal(true)
  110. })
  111. })
  112. describe('with logged in user', function() {
  113. beforeEach(function() {
  114. this.AuthenticationController.getLoggedInUserId.returns(this.userId)
  115. })
  116. describe('when user has permission', function() {
  117. beforeEach(function() {
  118. this.AuthorizationManager[managerMethod]
  119. .withArgs(this.userId, this.project_id, this.token)
  120. .yields(null, true)
  121. })
  122. it('should return next', function() {
  123. this.AuthorizationMiddleware[middlewareMethod](
  124. this.req,
  125. this.res,
  126. this.next
  127. )
  128. this.next.called.should.equal(true)
  129. })
  130. })
  131. describe("when user doesn't have permission", function() {
  132. beforeEach(function() {
  133. this.AuthorizationManager[managerMethod]
  134. .withArgs(this.userId, this.project_id, this.token)
  135. .yields(null, false)
  136. })
  137. it('should raise a 403', function() {
  138. this.AuthorizationMiddleware[middlewareMethod](
  139. this.req,
  140. this.res,
  141. this.next
  142. )
  143. this.next.called.should.equal(false)
  144. this.AuthorizationMiddleware.redirectToRestricted
  145. .calledWith(this.req, this.res, this.next)
  146. .should.equal(true)
  147. })
  148. })
  149. })
  150. describe('with anonymous user', function() {
  151. describe('when user has permission', function() {
  152. beforeEach(function() {
  153. this.AuthenticationController.getLoggedInUserId.returns(null)
  154. this.AuthorizationManager[managerMethod]
  155. .withArgs(null, this.project_id, this.token)
  156. .yields(null, true)
  157. })
  158. it('should return next', function() {
  159. this.AuthorizationMiddleware[middlewareMethod](
  160. this.req,
  161. this.res,
  162. this.next
  163. )
  164. this.next.called.should.equal(true)
  165. })
  166. })
  167. describe("when user doesn't have permission", function() {
  168. beforeEach(function() {
  169. this.AuthenticationController.getLoggedInUserId.returns(null)
  170. this.AuthorizationManager[managerMethod]
  171. .withArgs(null, this.project_id, this.token)
  172. .yields(null, false)
  173. })
  174. it('should redirect to redirectToRestricted', function() {
  175. this.AuthorizationMiddleware[middlewareMethod](
  176. this.req,
  177. this.res,
  178. this.next
  179. )
  180. this.next.called.should.equal(false)
  181. this.AuthorizationMiddleware.redirectToRestricted
  182. .calledWith(this.req, this.res, this.next)
  183. .should.equal(true)
  184. })
  185. })
  186. })
  187. describe('with malformed project id', function() {
  188. beforeEach(function() {
  189. this.req.params = { project_id: 'blah' }
  190. this.ObjectId.isValid = sinon.stub().returns(false)
  191. })
  192. it('should return a not found error', function(done) {
  193. this.AuthorizationMiddleware[middlewareMethod](
  194. this.req,
  195. this.res,
  196. error => {
  197. error.should.be.instanceof(Errors.NotFoundError)
  198. return done()
  199. }
  200. )
  201. })
  202. })
  203. })
  204. }
  205. )
  206. describe('ensureUserCanAdminProject', function() {
  207. beforeEach(function() {
  208. this.req.params = { project_id: this.project_id }
  209. this.AuthorizationManager.canUserAdminProject = sinon.stub()
  210. this.AuthorizationMiddleware.redirectToRestricted = sinon.stub()
  211. })
  212. describe('with missing project_id', function() {
  213. beforeEach(function() {
  214. this.req.params = {}
  215. })
  216. it('should return an error to next', function() {
  217. this.AuthorizationMiddleware.ensureUserCanAdminProject(
  218. this.req,
  219. this.res,
  220. this.next
  221. )
  222. this.next.calledWith(new Error()).should.equal(true)
  223. })
  224. })
  225. describe('with logged in user', function() {
  226. beforeEach(function() {
  227. this.AuthenticationController.getLoggedInUserId.returns(this.userId)
  228. })
  229. describe('when user has permission', function() {
  230. beforeEach(function() {
  231. this.AuthorizationManager.canUserAdminProject
  232. .withArgs(this.userId, this.project_id, this.token)
  233. .yields(null, true)
  234. })
  235. it('should return next', function() {
  236. this.AuthorizationMiddleware.ensureUserCanAdminProject(
  237. this.req,
  238. this.res,
  239. this.next
  240. )
  241. this.next.called.should.equal(true)
  242. })
  243. })
  244. describe("when user doesn't have permission", function() {
  245. beforeEach(function() {
  246. this.AuthorizationManager.canUserAdminProject
  247. .withArgs(this.userId, this.project_id, this.token)
  248. .yields(null, false)
  249. })
  250. it('should raise a 403', function(done) {
  251. this.AuthorizationMiddleware.ensureUserCanAdminProject(
  252. this.req,
  253. this.res,
  254. err => {
  255. expect(err).to.be.an.instanceof(HttpErrors.ForbiddenError)
  256. done()
  257. }
  258. )
  259. })
  260. })
  261. })
  262. describe('with anonymous user', function() {
  263. describe('when user has permission', function() {
  264. beforeEach(function() {
  265. this.AuthenticationController.getLoggedInUserId.returns(null)
  266. this.AuthorizationManager.canUserAdminProject
  267. .withArgs(null, this.project_id, this.token)
  268. .yields(null, true)
  269. })
  270. it('should return next', function() {
  271. this.AuthorizationMiddleware.ensureUserCanAdminProject(
  272. this.req,
  273. this.res,
  274. this.next
  275. )
  276. this.next.called.should.equal(true)
  277. })
  278. })
  279. describe("when user doesn't have permission", function() {
  280. beforeEach(function() {
  281. this.AuthenticationController.getLoggedInUserId.returns(null)
  282. this.AuthorizationManager.canUserAdminProject
  283. .withArgs(null, this.project_id, this.token)
  284. .yields(null, false)
  285. })
  286. it('should raise a 403', function(done) {
  287. this.AuthorizationMiddleware.ensureUserCanAdminProject(
  288. this.req,
  289. this.res,
  290. err => {
  291. expect(err).to.be.an.instanceof(HttpErrors.ForbiddenError)
  292. done()
  293. }
  294. )
  295. })
  296. })
  297. })
  298. describe('with malformed project id', function() {
  299. beforeEach(function() {
  300. this.req.params = { project_id: 'blah' }
  301. this.ObjectId.isValid = sinon.stub().returns(false)
  302. })
  303. it('should return a not found error', function(done) {
  304. this.AuthorizationMiddleware.ensureUserCanAdminProject(
  305. this.req,
  306. this.res,
  307. error => {
  308. error.should.be.instanceof(Errors.NotFoundError)
  309. return done()
  310. }
  311. )
  312. })
  313. })
  314. })
  315. describe('ensureUserIsSiteAdmin', function() {
  316. beforeEach(function() {
  317. this.AuthorizationManager.isUserSiteAdmin = sinon.stub()
  318. this.AuthorizationMiddleware.redirectToRestricted = sinon.stub()
  319. })
  320. describe('with logged in user', function() {
  321. beforeEach(function() {
  322. this.AuthenticationController.getLoggedInUserId.returns(this.userId)
  323. })
  324. describe('when user has permission', function() {
  325. beforeEach(function() {
  326. this.AuthorizationManager.isUserSiteAdmin
  327. .withArgs(this.userId)
  328. .yields(null, true)
  329. })
  330. it('should return next', function() {
  331. this.AuthorizationMiddleware.ensureUserIsSiteAdmin(
  332. this.req,
  333. this.res,
  334. this.next
  335. )
  336. this.next.called.should.equal(true)
  337. })
  338. })
  339. describe("when user doesn't have permission", function() {
  340. beforeEach(function() {
  341. this.AuthorizationManager.isUserSiteAdmin
  342. .withArgs(this.userId)
  343. .yields(null, false)
  344. })
  345. it('should redirect to redirectToRestricted', function() {
  346. this.AuthorizationMiddleware.ensureUserIsSiteAdmin(
  347. this.req,
  348. this.res,
  349. this.next
  350. )
  351. this.next.called.should.equal(false)
  352. this.AuthorizationMiddleware.redirectToRestricted
  353. .calledWith(this.req, this.res, this.next)
  354. .should.equal(true)
  355. })
  356. })
  357. })
  358. describe('with anonymous user', function() {
  359. describe('when user has permission', function() {
  360. beforeEach(function() {
  361. this.AuthenticationController.getLoggedInUserId.returns(null)
  362. this.AuthorizationManager.isUserSiteAdmin
  363. .withArgs(null)
  364. .yields(null, true)
  365. })
  366. it('should return next', function() {
  367. this.AuthorizationMiddleware.ensureUserIsSiteAdmin(
  368. this.req,
  369. this.res,
  370. this.next
  371. )
  372. this.next.called.should.equal(true)
  373. })
  374. })
  375. describe("when user doesn't have permission", function() {
  376. beforeEach(function() {
  377. this.AuthenticationController.getLoggedInUserId.returns(null)
  378. this.AuthorizationManager.isUserSiteAdmin
  379. .withArgs(null)
  380. .yields(null, false)
  381. })
  382. it('should redirect to redirectToRestricted', function() {
  383. this.AuthorizationMiddleware.ensureUserIsSiteAdmin(
  384. this.req,
  385. this.res,
  386. this.next
  387. )
  388. this.next.called.should.equal(false)
  389. this.AuthorizationMiddleware.redirectToRestricted
  390. .calledWith(this.req, this.res, this.next)
  391. .should.equal(true)
  392. })
  393. })
  394. })
  395. })
  396. describe('ensureUserCanReadMultipleProjects', function() {
  397. beforeEach(function() {
  398. this.AuthorizationManager.canUserReadProject = sinon.stub()
  399. this.AuthorizationMiddleware.redirectToRestricted = sinon.stub()
  400. this.req.query = { project_ids: 'project1,project2' }
  401. })
  402. describe('with logged in user', function() {
  403. beforeEach(function() {
  404. this.AuthenticationController.getLoggedInUserId.returns(this.userId)
  405. })
  406. describe('when user has permission to access all projects', function() {
  407. beforeEach(function() {
  408. this.AuthorizationManager.canUserReadProject
  409. .withArgs(this.userId, 'project1', this.token)
  410. .yields(null, true)
  411. this.AuthorizationManager.canUserReadProject
  412. .withArgs(this.userId, 'project2', this.token)
  413. .yields(null, true)
  414. })
  415. it('should return next', function() {
  416. this.AuthorizationMiddleware.ensureUserCanReadMultipleProjects(
  417. this.req,
  418. this.res,
  419. this.next
  420. )
  421. this.next.called.should.equal(true)
  422. })
  423. })
  424. describe("when user doesn't have permission to access one of the projects", function() {
  425. beforeEach(function() {
  426. this.AuthorizationManager.canUserReadProject
  427. .withArgs(this.userId, 'project1', this.token)
  428. .yields(null, true)
  429. this.AuthorizationManager.canUserReadProject
  430. .withArgs(this.userId, 'project2', this.token)
  431. .yields(null, false)
  432. })
  433. it('should redirect to redirectToRestricted', function() {
  434. this.AuthorizationMiddleware.ensureUserCanReadMultipleProjects(
  435. this.req,
  436. this.res,
  437. this.next
  438. )
  439. this.next.called.should.equal(false)
  440. this.AuthorizationMiddleware.redirectToRestricted
  441. .calledWith(this.req, this.res, this.next)
  442. .should.equal(true)
  443. })
  444. })
  445. })
  446. describe('with anonymous user', function() {
  447. describe('when user has permission', function() {
  448. describe('when user has permission to access all projects', function() {
  449. beforeEach(function() {
  450. this.AuthenticationController.getLoggedInUserId.returns(null)
  451. this.AuthorizationManager.canUserReadProject
  452. .withArgs(null, 'project1', this.token)
  453. .yields(null, true)
  454. this.AuthorizationManager.canUserReadProject
  455. .withArgs(null, 'project2', this.token)
  456. .yields(null, true)
  457. })
  458. it('should return next', function() {
  459. this.AuthorizationMiddleware.ensureUserCanReadMultipleProjects(
  460. this.req,
  461. this.res,
  462. this.next
  463. )
  464. this.next.called.should.equal(true)
  465. })
  466. })
  467. describe("when user doesn't have permission to access one of the projects", function() {
  468. beforeEach(function() {
  469. this.AuthenticationController.getLoggedInUserId.returns(null)
  470. this.AuthorizationManager.canUserReadProject
  471. .withArgs(null, 'project1', this.token)
  472. .yields(null, true)
  473. this.AuthorizationManager.canUserReadProject
  474. .withArgs(null, 'project2', this.token)
  475. .yields(null, false)
  476. })
  477. it('should redirect to redirectToRestricted', function() {
  478. this.AuthorizationMiddleware.ensureUserCanReadMultipleProjects(
  479. this.req,
  480. this.res,
  481. this.next
  482. )
  483. this.next.called.should.equal(false)
  484. this.AuthorizationMiddleware.redirectToRestricted
  485. .calledWith(this.req, this.res, this.next)
  486. .should.equal(true)
  487. })
  488. })
  489. })
  490. })
  491. })
  492. })