mirror of
https://github.com/overleaf/overleaf.git
synced 2024-11-07 20:31:06 -05:00
Merge pull request #19152 from overleaf/jdt-project-permissions
Allow checking permissions for all users on a project and rename checkPermissions -> AssertPermissions GitOrigin-RevId: 511356cf2fe68367e284347e68e59f6116bd0f80
This commit is contained in:
parent
c0f39267a9
commit
007cc42477
5 changed files with 67 additions and 22 deletions
|
@ -5,7 +5,7 @@ const {
|
|||
combineGroupPolicies,
|
||||
combineAllowedProperties,
|
||||
} = require('./PermissionsManager')
|
||||
const { checkUserPermissions } = require('./PermissionsManager').promises
|
||||
const { assertUserPermissions } = require('./PermissionsManager').promises
|
||||
const Modules = require('../../infrastructure/Modules')
|
||||
const { expressify } = require('@overleaf/promise-utils')
|
||||
|
||||
|
@ -84,7 +84,7 @@ function requirePermission(...requiredCapabilities) {
|
|||
return next(new Error('no user'))
|
||||
}
|
||||
try {
|
||||
await checkUserPermissions(req.user, requiredCapabilities)
|
||||
await assertUserPermissions(req.user, requiredCapabilities)
|
||||
next()
|
||||
} catch (error) {
|
||||
next(error)
|
||||
|
|
|
@ -370,8 +370,9 @@ async function getUserValidationStatus({ user, groupPolicy, subscription }) {
|
|||
}
|
||||
|
||||
/**
|
||||
* Checks if a user has permission for a given set of capabilities
|
||||
* as set out in both their current group subscription, and any institutions they are affiliated with
|
||||
* asserts that a user has permission for a given set of capabilities
|
||||
* as set out in both their current group subscription, and any institutions they are affiliated with,
|
||||
* throwing an ForbiddenError if they do not
|
||||
*
|
||||
* @param {Object} user - The user object to retrieve the group policy for.
|
||||
* Only the user's _id is required
|
||||
|
@ -379,11 +380,31 @@ async function getUserValidationStatus({ user, groupPolicy, subscription }) {
|
|||
* @returns {Promise<void>}
|
||||
* @throws {Error} If the user does not have permission
|
||||
*/
|
||||
async function assertUserPermissions(user, requiredCapabilities) {
|
||||
const hasAllPermissions = await checkUserPermissions(
|
||||
user,
|
||||
requiredCapabilities
|
||||
)
|
||||
if (!hasAllPermissions) {
|
||||
throw new ForbiddenError(
|
||||
`user does not have one or more permissions within ${requiredCapabilities}`
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a user has permission for a given set of capabilities
|
||||
* as set out in both their current group subscription, and any institutions they are affiliated with
|
||||
*
|
||||
* @param {Object} user - The user object to retrieve the group policy for.
|
||||
* Only the user's _id is required
|
||||
* @param {Array} capabilities - The list of the capabilities to check permission for.
|
||||
* @returns {Promise<Boolean>} - true if the user has all permissions, false if not
|
||||
*/
|
||||
async function checkUserPermissions(user, requiredCapabilities) {
|
||||
let results = await Modules.promises.hooks.fire('getGroupPolicyForUser', user)
|
||||
results = results.flat()
|
||||
|
||||
if (!results?.length) return
|
||||
if (!results?.length) return true
|
||||
|
||||
// get the combined group policy applying to the user
|
||||
const groupPolicies = results.map(result => result.groupPolicy)
|
||||
|
@ -391,11 +412,30 @@ async function checkUserPermissions(user, requiredCapabilities) {
|
|||
for (const requiredCapability of requiredCapabilities) {
|
||||
// if the user has the permission, continue
|
||||
if (!hasPermission(combinedGroupPolicy, requiredCapability)) {
|
||||
throw new ForbiddenError(
|
||||
`user does not have permission for ${requiredCapability}`
|
||||
)
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* checks if all collaborators of a given project have the specified capability, including the owner
|
||||
*
|
||||
* @async
|
||||
* @function checkCollaboratorsPermission
|
||||
* @param {string} userList - An array of all user to check permissions for
|
||||
* @param {Array} capabilities - The list of the capabilities to check permission for.
|
||||
* @returns {Promise<boolean>} - A promise that resolves to `true` if all collaborators have the specified capability, otherwise `false`.
|
||||
*/
|
||||
async function checkUserListPermissions(userList, capabilities) {
|
||||
for (const user of userList) {
|
||||
// mimic a user object with only id, since we need it to fetch permissions
|
||||
const allowed = await checkUserPermissions(user, capabilities)
|
||||
if (!allowed) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
|
@ -409,5 +449,10 @@ module.exports = {
|
|||
getUserCapabilities,
|
||||
getUserRestrictions,
|
||||
getUserValidationStatus: callbackify(getUserValidationStatus),
|
||||
promises: { checkUserPermissions, getUserValidationStatus },
|
||||
checkCollaboratorsPermission: callbackify(checkUserListPermissions),
|
||||
promises: {
|
||||
assertUserPermissions,
|
||||
getUserValidationStatus,
|
||||
checkUserListPermissions,
|
||||
},
|
||||
}
|
||||
|
|
|
@ -5,7 +5,7 @@ const OneTimeTokenHandler = require('../Security/OneTimeTokenHandler')
|
|||
const EmailHandler = require('../Email/EmailHandler')
|
||||
const AuthenticationManager = require('../Authentication/AuthenticationManager')
|
||||
const { callbackify, promisify } = require('util')
|
||||
const { checkUserPermissions } =
|
||||
const { assertUserPermissions } =
|
||||
require('../Authorization/PermissionsManager').promises
|
||||
|
||||
const AUDIT_LOG_TOKEN_PREFIX_LENGTH = 10
|
||||
|
@ -21,7 +21,7 @@ async function generateAndEmailResetToken(email) {
|
|||
return 'secondary'
|
||||
}
|
||||
|
||||
await checkUserPermissions(user, ['change-password'])
|
||||
await assertUserPermissions(user, ['change-password'])
|
||||
|
||||
const data = { user_id: user._id.toString(), email }
|
||||
const token = await OneTimeTokenHandler.promises.getNewToken('password', data)
|
||||
|
@ -72,7 +72,7 @@ async function getUserForPasswordResetToken(token) {
|
|||
email: 1,
|
||||
})
|
||||
|
||||
await checkUserPermissions(user, ['change-password'])
|
||||
await assertUserPermissions(user, ['change-password'])
|
||||
|
||||
if (user == null) {
|
||||
return { user: null, remainingPeeks: 0 }
|
||||
|
|
|
@ -399,11 +399,11 @@ describe('PermissionsManager', function () {
|
|||
)
|
||||
})
|
||||
})
|
||||
describe('checkUserPermissions', function () {
|
||||
describe('assertUserPermissions', function () {
|
||||
describe('allowed', function () {
|
||||
it('should not error when managedUsersEnabled is not enabled for user', async function () {
|
||||
const result =
|
||||
await this.PermissionsManager.promises.checkUserPermissions(
|
||||
await this.PermissionsManager.promises.assertUserPermissions(
|
||||
{ _id: 'user123' },
|
||||
['add-secondary-email']
|
||||
)
|
||||
|
@ -423,7 +423,7 @@ describe('PermissionsManager', function () {
|
|||
],
|
||||
])
|
||||
const result =
|
||||
await this.PermissionsManager.promises.checkUserPermissions(
|
||||
await this.PermissionsManager.promises.assertUserPermissions(
|
||||
{ _id: 'user123' },
|
||||
['some-policy-to-check']
|
||||
)
|
||||
|
@ -448,7 +448,7 @@ describe('PermissionsManager', function () {
|
|||
],
|
||||
])
|
||||
const result =
|
||||
await this.PermissionsManager.promises.checkUserPermissions(
|
||||
await this.PermissionsManager.promises.assertUserPermissions(
|
||||
{ _id: 'user123' },
|
||||
['some-policy-to-check']
|
||||
)
|
||||
|
@ -460,7 +460,7 @@ describe('PermissionsManager', function () {
|
|||
it('should return error when managedUsersEnabled is enabled for user but there is no group policy', async function () {
|
||||
this.hooksFire.resolves([[{ managedUsersEnabled: true }]])
|
||||
await expect(
|
||||
this.PermissionsManager.promises.checkUserPermissions(
|
||||
this.PermissionsManager.promises.assertUserPermissions(
|
||||
{ _id: 'user123' },
|
||||
['add-secondary-email']
|
||||
)
|
||||
|
@ -480,7 +480,7 @@ describe('PermissionsManager', function () {
|
|||
],
|
||||
])
|
||||
await expect(
|
||||
this.PermissionsManager.promises.checkUserPermissions(
|
||||
this.PermissionsManager.promises.assertUserPermissions(
|
||||
{ _id: 'user123' },
|
||||
['some-policy-to-check']
|
||||
)
|
||||
|
@ -503,7 +503,7 @@ describe('PermissionsManager', function () {
|
|||
],
|
||||
])
|
||||
await expect(
|
||||
this.PermissionsManager.promises.checkUserPermissions(
|
||||
this.PermissionsManager.promises.assertUserPermissions(
|
||||
{ _id: 'user123' },
|
||||
['some-policy-to-check']
|
||||
)
|
||||
|
|
|
@ -47,7 +47,7 @@ describe('PasswordResetHandler', function () {
|
|||
'@overleaf/settings': this.settings,
|
||||
'../Authorization/PermissionsManager': (this.PermissionsManager = {
|
||||
promises: {
|
||||
checkUserPermissions: sinon.stub(),
|
||||
assertUserPermissions: sinon.stub(),
|
||||
},
|
||||
}),
|
||||
},
|
||||
|
@ -537,7 +537,7 @@ describe('PasswordResetHandler', function () {
|
|||
it('should returns errors from user permissions', async function () {
|
||||
let error
|
||||
const err = new Error('nope')
|
||||
this.PermissionsManager.promises.checkUserPermissions.rejects(err)
|
||||
this.PermissionsManager.promises.assertUserPermissions.rejects(err)
|
||||
try {
|
||||
await this.PasswordResetHandler.promises.getUserForPasswordResetToken(
|
||||
'abc123'
|
||||
|
|
Loading…
Reference in a new issue