Added new / missing fields (#2035)
This commit is contained in:
parent
d5909709b7
commit
35ef6aff88
10 changed files with 290 additions and 19 deletions
|
@ -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 = {
|
||||
|
|
|
@ -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")}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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) => (
|
||||
|
|
|
@ -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[] {
|
||||
|
|
|
@ -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(() => {
|
||||
|
|
|
@ -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: "" }],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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)) {
|
||||
|
|
Loading…
Reference in a new issue