Added new / missing fields (#2035)

This commit is contained in:
Erik Jan de Wit 2022-02-21 16:43:23 +01:00 committed by GitHub
parent d5909709b7
commit 35ef6aff88
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 290 additions and 19 deletions

View file

@ -23,7 +23,11 @@ import {
useConfirmDialog,
} from "../components/confirm-dialog/ConfirmDialog";
import { DownloadDialog } from "../components/download-dialog/DownloadDialog";
import type { MultiLine } from "../components/multi-line-input/multi-line-convert";
import {
MultiLine,
stringToMultiline,
toStringValue,
} from "../components/multi-line-input/multi-line-convert";
import {
ViewHeader,
ViewHeaderBadge,
@ -172,6 +176,7 @@ export type ClientForm = Omit<
> & {
redirectUris: MultiLine[];
webOrigins: MultiLine[];
requestUris?: MultiLine[];
};
export type SaveOptions = {
@ -242,6 +247,10 @@ export default function ClientDetails() {
const setupForm = (client: ClientRepresentation) => {
convertToFormValues(client, form.setValue, ["redirectUris", "webOrigins"]);
form.setValue(
"requestUris",
stringToMultiline(client.attributes?.["request.uris"])
);
};
useFetch(
@ -271,10 +280,18 @@ export default function ClientDetails() {
toggleChangeAuthenticatorOpen();
return;
}
const submittedClient = convertFormValuesToObject(form.getValues(), [
"redirectUris",
"webOrigins",
]);
const values = form.getValues();
if (values.requestUris) {
values.attributes!["request.uris"] = toStringValue(values.requestUris);
delete values.requestUris;
}
const submittedClient = convertFormValuesToObject<
ClientForm,
ClientRepresentation
>(values, ["redirectUris", "webOrigins"]);
try {
const newClient: ClientRepresentation = {

View file

@ -14,6 +14,7 @@ import { FormAccess } from "../../components/form-access/FormAccess";
import { HelpItem } from "../../components/help-enabler/HelpItem";
import { useServerInfo } from "../../context/server-info/ServerInfoProvider";
import { sortProviders } from "../../util";
import { MultiLineInput } from "../../components/multi-line-input/MultiLineInput";
type FineGrainOpenIdConnectProps = {
control: Control<Record<string, any>>;
@ -43,6 +44,17 @@ export const FineGrainOpenIdConnect = ({
useState(false);
const [requestObjectRequiredOpen, setRequestObjectRequiredOpen] =
useState(false);
const [requestObjectEncryptionOpen, setRequestObjectEncryptionOpen] =
useState(false);
const [requestObjectEncodingOpen, setRequestObjectEncodingOpen] =
useState(false);
const [authorizationSignedOpen, setAuthorizationSignedOpen] = useState(false);
const [authorizationEncryptedOpen, setAuthorizationEncryptedOpen] =
useState(false);
const [
authorizationEncryptedResponseOpen,
setAuthorizationEncryptedResponseOpen,
] = useState(false);
const keyOptions = [
<SelectOption key="empty" value="">
@ -89,6 +101,33 @@ export const FineGrainOpenIdConnect = ({
)),
];
const requestObjectEncryptionOptions = [
<SelectOption key="any" value="any">
{t("common:any")}
</SelectOption>,
...sortProviders(cekManagementProviders!).map((p) => (
<SelectOption key={p} value={p} />
)),
];
const requestObjectEncodingOptions = [
<SelectOption key="any" value="any">
{t("common:any")}
</SelectOption>,
...sortProviders(contentEncryptionProviders!).map((p) => (
<SelectOption key={p} value={p} />
)),
];
const authorizationSignedResponseOptions = [
<SelectOption key="empty" value="">
{t("common:choose")}
</SelectOption>,
...sortProviders(signatureProviders!).map((p) => (
<SelectOption key={p} value={p} />
)),
];
const requestObjectRequiredOptions = [
"not required",
"request or request_uri",
@ -288,6 +327,68 @@ export const FineGrainOpenIdConnect = ({
)}
/>
</FormGroup>
<FormGroup
label={t("requestObjectEncryption")}
fieldId="requestObjectEncryption"
labelIcon={
<HelpItem
helpText="clients-help:requestObjectEncryption"
fieldLabelId="clients:requestObjectEncryption"
/>
}
>
<Controller
name="attributes.request.object.encryption.alg"
defaultValue=""
control={control}
render={({ onChange, value }) => (
<Select
toggleId="requestObjectEncryption"
variant={SelectVariant.single}
onToggle={setRequestObjectEncryptionOpen}
isOpen={requestObjectEncryptionOpen}
onSelect={(_, value) => {
onChange(value);
setRequestObjectEncryptionOpen(false);
}}
selections={value}
>
{requestObjectEncryptionOptions}
</Select>
)}
/>
</FormGroup>
<FormGroup
label={t("requestObjectEncoding")}
fieldId="requestObjectEncoding"
labelIcon={
<HelpItem
helpText="clients-help:requestObjectEncoding"
fieldLabelId="clients:requestObjectEncoding"
/>
}
>
<Controller
name="attributes.request.object.encryption.enc"
defaultValue=""
control={control}
render={({ onChange, value }) => (
<Select
toggleId="requestObjectEncoding"
variant={SelectVariant.single}
onToggle={setRequestObjectEncodingOpen}
isOpen={requestObjectEncodingOpen}
onSelect={(_, value) => {
onChange(value);
setRequestObjectEncodingOpen(false);
}}
selections={value}
>
{requestObjectEncodingOptions}
</Select>
)}
/>
</FormGroup>
<FormGroup
label={t("requestObjectRequired")}
fieldId="requestObjectRequired"
@ -319,6 +420,115 @@ export const FineGrainOpenIdConnect = ({
)}
/>
</FormGroup>
<FormGroup
label={t("validRequestURIs")}
fieldId="validRequestURIs"
labelIcon={
<HelpItem
helpText="clients-help:validRequestURIs"
fieldLabelId="clients:validRequestURIs"
/>
}
>
<MultiLineInput
name="requestUris"
aria-label={t("validRequestURIs")}
addButtonLabel="clients:addRequestUri"
/>
</FormGroup>
<FormGroup
label={t("authorizationSignedResponseAlg")}
fieldId="authorizationSignedResponseAlg"
labelIcon={
<HelpItem
helpText="clients-help:authorizationSignedResponseAlg"
fieldLabelId="clients:authorizationSignedResponseAlg"
/>
}
>
<Controller
name="attributes.authorization.signed.response.alg"
defaultValue=""
control={control}
render={({ onChange, value }) => (
<Select
toggleId="authorizationSignedResponseAlg"
variant={SelectVariant.single}
onToggle={setAuthorizationSignedOpen}
isOpen={authorizationSignedOpen}
onSelect={(_, value) => {
onChange(value);
setAuthorizationSignedOpen(false);
}}
selections={value}
>
{authorizationSignedResponseOptions}
</Select>
)}
/>
</FormGroup>
<FormGroup
label={t("authorizationEncryptedResponseAlg")}
fieldId="authorizationEncryptedResponseAlg"
labelIcon={
<HelpItem
helpText="clients-help:authorizationEncryptedResponseAlg"
fieldLabelId="clients:authorizationEncryptedResponseAlg"
/>
}
>
<Controller
name="attributes.authorization.encrypted.response.alg"
defaultValue=""
control={control}
render={({ onChange, value }) => (
<Select
toggleId="authorizationEncryptedResponseAlg"
variant={SelectVariant.single}
onToggle={setAuthorizationEncryptedOpen}
isOpen={authorizationEncryptedOpen}
onSelect={(_, value) => {
onChange(value);
setAuthorizationEncryptedOpen(false);
}}
selections={value}
>
{cekManagementOptions}
</Select>
)}
/>
</FormGroup>
<FormGroup
label={t("authorizationEncryptedResponseEnc")}
fieldId="authorizationEncryptedResponseEnc"
labelIcon={
<HelpItem
helpText="clients-help:authorizationEncryptedResponseEnc"
fieldLabelId="clients:authorizationEncryptedResponseEnc"
/>
}
>
<Controller
name="attributes.authorization.encrypted.response.enc"
defaultValue=""
control={control}
render={({ onChange, value }) => (
<Select
toggleId="authorizationEncryptedResponseEnc"
variant={SelectVariant.single}
onToggle={setAuthorizationEncryptedResponseOpen}
isOpen={authorizationEncryptedResponseOpen}
onSelect={(_, value) => {
onChange(value);
setAuthorizationEncryptedResponseOpen(false);
}}
selections={value}
>
{contentOptions}
</Select>
)}
/>
</FormGroup>
<ActionGroup>
<Button variant="secondary" id="fineGrainSave" onClick={save}>
{t("common:save")}

View file

@ -89,7 +89,10 @@ export default function ResourceDetails() {
);
const save = async (submitted: SubmittedResource) => {
const resource = convertFormValuesToObject(submitted, ["uris"]);
const resource = convertFormValuesToObject<
SubmittedResource,
ResourceRepresentation
>(submitted, ["uris"]);
try {
if (resourceId) {

View file

@ -120,6 +120,18 @@ export default {
"JWA algorithm, which client needs to use when sending OIDC request object specified by 'request' or 'request_uri' parameters. If set to 'any', Request object can be signed by any algorithm (including 'none' ).",
requestObjectRequired:
'Specifies if the client needs to provide a request object with their authorization requests, and what method they can use for this. If set to "not required", providing a request object is optional. In all other cases, providing a request object is mandatory. If set to "request", the request object must be provided by value. If set to "request_uri", the request object must be provided by reference. If set to "request or request_uri", either method can be used.',
requestObjectEncryption:
"JWE algorithm, which client needs to use when sending OIDC request object specified by 'request' or 'request_uri' parameters. If set to 'any', encryption is optional and any algorithm is allowed.",
requestObjectEncoding:
"JWE algorithm, which client needs to use when encrypting the content of the OIDC request object specified by 'request' or 'request_uri' parameters. If set to 'any', any algorithm is allowed.",
validRequestURIs:
"List of valid URIs, which can be used as values of 'request_uri' parameter during OpenID Connect authentication request. There is support for the same capabilities like for Valid Redirect URIs. For example wildcards or relative paths.",
authorizationSignedResponseAlg:
"JWA algorithm used for signing authorization response tokens when the response mode is jwt.",
authorizationEncryptedResponseAlg:
"JWA Algorithm used for key management in encrypting the authorization response when the response mode is jwt. This option is needed if you want encrypted authorization response. If left empty, the authorization response is just signed, but not encrypted.",
authorizationEncryptedResponseEnc:
"JWA Algorithm used for content encryption in encrypting the authorization response when the response mode is jwt. This option is needed if you want encrypted authorization response. If left empty, the authorization response is just signed, but not encrypted.",
openIdConnectCompatibilityModes:
"This section is used to configure settings for backward compatibility with older OpenID Connect / OAuth 2 adaptors. It's useful especially if your client uses older version of Keycloak / RH-SSO adapter.",
excludeSessionStateFromAuthenticationResponse:

View file

@ -465,6 +465,16 @@ export default {
"request only": "Request only",
"request_uri only": "Request URI only",
},
requestObjectEncryption: "Request object encryption algorithm",
requestObjectEncoding: "Request object content encryption algorithm",
validRequestURIs: "Valid request URIs",
addRequestUri: "Add valid request URIs",
authorizationSignedResponseAlg:
"Authorization response signature algorithm",
authorizationEncryptedResponseAlg:
"Authorization response encryption key management algorithm",
authorizationEncryptedResponseEnc:
"Authorization response encryption content encryption algorithm",
openIdConnectCompatibilityModes: "Open ID Connect Compatibly Modes",
excludeSessionStateFromAuthenticationResponse:
"Exclude Session State From Authentication Response",

View file

@ -1,4 +1,4 @@
import React, { Fragment, useEffect } from "react";
import React, { Fragment } from "react";
import { useFieldArray, useFormContext, useWatch } from "react-hook-form";
import {
TextInput,
@ -21,7 +21,7 @@ export const MultiLineInput = ({
...rest
}: MultiLineInputProps) => {
const { t } = useTranslation();
const { register, control, reset } = useFormContext();
const { register, control } = useFormContext();
const { fields, append, remove } = useFieldArray({
name,
control,
@ -29,11 +29,6 @@ export const MultiLineInput = ({
const currentValues: { [name: string]: { value: string } } | undefined =
useWatch({ control, name });
useEffect(() => {
reset({
[name]: [{ value: "" }],
});
}, []);
return (
<>
{fields.map(({ id, value }, index) => (

View file

@ -3,9 +3,15 @@ export type MultiLine = {
};
export function convertToMultiline(fields: string[]): MultiLine[] {
return (fields.length > 0 ? fields : [""]).map((field) => {
return { value: field };
});
return (fields.length > 0 ? fields : [""]).map((field) => ({ value: field }));
}
export function stringToMultiline(value?: string): MultiLine[] {
return (value || "").split("##").map((v) => ({ value: v }));
}
export function toStringValue(formValue: MultiLine[]): string {
return formValue.map((field) => field.value).join("##");
}
export function toValue(formValue: MultiLine[]): string[] {

View file

@ -36,7 +36,7 @@ export const GroupAttributes = () => {
const { currentGroup, subGroups, setSubGroups } = useSubGroups();
const convertAttributes = (attr?: Record<string, any>) => {
return attributesToArray(attr || currentGroup().attributes!);
return attributesToArray(attr || currentGroup()?.attributes!);
};
useEffect(() => {

View file

@ -137,4 +137,17 @@ describe("Tests the form convert util functions", () => {
redirectUris: ["http://bla.nl", "http://test.nl"],
});
});
it("convert empty multi-lines", () => {
const values: { [index: string]: any } = {};
const spy = (name: string, value: any) => (values[name] = value);
//when
convertToFormValues({}, spy, ["redirectUris"]);
//then
expect(values).toEqual({
redirectUris: [{ value: "" }],
});
});
});

View file

@ -99,12 +99,17 @@ export const convertToFormValues = (
setValue(key, value);
}
});
multiline?.map((line) => {
if (!Object.keys(obj).includes(line)) {
setValue(line, convertToMultiline([""]));
}
});
};
export function convertFormValuesToObject<T>(
export function convertFormValuesToObject<T, G = T>(
obj: T,
multiline: string[] | undefined = []
): Omit<T, typeof multiline[number] | "attributes" | "config"> {
): G {
const result: any = {};
Object.entries(obj).map(([key, value]) => {
if (isAttributeArray(value)) {