diff --git a/services/web/app/src/Features/Authorization/PermissionsController.js b/services/web/app/src/Features/Authorization/PermissionsController.js index afcfe5efb8..978337c866 100644 --- a/services/web/app/src/Features/Authorization/PermissionsController.js +++ b/services/web/app/src/Features/Authorization/PermissionsController.js @@ -1,5 +1,9 @@ -const { ForbiddenError } = require('../Errors/Errors') -const { hasPermission, getUserCapabilities } = require('./PermissionsManager') +const { ForbiddenError, UserNotFoundError } = require('../Errors/Errors') +const { + hasPermission, + getUserCapabilities, + getUserRestrictions, +} = require('./PermissionsManager') const ManagedUsersHandler = require('../Subscription/ManagedUsersHandler') /** @@ -8,6 +12,16 @@ const ManagedUsersHandler = require('../Subscription/ManagedUsersHandler') */ function useCapabilities() { return async function (req, res, next) { + // attach the user's capabilities to the request object + req.capabilitySet = new Set() + // provide a function to assert that a capability is present + req.assertPermission = capability => { + if (!req.capabilitySet.has(capability)) { + throw new ForbiddenError( + `user does not have permission for ${capability}` + ) + } + } if (!req.user) { return next() } @@ -15,20 +29,25 @@ function useCapabilities() { // get the group policy applying to the user const groupPolicy = await ManagedUsersHandler.promises.getGroupPolicyForUser(req.user) - const capabilitySet = getUserCapabilities(groupPolicy) - req.assertPermission = capability => { - if (!capabilitySet.has(capability)) { - throw new ForbiddenError( - `user does not have permission for ${capability}` - ) - } + // attach the new capabilities to the request object + for (const cap of getUserCapabilities(groupPolicy)) { + req.capabilitySet.add(cap) } + // also attach the user's restrictions (the capabilities they don't have) + req.userRestrictions = getUserRestrictions(groupPolicy) next() } catch (error) { - next(error) + if (error instanceof UserNotFoundError) { + // the user is logged in but doesn't exist in the database + // this can happen if the user has just deleted their account + return next() + } else { + next(error) + } } } } + /** * 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. diff --git a/services/web/app/src/Features/Authorization/PermissionsManager.js b/services/web/app/src/Features/Authorization/PermissionsManager.js index d702ba4024..bf511673b9 100644 --- a/services/web/app/src/Features/Authorization/PermissionsManager.js +++ b/services/web/app/src/Features/Authorization/PermissionsManager.js @@ -238,6 +238,23 @@ function getUserCapabilities(groupPolicy) { return userCapabilities } +/** + * Returns a set of capabilities that a user does not have based on their group policy. + * + * @param {Object} groupPolicy - The group policy object to check. + * @returns {Set} A set of capabilities that the user does not have, based on their group + * policy. + * @throws {Error} If the policy is unknown. + */ +function getUserRestrictions(groupPolicy) { + const userCapabilities = getUserCapabilities(groupPolicy) + const userRestrictions = getDefaultCapabilities() + for (const capability of userCapabilities) { + userRestrictions.delete(capability) + } + return userRestrictions +} + /** * Checks if a user has permission for a given capability based on their group * policy. @@ -299,6 +316,7 @@ module.exports = { registerPolicy, hasPermission, getUserCapabilities, + getUserRestrictions, getUserValidationStatus: callbackify(getUserValidationStatus), promises: { getUserValidationStatus }, } diff --git a/services/web/app/src/Features/User/UserPagesController.js b/services/web/app/src/Features/User/UserPagesController.js index 56cf51ad76..ac9190dc1f 100644 --- a/services/web/app/src/Features/User/UserPagesController.js +++ b/services/web/app/src/Features/User/UserPagesController.js @@ -127,6 +127,7 @@ async function settingsPage(req, res) { personalAccessTokens, emailAddressLimit: Settings.emailAddressLimit, isManagedAccount: !!user.enrollment?.managedBy, + userRestrictions: Array.from(req.userRestrictions || []), }) } diff --git a/services/web/app/src/router.js b/services/web/app/src/router.js index 0d036c6895..624ee610b2 100644 --- a/services/web/app/src/router.js +++ b/services/web/app/src/router.js @@ -285,6 +285,7 @@ function initialize(webRouter, privateApiRouter, publicApiRouter) { webRouter.get( '/user/settings', AuthenticationController.requireLogin(), + PermissionsController.useCapabilities(), UserPagesController.settingsPage ) webRouter.post( diff --git a/services/web/app/views/layout-base.pug b/services/web/app/views/layout-base.pug index 475a87c287..f2f5f830c8 100644 --- a/services/web/app/views/layout-base.pug +++ b/services/web/app/views/layout-base.pug @@ -64,6 +64,9 @@ html( indexName : settings.templates.indexName }) + each restriction in userRestrictions || [] + meta(name='ol-cannot-' + restriction data-type="boolean" content=true) + block head-scripts body(ng-csp=(cspEnabled ? "no-unsafe-eval" : false) class=(showThinFooter ? 'thin-footer' : undefined)) diff --git a/services/web/frontend/extracted-translations.json b/services/web/frontend/extracted-translations.json index 12cfa29ab4..112952a6c7 100644 --- a/services/web/frontend/extracted-translations.json +++ b/services/web/frontend/extracted-translations.json @@ -171,6 +171,7 @@ "confirm_primary_email_change": "", "conflicting_paths_found": "", "connected_users": "", + "contact_group_admin": "", "contact_message_label": "", "contact_sales": "", "contact_support_to_change_group_subscription": "", diff --git a/services/web/frontend/js/features/settings/components/emails-section.tsx b/services/web/frontend/js/features/settings/components/emails-section.tsx index f97e5907d4..24df04c84b 100644 --- a/services/web/frontend/js/features/settings/components/emails-section.tsx +++ b/services/web/frontend/js/features/settings/components/emails-section.tsx @@ -23,6 +23,11 @@ function EmailsSectionContent() { } = useUserEmailsContext() const userEmails = Object.values(userEmailsData.byId) + // Only show the "add email" button if the user has permission to add a secondary email + const hideAddSecondaryEmail = getMeta( + 'ol-cannot-add-secondary-email' + ) as boolean + return ( <>