Now uses the role mapping component (#3184)
This commit is contained in:
parent
7a556a2e1e
commit
05a660b681
13 changed files with 114 additions and 391 deletions
|
@ -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", () => {
|
||||||
|
|
|
@ -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
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
|
@ -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", () => {
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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";
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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";
|
||||||
|
|
|
@ -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.",
|
||||||
|
|
|
@ -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 (
|
||||||
|
|
|
@ -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>
|
|
||||||
);
|
|
||||||
};
|
|
|
@ -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!) && (
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in a new issue