2021-04-20 12:10:00 +00:00
|
|
|
import React, { useState } from "react";
|
|
|
|
import { useTranslation } from "react-i18next";
|
|
|
|
import {
|
|
|
|
AlertVariant,
|
|
|
|
Badge,
|
|
|
|
Button,
|
|
|
|
ButtonVariant,
|
|
|
|
Checkbox,
|
|
|
|
ToolbarItem,
|
|
|
|
} from "@patternfly/react-core";
|
|
|
|
|
2021-08-26 08:39:35 +00:00
|
|
|
import type ClientRepresentation from "@keycloak/keycloak-admin-client/lib/defs/clientRepresentation";
|
|
|
|
import type RoleRepresentation from "@keycloak/keycloak-admin-client/lib/defs/roleRepresentation";
|
2021-04-20 12:10:00 +00:00
|
|
|
import { AddRoleMappingModal, MappingType } from "./AddRoleMappingModal";
|
|
|
|
import { KeycloakDataTable } from "../table-toolbar/KeycloakDataTable";
|
|
|
|
import { emptyFormatter } from "../../util";
|
|
|
|
|
|
|
|
import "./role-mapping.css";
|
|
|
|
import { useConfirmDialog } from "../confirm-dialog/ConfirmDialog";
|
|
|
|
import { useAdminClient } from "../../context/auth/AdminClient";
|
|
|
|
import { useAlerts } from "../alert/Alerts";
|
2021-06-16 11:35:03 +00:00
|
|
|
import { ListEmptyState } from "../list-empty-state/ListEmptyState";
|
2021-04-20 12:10:00 +00:00
|
|
|
|
|
|
|
export type CompositeRole = RoleRepresentation & {
|
|
|
|
parent: RoleRepresentation;
|
2021-06-15 06:14:38 +00:00
|
|
|
isInherited?: boolean;
|
2021-04-20 12:10:00 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
export type Row = {
|
|
|
|
client?: ClientRepresentation;
|
2021-06-15 06:14:38 +00:00
|
|
|
role: RoleRepresentation | CompositeRole;
|
|
|
|
};
|
|
|
|
|
|
|
|
export const mapRoles = (
|
2021-06-15 11:12:32 +00:00
|
|
|
assignedRoles: Row[],
|
|
|
|
effectiveRoles: Row[],
|
2021-06-15 06:14:38 +00:00
|
|
|
hide: boolean
|
|
|
|
) => {
|
|
|
|
return [
|
|
|
|
...(hide
|
2021-06-15 11:12:32 +00:00
|
|
|
? assignedRoles.map((row) => ({
|
|
|
|
...row,
|
2021-06-15 06:14:38 +00:00
|
|
|
role: {
|
2021-06-15 11:12:32 +00:00
|
|
|
...row.role,
|
2021-06-15 06:14:38 +00:00
|
|
|
isInherited: false,
|
|
|
|
},
|
|
|
|
}))
|
2021-06-15 11:12:32 +00:00
|
|
|
: effectiveRoles.map((row) => ({
|
|
|
|
...row,
|
2021-06-15 06:14:38 +00:00
|
|
|
role: {
|
2021-06-15 11:12:32 +00:00
|
|
|
...row.role,
|
2021-06-15 06:14:38 +00:00
|
|
|
isInherited:
|
2021-06-15 11:12:32 +00:00
|
|
|
assignedRoles.find((r) => r.role.id === row.role.id) ===
|
|
|
|
undefined,
|
2021-06-15 06:14:38 +00:00
|
|
|
},
|
|
|
|
}))),
|
|
|
|
];
|
2021-04-20 12:10:00 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
export const ServiceRole = ({ role, client }: Row) => (
|
|
|
|
<>
|
|
|
|
{client && (
|
|
|
|
<Badge
|
|
|
|
key={`${client.id}-${role.id}`}
|
|
|
|
isRead
|
|
|
|
className="keycloak-admin--role-mapping__client-name"
|
|
|
|
>
|
|
|
|
{client.clientId}
|
|
|
|
</Badge>
|
|
|
|
)}
|
|
|
|
{role.name}
|
|
|
|
</>
|
|
|
|
);
|
|
|
|
|
|
|
|
type RoleMappingProps = {
|
|
|
|
name: string;
|
|
|
|
id: string;
|
|
|
|
type: MappingType;
|
|
|
|
loader: () => Promise<Row[]>;
|
|
|
|
save: (rows: Row[]) => Promise<void>;
|
|
|
|
onHideRolesToggle: () => void;
|
|
|
|
};
|
|
|
|
|
|
|
|
export const RoleMapping = ({
|
|
|
|
name,
|
|
|
|
id,
|
|
|
|
type,
|
|
|
|
loader,
|
|
|
|
save,
|
|
|
|
onHideRolesToggle,
|
|
|
|
}: RoleMappingProps) => {
|
|
|
|
const { t } = useTranslation("clients");
|
|
|
|
const adminClient = useAdminClient();
|
2021-07-28 12:01:42 +00:00
|
|
|
const { addAlert, addError } = useAlerts();
|
2021-04-20 12:10:00 +00:00
|
|
|
|
|
|
|
const [key, setKey] = useState(0);
|
|
|
|
const refresh = () => setKey(new Date().getTime());
|
|
|
|
|
|
|
|
const [hide, setHide] = useState(false);
|
|
|
|
const [showAssign, setShowAssign] = useState(false);
|
|
|
|
const [selected, setSelected] = useState<Row[]>([]);
|
|
|
|
|
|
|
|
const assignRoles = async (rows: Row[]) => {
|
|
|
|
await save(rows);
|
|
|
|
refresh();
|
|
|
|
};
|
|
|
|
|
|
|
|
const [toggleDeleteDialog, DeleteConfirm] = useConfirmDialog({
|
|
|
|
titleKey: "clients:removeMappingTitle",
|
|
|
|
messageKey: t("removeMappingConfirm", { count: selected.length }),
|
|
|
|
continueButtonLabel: "common:remove",
|
|
|
|
continueButtonVariant: ButtonVariant.danger,
|
|
|
|
onConfirm: async () => {
|
|
|
|
try {
|
2021-06-02 21:20:38 +00:00
|
|
|
switch (type) {
|
|
|
|
case "service-account":
|
|
|
|
await Promise.all(
|
|
|
|
selected.map((row) => {
|
|
|
|
const role = { id: row.role.id!, name: row.role.name! };
|
|
|
|
if (row.client) {
|
|
|
|
return adminClient.users.delClientRoleMappings({
|
2021-04-20 12:10:00 +00:00
|
|
|
id,
|
2021-06-02 21:20:38 +00:00
|
|
|
clientUniqueId: row.client!.id!,
|
|
|
|
roles: [role],
|
|
|
|
});
|
|
|
|
} else {
|
|
|
|
return adminClient.users.delRealmRoleMappings({
|
2021-04-20 12:10:00 +00:00
|
|
|
id,
|
2021-06-02 21:20:38 +00:00
|
|
|
roles: [role],
|
|
|
|
});
|
|
|
|
}
|
|
|
|
})
|
|
|
|
);
|
|
|
|
break;
|
|
|
|
case "client-scope":
|
|
|
|
await Promise.all(
|
|
|
|
selected.map((row) => {
|
|
|
|
const role = { id: row.role.id!, name: row.role.name! };
|
|
|
|
if (row.client) {
|
|
|
|
return adminClient.clientScopes.delClientScopeMappings(
|
|
|
|
{
|
|
|
|
id,
|
|
|
|
client: row.client!.id!,
|
|
|
|
},
|
|
|
|
[role]
|
|
|
|
);
|
|
|
|
} else {
|
|
|
|
return adminClient.clientScopes.delRealmScopeMappings(
|
|
|
|
{
|
|
|
|
id,
|
|
|
|
},
|
|
|
|
[role]
|
|
|
|
);
|
|
|
|
}
|
|
|
|
})
|
|
|
|
);
|
|
|
|
break;
|
2021-04-20 12:10:00 +00:00
|
|
|
}
|
|
|
|
addAlert(t("clientScopeRemoveSuccess"), AlertVariant.success);
|
|
|
|
refresh();
|
|
|
|
} catch (error) {
|
2021-07-28 12:01:42 +00:00
|
|
|
addError("clients:clientScopeRemoveError", error);
|
2021-04-20 12:10:00 +00:00
|
|
|
}
|
|
|
|
},
|
|
|
|
});
|
|
|
|
|
|
|
|
return (
|
|
|
|
<>
|
|
|
|
{showAssign && (
|
|
|
|
<AddRoleMappingModal
|
|
|
|
id={id}
|
|
|
|
type={type}
|
|
|
|
name={name}
|
|
|
|
onAssign={assignRoles}
|
|
|
|
onClose={() => setShowAssign(false)}
|
|
|
|
/>
|
|
|
|
)}
|
|
|
|
<DeleteConfirm />
|
|
|
|
<KeycloakDataTable
|
|
|
|
data-testid="assigned-roles"
|
|
|
|
key={key}
|
|
|
|
loader={loader}
|
2021-06-15 06:14:38 +00:00
|
|
|
canSelectAll
|
|
|
|
onSelect={(rows) => setSelected(rows)}
|
2021-04-20 12:10:00 +00:00
|
|
|
searchPlaceholderKey="clients:searchByName"
|
|
|
|
ariaLabelKey="clients:clientScopeList"
|
2021-06-15 06:14:38 +00:00
|
|
|
isRowDisabled={(value) =>
|
|
|
|
(value.role as CompositeRole).isInherited || false
|
|
|
|
}
|
2021-04-20 12:10:00 +00:00
|
|
|
toolbarItem={
|
|
|
|
<>
|
|
|
|
<ToolbarItem>
|
|
|
|
<Checkbox
|
|
|
|
label={t("hideInheritedRoles")}
|
|
|
|
id="hideInheritedRoles"
|
|
|
|
isChecked={hide}
|
|
|
|
onChange={(check) => {
|
|
|
|
setHide(check);
|
|
|
|
onHideRolesToggle();
|
|
|
|
refresh();
|
|
|
|
}}
|
|
|
|
/>
|
|
|
|
</ToolbarItem>
|
|
|
|
<ToolbarItem>
|
|
|
|
<Button
|
|
|
|
data-testid="assignRole"
|
|
|
|
onClick={() => setShowAssign(true)}
|
|
|
|
>
|
|
|
|
{t("assignRole")}
|
|
|
|
</Button>
|
|
|
|
</ToolbarItem>
|
|
|
|
<ToolbarItem>
|
|
|
|
<Button
|
|
|
|
variant="link"
|
|
|
|
data-testid="unAssignRole"
|
|
|
|
onClick={toggleDeleteDialog}
|
|
|
|
isDisabled={selected.length === 0}
|
|
|
|
>
|
|
|
|
{t("unAssignRole")}
|
|
|
|
</Button>
|
|
|
|
</ToolbarItem>
|
|
|
|
</>
|
|
|
|
}
|
2021-06-22 05:52:13 +00:00
|
|
|
actions={[
|
|
|
|
{
|
|
|
|
title: t("unAssignRole"),
|
|
|
|
onRowClick: async (role) => {
|
|
|
|
setSelected([role]);
|
|
|
|
toggleDeleteDialog();
|
|
|
|
return false;
|
|
|
|
},
|
|
|
|
},
|
|
|
|
]}
|
2021-04-20 12:10:00 +00:00
|
|
|
columns={[
|
|
|
|
{
|
|
|
|
name: "role.name",
|
|
|
|
displayKey: t("name"),
|
|
|
|
cellRenderer: ServiceRole,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "role.parent.name",
|
|
|
|
displayKey: t("inherentFrom"),
|
|
|
|
cellFormatters: [emptyFormatter()],
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "role.description",
|
|
|
|
displayKey: t("description"),
|
|
|
|
cellFormatters: [emptyFormatter()],
|
|
|
|
},
|
|
|
|
]}
|
2021-06-16 11:35:03 +00:00
|
|
|
emptyState={
|
|
|
|
<ListEmptyState
|
|
|
|
message={t("noRoles")}
|
|
|
|
instructions={t("noRolesInstructions")}
|
|
|
|
primaryActionText={t("assignRole")}
|
|
|
|
onPrimaryAction={() => setShowAssign(true)}
|
|
|
|
/>
|
|
|
|
}
|
2021-04-20 12:10:00 +00:00
|
|
|
/>
|
|
|
|
</>
|
|
|
|
);
|
|
|
|
};
|