added user role mapping tab to user details (#1075)

* added user role mapping tab to user details

* fix imports to admin client

* Update src/user/UserRoleMapping.tsx

Co-authored-by: Jon Koops <jonkoops@gmail.com>

* Update src/user/UserRoleMapping.tsx

Co-authored-by: Jon Koops <jonkoops@gmail.com>

* added await

* fixed merge error

Co-authored-by: Jon Koops <jonkoops@gmail.com>
This commit is contained in:
Erik Jan de Wit 2021-09-03 16:40:59 +02:00 committed by GitHub
parent f87463d036
commit e187314b1f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 115 additions and 7 deletions

View file

@ -1,6 +1,5 @@
export default { export default {
groups: { groups: {
groups: "Groups",
groupDetails: "Group details", groupDetails: "Group details",
childGroups: "Child groups", childGroups: "Child groups",
createGroup: "Create group", createGroup: "Create group",

View file

@ -61,7 +61,7 @@ export const UsersInRoleTab = () => {
variant="link" variant="link"
onClick={() => history.push(`/${realm}/groups`)} onClick={() => history.push(`/${realm}/groups`)}
> >
{t("groups")} {t("common:groups")}
</Button> </Button>
{t("or")} {t("or")}
<Button <Button
@ -98,7 +98,7 @@ export const UsersInRoleTab = () => {
variant="link" variant="link"
onClick={() => history.push(`/${realm}/groups`)} onClick={() => history.push(`/${realm}/groups`)}
> >
{t("groups")} {t("common:groups")}
</Button> </Button>
{t("or")} {t("or")}
<Button <Button

View file

@ -72,7 +72,6 @@ export default {
"Only the users with this role directly assigned will appear under this tab. If you need to find users assigned to this role, go to", "Only the users with this role directly assigned will appear under this tab. If you need to find users assigned to this role, go to",
noUsersEmptyStateDescriptionContinued: noUsersEmptyStateDescriptionContinued:
"to find them. Users that already have this role as an effective role cannot be added here.", "to find them. Users that already have this role as an effective role cannot be added here.",
groups: "Groups",
or: "or", or: "or",
users: "Users", users: "Users",
userName: "Username", userName: "Username",

View file

@ -310,7 +310,7 @@ export const KeysTabInner = ({ components, refresh }: KeysTabInnerProps) => {
</ToolbarGroup> </ToolbarGroup>
</Toolbar> </Toolbar>
<DataList <DataList
aria-label={t("groups")} aria-label={t("common:groups")}
onDragFinish={onDragFinish} onDragFinish={onDragFinish}
onDragStart={onDragStart} onDragStart={onDragStart}
onDragMove={onDragMove} onDragMove={onDragMove}

View file

@ -0,0 +1,101 @@
import React, { useState } from "react";
import { useTranslation } from "react-i18next";
import { AlertVariant } from "@patternfly/react-core";
import type { RoleMappingPayload } from "@keycloak/keycloak-admin-client/lib/defs/roleRepresentation";
import { useAdminClient } from "../context/auth/AdminClient";
import { useAlerts } from "../components/alert/Alerts";
import {
mapRoles,
RoleMapping,
Row,
} from "../components/role-mapping/RoleMapping";
type UserRoleMappingProps = {
id: string;
name: string;
};
export const UserRoleMapping = ({ id, name }: UserRoleMappingProps) => {
const { t } = useTranslation("clients");
const adminClient = useAdminClient();
const { addAlert, addError } = useAlerts();
const [hide, setHide] = useState(false);
const loader = async () => {
const [assignedRoles, effectiveRoles] = await Promise.all([
adminClient.users
.listRealmRoleMappings({ id })
.then((roles) => roles.map((role) => ({ role }))),
adminClient.users
.listCompositeRealmRoleMappings({ id })
.then((roles) => roles.map((role) => ({ role }))),
]);
const clients = await adminClient.clients.find();
const clientRoles = (
await Promise.all(
clients.map(async (client) => {
const [clientAssignedRoles, clientEffectiveRoles] = await Promise.all(
[
adminClient.users
.listClientRoleMappings({
id,
clientUniqueId: client.id!,
})
.then((roles) => roles.map((role) => ({ role, client }))),
adminClient.users
.listCompositeClientRoleMappings({
id,
clientUniqueId: client.id!,
})
.then((roles) => roles.map((role) => ({ role, client }))),
]
);
return mapRoles(clientAssignedRoles, clientEffectiveRoles, hide);
})
)
).flat();
return [...mapRoles(assignedRoles, effectiveRoles, hide), ...clientRoles];
};
const assignRoles = async (rows: Row[]) => {
try {
const realmRoles = rows
.filter((row) => row.client === undefined)
.map((row) => row.role as RoleMappingPayload)
.flat();
await adminClient.users.addRealmRoleMappings({
id,
roles: realmRoles,
});
await Promise.all(
rows
.filter((row) => row.client !== undefined)
.map((row) =>
adminClient.users.addClientRoleMappings({
id,
clientUniqueId: row.client!.id!,
roles: [row.role as RoleMappingPayload],
})
)
);
addAlert(t("roleMappingUpdatedSuccess"), AlertVariant.success);
} catch (error) {
addError("clients:roleMappingUpdatedError", error);
}
};
return (
<RoleMapping
name={name}
id={id}
type="service-account"
loader={loader}
save={assignRoles}
onHideRolesToggle={() => setHide(!hide)}
/>
);
};

View file

@ -25,6 +25,7 @@ import { UserIdentityProviderLinks } from "./UserIdentityProviderLinks";
import { useConfirmDialog } from "../components/confirm-dialog/ConfirmDialog"; import { useConfirmDialog } from "../components/confirm-dialog/ConfirmDialog";
import { toUser } from "./routes/User"; import { toUser } from "./routes/User";
import { toUsers } from "./routes/Users"; import { toUsers } from "./routes/Users";
import { UserRoleMapping } from "./UserRoleMapping";
export const UsersTabs = () => { export const UsersTabs = () => {
const { t } = useTranslation("users"); const { t } = useTranslation("users");
@ -89,7 +90,7 @@ export const UsersTabs = () => {
history.push(toUser({ id: createdUser.id, realm, tab: "settings" })); history.push(toUser({ id: createdUser.id, realm, tab: "settings" }));
} }
} catch (error) { } catch (error) {
addError("users:userCreateError", error); addError("userCreateError", error);
} }
}; };
@ -135,7 +136,7 @@ export const UsersTabs = () => {
<ImpersonateConfirm /> <ImpersonateConfirm />
<DeleteConfirm /> <DeleteConfirm />
<ViewHeader <ViewHeader
titleKey={user?.username || t("users:createUser")} titleKey={user?.username || t("createUser")}
divider={!id} divider={!id}
dropdownItems={[ dropdownItems={[
<DropdownItem <DropdownItem
@ -183,6 +184,13 @@ export const UsersTabs = () => {
> >
<UserConsents /> <UserConsents />
</Tab> </Tab>
<Tab
eventKey="role-mapping"
data-testid="role-mapping-tab"
title={<TabTitleText>{t("roleMapping")}</TabTitleText>}
>
<UserRoleMapping id={id} name={user.username!} />
</Tab>
<Tab <Tab
eventKey="identity-provider-links" eventKey="identity-provider-links"
data-testid="identity-provider-links-tab" data-testid="identity-provider-links-tab"

View file

@ -106,6 +106,7 @@ export default {
"Are you sure you want to revoke all granted client scopes for {{clientId}}?", "Are you sure you want to revoke all granted client scopes for {{clientId}}?",
deleteGrantsSuccess: "Grants successfully revoked.", deleteGrantsSuccess: "Grants successfully revoked.",
deleteGrantsError: "Error deleting grants.", deleteGrantsError: "Error deleting grants.",
roleMapping: "Role mapping",
unlockAllUsers: "Unlock all users", unlockAllUsers: "Unlock all users",
unlockUsersConfirm: unlockUsersConfirm:
"All the users that are temporarily locked will be unlocked.", "All the users that are temporarily locked will be unlocked.",