Added initial version of the client permission tab (#2452)
This commit is contained in:
parent
898d609f51
commit
324ebdaf11
7 changed files with 177 additions and 2 deletions
|
@ -837,7 +837,7 @@ describe("Clients test", () => {
|
||||||
.checkTabExists(ClientsDetailsTab.Settings, true)
|
.checkTabExists(ClientsDetailsTab.Settings, true)
|
||||||
.checkTabExists(ClientsDetailsTab.Roles, true)
|
.checkTabExists(ClientsDetailsTab.Roles, true)
|
||||||
.checkTabExists(ClientsDetailsTab.Advanced, true)
|
.checkTabExists(ClientsDetailsTab.Advanced, true)
|
||||||
.checkNumberOfTabsIsEqual(3);
|
.checkNumberOfTabsIsEqual(4);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("Hides the delete action", () => {
|
it("Hides the delete action", () => {
|
||||||
|
|
|
@ -69,6 +69,8 @@ import {
|
||||||
import { toClientScopesTab } from "./routes/ClientScopeTab";
|
import { toClientScopesTab } from "./routes/ClientScopeTab";
|
||||||
import { AuthorizationExport } from "./authorization/AuthorizationExport";
|
import { AuthorizationExport } from "./authorization/AuthorizationExport";
|
||||||
import { arrayToAttributes } from "../components/attribute-form/attribute-convert";
|
import { arrayToAttributes } from "../components/attribute-form/attribute-convert";
|
||||||
|
import { useServerInfo } from "../context/server-info/ServerInfoProvider";
|
||||||
|
import { PermissionsTab } from "./permissions/PermissionTab";
|
||||||
|
|
||||||
type ClientDetailHeaderProps = {
|
type ClientDetailHeaderProps = {
|
||||||
onChange: (value: boolean) => void;
|
onChange: (value: boolean) => void;
|
||||||
|
@ -178,6 +180,7 @@ export default function ClientDetails() {
|
||||||
const adminClient = useAdminClient();
|
const adminClient = useAdminClient();
|
||||||
const { addAlert, addError } = useAlerts();
|
const { addAlert, addError } = useAlerts();
|
||||||
const { realm } = useRealm();
|
const { realm } = useRealm();
|
||||||
|
const { profileInfo } = useServerInfo();
|
||||||
|
|
||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
|
|
||||||
|
@ -555,6 +558,19 @@ export default function ClientDetails() {
|
||||||
<ServiceAccount client={client} />
|
<ServiceAccount client={client} />
|
||||||
</Tab>
|
</Tab>
|
||||||
)}
|
)}
|
||||||
|
{!profileInfo?.disabledFeatures?.includes(
|
||||||
|
"ADMIN_FINE_GRAINED_AUTHZ"
|
||||||
|
) &&
|
||||||
|
client.access?.manage && (
|
||||||
|
<Tab
|
||||||
|
id="permissions"
|
||||||
|
data-testid="permissionsTab"
|
||||||
|
title={<TabTitleText>{t("permissions")}</TabTitleText>}
|
||||||
|
{...route("permissions")}
|
||||||
|
>
|
||||||
|
<PermissionsTab clientId={client.id!} />
|
||||||
|
</Tab>
|
||||||
|
)}
|
||||||
<Tab
|
<Tab
|
||||||
id="advanced"
|
id="advanced"
|
||||||
data-testid="advancedTab"
|
data-testid="advancedTab"
|
||||||
|
|
|
@ -297,5 +297,7 @@ export default {
|
||||||
"Specifies that this permission must be applied to all resources instances of a given type.",
|
"Specifies that this permission must be applied to all resources instances of a given type.",
|
||||||
permissionDecisionStrategy:
|
permissionDecisionStrategy:
|
||||||
"The decision strategy dictates how the policies associated with a given permission are evaluated and how a final decision is obtained. 'Affirmative' means that at least one policy must evaluate to a positive decision in order for the final decision to be also positive. 'Unanimous' means that all policies must evaluate to a positive decision in order for the final decision to be also positive. 'Consensus' means that the number of positive decisions must be greater than the number of negative decisions. If the number of positive and negative is the same, the final decision will be negative.",
|
"The decision strategy dictates how the policies associated with a given permission are evaluated and how a final decision is obtained. 'Affirmative' means that at least one policy must evaluate to a positive decision in order for the final decision to be also positive. 'Unanimous' means that all policies must evaluate to a positive decision in order for the final decision to be also positive. 'Consensus' means that the number of positive decisions must be greater than the number of negative decisions. If the number of positive and negative is the same, the final decision will be negative.",
|
||||||
|
permissionsEnabled:
|
||||||
|
"Determines if fine grained permissions are enabled for managing this role. Disabling will delete all current permissions that have been set up.",
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -554,5 +554,23 @@ export default {
|
||||||
never: "Never expires",
|
never: "Never expires",
|
||||||
},
|
},
|
||||||
mappers: "Mappers",
|
mappers: "Mappers",
|
||||||
|
|
||||||
|
permissionsEnabled: "Permissions enabled",
|
||||||
|
scopePermissions: {
|
||||||
|
"manage-description":
|
||||||
|
"Policies that decide if an administrator can manage this client",
|
||||||
|
"configure-description":
|
||||||
|
"Reduced management permissions for administrator. Cannot set scope, template, or protocol mappers.",
|
||||||
|
"view-description":
|
||||||
|
"Policies that decide if an administrator can view this client",
|
||||||
|
"map-roles-description":
|
||||||
|
"Policies that decide if an administrator can map roles defined by this client",
|
||||||
|
"map-roles-client-scope-description":
|
||||||
|
"Policies that decide if an administrator can apply roles defined by this client to the client scope of another client",
|
||||||
|
"map-roles-composite-description":
|
||||||
|
"Policies that decide if an administrator can apply roles defined by this client as a composite to another role",
|
||||||
|
"token-exchange-description":
|
||||||
|
"Policies that decide which clients are allowed exchange tokens for a token that is targeted to this client.",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
134
src/clients/permissions/PermissionTab.tsx
Normal file
134
src/clients/permissions/PermissionTab.tsx
Normal file
|
@ -0,0 +1,134 @@
|
||||||
|
import React, { useEffect, useState } from "react";
|
||||||
|
import { Link, useHistory } from "react-router-dom";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { Form, FormGroup, PageSection, Switch } from "@patternfly/react-core";
|
||||||
|
import type { IRowData } from "@patternfly/react-table";
|
||||||
|
|
||||||
|
import type { ManagementPermissionReference } from "@keycloak/keycloak-admin-client/lib/defs/managementPermissionReference";
|
||||||
|
import { KeycloakDataTable } from "../../components/table-toolbar/KeycloakDataTable";
|
||||||
|
import { useAdminClient } from "../../context/auth/AdminClient";
|
||||||
|
import { useRealm } from "../../context/realm-context/RealmContext";
|
||||||
|
import { toPermissionDetails } from "../routes/PermissionDetails";
|
||||||
|
import { KeycloakSpinner } from "../../components/keycloak-spinner/KeycloakSpinner";
|
||||||
|
import { HelpItem } from "../../components/help-enabler/HelpItem";
|
||||||
|
|
||||||
|
import "./permissions-tab.css";
|
||||||
|
|
||||||
|
type PermissionsTabProps = {
|
||||||
|
clientId: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const PermissionsTab = ({ clientId }: PermissionsTabProps) => {
|
||||||
|
const { t } = useTranslation("clients");
|
||||||
|
const history = useHistory();
|
||||||
|
const adminClient = useAdminClient();
|
||||||
|
const { realm } = useRealm();
|
||||||
|
const [realmId, setRealmId] = useState("");
|
||||||
|
const [permission, setPermission] = useState<ManagementPermissionReference>();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
Promise.all([
|
||||||
|
adminClient.clients.find({
|
||||||
|
search: true,
|
||||||
|
clientId: realm,
|
||||||
|
}),
|
||||||
|
adminClient.clients.listFineGrainPermissions({ id: clientId }),
|
||||||
|
]).then(([clients, permission]) => {
|
||||||
|
setRealmId(clients[0]?.id!);
|
||||||
|
setPermission(permission);
|
||||||
|
});
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const PermissionDetailLink = (permission: Record<string, string>) => (
|
||||||
|
<Link
|
||||||
|
key={permission.id}
|
||||||
|
to={toPermissionDetails({
|
||||||
|
realm,
|
||||||
|
id: realmId,
|
||||||
|
permissionType: "scope",
|
||||||
|
permissionId: permission.id,
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
{permission.name}
|
||||||
|
</Link>
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!permission) {
|
||||||
|
return <KeycloakSpinner />;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<PageSection variant="light" className="pf-u-p-0">
|
||||||
|
<PageSection variant="light">
|
||||||
|
<Form isHorizontal>
|
||||||
|
<FormGroup
|
||||||
|
className="permission-label"
|
||||||
|
label={t("permissionsEnabled")}
|
||||||
|
fieldId="permissionsEnabled"
|
||||||
|
labelIcon={
|
||||||
|
<HelpItem
|
||||||
|
helpText="clients-help:permissionsEnabled"
|
||||||
|
fieldLabelId="clients:permissionsEnabled"
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Switch
|
||||||
|
id="permissionsEnabled"
|
||||||
|
label={t("common:on")}
|
||||||
|
labelOff={t("common:off")}
|
||||||
|
isChecked={permission.enabled}
|
||||||
|
onChange={async (enabled) => {
|
||||||
|
const p = await adminClient.clients.updateFineGrainPermission(
|
||||||
|
{ id: clientId },
|
||||||
|
{ enabled }
|
||||||
|
);
|
||||||
|
setPermission(p);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</FormGroup>
|
||||||
|
</Form>
|
||||||
|
</PageSection>
|
||||||
|
<KeycloakDataTable
|
||||||
|
loader={Object.entries(permission.scopePermissions || {}).map(
|
||||||
|
([name, id]) => ({
|
||||||
|
id,
|
||||||
|
name,
|
||||||
|
})
|
||||||
|
)}
|
||||||
|
ariaLabelKey="clients:permissions"
|
||||||
|
searchPlaceholderKey="clients:searchForPermission"
|
||||||
|
actionResolver={(rowData: IRowData) => {
|
||||||
|
const permission: Record<string, string> = rowData.data;
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
title: t("common:edit"),
|
||||||
|
onClick() {
|
||||||
|
history.push(
|
||||||
|
toPermissionDetails({
|
||||||
|
realm,
|
||||||
|
id: realmId,
|
||||||
|
permissionType: "scope",
|
||||||
|
permissionId: permission.id,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}}
|
||||||
|
columns={[
|
||||||
|
{
|
||||||
|
name: "scopeName",
|
||||||
|
displayKey: "common:name",
|
||||||
|
cellRenderer: PermissionDetailLink,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "description",
|
||||||
|
displayKey: "common:description",
|
||||||
|
cellRenderer: (permission: Record<string, string>) =>
|
||||||
|
t(`scopePermissions.${permission.name}-description`),
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
</PageSection>
|
||||||
|
);
|
||||||
|
};
|
4
src/clients/permissions/permissions-tab.css
Normal file
4
src/clients/permissions/permissions-tab.css
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
|
||||||
|
.permission-label > .pf-c-form__group-label {
|
||||||
|
width: 120%;
|
||||||
|
}
|
|
@ -12,7 +12,8 @@ export type ClientTab =
|
||||||
| "advanced"
|
| "advanced"
|
||||||
| "mappers"
|
| "mappers"
|
||||||
| "authorization"
|
| "authorization"
|
||||||
| "serviceAccount";
|
| "serviceAccount"
|
||||||
|
| "permissions";
|
||||||
|
|
||||||
export type ClientParams = {
|
export type ClientParams = {
|
||||||
realm: string;
|
realm: string;
|
||||||
|
|
Loading…
Reference in a new issue