Added missing fields when dynamic scope is enabled (#19984)

Closes #19865
This commit is contained in:
Erik Jan de Wit 2023-05-02 11:46:57 +02:00 committed by GitHub
parent 81580908c3
commit e8ed1abda7
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 72 additions and 8 deletions

View file

@ -1,5 +1,7 @@
{
"name": "Name of the client scope. Must be unique in the realm. Name should not contain space characters as it is used as value of scope parameter",
"dynamicScope": "If on, this scope will be considered a Dynamic Scope, which will be comprised of a static and a variable portion.",
"dynamicScopeFormat": "This is the regular expression that the system will use to extract the scope name and variable.",
"description": "Description of the client scope",
"protocol": "Which SSO protocol configuration is being supplied by this client scope",
"type": "Client scopes, which will be added as default scopes to each created client",

View file

@ -5,6 +5,8 @@
"clientScopeDetails": "Client scope details",
"clientScopeExplain": "Client scopes are a common set of protocol mappers and roles that are shared between multiple clients.",
"searchFor": "Search for client scope",
"dynamicScope": "Dynamic scope",
"dynamicScopeFormat": "Dynamic scope format",
"protocol": "Protocol",
"assignedType": "Assigned type",
"displayOrder": "Display order",

View file

@ -10,7 +10,7 @@ import {
ValidatedOptions,
} from "@patternfly/react-core";
import { useEffect, useState } from "react";
import { Controller, useForm, useWatch } from "react-hook-form";
import { Controller, FormProvider, useForm, useWatch } from "react-hook-form";
import { useTranslation } from "react-i18next";
import { Link } from "react-router-dom";
@ -21,13 +21,15 @@ import {
clientScopeTypesSelectOptions,
} from "../../components/client-scope/ClientScopeTypes";
import { FormAccess } from "../../components/form-access/FormAccess";
import { HelpItem } from "ui-shared";
import { HelpItem, TextControl } from "ui-shared";
import { KeycloakTextArea } from "../../components/keycloak-text-area/KeycloakTextArea";
import { KeycloakTextInput } from "../../components/keycloak-text-input/KeycloakTextInput";
import { useRealm } from "../../context/realm-context/RealmContext";
import { useLoginProviders } from "../../context/server-info/ServerInfoProvider";
import { convertAttributeNameToForm, convertToFormValues } from "../../util";
import { toClientScopes } from "../routes/ClientScopes";
import useIsFeatureEnabled, { Feature } from "../../utils/useIsFeatureEnabled";
import { DefaultSwitchControl } from "../../components/SwitchControl";
type ScopeFormProps = {
clientScope?: ClientScopeRepresentation;
@ -37,16 +39,19 @@ type ScopeFormProps = {
export const ScopeForm = ({ clientScope, save }: ScopeFormProps) => {
const { t } = useTranslation("client-scopes");
const { t: tc } = useTranslation("clients");
const form = useForm<ClientScopeDefaultOptionalType>({ mode: "onChange" });
const {
register,
control,
handleSubmit,
setValue,
formState: { errors, isDirty, isValid },
} = useForm<ClientScopeDefaultOptionalType>({ mode: "onChange" });
} = form;
const { realm } = useRealm();
const providers = useLoginProviders();
const isFeatureEnabled = useIsFeatureEnabled();
const isDynamicScopesEnabled = isFeatureEnabled(Feature.DynamicScopes);
const [open, isOpen] = useState(false);
const [openType, setOpenType] = useState(false);
@ -57,6 +62,22 @@ export const ScopeForm = ({ clientScope, save }: ScopeFormProps) => {
clientScope?.attributes?.["display.on.consent.screen"] ?? "true",
});
const dynamicScope = useWatch({
control,
name: convertAttributeNameToForm<ClientScopeDefaultOptionalType>(
"attributes.is.dynamic.scope"
),
defaultValue: "false",
});
const setDynamicRegex = (value: string, append: boolean) =>
setValue(
convertAttributeNameToForm<ClientScopeDefaultOptionalType>(
"attributes.dynamic.scope.regexp"
),
append ? `${value}:*` : value
);
useEffect(() => {
convertToFormValues(clientScope ?? {}, setValue);
}, [clientScope]);
@ -88,9 +109,41 @@ export const ScopeForm = ({ clientScope, save }: ScopeFormProps) => {
errors.name ? ValidatedOptions.error : ValidatedOptions.default
}
isRequired
{...register("name", { required: true })}
{...register("name", {
required: true,
onChange: (e) => {
if (isDynamicScopesEnabled) {
setDynamicRegex(e.target.value, true);
}
},
})}
/>
</FormGroup>
{isDynamicScopesEnabled && (
<FormProvider {...form}>
<DefaultSwitchControl
name={convertAttributeNameToForm<ClientScopeDefaultOptionalType>(
"attributes.is.dynamic.scope"
)}
label={t("dynamicScope")}
labelIcon={t("client-scopes-help:dynamicScope")}
onChange={(value) => {
setDynamicRegex(value ? form.getValues("name") || "" : "", value);
}}
stringify
/>
{dynamicScope === "true" && (
<TextControl
name={convertAttributeNameToForm<ClientScopeDefaultOptionalType>(
"attributes.dynamic.scope.regexp"
)}
label={t("dynamicScopeFormat")}
labelIcon={t("client-scopes-help:dynamicScopeFormat")}
isDisabled
/>
)}
</FormProvider>
)}
<FormGroup
label={t("common:description")}
labelIcon={

View file

@ -3,7 +3,7 @@ import { FieldPath, FieldValues, UseControllerProps } from "react-hook-form";
import { useTranslation } from "react-i18next";
import { SwitchControl } from "ui-shared";
type AdminSwitchControlProps<
type DefaultSwitchControlProps<
T extends FieldValues,
P extends FieldPath<T> = FieldPath<T>
> = SwitchProps &
@ -11,13 +11,14 @@ type AdminSwitchControlProps<
name: string;
label?: string;
labelIcon?: string;
stringify?: boolean;
};
export const DefaultSwitchControl = <
T extends FieldValues,
P extends FieldPath<T> = FieldPath<T>
>(
props: AdminSwitchControlProps<T, P>
props: DefaultSwitchControlProps<T, P>
) => {
const { t } = useTranslation("common");

View file

@ -5,6 +5,7 @@ export enum Feature {
ClientPolicies = "CLIENT_POLICIES",
DeclarativeUserProfile = "DECLARATIVE_USER_PROFILE",
Kerberos = "KERBEROS",
DynamicScopes = "DYNAMIC_SCOPES",
}
export default function useIsFeatureEnabled() {

View file

@ -19,6 +19,7 @@ export type SwitchControlProps<
labelIcon?: string;
labelOn: string;
labelOff: string;
stringify?: boolean;
};
export const SwitchControl = <
@ -46,8 +47,12 @@ export const SwitchControl = <
data-testid={props.name}
label={props.labelOn}
labelOff={props.labelOff}
isChecked={value}
onChange={(checked) => onChange(checked)}
isChecked={props.stringify ? value === "true" : value}
onChange={(checked, e) => {
const value = props.stringify ? checked.toString() : checked;
props.onChange?.(checked, e);
onChange(value);
}}
/>
)}
/>