Now uses the role mapping component (#3184)

This commit is contained in:
Erik Jan de Wit 2022-09-16 10:58:43 +02:00 committed by GitHub
parent 7a556a2e1e
commit 05a660b681
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 114 additions and 391 deletions

View file

@ -458,7 +458,6 @@ describe("Clients test", () => {
}); });
beforeEach(() => { beforeEach(() => {
keycloakBefore();
loginPage.logIn(); loginPage.logIn();
commonPage.sidebar().goToClients(); commonPage.sidebar().goToClients();
commonPage.tableToolbarUtils().searchItem(client); commonPage.tableToolbarUtils().searchItem(client);
@ -591,24 +590,18 @@ describe("Clients test", () => {
commonPage.tableToolbarUtils().searchItem(itemId, false); commonPage.tableToolbarUtils().searchItem(itemId, false);
commonPage.tableUtils().clickRowItemLink(itemId); commonPage.tableUtils().clickRowItemLink(itemId);
rolesTab.goToAssociatedRolesTab(); rolesTab.goToAssociatedRolesTab();
commonPage.tableUtils().selectRowItemAction("create-realm", "Remove"); commonPage.tableUtils().selectRowItemAction("create-realm", "Unassign");
commonPage.sidebar().waitForPageLoad(); commonPage.sidebar().waitForPageLoad();
commonPage commonPage.modalUtils().checkModalTitle("Remove mapping?").confirmModal();
.modalUtils()
.checkModalTitle("Remove associated role?")
.confirmModal();
commonPage.sidebar().waitForPageLoad(); commonPage.sidebar().waitForPageLoad();
commonPage commonPage
.masthead() .masthead()
.checkNotificationMessage("Associated roles have been removed", true); .checkNotificationMessage("Scope mapping successfully removed", true);
commonPage.tableUtils().selectRowItemAction("manage-consent", "Remove"); commonPage.tableUtils().selectRowItemAction("manage-consent", "Unassign");
commonPage.sidebar().waitForPageLoad(); commonPage.sidebar().waitForPageLoad();
commonPage commonPage.modalUtils().checkModalTitle("Remove mapping?").confirmModal();
.modalUtils()
.checkModalTitle("Remove associated role?")
.confirmModal();
}); });
it("Should delete associated role from search bar test", () => { it("Should delete associated role from search bar test", () => {
@ -617,7 +610,7 @@ describe("Clients test", () => {
commonPage.sidebar().waitForPageLoad(); commonPage.sidebar().waitForPageLoad();
rolesTab.goToAssociatedRolesTab(); rolesTab.goToAssociatedRolesTab();
cy.get('td[data-label="Role name"]') cy.get('td[data-label="Name"]')
.contains("manage-account") .contains("manage-account")
.parent() .parent()
.within(() => { .within(() => {
@ -627,15 +620,12 @@ describe("Clients test", () => {
associatedRolesPage.removeAssociatedRoles(); associatedRolesPage.removeAssociatedRoles();
commonPage.sidebar().waitForPageLoad(); commonPage.sidebar().waitForPageLoad();
commonPage commonPage.modalUtils().checkModalTitle("Remove mapping?").confirmModal();
.modalUtils()
.checkModalTitle("Remove associated roles?")
.confirmModal();
commonPage.sidebar().waitForPageLoad(); commonPage.sidebar().waitForPageLoad();
commonPage commonPage
.masthead() .masthead()
.checkNotificationMessage("Associated roles have been removed", true); .checkNotificationMessage("Scope mapping successfully removed", true);
}); });
it("Should delete client role test", () => { it("Should delete client role test", () => {

View file

@ -156,11 +156,11 @@ describe("Realm roles test", () => {
rolesTab.goToAssociatedRolesTab(); rolesTab.goToAssociatedRolesTab();
listingPage.removeItem("create-realm"); listingPage.removeItem("create-realm");
sidebarPage.waitForPageLoad(); sidebarPage.waitForPageLoad();
modalUtils.checkModalTitle("Remove associated role?").confirmModal(); modalUtils.checkModalTitle("Remove mapping?").confirmModal();
sidebarPage.waitForPageLoad(); sidebarPage.waitForPageLoad();
masthead.checkNotificationMessage( masthead.checkNotificationMessage(
"Associated roles have been removed", "Scope mapping successfully removed",
true true
); );
}); });
@ -175,11 +175,11 @@ describe("Realm roles test", () => {
associatedRolesPage.removeAssociatedRoles(); associatedRolesPage.removeAssociatedRoles();
sidebarPage.waitForPageLoad(); sidebarPage.waitForPageLoad();
modalUtils.checkModalTitle("Remove associated roles?").confirmModal(); modalUtils.checkModalTitle("Remove mapping?").confirmModal();
sidebarPage.waitForPageLoad(); sidebarPage.waitForPageLoad();
masthead.checkNotificationMessage( masthead.checkNotificationMessage(
"Associated roles have been removed", "Scope mapping successfully removed",
true true
); );
}); });
@ -206,20 +206,20 @@ describe("Realm roles test", () => {
// delete associated roles from list // delete associated roles from list
listingPage.removeItem("create-realm"); listingPage.removeItem("create-realm");
sidebarPage.waitForPageLoad(); sidebarPage.waitForPageLoad();
modalUtils.checkModalTitle("Remove associated role?").confirmModal(); modalUtils.checkModalTitle("Remove mapping?").confirmModal();
sidebarPage.waitForPageLoad(); sidebarPage.waitForPageLoad();
masthead.checkNotificationMessage( masthead.checkNotificationMessage(
"Associated roles have been removed", "Scope mapping successfully removed",
true true
); );
listingPage.removeItem("offline_access"); listingPage.removeItem("offline_access");
sidebarPage.waitForPageLoad(); sidebarPage.waitForPageLoad();
modalUtils.checkModalTitle("Remove associated role?").confirmModal(); modalUtils.checkModalTitle("Remove mapping?").confirmModal();
sidebarPage.waitForPageLoad(); sidebarPage.waitForPageLoad();
masthead.checkNotificationMessage( masthead.checkNotificationMessage(
"Associated roles have been removed", "Scope mapping successfully removed",
true true
); );
}); });

View file

@ -45,16 +45,14 @@ describe("Realm settings - User registration tab", () => {
it("Remove admin role", () => { it("Remove admin role", () => {
const role = "admin"; const role = "admin";
listingPage.markItemRow(role).removeMarkedItems(); listingPage.markItemRow(role).removeMarkedItems("Unassign");
sidebarPage.waitForPageLoad(); sidebarPage.waitForPageLoad();
modalUtils modalUtils
.checkModalTitle("Remove associated roles?") .checkModalTitle("Remove mapping?")
.checkModalMessage( .checkModalMessage("Are you sure you want to remove this mapping?")
"This action will remove the associated roles of default-roles-master. Users who have permission to default-roles-master will no longer have access to these roles."
)
.checkConfirmButtonText("Remove") .checkConfirmButtonText("Remove")
.confirmModal(); .confirmModal();
masthead.checkNotificationMessage("Associated roles have been removed"); masthead.checkNotificationMessage("Scope mapping successfully removed");
}); });
it("Add default group", () => { it("Add default group", () => {

View file

@ -162,8 +162,8 @@ export default class ListingPage extends CommonElements {
return this; return this;
} }
removeMarkedItems() { removeMarkedItems(name: string = "Remove") {
cy.get(this.listHeaderSecondaryBtn).contains("Remove").click(); cy.get(this.listHeaderSecondaryBtn).contains(name).click();
return this; return this;
} }
@ -246,7 +246,7 @@ export default class ListingPage extends CommonElements {
removeItem(itemName: string) { removeItem(itemName: string) {
this.clickRowDetails(itemName); this.clickRowDetails(itemName);
this.clickDetailMenu("Remove"); this.clickDetailMenu("Unassign");
return this; return this;
} }

View file

@ -9,7 +9,7 @@ enum ClientRolesTabItems {
export default class ClientRolesTab extends CommonPage { export default class ClientRolesTab extends CommonPage {
private createRoleBtn = "create-role"; private createRoleBtn = "create-role";
private createRoleEmptyStateBtn = "no-roles-for-this-client-empty-action"; private createRoleEmptyStateBtn = "no-roles-for-this-client-empty-action";
private hideInheritedRolesChkBox = "#kc-hide-inherited-roles-checkbox"; private hideInheritedRolesChkBox = "#hideInheritedRoles";
private rolesTab = "rolesTab"; private rolesTab = "rolesTab";
private associatedRolesTab = ".kc-associated-roles-tab > button"; private associatedRolesTab = ".kc-associated-roles-tab > button";

View file

@ -28,9 +28,11 @@ export default class DedicatedScopesMappersTab extends CommonPage {
} }
addPredefinedMapper() { addPredefinedMapper() {
this.emptyState() this.emptyState().checkIfExists(true);
.checkIfExists(true) this.emptyState().clickSecondaryBtn(
.clickSecondaryBtn(mapperTypeEmptyState.AddPredefinedMapper, true); mapperTypeEmptyState.AddPredefinedMapper,
true
);
return this; return this;
} }

View file

@ -1,14 +1,14 @@
export default class AssociatedRolesPage { export default class AssociatedRolesPage {
private actionDropdown = "action-dropdown"; private actionDropdown = "action-dropdown";
private addRolesDropdownItem = "add-roles"; private addRolesDropdownItem = "add-roles";
private addRoleToolbarButton = "add-role-button"; private addRoleToolbarButton = "assignRole";
private addAssociatedRolesModalButton = "assign"; private addAssociatedRolesModalButton = "assign";
private compositeRoleBadge = "composite-role-badge"; private compositeRoleBadge = "composite-role-badge";
private filterTypeDropdown = "filter-type-dropdown"; private filterTypeDropdown = "filter-type-dropdown";
private filterTypeDropdownItem = "roles"; private filterTypeDropdownItem = "roles";
private usersPage = "users-page"; private usersPage = "users-page";
private removeRolesButton = "removeRoles"; private removeRolesButton = "unAssignRole";
private addRoleTable = 'td[data-label="Name"]'; private addRoleTable = '[aria-label="Roles"] td[data-label="Name"]';
addAssociatedRealmRole(roleName: string) { addAssociatedRealmRole(roleName: string) {
cy.findByTestId(this.actionDropdown).last().click(); cy.findByTestId(this.actionDropdown).last().click();

View file

@ -1,7 +1,7 @@
export default class UserRegistration { export default class UserRegistration {
private userRegistrationTab = "rs-userRegistration-tab"; private userRegistrationTab = "rs-userRegistration-tab";
private defaultGroupTab = "#pf-tab-20-groups"; private defaultGroupTab = "#pf-tab-20-groups";
private addRoleBtn = "add-role-button"; private addRoleBtn = "assignRole";
private addDefaultGroupBtn = "no-default-groups-empty-action"; private addDefaultGroupBtn = "no-default-groups-empty-action";
private namesColumn = 'tbody td[data-label="Name"]:visible'; private namesColumn = 'tbody td[data-label="Name"]:visible';
private addBtn = "assign"; private addBtn = "assign";

View file

@ -30,7 +30,7 @@
"roleCreateError": "Could not create role: {{error}}", "roleCreateError": "Could not create role: {{error}}",
"roleImportSuccess": "Role import successful", "roleImportSuccess": "Role import successful",
"roleDeleteConfirm": "Delete role?", "roleDeleteConfirm": "Delete role?",
"roleDeleteConfirmDialog": "This action will permanently delete the role {{selectedRoleName}} and cannot be undone.", "roleDeleteConfirmDialog": "This action will permanently delete the role \"{{selectedRoleName}}\" and cannot be undone.",
"roleDeletedSuccess": "The role has been deleted", "roleDeletedSuccess": "The role has been deleted",
"roleDeleteError": "Could not delete role: {{error}}", "roleDeleteError": "Could not delete role: {{error}}",
"defaultRole": "This role serves as a container for both realm and client default roles. It cannot be removed.", "defaultRole": "This role serves as a container for both realm and client default roles. It cannot be removed.",

View file

@ -13,10 +13,8 @@ export type ResourcesKey = keyof KeycloakAdminClient;
type DeleteFunctions = type DeleteFunctions =
| keyof Pick<Groups, "delClientRoleMappings" | "delRealmRoleMappings"> | keyof Pick<Groups, "delClientRoleMappings" | "delRealmRoleMappings">
| keyof Pick< | keyof Pick<ClientScopes, "delClientScopeMappings" | "delRealmScopeMappings">
ClientScopes, | keyof Pick<Roles, "delCompositeRoles">;
"delClientScopeMappings" | "delRealmScopeMappings"
>;
type ListEffectiveFunction = type ListEffectiveFunction =
| keyof Pick<Groups, "listRoleMappings" | "listAvailableRealmRoleMappings"> | keyof Pick<Groups, "listRoleMappings" | "listAvailableRealmRoleMappings">
@ -26,7 +24,7 @@ type ListEffectiveFunction =
| "listAvailableRealmScopeMappings" | "listAvailableRealmScopeMappings"
| "listCompositeClientScopeMappings" | "listCompositeClientScopeMappings"
> >
| keyof Pick<Roles, "getCompositeRoles"> | keyof Pick<Roles, "getCompositeRoles" | "getCompositeRolesForClient">
| keyof Pick< | keyof Pick<
Users, Users,
"listCompositeClientRoleMappings" | "listCompositeRealmRoleMappings" "listCompositeClientRoleMappings" | "listCompositeRealmRoleMappings"
@ -83,8 +81,12 @@ const mapping: ResourceMapping = {
clientScopes: clientFunctions, clientScopes: clientFunctions,
clients: clientFunctions, clients: clientFunctions,
roles: { roles: {
delete: [], delete: ["delCompositeRoles", "delCompositeRoles"],
listEffective: ["getCompositeRoles", "getCompositeRoles"], listEffective: [
"getCompositeRoles",
"getCompositeRoles",
"getCompositeRolesForClient",
],
listAvailable: ["listRoles", "find"], listAvailable: ["listRoles", "find"],
}, },
}; };
@ -140,7 +142,28 @@ export const getMapping = async (
id: string id: string
): Promise<MappingsRepresentation> => { ): Promise<MappingsRepresentation> => {
const query = mapping[type]!.listEffective[0]; const query = mapping[type]!.listEffective[0];
return applyQuery(adminClient, type, query, { id }) as MappingsRepresentation; const result = applyQuery(adminClient, type, query, { id });
if (type !== "roles") {
return result as MappingsRepresentation;
}
const roles = await result;
const clientRoles = await Promise.all(
roles
.filter((r) => r.clientRole)
.map(async (role) => {
const client = await adminClient.clients.findOne({
id: role.containerId!,
});
role.containerId = client?.clientId;
return { ...client, mappings: [role] };
})
);
return {
clientMappings: clientRoles,
realmMappings: roles.filter((r) => !r.clientRole),
};
}; };
export const getEffectiveRoles = async ( export const getEffectiveRoles = async (
@ -149,9 +172,18 @@ export const getEffectiveRoles = async (
id: string id: string
): Promise<Row[]> => { ): Promise<Row[]> => {
const query = mapping[type]!.listEffective[1]; const query = mapping[type]!.listEffective[1];
return (await applyQuery(adminClient, type, query, { id })).map((role) => ({ if (type !== "roles") {
role, return (await applyQuery(adminClient, type, query, { id })).map((role) => ({
})); role,
}));
}
const roles = await applyQuery(adminClient, type, query, { id });
const parentRoles = await Promise.all(
roles
.filter((r) => r.composite)
.map((r) => applyQuery(adminClient, type, query, { id: r.id }))
);
return [...roles, ...parentRoles.flat()].map((role) => ({ role }));
}; };
export const getAvailableRoles = async ( export const getAvailableRoles = async (

View file

@ -1,326 +0,0 @@
import { useState } from "react";
import { useParams, useRouteMatch } from "react-router-dom";
import { useNavigate } from "react-router-dom-v5-compat";
import { useTranslation } from "react-i18next";
import {
AlertVariant,
Button,
ButtonVariant,
Checkbox,
Label,
PageSection,
ToolbarItem,
} from "@patternfly/react-core";
import {
ClientRoleParams,
ClientRoleRoute,
toClientRole,
} from "./routes/ClientRole";
import {
RealmSettingsParams,
RealmSettingsRoute,
} from "../realm-settings/routes/RealmSettings";
import { RealmRoleParams, RealmRoleTab, toRealmRole } from "./routes/RealmRole";
import type RoleRepresentation from "@keycloak/keycloak-admin-client/lib/defs/roleRepresentation";
import { ListEmptyState } from "../components/list-empty-state/ListEmptyState";
import { KeycloakDataTable } from "../components/table-toolbar/KeycloakDataTable";
import { useAlerts } from "../components/alert/Alerts";
import { useConfirmDialog } from "../components/confirm-dialog/ConfirmDialog";
import { emptyFormatter } from "../util";
import { AddRoleMappingModal } from "../components/role-mapping/AddRoleMappingModal";
import { useAdminClient } from "../context/auth/AdminClient";
import type { AttributeForm } from "../components/key-value-form/AttributeForm";
import type ClientRepresentation from "@keycloak/keycloak-admin-client/lib/defs/clientRepresentation";
type AssociatedRolesTabProps = {
parentRole: AttributeForm;
client?: ClientRepresentation;
refresh: () => void;
};
type Role = RoleRepresentation & {
inherited?: string;
};
export const AssociatedRolesTab = ({
parentRole,
refresh: refreshParent,
}: AssociatedRolesTabProps) => {
const { t } = useTranslation("roles");
const navigate = useNavigate();
const { addAlert, addError } = useAlerts();
const { id, realm } = useParams<RealmRoleParams>();
const [key, setKey] = useState(0);
const refresh = () => setKey(new Date().getTime());
const [selectedRows, setSelectedRows] = useState<RoleRepresentation[]>([]);
const [isInheritedHidden, setIsInheritedHidden] = useState(false);
const [count, setCount] = useState(0);
const [open, setOpen] = useState(false);
const { adminClient } = useAdminClient();
const clientRoleRouteMatch = useRouteMatch<ClientRoleParams>(
ClientRoleRoute.path
);
const realmSettingsMatch = useRouteMatch<RealmSettingsParams>(
RealmSettingsRoute.path
);
const subRoles = async (result: Role[], roles: Role[]): Promise<Role[]> => {
const promises = roles.map(async (r) => {
if (result.find((o) => o.id === r.id)) return result;
result.push(r);
if (r.composite) {
const subList = (await adminClient.roles.getCompositeRoles({
id: r.id!,
})) as Role[];
subList.map((o) => (o.inherited = r.name));
result.concat(await subRoles(result, subList));
}
return result;
});
await Promise.all(promises);
return [...result];
};
const loader = async (first?: number, max?: number, search?: string) => {
const compositeRoles = await adminClient.roles.getCompositeRoles({
id: parentRole.id!,
first: first,
max: max!,
search: search,
});
setCount(compositeRoles.length);
if (!isInheritedHidden) {
const children = await subRoles([], compositeRoles);
compositeRoles.splice(0, compositeRoles.length);
compositeRoles.push(...children);
}
await Promise.all(
compositeRoles.map(async (role) => {
if (role.clientRole) {
role.containerId = (
await adminClient.clients.findOne({
id: role.containerId!,
})
)?.clientId;
}
})
);
return compositeRoles;
};
const toRolesTab = (tab: RealmRoleTab | undefined = "associated-roles") => {
const to = clientRoleRouteMatch
? toClientRole({ ...clientRoleRouteMatch.params, tab })
: !realmSettingsMatch
? toRealmRole({
realm,
id,
tab,
})
: undefined;
if (to) navigate(to);
};
const AliasRenderer = ({ id, name, clientRole, containerId }: Role) => {
return (
<>
{clientRole && (
<Label color="blue" key={`label-${id}`}>
{containerId}
</Label>
)}{" "}
{name}
</>
);
};
const toggleModal = () => {
setOpen(!open);
};
const reload = () => {
if (selectedRows.length >= count) {
refreshParent();
toRolesTab("details");
} else {
refresh();
}
};
const [toggleDeleteDialog, DeleteConfirm] = useConfirmDialog({
titleKey: "roles:roleRemoveAssociatedRoleConfirm",
messageKey: t("roles:roleRemoveAssociatedText", {
role: selectedRows.map((r) => r.name),
roleName: parentRole.name,
}),
continueButtonLabel: "common:remove",
continueButtonVariant: ButtonVariant.danger,
onConfirm: async () => {
try {
await adminClient.roles.delCompositeRoles(
{ id: parentRole.id! },
selectedRows
);
reload();
setSelectedRows([]);
addAlert(t("associatedRolesRemoved"), AlertVariant.success);
} catch (error) {
addError("roles:roleDeleteError", error);
}
},
});
const [toggleDeleteAssociatedRolesDialog, DeleteAssociatedRolesConfirm] =
useConfirmDialog({
titleKey: t("roles:removeAssociatedRoles") + "?",
messageKey: t("roles:removeAllAssociatedRolesConfirmDialog", {
name: parentRole.name || t("createRole"),
}),
continueButtonLabel: "common:remove",
continueButtonVariant: ButtonVariant.danger,
onConfirm: async () => {
try {
await adminClient.roles.delCompositeRoles(
{ id: parentRole.id! },
selectedRows
);
addAlert(t("associatedRolesRemoved"), AlertVariant.success);
reload();
} catch (error) {
addError("roles:roleDeleteError", error);
}
},
});
const addComposites = async (composites: RoleRepresentation[]) => {
const compositeArray = composites;
try {
await adminClient.roles.createComposite(
{ roleId: parentRole.id!, realm },
compositeArray
);
toRolesTab();
refresh();
addAlert(t("addAssociatedRolesSuccess"), AlertVariant.success);
} catch (error) {
addError("roles:addAssociatedRolesError", error);
}
};
return (
<PageSection variant="light" padding={{ default: "noPadding" }}>
<DeleteConfirm />
<DeleteAssociatedRolesConfirm />
{open && (
<AddRoleMappingModal
id={parentRole.id!}
type="roles"
name={parentRole.name}
onAssign={(rows) => addComposites(rows.map((r) => r.role))}
onClose={() => setOpen(false)}
/>
)}
<KeycloakDataTable
key={key}
loader={loader}
ariaLabelKey="roles:roleList"
searchPlaceholderKey="roles:searchFor"
canSelectAll
isPaginated
onSelect={(rows) => {
setSelectedRows([
...rows.map((r) => {
delete r.inherited;
return r;
}),
]);
}}
toolbarItem={
<>
<ToolbarItem>
<Checkbox
label="Hide inherited roles"
key="associated-roles-check"
id="kc-hide-inherited-roles-checkbox"
onChange={() => {
setIsInheritedHidden(!isInheritedHidden);
refresh();
}}
isChecked={isInheritedHidden}
/>
</ToolbarItem>
<ToolbarItem>
<Button
key="add-role-button"
onClick={() => toggleModal()}
data-testid="add-role-button"
>
{t("addRole")}
</Button>
</ToolbarItem>
<ToolbarItem>
<Button
variant="link"
isDisabled={selectedRows.length === 0}
key="remove-role-button"
data-testid="removeRoles"
onClick={() => {
toggleDeleteAssociatedRolesDialog();
}}
>
{t("removeRoles")}
</Button>
</ToolbarItem>
</>
}
actions={[
{
title: t("common:remove"),
onRowClick: (role) => {
setSelectedRows([role]);
toggleDeleteDialog();
},
},
]}
columns={[
{
name: "name",
displayKey: "roles:roleName",
cellRenderer: AliasRenderer,
cellFormatters: [emptyFormatter()],
},
{
name: "inherited",
displayKey: "roles:inheritedFrom",
cellFormatters: [emptyFormatter()],
},
{
name: "description",
displayKey: "common:description",
cellFormatters: [emptyFormatter()],
},
]}
emptyState={
<ListEmptyState
hasIcon
message={t("noRolesAssociated")}
instructions={t("noRolesAssociatedInstructions")}
primaryActionText={t("addRole")}
onPrimaryAction={() => setOpen(true)}
/>
}
/>
</PageSection>
);
};

View file

@ -31,7 +31,6 @@ import { RealmRoleForm } from "./RealmRoleForm";
import { useRealm } from "../context/realm-context/RealmContext"; import { useRealm } from "../context/realm-context/RealmContext";
import { AddRoleMappingModal } from "../components/role-mapping/AddRoleMappingModal"; import { AddRoleMappingModal } from "../components/role-mapping/AddRoleMappingModal";
import { KeycloakTabs } from "../components/keycloak-tabs/KeycloakTabs"; import { KeycloakTabs } from "../components/keycloak-tabs/KeycloakTabs";
import { AssociatedRolesTab } from "./AssociatedRolesTab";
import { UsersInRoleTab } from "./UsersInRoleTab"; import { UsersInRoleTab } from "./UsersInRoleTab";
import type RealmRepresentation from "@keycloak/keycloak-admin-client/lib/defs/realmRepresentation"; import type RealmRepresentation from "@keycloak/keycloak-admin-client/lib/defs/realmRepresentation";
import { toRealmRole } from "./routes/RealmRole"; import { toRealmRole } from "./routes/RealmRole";
@ -41,6 +40,7 @@ import {
toClientRole, toClientRole,
} from "./routes/ClientRole"; } from "./routes/ClientRole";
import { PermissionsTab } from "../components/permission-tab/PermissionTab"; import { PermissionsTab } from "../components/permission-tab/PermissionTab";
import { RoleMapping } from "../components/role-mapping/RoleMapping";
export default function RealmRoleTabs() { export default function RealmRoleTabs() {
const { t } = useTranslation("roles"); const { t } = useTranslation("roles");
@ -59,10 +59,10 @@ export default function RealmRoleTabs() {
const { realm: realmName } = useRealm(); const { realm: realmName } = useRealm();
const [key, setKey] = useState(""); const [key, setKey] = useState(0);
const refresh = () => { const refresh = () => {
setKey(`${new Date().getTime()}`); setKey(key + 1);
}; };
const { addAlert, addError } = useAlerts(); const { addAlert, addError } = useAlerts();
@ -184,7 +184,7 @@ export default function RealmRoleTabs() {
const [toggleDeleteDialog, DeleteConfirm] = useConfirmDialog({ const [toggleDeleteDialog, DeleteConfirm] = useConfirmDialog({
titleKey: "roles:roleDeleteConfirm", titleKey: "roles:roleDeleteConfirm",
messageKey: t("roles:roleDeleteConfirmDialog", { messageKey: t("roles:roleDeleteConfirmDialog", {
name: role?.name || t("createRole"), selectedRoleName: role?.name || t("createRole"),
}), }),
continueButtonLabel: "common:delete", continueButtonLabel: "common:delete",
continueButtonVariant: ButtonVariant.danger, continueButtonVariant: ButtonVariant.danger,
@ -385,7 +385,13 @@ export default function RealmRoleTabs() {
className="kc-associated-roles-tab" className="kc-associated-roles-tab"
title={<TabTitleText>{t("associatedRolesText")}</TabTitleText>} title={<TabTitleText>{t("associatedRolesText")}</TabTitleText>}
> >
<AssociatedRolesTab parentRole={role} refresh={refresh} /> <RoleMapping
name={role.name!}
id={role.id!}
type="roles"
isManager
save={(rows) => addComposites(rows.map((r) => r.role))}
/>
</Tab> </Tab>
)} )}
{!isDefaultRole(role.name!) && ( {!isDefaultRole(role.name!) && (

View file

@ -1,12 +1,14 @@
import { useState } from "react"; import { useState } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { Tab, Tabs, TabTitleText } from "@patternfly/react-core"; import { AlertVariant, Tab, Tabs, TabTitleText } from "@patternfly/react-core";
import type RealmRepresentation from "@keycloak/keycloak-admin-client/lib/defs/realmRepresentation"; import type RealmRepresentation from "@keycloak/keycloak-admin-client/lib/defs/realmRepresentation";
import type RoleRepresentation from "@keycloak/keycloak-admin-client/lib/defs/roleRepresentation";
import { useAdminClient, useFetch } from "../context/auth/AdminClient"; import { useAdminClient, useFetch } from "../context/auth/AdminClient";
import { useRealm } from "../context/realm-context/RealmContext"; import { useRealm } from "../context/realm-context/RealmContext";
import { AssociatedRolesTab } from "../realm-roles/AssociatedRolesTab";
import { KeycloakSpinner } from "../components/keycloak-spinner/KeycloakSpinner"; import { KeycloakSpinner } from "../components/keycloak-spinner/KeycloakSpinner";
import { useAlerts } from "../components/alert/Alerts";
import { RoleMapping } from "../components/role-mapping/RoleMapping";
import { DefaultsGroupsTab } from "./DefaultGroupsTab"; import { DefaultsGroupsTab } from "./DefaultGroupsTab";
export const UserRegistration = () => { export const UserRegistration = () => {
@ -16,6 +18,7 @@ export const UserRegistration = () => {
const [key, setKey] = useState(0); const [key, setKey] = useState(0);
const { adminClient } = useAdminClient(); const { adminClient } = useAdminClient();
const { addAlert, addError } = useAlerts();
const { realm: realmName } = useRealm(); const { realm: realmName } = useRealm();
useFetch( useFetch(
@ -28,6 +31,21 @@ export const UserRegistration = () => {
return <KeycloakSpinner />; return <KeycloakSpinner />;
} }
const addComposites = async (composites: RoleRepresentation[]) => {
const compositeArray = composites;
try {
await adminClient.roles.createComposite(
{ roleId: realm.defaultRole!.id!, realm: realmName },
compositeArray
);
setKey(key + 1);
addAlert(t("roles:addAssociatedRolesSuccess"), AlertVariant.success);
} catch (error) {
addError("roles:addAssociatedRolesError", error);
}
};
return ( return (
<Tabs <Tabs
activeKey={activeTab} activeKey={activeTab}
@ -39,9 +57,12 @@ export const UserRegistration = () => {
eventKey={10} eventKey={10}
title={<TabTitleText>{t("defaultRoles")}</TabTitleText>} title={<TabTitleText>{t("defaultRoles")}</TabTitleText>}
> >
<AssociatedRolesTab <RoleMapping
parentRole={{ ...realm.defaultRole, attributes: [] }} name={realm.defaultRole!.name!}
refresh={() => setKey(key + 1)} id={realm.defaultRole!.id!}
type="roles"
isManager
save={(rows) => addComposites(rows.map((r) => r.role))}
/> />
</Tab> </Tab>
<Tab <Tab