Просмотр исходного кода

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

hide restricted elements for managed users on settings page

GitOrigin-RevId: be8679957eb5185d8b91d29e5a787c837684c314
June Kelly 3 лет назад
Родитель
Сommit
bf92436e6f

+ 29 - 10
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.

+ 18 - 0
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 },
 }

+ 1 - 0
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 || []),
   })
 }
 

+ 1 - 0
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(

+ 3 - 0
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))

+ 1 - 0
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": "",

+ 6 - 1
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 (
     <>
       <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 />{' '}

+ 10 - 0
services/web/frontend/js/features/settings/components/leave-section.tsx

@@ -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')}{' '}

+ 19 - 0
services/web/frontend/js/features/settings/components/linking-section.tsx

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

+ 1 - 0
services/web/locales/en.json

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