removed components that don't adhere to server configuration (#2091)

This commit is contained in:
Erik Jan de Wit 2022-02-21 17:30:57 +01:00 committed by GitHub
parent c84c983415
commit 10b53617a0
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 87 additions and 364 deletions

View file

@ -158,7 +158,6 @@ describe("Realm settings client policies tab tests", () => {
// TODO: UNCOMMENT WHEN THE ISSUE 2050 IS FIXED // TODO: UNCOMMENT WHEN THE ISSUE 2050 IS FIXED
//realmSettingsPage.checkAlertMessage("Could not create client policy: 'proposed client policy name duplicated.'"); //realmSettingsPage.checkAlertMessage("Could not create client policy: 'proposed client policy name duplicated.'");
sidebarPage.waitForPageLoad();
sidebarPage.goToRealmSettings(); sidebarPage.goToRealmSettings();
realmSettingsPage realmSettingsPage

View file

@ -218,8 +218,8 @@ export default class RealmSettingsPage {
private eventListenersDrwDwnSelect = private eventListenersDrwDwnSelect =
".pf-c-button.pf-c-select__toggle-button.pf-m-plain"; ".pf-c-button.pf-c-select__toggle-button.pf-m-plain";
private eventListenerRemove = '[data-ouia-component-id="Remove"]'; private eventListenerRemove = '[data-ouia-component-id="Remove"]';
private roleSelect = ".pf-c-select.kc-role-select"; private roleSelect = "#config\\.roles0";
private selectScopeButton = "select-scope-button"; private selectScopeButton = "addValue";
private deleteClientRolesConditionBtn = "delete-client-roles-condition"; private deleteClientRolesConditionBtn = "delete-client-roles-condition";
private deleteClientScopesConditionBtn = "delete-client-scopes-condition"; private deleteClientScopesConditionBtn = "delete-client-scopes-condition";
@ -947,13 +947,7 @@ export default class RealmSettingsPage {
cy.findByTestId(this.addConditionDrpDwnOption) cy.findByTestId(this.addConditionDrpDwnOption)
.contains("client-roles") .contains("client-roles")
.click(); .click();
cy.get(this.roleSelect).click().contains("impersonation").click(); cy.get(this.roleSelect).clear().type("manage-realm");
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.findByTestId(this.addConditionSaveBtn).click();
cy.get(this.alertMessage).should( cy.get(this.alertMessage).should(
@ -964,18 +958,14 @@ export default class RealmSettingsPage {
} }
addClientScopes() { addClientScopes() {
cy.get("#config\\.scopes0").clear().type("one");
cy.findByTestId(this.selectScopeButton).click(); cy.findByTestId(this.selectScopeButton).click();
cy.get(".pf-c-table__check input[name=checkrow0]").click(); cy.get("#config\\.scopes1").clear().type("two");
cy.get(".pf-c-table__check input[name=checkrow1]").click(); cy.findByTestId(this.selectScopeButton).click();
cy.get(".pf-c-table__check input[name=checkrow2]").click(); cy.get("#config\\.scopes2").clear().type("three");
cy.findByTestId(this.modalConfirm).contains("Add").click();
} }
shouldAddClientScopesCondition() { shouldAddClientScopesCondition() {
cy.intercept(`/auth/admin/realms/${this.realmName}/client-scopes`).as(
"clientScopes"
);
cy.get(this.clientPolicy).click(); cy.get(this.clientPolicy).click();
cy.findByTestId(this.addCondition).click(); cy.findByTestId(this.addCondition).click();
cy.get(this.addConditionDrpDwn).click(); cy.get(this.addConditionDrpDwn).click();
@ -983,7 +973,6 @@ export default class RealmSettingsPage {
.contains("client-scopes") .contains("client-scopes")
.click(); .click();
cy.wait("@clientScopes");
this.addClientScopes(); this.addClientScopes();
cy.findByTestId(this.addConditionSaveBtn).click(); cy.findByTestId(this.addConditionSaveBtn).click();
@ -999,10 +988,8 @@ export default class RealmSettingsPage {
cy.findByTestId(this.clientRolesConditionLink).click(); cy.findByTestId(this.clientRolesConditionLink).click();
cy.get(this.roleSelect).click(); cy.get(this.roleSelect).should("have.value", "manage-realm");
cy.get(this.roleSelect).contains("create-client").click(); cy.get(this.roleSelect).clear().type("admin");
cy.get(this.roleSelect).click();
cy.findByTestId(this.addConditionSaveBtn).click(); cy.findByTestId(this.addConditionSaveBtn).click();
cy.get(this.alertMessage).should( cy.get(this.alertMessage).should(
@ -1012,15 +999,11 @@ export default class RealmSettingsPage {
} }
shouldEditClientScopesCondition() { shouldEditClientScopesCondition() {
cy.intercept(`/auth/admin/realms/${this.realmName}/client-scopes`).as(
"clientScopes"
);
cy.get(this.clientPolicy).click(); cy.get(this.clientPolicy).click();
cy.findByTestId(this.clientScopesConditionLink).click(); cy.findByTestId(this.clientScopesConditionLink).click();
cy.wait("@clientScopes"); cy.get("#config\\.scopes0").clear().type("edit");
this.addClientScopes();
cy.findByTestId(this.addConditionSaveBtn).click(); cy.findByTestId(this.addConditionSaveBtn).click();
cy.get(this.alertMessage).should( cy.get(this.alertMessage).should(

View file

@ -16,7 +16,7 @@ export const DynamicComponents = ({
<> <>
{properties.map((property) => { {properties.map((property) => {
const componentType = property.type!; const componentType = property.type!;
if (isValidComponentType(componentType) && property.name !== "scopes") { if (isValidComponentType(componentType)) {
const Component = COMPONENTS[componentType]; const Component = COMPONENTS[componentType];
return <Component key={property.name} {...property} {...rest} />; return <Component key={property.name} {...property} {...rest} />;
} else { } else {

View file

@ -1,144 +0,0 @@
import React, { useState } from "react";
import { useTranslation } from "react-i18next";
import { Controller, useFormContext } from "react-hook-form";
import {
Button,
Chip,
ChipGroup,
FormGroup,
TextInput,
} from "@patternfly/react-core";
import { HelpItem } from "../help-enabler/HelpItem";
import type { ComponentProps } from "./components";
import { AddScopeDialog } from "../../clients/scopes/AddScopeDialog";
import { useAdminClient, useFetch } from "../../context/auth/AdminClient";
import type ClientScopeRepresentation from "@keycloak/keycloak-admin-client/lib/defs/clientScopeRepresentation";
import { useParams } from "react-router";
import type { EditClientPolicyConditionParams } from "../../realm-settings/routes/EditCondition";
import { GroupPickerDialog } from "../group/GroupPickerDialog";
export const MultivaluedChipsComponent = ({
defaultValue,
name,
label,
helpText,
isDisabled = false,
}: ComponentProps) => {
const { t } = useTranslation("dynamic");
const { control } = useFormContext();
const { conditionName } = useParams<EditClientPolicyConditionParams>();
const adminClient = useAdminClient();
const [open, setOpen] = useState(false);
const [clientScopes, setClientScopes] = useState<ClientScopeRepresentation[]>(
[]
);
useFetch(
() => adminClient.clientScopes.find(),
(clientScopes) => {
setClientScopes(clientScopes);
},
[]
);
const toggleModal = () => {
setOpen(!open);
};
return (
<FormGroup
label={t(label!)}
labelIcon={
<HelpItem helpText={t(helpText!)} fieldLabelId={`dynamic:${label}`} />
}
fieldId={name!}
>
<Controller
name={`config.${name}`}
control={control}
defaultValue={[defaultValue]}
rules={{ required: true }}
render={({ onChange, value }) => (
<>
{open && name === "scopes" && (
<AddScopeDialog
clientScopes={clientScopes.filter(
(scope) => !value.includes(scope.name!)
)}
isClientScopesConditionType
open={open}
toggleDialog={() => setOpen(!open)}
onAdd={(scopes) => {
onChange([
...value,
...scopes
.map((scope) => scope.scope)
.map((item) => item.name!),
]);
}}
/>
)}
{open && name === "groups" && (
<GroupPickerDialog
type="selectMany"
text={{
title: "users:selectGroups",
ok: "users:join",
}}
onConfirm={(groups) => {
onChange([...value, ...groups.map((group) => group.name)]);
setOpen(false);
}}
onClose={() => {
setOpen(false);
}}
filterGroups={value}
/>
)}
{value.length === 0 && !conditionName && (
<TextInput
type="text"
id="kc-scopes"
value={value}
data-testid="client-scope-input"
name="config.client-scopes"
isDisabled
/>
)}
<ChipGroup
className="kc-client-scopes-chip-group"
isClosable
onClick={() => {
onChange([]);
}}
>
{value.map((currentChip: string) => (
<Chip
key={currentChip}
onClick={() => {
onChange(
value.filter((item: string) => item !== currentChip)
);
}}
>
{currentChip}
</Chip>
))}
</ChipGroup>
<Button
isDisabled={isDisabled}
data-testid="select-scope-button"
variant="secondary"
onClick={() => {
toggleModal();
}}
>
{t("common:select")}
</Button>
</>
)}
/>
</FormGroup>
);
};

View file

@ -1,113 +0,0 @@
import React, { useState } from "react";
import { Controller, useFormContext } from "react-hook-form";
import { useTranslation } from "react-i18next";
import { sortedUniq } from "lodash-es";
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/multi-line-convert";
import { useAdminClient, useFetch } from "../../context/auth/AdminClient";
import { HelpItem } from "../help-enabler/HelpItem";
import { useWhoAmI } from "../../context/whoami/WhoAmI";
export const MultivaluedRoleComponent = ({
name,
label,
helpText,
isDisabled = false,
}: ComponentProps) => {
const { t } = useTranslation("dynamic");
const { whoAmI } = useWhoAmI();
const fieldName = `config.${name}`;
const adminClient = useAdminClient();
const { control } = useFormContext();
const [clientRoles, setClientRoles] = useState<RoleRepresentation[]>([]);
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<RoleRepresentation>((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 (
<FormGroup
label={t(label!)}
labelIcon={
<HelpItem helpText={t(helpText!)} fieldLabelId={`dynamic:${label}`} />
}
fieldId={name!}
>
<Controller
name={fieldName}
defaultValue={[]}
control={control}
rules={{ required: true }}
render={({ onChange, value }) => (
<Select
isDisabled={isDisabled}
onToggle={(isExpanded) => setOpen(isExpanded)}
isOpen={open}
className="kc-role-select"
data-testid="multivalued-role-select"
variant={SelectVariant.typeaheadMulti}
placeholderText={t("selectARole")}
chipGroupProps={{
numChips: 5,
expandedText: t("common:hide"),
collapsedText: t("common:showRemaining"),
}}
typeAheadAriaLabel={t("selectARole")}
selections={value.map((v: MultiLine) => v.value)}
isCreatable
onSelect={(_, v) => {
const option = v.toString();
if (value.map((v: MultiLine) => v.value).includes(option)) {
onChange(
value.filter((item: MultiLine) => item.value !== option)
);
} else {
onChange([...value, { value: option }]);
}
}}
maxHeight={200}
onClear={() => onChange([])}
>
{alphabetizedClientRoles.map((option) => (
<SelectOption key={option} value={option} />
))}
</Select>
)}
/>
</FormGroup>
);
};

View file

@ -1,18 +1,41 @@
import React from "react"; import React, { Fragment, useEffect } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { FormGroup } from "@patternfly/react-core"; import { useFormContext } from "react-hook-form";
import {
Button,
ButtonVariant,
FormGroup,
InputGroup,
TextInput,
} from "@patternfly/react-core";
import { HelpItem } from "../../components/help-enabler/HelpItem"; import { HelpItem } from "../../components/help-enabler/HelpItem";
import type { ComponentProps } from "./components"; import type { ComponentProps } from "./components";
import { MultiLineInput } from "../../components/multi-line-input/MultiLineInput"; import { MinusCircleIcon, PlusCircleIcon } from "@patternfly/react-icons";
export const MultiValuedStringComponent = ({ export const MultiValuedStringComponent = ({
name, name,
label, label,
defaultValue,
helpText, helpText,
isDisabled = false, isDisabled = false,
}: ComponentProps) => { }: ComponentProps) => {
const { t } = useTranslation("dynamic"); const { t } = useTranslation("dynamic");
const fieldName = `config.${name}`;
const { register, setValue, watch } = useFormContext();
const fields = watch(fieldName, [defaultValue]);
const remove = (id: number) => {
fields.splice(id, 1);
setValue(fieldName, [...fields]);
};
const append = () => {
setValue(fieldName, [...fields, ""]);
};
useEffect(() => register(`config.${name}`), [register]);
return ( return (
<FormGroup <FormGroup
@ -22,14 +45,46 @@ export const MultiValuedStringComponent = ({
} }
fieldId={name!} fieldId={name!}
> >
<MultiLineInput {fields.map((value: string, index: number) => (
name={`config.${name}`} <Fragment key={index}>
aria-label={name} <InputGroup>
isDisabled={isDisabled} <TextInput
addButtonLabel={t("addMultivaluedLabel", { id={fieldName + index}
fieldLabel: t(label!).toLowerCase(), onChange={(value) => {
})} fields[index] = value;
/> setValue(fieldName, [...fields]);
}}
name={`${fieldName}[${index}]`}
value={value}
isDisabled={isDisabled}
/>
<Button
variant={ButtonVariant.link}
onClick={() => remove(index)}
tabIndex={-1}
aria-label={t("common:remove")}
isDisabled={index === fields.length - 1}
>
<MinusCircleIcon />
</Button>
</InputGroup>
{index === fields.length - 1 && (
<Button
variant={ButtonVariant.link}
onClick={append}
tabIndex={-1}
aria-label={t("common:add")}
data-testid="addValue"
isDisabled={!value}
>
<PlusCircleIcon />{" "}
{t("addMultivaluedLabel", {
fieldLabel: t(label!).toLowerCase(),
})}
</Button>
)}
</Fragment>
))}
</FormGroup> </FormGroup>
); );
}; };

View file

@ -30,16 +30,8 @@ import {
toEditClientPolicy, toEditClientPolicy,
} from "./routes/EditClientPolicy"; } from "./routes/EditClientPolicy";
import type { EditClientPolicyConditionParams } from "./routes/EditCondition"; import type { EditClientPolicyConditionParams } from "./routes/EditCondition";
import { import { DynamicComponents } from "../components/dynamic/DynamicComponents";
convertToMultiline,
toValue,
} from "../components/multi-line-input/multi-line-convert";
import {
COMPONENTS,
isValidComponentType,
} from "../components/dynamic/components";
import { MultivaluedChipsComponent } from "../components/dynamic/MultivaluedChipsComponent";
import { MultivaluedRoleComponent } from "../components/dynamic/MultivaluedRoleComponent";
export type ItemType = { value: string }; export type ItemType = { value: string };
type ConfigProperty = ConfigPropertyRepresentation & { type ConfigProperty = ConfigPropertyRepresentation & {
@ -69,7 +61,7 @@ export default function NewClientPolicyCondition() {
const { conditionName } = useParams<EditClientPolicyConditionParams>(); const { conditionName } = useParams<EditClientPolicyConditionParams>();
const serverInfo = useServerInfo(); const serverInfo = useServerInfo();
const form = useForm<ClientPolicyConditionRepresentation>({ const form = useForm({
shouldUnregister: false, shouldUnregister: false,
}); });
@ -80,28 +72,8 @@ export default function NewClientPolicyCondition() {
const adminClient = useAdminClient(); const adminClient = useAdminClient();
const setupForm = ( const setupForm = (condition: ClientPolicyConditionRepresentation) => {
condition: ClientPolicyConditionRepresentation, form.reset({ config: condition.configuration || {} });
properties: ConfigPropertyRepresentation[]
) => {
form.reset();
Object.entries(condition.configuration!).map(([key, value]) => {
const formKey = `config.${key}`;
const property = properties.find((p) => p.name === key);
if (
property?.type === "MultivaluedString" &&
property.name !== "scopes" &&
property.name !== "groups"
) {
form.setValue(formKey, convertToMultiline(value));
} else if (property?.name === "client-scopes") {
form.setValue("config.scopes", value);
} else {
form.setValue(formKey, value);
}
});
}; };
useFetch( useFetch(
@ -125,7 +97,7 @@ export default function NewClientPolicyCondition() {
setConditionData(typeAndConfigData!); setConditionData(typeAndConfigData!);
setConditionProperties(currentCondition?.properties!); setConditionProperties(currentCondition?.properties!);
setupForm(typeAndConfigData!, currentCondition?.properties!); setupForm(typeAndConfigData!);
} }
}, },
[] []
@ -136,11 +108,7 @@ export default function NewClientPolicyCondition() {
const writeConfig = () => { const writeConfig = () => {
return conditionProperties.reduce((r: any, p) => { return conditionProperties.reduce((r: any, p) => {
p.type === "MultivaluedString" && r[p.name!] = configValues[p.name!];
p.name !== "scopes" &&
p.name !== "groups"
? (r[p.name!] = toValue(configValues[p.name!]))
: (r[p.name!] = configValues[p.name!]);
return r; return r;
}, {}); }, {});
}; };
@ -284,32 +252,7 @@ export default function NewClientPolicyCondition() {
</FormGroup> </FormGroup>
<FormProvider {...form}> <FormProvider {...form}>
{conditionProperties.map((property) => { <DynamicComponents properties={conditionProperties} />
const componentType = property.type!;
if (property.name === "roles") {
return <MultivaluedRoleComponent {...property} />;
}
if (property.name === "scopes" || property.name === "groups") {
return (
<MultivaluedChipsComponent
defaultValue={
property.name === "scopes" ? "offline_access" : "topgroup"
}
{...property}
/>
);
}
if (isValidComponentType(componentType)) {
const Component = COMPONENTS[componentType];
return <Component key={property.name} {...property} />;
} else {
console.warn(
`There is no editor registered for ${componentType}`
);
}
})}
</FormProvider> </FormProvider>
<ActionGroup> <ActionGroup>
<Button <Button

View file

@ -1,7 +1,7 @@
import type { LocationDescriptorObject } from "history"; import type { LocationDescriptorObject } from "history";
import { lazy } from "react";
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 NewClientPolicyCondition from "../NewClientPolicyCondition";
export type EditClientPolicyConditionParams = { export type EditClientPolicyConditionParams = {
realm: string; realm: string;
@ -11,7 +11,7 @@ export type EditClientPolicyConditionParams = {
export const EditClientPolicyConditionRoute: RouteDef = { export const EditClientPolicyConditionRoute: RouteDef = {
path: "/:realm/realm-settings/clientPolicies/:policyName?/edit-policy/:conditionName/edit-condition", path: "/:realm/realm-settings/clientPolicies/:policyName?/edit-policy/:conditionName/edit-condition",
component: NewClientPolicyCondition, component: lazy(() => import("../NewClientPolicyCondition")),
breadcrumb: (t) => t("realm-settings:editCondition"), breadcrumb: (t) => t("realm-settings:editCondition"),
access: "manage-clients", access: "manage-clients",
}; };