Merge pull request #4947 from overleaf/em-project-rename-for-owners-only

Prevent collaborators from renaming a project

GitOrigin-RevId: 94d12e25592fea55b84427aeae78f7bb2a544a58
This commit is contained in:
Eric Mc Sween
2021-09-14 08:03:38 +00:00
committed by Copybot
parent aec8d78254
commit a10c042e20
7 changed files with 1147 additions and 1788 deletions
@@ -1,6 +1,4 @@
let AuthorizationMiddleware
const AuthorizationManager = require('./AuthorizationManager')
const async = require('async')
const logger = require('logger-sharelatex')
const { ObjectId } = require('mongodb')
const Errors = require('../Errors/Errors')
@@ -8,265 +6,188 @@ const HttpErrorHandler = require('../Errors/HttpErrorHandler')
const AuthenticationController = require('../Authentication/AuthenticationController')
const SessionManager = require('../Authentication/SessionManager')
const TokenAccessHandler = require('../TokenAccess/TokenAccessHandler')
const { expressify } = require('../../util/promises')
module.exports = AuthorizationMiddleware = {
ensureUserCanReadMultipleProjects(req, res, next) {
const projectIds = (req.query.project_ids || '').split(',')
AuthorizationMiddleware._getUserId(req, function (error, userId) {
if (error) {
return next(error)
}
// Remove the projects we have access to. Note rejectSeries doesn't use
// errors in callbacks
async.rejectSeries(
projectIds,
function (projectId, cb) {
const token = TokenAccessHandler.getRequestToken(req, projectId)
AuthorizationManager.canUserReadProject(
userId,
projectId,
token,
function (error, canRead) {
if (error) {
return next(error)
}
cb(canRead)
}
)
},
function (unauthorizedProjectIds) {
if (unauthorizedProjectIds.length > 0) {
return AuthorizationMiddleware.redirectToRestricted(req, res, next)
}
next()
}
)
})
},
blockRestrictedUserFromProject(req, res, next) {
AuthorizationMiddleware._getUserAndProjectId(
req,
function (error, userId, projectId) {
if (error) {
return next(error)
}
const token = TokenAccessHandler.getRequestToken(req, projectId)
AuthorizationManager.isRestrictedUserForProject(
userId,
projectId,
token,
(err, isRestrictedUser) => {
if (err) {
return next(err)
}
if (isRestrictedUser) {
return res.sendStatus(403)
}
next()
}
)
}
async function ensureUserCanReadMultipleProjects(req, res, next) {
const projectIds = (req.query.project_ids || '').split(',')
const userId = _getUserId(req)
for (const projectId of projectIds) {
const token = TokenAccessHandler.getRequestToken(req, projectId)
const canRead = await AuthorizationManager.promises.canUserReadProject(
userId,
projectId,
token
)
},
ensureUserCanReadProject(req, res, next) {
AuthorizationMiddleware._getUserAndProjectId(
req,
function (error, userId, projectId) {
if (error) {
return next(error)
}
const token = TokenAccessHandler.getRequestToken(req, projectId)
AuthorizationManager.canUserReadProject(
userId,
projectId,
token,
function (error, canRead) {
if (error) {
return next(error)
}
if (canRead) {
logger.log(
{ userId, projectId },
'allowing user read access to project'
)
return next()
}
logger.log(
{ userId, projectId },
'denying user read access to project'
)
HttpErrorHandler.forbidden(req, res)
}
)
}
)
},
ensureUserCanWriteProjectSettings(req, res, next) {
AuthorizationMiddleware._getUserAndProjectId(
req,
function (error, userId, projectId) {
if (error) {
return next(error)
}
const token = TokenAccessHandler.getRequestToken(req, projectId)
AuthorizationManager.canUserWriteProjectSettings(
userId,
projectId,
token,
function (error, canWrite) {
if (error) {
return next(error)
}
if (canWrite) {
logger.log(
{ userId, projectId },
'allowing user write access to project settings'
)
return next()
}
logger.log(
{ userId, projectId },
'denying user write access to project settings'
)
HttpErrorHandler.forbidden(req, res)
}
)
}
)
},
ensureUserCanWriteProjectContent(req, res, next) {
AuthorizationMiddleware._getUserAndProjectId(
req,
function (error, userId, projectId) {
if (error) {
return next(error)
}
const token = TokenAccessHandler.getRequestToken(req, projectId)
AuthorizationManager.canUserWriteProjectContent(
userId,
projectId,
token,
function (error, canWrite) {
if (error) {
return next(error)
}
if (canWrite) {
logger.log(
{ userId, projectId },
'allowing user write access to project content'
)
return next()
}
logger.log(
{ userId, projectId },
'denying user write access to project settings'
)
HttpErrorHandler.forbidden(req, res)
}
)
}
)
},
ensureUserCanAdminProject(req, res, next) {
AuthorizationMiddleware._getUserAndProjectId(
req,
function (error, userId, projectId) {
if (error) {
return next(error)
}
const token = TokenAccessHandler.getRequestToken(req, projectId)
AuthorizationManager.canUserAdminProject(
userId,
projectId,
token,
function (error, canAdmin) {
if (error) {
return next(error)
}
if (canAdmin) {
logger.log(
{ userId, projectId },
'allowing user admin access to project'
)
return next()
}
logger.log(
{ userId, projectId },
'denying user admin access to project'
)
HttpErrorHandler.forbidden(req, res)
}
)
}
)
},
ensureUserIsSiteAdmin(req, res, next) {
AuthorizationMiddleware._getUserId(req, function (error, userId) {
if (error) {
return next(error)
}
AuthorizationManager.isUserSiteAdmin(userId, function (error, isAdmin) {
if (error) {
return next(error)
}
if (isAdmin) {
logger.log({ userId }, 'allowing user admin access to site')
return next()
}
logger.log({ userId }, 'denying user admin access to site')
AuthorizationMiddleware.redirectToRestricted(req, res, next)
})
})
},
_getUserAndProjectId(req, callback) {
const projectId = req.params.project_id || req.params.Project_id
if (!projectId) {
return callback(new Error('Expected project_id in request parameters'))
if (!canRead) {
return _redirectToRestricted(req, res, next)
}
if (!ObjectId.isValid(projectId)) {
return callback(
new Errors.NotFoundError(`invalid projectId: ${projectId}`)
)
}
next()
}
async function blockRestrictedUserFromProject(req, res, next) {
const projectId = _getProjectId(req)
const userId = _getUserId(req)
const token = TokenAccessHandler.getRequestToken(req, projectId)
const isRestrictedUser = await AuthorizationManager.promises.isRestrictedUserForProject(
userId,
projectId,
token
)
if (isRestrictedUser) {
return HttpErrorHandler.forbidden(req, res)
}
next()
}
async function ensureUserCanReadProject(req, res, next) {
const projectId = _getProjectId(req)
const userId = _getUserId(req)
const token = TokenAccessHandler.getRequestToken(req, projectId)
const canRead = await AuthorizationManager.promises.canUserReadProject(
userId,
projectId,
token
)
if (canRead) {
logger.log({ userId, projectId }, 'allowing user read access to project')
return next()
}
logger.log({ userId, projectId }, 'denying user read access to project')
HttpErrorHandler.forbidden(req, res)
}
async function ensureUserCanWriteProjectSettings(req, res, next) {
const projectId = _getProjectId(req)
const userId = _getUserId(req)
const token = TokenAccessHandler.getRequestToken(req, projectId)
if (req.body.name != null) {
const canRename = await AuthorizationManager.promises.canUserRenameProject(
userId,
projectId,
token
)
if (!canRename) {
return HttpErrorHandler.forbidden(req, res)
}
AuthorizationMiddleware._getUserId(req, function (error, userId) {
if (error) {
return callback(error)
}
callback(null, userId, projectId)
})
},
_getUserId(req, callback) {
const userId =
SessionManager.getLoggedInUserId(req.session) ||
(req.oauth_user && req.oauth_user._id) ||
null
callback(null, userId)
},
redirectToRestricted(req, res, next) {
// TODO: move this to throwing ForbiddenError
res.redirect(
`/restricted?from=${encodeURIComponent(res.locals.currentUrl)}`
}
const otherParams = Object.keys(req.body).filter(x => x !== 'name')
if (otherParams.length > 0) {
const canWrite = await AuthorizationManager.promises.canUserWriteProjectSettings(
userId,
projectId,
token
)
if (!canWrite) {
return HttpErrorHandler.forbidden(req, res)
}
}
next()
}
async function ensureUserCanWriteProjectContent(req, res, next) {
const projectId = _getProjectId(req)
const userId = _getUserId(req)
const token = TokenAccessHandler.getRequestToken(req, projectId)
const canWrite = await AuthorizationManager.promises.canUserWriteProjectContent(
userId,
projectId,
token
)
if (canWrite) {
logger.log(
{ userId, projectId },
'allowing user write access to project content'
)
},
return next()
}
logger.log(
{ userId, projectId },
'denying user write access to project settings'
)
HttpErrorHandler.forbidden(req, res)
}
async function ensureUserCanAdminProject(req, res, next) {
const projectId = _getProjectId(req)
const userId = _getUserId(req)
const token = TokenAccessHandler.getRequestToken(req, projectId)
const canAdmin = await AuthorizationManager.promises.canUserAdminProject(
userId,
projectId,
token
)
if (canAdmin) {
logger.log({ userId, projectId }, 'allowing user admin access to project')
return next()
}
logger.log({ userId, projectId }, 'denying user admin access to project')
HttpErrorHandler.forbidden(req, res)
}
async function ensureUserIsSiteAdmin(req, res, next) {
const userId = _getUserId(req)
const isAdmin = await AuthorizationManager.promises.isUserSiteAdmin(userId)
if (isAdmin) {
logger.log({ userId }, 'allowing user admin access to site')
return next()
}
logger.log({ userId }, 'denying user admin access to site')
_redirectToRestricted(req, res, next)
}
function _getProjectId(req) {
const projectId = req.params.project_id || req.params.Project_id
if (!projectId) {
throw new Error('Expected project_id in request parameters')
}
if (!ObjectId.isValid(projectId)) {
throw new Errors.NotFoundError(`invalid projectId: ${projectId}`)
}
return projectId
}
function _getUserId(req) {
return (
SessionManager.getLoggedInUserId(req.session) ||
(req.oauth_user && req.oauth_user._id) ||
null
)
}
restricted(req, res, next) {
if (SessionManager.isUserLoggedIn(req.session)) {
return res.render('user/restricted', { title: 'restricted' })
}
const { from } = req.query
logger.log({ from }, 'redirecting to login')
if (from) {
AuthenticationController.setRedirectInSession(req, from)
}
res.redirect('/login')
},
function _redirectToRestricted(req, res, next) {
// TODO: move this to throwing ForbiddenError
res.redirect(`/restricted?from=${encodeURIComponent(res.locals.currentUrl)}`)
}
function restricted(req, res, next) {
if (SessionManager.isUserLoggedIn(req.session)) {
return res.render('user/restricted', { title: 'restricted' })
}
const { from } = req.query
logger.log({ from }, 'redirecting to login')
if (from) {
AuthenticationController.setRedirectInSession(req, from)
}
res.redirect('/login')
}
module.exports = {
ensureUserCanReadMultipleProjects: expressify(
ensureUserCanReadMultipleProjects
),
blockRestrictedUserFromProject: expressify(blockRestrictedUserFromProject),
ensureUserCanReadProject: expressify(ensureUserCanReadProject),
ensureUserCanWriteProjectSettings: expressify(
ensureUserCanWriteProjectSettings
),
ensureUserCanWriteProjectContent: expressify(
ensureUserCanWriteProjectContent
),
ensureUserCanAdminProject: expressify(ensureUserCanAdminProject),
ensureUserIsSiteAdmin: expressify(ensureUserIsSiteAdmin),
restricted,
}