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:
@@ -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,
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user