Skip to content

Commit

Permalink
Merge pull request #483 from bcgsc/feat/DEVSU-2311-add-more-permissio…
Browse files Browse the repository at this point in the history
…ns-groups

Feat/devsu 2311 add more permissions groups
  • Loading branch information
elewis2 committed May 23, 2024
2 parents 17f87b4 + cea3f21 commit b32a648
Show file tree
Hide file tree
Showing 7 changed files with 117 additions and 35 deletions.
29 changes: 25 additions & 4 deletions app/components/AuthenticatedRoute/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,10 @@ import { isAuthorized } from '@/services/management/auth';
* @returns {Route} a route component which checks authorization on render or redirects to login
*/
const AuthenticatedRoute = ({
component: Component, managerRequired, showNav, onToggleNav, ...rest
component: Component, managerRequired, templateEditorRequired, appendixEditorRequired, germlineRequired, showNav, onToggleNav, ...rest
}) => {
const { authorizationToken } = useSecurity();
const { managerAccess, adminAccess } = useResource();
const { managerAccess, adminAccess, templateEditAccess, appendixEditAccess, germlineAccess } = useResource();
const authOk = isAuthorized(authorizationToken);

const ChildComponent = useMemo(() => {
Expand All @@ -39,8 +39,23 @@ const AuthenticatedRoute = ({
<Redirect to="/" />
);
}
if (!templateEditAccess && templateEditorRequired) {
return () => (
<Redirect to="/" />
);
}
if (!appendixEditAccess && appendixEditorRequired) {
return () => (
<Redirect to="/" />
);
}
if (!germlineAccess && germlineRequired) {
return () => (
<Redirect to="/" />
);
}
return Component;
}, [Component, adminAccess, managerAccess, managerRequired, authOk]);
}, [Component, adminAccess, managerAccess, templateEditAccess, germlineAccess, appendixEditAccess, managerRequired, templateEditorRequired, germlineRequired, appendixEditorRequired, authOk]);

if (showNav) {
onToggleNav(true);
Expand All @@ -58,6 +73,9 @@ const AuthenticatedRoute = ({

AuthenticatedRoute.propTypes = {
managerRequired: PropTypes.bool,
templateEditorRequired: PropTypes.bool,
appendixEditorRequired: PropTypes.bool,
germlineRequired: PropTypes.bool,
// eslint-disable-next-line react/forbid-prop-types
component: PropTypes.object.isRequired,
// eslint-disable-next-line react/forbid-prop-types
Expand All @@ -68,8 +86,11 @@ AuthenticatedRoute.propTypes = {

AuthenticatedRoute.defaultProps = {
managerRequired: false,
templateEditorRequired: false,
appendixEditorRequired: false,
germlineRequired: false,
location: null,
onToggleNav: () => {},
onToggleNav: () => { },
showNav: false,
};

Expand Down
66 changes: 50 additions & 16 deletions app/components/Sidebar/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ import './index.scss';
const Sidebar = (): JSX.Element => {
const { pathname } = useLocation();
const { sidebarMaximized, setSidebarMaximized } = useContext(SidebarContext);
const { germlineAccess, reportsAccess, managerAccess, adminAccess } = useResource();
const { germlineAccess, reportsAccess, managerAccess, adminAccess, templateEditAccess, appendixEditAccess } = useResource();

const handleSidebarClose = useCallback(() => {
setSidebarMaximized(false);
Expand Down Expand Up @@ -131,26 +131,60 @@ const Sidebar = (): JSX.Element => {
);
} else if (!managerAccess && reportsAccess) {
adminSection = (
<ListItem
className={`
<>
<ListItem
className={`
sidebar__list-item
${pathname.includes('projects') ? 'sidebar__list-item--active' : ''}
`}
disableGutters
>
<Link className="sidebar__link" to="/projects">
<FolderSharedIcon color="action" />
<Typography
display="inline"
className={`sidebar__text ${sidebarMaximized ? 'sidebar__text--visible' : 'sidebar__text--hidden'}`}
>
Projects
</Typography>
</Link>
</ListItem>
disableGutters
>
<Link className="sidebar__link" to="/projects">
<FolderSharedIcon color="action" />
<Typography
display="inline"
className={`sidebar__text ${sidebarMaximized ? 'sidebar__text--visible' : 'sidebar__text--hidden'}`}
>
Projects
</Typography>
</Link>
</ListItem>
{templateEditAccess && <ListItem
className={`sidebar__list-item ${pathname.includes('template') ? 'sidebar__list-item--active' : ''}`}
disableGutters
>
<Link className="sidebar__link" to="/template">
<DashboardIcon color="action" />
<Typography
display="inline"
className={`sidebar__text ${sidebarMaximized ? 'sidebar__text--visible' : 'sidebar__text--hidden'}`}
>
Templates
</Typography>
</Link>
</ListItem>}
{appendixEditAccess && <ListItem
className={`
sidebar__list-item
${pathname.includes('admin/appendices') ? 'sidebar__list-item--active' : ''}
`}
disableGutters
>
<Link className="sidebar__link" to="/admin/appendices">
<FilePresentIcon color="action" />
<Typography
display="inline"
className={`sidebar__text ${sidebarMaximized ? 'sidebar__text--visible' : 'sidebar__text--hidden'}`}
>
Appendices
</Typography>
</Link>
</ListItem>}
</>
);
}


return (
<div>
<div className="sidebar__minimize">
Expand Down Expand Up @@ -233,7 +267,7 @@ const Sidebar = (): JSX.Element => {
</List>
</div>
);
}, [adminAccess, managerAccess, germlineAccess, handleSidebarClose, pathname, reportsAccess, sidebarMaximized]);
}, [adminAccess, managerAccess, germlineAccess, handleSidebarClose, pathname, reportsAccess, templateEditAccess, appendixEditAccess, sidebarMaximized]);

let drawerProps: DrawerProps = {
variant: 'temporary',
Expand Down
36 changes: 27 additions & 9 deletions app/context/ResourceContext/index.tsx
Original file line number Diff line number Diff line change
@@ -1,20 +1,19 @@
import React, {
createContext, ReactChild, useState, useEffect, useMemo,
} from 'react';
import { checkAccess, ALL_ROLES } from '@/utils/checkAccess';
import { checkAccess, ALL_ROLES, NO_GROUP_MATCH } from '@/utils/checkAccess';
import useSecurity from '@/hooks/useSecurity';
import ResourceContextType from './types';

// TODO: determine whether bioinformaticians need nonprod or germline access;
// determine whether report managers do
// TODO: rename bioinformatician role?
const GERMLINE_ACCESS = ['admin', 'manager', 'bioinformatician', 'germline access'];
const GERMLINE_ACCESS = ['admin', 'manager', 'germline access'];
const UNREVIEWED_ACCESS = ['admin', 'manager', 'report manager', 'bioinformatician', 'unreviewed access'];
const NONPRODUCTION_ACCESS = ['admin', 'manager', 'bioinformatician', 'non-production access'];
const TEMPLATE_EDIT_ACCESS = ['admin', 'manager', 'template edit access'];
const APPENDIX_EDIT_ACCESS = ['admin', 'manager', 'appendix edit access'];

const GERMLINE_BLOCK = ALL_ROLES;
const UNREVIEWED_ACCESS_BLOCK = [];
const NONPRODUCTION_ACCESS_BLOCK = [];
const GERMLINE_BLOCK = [...ALL_ROLES, ...NO_GROUP_MATCH];
const UNREVIEWED_ACCESS_BLOCK = NO_GROUP_MATCH;
const NONPRODUCTION_ACCESS_BLOCK = NO_GROUP_MATCH;

const ALL_STATES = ['signedoff', 'nonproduction', 'uploaded', 'reviewed', 'completed', 'ready', 'active'];
const UNREVIEWED_STATES = ['uploaded', 'ready', 'active']; // TODO decide if nonproduction should go in unreviewed as well
Expand All @@ -23,7 +22,7 @@ const NONPRODUCTION_STATES = ['nonproduction'];
const REPORTS_ACCESS = ['*'];
const REPORTS_BLOCK = [];
const ADMIN_ACCESS = ['admin'];
const ADMIN_BLOCK = ALL_ROLES;
const ADMIN_BLOCK = [...ALL_ROLES, ...NO_GROUP_MATCH];

const useResources = (): ResourceContextType => {
const { userDetails: { groups } } = useSecurity();
Expand All @@ -36,6 +35,8 @@ const useResources = (): ResourceContextType => {
const [reportSettingAccess, setReportSettingAccess] = useState(false);
const [unreviewedAccess, setUnreviewedAccess] = useState(false);
const [nonproductionAccess, setNonproductionAccess] = useState(false);
const [templateEditAccess, setTemplateEditAccess] = useState(false);
const [appendixEditAccess, setAppendixEditAccess] = useState(false);

// Check user group first to see which resources they can access
useEffect(() => {
Expand All @@ -56,6 +57,13 @@ const useResources = (): ResourceContextType => {
setManagerAccess(true);
}

if (checkAccess(groups, [...TEMPLATE_EDIT_ACCESS], GERMLINE_BLOCK)) {
setTemplateEditAccess(true);
}
if (checkAccess(groups, [...APPENDIX_EDIT_ACCESS], GERMLINE_BLOCK)) {
setAppendixEditAccess(true);
}

if (checkAccess(groups, [...ADMIN_ACCESS, 'manager', 'report manager'], ADMIN_BLOCK)) {
setReportSettingAccess(true);
setReportEditAccess(true);
Expand All @@ -79,6 +87,8 @@ const useResources = (): ResourceContextType => {
reportEditAccess,
unreviewedAccess,
nonproductionAccess,
templateEditAccess,
appendixEditAccess,
allStates: ALL_STATES,
unreviewedStates: UNREVIEWED_STATES,
nonproductionStates: NONPRODUCTION_STATES,
Expand All @@ -94,6 +104,8 @@ const ResourceContext = createContext<ResourceContextType>({
reportEditAccess: false,
unreviewedAccess: false,
nonproductionAccess: false,
templateEditAccess: false,
appendixEditAccess: false,
allStates: ALL_STATES,
unreviewedStates: UNREVIEWED_STATES,
nonproductionStates: NONPRODUCTION_STATES,
Expand All @@ -106,6 +118,8 @@ type ResourceContextProviderProps = {
const ResourceContextProvider = ({ children }: ResourceContextProviderProps): JSX.Element => {
const {
germlineAccess, reportsAccess, adminAccess, managerAccess, reportSettingAccess, reportEditAccess, unreviewedAccess, nonproductionAccess,
templateEditAccess,
appendixEditAccess,
allStates,
unreviewedStates,
nonproductionStates,
Expand All @@ -120,6 +134,8 @@ const ResourceContextProvider = ({ children }: ResourceContextProviderProps): JS
reportEditAccess,
unreviewedAccess,
nonproductionAccess,
templateEditAccess,
appendixEditAccess,
allStates,
unreviewedStates,
nonproductionStates,
Expand All @@ -132,6 +148,8 @@ const ResourceContextProvider = ({ children }: ResourceContextProviderProps): JS
reportEditAccess,
unreviewedAccess,
nonproductionAccess,
templateEditAccess,
appendixEditAccess,
allStates,
unreviewedStates,
nonproductionStates,
Expand Down
2 changes: 2 additions & 0 deletions app/context/ResourceContext/types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ type ResourceContextType = {
reportsAccess: boolean;
adminAccess: boolean;
managerAccess: boolean;
templateEditAccess: boolean;
appendixEditAccess: boolean;
reportSettingAccess: boolean;
reportEditAccess: boolean;
unreviewedAccess: boolean;
Expand Down
9 changes: 8 additions & 1 deletion app/utils/checkAccess.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ const ALL_ROLES = [
'report manager',
];

const NO_GROUP_MATCH = [
'no groups',
]
/*
Checks if a user is allowed access, given an allow and block list, with allow list taking precedence
*/
Expand All @@ -17,12 +20,16 @@ const checkAccess = (
if (groups.length < 1) { return false; }
const groupNames = groups.map((group) => group.name.toLowerCase());
const isAllowed = allowList.includes('*') || allowList.some((group) => groupNames.includes(group.toLowerCase()));
const isBlocked = blockList.some((group) => groupNames.includes(group.toLowerCase()));
let isBlocked = blockList.some((group) => groupNames.includes(group.toLowerCase()));
if (!isBlocked && blockList.includes('no groups')) {
isBlocked = true;
}
return isAllowed || !isBlocked;
};

export {
checkAccess,
ALL_ROLES,
NO_GROUP_MATCH,
};
export default checkAccess;
3 changes: 1 addition & 2 deletions app/views/AdminView/components/Groups/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import AddEditGroupDialog from './components/AddEditGroupDialog';

import './index.scss';

const ALL_ACCESS = ['admin', 'manager', 'report manager', 'bioinformatician', 'read access', 'germline access', 'non-production access', 'unreviewed access'];
const ALL_ACCESS = ['admin', 'manager', 'report manager', 'bioinformatician', 'read access', 'germline access', 'non-production access', 'unreviewed access', 'all projects access', 'template edit access', 'appendix edit access'];

const Groups = (): JSX.Element => {
const [groups, setGroups] = useState<GroupType[]>([]);
Expand All @@ -29,7 +29,6 @@ const Groups = (): JSX.Element => {
let groupsResp = await api.get('/user/group').request();
groupsResp = groupsResp.filter((group) => ALL_ACCESS.includes(group.name.toLowerCase()));
groupsResp.sort((a, b) => ALL_ACCESS.indexOf(a.name.toLowerCase()) - ALL_ACCESS.indexOf(b.name.toLowerCase()));
console.dir(groupsResp);
setGroups(groupsResp);
setLoading(false);
};
Expand Down
7 changes: 4 additions & 3 deletions app/views/MainView/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,7 @@ const Main = (): JSX.Element => {
>
<CircularProgress color="secondary" />
</Box>
)}
)}
>
<TimeoutModal authorizationToken={authorizationToken} setAuthorizationToken={setAuthorizationToken} />
<Switch>
Expand All @@ -185,10 +185,11 @@ const Main = (): JSX.Element => {
<AuthenticatedRoute component={ReportView} path="/report/:ident" />
<AuthenticatedRoute component={PrintView} path="/print/:ident" showNav={false} onToggleNav={setIsNavVisible} />
<AuthenticatedRoute component={CondensedPrintView} path="/condensedLayoutPrint/:ident" showNav={false} onToggleNav={setIsNavVisible} />
<AuthenticatedRoute component={GermlineView} path="/germline" />
<AuthenticatedRoute germlineRequired component={GermlineView} path="/germline" />
<AuthenticatedRoute component={ProjectsView} path="/projects" />
<AuthenticatedRoute appendixEditorRequired component={AdminView} path="/admin/appendices" />
<AuthenticatedRoute managerRequired component={AdminView} path="/admin" />
<AuthenticatedRoute managerRequired component={TemplateView} path="/template" />
<AuthenticatedRoute templateEditorRequired component={TemplateView} path="/template" />
</Switch>
</Suspense>
</section>
Expand Down

0 comments on commit b32a648

Please sign in to comment.