| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493 |
- import { vi, expect } from 'vitest'
- import sinon from 'sinon'
- import mongodb from 'mongodb-legacy'
- import Errors from '../../../../app/src/Features/Errors/Errors.js'
- const { ObjectId } = mongodb
- const MODULE_PATH =
- '../../../../app/src/Features/Authorization/AuthorizationMiddleware.mjs'
- describe('AuthorizationMiddleware', function () {
- beforeEach(async function (ctx) {
- ctx.userId = new ObjectId().toString()
- ctx.project_id = new ObjectId().toString()
- ctx.doc_id = new ObjectId().toString()
- ctx.thread_id = new ObjectId().toString()
- ctx.token = 'some-token'
- ctx.AuthenticationController = {}
- ctx.SessionManager = {
- getSessionUser: sinon.stub().returns(null),
- getLoggedInUserId: sinon.stub().returns(ctx.userId),
- isUserLoggedIn: sinon.stub().returns(true),
- }
- ctx.AuthorizationManager = {
- promises: {
- canUserReadProject: sinon.stub(),
- canUserWriteProjectSettings: sinon.stub(),
- canUserWriteProjectContent: sinon.stub(),
- canUserWriteOrReviewProjectContent: sinon.stub(),
- canUserDeleteOrResolveThread: sinon.stub(),
- canUserAdminProject: sinon.stub(),
- canUserRenameProject: sinon.stub(),
- isUserSiteAdmin: sinon.stub(),
- isRestrictedUserForProject: sinon.stub(),
- },
- }
- ctx.HttpErrorHandler = {
- forbidden: sinon.stub(),
- }
- ctx.TokenAccessHandler = {
- getRequestToken: sinon.stub().returns(ctx.token),
- }
- ctx.DocumentUpdaterHandler = {
- promises: {
- getComment: sinon.stub().resolves(),
- },
- }
- vi.doMock('../../../../app/src/Features/Errors/Errors.js', () => ({
- default: Errors,
- }))
- vi.doMock(
- '../../../../app/src/Features/Authorization/AuthorizationManager',
- () => ({
- default: ctx.AuthorizationManager,
- })
- )
- vi.doMock('../../../../app/src/Features/Errors/HttpErrorHandler', () => ({
- default: ctx.HttpErrorHandler,
- }))
- vi.doMock('mongodb-legacy', () => ({
- default: { ObjectId },
- }))
- vi.doMock(
- '../../../../app/src/Features/Authentication/AuthenticationController',
- () => ({
- default: ctx.AuthenticationController,
- })
- )
- vi.doMock(
- '../../../../app/src/Features/Authentication/SessionManager',
- () => ({
- default: ctx.SessionManager,
- })
- )
- vi.doMock(
- '../../../../app/src/Features/TokenAccess/TokenAccessHandler',
- () => ({
- default: ctx.TokenAccessHandler,
- })
- )
- vi.doMock(
- '../../../../app/src/Features/Helpers/AdminAuthorizationHelper',
- () => ({
- default: {
- canRedirectToAdminDomain: sinon.stub().returns(false),
- },
- })
- )
- vi.doMock(
- '../../../../app/src/Features/DocumentUpdater/DocumentUpdaterHandler',
- () => ({
- default: ctx.DocumentUpdaterHandler,
- })
- )
- ctx.AuthorizationMiddleware = (await import(MODULE_PATH)).default
- ctx.req = {
- params: {
- project_id: ctx.project_id,
- },
- body: {},
- }
- ctx.res = {
- redirect: sinon.stub(),
- locals: {
- currentUrl: '/current/url',
- },
- }
- ctx.next = sinon.stub()
- })
- describe('ensureCanReadProject', function () {
- testMiddleware('ensureUserCanReadProject', 'canUserReadProject')
- })
- describe('ensureUserCanWriteProjectContent', function () {
- testMiddleware(
- 'ensureUserCanWriteProjectContent',
- 'canUserWriteProjectContent'
- )
- })
- describe('ensureUserCanWriteOrReviewProjectContent', function () {
- testMiddleware(
- 'ensureUserCanWriteOrReviewProjectContent',
- 'canUserWriteOrReviewProjectContent'
- )
- })
- describe('ensureUserCanDeleteOrResolveThread', function () {
- beforeEach(function (ctx) {
- ctx.req.params.thread_id = ctx.thread_id
- })
- describe('when user has permission', function () {
- beforeEach(function (ctx) {
- ctx.AuthorizationManager.promises.canUserDeleteOrResolveThread
- .withArgs(ctx.userId, ctx.project_id, ctx.thread_id, ctx.token)
- .resolves(true)
- })
- invokeMiddleware('ensureUserCanDeleteOrResolveThread')
- expectNext()
- })
- describe("when user doesn't have permission", function () {
- beforeEach(function (ctx) {
- ctx.AuthorizationManager.promises.canUserDeleteOrResolveThread
- .withArgs(ctx.userId, ctx.project_id, ctx.thread_id, ctx.token)
- .resolves(false)
- })
- invokeMiddleware('ensureUserCanDeleteOrResolveThread')
- expectForbidden()
- })
- })
- describe('ensureUserCanWriteProjectSettings', function () {
- describe('when renaming a project', function () {
- beforeEach(function (ctx) {
- ctx.req.body.name = 'new project name'
- })
- testMiddleware(
- 'ensureUserCanWriteProjectSettings',
- 'canUserRenameProject'
- )
- })
- describe('when setting another parameter', function () {
- beforeEach(function (ctx) {
- ctx.req.body.compiler = 'texlive-2017'
- })
- testMiddleware(
- 'ensureUserCanWriteProjectSettings',
- 'canUserWriteProjectSettings'
- )
- })
- })
- describe('ensureUserCanAdminProject', function () {
- testMiddleware('ensureUserCanAdminProject', 'canUserAdminProject')
- })
- describe('ensureUserIsSiteAdmin', function () {
- describe('with logged in user', function () {
- describe('when user has permission', function () {
- setupSiteAdmin(true)
- invokeMiddleware('ensureUserIsSiteAdmin')
- expectNext()
- })
- describe("when user doesn't have permission", function () {
- setupSiteAdmin(false)
- invokeMiddleware('ensureUserIsSiteAdmin')
- expectRedirectToRestricted()
- })
- })
- describe('with oauth user', function () {
- setupOAuthUser()
- describe('when user has permission', function () {
- setupSiteAdmin(true)
- invokeMiddleware('ensureUserIsSiteAdmin')
- expectNext()
- })
- describe("when user doesn't have permission", function () {
- setupSiteAdmin(false)
- invokeMiddleware('ensureUserIsSiteAdmin')
- expectRedirectToRestricted()
- })
- })
- describe('with anonymous user', function () {
- setupAnonymousUser()
- invokeMiddleware('ensureUserIsSiteAdmin')
- expectRedirectToRestricted()
- })
- })
- describe('blockRestrictedUserFromProject', function () {
- describe('for a restricted user', function () {
- setupPermission('isRestrictedUserForProject', true)
- invokeMiddleware('blockRestrictedUserFromProject')
- expectForbidden()
- })
- describe('for a regular user', function () {
- setupPermission('isRestrictedUserForProject', false)
- invokeMiddleware('blockRestrictedUserFromProject')
- expectNext()
- })
- })
- describe('ensureUserCanReadMultipleProjects', function () {
- beforeEach(function (ctx) {
- ctx.req.query = { project_ids: 'project1,project2' }
- })
- describe('with logged in user', function () {
- describe('when user has permission to access all projects', function () {
- beforeEach(function (ctx) {
- ctx.AuthorizationManager.promises.canUserReadProject
- .withArgs(ctx.userId, 'project1', ctx.token)
- .resolves(true)
- ctx.AuthorizationManager.promises.canUserReadProject
- .withArgs(ctx.userId, 'project2', ctx.token)
- .resolves(true)
- })
- invokeMiddleware('ensureUserCanReadMultipleProjects')
- expectNext()
- })
- describe("when user doesn't have permission to access one of the projects", function () {
- beforeEach(function (ctx) {
- ctx.AuthorizationManager.promises.canUserReadProject
- .withArgs(ctx.userId, 'project1', ctx.token)
- .resolves(true)
- ctx.AuthorizationManager.promises.canUserReadProject
- .withArgs(ctx.userId, 'project2', ctx.token)
- .resolves(false)
- })
- invokeMiddleware('ensureUserCanReadMultipleProjects')
- expectRedirectToRestricted()
- })
- })
- describe('with oauth user', function () {
- setupOAuthUser()
- beforeEach(function (ctx) {
- ctx.AuthorizationManager.promises.canUserReadProject
- .withArgs(ctx.userId, 'project1', ctx.token)
- .resolves(true)
- ctx.AuthorizationManager.promises.canUserReadProject
- .withArgs(ctx.userId, 'project2', ctx.token)
- .resolves(true)
- })
- invokeMiddleware('ensureUserCanReadMultipleProjects')
- expectNext()
- })
- describe('with anonymous user', function () {
- setupAnonymousUser()
- describe('when user has permission', function () {
- describe('when user has permission to access all projects', function () {
- beforeEach(function (ctx) {
- ctx.AuthorizationManager.promises.canUserReadProject
- .withArgs(null, 'project1', ctx.token)
- .resolves(true)
- ctx.AuthorizationManager.promises.canUserReadProject
- .withArgs(null, 'project2', ctx.token)
- .resolves(true)
- })
- invokeMiddleware('ensureUserCanReadMultipleProjects')
- expectNext()
- })
- describe("when user doesn't have permission to access one of the projects", function () {
- beforeEach(function (ctx) {
- ctx.AuthorizationManager.promises.canUserReadProject
- .withArgs(null, 'project1', ctx.token)
- .resolves(true)
- ctx.AuthorizationManager.promises.canUserReadProject
- .withArgs(null, 'project2', ctx.token)
- .resolves(false)
- })
- invokeMiddleware('ensureUserCanReadMultipleProjects')
- expectRedirectToRestricted()
- })
- })
- })
- })
- })
- function testMiddleware(middleware, permission) {
- describe(middleware, function () {
- describe('with missing project_id', function () {
- setupMissingProjectId()
- invokeMiddleware(middleware)
- expectError()
- })
- describe('with logged in user', function () {
- describe('when user has permission', function () {
- setupPermission(permission, true)
- invokeMiddleware(middleware)
- expectNext()
- })
- describe("when user doesn't have permission", function () {
- setupPermission(permission, false)
- invokeMiddleware(middleware)
- expectForbidden()
- })
- })
- describe('with oauth user', function () {
- setupOAuthUser()
- describe('when user has permission', function () {
- setupPermission(permission, true)
- invokeMiddleware(middleware)
- expectNext()
- })
- describe("when user doesn't have permission", function () {
- setupPermission(permission, false)
- invokeMiddleware(middleware)
- expectForbidden()
- })
- })
- describe('with anonymous user', function () {
- setupAnonymousUser()
- describe('when user has permission', function () {
- setupAnonymousPermission(permission, true)
- invokeMiddleware(middleware)
- expectNext()
- })
- describe("when user doesn't have permission", function () {
- setupAnonymousPermission(permission, false)
- invokeMiddleware(middleware)
- expectForbidden()
- })
- })
- describe('with malformed project id', function () {
- setupMalformedProjectId()
- invokeMiddleware(middleware)
- expectNotFound()
- })
- })
- }
- function setupAnonymousUser() {
- beforeEach(function (ctx) {
- ctx.SessionManager.getLoggedInUserId.returns(null)
- ctx.SessionManager.isUserLoggedIn.returns(false)
- })
- }
- function setupOAuthUser() {
- beforeEach(function (ctx) {
- ctx.SessionManager.getLoggedInUserId.returns(null)
- ctx.req.oauth_user = { _id: ctx.userId }
- })
- }
- function setupPermission(permission, value) {
- beforeEach(function (ctx) {
- ctx.AuthorizationManager.promises[permission]
- .withArgs(ctx.userId, ctx.project_id, ctx.token)
- .resolves(value)
- })
- }
- function setupAnonymousPermission(permission, value) {
- beforeEach(function (ctx) {
- ctx.AuthorizationManager.promises[permission]
- .withArgs(null, ctx.project_id, ctx.token)
- .resolves(value)
- })
- }
- function setupSiteAdmin(value) {
- beforeEach(function (ctx) {
- ctx.AuthorizationManager.promises.isUserSiteAdmin
- .withArgs(ctx.userId)
- .resolves(value)
- })
- }
- function setupMissingProjectId() {
- beforeEach(function (ctx) {
- delete ctx.req.params.project_id
- })
- }
- function setupMalformedProjectId() {
- beforeEach(function (ctx) {
- ctx.req.params = { project_id: 'bad-project-id' }
- })
- }
- function invokeMiddleware(method) {
- beforeEach(async function (ctx) {
- await new Promise(resolve => {
- ctx.next.callsFake(() => resolve())
- ctx.HttpErrorHandler.forbidden.callsFake(() => resolve())
- ctx.res.redirect.callsFake(() => resolve())
- ctx.AuthorizationMiddleware[method](ctx.req, ctx.res, ctx.next)
- })
- })
- }
- function expectNext() {
- it('calls the next middleware', function (ctx) {
- expect(ctx.next).to.have.been.calledWithExactly()
- })
- }
- function expectError() {
- it('calls the error middleware', function (ctx) {
- expect(ctx.next).to.have.been.calledWith(sinon.match.instanceOf(Error))
- })
- }
- function expectNotFound() {
- it('raises a 404', function (ctx) {
- expect(ctx.next).to.have.been.calledWith(
- sinon.match.instanceOf(Errors.NotFoundError)
- )
- })
- }
- function expectForbidden() {
- it('raises a 403', function (ctx) {
- expect(ctx.HttpErrorHandler.forbidden).to.have.been.calledWith(
- ctx.req,
- ctx.res
- )
- expect(ctx.next).not.to.have.been.called
- })
- }
- function expectRedirectToRestricted() {
- it('redirects to restricted', function (ctx) {
- expect(ctx.res.redirect).to.have.been.calledWith(
- '/restricted?from=%2Fcurrent%2Furl'
- )
- expect(ctx.next).not.to.have.been.called
- })
- }
|