Fine-grained permissions for groups (#2579)

This commit is contained in:
Stan Silvert 2022-05-09 06:44:37 -04:00 committed by GitHub
parent 93088c5380
commit 5427eaf6ff
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 135 additions and 99 deletions

View file

@ -64,6 +64,7 @@ export const GroupAttributes = () => {
<AttributesForm <AttributesForm
form={form} form={form}
save={save} save={save}
fineGrainedAccess={currentGroup()?.access?.manage}
reset={() => reset={() =>
form.reset({ form.reset({
attributes: convertAttributes(), attributes: convertAttributes(),

View file

@ -37,7 +37,7 @@ export const GroupTable = () => {
const [selectedRows, setSelectedRows] = useState<GroupRepresentation[]>([]); const [selectedRows, setSelectedRows] = useState<GroupRepresentation[]>([]);
const [move, setMove] = useState<GroupRepresentation>(); const [move, setMove] = useState<GroupRepresentation>();
const { subGroups, setSubGroups } = useSubGroups(); const { subGroups, currentGroup, setSubGroups } = useSubGroups();
const [key, setKey] = useState(0); const [key, setKey] = useState(0);
const refresh = () => setKey(new Date().getTime()); const refresh = () => setKey(new Date().getTime());
@ -47,7 +47,10 @@ export const GroupTable = () => {
const id = getLastId(location.pathname); const id = getLastId(location.pathname);
const { hasAccess } = useAccess(); const { hasAccess } = useAccess();
const isManager = hasAccess("manage-users", "query-clients"); const isManager = hasAccess("manage-users") || currentGroup()?.access?.manage;
const canView =
hasAccess("query-groups", "view-users") ||
hasAccess("manage-users", "query-groups");
const loader = async () => { const loader = async () => {
let groupsData = undefined; let groupsData = undefined;
@ -90,14 +93,17 @@ export const GroupTable = () => {
}; };
const GroupNameCell = (group: GroupRepresentation) => { const GroupNameCell = (group: GroupRepresentation) => {
if (!isManager) return <span>{group.name}</span>; if (!canView) return <span>{group.name}</span>;
return ( return (
<Link <Link
key={group.id} key={group.id}
to={`${location.pathname}/${group.id}`} to={`${location.pathname}/${group.id}`}
onClick={() => { onClick={async () => {
setSubGroups([...subGroups, group]); const loadedGroup = await adminClient.groups.findOne({
id: group.id!,
});
setSubGroups([...subGroups, loadedGroup!]);
}} }}
> >
{group.name} {group.name}

View file

@ -28,6 +28,7 @@ import { toGroupsSearch } from "./routes/GroupsSearch";
import { GroupRoleMapping } from "./GroupRoleMapping"; import { GroupRoleMapping } from "./GroupRoleMapping";
import helpUrls from "../help-urls"; import helpUrls from "../help-urls";
import { PermissionsTab } from "../components/permission-tab/PermissionTab"; import { PermissionsTab } from "../components/permission-tab/PermissionTab";
import { useAccess } from "../context/access/Access";
import "./GroupsSection.css"; import "./GroupsSection.css";
@ -46,6 +47,16 @@ export default function GroupsSection() {
const location = useLocation(); const location = useLocation();
const id = getLastId(location.pathname); const id = getLastId(location.pathname);
const { hasAccess } = useAccess();
const canViewPermissions = hasAccess(
"manage-authorization",
"manage-users",
"manage-clients"
);
const canManageGroup =
hasAccess("manage-users") || currentGroup()?.access?.manage;
const canManageRoles = hasAccess("manage-users");
const deleteGroup = async (group: GroupRepresentation) => { const deleteGroup = async (group: GroupRepresentation) => {
try { try {
await adminClient.groups.del({ await adminClient.groups.del({
@ -112,7 +123,7 @@ export default function GroupsSection() {
helpUrl={!id ? helpUrls.groupsUrl : ""} helpUrl={!id ? helpUrls.groupsUrl : ""}
divider={!id} divider={!id}
dropdownItems={ dropdownItems={
id id && canManageGroup
? [ ? [
SearchDropdown, SearchDropdown,
<DropdownItem <DropdownItem
@ -170,6 +181,7 @@ export default function GroupsSection() {
> >
<GroupAttributes /> <GroupAttributes />
</Tab> </Tab>
{canManageRoles && (
<Tab <Tab
eventKey={3} eventKey={3}
data-testid="role-mapping-tab" data-testid="role-mapping-tab"
@ -177,6 +189,8 @@ export default function GroupsSection() {
> >
<GroupRoleMapping id={id!} name={currentGroup()?.name!} /> <GroupRoleMapping id={id!} name={currentGroup()?.name!} />
</Tab> </Tab>
)}
{canViewPermissions && (
<Tab <Tab
eventKey={4} eventKey={4}
data-testid="permissionsTab" data-testid="permissionsTab"
@ -184,6 +198,7 @@ export default function GroupsSection() {
> >
<PermissionsTab id={id} type="groups" /> <PermissionsTab id={id} type="groups" />
</Tab> </Tab>
)}
</Tabs> </Tabs>
)} )}
{subGroups.length === 0 && <GroupTable />} {subGroups.length === 0 && <GroupTable />}

View file

@ -26,6 +26,7 @@ import { MemberModal } from "./MembersModal";
import { ListEmptyState } from "../components/list-empty-state/ListEmptyState"; import { ListEmptyState } from "../components/list-empty-state/ListEmptyState";
import { GroupPath } from "../components/group/GroupPath"; import { GroupPath } from "../components/group/GroupPath";
import { toUser } from "../user/routes/User"; import { toUser } from "../user/routes/User";
import { useAccess } from "../context/access/Access";
type MembersOf = UserRepresentation & { type MembersOf = UserRepresentation & {
membership: GroupRepresentation[]; membership: GroupRepresentation[];
@ -43,6 +44,10 @@ export const Members = () => {
const [addMembers, setAddMembers] = useState(false); const [addMembers, setAddMembers] = useState(false);
const [isKebabOpen, setIsKebabOpen] = useState(false); const [isKebabOpen, setIsKebabOpen] = useState(false);
const [selectedRows, setSelectedRows] = useState<UserRepresentation[]>([]); const [selectedRows, setSelectedRows] = useState<UserRepresentation[]>([]);
const { hasAccess } = useAccess();
const isManager =
hasAccess("manage-users") || currentGroup()!.access!.manageMembership;
const [key, setKey] = useState(0); const [key, setKey] = useState(0);
const refresh = () => setKey(new Date().getTime()); const refresh = () => setKey(new Date().getTime());
@ -123,6 +128,7 @@ export const Members = () => {
canSelectAll canSelectAll
onSelect={(rows) => setSelectedRows([...rows])} onSelect={(rows) => setSelectedRows([...rows])}
toolbarItem={ toolbarItem={
isManager && (
<> <>
<ToolbarItem> <ToolbarItem>
<Button <Button
@ -184,8 +190,11 @@ export const Members = () => {
/> />
</ToolbarItem> </ToolbarItem>
</> </>
)
} }
actions={[ actions={
isManager
? [
{ {
title: t("leave"), title: t("leave"),
onRowClick: async (user) => { onRowClick: async (user) => {
@ -194,7 +203,10 @@ export const Members = () => {
id: user.id!, id: user.id!,
groupId: id!, groupId: id!,
}); });
addAlert(t("usersLeft", { count: 1 }), AlertVariant.success); addAlert(
t("usersLeft", { count: 1 }),
AlertVariant.success
);
} catch (error) { } catch (error) {
addError("groups:usersLeftError", error); addError("groups:usersLeftError", error);
} }
@ -202,7 +214,9 @@ export const Members = () => {
return true; return true;
}, },
}, },
]} ]
: []
}
columns={[ columns={[
{ {
name: "username", name: "username",
@ -233,8 +247,8 @@ export const Members = () => {
emptyState={ emptyState={
<ListEmptyState <ListEmptyState
message={t("users:noUsersFound")} message={t("users:noUsersFound")}
instructions={t("users:emptyInstructions")} instructions={isManager ? t("users:emptyInstructions") : undefined}
primaryActionText={t("addMember")} primaryActionText={isManager ? t("addMember") : undefined}
onPrimaryAction={() => setAddMembers(true)} onPrimaryAction={() => setAddMembers(true)}
/> />
} }