added acr loa map field to realm settings (#2736)

* added acr loa map field to realm settings

fixes: #2668

* changed to use one form per tab, instead of shared

* fixed test
This commit is contained in:
Erik Jan de Wit 2022-06-15 14:57:39 +02:00 committed by GitHub
parent f775fd6329
commit afbbdaaf52
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 620 additions and 617 deletions

View file

@ -237,7 +237,6 @@ describe("Realm settings events tab tests", () => {
sidebarPage.goToRealmSettings();
cy.findByTestId("rs-localization-tab").click();
cy.findByTestId("internationalization-disabled").click({ force: true });
cy.get(realmSettingsPage.supportedLocalesTypeahead)
@ -245,10 +244,14 @@ describe("Realm settings events tab tests", () => {
.get(".pf-c-select__menu-item")
.contains("Dansk")
.click();
cy.get("#kc-l-supported-locales").click();
cy.intercept("GET", `/admin/realms/${realmName}/localization/en*`).as(
"load"
);
cy.findByTestId("localization-tab-save").click();
cy.wait("@load");
addBundle();

View file

@ -99,25 +99,25 @@
"noKeysDescription": "You haven't created any active keys",
"certificate": "Certificate",
"loginScreenCustomization": "Login screen customization",
"userRegistration": "User registration",
"registrationAllowed": "User registration",
"userRegistrationHelpText": "Enable/disable the registration page. A link for registration will show on login page too.",
"forgotPassword": "Forgot password",
"resetPasswordAllowed": "Forgot password",
"forgotPasswordHelpText": "Show a link on login page for user to click when they have forgotten their credentials.",
"rememberMe": "Remember me",
"rememberMeHelpText": "Show checkbox on login page to allow user to remain logged in between browser restarts until session expires.",
"emailSettings": "Email settings",
"emailAsUsername": "Email as username",
"registrationEmailAsUsername": "Email as username",
"emailAsUsernameHelpText": "Allow users to set email as username.",
"loginWithEmail": "Login with email",
"loginWithEmailAllowed": "Login with email",
"loginWithEmailHelpText": "Allow users to log in with their email address.",
"duplicateEmails": "Duplicate emails",
"duplicateEmailsAllowed": "Duplicate emails",
"duplicateEmailsHelpText": "Allow multiple users to have the same email address. Changing this setting will also clear the user's cache. It is recommended to manually update email constraints of existing users in the database after switching off support for duplicate email addresses.",
"provideEmailTitle": "Provide your email address",
"provideEmail": "To test connection, you should provide your email address first.",
"verifyEmail": "Verify email",
"verifyEmailHelpText": "Require user to verify their email address after initial login or after address changes are submitted.",
"userInfoSettings": "User info settings",
"editUsername": "Edit username",
"editUsernameAllowed": "Edit username",
"enableSwitchSuccess": "{{switch}} changed successfully",
"enableSwitchError": "Could not enable / disable due to {{error}}",
"testConnection": "Test connection",

View file

@ -1,7 +1,7 @@
@import "@patternfly/patternfly/patternfly.min.css";
@import "@patternfly/patternfly/patternfly-addons.css";
.keycloak__pageheader_brand {
img.keycloak__pageheader_brand {
height: 35px;
}

View file

@ -1,6 +1,6 @@
import React, { useState } from "react";
import React, { useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import { Controller, useFormContext } from "react-hook-form";
import { Controller, FormProvider, useForm } from "react-hook-form";
import {
ActionGroup,
Button,
@ -16,7 +16,7 @@ import {
} from "@patternfly/react-core";
import type RealmRepresentation from "@keycloak/keycloak-admin-client/lib/defs/realmRepresentation";
import { getBaseUrl } from "../util";
import { convertToFormValues, getBaseUrl } from "../util";
import useIsFeatureEnabled, { Feature } from "../utils/useIsFeatureEnabled";
import { useAdminClient } from "../context/auth/AdminClient";
import { useRealm } from "../context/realm-context/RealmContext";
@ -24,25 +24,28 @@ import { FormAccess } from "../components/form-access/FormAccess";
import { HelpItem } from "../components/help-enabler/HelpItem";
import { FormattedLink } from "../components/external-link/FormattedLink";
import { KeycloakTextInput } from "../components/keycloak-text-input/KeycloakTextInput";
import { KeyValueInput } from "../components/key-value-form/KeyValueInput";
type RealmSettingsGeneralTabProps = {
realm: RealmRepresentation;
save: (realm: RealmRepresentation) => void;
reset: () => void;
};
export const RealmSettingsGeneralTab = ({
realm,
save,
reset,
}: RealmSettingsGeneralTabProps) => {
const { t } = useTranslation("realm-settings");
const adminClient = useAdminClient();
const { realm: realmName } = useRealm();
const form = useForm<RealmRepresentation>({ shouldUnregister: false });
const {
register,
control,
handleSubmit,
setValue,
formState: { isDirty },
} = useFormContext();
} = form;
const isFeatureEnabled = useIsFeatureEnabled();
const [open, setOpen] = useState(false);
@ -50,6 +53,19 @@ export const RealmSettingsGeneralTab = ({
const requireSslTypes = ["all", "external", "none"];
const setupForm = () => {
convertToFormValues(realm, setValue);
if (realm.attributes?.["acr.loa.map"]) {
const result = Object.entries(
JSON.parse(realm.attributes["acr.loa.map"])
).flatMap(([key, value]) => ({ key, value }));
result.concat({ key: "", value: "" });
setValue("attributes.acr.loa.map", result);
}
};
useEffect(setupForm, []);
return (
<PageSection variant="light">
<FormAccess
@ -143,6 +159,20 @@ export const RealmSettingsGeneralTab = ({
)}
/>
</FormGroup>
<FormGroup
label={t("clients:acrToLoAMapping")}
fieldId="acrToLoAMapping"
labelIcon={
<HelpItem
helpText="clients-help:acrToLoAMapping"
fieldLabelId="clients:acrToLoAMapping"
/>
}
>
<FormProvider {...form}>
<KeyValueInput name="attributes.acr.loa.map" />
</FormProvider>
</FormGroup>
<FormGroup
hasNoPaddingTop
label={t("userManagedAccess")}
@ -237,7 +267,7 @@ export const RealmSettingsGeneralTab = ({
<Button
data-testid="general-tab-revert"
variant="link"
onClick={reset}
onClick={setupForm}
>
{t("common:revert")}
</Button>

View file

@ -1,7 +1,7 @@
import React, { useState } from "react";
import React, { useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import { cloneDeep, isEqual, uniqWith } from "lodash-es";
import { Controller, useForm, useFormContext, useWatch } from "react-hook-form";
import { Controller, useForm, useWatch } from "react-hook-form";
import {
ActionGroup,
AlertVariant,
@ -49,10 +49,10 @@ import { PaginatingTableToolbar } from "../components/table-toolbar/PaginatingTa
import { SearchIcon } from "@patternfly/react-icons";
import { useWhoAmI } from "../context/whoami/WhoAmI";
import type { KeyValueType } from "../components/key-value-form/key-value-convert";
import { convertToFormValues } from "../util";
type LocalizationTabProps = {
save: (realm: RealmRepresentation) => void;
reset: () => void;
refresh: () => void;
realm: RealmRepresentation;
};
@ -67,11 +67,7 @@ export type BundleForm = {
messageBundle: KeyValueType;
};
export const LocalizationTab = ({
save,
reset,
realm,
}: LocalizationTabProps) => {
export const LocalizationTab = ({ save, realm }: LocalizationTabProps) => {
const { t } = useTranslation("realm-settings");
const adminClient = useAdminClient();
const [addMessageBundleModalOpen, setAddMessageBundleModalOpen] =
@ -82,7 +78,9 @@ export const LocalizationTab = ({
const [filterDropdownOpen, setFilterDropdownOpen] = useState(false);
const [selectMenuLocale, setSelectMenuLocale] = useState(DEFAULT_LOCALE);
const { getValues, control, handleSubmit, formState } = useFormContext();
const { setValue, getValues, control, handleSubmit, formState } = useForm({
shouldUnregister: false,
});
const [selectMenuValueSelected, setSelectMenuValueSelected] = useState(false);
const [messageBundles, setMessageBundles] = useState<[string, string][]>([]);
const [tableRows, setTableRows] = useState<IRow[]>([]);
@ -93,6 +91,15 @@ export const LocalizationTab = ({
const { realm: currentRealm } = useRealm();
const { whoAmI } = useWhoAmI();
const setupForm = () => {
convertToFormValues(realm, setValue);
if (realm.supportedLocales?.length === 0) {
setValue("supportedLocales", [DEFAULT_LOCALE]);
}
};
useEffect(setupForm, []);
const watchSupportedLocales = useWatch<string[]>({
control,
name: "supportedLocales",
@ -475,7 +482,7 @@ export const LocalizationTab = ({
>
{t("common:save")}
</Button>
<Button variant="link" onClick={reset}>
<Button variant="link" onClick={setupForm}>
{t("common:revert")}
</Button>
</ActionGroup>

View file

@ -5,7 +5,6 @@ import { FormAccess } from "../components/form-access/FormAccess";
import { HelpItem } from "../components/help-enabler/HelpItem";
import { FormPanel } from "../components/scroll-form/FormPanel";
import type RealmRepresentation from "@keycloak/keycloak-admin-client/lib/defs/realmRepresentation";
import { Controller, useForm } from "react-hook-form";
import { useAdminClient } from "../context/auth/AdminClient";
import { useAlerts } from "../components/alert/Alerts";
import { useRealm } from "../context/realm-context/RealmContext";
@ -15,26 +14,31 @@ type RealmSettingsLoginTabProps = {
refresh: () => void;
};
type SwitchType = { [K in keyof RealmRepresentation]: boolean };
export const RealmSettingsLoginTab = ({
realm,
refresh,
}: RealmSettingsLoginTabProps) => {
const { t } = useTranslation("realm-settings");
const form = useForm<RealmRepresentation>({ mode: "onChange" });
const { addAlert, addError } = useAlerts();
const adminClient = useAdminClient();
const { realm: realmName } = useRealm();
const updateSwitchValue = async (name: string) => {
const switchValues = form.getValues();
const updateSwitchValue = async (switches: SwitchType | SwitchType[]) => {
const name = Array.isArray(switches)
? Object.keys(switches[0])[0]
: Object.keys(switches)[0];
try {
await adminClient.realms.update(
{
realm: realmName,
},
switchValues
Array.isArray(switches)
? switches.reduce((realm, s) => Object.assign(realm, s), realm)
: Object.assign(realm, switches)
);
addAlert(t("enableSwitchSuccess", { switch: t(name) }));
refresh();
@ -51,67 +55,51 @@ export const RealmSettingsLoginTab = ({
>
<FormAccess isHorizontal role="manage-realm">
<FormGroup
label={t("userRegistration")}
label={t("registrationAllowed")}
fieldId="kc-user-reg"
labelIcon={
<HelpItem
helpText={t("userRegistrationHelpText")}
fieldLabelId="realm-settings:userRegistration"
fieldLabelId="realm-settings:registrationAllowed"
/>
}
hasNoPaddingTop
>
<Controller
name="registrationAllowed"
defaultValue={realm.registrationAllowed}
control={form.control}
render={({ onChange, value }) => (
<Switch
id="kc-user-reg-switch"
data-testid="user-reg-switch"
value={value ? "on" : "off"}
value={realm.registrationAllowed ? "on" : "off"}
label={t("common:on")}
labelOff={t("common:off")}
isChecked={value}
isChecked={realm.registrationAllowed}
onChange={(value) => {
onChange(value);
updateSwitchValue("userRegistration");
updateSwitchValue({ registrationAllowed: value });
}}
/>
)}
/>
</FormGroup>
<FormGroup
label={t("forgotPassword")}
label={t("resetPasswordAllowed")}
fieldId="kc-forgot-pw"
labelIcon={
<HelpItem
helpText="realm-settings:forgotPasswordHelpText"
fieldLabelId="realm-settings:forgotPassword"
fieldLabelId="realm-settings:resetPasswordAllowed"
/>
}
hasNoPaddingTop
>
<Controller
name="resetPasswordAllowed"
defaultValue={realm.resetPasswordAllowed}
control={form.control}
render={({ onChange, value }) => (
<Switch
id="kc-forgot-pw-switch"
data-testid="forgot-pw-switch"
name="resetPasswordAllowed"
value={value ? "on" : "off"}
value={realm.resetPasswordAllowed ? "on" : "off"}
label={t("common:on")}
labelOff={t("common:off")}
isChecked={value}
isChecked={realm.resetPasswordAllowed}
onChange={(value) => {
onChange(value);
updateSwitchValue("forgotPassword");
updateSwitchValue({ resetPasswordAllowed: value });
}}
/>
)}
/>
</FormGroup>
<FormGroup
label={t("rememberMe")}
@ -124,129 +112,110 @@ export const RealmSettingsLoginTab = ({
}
hasNoPaddingTop
>
<Controller
name="rememberMe"
defaultValue={realm.rememberMe}
control={form.control}
render={({ onChange, value }) => (
<Switch
id="kc-remember-me-switch"
data-testid="remember-me-switch"
value={value ? "on" : "off"}
value={realm.rememberMe ? "on" : "off"}
label={t("common:on")}
labelOff={t("common:off")}
isChecked={value}
isChecked={realm.rememberMe}
onChange={(value) => {
onChange(value);
updateSwitchValue("rememberMe");
updateSwitchValue({ rememberMe: value });
}}
/>
)}
/>
</FormGroup>
</FormAccess>
</FormPanel>
<FormPanel className="kc-email-settings" title={t("emailSettings")}>
<FormAccess isHorizontal role="manage-realm">
<FormGroup
label={t("emailAsUsername")}
label={t("registrationEmailAsUsername")}
fieldId="kc-email-as-username"
labelIcon={
<HelpItem
helpText="realm-settings:emailAsUsernameHelpText"
fieldLabelId="realm-settings:emailAsUsername"
fieldLabelId="realm-settings:registrationEmailAsUsername"
/>
}
hasNoPaddingTop
>
<Controller
name="registrationEmailAsUsername"
defaultValue={realm.registrationEmailAsUsername}
control={form.control}
render={({ onChange, value }) => (
<Switch
id="kc-email-as-username-switch"
data-testid="email-as-username-switch"
value={value ? "on" : "off"}
value={realm.registrationEmailAsUsername ? "on" : "off"}
label={t("common:on")}
labelOff={t("common:off")}
isChecked={value}
isChecked={realm.registrationEmailAsUsername}
onChange={(value) => {
onChange(value);
updateSwitchValue("emailAsUsername");
updateSwitchValue([
{
registrationEmailAsUsername: value,
},
{
duplicateEmailsAllowed: false,
},
]);
}}
/>
)}
/>
</FormGroup>
<FormGroup
label={t("loginWithEmail")}
label={t("loginWithEmailAllowed")}
fieldId="kc-login-with-email"
labelIcon={
<HelpItem
helpText="realm-settings:loginWithEmailHelpText"
fieldLabelId="realm-settings:loginWithEmail"
fieldLabelId="realm-settings:loginWithEmailAllowed"
/>
}
hasNoPaddingTop
>
<Controller
name="loginWithEmailAllowed"
defaultValue={realm.loginWithEmailAllowed}
control={form.control}
render={({ onChange, value }) => (
<Switch
id="kc-login-with-email-switch"
data-testid="login-with-email-switch"
value={value ? "on" : "off"}
value={realm.loginWithEmailAllowed ? "on" : "off"}
label={t("common:on")}
labelOff={t("common:off")}
isChecked={value}
isChecked={realm.loginWithEmailAllowed}
onChange={(value) => {
onChange(value);
updateSwitchValue("loginWithEmail");
updateSwitchValue([
{
loginWithEmailAllowed: value,
},
{ duplicateEmailsAllowed: false },
]);
}}
/>
)}
/>
</FormGroup>
<FormGroup
label={t("duplicateEmails")}
label={t("duplicateEmailsAllowed")}
fieldId="kc-duplicate-emails"
labelIcon={
<HelpItem
helpText="realm-settings:duplicateEmailsHelpText"
fieldLabelId="realm-settings:duplicateEmails"
fieldLabelId="realm-settings:duplicateEmailsAllowed"
/>
}
hasNoPaddingTop
>
<Controller
name="duplicateEmailsAllowed"
defaultValue={realm.duplicateEmailsAllowed}
control={form.control}
render={({ onChange }) => (
<Switch
id="kc-duplicate-emails-switch"
data-testid="duplicate-emails-switch"
label={t("common:on")}
labelOff={t("common:off")}
isChecked={
form.getValues().duplicateEmailsAllowed &&
!form.getValues().loginWithEmailAllowed &&
!form.getValues().registrationEmailAsUsername
realm.duplicateEmailsAllowed ||
realm.loginWithEmailAllowed ||
realm.registrationEmailAsUsername
}
onChange={(value) => {
onChange(value);
updateSwitchValue("duplicateEmails");
updateSwitchValue({
duplicateEmailsAllowed: value,
});
}}
isDisabled={
form.getValues().loginWithEmailAllowed ||
form.getValues().registrationEmailAsUsername
realm.loginWithEmailAllowed || realm.registrationEmailAsUsername
}
/>
)}
/>
</FormGroup>
<FormGroup
label={t("verifyEmail")}
@ -259,26 +228,18 @@ export const RealmSettingsLoginTab = ({
}
hasNoPaddingTop
>
<Controller
name="verifyEmail"
defaultValue={realm.verifyEmail}
control={form.control}
render={({ onChange, value }) => (
<Switch
id="kc-verify-email-switch"
data-testid="verify-email-switch"
name="verifyEmail"
value={value ? "on" : "off"}
value={realm.verifyEmail ? "on" : "off"}
label={t("common:on")}
labelOff={t("common:off")}
isChecked={value}
isChecked={realm.verifyEmail}
onChange={(value) => {
onChange(value);
updateSwitchValue("verifyEmail");
updateSwitchValue({ verifyEmail: value });
}}
/>
)}
/>
</FormGroup>
</FormAccess>
</FormPanel>
@ -288,35 +249,27 @@ export const RealmSettingsLoginTab = ({
>
<FormAccess isHorizontal role="manage-realm">
<FormGroup
label={t("editUsername")}
label={t("editUsernameAllowed")}
fieldId="kc-edit-username"
labelIcon={
<HelpItem
helpText="realm-settings-help:editUsername"
fieldLabelId="realm-settings:editUsername"
fieldLabelId="realm-settings:editUsernameAllowed"
/>
}
hasNoPaddingTop
>
<Controller
name="editUsernameAllowed"
defaultValue={realm.editUsernameAllowed}
control={form.control}
render={({ onChange, value }) => (
<Switch
id="kc-edit-username-switch"
data-testid="edit-username-switch"
value={value ? "on" : "off"}
value={realm.editUsernameAllowed ? "on" : "off"}
label={t("common:on")}
labelOff={t("common:off")}
isChecked={value}
isChecked={realm.editUsernameAllowed}
onChange={(value) => {
onChange(value);
updateSwitchValue("userInfoSettings");
updateSwitchValue({ editUsernameAllowed: value });
}}
/>
)}
/>
</FormGroup>
</FormAccess>
</FormPanel>

View file

@ -1,19 +1,21 @@
import React, { useState } from "react";
import type RealmRepresentation from "@keycloak/keycloak-admin-client/lib/defs/realmRepresentation";
import type { RealmSettingsParams } from "./routes/RealmSettings";
import { KeycloakSpinner } from "../components/keycloak-spinner/KeycloakSpinner";
import { useAdminClient, useFetch } from "../context/auth/AdminClient";
import { useRealm } from "../context/realm-context/RealmContext";
import { RealmSettingsTabs } from "./RealmSettingsTabs";
import { useParams } from "react-router-dom";
export default function RealmSettingsSection() {
const adminClient = useAdminClient();
const { realm: realmName } = useRealm();
const { realm: realmName } = useParams<RealmSettingsParams>();
const [realm, setRealm] = useState<RealmRepresentation>();
const [key, setKey] = useState(0);
const refresh = () => {
setKey(key + 1);
setRealm(undefined);
};
useFetch(() => adminClient.realms.findOne({ realm: realmName }), setRealm, [

View file

@ -1,6 +1,6 @@
import React, { useEffect, useState } from "react";
import { useHistory } from "react-router-dom";
import { Controller, FormProvider, useForm, useWatch } from "react-hook-form";
import { Controller, useForm } from "react-hook-form";
import { useTranslation } from "react-i18next";
import {
AlertVariant,
@ -34,7 +34,7 @@ import { RealmSettingsEmailTab } from "./EmailTab";
import { EventsTab } from "./event-config/EventsTab";
import { RealmSettingsGeneralTab } from "./GeneralTab";
import { RealmSettingsLoginTab } from "./LoginTab";
import { SecurityDefences } from "./security-defences/SecurityDefences";
import { SecurityDefenses } from "./security-defences/SecurityDefenses";
import { RealmSettingsSessionsTab } from "./SessionsTab";
import { RealmSettingsThemesTab } from "./ThemesTab";
import { RealmSettingsTokensTab } from "./TokensTab";
@ -53,7 +53,7 @@ import { UserProfileTab } from "./user-profile/UserProfileTab";
import useIsFeatureEnabled, { Feature } from "../utils/useIsFeatureEnabled";
import { ClientPoliciesTab, toClientPolicies } from "./routes/ClientPolicies";
import { KeysTab } from "./keys/KeysTab";
import { DEFAULT_LOCALE } from "../i18n";
import type { KeyValueType } from "../components/key-value-form/key-value-convert";
type RealmSettingsHeaderProps = {
onChange: (value: boolean) => void;
@ -176,55 +176,59 @@ export const RealmSettingsTabs = ({
const history = useHistory();
const isFeatureEnabled = useIsFeatureEnabled();
const form = useForm({ mode: "onChange", shouldUnregister: false });
const { control, getValues, setValue, reset: resetForm } = form;
const { control, setValue, getValues } = useForm({
mode: "onChange",
shouldUnregister: false,
});
const [key, setKey] = useState(0);
const refreshHeader = () => {
setKey(new Date().getTime());
setKey(key + 1);
};
const setupForm = (r: RealmRepresentation = realm) => {
convertToFormValues(r, setValue);
if (r.supportedLocales?.length === 0) {
setValue("supportedLocales", [DEFAULT_LOCALE]);
}
resetForm(getValues());
};
useEffect(() => {
setupForm();
}, []);
useEffect(setupForm, []);
const save = async (r: RealmRepresentation) => {
r = convertFormValuesToObject(r);
if (
r.attributes?.["acr.loa.map"] &&
typeof r.attributes["acr.loa.map"] !== "string"
) {
const map = JSON.stringify(
Object.fromEntries(
(r.attributes["acr.loa.map"] as KeyValueType[])
.filter(({ key }) => key !== "")
.map(({ key, value }) => [key, value])
)
);
r.attributes["acr.loa.map"] = map !== "{}" ? map : "[]";
}
const save = async (realm: RealmRepresentation) => {
try {
realm = convertFormValuesToObject(realm);
await adminClient.realms.update(
{ realm: realmName },
{
...realm,
id: realmName,
...r,
id: r.realm,
}
);
setupForm(realm);
const isRealmRenamed = realmName !== realm.realm;
if (isRealmRenamed) {
await refreshRealms();
history.push(toRealmSettings({ realm: realm.realm!, tab: "general" }));
}
addAlert(t("saveSuccess"), AlertVariant.success);
} catch (error) {
addError("realm-settings:saveError", error);
}
};
const userProfileEnabled = useWatch({
control,
name: "attributes.userProfileEnabled",
defaultValue: "false",
});
const isRealmRenamed = realmName !== (r.realm || realm.realm);
if (isRealmRenamed) {
await refreshRealms();
history.push(toRealmSettings({ realm: r.realm!, tab: "general" }));
}
refresh();
};
const route = (tab: RealmSettingsTab | undefined = "general") =>
routableTab({
@ -258,7 +262,6 @@ export const RealmSettingsTabs = ({
)}
/>
<PageSection variant="light" className="pf-u-p-0">
<FormProvider {...form}>
<RoutableTabs
isBox
mountOnEnter
@ -272,10 +275,7 @@ export const RealmSettingsTabs = ({
data-testid="rs-general-tab"
{...route()}
>
<RealmSettingsGeneralTab
save={save}
reset={() => resetForm(realm)}
/>
<RealmSettingsGeneralTab realm={realm} save={save} />
</Tab>
<Tab
title={<TabTitleText>{t("login")}</TabTitleText>}
@ -296,10 +296,7 @@ export const RealmSettingsTabs = ({
data-testid="rs-themes-tab"
{...route("themes")}
>
<RealmSettingsThemesTab
save={save}
reset={() => resetForm(realm)}
/>
<RealmSettingsThemesTab realm={realm} save={save} />
</Tab>
<Tab
title={<TabTitleText>{t("realm-settings:keys")}</TabTitleText>}
@ -324,7 +321,6 @@ export const RealmSettingsTabs = ({
key={key}
refresh={refresh}
save={save}
reset={() => resetForm(realm)}
realm={realm}
/>
</Tab>
@ -333,12 +329,10 @@ export const RealmSettingsTabs = ({
data-testid="rs-security-defenses-tab"
{...route("securityDefences")}
>
<SecurityDefences save={save} reset={() => resetForm(realm)} />
<SecurityDefenses realm={realm} save={save} />
</Tab>
<Tab
title={
<TabTitleText>{t("realm-settings:sessions")}</TabTitleText>
}
title={<TabTitleText>{t("realm-settings:sessions")}</TabTitleText>}
data-testid="rs-sessions-tab"
{...route("sessions")}
>
@ -349,17 +343,11 @@ export const RealmSettingsTabs = ({
data-testid="rs-tokens-tab"
{...route("tokens")}
>
<RealmSettingsTokensTab
save={save}
realm={realm}
reset={() => resetForm(realm)}
/>
<RealmSettingsTokensTab save={save} realm={realm} />
</Tab>
<Tab
title={
<TabTitleText>
{t("realm-settings:clientPolicies")}
</TabTitleText>
<TabTitleText>{t("realm-settings:clientPolicies")}</TabTitleText>
}
data-testid="rs-clientPolicies-tab"
{...route("clientPolicies")}
@ -411,12 +399,10 @@ export const RealmSettingsTabs = ({
</RoutableTabs>
</Tab>
{isFeatureEnabled(Feature.DeclarativeUserProfile) &&
userProfileEnabled === "true" && (
realm.attributes?.userProfileEnabled === "true" && (
<Tab
title={
<TabTitleText>
{t("realm-settings:userProfile")}
</TabTitleText>
<TabTitleText>{t("realm-settings:userProfile")}</TabTitleText>
}
data-testid="rs-user-profile-tab"
{...route("userProfile")}
@ -432,7 +418,6 @@ export const RealmSettingsTabs = ({
<UserRegistration />
</Tab>
</RoutableTabs>
</FormProvider>
</PageSection>
</>
);

View file

@ -1,6 +1,6 @@
import React from "react";
import React, { useEffect } from "react";
import { useTranslation } from "react-i18next";
import { Controller, useFormContext, useWatch } from "react-hook-form";
import { Controller, useForm, useWatch } from "react-hook-form";
import {
ActionGroup,
Button,
@ -14,6 +14,7 @@ import { FormAccess } from "../components/form-access/FormAccess";
import { HelpItem } from "../components/help-enabler/HelpItem";
import { FormPanel } from "../components/scroll-form/FormPanel";
import { TimeSelector } from "../components/time-selector/TimeSelector";
import { convertToFormValues } from "../util";
import "./realm-settings-section.css";
@ -28,24 +29,22 @@ export const RealmSettingsSessionsTab = ({
}: RealmSettingsSessionsTabProps) => {
const { t } = useTranslation("realm-settings");
const {
control,
handleSubmit,
reset: resetForm,
formState,
} = useFormContext<RealmRepresentation>();
const { setValue, control, handleSubmit, formState } =
useForm<RealmRepresentation>({
shouldUnregister: false,
});
const offlineSessionMaxEnabled = useWatch({
control,
name: "offlineSessionMaxLifespanEnabled",
});
const reset = () => {
if (realm) {
resetForm(realm);
}
const setupForm = () => {
convertToFormValues(realm, setValue);
};
useEffect(setupForm, []);
return (
<PageSection variant="light">
<FormPanel
@ -399,7 +398,7 @@ export const RealmSettingsSessionsTab = ({
>
{t("common:save")}
</Button>
<Button variant="link" onClick={reset}>
<Button variant="link" onClick={setupForm}>
{t("common:revert")}
</Button>
</ActionGroup>

View file

@ -1,6 +1,6 @@
import React, { useState } from "react";
import React, { useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import { Controller, useFormContext } from "react-hook-form";
import { Controller, useForm } from "react-hook-form";
import {
ActionGroup,
Button,
@ -15,15 +15,16 @@ import type RealmRepresentation from "@keycloak/keycloak-admin-client/lib/defs/r
import { FormAccess } from "../components/form-access/FormAccess";
import { HelpItem } from "../components/help-enabler/HelpItem";
import { useServerInfo } from "../context/server-info/ServerInfoProvider";
import { convertToFormValues } from "../util";
type RealmSettingsThemesTabProps = {
realm: RealmRepresentation;
save: (realm: RealmRepresentation) => void;
reset: () => void;
};
export const RealmSettingsThemesTab = ({
realm,
save,
reset,
}: RealmSettingsThemesTabProps) => {
const { t } = useTranslation("realm-settings");
@ -32,9 +33,14 @@ export const RealmSettingsThemesTab = ({
const [adminConsoleThemeOpen, setAdminConsoleThemeOpen] = useState(false);
const [emailThemeOpen, setEmailThemeOpen] = useState(false);
const { control, handleSubmit } = useFormContext();
const { control, handleSubmit, setValue } = useForm<RealmRepresentation>();
const themeTypes = useServerInfo().themes!;
const setupForm = () => {
convertToFormValues(realm, setValue);
};
useEffect(setupForm, []);
return (
<PageSection variant="light">
<FormAccess
@ -217,7 +223,7 @@ export const RealmSettingsThemesTab = ({
<Button variant="primary" type="submit" data-testid="themes-tab-save">
{t("common:save")}
</Button>
<Button variant="link" onClick={reset}>
<Button variant="link" onClick={setupForm}>
{t("common:revert")}
</Button>
</ActionGroup>

View file

@ -1,6 +1,6 @@
import React, { useState } from "react";
import React, { useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import { Controller, useFormContext, useWatch } from "react-hook-form";
import { Controller, useForm, useWatch } from "react-hook-form";
import {
ActionGroup,
Button,
@ -27,6 +27,7 @@ import { useServerInfo } from "../context/server-info/ServerInfoProvider";
import "./realm-settings-section.css";
import { useWhoAmI } from "../context/whoami/WhoAmI";
import { convertToFormValues } from "../util";
type RealmSettingsSessionsTabProps = {
realm: RealmRepresentation;
@ -60,8 +61,8 @@ export const RealmSettingsTokensTab = ({
javaKeystoreAlgOptions!
);
const form = useFormContext<RealmRepresentation>();
const { control } = form;
const form = useForm<RealmRepresentation>({ shouldUnregister: false });
const { setValue, control } = form;
const offlineSessionMaxEnabled = useWatch({
control,
@ -81,6 +82,10 @@ export const RealmSettingsTokensTab = ({
defaultValue: false,
});
useEffect(() => {
convertToFormValues(realm, setValue);
}, []);
return (
<PageSection variant="light">
<FormPanel

View file

@ -1,6 +1,6 @@
import React from "react";
import React, { useEffect } from "react";
import { useTranslation } from "react-i18next";
import { Controller, useFormContext, useWatch } from "react-hook-form";
import { Controller, FormProvider, useForm, useWatch } from "react-hook-form";
import {
ActionGroup,
Button,
@ -13,22 +13,25 @@ import type RealmRepresentation from "@keycloak/keycloak-admin-client/lib/defs/r
import { FormAccess } from "../../components/form-access/FormAccess";
import { HelpItem } from "../../components/help-enabler/HelpItem";
import { Time } from "./Time";
import { convertToFormValues } from "../../util";
type BruteForceDetectionProps = {
realm: RealmRepresentation;
save: (realm: RealmRepresentation) => void;
reset: () => void;
};
export const BruteForceDetection = ({
realm,
save,
reset,
}: BruteForceDetectionProps) => {
const { t } = useTranslation("realm-settings");
const form = useForm({ shouldUnregister: false });
const {
setValue,
handleSubmit,
control,
formState: { isDirty },
} = useFormContext();
} = form;
const enable = useWatch({
control,
@ -40,8 +43,16 @@ export const BruteForceDetection = ({
name: "permanentLockout",
});
const setupForm = () => convertToFormValues(realm, setValue);
useEffect(setupForm, []);
return (
<FormAccess role="manage-realm" isHorizontal onSubmit={handleSubmit(save)}>
<FormProvider {...form}>
<FormAccess
role="manage-realm"
isHorizontal
onSubmit={handleSubmit(save)}
>
<FormGroup
label={t("common:enabled")}
fieldId="bruteForceProtected"
@ -62,6 +73,8 @@ export const BruteForceDetection = ({
)}
/>
</FormGroup>
{enable && (
<>
<FormGroup
label={t("failureFactor")}
labelIcon={
@ -71,7 +84,6 @@ export const BruteForceDetection = ({
/>
}
fieldId="failureFactor"
style={enable ? {} : { display: "none" }}
>
<Controller
name="failureFactor"
@ -96,7 +108,6 @@ export const BruteForceDetection = ({
label={t("permanentLockout")}
fieldId="permanentLockout"
hasNoPaddingTop
style={enable ? {} : { display: "none" }}
>
<Controller
name="permanentLockout"
@ -114,18 +125,13 @@ export const BruteForceDetection = ({
/>
</FormGroup>
<Time
name="waitIncrementSeconds"
style={enable && !permanentLockout ? {} : { display: "none" }}
/>
<Time
name="maxFailureWaitSeconds"
style={enable && !permanentLockout ? {} : { display: "none" }}
/>
<Time
name="maxDeltaTimeSeconds"
style={enable && !permanentLockout ? {} : { display: "none" }}
/>
{!permanentLockout && (
<>
<Time name="waitIncrementSeconds" />
<Time name="maxFailureWaitSeconds" />
<Time name="maxDeltaTimeSeconds" />
</>
)}
<FormGroup
label={t("quickLoginCheckMilliSeconds")}
@ -136,7 +142,6 @@ export const BruteForceDetection = ({
/>
}
fieldId="quickLoginCheckMilliSeconds"
style={enable ? {} : { display: "none" }}
>
<Controller
name="quickLoginCheckMilliSeconds"
@ -157,10 +162,9 @@ export const BruteForceDetection = ({
/>
</FormGroup>
<Time
name="minimumQuickLoginWaitSeconds"
style={enable ? {} : { display: "none" }}
/>
<Time name="minimumQuickLoginWaitSeconds" />
</>
)}
<ActionGroup>
<Button
@ -171,10 +175,11 @@ export const BruteForceDetection = ({
>
{t("common:save")}
</Button>
<Button variant="link" onClick={reset}>
<Button variant="link" onClick={setupForm}>
{t("common:revert")}
</Button>
</ActionGroup>
</FormAccess>
</FormProvider>
);
};

View file

@ -1,27 +1,34 @@
import React from "react";
import React, { useEffect } from "react";
import { useTranslation } from "react-i18next";
import { useFormContext } from "react-hook-form";
import { FormProvider, useForm } from "react-hook-form";
import { ActionGroup, Button } from "@patternfly/react-core";
import type RealmRepresentation from "@keycloak/keycloak-admin-client/lib/defs/realmRepresentation";
import { FormAccess } from "../../components/form-access/FormAccess";
import { HelpLinkTextInput } from "./HelpLinkTextInput";
import { convertToFormValues } from "../../util";
import "./security-defences.css";
type HeadersFormProps = {
realm: RealmRepresentation;
save: (realm: RealmRepresentation) => void;
reset: () => void;
};
export const HeadersForm = ({ save, reset }: HeadersFormProps) => {
export const HeadersForm = ({ realm, save }: HeadersFormProps) => {
const { t } = useTranslation();
const form = useForm();
const {
setValue,
formState: { isDirty },
handleSubmit,
} = useFormContext();
} = form;
const setupForm = () => convertToFormValues(realm, setValue);
useEffect(setupForm, []);
return (
<FormProvider {...form}>
<FormAccess
isHorizontal
role="manage-realm"
@ -66,10 +73,11 @@ export const HeadersForm = ({ save, reset }: HeadersFormProps) => {
>
{t("common:save")}
</Button>
<Button variant="link" onClick={reset}>
<Button variant="link" onClick={setupForm}>
{t("common:revert")}
</Button>
</ActionGroup>
</FormAccess>
</FormProvider>
);
};

View file

@ -6,12 +6,12 @@ import type RealmRepresentation from "@keycloak/keycloak-admin-client/lib/defs/r
import { HeadersForm } from "./HeadersForm";
import { BruteForceDetection } from "./BruteForceDetection";
type SecurityDefencesProps = {
type SecurityDefensesProps = {
realm: RealmRepresentation;
save: (realm: RealmRepresentation) => void;
reset: () => void;
};
export const SecurityDefences = ({ save, reset }: SecurityDefencesProps) => {
export const SecurityDefenses = ({ realm, save }: SecurityDefensesProps) => {
const { t } = useTranslation("realm-settings");
const [activeTab, setActiveTab] = useState(10);
return (
@ -25,7 +25,7 @@ export const SecurityDefences = ({ save, reset }: SecurityDefencesProps) => {
title={<TabTitleText>{t("headers")}</TabTitleText>}
>
<PageSection variant="light">
<HeadersForm save={save} reset={reset} />
<HeadersForm realm={realm} save={save} />
</PageSection>
</Tab>
<Tab
@ -34,7 +34,7 @@ export const SecurityDefences = ({ save, reset }: SecurityDefencesProps) => {
title={<TabTitleText>{t("bruteForceDetection")}</TabTitleText>}
>
<PageSection variant="light">
<BruteForceDetection save={save} reset={reset} />
<BruteForceDetection realm={realm} save={save} />
</PageSection>
</Tab>
</Tabs>