Merge pull request #13505 from overleaf/bg-group-policy-meta-tags

hide restricted elements for managed users on settings page

GitOrigin-RevId: be8679957eb5185d8b91d29e5a787c837684c314
This commit is contained in:
June Kelly 2023-07-14 10:12:12 +01:00 committed by Copybot
parent 4f8852feaa
commit bf92436e6f
10 changed files with 89 additions and 11 deletions

View file

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

View file

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

View file

@ -127,6 +127,7 @@ async function settingsPage(req, res) {
personalAccessTokens,
emailAddressLimit: Settings.emailAddressLimit,
isManagedAccount: !!user.enrollment?.managedBy,
userRestrictions: Array.from(req.userRestrictions || []),
})
}

View file

@ -285,6 +285,7 @@ function initialize(webRouter, privateApiRouter, publicApiRouter) {
webRouter.get(
'/user/settings',
AuthenticationController.requireLogin(),
PermissionsController.useCapabilities(),
UserPagesController.settingsPage
)
webRouter.post(

View file

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

View file

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

View file

@ -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 (
<>
<h3>{t('emails_and_affiliations_title')}</h3>
@ -57,7 +62,7 @@ function EmailsSectionContent() {
</>
)}
{isInitializingSuccess && <LeaversSurveyAlert />}
{isInitializingSuccess && <AddEmail />}
{isInitializingSuccess && !hideAddSecondaryEmail && <AddEmail />}
{isInitializingError && (
<Alert bsStyle="danger" className="text-center">
<Icon type="exclamation-triangle" fw />{' '}

View file

@ -1,6 +1,7 @@
import { useState, useCallback } from 'react'
import { useTranslation } from 'react-i18next'
import LeaveModal from './leave/modal'
import getMeta from '../../../utils/meta'
function LeaveSection() {
const { t } = useTranslation()
@ -15,6 +16,15 @@ function LeaveSection() {
setIsModalOpen(true)
}, [])
// Prevent managed users deleting their own accounts
if (getMeta('ol-cannot-delete-own-account')) {
return (
<>
{t('need_to_leave')} {t('contact_group_admin')}
</>
)
}
return (
<>
{t('need_to_leave')}{' '}

View file

@ -39,6 +39,25 @@ function LinkingSection() {
const hasIntegrationLinkingSection = allIntegrationLinkingWidgets.length
const hasReferencesLinkingSection = referenceLinkingWidgets.length
// Filter out SSO providers that are not allowed to be linked by
// managed users. Allow unlinking them if they are already linked.
const hideGoogleSSO = getMeta('ol-cannot-link-google-sso')
const hideOtherThirdPartySSO = getMeta('ol-cannot-link-other-third-party-sso')
for (const providerId in subscriptions) {
const isLinked = subscriptions[providerId].linked
if (providerId === 'google') {
if (hideGoogleSSO && !isLinked) {
delete subscriptions[providerId]
}
} else {
if (hideOtherThirdPartySSO && !isLinked) {
delete subscriptions[providerId]
}
}
}
const hasSSOLinkingSection = Object.keys(subscriptions).length > 0
if (

View file

@ -275,6 +275,7 @@
"connected_users": "Connected Users",
"connecting": "Connecting",
"contact": "Contact",
"contact_group_admin": "Please contact your group administrator.",
"contact_message_label": "Message",
"contact_sales": "Contact Sales",
"contact_support_to_change_group_subscription": "Please <0>contact support</0> if you wish to change your group subscription.",