diff --git a/src/clients/ClientDetails.tsx b/src/clients/ClientDetails.tsx index 96766f40db..a39437bf2d 100644 --- a/src/clients/ClientDetails.tsx +++ b/src/clients/ClientDetails.tsx @@ -598,7 +598,6 @@ export default function ClientDetails() { clientRoles={clientRoles} users={users} save={save} - reset={() => setupForm(client)} /> { + applyToResource: boolean; + alias: string; + authScopes: string[]; + context: { + attributes: Record[]; + }; + resources: Record[]; +} export type AttributeType = { key: string; @@ -42,20 +54,28 @@ type ClientSettingsProps = { clients: ClientRepresentation[]; clientName?: string; save: () => void; - reset: () => void; users: UserRepresentation[]; clientRoles: RoleRepresentation[]; }; +export type AttributeForm = Omit< + EvaluateFormInputs, + "context" | "resources" +> & { + context: { + attributes?: KeyValueType[]; + }; + resources?: KeyValueType[]; +}; + export const AuthorizationEvaluate = ({ clients, clientRoles, clientName, users, - reset, }: ClientSettingsProps) => { - const form = useFormContext(); - const { control } = form; + const form = useFormContext(); + const { control, reset, trigger } = form; const { t } = useTranslation("clients"); const adminClient = useAdminClient(); const realm = useRealm(); @@ -90,15 +110,27 @@ export const AuthorizationEvaluate = ({ [] ); - const evaluate = (formValues: ResourceEvaluation) => { + const evaluate = async () => { + if (!(await trigger())) { + return; + } + const formValues = form.getValues(); + const keys = formValues.resources.map(({ key }) => key); const resEval: ResourceEvaluation = { roleIds: formValues.roleIds ?? [], + clientId: selectedClient ? selectedClient.id! : clientId, userId: selectedUser?.id!, + resources: resources.filter((resource) => keys.includes(resource.name!)), entitlements: false, - context: formValues.context, - resources: formValues.resources, - clientId: selectedClient?.id!, + context: { + attributes: Object.fromEntries( + formValues.context.attributes + .filter((item) => item.key || item.value !== "") + .map(({ key, value }) => [key, value]) + ), + }, }; + return adminClient.clients.evaluateResource( { id: clientId!, realm: realm.realm }, resEval @@ -128,7 +160,10 @@ export const AuthorizationEvaluate = ({ fieldId="client" > value.length > 0, + }} defaultValue={clientName} control={control} render={({ onChange, value }) => ( @@ -140,7 +175,7 @@ export const AuthorizationEvaluate = ({ onChange((value as ClientRepresentation).clientId); setClientsDropdownOpen(false); }} - selections={value} + selections={selectedClient === value ? value : clientName} variant={SelectVariant.typeahead} aria-label={t("client")} isOpen={clientsDropdownOpen} @@ -171,6 +206,9 @@ export const AuthorizationEvaluate = ({ > value.length > 0, + }} defaultValue="" control={control} render={({ onChange, value }) => ( @@ -212,7 +250,7 @@ export const AuthorizationEvaluate = ({ fieldId="realmRole" > item.name!)} + selectableValues={resources.map((item) => ({ + name: item.name!, + key: item._id!, + }))} resources={resources} isKeySelectable name="resources" @@ -390,35 +431,33 @@ export const AuthorizationEvaluate = ({ fieldId={name!} > item.name - )} + selectableValues={defaultContextAttributes} isKeySelectable - name="context" + name="context.attributes" /> - - - - - + + + + + ); diff --git a/src/components/attribute-form/AttributeForm.tsx b/src/components/attribute-form/AttributeForm.tsx index 339e53444a..e3da98a9a5 100644 --- a/src/components/attribute-form/AttributeForm.tsx +++ b/src/components/attribute-form/AttributeForm.tsx @@ -5,7 +5,10 @@ import { ActionGroup, Button } from "@patternfly/react-core"; import type { RoleRepresentation } from "../../model/role-model"; import type { KeyValueType } from "./attribute-convert"; -import { AttributeInput } from "../attribute-input/AttributeInput"; +import { + AttributeInput, + AttributeType, +} from "../attribute-input/AttributeInput"; import { FormAccess } from "../form-access/FormAccess"; export type AttributeForm = Omit & { @@ -15,7 +18,7 @@ export type AttributeForm = Omit & { export type AttributesFormProps = { form: UseFormMethods; isKeySelectable?: boolean; - selectableValues?: string[]; + selectableValues?: AttributeType[]; save?: (model: AttributeForm) => void; reset?: () => void; }; diff --git a/src/components/attribute-input/AttributeInput.tsx b/src/components/attribute-input/AttributeInput.tsx index 653991a91c..5d5e848d97 100644 --- a/src/components/attribute-input/AttributeInput.tsx +++ b/src/components/attribute-input/AttributeInput.tsx @@ -24,7 +24,7 @@ import { camelCase } from "lodash-es"; import type ResourceRepresentation from "@keycloak/keycloak-admin-client/lib/defs/resourceRepresentation"; export type AttributeType = { - key: string; + key?: string; name: string; custom?: boolean; values?: { @@ -34,7 +34,7 @@ export type AttributeType = { type AttributeInputProps = { name: string; - selectableValues?: string[]; + selectableValues?: AttributeType[]; isKeySelectable?: boolean; resources?: ResourceRepresentation[]; }; @@ -56,7 +56,7 @@ export const AttributeInput = ({ if (!fields.length) { append({ key: "", value: "" }); } - }, []); + }, [fields]); const [isKeyOpenArray, setIsKeyOpenArray] = useState([false]); const watchLastKey = watch(`${name}[${fields.length - 1}].key`, ""); @@ -84,16 +84,32 @@ export const AttributeInput = ({ if (selectableValues) { attributeValues = defaultContextAttributes.find( - (attr) => attr.name === getValues().context[rowIndex]?.key + (attr) => attr.key === getValues().context[rowIndex]?.key )?.values; } + const renderSelectOptionType = () => { + if (attributeValues?.length && !resources) { + return attributeValues.map((attr) => ( + + {attr.name} + + )); + } else if (scopeValues?.length) { + return scopeValues.map((scope) => ( + + {scope.name} + + )); + } + }; + const getMessageBundleKey = (attributeName: string) => camelCase(attributeName).replace(/\W/g, ""); return ( - {scopeValues?.length || attributeValues?.length ? ( + {resources || attributeValues?.length ? ( toggleValueSelect(rowIndex, open)} isOpen={isValueOpenArray[rowIndex]} - variant={ - resources - ? SelectVariant.typeaheadMulti - : SelectVariant.typeahead - } + variant={SelectVariant.typeahead} typeAheadAriaLabel={t("clients:selectOrTypeAKey")} placeholderText={t("clients:selectOrTypeAKey")} selections={value} onSelect={(_, v) => { - if (resources) { - const option = v.toString(); - if (value.includes(option)) { - onChange(value.filter((item: string) => item !== option)); - } else { - onChange([...value, option]); - } - } else { - onChange(v); - } + onChange(v); + toggleValueSelect(rowIndex, false); }} > - {(scopeValues || attributeValues)?.map((scope) => ( - - ))} + {renderSelectOptionType()} )} /> @@ -192,18 +194,18 @@ export const AttributeInput = ({ placeholderText={t("clients:selectOrTypeAKey")} selections={value} onSelect={(_, v) => { - onChange(v); + onChange(v.toString()); toggleKeySelect(rowIndex, false); }} > {selectableValues?.map((attribute) => ( - {attribute} + {attribute.name} ))} diff --git a/src/realm-roles/RealmRoleTabs.tsx b/src/realm-roles/RealmRoleTabs.tsx index a2b3d49013..5e43757703 100644 --- a/src/realm-roles/RealmRoleTabs.tsx +++ b/src/realm-roles/RealmRoleTabs.tsx @@ -39,7 +39,6 @@ import { ClientRoleRoute, toClientRole, } from "./routes/ClientRole"; -import { defaultContextAttributes } from "../clients/utils"; export default function RealmRoleTabs() { const { t } = useTranslation("roles"); @@ -378,10 +377,6 @@ export default function RealmRoleTabs() { title={{t("common:attributes")}} > item.key - )} form={form} save={save} reset={() => reset(role)}