diff --git a/rdmo/projects/assets/js/common/utils/permissions.js b/rdmo/projects/assets/js/common/utils/permissions.js new file mode 100644 index 0000000000..31857c7d0c --- /dev/null +++ b/rdmo/projects/assets/js/common/utils/permissions.js @@ -0,0 +1,12 @@ +// combine permission sets, granting a permission when it is true in any set +export const combinePermissions = (...permissionSets) => { + const effectivePermissions = {} + + permissionSets.forEach((permissions) => { + Object.entries(permissions ?? {}).forEach(([key, value]) => { + effectivePermissions[key] = effectivePermissions[key] === true || value === true + }) + }) + + return effectivePermissions +} diff --git a/rdmo/projects/assets/js/project/components/areas/Dashboard.js b/rdmo/projects/assets/js/project/components/areas/Dashboard.js index b53c563b83..4c52d08c06 100644 --- a/rdmo/projects/assets/js/project/components/areas/Dashboard.js +++ b/rdmo/projects/assets/js/project/components/areas/Dashboard.js @@ -4,6 +4,7 @@ import { useDispatch, useSelector } from 'react-redux' import * as configActions from 'rdmo/core/assets/js/actions/configActions' import { navigateDashboard, updateProjectTask } from '../../actions/projectActions' +import { usePermissions } from '../../hooks' import { Tile } from '../helper' import IssueDate from '../../../common/components/IssueDate' @@ -17,7 +18,7 @@ const Dashboard = () => { const dispatch = useDispatch() const config = useSelector(state => state.config) const settings = useSelector(state => state.settings) - const perms = useSelector(state => state.project.project.project.permissions) ?? {} + const perms = usePermissions() const allIssues = useSelector((state) => state.project.project.tasks) ?? [] /* Show only issues that resolve */ diff --git a/rdmo/projects/assets/js/project/components/areas/Information.js b/rdmo/projects/assets/js/project/components/areas/Information.js index 6ba4630e3a..8879ca8144 100644 --- a/rdmo/projects/assets/js/project/components/areas/Information.js +++ b/rdmo/projects/assets/js/project/components/areas/Information.js @@ -1,6 +1,8 @@ import React from 'react' import { useSelector } from 'react-redux' +import { usePermissions } from '../../hooks' + import CopyProject from './information/CopyProject' import HierarchyTree from './information/HierarchyTree' import ProjectDelete from './information/ProjectDelete' @@ -9,7 +11,7 @@ import ProjectVisibilityForm from './information/ProjectVisibilityForm' const Information = () => { const { hierarchy, project } = useSelector((state) => state.project.project) ?? {} - const perms = project?.permissions ?? {} + const perms = usePermissions() return (
diff --git a/rdmo/projects/assets/js/project/components/areas/Integrations.js b/rdmo/projects/assets/js/project/components/areas/Integrations.js index 8faa7eb112..5d244b49be 100644 --- a/rdmo/projects/assets/js/project/components/areas/Integrations.js +++ b/rdmo/projects/assets/js/project/components/areas/Integrations.js @@ -3,13 +3,15 @@ import { useSelector } from 'react-redux' import { useModal } from 'rdmo/core/assets/js/hooks' +import { usePermissions } from '../../hooks' + import IntegrationsDropdown from '../helper/IntegrationsDropdown' import IntegrationDeleteModal from './integrations/IntegrationDeleteModal' import IntegrationModal from './integrations/IntegrationModal' const Integrations = () => { - const perms = useSelector((state) => state.project.project.project.permissions) ?? {} + const perms = usePermissions() const providers = useSelector((state) => state.project.providers) ?? {} const integrations = useSelector((state) => state.project.integrations) ?? [] diff --git a/rdmo/projects/assets/js/project/components/areas/Memberships.js b/rdmo/projects/assets/js/project/components/areas/Memberships.js index bdfb74f960..d44e1c3728 100644 --- a/rdmo/projects/assets/js/project/components/areas/Memberships.js +++ b/rdmo/projects/assets/js/project/components/areas/Memberships.js @@ -4,15 +4,17 @@ import { isEmpty } from 'lodash' import { useModal } from 'rdmo/core/assets/js/hooks' +import { usePermissions } from '../../hooks' + import MembershipInviteModal from './memberships/MembershipInviteModal' import MembershipTable from './memberships/MembershipTable' const Memberships = () => { const { show: showInvite, open: openInvite, close: closeInvite } = useModal() - const { memberships, project } = useSelector((state) => state.project.project) ?? {} + const { memberships } = useSelector((state) => state.project.project) ?? {} const { invites } = useSelector((state) => state.project) - const perms = project?.permissions ?? {} + const perms = usePermissions() return ( <> diff --git a/rdmo/projects/assets/js/project/components/areas/Snapshots.js b/rdmo/projects/assets/js/project/components/areas/Snapshots.js index 8b19c71c9e..59c5f9beca 100644 --- a/rdmo/projects/assets/js/project/components/areas/Snapshots.js +++ b/rdmo/projects/assets/js/project/components/areas/Snapshots.js @@ -3,14 +3,16 @@ import { useSelector } from 'react-redux' import { useModal } from 'rdmo/core/assets/js/hooks' +import { usePermissions } from '../../hooks' + import SnapshotModal from './snapshots/SnapshotModal' import SnapshotTable from './snapshots/SnapshotTable' const Snapshots = () => { const { show: showSnapshot, open: openSnapshot, close: closeSnapshot } = useModal() - const { snapshots, project } = useSelector((state) => state.project.project) ?? {} - const perms = project?.permissions ?? {} + const { snapshots } = useSelector((state) => state.project.project) ?? {} + const perms = usePermissions() return (
diff --git a/rdmo/projects/assets/js/project/components/areas/information/ProjectForm.js b/rdmo/projects/assets/js/project/components/areas/information/ProjectForm.js index e74ea1cd5c..e2f530bfec 100644 --- a/rdmo/projects/assets/js/project/components/areas/information/ProjectForm.js +++ b/rdmo/projects/assets/js/project/components/areas/information/ProjectForm.js @@ -13,7 +13,7 @@ import Html from 'rdmo/core/assets/js/components/Html' import { copyProject, createProject } from '../../../../projects/actions/projectsActions' import { updateProject } from '../../../actions/projectActions' -import { useFieldErrors } from '../../../hooks/useFieldErrors' +import { useFieldErrors } from '../../../hooks' import ProjectApi from '../../../api/ProjectApi' @@ -265,7 +265,7 @@ const ProjectForm = ({ } { - unavailableCatalog && project.permissions.can_change_project && + unavailableCatalog && !disabled && } diff --git a/rdmo/projects/assets/js/project/components/areas/integrations/IntegrationDeleteModal.js b/rdmo/projects/assets/js/project/components/areas/integrations/IntegrationDeleteModal.js index 5e809bd38f..45bda5ea96 100644 --- a/rdmo/projects/assets/js/project/components/areas/integrations/IntegrationDeleteModal.js +++ b/rdmo/projects/assets/js/project/components/areas/integrations/IntegrationDeleteModal.js @@ -7,7 +7,7 @@ import { Modal } from 'rdmo/core/assets/js/components' import Html from 'rdmo/core/assets/js/components/Html' import { clearProjectErrors, deleteProjectIntegration } from '../../../actions/projectActions' -import { useFieldErrors } from '../../../hooks/useFieldErrors' +import { useFieldErrors } from '../../../hooks' const IntegrationDeleteModal = ({ show, onClose, integration }) => { const dispatch = useDispatch() diff --git a/rdmo/projects/assets/js/project/components/areas/integrations/IntegrationModal.js b/rdmo/projects/assets/js/project/components/areas/integrations/IntegrationModal.js index d34c67f72f..4955899b78 100644 --- a/rdmo/projects/assets/js/project/components/areas/integrations/IntegrationModal.js +++ b/rdmo/projects/assets/js/project/components/areas/integrations/IntegrationModal.js @@ -10,7 +10,7 @@ import { createProjectIntegration, updateProjectIntegration } from '../../../actions/projectActions' -import { useFieldErrors } from '../../../hooks/useFieldErrors' +import { useFieldErrors } from '../../../hooks' import IntegrationSecretField from './IntegrationSecretField' diff --git a/rdmo/projects/assets/js/project/components/areas/memberships/MembershipDeleteModal.js b/rdmo/projects/assets/js/project/components/areas/memberships/MembershipDeleteModal.js index 3ec975e08f..519306544f 100644 --- a/rdmo/projects/assets/js/project/components/areas/memberships/MembershipDeleteModal.js +++ b/rdmo/projects/assets/js/project/components/areas/memberships/MembershipDeleteModal.js @@ -6,7 +6,7 @@ import Html from 'rdmo/core/assets/js/components/Html' import Modal from 'rdmo/core/assets/js/components/Modal' import { deleteProjectInvite, deleteProjectMember, leaveProject } from '../../../actions/projectActions' -import { useFieldErrors } from '../../../hooks/useFieldErrors' +import { useFieldErrors } from '../../../hooks' const MembershipDeleteModal = ({ type, show, person, onClose, isAdminOrSiteManager = false, diff --git a/rdmo/projects/assets/js/project/components/areas/memberships/MembershipInviteModal.js b/rdmo/projects/assets/js/project/components/areas/memberships/MembershipInviteModal.js index 3799c8c4cb..943c43b01d 100644 --- a/rdmo/projects/assets/js/project/components/areas/memberships/MembershipInviteModal.js +++ b/rdmo/projects/assets/js/project/components/areas/memberships/MembershipInviteModal.js @@ -8,16 +8,15 @@ import { Modal, Tooltip } from 'rdmo/core/assets/js/components' import Html from 'rdmo/core/assets/js/components/Html' import { clearProjectErrors, createProjectMember, sendProjectInvite } from '../../../actions/projectActions' -import { useFieldErrors } from '../../../hooks/useFieldErrors' +import { useFieldErrors, usePermissions } from '../../../hooks' const initialForm = { lookup: '', role: 'author' } const MembershipInviteModal = ({ show, onClose }) => { const dispatch = useDispatch() const templates = useSelector((state) => state.templates) - const { project } = useSelector((state) => state.project.project) || {} const errors = useFieldErrors() - const perms = project?.permissions || {} + const perms = usePermissions() const roleOptions = useSelector((state) => state.roles?.roles) || [] const [formData, setFormData] = useState(initialForm) diff --git a/rdmo/projects/assets/js/project/components/areas/memberships/MembershipTable.js b/rdmo/projects/assets/js/project/components/areas/memberships/MembershipTable.js index 7de7315db1..d7c25125de 100644 --- a/rdmo/projects/assets/js/project/components/areas/memberships/MembershipTable.js +++ b/rdmo/projects/assets/js/project/components/areas/memberships/MembershipTable.js @@ -8,14 +8,14 @@ import { useModal } from 'rdmo/core/assets/js/hooks' import Select from 'rdmo/core/assets/js/components/Select' import { updateProjectInvite, updateProjectMember } from '../../../actions/projectActions' +import { usePermissions } from '../../../hooks' import MembershipDeleteModal from './MembershipDeleteModal' const MembershipTable = ({ persons, type }) => { const dispatch = useDispatch() const currentUser = useSelector((state) => state.user.currentUser) - const { project } = useSelector((state) => state.project.project) || {} - const perms = project?.permissions || {} + const perms = usePermissions() const roleOptions = useSelector((state) => state.roles?.roles) || [] const { show: showConfirm, open: openConfirm, close: closeConfirm } = useModal() const [modalState, setModalState] = useState(null) diff --git a/rdmo/projects/assets/js/project/components/areas/snapshots/SnapshotDeleteModal.js b/rdmo/projects/assets/js/project/components/areas/snapshots/SnapshotDeleteModal.js index 4f78a6e55e..687f9eacbf 100644 --- a/rdmo/projects/assets/js/project/components/areas/snapshots/SnapshotDeleteModal.js +++ b/rdmo/projects/assets/js/project/components/areas/snapshots/SnapshotDeleteModal.js @@ -6,7 +6,7 @@ import Html from 'rdmo/core/assets/js/components/Html' import Modal from 'rdmo/core/assets/js/components/Modal' import { deleteSnapshot } from '../../../actions/projectActions' -import { useFieldErrors } from '../../../hooks/useFieldErrors' +import { useFieldErrors } from '../../../hooks' const SnapshotDeleteModal = ({ show, onClose, snapshot }) => { const dispatch = useDispatch() diff --git a/rdmo/projects/assets/js/project/components/areas/snapshots/SnapshotModal.js b/rdmo/projects/assets/js/project/components/areas/snapshots/SnapshotModal.js index 9235fa65d3..29956ff8cb 100644 --- a/rdmo/projects/assets/js/project/components/areas/snapshots/SnapshotModal.js +++ b/rdmo/projects/assets/js/project/components/areas/snapshots/SnapshotModal.js @@ -8,7 +8,7 @@ import { Input, Textarea } from 'rdmo/core/assets/js/components/forms' import Html from 'rdmo/core/assets/js/components/Html' import { clearProjectErrors, createSnapshot, updateSnapshot } from '../../../actions/projectActions' -import { useFieldErrors } from '../../../hooks/useFieldErrors' +import { useFieldErrors } from '../../../hooks' const initialForm = { title: '', description: '' } diff --git a/rdmo/projects/assets/js/project/components/areas/snapshots/SnapshotRollbackModal.js b/rdmo/projects/assets/js/project/components/areas/snapshots/SnapshotRollbackModal.js index 1b9bc15a8f..f222ad804c 100644 --- a/rdmo/projects/assets/js/project/components/areas/snapshots/SnapshotRollbackModal.js +++ b/rdmo/projects/assets/js/project/components/areas/snapshots/SnapshotRollbackModal.js @@ -6,7 +6,7 @@ import Html from 'rdmo/core/assets/js/components/Html' import Modal from 'rdmo/core/assets/js/components/Modal' import { rollbackSnapshot } from '../../../actions/projectActions' -import { useFieldErrors } from '../../../hooks/useFieldErrors' +import { useFieldErrors } from '../../../hooks' const SnapshotRollbackModal = ({ show, onClose, snapshot }) => { const dispatch = useDispatch() diff --git a/rdmo/projects/assets/js/project/components/areas/snapshots/SnapshotTable.js b/rdmo/projects/assets/js/project/components/areas/snapshots/SnapshotTable.js index 9245b27772..8bc010ab2e 100644 --- a/rdmo/projects/assets/js/project/components/areas/snapshots/SnapshotTable.js +++ b/rdmo/projects/assets/js/project/components/areas/snapshots/SnapshotTable.js @@ -1,12 +1,13 @@ import React, { useState } from 'react' import PropTypes from 'prop-types' -import { useDispatch, useSelector } from 'react-redux' +import { useDispatch } from 'react-redux' import { Link } from 'rdmo/core/assets/js/components' import { useModal } from 'rdmo/core/assets/js/hooks' import { formatDateTime } from 'rdmo/core/assets/js/utils/date' import { navigateDashboard } from '../../../actions/projectActions' +import { usePermissions } from '../../../hooks' import { buildPath } from '../../../utils/location' import SnapshotDeleteModal from './SnapshotDeleteModal' @@ -15,8 +16,7 @@ import SnapshotRollbackModal from './SnapshotRollbackModal' const SnapshotTable = ({ snapshots, onCreate}) => { const dispatch = useDispatch() - const { project } = useSelector((state) => state.project.project) || {} - const perms = project?.permissions || {} + const perms = usePermissions() const updateModal = useModal() const rollbackModal = useModal() diff --git a/rdmo/projects/assets/js/project/hooks/index.js b/rdmo/projects/assets/js/project/hooks/index.js new file mode 100644 index 0000000000..75cbdd359b --- /dev/null +++ b/rdmo/projects/assets/js/project/hooks/index.js @@ -0,0 +1,2 @@ +export { useFieldErrors } from './useFieldErrors' +export { usePermissions } from './usePermissions' diff --git a/rdmo/projects/assets/js/project/hooks/usePermissions.js b/rdmo/projects/assets/js/project/hooks/usePermissions.js new file mode 100644 index 0000000000..4f132c522b --- /dev/null +++ b/rdmo/projects/assets/js/project/hooks/usePermissions.js @@ -0,0 +1,19 @@ +import { useMemo } from 'react' +import { useSelector } from 'react-redux' + +import { combinePermissions } from '../../common/utils/permissions' + +// return the combined model-based permissions for the current user and object-based permissions for the current project +export const usePermissions = () => { + const projectPermissions = useSelector( + (state) => state.project.project?.project?.permissions + ) + const userPermissions = useSelector( + (state) => state.user.currentUser?.permissions + ) + + return useMemo( + () => combinePermissions(projectPermissions, userPermissions), + [projectPermissions, userPermissions] + ) +} diff --git a/rdmo/projects/assets/js/project/store/configureStore.js b/rdmo/projects/assets/js/project/store/configureStore.js index 05e88b3813..3b1a2bec58 100644 --- a/rdmo/projects/assets/js/project/store/configureStore.js +++ b/rdmo/projects/assets/js/project/store/configureStore.js @@ -20,7 +20,7 @@ import { checkStoreId } from 'rdmo/core/assets/js/utils/store' import * as rolesActions from '../../common/actions/rolesActions' import rolesReducer from '../../common/reducers/rolesReducer' -// import { getEffectivePermissions } from '../../common/utils/permissions' +import { combinePermissions } from '../../common/utils/permissions' import * as projectActions from '../actions/projectActions' import projectReducer from '../reducers/projectReducer' import { parseLocation } from '../utils/location' @@ -91,22 +91,20 @@ export default function configureStore() { const project = state.project.project.project const currentUser = state.user.currentUser - const permissions = project.permissions - - // const permissions = getEffectivePermissions( - // project.permissions, - // currentUser.permissions - // ) + const permissions = combinePermissions( + project.permissions, + currentUser.permissions + ) if (permissions.can_view_invite) { store.dispatch(projectActions.fetchProjectInvites(projectId)) } if (permissions.can_view_visibility) { - if (state.settings.multisite && currentUser.permissions.can_view_site) { + if (state.settings.multisite && permissions.can_view_site) { store.dispatch(sitesActions.fetchSites()) } - if (state.settings.groups && currentUser.permissions.can_view_group) { + if (state.settings.groups && permissions.can_view_group) { store.dispatch(groupsActions.fetchGroups()) } if (!isNil(project.visibility)) { diff --git a/rdmo/projects/assets/js/projects/components/Main.js b/rdmo/projects/assets/js/projects/components/Main.js index c65661603b..659faa369e 100644 --- a/rdmo/projects/assets/js/projects/components/Main.js +++ b/rdmo/projects/assets/js/projects/components/Main.js @@ -9,6 +9,7 @@ import { useModal, useScrollToTop } from 'rdmo/core/assets/js/hooks' import { formatDateTime } from 'rdmo/core/assets/js/utils/date' import { baseUrl } from 'rdmo/core/assets/js/utils/meta' +import { combinePermissions } from '../../common/utils/permissions' import * as projectsActions from '../actions/projectsActions' import { HEADER_FORMATTERS, SORTABLE_COLUMNS } from '../utils' @@ -39,6 +40,7 @@ const Main = () => { if (!projectsObject.ready) return null const { allowedTypes, catalogs, importUrls, invites, projects, projectsCount, hasNext } = projectsObject const { currentUser } = currentUserObject + const userPerms = currentUser.permissions ?? {} const { myProjects } = config const invitationsModalProps = { @@ -266,7 +268,7 @@ const Main = () => { created: content => formatDateTime(content, 'long'), last_changed: content => formatDateTime(content, 'long'), actions: (_content, row) => { - const perms = row.permissions || {} + const perms = combinePermissions(row.permissions, userPerms) return (