Fine-grained permissions for user section. (#2451)
This commit is contained in:
parent
d5f6aca82e
commit
1c296a1641
7 changed files with 39 additions and 12 deletions
|
@ -16,9 +16,15 @@ export type AttributesFormProps = {
|
||||||
form: UseFormMethods<AttributeForm>;
|
form: UseFormMethods<AttributeForm>;
|
||||||
save?: (model: AttributeForm) => void;
|
save?: (model: AttributeForm) => void;
|
||||||
reset?: () => void;
|
reset?: () => void;
|
||||||
|
fineGrainedAccess?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const AttributesForm = ({ form, reset, save }: AttributesFormProps) => {
|
export const AttributesForm = ({
|
||||||
|
form,
|
||||||
|
reset,
|
||||||
|
save,
|
||||||
|
fineGrainedAccess,
|
||||||
|
}: AttributesFormProps) => {
|
||||||
const { t } = useTranslation("roles");
|
const { t } = useTranslation("roles");
|
||||||
const noSaveCancelButtons = !save && !reset;
|
const noSaveCancelButtons = !save && !reset;
|
||||||
const {
|
const {
|
||||||
|
@ -30,6 +36,7 @@ export const AttributesForm = ({ form, reset, save }: AttributesFormProps) => {
|
||||||
<FormAccess
|
<FormAccess
|
||||||
role="manage-realm"
|
role="manage-realm"
|
||||||
onSubmit={save ? handleSubmit(save) : undefined}
|
onSubmit={save ? handleSubmit(save) : undefined}
|
||||||
|
fineGrainedAccess={fineGrainedAccess}
|
||||||
>
|
>
|
||||||
<FormProvider {...form}>
|
<FormProvider {...form}>
|
||||||
<KeyValueInput name="attributes" />
|
<KeyValueInput name="attributes" />
|
||||||
|
|
|
@ -56,6 +56,7 @@ export const UserAttributes = ({ user: defaultUser }: UserAttributesProps) => {
|
||||||
<AttributesForm
|
<AttributesForm
|
||||||
form={form}
|
form={form}
|
||||||
save={save}
|
save={save}
|
||||||
|
fineGrainedAccess={user.access?.manage}
|
||||||
reset={() =>
|
reset={() =>
|
||||||
form.reset({
|
form.reset({
|
||||||
attributes: convertAttributes(),
|
attributes: convertAttributes(),
|
||||||
|
|
|
@ -153,6 +153,7 @@ export const UserForm = ({
|
||||||
isHorizontal
|
isHorizontal
|
||||||
onSubmit={handleSubmit(save)}
|
onSubmit={handleSubmit(save)}
|
||||||
role="manage-users"
|
role="manage-users"
|
||||||
|
fineGrainedAccess={user?.access?.manage}
|
||||||
className="pf-u-mt-lg"
|
className="pf-u-mt-lg"
|
||||||
>
|
>
|
||||||
{open && (
|
{open && (
|
||||||
|
|
|
@ -216,6 +216,7 @@ export const UserGroups = ({ user }: UserGroupsProps) => {
|
||||||
data-testid={`leave-${group.name}`}
|
data-testid={`leave-${group.name}`}
|
||||||
onClick={() => leave([group])}
|
onClick={() => leave([group])}
|
||||||
variant="link"
|
variant="link"
|
||||||
|
isDisabled={!user.access?.manageGroupMembership}
|
||||||
>
|
>
|
||||||
{t("leave")}
|
{t("leave")}
|
||||||
</Button>
|
</Button>
|
||||||
|
@ -286,6 +287,7 @@ export const UserGroups = ({ user }: UserGroupsProps) => {
|
||||||
className="kc-join-group-button"
|
className="kc-join-group-button"
|
||||||
onClick={toggleModal}
|
onClick={toggleModal}
|
||||||
data-testid="add-group-button"
|
data-testid="add-group-button"
|
||||||
|
isDisabled={!user.access?.manageGroupMembership}
|
||||||
>
|
>
|
||||||
{t("joinGroup")}
|
{t("joinGroup")}
|
||||||
</Button>
|
</Button>
|
||||||
|
|
|
@ -23,6 +23,7 @@ import {
|
||||||
SearchIcon,
|
SearchIcon,
|
||||||
WarningTriangleIcon,
|
WarningTriangleIcon,
|
||||||
} from "@patternfly/react-icons";
|
} from "@patternfly/react-icons";
|
||||||
|
import type { IRowData } from "@patternfly/react-table";
|
||||||
import type RealmRepresentation from "@keycloak/keycloak-admin-client/lib/defs/realmRepresentation";
|
import type RealmRepresentation from "@keycloak/keycloak-admin-client/lib/defs/realmRepresentation";
|
||||||
import type ComponentRepresentation from "@keycloak/keycloak-admin-client/lib/defs/componentRepresentation";
|
import type ComponentRepresentation from "@keycloak/keycloak-admin-client/lib/defs/componentRepresentation";
|
||||||
import type UserRepresentation from "@keycloak/keycloak-admin-client/lib/defs/userRepresentation";
|
import type UserRepresentation from "@keycloak/keycloak-admin-client/lib/defs/userRepresentation";
|
||||||
|
@ -113,7 +114,10 @@ export default function UsersSection() {
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const users = await adminClient.users.find({ ...params });
|
const users = await adminClient.users.find({
|
||||||
|
briefRepresentation: true,
|
||||||
|
...params,
|
||||||
|
});
|
||||||
if (realm?.bruteForceProtected) {
|
if (realm?.bruteForceProtected) {
|
||||||
const brutes = await Promise.all(
|
const brutes = await Promise.all(
|
||||||
users.map((user: BruteUser) =>
|
users.map((user: BruteUser) =>
|
||||||
|
@ -344,15 +348,20 @@ export default function UsersSection() {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
toolbarItem={toolbar}
|
toolbarItem={toolbar}
|
||||||
actions={[
|
actionResolver={(rowData: IRowData) => {
|
||||||
{
|
const user: UserRepresentation = rowData.data;
|
||||||
title: t("common:delete"),
|
if (!user.access?.manage) return [];
|
||||||
onRowClick: (user) => {
|
|
||||||
setSelectedRows([user]);
|
return [
|
||||||
toggleDeleteDialog();
|
{
|
||||||
|
title: t("common:delete"),
|
||||||
|
onClick: () => {
|
||||||
|
setSelectedRows([user]);
|
||||||
|
toggleDeleteDialog();
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
];
|
||||||
]}
|
}}
|
||||||
columns={[
|
columns={[
|
||||||
{
|
{
|
||||||
name: "username",
|
name: "username",
|
||||||
|
|
|
@ -157,11 +157,16 @@ const UsersTabs = () => {
|
||||||
dropdownItems={[
|
dropdownItems={[
|
||||||
<DropdownItem
|
<DropdownItem
|
||||||
key="impersonate"
|
key="impersonate"
|
||||||
|
isDisabled={!user?.access?.impersonate}
|
||||||
onClick={() => toggleImpersonateDialog()}
|
onClick={() => toggleImpersonateDialog()}
|
||||||
>
|
>
|
||||||
{t("impersonate")}
|
{t("impersonate")}
|
||||||
</DropdownItem>,
|
</DropdownItem>,
|
||||||
<DropdownItem key="delete" onClick={() => toggleDeleteDialog()}>
|
<DropdownItem
|
||||||
|
key="delete"
|
||||||
|
isDisabled={!user?.access?.manage}
|
||||||
|
onClick={() => toggleDeleteDialog()}
|
||||||
|
>
|
||||||
{t("common:delete")}
|
{t("common:delete")}
|
||||||
</DropdownItem>,
|
</DropdownItem>,
|
||||||
]}
|
]}
|
||||||
|
@ -196,6 +201,7 @@ const UsersTabs = () => {
|
||||||
<Tab
|
<Tab
|
||||||
eventKey="credentials"
|
eventKey="credentials"
|
||||||
data-testid="credentials"
|
data-testid="credentials"
|
||||||
|
isHidden={!user.access?.manage}
|
||||||
title={<TabTitleText>{t("common:credentials")}</TabTitleText>}
|
title={<TabTitleText>{t("common:credentials")}</TabTitleText>}
|
||||||
>
|
>
|
||||||
<UserCredentials user={user} />
|
<UserCredentials user={user} />
|
||||||
|
@ -203,6 +209,7 @@ const UsersTabs = () => {
|
||||||
<Tab
|
<Tab
|
||||||
eventKey="role-mapping"
|
eventKey="role-mapping"
|
||||||
data-testid="role-mapping-tab"
|
data-testid="role-mapping-tab"
|
||||||
|
isHidden={!user.access?.mapRoles}
|
||||||
title={<TabTitleText>{t("roleMapping")}</TabTitleText>}
|
title={<TabTitleText>{t("roleMapping")}</TabTitleText>}
|
||||||
>
|
>
|
||||||
<UserRoleMapping id={id} name={user.username!} />
|
<UserRoleMapping id={id} name={user.username!} />
|
||||||
|
|
|
@ -15,7 +15,7 @@ export const UserRoute: RouteDef = {
|
||||||
path: "/:realm/users/:id/:tab",
|
path: "/:realm/users/:id/:tab",
|
||||||
component: lazy(() => import("../UsersTabs")),
|
component: lazy(() => import("../UsersTabs")),
|
||||||
breadcrumb: (t) => t("users:userDetails"),
|
breadcrumb: (t) => t("users:userDetails"),
|
||||||
access: "manage-users",
|
access: "view-users",
|
||||||
};
|
};
|
||||||
|
|
||||||
export const toUser = (params: UserParams): LocationDescriptorObject => ({
|
export const toUser = (params: UserParams): LocationDescriptorObject => ({
|
||||||
|
|
Loading…
Reference in a new issue