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,66 +55,50 @@ 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"}
label={t("common:on")}
labelOff={t("common:off")}
isChecked={value}
onChange={(value) => {
onChange(value);
updateSwitchValue("userRegistration");
}}
/>
)}
<Switch
id="kc-user-reg-switch"
data-testid="user-reg-switch"
value={realm.registrationAllowed ? "on" : "off"}
label={t("common:on")}
labelOff={t("common:off")}
isChecked={realm.registrationAllowed}
onChange={(value) => {
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
<Switch
id="kc-forgot-pw-switch"
data-testid="forgot-pw-switch"
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"}
label={t("common:on")}
labelOff={t("common:off")}
isChecked={value}
onChange={(value) => {
onChange(value);
updateSwitchValue("forgotPassword");
}}
/>
)}
value={realm.resetPasswordAllowed ? "on" : "off"}
label={t("common:on")}
labelOff={t("common:off")}
isChecked={realm.resetPasswordAllowed}
onChange={(value) => {
updateSwitchValue({ resetPasswordAllowed: value });
}}
/>
</FormGroup>
<FormGroup
@ -124,24 +112,16 @@ 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"}
label={t("common:on")}
labelOff={t("common:off")}
isChecked={value}
onChange={(value) => {
onChange(value);
updateSwitchValue("rememberMe");
}}
/>
)}
<Switch
id="kc-remember-me-switch"
data-testid="remember-me-switch"
value={realm.rememberMe ? "on" : "off"}
label={t("common:on")}
labelOff={t("common:off")}
isChecked={realm.rememberMe}
onChange={(value) => {
updateSwitchValue({ rememberMe: value });
}}
/>
</FormGroup>
</FormAccess>
@ -149,103 +129,92 @@ export const RealmSettingsLoginTab = ({
<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"}
label={t("common:on")}
labelOff={t("common:off")}
isChecked={value}
onChange={(value) => {
onChange(value);
updateSwitchValue("emailAsUsername");
}}
/>
)}
<Switch
id="kc-email-as-username-switch"
data-testid="email-as-username-switch"
value={realm.registrationEmailAsUsername ? "on" : "off"}
label={t("common:on")}
labelOff={t("common:off")}
isChecked={realm.registrationEmailAsUsername}
onChange={(value) => {
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"}
label={t("common:on")}
labelOff={t("common:off")}
isChecked={value}
onChange={(value) => {
onChange(value);
updateSwitchValue("loginWithEmail");
}}
/>
)}
<Switch
id="kc-login-with-email-switch"
data-testid="login-with-email-switch"
value={realm.loginWithEmailAllowed ? "on" : "off"}
label={t("common:on")}
labelOff={t("common:off")}
isChecked={realm.loginWithEmailAllowed}
onChange={(value) => {
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
}
onChange={(value) => {
onChange(value);
updateSwitchValue("duplicateEmails");
}}
isDisabled={
form.getValues().loginWithEmailAllowed ||
form.getValues().registrationEmailAsUsername
}
/>
)}
<Switch
id="kc-duplicate-emails-switch"
data-testid="duplicate-emails-switch"
label={t("common:on")}
labelOff={t("common:off")}
isChecked={
realm.duplicateEmailsAllowed ||
realm.loginWithEmailAllowed ||
realm.registrationEmailAsUsername
}
onChange={(value) => {
updateSwitchValue({
duplicateEmailsAllowed: value,
});
}}
isDisabled={
realm.loginWithEmailAllowed || realm.registrationEmailAsUsername
}
/>
</FormGroup>
<FormGroup
@ -259,25 +228,17 @@ export const RealmSettingsLoginTab = ({
}
hasNoPaddingTop
>
<Controller
<Switch
id="kc-verify-email-switch"
data-testid="verify-email-switch"
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"}
label={t("common:on")}
labelOff={t("common:off")}
isChecked={value}
onChange={(value) => {
onChange(value);
updateSwitchValue("verifyEmail");
}}
/>
)}
value={realm.verifyEmail ? "on" : "off"}
label={t("common:on")}
labelOff={t("common:off")}
isChecked={realm.verifyEmail}
onChange={(value) => {
updateSwitchValue({ verifyEmail: value });
}}
/>
</FormGroup>
</FormAccess>
@ -288,34 +249,26 @@ 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"}
label={t("common:on")}
labelOff={t("common:off")}
isChecked={value}
onChange={(value) => {
onChange(value);
updateSwitchValue("userInfoSettings");
}}
/>
)}
<Switch
id="kc-edit-username-switch"
data-testid="edit-username-switch"
value={realm.editUsernameAllowed ? "on" : "off"}
label={t("common:on")}
labelOff={t("common:off")}
isChecked={realm.editUsernameAllowed}
onChange={(value) => {
updateSwitchValue({ editUsernameAllowed: value });
}}
/>
</FormGroup>
</FormAccess>

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,181 +262,162 @@ export const RealmSettingsTabs = ({
)}
/>
<PageSection variant="light" className="pf-u-p-0">
<FormProvider {...form}>
<RoutableTabs
isBox
mountOnEnter
defaultLocation={toRealmSettings({
realm: realmName,
tab: "general",
})}
<RoutableTabs
isBox
mountOnEnter
defaultLocation={toRealmSettings({
realm: realmName,
tab: "general",
})}
>
<Tab
title={<TabTitleText>{t("general")}</TabTitleText>}
data-testid="rs-general-tab"
{...route()}
>
<Tab
title={<TabTitleText>{t("general")}</TabTitleText>}
data-testid="rs-general-tab"
{...route()}
<RealmSettingsGeneralTab realm={realm} save={save} />
</Tab>
<Tab
title={<TabTitleText>{t("login")}</TabTitleText>}
data-testid="rs-login-tab"
{...route("login")}
>
<RealmSettingsLoginTab refresh={refresh} realm={realm} />
</Tab>
<Tab
title={<TabTitleText>{t("email")}</TabTitleText>}
data-testid="rs-email-tab"
{...route("email")}
>
<RealmSettingsEmailTab realm={realm} />
</Tab>
<Tab
title={<TabTitleText>{t("themes")}</TabTitleText>}
data-testid="rs-themes-tab"
{...route("themes")}
>
<RealmSettingsThemesTab realm={realm} save={save} />
</Tab>
<Tab
title={<TabTitleText>{t("realm-settings:keys")}</TabTitleText>}
data-testid="rs-keys-tab"
{...route("keys")}
>
<KeysTab />
</Tab>
<Tab
title={<TabTitleText>{t("events")}</TabTitleText>}
data-testid="rs-realm-events-tab"
{...route("events")}
>
<EventsTab />
</Tab>
<Tab
title={<TabTitleText>{t("localization")}</TabTitleText>}
data-testid="rs-localization-tab"
{...route("localization")}
>
<LocalizationTab
key={key}
refresh={refresh}
save={save}
realm={realm}
/>
</Tab>
<Tab
title={<TabTitleText>{t("securityDefences")}</TabTitleText>}
data-testid="rs-security-defenses-tab"
{...route("securityDefences")}
>
<SecurityDefenses realm={realm} save={save} />
</Tab>
<Tab
title={<TabTitleText>{t("realm-settings:sessions")}</TabTitleText>}
data-testid="rs-sessions-tab"
{...route("sessions")}
>
<RealmSettingsSessionsTab key={key} realm={realm} save={save} />
</Tab>
<Tab
title={<TabTitleText>{t("realm-settings:tokens")}</TabTitleText>}
data-testid="rs-tokens-tab"
{...route("tokens")}
>
<RealmSettingsTokensTab save={save} realm={realm} />
</Tab>
<Tab
title={
<TabTitleText>{t("realm-settings:clientPolicies")}</TabTitleText>
}
data-testid="rs-clientPolicies-tab"
{...route("clientPolicies")}
>
<RoutableTabs
mountOnEnter
defaultLocation={toClientPolicies({
realm: realmName,
tab: "profiles",
})}
>
<RealmSettingsGeneralTab
save={save}
reset={() => resetForm(realm)}
/>
</Tab>
<Tab
title={<TabTitleText>{t("login")}</TabTitleText>}
data-testid="rs-login-tab"
{...route("login")}
>
<RealmSettingsLoginTab refresh={refresh} realm={realm} />
</Tab>
<Tab
title={<TabTitleText>{t("email")}</TabTitleText>}
data-testid="rs-email-tab"
{...route("email")}
>
<RealmSettingsEmailTab realm={realm} />
</Tab>
<Tab
title={<TabTitleText>{t("themes")}</TabTitleText>}
data-testid="rs-themes-tab"
{...route("themes")}
>
<RealmSettingsThemesTab
save={save}
reset={() => resetForm(realm)}
/>
</Tab>
<Tab
title={<TabTitleText>{t("realm-settings:keys")}</TabTitleText>}
data-testid="rs-keys-tab"
{...route("keys")}
>
<KeysTab />
</Tab>
<Tab
title={<TabTitleText>{t("events")}</TabTitleText>}
data-testid="rs-realm-events-tab"
{...route("events")}
>
<EventsTab />
</Tab>
<Tab
title={<TabTitleText>{t("localization")}</TabTitleText>}
data-testid="rs-localization-tab"
{...route("localization")}
>
<LocalizationTab
key={key}
refresh={refresh}
save={save}
reset={() => resetForm(realm)}
realm={realm}
/>
</Tab>
<Tab
title={<TabTitleText>{t("securityDefences")}</TabTitleText>}
data-testid="rs-security-defenses-tab"
{...route("securityDefences")}
>
<SecurityDefences save={save} reset={() => resetForm(realm)} />
</Tab>
<Tab
title={
<TabTitleText>{t("realm-settings:sessions")}</TabTitleText>
}
data-testid="rs-sessions-tab"
{...route("sessions")}
>
<RealmSettingsSessionsTab key={key} realm={realm} save={save} />
</Tab>
<Tab
title={<TabTitleText>{t("realm-settings:tokens")}</TabTitleText>}
data-testid="rs-tokens-tab"
{...route("tokens")}
>
<RealmSettingsTokensTab
save={save}
realm={realm}
reset={() => resetForm(realm)}
/>
</Tab>
<Tab
title={
<TabTitleText>
{t("realm-settings:clientPolicies")}
</TabTitleText>
}
data-testid="rs-clientPolicies-tab"
{...route("clientPolicies")}
>
<RoutableTabs
mountOnEnter
defaultLocation={toClientPolicies({
realm: realmName,
tab: "profiles",
})}
<Tab
data-testid="rs-policies-clientProfiles-tab"
aria-label={t("clientProfilesSubTab")}
title={
<TabTitleText>
{t("profiles")}
<span className="kc-help-text">
<HelpItem
helpText="realm-settings:clientPoliciesProfilesHelpText"
fieldLabelId="realm-settings:clientPoliciesProfiles"
/>
</span>
</TabTitleText>
}
{...policiesRoute("profiles")}
>
<Tab
data-testid="rs-policies-clientProfiles-tab"
aria-label={t("clientProfilesSubTab")}
title={
<TabTitleText>
{t("profiles")}
<span className="kc-help-text">
<HelpItem
helpText="realm-settings:clientPoliciesProfilesHelpText"
fieldLabelId="realm-settings:clientPoliciesProfiles"
/>
</span>
</TabTitleText>
}
{...policiesRoute("profiles")}
>
<ProfilesTab />
</Tab>
<Tab
id="policies"
data-testid="rs-policies-clientPolicies-tab"
aria-label={t("clientPoliciesSubTab")}
{...policiesRoute("policies")}
title={
<TabTitleText>
{t("policies")}
<span className="kc-help-text">
<HelpItem
helpText="realm-settings:clientPoliciesPoliciesHelpText"
fieldLabelId="realm-settings:clientPoliciesPolicies"
/>
</span>
</TabTitleText>
}
>
<PoliciesTab />
</Tab>
</RoutableTabs>
</Tab>
{isFeatureEnabled(Feature.DeclarativeUserProfile) &&
userProfileEnabled === "true" && (
<Tab
title={
<TabTitleText>
{t("realm-settings:userProfile")}
</TabTitleText>
}
data-testid="rs-user-profile-tab"
{...route("userProfile")}
>
<UserProfileTab />
</Tab>
)}
<Tab
title={<TabTitleText>{t("userRegistration")}</TabTitleText>}
data-testid="rs-userRegistration-tab"
{...route("userRegistration")}
>
<UserRegistration />
</Tab>
</RoutableTabs>
</FormProvider>
<ProfilesTab />
</Tab>
<Tab
id="policies"
data-testid="rs-policies-clientPolicies-tab"
aria-label={t("clientPoliciesSubTab")}
{...policiesRoute("policies")}
title={
<TabTitleText>
{t("policies")}
<span className="kc-help-text">
<HelpItem
helpText="realm-settings:clientPoliciesPoliciesHelpText"
fieldLabelId="realm-settings:clientPoliciesPolicies"
/>
</span>
</TabTitleText>
}
>
<PoliciesTab />
</Tab>
</RoutableTabs>
</Tab>
{isFeatureEnabled(Feature.DeclarativeUserProfile) &&
realm.attributes?.userProfileEnabled === "true" && (
<Tab
title={
<TabTitleText>{t("realm-settings:userProfile")}</TabTitleText>
}
data-testid="rs-user-profile-tab"
{...route("userProfile")}
>
<UserProfileTab />
</Tab>
)}
<Tab
title={<TabTitleText>{t("userRegistration")}</TabTitleText>}
data-testid="rs-userRegistration-tab"
{...route("userRegistration")}
>
<UserRegistration />
</Tab>
</RoutableTabs>
</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,141 +43,143 @@ export const BruteForceDetection = ({
name: "permanentLockout",
});
const setupForm = () => convertToFormValues(realm, setValue);
useEffect(setupForm, []);
return (
<FormAccess role="manage-realm" isHorizontal onSubmit={handleSubmit(save)}>
<FormGroup
label={t("common:enabled")}
fieldId="bruteForceProtected"
hasNoPaddingTop
<FormProvider {...form}>
<FormAccess
role="manage-realm"
isHorizontal
onSubmit={handleSubmit(save)}
>
<Controller
name="bruteForceProtected"
defaultValue={false}
control={control}
render={({ onChange, value }) => (
<Switch
id="bruteForceProtected"
label={t("common:on")}
labelOff={t("common:off")}
isChecked={value}
onChange={onChange}
/>
)}
/>
</FormGroup>
<FormGroup
label={t("failureFactor")}
labelIcon={
<HelpItem
helpText="realm-settings-help:failureFactor"
fieldLabelId="realm-settings:failureFactor"
/>
}
fieldId="failureFactor"
style={enable ? {} : { display: "none" }}
>
<Controller
name="failureFactor"
defaultValue={0}
control={control}
rules={{ required: true }}
render={({ onChange, value }) => (
<NumberInput
type="text"
id="failureFactor"
value={value}
onPlus={() => onChange(value + 1)}
onMinus={() => onChange(value - 1)}
onChange={(event) =>
onChange(Number((event.target as HTMLInputElement).value))
}
/>
)}
/>
</FormGroup>
<FormGroup
label={t("permanentLockout")}
fieldId="permanentLockout"
hasNoPaddingTop
style={enable ? {} : { display: "none" }}
>
<Controller
name="permanentLockout"
defaultValue={false}
control={control}
render={({ onChange, value }) => (
<Switch
id="permanentLockout"
label={t("common:on")}
labelOff={t("common:off")}
isChecked={value}
onChange={onChange}
/>
)}
/>
</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" }}
/>
<FormGroup
label={t("quickLoginCheckMilliSeconds")}
labelIcon={
<HelpItem
helpText="realm-settings-help:quickLoginCheckMilliSeconds"
fieldLabelId="realm-settings:quickLoginCheckMilliSeconds"
/>
}
fieldId="quickLoginCheckMilliSeconds"
style={enable ? {} : { display: "none" }}
>
<Controller
name="quickLoginCheckMilliSeconds"
defaultValue={0}
control={control}
render={({ onChange, value }) => (
<NumberInput
type="text"
id="quickLoginCheckMilliSeconds"
value={value}
onPlus={() => onChange(value + 1)}
onMinus={() => onChange(value - 1)}
onChange={(event) =>
onChange(Number((event.target as HTMLInputElement).value))
}
/>
)}
/>
</FormGroup>
<Time
name="minimumQuickLoginWaitSeconds"
style={enable ? {} : { display: "none" }}
/>
<ActionGroup>
<Button
variant="primary"
type="submit"
data-testid="brute-force-tab-save"
isDisabled={!isDirty}
<FormGroup
label={t("common:enabled")}
fieldId="bruteForceProtected"
hasNoPaddingTop
>
{t("common:save")}
</Button>
<Button variant="link" onClick={reset}>
{t("common:revert")}
</Button>
</ActionGroup>
</FormAccess>
<Controller
name="bruteForceProtected"
defaultValue={false}
control={control}
render={({ onChange, value }) => (
<Switch
id="bruteForceProtected"
label={t("common:on")}
labelOff={t("common:off")}
isChecked={value}
onChange={onChange}
/>
)}
/>
</FormGroup>
{enable && (
<>
<FormGroup
label={t("failureFactor")}
labelIcon={
<HelpItem
helpText="realm-settings-help:failureFactor"
fieldLabelId="realm-settings:failureFactor"
/>
}
fieldId="failureFactor"
>
<Controller
name="failureFactor"
defaultValue={0}
control={control}
rules={{ required: true }}
render={({ onChange, value }) => (
<NumberInput
type="text"
id="failureFactor"
value={value}
onPlus={() => onChange(value + 1)}
onMinus={() => onChange(value - 1)}
onChange={(event) =>
onChange(Number((event.target as HTMLInputElement).value))
}
/>
)}
/>
</FormGroup>
<FormGroup
label={t("permanentLockout")}
fieldId="permanentLockout"
hasNoPaddingTop
>
<Controller
name="permanentLockout"
defaultValue={false}
control={control}
render={({ onChange, value }) => (
<Switch
id="permanentLockout"
label={t("common:on")}
labelOff={t("common:off")}
isChecked={value}
onChange={onChange}
/>
)}
/>
</FormGroup>
{!permanentLockout && (
<>
<Time name="waitIncrementSeconds" />
<Time name="maxFailureWaitSeconds" />
<Time name="maxDeltaTimeSeconds" />
</>
)}
<FormGroup
label={t("quickLoginCheckMilliSeconds")}
labelIcon={
<HelpItem
helpText="realm-settings-help:quickLoginCheckMilliSeconds"
fieldLabelId="realm-settings:quickLoginCheckMilliSeconds"
/>
}
fieldId="quickLoginCheckMilliSeconds"
>
<Controller
name="quickLoginCheckMilliSeconds"
defaultValue={0}
control={control}
render={({ onChange, value }) => (
<NumberInput
type="text"
id="quickLoginCheckMilliSeconds"
value={value}
onPlus={() => onChange(value + 1)}
onMinus={() => onChange(value - 1)}
onChange={(event) =>
onChange(Number((event.target as HTMLInputElement).value))
}
/>
)}
/>
</FormGroup>
<Time name="minimumQuickLoginWaitSeconds" />
</>
)}
<ActionGroup>
<Button
variant="primary"
type="submit"
data-testid="brute-force-tab-save"
isDisabled={!isDirty}
>
{t("common:save")}
</Button>
<Button variant="link" onClick={setupForm}>
{t("common:revert")}
</Button>
</ActionGroup>
</FormAccess>
</FormProvider>
);
};

View file

@ -1,75 +1,83 @@
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 (
<FormAccess
isHorizontal
role="manage-realm"
className="keycloak__security-defences__form"
onSubmit={handleSubmit(save)}
>
<HelpLinkTextInput
fieldName="browserSecurityHeaders.xFrameOptions"
url="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Frame-Options"
/>
<HelpLinkTextInput
fieldName="browserSecurityHeaders.contentSecurityPolicy"
url="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy"
/>
<HelpLinkTextInput
fieldName="browserSecurityHeaders.contentSecurityPolicyReportOnly"
url="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy-Report-Only"
/>
<HelpLinkTextInput
fieldName="browserSecurityHeaders.xContentTypeOptions"
url="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Content-Type-Options"
/>
<HelpLinkTextInput
fieldName="browserSecurityHeaders.xRobotsTag"
url="https://developers.google.com/search/docs/advanced/robots/robots_meta_tag"
/>
<HelpLinkTextInput
fieldName="browserSecurityHeaders.xXSSProtection"
url="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-XSS-Protection"
/>
<HelpLinkTextInput
fieldName="browserSecurityHeaders.strictTransportSecurity"
url="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Strict-Transport-Security"
/>
<FormProvider {...form}>
<FormAccess
isHorizontal
role="manage-realm"
className="keycloak__security-defences__form"
onSubmit={handleSubmit(save)}
>
<HelpLinkTextInput
fieldName="browserSecurityHeaders.xFrameOptions"
url="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Frame-Options"
/>
<HelpLinkTextInput
fieldName="browserSecurityHeaders.contentSecurityPolicy"
url="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy"
/>
<HelpLinkTextInput
fieldName="browserSecurityHeaders.contentSecurityPolicyReportOnly"
url="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy-Report-Only"
/>
<HelpLinkTextInput
fieldName="browserSecurityHeaders.xContentTypeOptions"
url="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Content-Type-Options"
/>
<HelpLinkTextInput
fieldName="browserSecurityHeaders.xRobotsTag"
url="https://developers.google.com/search/docs/advanced/robots/robots_meta_tag"
/>
<HelpLinkTextInput
fieldName="browserSecurityHeaders.xXSSProtection"
url="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-XSS-Protection"
/>
<HelpLinkTextInput
fieldName="browserSecurityHeaders.strictTransportSecurity"
url="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Strict-Transport-Security"
/>
<ActionGroup>
<Button
variant="primary"
type="submit"
data-testid="headers-form-tab-save"
isDisabled={!isDirty}
>
{t("common:save")}
</Button>
<Button variant="link" onClick={reset}>
{t("common:revert")}
</Button>
</ActionGroup>
</FormAccess>
<ActionGroup>
<Button
variant="primary"
type="submit"
data-testid="headers-form-tab-save"
isDisabled={!isDirty}
>
{t("common:save")}
</Button>
<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>