index on authEvaluateTab: f0a2494d Add nexus profile for releases (#1704) (#1961)

auth evaluate wip

wip auth evaluate tab

add identity information and permissions

help text and wip evaluate

wip contextual attributes

wip contextual attributes

add conditional dropdown for auth method

wip resource fields

scopes and context inputs working

fix resources error and update onChange

package-lock

cleanup: remove comments and log stmts

add conditional fields when applyToResourceType is true

package.json from main

PR feedback from Erik

Co-authored-by: Erik Jan de Wit <edewit@redhat.com>

Update src/clients/authorization/AuthorizationEvaluate.tsx

Co-authored-by: Erik Jan de Wit <edewit@redhat.com>

handleSubmit

remove log stmt

fix cypress test

PR feedback from Erik

try fixing policies test

PR feedback from Jon

Co-authored-by: Jon Koops <jonkoops@gmail.com>

PR feedback from Jon

rename id

revert client policy test

reset

add trigger

authEvaluateReset

conditionally render based on type

Apply suggestions from code review

Co-authored-by: Jon Koops <jonkoops@gmail.com>

PR feedback

remove controller

Update src/components/attribute-input/AttributeInput.tsx

Co-authored-by: Jon Koops <jonkoops@gmail.com>

PR feedback

Update src/clients/authorization/AuthorizationEvaluate.tsx

Co-authored-by: Jon Koops <jonkoops@gmail.com>

remove reset
This commit is contained in:
Jenny 2022-02-15 12:52:46 -05:00 committed by GitHub
parent a9e67b5fc4
commit 158bd07398
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 110 additions and 72 deletions

View file

@ -598,7 +598,6 @@ export default function ClientDetails() {
clientRoles={clientRoles} clientRoles={clientRoles}
users={users} users={users}
save={save} save={save}
reset={() => setupForm(client)}
/> />
</Tab> </Tab>
<Tab <Tab

View file

@ -28,6 +28,18 @@ import { defaultContextAttributes } from "../utils";
import type ResourceRepresentation from "@keycloak/keycloak-admin-client/lib/defs/resourceRepresentation"; import type ResourceRepresentation from "@keycloak/keycloak-admin-client/lib/defs/resourceRepresentation";
import { useParams } from "react-router-dom"; import { useParams } from "react-router-dom";
import type ScopeRepresentation from "@keycloak/keycloak-admin-client/lib/defs/scopeRepresentation"; import type ScopeRepresentation from "@keycloak/keycloak-admin-client/lib/defs/scopeRepresentation";
import type { KeyValueType } from "../../components/attribute-form/attribute-convert";
interface EvaluateFormInputs
extends Omit<ResourceEvaluation, "context" | "resources"> {
applyToResource: boolean;
alias: string;
authScopes: string[];
context: {
attributes: Record<string, string>[];
};
resources: Record<string, string>[];
}
export type AttributeType = { export type AttributeType = {
key: string; key: string;
@ -42,20 +54,28 @@ type ClientSettingsProps = {
clients: ClientRepresentation[]; clients: ClientRepresentation[];
clientName?: string; clientName?: string;
save: () => void; save: () => void;
reset: () => void;
users: UserRepresentation[]; users: UserRepresentation[];
clientRoles: RoleRepresentation[]; clientRoles: RoleRepresentation[];
}; };
export type AttributeForm = Omit<
EvaluateFormInputs,
"context" | "resources"
> & {
context: {
attributes?: KeyValueType[];
};
resources?: KeyValueType[];
};
export const AuthorizationEvaluate = ({ export const AuthorizationEvaluate = ({
clients, clients,
clientRoles, clientRoles,
clientName, clientName,
users, users,
reset,
}: ClientSettingsProps) => { }: ClientSettingsProps) => {
const form = useFormContext<ResourceEvaluation>(); const form = useFormContext<EvaluateFormInputs>();
const { control } = form; const { control, reset, trigger } = form;
const { t } = useTranslation("clients"); const { t } = useTranslation("clients");
const adminClient = useAdminClient(); const adminClient = useAdminClient();
const realm = useRealm(); 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 = { const resEval: ResourceEvaluation = {
roleIds: formValues.roleIds ?? [], roleIds: formValues.roleIds ?? [],
clientId: selectedClient ? selectedClient.id! : clientId,
userId: selectedUser?.id!, userId: selectedUser?.id!,
resources: resources.filter((resource) => keys.includes(resource.name!)),
entitlements: false, entitlements: false,
context: formValues.context, context: {
resources: formValues.resources, attributes: Object.fromEntries(
clientId: selectedClient?.id!, formValues.context.attributes
.filter((item) => item.key || item.value !== "")
.map(({ key, value }) => [key, value])
),
},
}; };
return adminClient.clients.evaluateResource( return adminClient.clients.evaluateResource(
{ id: clientId!, realm: realm.realm }, { id: clientId!, realm: realm.realm },
resEval resEval
@ -128,7 +160,10 @@ export const AuthorizationEvaluate = ({
fieldId="client" fieldId="client"
> >
<Controller <Controller
name="client" name="clientId"
rules={{
validate: (value) => value.length > 0,
}}
defaultValue={clientName} defaultValue={clientName}
control={control} control={control}
render={({ onChange, value }) => ( render={({ onChange, value }) => (
@ -140,7 +175,7 @@ export const AuthorizationEvaluate = ({
onChange((value as ClientRepresentation).clientId); onChange((value as ClientRepresentation).clientId);
setClientsDropdownOpen(false); setClientsDropdownOpen(false);
}} }}
selections={value} selections={selectedClient === value ? value : clientName}
variant={SelectVariant.typeahead} variant={SelectVariant.typeahead}
aria-label={t("client")} aria-label={t("client")}
isOpen={clientsDropdownOpen} isOpen={clientsDropdownOpen}
@ -171,6 +206,9 @@ export const AuthorizationEvaluate = ({
> >
<Controller <Controller
name="userId" name="userId"
rules={{
validate: (value) => value.length > 0,
}}
defaultValue="" defaultValue=""
control={control} control={control}
render={({ onChange, value }) => ( render={({ onChange, value }) => (
@ -212,7 +250,7 @@ export const AuthorizationEvaluate = ({
fieldId="realmRole" fieldId="realmRole"
> >
<Controller <Controller
name="rolesIds" name="roleIds"
placeholderText={t("selectARole")} placeholderText={t("selectARole")}
control={control} control={control}
defaultValue={[]} defaultValue={[]}
@ -297,7 +335,10 @@ export const AuthorizationEvaluate = ({
fieldId={name!} fieldId={name!}
> >
<AttributeInput <AttributeInput
selectableValues={resources.map((item) => item.name!)} selectableValues={resources.map<AttributeType>((item) => ({
name: item.name!,
key: item._id!,
}))}
resources={resources} resources={resources}
isKeySelectable isKeySelectable
name="resources" name="resources"
@ -390,35 +431,33 @@ export const AuthorizationEvaluate = ({
fieldId={name!} fieldId={name!}
> >
<AttributeInput <AttributeInput
selectableValues={defaultContextAttributes.map( selectableValues={defaultContextAttributes}
(item) => item.name
)}
isKeySelectable isKeySelectable
name="context" name="context.attributes"
/> />
</FormGroup> </FormGroup>
</ExpandableSection> </ExpandableSection>
<ActionGroup>
<Button data-testid="authorization-eval" type="submit">
{t("evaluate")}
</Button>
<Button
data-testid="authorization-revert"
variant="link"
onClick={reset}
>
{t("common:revert")}
</Button>
<Button
data-testid="authorization-revert"
variant="primary"
onClick={reset}
isDisabled
>
{t("lastEvaluation")}
</Button>
</ActionGroup>
</FormAccess> </FormAccess>
<ActionGroup>
<Button data-testid="authorization-eval" onClick={() => evaluate()}>
{t("evaluate")}
</Button>
<Button
data-testid="authorization-revert"
variant="link"
onClick={() => reset()}
>
{t("common:revert")}
</Button>
<Button
data-testid="authorization-revert"
variant="primary"
onClick={() => reset()}
isDisabled
>
{t("lastEvaluation")}
</Button>
</ActionGroup>
</FormPanel> </FormPanel>
</PageSection> </PageSection>
); );

View file

@ -5,7 +5,10 @@ import { ActionGroup, Button } from "@patternfly/react-core";
import type { RoleRepresentation } from "../../model/role-model"; import type { RoleRepresentation } from "../../model/role-model";
import type { KeyValueType } from "./attribute-convert"; 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"; import { FormAccess } from "../form-access/FormAccess";
export type AttributeForm = Omit<RoleRepresentation, "attributes"> & { export type AttributeForm = Omit<RoleRepresentation, "attributes"> & {
@ -15,7 +18,7 @@ export type AttributeForm = Omit<RoleRepresentation, "attributes"> & {
export type AttributesFormProps = { export type AttributesFormProps = {
form: UseFormMethods<AttributeForm>; form: UseFormMethods<AttributeForm>;
isKeySelectable?: boolean; isKeySelectable?: boolean;
selectableValues?: string[]; selectableValues?: AttributeType[];
save?: (model: AttributeForm) => void; save?: (model: AttributeForm) => void;
reset?: () => void; reset?: () => void;
}; };

View file

@ -24,7 +24,7 @@ import { camelCase } from "lodash-es";
import type ResourceRepresentation from "@keycloak/keycloak-admin-client/lib/defs/resourceRepresentation"; import type ResourceRepresentation from "@keycloak/keycloak-admin-client/lib/defs/resourceRepresentation";
export type AttributeType = { export type AttributeType = {
key: string; key?: string;
name: string; name: string;
custom?: boolean; custom?: boolean;
values?: { values?: {
@ -34,7 +34,7 @@ export type AttributeType = {
type AttributeInputProps = { type AttributeInputProps = {
name: string; name: string;
selectableValues?: string[]; selectableValues?: AttributeType[];
isKeySelectable?: boolean; isKeySelectable?: boolean;
resources?: ResourceRepresentation[]; resources?: ResourceRepresentation[];
}; };
@ -56,7 +56,7 @@ export const AttributeInput = ({
if (!fields.length) { if (!fields.length) {
append({ key: "", value: "" }); append({ key: "", value: "" });
} }
}, []); }, [fields]);
const [isKeyOpenArray, setIsKeyOpenArray] = useState([false]); const [isKeyOpenArray, setIsKeyOpenArray] = useState([false]);
const watchLastKey = watch(`${name}[${fields.length - 1}].key`, ""); const watchLastKey = watch(`${name}[${fields.length - 1}].key`, "");
@ -84,16 +84,32 @@ export const AttributeInput = ({
if (selectableValues) { if (selectableValues) {
attributeValues = defaultContextAttributes.find( attributeValues = defaultContextAttributes.find(
(attr) => attr.name === getValues().context[rowIndex]?.key (attr) => attr.key === getValues().context[rowIndex]?.key
)?.values; )?.values;
} }
const renderSelectOptionType = () => {
if (attributeValues?.length && !resources) {
return attributeValues.map((attr) => (
<SelectOption key={attr.key} value={attr.key}>
{attr.name}
</SelectOption>
));
} else if (scopeValues?.length) {
return scopeValues.map((scope) => (
<SelectOption key={scope.name} value={scope.name}>
{scope.name}
</SelectOption>
));
}
};
const getMessageBundleKey = (attributeName: string) => const getMessageBundleKey = (attributeName: string) =>
camelCase(attributeName).replace(/\W/g, ""); camelCase(attributeName).replace(/\W/g, "");
return ( return (
<Td> <Td>
{scopeValues?.length || attributeValues?.length ? ( {resources || attributeValues?.length ? (
<Controller <Controller
name={`${name}[${rowIndex}].value`} name={`${name}[${rowIndex}].value`}
defaultValue={[]} defaultValue={[]}
@ -111,31 +127,17 @@ export const AttributeInput = ({
toggleId={`group-${name}`} toggleId={`group-${name}`}
onToggle={(open) => toggleValueSelect(rowIndex, open)} onToggle={(open) => toggleValueSelect(rowIndex, open)}
isOpen={isValueOpenArray[rowIndex]} isOpen={isValueOpenArray[rowIndex]}
variant={ variant={SelectVariant.typeahead}
resources
? SelectVariant.typeaheadMulti
: SelectVariant.typeahead
}
typeAheadAriaLabel={t("clients:selectOrTypeAKey")} typeAheadAriaLabel={t("clients:selectOrTypeAKey")}
placeholderText={t("clients:selectOrTypeAKey")} placeholderText={t("clients:selectOrTypeAKey")}
selections={value} selections={value}
onSelect={(_, v) => { onSelect={(_, v) => {
if (resources) { onChange(v);
const option = v.toString();
if (value.includes(option)) {
onChange(value.filter((item: string) => item !== option));
} else {
onChange([...value, option]);
}
} else {
onChange(v);
}
toggleValueSelect(rowIndex, false); toggleValueSelect(rowIndex, false);
}} }}
> >
{(scopeValues || attributeValues)?.map((scope) => ( {renderSelectOptionType()}
<SelectOption key={scope.name} value={scope.name} />
))}
</Select> </Select>
)} )}
/> />
@ -192,18 +194,18 @@ export const AttributeInput = ({
placeholderText={t("clients:selectOrTypeAKey")} placeholderText={t("clients:selectOrTypeAKey")}
selections={value} selections={value}
onSelect={(_, v) => { onSelect={(_, v) => {
onChange(v); onChange(v.toString());
toggleKeySelect(rowIndex, false); toggleKeySelect(rowIndex, false);
}} }}
> >
{selectableValues?.map((attribute) => ( {selectableValues?.map((attribute) => (
<SelectOption <SelectOption
selected={attribute === value} selected={attribute.name === value}
key={attribute} key={attribute.key}
value={attribute} value={resources ? attribute.name : attribute.key}
> >
{attribute} {attribute.name}
</SelectOption> </SelectOption>
))} ))}
</Select> </Select>

View file

@ -39,7 +39,6 @@ import {
ClientRoleRoute, ClientRoleRoute,
toClientRole, toClientRole,
} from "./routes/ClientRole"; } from "./routes/ClientRole";
import { defaultContextAttributes } from "../clients/utils";
export default function RealmRoleTabs() { export default function RealmRoleTabs() {
const { t } = useTranslation("roles"); const { t } = useTranslation("roles");
@ -378,10 +377,6 @@ export default function RealmRoleTabs() {
title={<TabTitleText>{t("common:attributes")}</TabTitleText>} title={<TabTitleText>{t("common:attributes")}</TabTitleText>}
> >
<AttributesForm <AttributesForm
isKeySelectable
selectableValues={defaultContextAttributes.map(
(item) => item.key
)}
form={form} form={form}
save={save} save={save}
reset={() => reset(role)} reset={() => reset(role)}