Realm settings(Client policies): Add support for adding, listing, and deleting conditions (#1361)
* add create client policy form; WIP add client policy tests checkout realm settings test from master RealmSettingsPage.ts master remove comment and add missing translation fix tests PR feedback from Jon and Erik rebase editClientPolicy edit client policy add client policy conditions form fix bug in create form remove comment update help text fixes breadcrumbs add support for adding multiple conditions, deleting conditions, and list conditions in data table clean up names add delete functionality to conditions form PR feedback from Jon useMemo for conditions remove comments and logs remove unused hook PR feedback from Jon messages rename message rebase * remove duplicate value * fixed multi select bug
This commit is contained in:
parent
6b7060c9d7
commit
a5fd9dd0bf
9 changed files with 498 additions and 31 deletions
197
src/realm-settings/NewClientPolicyCondition.tsx
Normal file
197
src/realm-settings/NewClientPolicyCondition.tsx
Normal file
|
@ -0,0 +1,197 @@
|
||||||
|
import React, { useMemo, useState } from "react";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { Controller, useForm } from "react-hook-form";
|
||||||
|
import {
|
||||||
|
ActionGroup,
|
||||||
|
AlertVariant,
|
||||||
|
Button,
|
||||||
|
FormGroup,
|
||||||
|
PageSection,
|
||||||
|
Select,
|
||||||
|
SelectOption,
|
||||||
|
SelectVariant,
|
||||||
|
} from "@patternfly/react-core";
|
||||||
|
|
||||||
|
import { FormAccess } from "../components/form-access/FormAccess";
|
||||||
|
import { FormPanel } from "../components/scroll-form/FormPanel";
|
||||||
|
import { HelpItem } from "../components/help-enabler/HelpItem";
|
||||||
|
import { useServerInfo } from "../context/server-info/ServerInfoProvider";
|
||||||
|
import type ClientPolicyRepresentation from "@keycloak/keycloak-admin-client/lib/defs/clientPolicyRepresentation";
|
||||||
|
import { camelCase } from "lodash";
|
||||||
|
import { useAdminClient, useFetch } from "../context/auth/AdminClient";
|
||||||
|
import { useAlerts } from "../components/alert/Alerts";
|
||||||
|
import { useHistory, useParams } from "react-router";
|
||||||
|
import type ClientPolicyConditionRepresentation from "@keycloak/keycloak-admin-client/lib/defs/clientPolicyConditionRepresentation";
|
||||||
|
import type ComponentTypeRepresentation from "@keycloak/keycloak-admin-client/lib/defs/componentTypeRepresentation";
|
||||||
|
import { useRealm } from "../context/realm-context/RealmContext";
|
||||||
|
import type { EditClientPolicyParams } from "./routes/EditClientPolicy";
|
||||||
|
|
||||||
|
export const NewClientPolicyCondition = () => {
|
||||||
|
const { t } = useTranslation("realm-settings");
|
||||||
|
const { addAlert, addError } = useAlerts();
|
||||||
|
const history = useHistory();
|
||||||
|
const { realm } = useRealm();
|
||||||
|
|
||||||
|
const { handleSubmit, control } = useForm<ClientPolicyRepresentation>({
|
||||||
|
mode: "onChange",
|
||||||
|
});
|
||||||
|
|
||||||
|
const [openConditionType, setOpenConditionType] = useState(false);
|
||||||
|
const [policies, setPolicies] = useState<ClientPolicyRepresentation[]>([]);
|
||||||
|
const [condition, setCondition] = useState<
|
||||||
|
ClientPolicyConditionRepresentation[]
|
||||||
|
>([]);
|
||||||
|
const [conditionType, setConditionType] = useState("");
|
||||||
|
|
||||||
|
const { policyName } = useParams<EditClientPolicyParams>();
|
||||||
|
|
||||||
|
const serverInfo = useServerInfo();
|
||||||
|
|
||||||
|
const conditionTypes =
|
||||||
|
serverInfo.componentTypes?.[
|
||||||
|
"org.keycloak.services.clientpolicy.condition.ClientPolicyConditionProvider"
|
||||||
|
];
|
||||||
|
|
||||||
|
const adminClient = useAdminClient();
|
||||||
|
|
||||||
|
const currentPolicy = useMemo(
|
||||||
|
() => policies.find(({ name }) => name === policyName),
|
||||||
|
[policies, policyName]
|
||||||
|
);
|
||||||
|
|
||||||
|
useFetch(
|
||||||
|
() => adminClient.clientPolicies.listPolicies(),
|
||||||
|
(policies) => {
|
||||||
|
setPolicies(policies.policies ?? []);
|
||||||
|
},
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
|
||||||
|
const save = async () => {
|
||||||
|
const createdPolicy = {
|
||||||
|
...currentPolicy,
|
||||||
|
profiles: [],
|
||||||
|
conditions: currentPolicy?.conditions?.concat(condition),
|
||||||
|
};
|
||||||
|
|
||||||
|
const index = policies.findIndex(
|
||||||
|
(policy) => createdPolicy.name === policy.name
|
||||||
|
);
|
||||||
|
|
||||||
|
if (index === -1) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const newPolicies = [
|
||||||
|
...policies.slice(0, index),
|
||||||
|
createdPolicy,
|
||||||
|
...policies.slice(index + 1),
|
||||||
|
];
|
||||||
|
|
||||||
|
try {
|
||||||
|
await adminClient.clientPolicies.updatePolicy({
|
||||||
|
policies: newPolicies,
|
||||||
|
});
|
||||||
|
setPolicies(newPolicies);
|
||||||
|
history.push(
|
||||||
|
`/${realm}/realm-settings/clientPolicies/${policyName}/edit-policy`
|
||||||
|
);
|
||||||
|
addAlert(
|
||||||
|
t("realm-settings:createClientConditionSuccess"),
|
||||||
|
AlertVariant.success
|
||||||
|
);
|
||||||
|
} catch (error) {
|
||||||
|
addError("realm-settings:createClientConditionError", error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<PageSection variant="light">
|
||||||
|
<FormPanel className="kc-login-screen" title={t("addCondition")}>
|
||||||
|
<FormAccess
|
||||||
|
isHorizontal
|
||||||
|
role="manage-realm"
|
||||||
|
className="pf-u-mt-lg"
|
||||||
|
onSubmit={handleSubmit(save)}
|
||||||
|
>
|
||||||
|
<FormGroup
|
||||||
|
label={t("conditionType")}
|
||||||
|
fieldId="conditionType"
|
||||||
|
labelIcon={
|
||||||
|
<HelpItem
|
||||||
|
helpText={
|
||||||
|
conditionType
|
||||||
|
? t(`${camelCase(conditionType.replace(/-/g, " "))}`)
|
||||||
|
: t("anyClient")
|
||||||
|
}
|
||||||
|
forLabel={t("conditionType")}
|
||||||
|
forID="conditionType"
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Controller
|
||||||
|
name="conditions"
|
||||||
|
defaultValue={"any-client"}
|
||||||
|
control={control}
|
||||||
|
render={({ onChange, value }) => (
|
||||||
|
<Select
|
||||||
|
placeholderText={t("selectACondition")}
|
||||||
|
toggleId="provider"
|
||||||
|
onToggle={(toggle) => setOpenConditionType(toggle)}
|
||||||
|
onSelect={(_, value) => {
|
||||||
|
onChange(value);
|
||||||
|
setConditionType((value as ComponentTypeRepresentation).id);
|
||||||
|
setCondition([
|
||||||
|
{
|
||||||
|
condition: (value as ComponentTypeRepresentation).id,
|
||||||
|
configuration: {},
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
setOpenConditionType(false);
|
||||||
|
}}
|
||||||
|
selections={conditionType}
|
||||||
|
variant={SelectVariant.single}
|
||||||
|
aria-label={t("conditionType")}
|
||||||
|
isOpen={openConditionType}
|
||||||
|
>
|
||||||
|
{conditionTypes?.map((condition) => (
|
||||||
|
<SelectOption
|
||||||
|
selected={condition.id === value}
|
||||||
|
description={t(
|
||||||
|
`${camelCase(condition.id.replace(/-/g, " "))}`
|
||||||
|
)}
|
||||||
|
key={condition.id}
|
||||||
|
value={condition}
|
||||||
|
>
|
||||||
|
{condition.id}
|
||||||
|
</SelectOption>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</FormGroup>
|
||||||
|
<ActionGroup>
|
||||||
|
<Button
|
||||||
|
variant="primary"
|
||||||
|
type="submit"
|
||||||
|
data-testid="edit-policy-tab-save"
|
||||||
|
isDisabled={conditionType === ""}
|
||||||
|
>
|
||||||
|
{t("common:add")}
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="link"
|
||||||
|
onClick={() =>
|
||||||
|
history.push(
|
||||||
|
`/${realm}/realm-settings/clientPolicies/${policyName}/edit-policy`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{t("common:cancel")}
|
||||||
|
</Button>
|
||||||
|
</ActionGroup>
|
||||||
|
</FormAccess>
|
||||||
|
</FormPanel>
|
||||||
|
</PageSection>
|
||||||
|
);
|
||||||
|
};
|
|
@ -4,6 +4,11 @@ import {
|
||||||
AlertVariant,
|
AlertVariant,
|
||||||
Button,
|
Button,
|
||||||
ButtonVariant,
|
ButtonVariant,
|
||||||
|
DataList,
|
||||||
|
DataListCell,
|
||||||
|
DataListItem,
|
||||||
|
DataListItemCells,
|
||||||
|
DataListItemRow,
|
||||||
Divider,
|
Divider,
|
||||||
DropdownItem,
|
DropdownItem,
|
||||||
Flex,
|
Flex,
|
||||||
|
@ -24,13 +29,14 @@ import { Link, useHistory, useParams } from "react-router-dom";
|
||||||
import { useRealm } from "../context/realm-context/RealmContext";
|
import { useRealm } from "../context/realm-context/RealmContext";
|
||||||
import { useAlerts } from "../components/alert/Alerts";
|
import { useAlerts } from "../components/alert/Alerts";
|
||||||
import { useAdminClient, useFetch } from "../context/auth/AdminClient";
|
import { useAdminClient, useFetch } from "../context/auth/AdminClient";
|
||||||
import type ClientProfileRepresentation from "@keycloak/keycloak-admin-client/lib/defs/clientProfileRepresentation";
|
|
||||||
import { HelpItem } from "../components/help-enabler/HelpItem";
|
import { HelpItem } from "../components/help-enabler/HelpItem";
|
||||||
import { PlusCircleIcon } from "@patternfly/react-icons";
|
import { PlusCircleIcon, TrashIcon } from "@patternfly/react-icons";
|
||||||
import "./RealmSettingsSection.css";
|
import "./RealmSettingsSection.css";
|
||||||
import { useConfirmDialog } from "../components/confirm-dialog/ConfirmDialog";
|
import { useConfirmDialog } from "../components/confirm-dialog/ConfirmDialog";
|
||||||
import type ClientPolicyRepresentation from "@keycloak/keycloak-admin-client/lib/defs/clientPolicyRepresentation";
|
import type ClientPolicyRepresentation from "@keycloak/keycloak-admin-client/lib/defs/clientPolicyRepresentation";
|
||||||
import { toClientPolicies } from "./routes/ClientPolicies";
|
import { toClientPolicies } from "./routes/ClientPolicies";
|
||||||
|
import { toNewClientPolicyCondition } from "./routes/AddCondition";
|
||||||
|
import { useServerInfo } from "../context/server-info/ServerInfoProvider";
|
||||||
import type { EditClientPolicyParams } from "./routes/EditClientPolicy";
|
import type { EditClientPolicyParams } from "./routes/EditClientPolicy";
|
||||||
|
|
||||||
type NewClientPolicyForm = Required<ClientPolicyRepresentation>;
|
type NewClientPolicyForm = Required<ClientPolicyRepresentation>;
|
||||||
|
@ -49,33 +55,42 @@ export const NewClientPolicyForm = () => {
|
||||||
defaultValues,
|
defaultValues,
|
||||||
});
|
});
|
||||||
const { realm } = useRealm();
|
const { realm } = useRealm();
|
||||||
const { policyName } = useParams<EditClientPolicyParams>();
|
|
||||||
const { addAlert, addError } = useAlerts();
|
const { addAlert, addError } = useAlerts();
|
||||||
const adminClient = useAdminClient();
|
const adminClient = useAdminClient();
|
||||||
const [policies, setPolicies] = useState<ClientProfileRepresentation[]>([]);
|
const [policies, setPolicies] = useState<ClientPolicyRepresentation[]>([]);
|
||||||
|
const [currentPolicy, setCurrentPolicy] =
|
||||||
|
useState<ClientPolicyRepresentation>();
|
||||||
const [
|
const [
|
||||||
showAddConditionsAndProfilesForm,
|
showAddConditionsAndProfilesForm,
|
||||||
setShowAddConditionsAndProfilesForm,
|
setShowAddConditionsAndProfilesForm,
|
||||||
] = useState(false);
|
] = useState(false);
|
||||||
|
|
||||||
const [createdPolicy, setCreatedPolicy] =
|
const [conditionToDelete, setConditionToDelete] =
|
||||||
useState<ClientPolicyRepresentation>();
|
useState<{ idx: number; name: string }>();
|
||||||
|
|
||||||
|
const { policyName } = useParams<EditClientPolicyParams>();
|
||||||
|
|
||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
const form = useForm<ClientPolicyRepresentation>({ mode: "onChange" });
|
const form = useForm<ClientPolicyRepresentation>({ mode: "onChange" });
|
||||||
|
const { handleSubmit } = form;
|
||||||
|
|
||||||
|
const [key, setKey] = useState(0);
|
||||||
|
const refresh = () => setKey(new Date().getTime());
|
||||||
|
|
||||||
useFetch(
|
useFetch(
|
||||||
() => adminClient.clientPolicies.listPolicies(),
|
() => adminClient.clientPolicies.listPolicies(),
|
||||||
(policies) => {
|
(policies) => {
|
||||||
setPolicies(policies.policies ?? []);
|
|
||||||
const currentPolicy = policies.policies?.find(
|
const currentPolicy = policies.policies?.find(
|
||||||
(item) => item.name === policyName
|
(item) => item.name === policyName
|
||||||
);
|
);
|
||||||
|
setPolicies(policies.policies ?? []);
|
||||||
if (currentPolicy) {
|
if (currentPolicy) {
|
||||||
setupForm(currentPolicy);
|
setupForm(currentPolicy);
|
||||||
|
setCurrentPolicy(currentPolicy);
|
||||||
|
setShowAddConditionsAndProfilesForm(true);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[]
|
[key]
|
||||||
);
|
);
|
||||||
|
|
||||||
const setupForm = (policy: ClientPolicyRepresentation) => {
|
const setupForm = (policy: ClientPolicyRepresentation) => {
|
||||||
|
@ -85,6 +100,16 @@ export const NewClientPolicyForm = () => {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const policy = policies.filter((policy) => policy.name === policyName);
|
||||||
|
const policyConditions = policy[0]?.conditions || [];
|
||||||
|
|
||||||
|
const serverInfo = useServerInfo();
|
||||||
|
|
||||||
|
const conditionTypes =
|
||||||
|
serverInfo.componentTypes?.[
|
||||||
|
"org.keycloak.services.clientpolicy.condition.ClientPolicyConditionProvider"
|
||||||
|
];
|
||||||
|
|
||||||
const save = async () => {
|
const save = async () => {
|
||||||
const createdForm = form.getValues();
|
const createdForm = form.getValues();
|
||||||
const createdPolicy = {
|
const createdPolicy = {
|
||||||
|
@ -111,10 +136,15 @@ export const NewClientPolicyForm = () => {
|
||||||
t("realm-settings:createClientPolicySuccess"),
|
t("realm-settings:createClientPolicySuccess"),
|
||||||
AlertVariant.success
|
AlertVariant.success
|
||||||
);
|
);
|
||||||
|
history.push(
|
||||||
|
`/${realm}/realm-settings/clientPolicies/${
|
||||||
|
form.getValues().name
|
||||||
|
}/edit-policy`
|
||||||
|
);
|
||||||
setShowAddConditionsAndProfilesForm(true);
|
setShowAddConditionsAndProfilesForm(true);
|
||||||
setCreatedPolicy(createdPolicy);
|
refresh();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
addError("realm-settings:createClientProfileError", error);
|
addError("realm-settings:createClientPolicyError", error);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -142,13 +172,61 @@ export const NewClientPolicyForm = () => {
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const [toggleDeleteConditionDialog, DeleteConditionConfirm] =
|
||||||
|
useConfirmDialog({
|
||||||
|
titleKey: t("deleteClientPolicyConditionConfirmTitle"),
|
||||||
|
messageKey: t("deleteClientPolicyConditionConfirm", {
|
||||||
|
condition: conditionToDelete?.name,
|
||||||
|
}),
|
||||||
|
continueButtonLabel: t("delete"),
|
||||||
|
continueButtonVariant: ButtonVariant.danger,
|
||||||
|
onConfirm: async () => {
|
||||||
|
if (conditionToDelete?.name) {
|
||||||
|
currentPolicy?.conditions?.splice(conditionToDelete.idx!, 1);
|
||||||
|
try {
|
||||||
|
await adminClient.clientPolicies.updatePolicy({
|
||||||
|
policies: policies,
|
||||||
|
});
|
||||||
|
addAlert(t("deleteConditionSuccess"), AlertVariant.success);
|
||||||
|
history.push(
|
||||||
|
`/${realm}/realm-settings/clientPolicies/${
|
||||||
|
form.getValues().name
|
||||||
|
}/edit-policy`
|
||||||
|
);
|
||||||
|
} catch (error) {
|
||||||
|
addError(t("deleteConditionError"), error);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const updatedPolicies = policies.filter(
|
||||||
|
(policy) => policy.name !== policyName
|
||||||
|
);
|
||||||
|
|
||||||
|
try {
|
||||||
|
await adminClient.clientPolicies.updatePolicy({
|
||||||
|
policies: updatedPolicies,
|
||||||
|
});
|
||||||
|
addAlert(t("deleteClientSuccess"), AlertVariant.success);
|
||||||
|
history.push(toClientPolicies({ realm }));
|
||||||
|
} catch (error) {
|
||||||
|
addError(t("deleteClientError"), error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const reset = () => {
|
||||||
|
form.setValue("name", currentPolicy?.name);
|
||||||
|
form.setValue("description", currentPolicy?.description);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<DeleteConfirm />
|
<DeleteConfirm />
|
||||||
|
<DeleteConditionConfirm />
|
||||||
<ViewHeader
|
<ViewHeader
|
||||||
titleKey={
|
titleKey={
|
||||||
showAddConditionsAndProfilesForm || policyName
|
showAddConditionsAndProfilesForm || policyName
|
||||||
? createdPolicy?.name! || policyName
|
? policyName!
|
||||||
: t("createPolicy")
|
: t("createPolicy")
|
||||||
}
|
}
|
||||||
divider
|
divider
|
||||||
|
@ -170,7 +248,12 @@ export const NewClientPolicyForm = () => {
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<PageSection variant="light">
|
<PageSection variant="light">
|
||||||
<FormAccess isHorizontal role="view-realm" className="pf-u-mt-lg">
|
<FormAccess
|
||||||
|
onSubmit={handleSubmit(save)}
|
||||||
|
isHorizontal
|
||||||
|
role="view-realm"
|
||||||
|
className="pf-u-mt-lg"
|
||||||
|
>
|
||||||
<FormGroup
|
<FormGroup
|
||||||
label={t("common:name")}
|
label={t("common:name")}
|
||||||
fieldId="kc-name"
|
fieldId="kc-name"
|
||||||
|
@ -201,7 +284,7 @@ export const NewClientPolicyForm = () => {
|
||||||
<ActionGroup>
|
<ActionGroup>
|
||||||
<Button
|
<Button
|
||||||
variant="primary"
|
variant="primary"
|
||||||
onClick={save}
|
type="submit"
|
||||||
data-testid="saveCreatePolicy"
|
data-testid="saveCreatePolicy"
|
||||||
>
|
>
|
||||||
{t("common:save")}
|
{t("common:save")}
|
||||||
|
@ -210,18 +293,18 @@ export const NewClientPolicyForm = () => {
|
||||||
id="cancelCreatePolicy"
|
id="cancelCreatePolicy"
|
||||||
variant="secondary"
|
variant="secondary"
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
showAddConditionsAndProfilesForm
|
showAddConditionsAndProfilesForm || policyName
|
||||||
? resetForm(createdPolicy)
|
? reset()
|
||||||
: history.push(toClientPolicies({ realm }))
|
: history.push(toClientPolicies({ realm }))
|
||||||
}
|
}
|
||||||
data-testid="cancelCreatePolicy"
|
data-testid="cancelCreatePolicy"
|
||||||
>
|
>
|
||||||
{showAddConditionsAndProfilesForm
|
{showAddConditionsAndProfilesForm
|
||||||
? t("realm-settings:reload")
|
? t("common:revert")
|
||||||
: t("common:cancel")}
|
: t("common:cancel")}
|
||||||
</Button>
|
</Button>
|
||||||
</ActionGroup>
|
</ActionGroup>
|
||||||
{(showAddConditionsAndProfilesForm || policyName) && (
|
{(showAddConditionsAndProfilesForm || form.formState.isSubmitted) && (
|
||||||
<>
|
<>
|
||||||
<Flex>
|
<Flex>
|
||||||
<FlexItem>
|
<FlexItem>
|
||||||
|
@ -240,7 +323,10 @@ export const NewClientPolicyForm = () => {
|
||||||
component={(props) => (
|
component={(props) => (
|
||||||
<Link
|
<Link
|
||||||
{...props}
|
{...props}
|
||||||
to={`/${realm}/realm-settings/clientPolicies`}
|
to={toNewClientPolicyCondition({
|
||||||
|
realm,
|
||||||
|
policyName: form.getValues().name!,
|
||||||
|
})}
|
||||||
></Link>
|
></Link>
|
||||||
)}
|
)}
|
||||||
variant="link"
|
variant="link"
|
||||||
|
@ -252,13 +338,86 @@ export const NewClientPolicyForm = () => {
|
||||||
</Button>
|
</Button>
|
||||||
</FlexItem>
|
</FlexItem>
|
||||||
</Flex>
|
</Flex>
|
||||||
|
{policyConditions.length > 0 ? (
|
||||||
|
<DataList aria-label={t("conditions")} isCompact>
|
||||||
|
{policyConditions.map((condition, idx) => (
|
||||||
|
<DataListItem
|
||||||
|
aria-labelledby={"conditions-list-item"}
|
||||||
|
key={`list-item-${idx}`}
|
||||||
|
id={condition.condition}
|
||||||
|
>
|
||||||
|
<DataListItemRow data-testid="conditions-list-row">
|
||||||
|
<DataListItemCells
|
||||||
|
dataListCells={[
|
||||||
|
<DataListCell
|
||||||
|
key={`name-${idx}`}
|
||||||
|
data-testid="condition-type"
|
||||||
|
>
|
||||||
|
{Object.keys(condition.configuration!).length !==
|
||||||
|
0 ? (
|
||||||
|
<Link
|
||||||
|
key={condition.condition}
|
||||||
|
data-testid="condition-type-link"
|
||||||
|
to={""}
|
||||||
|
className="kc-condition-link"
|
||||||
|
>
|
||||||
|
{condition.condition}
|
||||||
|
</Link>
|
||||||
|
) : (
|
||||||
|
condition.condition
|
||||||
|
)}
|
||||||
|
{conditionTypes?.map(
|
||||||
|
(type) =>
|
||||||
|
type.id === condition.condition && (
|
||||||
|
<>
|
||||||
|
<HelpItem
|
||||||
|
helpText={type.helpText}
|
||||||
|
forLabel={t("conditionTypeHelpText")}
|
||||||
|
forID={t(`common:helpLabel`, {
|
||||||
|
label: t("conditionTypeHelpText"),
|
||||||
|
})}
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
variant="link"
|
||||||
|
isInline
|
||||||
|
icon={
|
||||||
|
<TrashIcon
|
||||||
|
className="kc-conditionType-trash-icon"
|
||||||
|
data-testid="deleteClientProfileDropdown"
|
||||||
|
onClick={() => {
|
||||||
|
toggleDeleteConditionDialog();
|
||||||
|
setConditionToDelete({
|
||||||
|
idx: idx,
|
||||||
|
name: type.id!,
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
></Button>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
)}
|
||||||
|
</DataListCell>,
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
</DataListItemRow>
|
||||||
|
</DataListItem>
|
||||||
|
))}
|
||||||
|
</DataList>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
<Divider />
|
<Divider />
|
||||||
<Text className="kc-emptyConditions" component={TextVariants.h6}>
|
<Text
|
||||||
|
className="kc-emptyConditions"
|
||||||
|
component={TextVariants.h6}
|
||||||
|
>
|
||||||
{t("realm-settings:emptyConditions")}
|
{t("realm-settings:emptyConditions")}
|
||||||
</Text>
|
</Text>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
{(showAddConditionsAndProfilesForm || policyName) && (
|
</>
|
||||||
|
)}
|
||||||
|
{(showAddConditionsAndProfilesForm || form.formState.isSubmitted) && (
|
||||||
<>
|
<>
|
||||||
<Flex>
|
<Flex>
|
||||||
<FlexItem>
|
<FlexItem>
|
||||||
|
|
|
@ -195,17 +195,34 @@ article.pf-c-card.pf-m-flat.kc-login-settings-template
|
||||||
}
|
}
|
||||||
|
|
||||||
.kc-emptyExecutors {
|
.kc-emptyExecutors {
|
||||||
color: #8D9195;
|
color: #8d9195;
|
||||||
}
|
}
|
||||||
|
|
||||||
.kc-emptyConditions {
|
.kc-emptyConditions {
|
||||||
color: #8D9195;
|
color: #8d9195;
|
||||||
}
|
}
|
||||||
|
|
||||||
.kc-emptyClientProfiles {
|
.kc-emptyClientProfiles {
|
||||||
color: #8D9195;
|
color: #8d9195;
|
||||||
}
|
}
|
||||||
|
|
||||||
.kc-action-dropdown {
|
.kc-action-dropdown {
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.kc-condition-link {
|
||||||
|
margin-right: 0.625rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.kc-conditionType-trash-icon {
|
||||||
|
margin-left: .5rem;
|
||||||
|
color: var(--pf-global--Color--400);
|
||||||
|
}
|
||||||
|
|
||||||
|
.kc-conditionType-trash-icon:hover {
|
||||||
|
filter: brightness(55%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.kc-backToPolicies {
|
||||||
|
width: 5rem;
|
||||||
|
}
|
||||||
|
|
|
@ -9,6 +9,7 @@ import { useRealm } from "../context/realm-context/RealmContext";
|
||||||
import { KEY_PROVIDER_TYPE } from "../util";
|
import { KEY_PROVIDER_TYPE } from "../util";
|
||||||
import { toRealmSettings } from "./routes/RealmSettings";
|
import { toRealmSettings } from "./routes/RealmSettings";
|
||||||
import { RealmSettingsTabs } from "./RealmSettingsTabs";
|
import { RealmSettingsTabs } from "./RealmSettingsTabs";
|
||||||
|
import { toClientPolicies } from "./routes/ClientPolicies";
|
||||||
|
|
||||||
export const EditProviderCrumb = () => {
|
export const EditProviderCrumb = () => {
|
||||||
const { t } = useTranslation("realm-settings");
|
const { t } = useTranslation("realm-settings");
|
||||||
|
@ -29,6 +30,50 @@ export const EditProviderCrumb = () => {
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const ToClientPolicies = () => {
|
||||||
|
const { t } = useTranslation("realm-settings");
|
||||||
|
const { realm } = useRealm();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<BreadcrumbItem
|
||||||
|
render={(props) => (
|
||||||
|
<Link {...props} to={toClientPolicies({ realm })}>
|
||||||
|
{t("clientPolicies")}
|
||||||
|
</Link>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const EditPolicyCrumb = () => {
|
||||||
|
const { t } = useTranslation("realm-settings");
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Breadcrumb>
|
||||||
|
<ToClientPolicies />
|
||||||
|
<BreadcrumbItem isActive>{t("policyDetails")}</BreadcrumbItem>
|
||||||
|
</Breadcrumb>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const NewPolicyCrumb = () => {
|
||||||
|
const { t } = useTranslation("realm-settings");
|
||||||
|
const { realm } = useRealm();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Breadcrumb>
|
||||||
|
<BreadcrumbItem
|
||||||
|
render={(props) => (
|
||||||
|
<Link {...props} to={toClientPolicies({ realm })}>
|
||||||
|
{t("clientPolicies")}
|
||||||
|
</Link>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<BreadcrumbItem isActive>{t("createPolicy")}</BreadcrumbItem>
|
||||||
|
</Breadcrumb>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
const sortByPriority = (components: ComponentRepresentation[]) => {
|
const sortByPriority = (components: ComponentRepresentation[]) => {
|
||||||
const sortedComponents = [...components].sort((a, b) => {
|
const sortedComponents = [...components].sort((a, b) => {
|
||||||
const priorityA = Number(a.config?.priority);
|
const priorityA = Number(a.config?.priority);
|
||||||
|
|
|
@ -14,6 +14,7 @@ export default {
|
||||||
deleteProviderError: "Error deleting the provider",
|
deleteProviderError: "Error deleting the provider",
|
||||||
deletedSuccess: "The realm has been deleted",
|
deletedSuccess: "The realm has been deleted",
|
||||||
deleteError: "Could not delete realm: {{error}}",
|
deleteError: "Could not delete realm: {{error}}",
|
||||||
|
deleteConditionSuccess: "The condition has been deleted",
|
||||||
disableConfirmTitle: "Disable realm?",
|
disableConfirmTitle: "Disable realm?",
|
||||||
disableConfirm:
|
disableConfirm:
|
||||||
"User and clients can't access the realm if it's disabled. Are you sure you want to continue?",
|
"User and clients can't access the realm if it's disabled. Are you sure you want to continue?",
|
||||||
|
@ -197,6 +198,10 @@ export default {
|
||||||
createPolicy: "Create policy",
|
createPolicy: "Create policy",
|
||||||
createClientPolicy: "Create client policy",
|
createClientPolicy: "Create client policy",
|
||||||
createClientPolicySuccess: "New policy created",
|
createClientPolicySuccess: "New policy created",
|
||||||
|
createClientConditionSuccess: "Condition created successfully.",
|
||||||
|
createClientConditionError: "Error creating condition: {{error}}",
|
||||||
|
deleteClientConditionSuccess: "Condition deleted successfully.",
|
||||||
|
deleteClientConditionError: "Error creating condition: {{error}}",
|
||||||
clientPolicySearch: "Search client policy",
|
clientPolicySearch: "Search client policy",
|
||||||
policiesConfigType: "Configure via:",
|
policiesConfigType: "Configure via:",
|
||||||
policiesConfigTypes: {
|
policiesConfigTypes: {
|
||||||
|
@ -260,8 +265,28 @@ export default {
|
||||||
"The client profiles configuration was updated",
|
"The client profiles configuration was updated",
|
||||||
updateClientProfilesError:
|
updateClientProfilesError:
|
||||||
"Provided JSON is incorrect: Unexpected token { in JSON",
|
"Provided JSON is incorrect: Unexpected token { in JSON",
|
||||||
|
deleteClientPolicyConditionConfirmTitle: "Delete condition?",
|
||||||
|
deleteClientPolicyConditionConfirm:
|
||||||
|
"This action will permanently delete {{condition}}. This cannot be undone.",
|
||||||
|
selectACondition: "Select a condition",
|
||||||
conditions: "Conditions",
|
conditions: "Conditions",
|
||||||
|
conditionType: "Condition type",
|
||||||
|
policyDetails: "Policy details",
|
||||||
|
anyClient: "The condition is satisfied by any client on any event.",
|
||||||
|
clientAccessType:
|
||||||
|
"It uses the client's access type (confidential, public, bearer-only) to determine whether the policy is applied. Condition is checked during most of OpenID Connect requests (Authorization requests, token requests, introspection endpoint request, etc.)",
|
||||||
|
clientRoles:
|
||||||
|
"The condition checks whether one of the specified client roles exists on the client to determine whether the policy is applied. This effectively allows client administrator to create client role of specified name on the client to make sure that particular client policy will be applied on requests of this client. Condition is checked during most of OpenID Connect requests (Authorization requests, token requests, introspection endpoint request, etc.)",
|
||||||
|
clientScopes:
|
||||||
|
"It uses the scopes requested or assigned in advance to the client to determine whether the policy is applied to this client. Condition is evaluated during OpenID Connect authorization request and/or token request.",
|
||||||
|
clientUpdaterContext:
|
||||||
|
"The condition checks the context how is client created/updated to determine whether the policy is applied. For example it checks if client is created with admin REST API or OIDC dynamic client registration. And for the letter case if it is ANONYMOUS client registration or AUTHENTICATED client registration with Initial access token or Registration access token and so on.",
|
||||||
|
clientUpdaterSourceGroups:
|
||||||
|
"The condition checks the group of the entity who tries to create/update the client to determine whether the policy is applied.",
|
||||||
|
clientUpdaterSourceHost:
|
||||||
|
"The condition checks the host/domain of the entity who tries to create/update the client to determine whether the policy is applied.",
|
||||||
|
clientUpdaterSourceRoles:
|
||||||
|
"The condition checks the role of the entity who tries to create/update the client to determine whether the policy is applied.",
|
||||||
conditionsHelpItem: "Conditions help item",
|
conditionsHelpItem: "Conditions help item",
|
||||||
addCondition: "Add condition",
|
addCondition: "Add condition",
|
||||||
emptyConditions: "No conditions configured",
|
emptyConditions: "No conditions configured",
|
||||||
|
|
|
@ -6,10 +6,10 @@ import { JavaKeystoreSettingsRoute } from "./routes/JavaKeystoreSettings";
|
||||||
import { RealmSettingsRoute } from "./routes/RealmSettings";
|
import { RealmSettingsRoute } from "./routes/RealmSettings";
|
||||||
import { RsaGeneratedSettingsRoute } from "./routes/RsaGeneratedSettings";
|
import { RsaGeneratedSettingsRoute } from "./routes/RsaGeneratedSettings";
|
||||||
import { RsaSettingsRoute } from "./routes/RsaSettings";
|
import { RsaSettingsRoute } from "./routes/RsaSettings";
|
||||||
import { ClientPoliciesRoute } from "./routes/ClientPolicies";
|
|
||||||
import { NewClientProfileRoute } from "./routes/NewClientProfile";
|
import { NewClientProfileRoute } from "./routes/NewClientProfile";
|
||||||
import { NewClientPolicyRoute } from "./routes/NewClientPolicy";
|
import { NewClientPolicyRoute } from "./routes/NewClientPolicy";
|
||||||
import { EditClientPolicyRoute } from "./routes/EditClientPolicy";
|
import { EditClientPolicyRoute } from "./routes/EditClientPolicy";
|
||||||
|
import { NewClientPolicyConditionRoute } from "./routes/AddCondition";
|
||||||
|
|
||||||
const routes: RouteDef[] = [
|
const routes: RouteDef[] = [
|
||||||
RealmSettingsRoute,
|
RealmSettingsRoute,
|
||||||
|
@ -19,10 +19,10 @@ const routes: RouteDef[] = [
|
||||||
JavaKeystoreSettingsRoute,
|
JavaKeystoreSettingsRoute,
|
||||||
RsaGeneratedSettingsRoute,
|
RsaGeneratedSettingsRoute,
|
||||||
RsaSettingsRoute,
|
RsaSettingsRoute,
|
||||||
ClientPoliciesRoute,
|
|
||||||
NewClientProfileRoute,
|
NewClientProfileRoute,
|
||||||
NewClientPolicyRoute,
|
NewClientPolicyRoute,
|
||||||
EditClientPolicyRoute,
|
EditClientPolicyRoute,
|
||||||
|
NewClientPolicyConditionRoute,
|
||||||
];
|
];
|
||||||
|
|
||||||
export default routes;
|
export default routes;
|
||||||
|
|
22
src/realm-settings/routes/AddCondition.ts
Normal file
22
src/realm-settings/routes/AddCondition.ts
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
import type { LocationDescriptorObject } from "history";
|
||||||
|
import { generatePath } from "react-router-dom";
|
||||||
|
import type { RouteDef } from "../../route-config";
|
||||||
|
import { NewClientPolicyCondition } from "../NewClientPolicyCondition";
|
||||||
|
|
||||||
|
export type NewClientPolicyConditionParams = {
|
||||||
|
realm: string;
|
||||||
|
policyName?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const NewClientPolicyConditionRoute: RouteDef = {
|
||||||
|
path: "/:realm/realm-settings/clientPolicies/:policyName?/edit-policy/create-condition",
|
||||||
|
component: NewClientPolicyCondition,
|
||||||
|
breadcrumb: (t) => t("realm-settings:addCondition"),
|
||||||
|
access: "manage-clients",
|
||||||
|
};
|
||||||
|
|
||||||
|
export const toNewClientPolicyCondition = (
|
||||||
|
params: NewClientPolicyConditionParams
|
||||||
|
): LocationDescriptorObject => ({
|
||||||
|
pathname: generatePath(NewClientPolicyConditionRoute.path, params),
|
||||||
|
});
|
|
@ -2,6 +2,7 @@ import type { LocationDescriptorObject } from "history";
|
||||||
import { generatePath } from "react-router-dom";
|
import { generatePath } from "react-router-dom";
|
||||||
import type { RouteDef } from "../../route-config";
|
import type { RouteDef } from "../../route-config";
|
||||||
import { NewClientPolicyForm } from "../NewClientPolicyForm";
|
import { NewClientPolicyForm } from "../NewClientPolicyForm";
|
||||||
|
import { EditPolicyCrumb } from "../RealmSettingsSection";
|
||||||
|
|
||||||
export type EditClientPolicyParams = {
|
export type EditClientPolicyParams = {
|
||||||
realm: string;
|
realm: string;
|
||||||
|
@ -12,7 +13,7 @@ export const EditClientPolicyRoute: RouteDef = {
|
||||||
path: "/:realm/realm-settings/clientPolicies/:policyName/edit-policy",
|
path: "/:realm/realm-settings/clientPolicies/:policyName/edit-policy",
|
||||||
component: NewClientPolicyForm,
|
component: NewClientPolicyForm,
|
||||||
access: "manage-realm",
|
access: "manage-realm",
|
||||||
breadcrumb: (t) => t("identity-providers:editIdPMapper"),
|
breadcrumb: () => EditPolicyCrumb,
|
||||||
};
|
};
|
||||||
|
|
||||||
export const toEditClientPolicy = (
|
export const toEditClientPolicy = (
|
||||||
|
|
|
@ -2,13 +2,14 @@ import type { LocationDescriptorObject } from "history";
|
||||||
import { generatePath } from "react-router-dom";
|
import { generatePath } from "react-router-dom";
|
||||||
import type { RouteDef } from "../../route-config";
|
import type { RouteDef } from "../../route-config";
|
||||||
import { NewClientPolicyForm } from "../NewClientPolicyForm";
|
import { NewClientPolicyForm } from "../NewClientPolicyForm";
|
||||||
|
import { NewPolicyCrumb } from "../RealmSettingsSection";
|
||||||
|
|
||||||
export type NewClientPolicyParams = { realm: string };
|
export type NewClientPolicyParams = { realm: string };
|
||||||
|
|
||||||
export const NewClientPolicyRoute: RouteDef = {
|
export const NewClientPolicyRoute: RouteDef = {
|
||||||
path: "/:realm/realm-settings/clientPolicies/new-client-policy",
|
path: "/:realm/realm-settings/clientPolicies/new-client-policy",
|
||||||
component: NewClientPolicyForm,
|
component: NewClientPolicyForm,
|
||||||
breadcrumb: (t) => t("realm-settings:createPolicy"),
|
breadcrumb: () => NewPolicyCrumb,
|
||||||
access: "manage-clients",
|
access: "manage-clients",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue