diff --git a/cypress/integration/realm_settings_test.spec.ts b/cypress/integration/realm_settings_test.spec.ts
index 5b06e330cb..d89fe90e5e 100644
--- a/cypress/integration/realm_settings_test.spec.ts
+++ b/cypress/integration/realm_settings_test.spec.ts
@@ -568,7 +568,7 @@ describe("Realm settings tests", () => {
});
});
- describe.skip("Realm settings client policies tab tests", () => {
+ describe("Realm settings client policies tab tests", () => {
beforeEach(() => {
keycloakBefore();
loginPage.logIn();
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 b64f0faeb3..bd7f3137b1 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,10 @@ 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";
@@ -195,7 +197,6 @@ export default class RealmSettingsPage {
private addConditionCancelBtn = "addCondition-cancelBtn";
private addConditionSaveBtn = "addCondition-saveBtn";
private conditionTypeLink = "condition-type-link";
- private addValue = "addValue";
private eventListenersFormLabel = ".pf-c-form__label-text";
private eventListenersDrpDwn = ".pf-c-select.kc_eventListeners_select";
private eventListenersSaveBtn = "saveEventListenerBtn";
@@ -206,6 +207,7 @@ export default class RealmSettingsPage {
private eventListenersDrwDwnSelect =
".pf-c-button.pf-c-select__toggle-button.pf-m-plain";
private eventListenerRemove = '[data-ouia-component-id="Remove"]';
+ private roleSelect = ".pf-c-select.kc-role-select";
selectLoginThemeType(themeType: string) {
cy.get(this.selectLoginTheme).click();
@@ -971,7 +973,13 @@ export default class RealmSettingsPage {
cy.findByTestId(this.addConditionDrpDwnOption)
.contains("client-roles")
.click();
- cy.get('input[name="config.roles[0].value"]').click().type("role 1");
+ cy.get(this.roleSelect).click().contains("impersonation").click();
+
+ cy.get(this.roleSelect).contains("manage-realm").click();
+
+ cy.get(this.roleSelect).contains("view-users").click();
+
+ cy.get(this.roleSelect).click();
cy.findByTestId(this.addConditionSaveBtn).click();
cy.get(this.alertMessage).should(
@@ -986,9 +994,10 @@ export default class RealmSettingsPage {
cy.findByTestId(this.conditionTypeLink).contains("client-roles").click();
- cy.findByTestId(this.addValue).click();
+ cy.get(this.roleSelect).click();
+ cy.get(this.roleSelect).contains("create-client").click();
- cy.get('input[name="config.roles[1].value"]').click().type("role 2");
+ cy.get(this.roleSelect).click();
cy.findByTestId(this.addConditionSaveBtn).click();
cy.get(this.alertMessage).should(
diff --git a/src/client-scopes/messages.ts b/src/client-scopes/messages.ts
index 61c195c14a..3f18ceadf8 100644
--- a/src/client-scopes/messages.ts
+++ b/src/client-scopes/messages.ts
@@ -30,7 +30,6 @@ export default {
addMapperExplain:
"If you want more fine-grain control, you can create protocol mapper on this client",
realmRoles: "Realm roles",
- selectARole: "Select a role",
clientRoles: "Client roles",
selectASourceOfRoles: "Select a source of roles",
newRoleName: "New role name",
diff --git a/src/components/dynamic/DynamicComponents.tsx b/src/components/dynamic/DynamicComponents.tsx
index 516b8a979f..a6f692cc92 100644
--- a/src/components/dynamic/DynamicComponents.tsx
+++ b/src/components/dynamic/DynamicComponents.tsx
@@ -9,24 +9,13 @@ type DynamicComponentProps = {
parentCallback?: (data: string[]) => void;
};
-export const DynamicComponents = ({
- properties,
- selectedValues,
- parentCallback,
-}: DynamicComponentProps) => (
+export const DynamicComponents = ({ properties }: 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 cde0a76930..809f7c9722 100644
--- a/src/components/dynamic/MultivaluedListComponent.tsx
+++ b/src/components/dynamic/MultivaluedListComponent.tsx
@@ -48,12 +48,13 @@ export const MultiValuedListComponent = ({
typeAheadAriaLabel="Select"
onToggle={(isOpen) => setOpen(isOpen)}
selections={value}
- onSelect={(_, selectedValue) => {
- const option = selectedValue.toString();
- const changedValue = value.includes(option)
- ? value.filter((item: string) => item !== option)
- : [...value, option];
- onChange(changedValue);
+ onSelect={(_, v) => {
+ const option = v.toString();
+ if (value.includes(option)) {
+ onChange(value.filter((item: string) => item !== option));
+ } else {
+ onChange([...value, option]);
+ }
}}
onClear={(event) => {
event.stopPropagation();
diff --git a/src/components/dynamic/MultivaluedRoleComponent.tsx b/src/components/dynamic/MultivaluedRoleComponent.tsx
new file mode 100644
index 0000000000..9d0335652f
--- /dev/null
+++ b/src/components/dynamic/MultivaluedRoleComponent.tsx
@@ -0,0 +1,112 @@
+import React, { useState } from "react";
+import { Controller, useFormContext } from "react-hook-form";
+import { useTranslation } from "react-i18next";
+import { sortedUniq } from "lodash";
+import {
+ FormGroup,
+ Select,
+ SelectOption,
+ SelectVariant,
+} from "@patternfly/react-core";
+
+import type RoleRepresentation from "@keycloak/keycloak-admin-client/lib/defs/roleRepresentation";
+import type { ComponentProps } from "./components";
+import type { MultiLine } from "../multi-line-input/MultiLineInput";
+import { useAdminClient, useFetch } from "../../context/auth/AdminClient";
+import { HelpItem } from "../help-enabler/HelpItem";
+import { convertToHyphens } from "../../util";
+import { useWhoAmI } from "../../context/whoami/WhoAmI";
+
+export const MultivaluedRoleComponent = ({
+ name,
+ label,
+ helpText,
+}: ComponentProps) => {
+ const { t } = useTranslation("dynamic");
+ const { whoAmI } = useWhoAmI();
+ const fieldName = `config.${convertToHyphens(name!)}`;
+
+ const adminClient = useAdminClient();
+ const { control } = useFormContext();
+
+ const [clientRoles, setClientRoles] = useState([]);
+ const [open, setOpen] = useState(false);
+
+ useFetch(
+ async () => {
+ const clients = await adminClient.clients.find();
+ const clientRoles = await Promise.all(
+ clients.map(async (client) => {
+ const roles = await adminClient.clients.listRoles({ id: client.id! });
+
+ return roles.map((role) => ({
+ ...role,
+ }));
+ })
+ );
+
+ return clientRoles.flat();
+ },
+ (clientRoles) => {
+ setClientRoles(clientRoles);
+ },
+ []
+ );
+
+ const alphabetizedClientRoles = sortedUniq(
+ clientRoles.map((item) => item.name)
+ ).sort((a, b) =>
+ a!.localeCompare(b!, whoAmI.getLocale(), { ignorePunctuation: true })
+ );
+
+ return (
+
+ }
+ fieldId={name!}
+ >
+ (
+
+ )}
+ />
+
+ );
+};
diff --git a/src/components/dynamic/components.ts b/src/components/dynamic/components.ts
index 17b7bcb0da..f09855ee21 100644
--- a/src/components/dynamic/components.ts
+++ b/src/components/dynamic/components.ts
@@ -10,10 +10,7 @@ import { ClientSelectComponent } from "./ClientSelectComponent";
import { MultiValuedStringComponent } from "./MultivaluedStringComponent";
import { MultiValuedListComponent } from "./MultivaluedListComponent";
-export type ComponentProps = Omit & {
- selectedValues?: string[];
- parentCallback?: (data: any) => void;
-};
+export type ComponentProps = Omit;
const ComponentTypes = [
"String",
"boolean",
diff --git a/src/components/dynamic/labels.ts b/src/components/dynamic/labels.ts
index fb29ab14a6..7df9034e73 100644
--- a/src/components/dynamic/labels.ts
+++ b/src/components/dynamic/labels.ts
@@ -1,6 +1,7 @@
export default {
dynamic: {
addMultivaluedLabel: "Add {{fieldLabel}}",
+ selectARole: "Select a role",
usermodel: {
prop: {
label: "Property",
diff --git a/src/identity-providers/add/AddMapper.tsx b/src/identity-providers/add/AddMapper.tsx
index 577f9aa84d..cc4784873d 100644
--- a/src/identity-providers/add/AddMapper.tsx
+++ b/src/identity-providers/add/AddMapper.tsx
@@ -44,7 +44,7 @@ import { groupBy } from "lodash";
export type IdPMapperRepresentationWithAttributes =
IdentityProviderMapperRepresentation & AttributeForm;
-type Role = RoleRepresentation & {
+export type Role = RoleRepresentation & {
clientId?: string;
};
diff --git a/src/realm-settings/NewClientPolicyCondition.tsx b/src/realm-settings/NewClientPolicyCondition.tsx
index 6558dc99c6..c10fc836a8 100644
--- a/src/realm-settings/NewClientPolicyCondition.tsx
+++ b/src/realm-settings/NewClientPolicyCondition.tsx
@@ -25,26 +25,33 @@ import type ComponentTypeRepresentation from "@keycloak/keycloak-admin-client/li
import { useRealm } from "../context/realm-context/RealmContext";
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";
+import {
+ convertToMultiline,
+ toValue,
+} from "../components/multi-line-input/MultiLineInput";
+import { MultivaluedRoleComponent } from "../components/dynamic/MultivaluedRoleComponent";
+import {
+ COMPONENTS,
+ isValidComponentType,
+} from "../components/dynamic/components";
export type ItemType = { value: string };
+type ConfigProperty = ConfigPropertyRepresentation & {
+ config: any;
+};
+
export default function NewClientPolicyCondition() {
const { t } = useTranslation("realm-settings");
const { addAlert, addError } = useAlerts();
const history = useHistory();
const { realm } = useRealm();
- const { handleSubmit, control } = useForm({
- mode: "onChange",
- });
-
const [openConditionType, setOpenConditionType] = useState(false);
const [policies, setPolicies] = useState([]);
const [condition, setCondition] = useState<
@@ -57,13 +64,13 @@ export default function NewClientPolicyCondition() {
ConfigPropertyRepresentation[]
>([]);
- const [selectedVals, setSelectedVals] = useState();
-
const { policyName } = useParams();
const { conditionName } = useParams();
const serverInfo = useServerInfo();
- const form = useForm();
+ const form = useForm({
+ shouldUnregister: false,
+ });
const conditionTypes =
serverInfo.componentTypes?.[
@@ -72,36 +79,20 @@ export default function NewClientPolicyCondition() {
const adminClient = useAdminClient();
- const setupForm = (condition: ClientPolicyConditionRepresentation) => {
+ const setupForm = (
+ condition: ClientPolicyConditionRepresentation,
+ properties: ConfigPropertyRepresentation[]
+ ) => {
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]);
- }
+ Object.entries(condition.configuration!).map(([key, value]) => {
+ const formKey = `config.${key}`;
+ const property = properties.find((p) => p.name === key);
+ if (property?.type === "MultivaluedString") {
+ form.setValue(formKey, convertToMultiline(value));
+ } else {
+ form.setValue(formKey, value);
}
- form.setValue(key, value);
});
};
@@ -123,53 +114,23 @@ export default function NewClientPolicyCondition() {
);
setConditionData(typeAndConfigData!);
- setSelectedVals(Object.values(typeAndConfigData?.configuration!)[0][0]);
setConditionProperties(currentCondition?.properties!);
- setupForm(typeAndConfigData!);
+ setupForm(typeAndConfigData!, currentCondition?.properties!);
}
},
[]
);
- const save = async () => {
- const formValues = form.getValues();
- const configValues = formValues.config;
+ const save = async (configPolicy: ConfigProperty) => {
+ const configValues = configPolicy.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 writeConfig = () =>
+ conditionProperties.reduce((r: any, p) => {
+ p.type === "MultivaluedString"
+ ? (r[p.name!] = toValue(configValues[p.name!]))
+ : (r[p.name!] = configValues[p.name!]);
+ return r;
+ }, {});
const updatedPolicies = policies.map((policy) => {
if (policy.name !== policyName) {
@@ -234,10 +195,6 @@ export default function NewClientPolicyCondition() {
}
};
- const handleCallback = (childData: any) => {
- setSelectedVals(childData);
- };
-
return (
(
+
- {
+ const componentType = property.type!;
+ if (
+ property.name === "roles" &&
+ (conditionType === "client-roles" ||
+ conditionName === "client-roles")
+ ) {
+ return ;
+ } else if (isValidComponentType(componentType)) {
+ const Component = COMPONENTS[componentType];
+ return ;
+ } else {
+ console.warn(
+ `There is no editor registered for ${componentType}`
+ );
}
- parentCallback={handleCallback}
- />
+ })}