Merge pull request #13583 from overleaf/bg-add-permissions-middleware

add permissions middleware for managed users

GitOrigin-RevId: debd2398a3b75ce71023463ad3c0781750983b53
This commit is contained in:
Brian Gough 2023-07-04 15:19:48 +01:00 committed by Copybot
parent 086e2a6794
commit f0420000c5
3 changed files with 73 additions and 2 deletions

View file

@ -0,0 +1,48 @@
const { ForbiddenError } = require('../Errors/Errors')
const { hasPermission } = require('./PermissionsManager')
const ManagedUsersHandler = require('../Subscription/ManagedUsersHandler')
/**
* Function that returns middleware to check if the user has permission to access a resource.
* @param {[string]} requiredCapabilities - the capabilities required to access the resource.
* @returns {Function} The middleware function that checks if the user has the required capabilities.
*/
function requirePermission(...requiredCapabilities) {
if (
requiredCapabilities.length === 0 ||
requiredCapabilities.some(capability => typeof capability !== 'string')
) {
throw new Error('invalid required capabilities')
}
const doRequest = async function (req, res, next) {
if (!req.user) {
return next(new Error('no user'))
}
try {
// get the group policy applying to the user
const groupPolicy =
await ManagedUsersHandler.promises.getGroupPolicyForUser(req.user)
// if there is no group policy, the user is not managed
if (!groupPolicy) {
return next()
}
// check that the user has all the required capabilities
for (const requiredCapability of requiredCapabilities) {
// if the user has the permission, continue
if (!hasPermission(groupPolicy, requiredCapability)) {
throw new ForbiddenError(
`user does not have permission for ${requiredCapability}`
)
}
}
next()
} catch (error) {
next(error)
}
}
return doRequest
}
module.exports = {
requirePermission,
}

View file

@ -148,6 +148,12 @@ class SubscriptionAdminDeletionError extends OErrorV2CompatibleError {
}
}
class SubscriptionNotFoundError extends OErrorV2CompatibleError {
constructor(options) {
super('subscription not found', options)
}
}
class ProjectNotFoundError extends OErrorV2CompatibleError {
constructor(options) {
super('project not found', options)
@ -213,6 +219,7 @@ module.exports = {
ThirdPartyIdentityExistsError,
ThirdPartyUserNotFoundError,
SubscriptionAdminDeletionError,
SubscriptionNotFoundError,
ProjectNotFoundError,
UserNotFoundError,
UserNotCollaboratorError,

View file

@ -4,6 +4,10 @@ const { GroupPolicy } = require('../../models/GroupPolicy')
const { User } = require('../../models/User')
const ManagedUsersPolicy = require('./ManagedUsersPolicy')
const OError = require('@overleaf/o-error')
const {
UserNotFoundError,
SubscriptionNotFoundError,
} = require('../Errors/Errors')
/**
* This module contains functions for handling managed users in a
@ -40,7 +44,14 @@ async function enableManagedUsers(subscriptionId) {
* @returns {Promise<Object>} - A Promise that resolves with the group policy
* object for the user's enrollment, or undefined if it does not exist.
*/
async function getGroupPolicyForUser(user) {
async function getGroupPolicyForUser(requestedUser) {
// Don't rely on the user being populated, it may be a session user without
// the enrollment property. Force the user to be loaded from mongo.
const user = await User.findById(requestedUser._id, 'enrollment')
if (!user) {
throw new UserNotFoundError({ info: { userId: requestedUser._id } })
}
// Now we are sure the user exists and we have the full contents
if (user.enrollment?.managedBy == null) {
return
}
@ -48,8 +59,13 @@ async function getGroupPolicyForUser(user) {
const subscription = await Subscription.findById(user.enrollment.managedBy)
.populate('groupPolicy', '-_id')
.exec()
if (!subscription) {
throw new SubscriptionNotFoundError({
info: { subscriptionId: user.enrollment.managedBy, userId: user._id },
})
}
// return the group policy as a plain object (without the __v field)
const groupPolicy = subscription?.groupPolicy.toObject({
const groupPolicy = subscription.groupPolicy.toObject({
versionKey: false,
})
return groupPolicy