diff --git a/cypress/integration/realm_settings_test.spec.ts b/cypress/integration/realm_settings_test.spec.ts
index 88ac61a48b..a1ff5e083d 100644
--- a/cypress/integration/realm_settings_test.spec.ts
+++ b/cypress/integration/realm_settings_test.spec.ts
@@ -566,6 +566,30 @@ describe("Realm settings tests", () => {
realmSettingsPage.shouldSearchClientPolicy();
});
+ it("Should not have conditions configured by default", () => {
+ realmSettingsPage.shouldNotHaveConditionsConfigured();
+ });
+
+ it("Should cancel adding a new condition to a client profile", () => {
+ realmSettingsPage.shouldCancelAddingCondition();
+ });
+
+ it("Should add a new condition to a client profile", () => {
+ realmSettingsPage.shouldAddCondition();
+ });
+
+ it("Should edit the condition of a client profile", () => {
+ realmSettingsPage.shouldEditCondition();
+ });
+
+ it("Should cancel deleting condition from a client profile", () => {
+ realmSettingsPage.shouldCancelDeletingCondition();
+ });
+
+ it("Should delete condition from a client profile", () => {
+ realmSettingsPage.shouldDeleteCondition();
+ });
+
it("Check cancelling the client policy deletion", () => {
realmSettingsPage.shouldDisplayDeleteClientPolicyDialog();
});
diff --git a/cypress/support/pages/admin_console/manage/realm_settings/RealmSettingsPage.ts b/cypress/support/pages/admin_console/manage/realm_settings/RealmSettingsPage.ts
index 56e1994b50..485204b908 100644
--- a/cypress/support/pages/admin_console/manage/realm_settings/RealmSettingsPage.ts
+++ b/cypress/support/pages/admin_console/manage/realm_settings/RealmSettingsPage.ts
@@ -176,8 +176,12 @@ export default class RealmSettingsPage {
private clientPolicyDrpDwn = "action-dropdown";
private searchFld = "[id^=realm-settings][id$=profilesinput]";
private searchFldPolicies = "[id^=realm-settings][id$=clientPoliciesinput]";
- private clientProfileOne = 'a[href*="realm-settings/clientPolicies/Test"]';
- private clientProfileTwo = 'a[href*="realm-settings/clientPolicies/Edit"]';
+ private clientProfileOne =
+ 'a[href*="realm-settings/clientPolicies/Test/edit-profile"]';
+ private clientProfileTwo =
+ 'a[href*="realm-settings/clientPolicies/Edit/edit-profile"]';
+ private clientPolicy =
+ 'a[href*="realm-settings/clientPolicies/Test/edit-policy"]';
private reloadBtn = "reloadProfile";
private addExecutor = "addExecutor";
private addExecutorDrpDwn = ".pf-c-select__toggle";
@@ -185,6 +189,13 @@ export default class RealmSettingsPage {
private addExecutorCancelBtn = "addExecutor-cancelBtn";
private addExecutorSaveBtn = "addExecutor-saveBtn";
private listingPage = new ListingPage();
+ private addCondition = "addCondition";
+ private addConditionDrpDwn = ".pf-c-select__toggle";
+ private addConditionDrpDwnOption = "conditionType-select";
+ private addConditionCancelBtn = "addCondition-cancelBtn";
+ private addConditionSaveBtn = "addCondition-saveBtn";
+ private conditionTypeLink = "condition-type-link";
+ private addValue = "addValue";
selectLoginThemeType(themeType: string) {
cy.get(this.selectLoginTheme).click();
@@ -843,6 +854,87 @@ export default class RealmSettingsPage {
cy.get("table").should("be.visible").contains("td", "Test");
}
+ shouldNotHaveConditionsConfigured() {
+ cy.get(this.clientPolicy).click();
+ cy.get('h6[class*="kc-emptyConditions"]').should(
+ "have.text",
+ "No conditions configured"
+ );
+ }
+
+ shouldCancelAddingCondition() {
+ cy.get(this.clientPolicy).click();
+ cy.findByTestId(this.addCondition).click();
+ cy.get(this.addConditionDrpDwn).click();
+ cy.findByTestId(this.addConditionDrpDwnOption)
+ .contains("any-client")
+ .click();
+ cy.findByTestId(this.addConditionCancelBtn).click();
+ cy.get('h6[class*="kc-emptyConditions"]').should(
+ "have.text",
+ "No conditions configured"
+ );
+ }
+
+ shouldAddCondition() {
+ cy.get(this.clientPolicy).click();
+ cy.findByTestId(this.addCondition).click();
+ cy.get(this.addConditionDrpDwn).click();
+ cy.findByTestId(this.addConditionDrpDwnOption)
+ .contains("client-roles")
+ .click();
+ cy.get('input[name="config.roles[0].value"]').click().type("role 1");
+
+ cy.findByTestId(this.addConditionSaveBtn).click();
+ cy.get(this.alertMessage).should(
+ "be.visible",
+ "Success! Condition created successfully"
+ );
+ cy.get('ul[class*="pf-c-data-list"]').should("have.text", "client-roles");
+ }
+
+ shouldEditCondition() {
+ cy.get(this.clientPolicy).click();
+
+ cy.findByTestId(this.conditionTypeLink).contains("client-roles").click();
+
+ cy.findByTestId(this.addValue).click();
+
+ cy.get('input[name="config.roles[1].value"]').click().type("role 2");
+
+ cy.findByTestId(this.addConditionSaveBtn).click();
+ cy.get(this.alertMessage).should(
+ "be.visible",
+ "Success! Condition updated successfully"
+ );
+ }
+
+ shouldCancelDeletingCondition() {
+ cy.get(this.clientPolicy).click();
+ cy.get('svg[class*="kc-conditionType-trash-icon"]').click();
+ cy.get(this.deleteDialogTitle).contains("Delete condition?");
+ cy.get(this.deleteDialogBodyText).contains(
+ "This action will permanently delete client-roles. This cannot be undone."
+ );
+ cy.findByTestId("modalConfirm").contains("Delete");
+ cy.get(this.deleteDialogCancelBtn).contains("Cancel").click();
+ cy.get('ul[class*="pf-c-data-list"]').should("have.text", "client-roles");
+ }
+
+ shouldDeleteCondition() {
+ cy.get(this.clientPolicy).click();
+ cy.get('svg[class*="kc-conditionType-trash-icon"]').click();
+ cy.get(this.deleteDialogTitle).contains("Delete condition?");
+ cy.get(this.deleteDialogBodyText).contains(
+ "This action will permanently delete client-roles. This cannot be undone."
+ );
+ cy.findByTestId("modalConfirm").contains("Delete").click();
+ cy.get('h6[class*="kc-emptyConditions"]').should(
+ "have.text",
+ "No conditions configured"
+ );
+ }
+
shouldDeleteClientPolicyDialog() {
this.listingPage.searchItem("Test", false);
this.listingPage.clickRowDetails("Test").clickDetailMenu("Delete");
diff --git a/src/components/dynamic/DynamicComponents.tsx b/src/components/dynamic/DynamicComponents.tsx
index 5dd8825ca5..516b8a979f 100644
--- a/src/components/dynamic/DynamicComponents.tsx
+++ b/src/components/dynamic/DynamicComponents.tsx
@@ -5,15 +5,28 @@ import { COMPONENTS, isValidComponentType } from "./components";
type DynamicComponentProps = {
properties: ConfigPropertyRepresentation[];
+ selectedValues?: string[];
+ parentCallback?: (data: string[]) => void;
};
-export const DynamicComponents = ({ properties }: DynamicComponentProps) => (
+export const DynamicComponents = ({
+ properties,
+ selectedValues,
+ parentCallback,
+}: DynamicComponentProps) => (
<>
{properties.map((property) => {
const componentType = property.type!;
if (isValidComponentType(componentType)) {
const Component = COMPONENTS[componentType];
- return ;
+ return (
+
+ );
} else {
console.warn(`There is no editor registered for ${componentType}`);
}
diff --git a/src/components/dynamic/MultivaluedListComponent.tsx b/src/components/dynamic/MultivaluedListComponent.tsx
index ae5e0f0c5a..f98b3c99a7 100644
--- a/src/components/dynamic/MultivaluedListComponent.tsx
+++ b/src/components/dynamic/MultivaluedListComponent.tsx
@@ -1,4 +1,4 @@
-import React, { useState } from "react";
+import React, { useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import { Controller, useFormContext } from "react-hook-form";
import {
@@ -18,10 +18,20 @@ export const MultiValuedListComponent = ({
helpText,
defaultValue,
options,
+ selectedValues,
+ parentCallback,
}: ComponentProps) => {
const { t } = useTranslation("dynamic");
const { control } = useFormContext();
const [open, setOpen] = useState(false);
+ const [selectedItems, setSelectedItems] = useState([defaultValue]);
+
+ useEffect(() => {
+ if (selectedValues) {
+ parentCallback!(selectedValues);
+ setSelectedItems(selectedValues!);
+ }
+ }, []);
return (
item !== option));
+ const updatedItems = selectedItems.filter(
+ (item: string) => item !== option
+ );
+ setSelectedItems(updatedItems);
+ onChange(updatedItems);
+ parentCallback!(updatedItems);
} else {
onChange([...value, option]);
+ parentCallback!([...value, option]);
+ setSelectedItems([...selectedItems, option]);
}
}}
onClear={(event) => {
diff --git a/src/components/dynamic/components.ts b/src/components/dynamic/components.ts
index f09855ee21..17b7bcb0da 100644
--- a/src/components/dynamic/components.ts
+++ b/src/components/dynamic/components.ts
@@ -10,7 +10,10 @@ import { ClientSelectComponent } from "./ClientSelectComponent";
import { MultiValuedStringComponent } from "./MultivaluedStringComponent";
import { MultiValuedListComponent } from "./MultivaluedListComponent";
-export type ComponentProps = Omit;
+export type ComponentProps = Omit & {
+ selectedValues?: string[];
+ parentCallback?: (data: any) => void;
+};
const ComponentTypes = [
"String",
"boolean",
diff --git a/src/components/multi-line-input/MultiLineInput.tsx b/src/components/multi-line-input/MultiLineInput.tsx
index 22898b2257..d80ef89f02 100644
--- a/src/components/multi-line-input/MultiLineInput.tsx
+++ b/src/components/multi-line-input/MultiLineInput.tsx
@@ -15,13 +15,13 @@ export type MultiLine = {
};
export function convertToMultiline(fields: string[]): MultiLine[] {
- return (fields && fields.length > 0 ? fields : [""]).map((field) => {
+ return (fields.length > 0 ? fields : [""]).map((field) => {
return { value: field };
});
}
export function toValue(formValue: MultiLine[]): string[] {
- return formValue?.map((field) => field.value);
+ return formValue.map((field) => field.value);
}
export type MultiLineInputProps = Omit & {
@@ -76,6 +76,8 @@ export const MultiLineInput = ({
onClick={() => append({})}
tabIndex={-1}
aria-label={t("common:add")}
+ data-testid="addValue"
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
isDisabled={rest.isDisabled || !currentValues?.[index]?.value}
>
{t(addButtonLabel || "common:add")}
diff --git a/src/realm-settings/NewClientPolicyCondition.tsx b/src/realm-settings/NewClientPolicyCondition.tsx
index 5a6f255bae..6558dc99c6 100644
--- a/src/realm-settings/NewClientPolicyCondition.tsx
+++ b/src/realm-settings/NewClientPolicyCondition.tsx
@@ -23,10 +23,15 @@ import { useAlerts } from "../components/alert/Alerts";
import { useHistory, useParams } from "react-router";
import type ComponentTypeRepresentation from "@keycloak/keycloak-admin-client/lib/defs/componentTypeRepresentation";
import { useRealm } from "../context/realm-context/RealmContext";
-import type { EditClientPolicyParams } from "./routes/EditClientPolicy";
import type { ConfigPropertyRepresentation } from "@keycloak/keycloak-admin-client/lib/defs/authenticatorConfigInfoRepresentation";
import type ClientPolicyConditionRepresentation from "@keycloak/keycloak-admin-client/lib/defs/clientPolicyConditionRepresentation";
import { DynamicComponents } from "../components/dynamic/DynamicComponents";
+import {
+ EditClientPolicyParams,
+ toEditClientPolicy,
+} from "./routes/EditClientPolicy";
+import type { EditClientPolicyConditionParams } from "./routes/EditCondition";
+import { convertToMultiline } from "../components/multi-line-input/MultiLineInput";
export type ItemType = { value: string };
@@ -45,12 +50,17 @@ export default function NewClientPolicyCondition() {
const [condition, setCondition] = useState<
ClientPolicyConditionRepresentation[]
>([]);
+ const [conditionData, setConditionData] =
+ useState();
const [conditionType, setConditionType] = useState("");
const [conditionProperties, setConditionProperties] = useState<
ConfigPropertyRepresentation[]
>([]);
+ const [selectedVals, setSelectedVals] = useState();
+
const { policyName } = useParams();
+ const { conditionName } = useParams();
const serverInfo = useServerInfo();
const form = useForm();
@@ -62,48 +72,139 @@ export default function NewClientPolicyCondition() {
const adminClient = useAdminClient();
+ const setupForm = (condition: ClientPolicyConditionRepresentation) => {
+ form.reset();
+
+ Object.entries(condition).map(([key, value]) => {
+ if (key === "configuration") {
+ if (
+ conditionName === "client-roles" ||
+ conditionName === "client-updater-source-roles"
+ ) {
+ form.setValue("config.roles", convertToMultiline(value["roles"]));
+ } else if (conditionName === "client-scopes") {
+ form.setValue("config.scopes", convertToMultiline(value["scopes"]));
+ form.setValue("config.type", value["type"]);
+ } else if (conditionName === "client-updater-source-groups") {
+ form.setValue("config.groups", convertToMultiline(value["groups"]));
+ } else if (conditionName === "client-updater-source-host") {
+ form.setValue(
+ "config.trusted-hosts",
+ convertToMultiline(value["trusted-hosts"])
+ );
+ } else if (conditionName === "client-updater-context") {
+ form.setValue(
+ "config.update-client-source",
+ value["update-client-source"][0]["update-client-source"]
+ );
+ } else if (conditionName === "client-access-type") {
+ form.setValue("config.type", value.type[0]);
+ }
+ }
+ form.setValue(key, value);
+ });
+ };
+
useFetch(
() => adminClient.clientPolicies.listPolicies(),
(policies) => {
setPolicies(policies.policies ?? []);
+ if (conditionName) {
+ const currentPolicy = policies.policies?.find(
+ (item) => item.name === policyName
+ );
+
+ const typeAndConfigData = currentPolicy?.conditions?.find(
+ (item) => item.condition === conditionName
+ );
+
+ const currentCondition = conditionTypes?.find(
+ (condition) => condition.id === conditionName
+ );
+
+ setConditionData(typeAndConfigData!);
+ setSelectedVals(Object.values(typeAndConfigData?.configuration!)[0][0]);
+ setConditionProperties(currentCondition?.properties!);
+ setupForm(typeAndConfigData!);
+ }
},
[]
);
const save = async () => {
+ const formValues = form.getValues();
+ const configValues = formValues.config;
+
+ const writeConfig = () => {
+ if (
+ condition[0]?.condition === "any-client" ||
+ conditionName === "any-client"
+ ) {
+ return {};
+ } else if (
+ condition[0]?.condition === "client-access-type" ||
+ conditionName === "client-access-type"
+ ) {
+ return { type: [formValues.config.type] };
+ } else if (
+ condition[0]?.condition === "client-updater-context" ||
+ conditionName === "client-updater-context"
+ ) {
+ return {
+ "update-client-source": [Object.values(formValues)[0]],
+ };
+ } else if (
+ condition[0]?.condition === "client-scopes" ||
+ conditionName === "client-scopes"
+ ) {
+ return {
+ type: Object.values(formValues)[0].type,
+ scopes: (Object.values(formValues)[0].scopes as ItemType[]).map(
+ (item) => (item as ItemType).value
+ ),
+ };
+ } else
+ return {
+ [Object.keys(configValues)[0]]: Object.values(
+ configValues?.[Object.keys(configValues)[0]]
+ ).map((item) => (item as ItemType).value),
+ };
+ };
+
const updatedPolicies = policies.map((policy) => {
if (policy.name !== policyName) {
return policy;
}
- const formValues = form.getValues();
- const configValues = formValues.config;
+ let conditions = policy.conditions ?? [];
- const writeConfig = () => {
- if (condition[0]?.condition === "any-client") {
- return {};
- } else if (condition[0]?.condition === "client-access-type") {
- return { type: [formValues["client-accesstype"].label] };
- } else if (condition[0]?.condition === "client-updater-context") {
- return {
- "update-client-source": [Object.values(formValues)[0]],
- };
- } else if (condition[0]?.condition === "client-scopes") {
- return {
- type: Object.values(formValues)[0].type,
- scopes: (Object.values(formValues)[0].scopes as ItemType[]).map(
- (item) => (item as ItemType).value
- ),
- };
- } else
- return {
- [Object.keys(configValues)[0]]: Object.values(
- configValues?.[Object.keys(configValues)[0]]
- ).map((item) => (item as ItemType).value),
- };
- };
+ if (conditionName) {
+ const createdCondition = {
+ condition: conditionData?.condition,
+ configuration: writeConfig(),
+ };
- const conditions = (policy.conditions ?? []).concat({
+ const index = conditions.findIndex(
+ (condition) => conditionName === condition.condition
+ );
+
+ if (index === -1) {
+ return;
+ }
+
+ const newConditions = [
+ ...conditions.slice(0, index),
+ createdCondition,
+ ...conditions.slice(index + 1),
+ ];
+
+ return {
+ ...policy,
+ conditions: newConditions,
+ };
+ }
+
+ conditions = conditions.concat({
condition: condition[0].condition,
configuration: writeConfig(),
});
@@ -112,7 +213,7 @@ export default function NewClientPolicyCondition() {
...policy,
conditions,
};
- });
+ }) as ClientPolicyRepresentation[];
try {
await adminClient.clientPolicies.updatePolicy({
@@ -123,7 +224,9 @@ export default function NewClientPolicyCondition() {
`/${realm}/realm-settings/clientPolicies/${policyName}/edit-policy`
);
addAlert(
- t("realm-settings:createClientConditionSuccess"),
+ conditionName
+ ? t("realm-settings:updateClientConditionSuccess")
+ : t("realm-settings:createClientConditionSuccess"),
AlertVariant.success
);
} catch (error) {
@@ -131,9 +234,16 @@ export default function NewClientPolicyCondition() {
}
};
+ const handleCallback = (childData: any) => {
+ setSelectedVals(childData);
+ };
+
return (
-
+
(
-
+