فهرست منبع

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

add permissions middleware for managed users

GitOrigin-RevId: debd2398a3b75ce71023463ad3c0781750983b53
Brian Gough 3 سال پیش
والد
کامیت
f0420000c5

+ 48 - 0
services/web/app/src/Features/Authorization/PermissionsController.js

@@ -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,
+}

+ 7 - 0
services/web/app/src/Features/Errors/Errors.js

@@ -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,

+ 18 - 2
services/web/app/src/Features/Subscription/ManagedUsersHandler.js

@@ -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