Added initial version of the client permission tab (#2452)

This commit is contained in:
Erik Jan de Wit 2022-04-20 11:42:24 +02:00 committed by GitHub
parent 898d609f51
commit 324ebdaf11
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 177 additions and 2 deletions

View file

@ -837,7 +837,7 @@ describe("Clients test", () => {
.checkTabExists(ClientsDetailsTab.Settings, true)
.checkTabExists(ClientsDetailsTab.Roles, true)
.checkTabExists(ClientsDetailsTab.Advanced, true)
.checkNumberOfTabsIsEqual(3);
.checkNumberOfTabsIsEqual(4);
});
it("Hides the delete action", () => {

View file

@ -69,6 +69,8 @@ import {
import { toClientScopesTab } from "./routes/ClientScopeTab";
import { AuthorizationExport } from "./authorization/AuthorizationExport";
import { arrayToAttributes } from "../components/attribute-form/attribute-convert";
import { useServerInfo } from "../context/server-info/ServerInfoProvider";
import { PermissionsTab } from "./permissions/PermissionTab";
type ClientDetailHeaderProps = {
onChange: (value: boolean) => void;
@ -178,6 +180,7 @@ export default function ClientDetails() {
const adminClient = useAdminClient();
const { addAlert, addError } = useAlerts();
const { realm } = useRealm();
const { profileInfo } = useServerInfo();
const history = useHistory();
@ -555,6 +558,19 @@ export default function ClientDetails() {
<ServiceAccount client={client} />
</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
id="advanced"
data-testid="advancedTab"

View file

@ -297,5 +297,7 @@ export default {
"Specifies that this permission must be applied to all resources instances of a given type.",
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.",
permissionsEnabled:
"Determines if fine grained permissions are enabled for managing this role. Disabling will delete all current permissions that have been set up.",
},
};

View file

@ -554,5 +554,23 @@ export default {
never: "Never expires",
},
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.",
},
},
};

View 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>
);
};

View file

@ -0,0 +1,4 @@
.permission-label > .pf-c-form__group-label {
width: 120%;
}

View file

@ -12,7 +12,8 @@ export type ClientTab =
| "advanced"
| "mappers"
| "authorization"
| "serviceAccount";
| "serviceAccount"
| "permissions";
export type ClientParams = {
realm: string;