fixes realm save (#633)

* fixes realm save

currently save on realm screen didn't work as the email settings where required. Fixed this by changing email to be it's own section. Also email section was using the wrong attributes therefor the changes where not reflected in the old console

* fix empty realmComponent array

* fixed password help text
This commit is contained in:
Erik Jan de Wit 2021-05-27 09:01:55 +02:00 committed by GitHub
parent f278f34fe5
commit bbc177abb8
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 158 additions and 133 deletions

View file

@ -1,8 +1,9 @@
import React, { useState } from "react"; import React, { useEffect, useState } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { Controller, useFormContext, UseFormMethods } from "react-hook-form"; import { Controller, useForm } from "react-hook-form";
import { import {
ActionGroup, ActionGroup,
AlertVariant,
Button, Button,
Checkbox, Checkbox,
FormGroup, FormGroup,
@ -15,26 +16,60 @@ import type RealmRepresentation from "keycloak-admin/lib/defs/realmRepresentatio
import { FormAccess } from "../components/form-access/FormAccess"; import { FormAccess } from "../components/form-access/FormAccess";
import { HelpItem } from "../components/help-enabler/HelpItem"; import { HelpItem } from "../components/help-enabler/HelpItem";
import { FormPanel } from "../components/scroll-form/FormPanel"; import { FormPanel } from "../components/scroll-form/FormPanel";
import { emailRegexPattern } from "../util";
import { useAdminClient } from "../context/auth/AdminClient";
import { useAlerts } from "../components/alert/Alerts";
import { useRealm } from "../context/realm-context/RealmContext";
import "./RealmSettingsSection.css"; import "./RealmSettingsSection.css";
import { emailRegexPattern } from "../util";
export type UserFormProps = {
form: UseFormMethods<RealmRepresentation>;
};
type RealmSettingsEmailTabProps = { type RealmSettingsEmailTabProps = {
save: (realm: RealmRepresentation) => void; realm: RealmRepresentation;
reset: () => void;
}; };
export const RealmSettingsEmailTab = ({ export const RealmSettingsEmailTab = ({
save, realm: initialRealm,
reset,
}: RealmSettingsEmailTabProps) => { }: RealmSettingsEmailTabProps) => {
const { t } = useTranslation("realm-settings"); const { t } = useTranslation("realm-settings");
const [isAuthenticationEnabled, setAuthenticationEnabled] = useState(""); const adminClient = useAdminClient();
const { register, control, handleSubmit, errors } = useFormContext(); const { realm: realmName } = useRealm();
const { addAlert } = useAlerts();
const [isAuthenticationEnabled, setAuthenticationEnabled] = useState("true");
const [realm, setRealm] = useState(initialRealm);
const {
register,
control,
handleSubmit,
errors,
setValue,
reset: resetForm,
} = useForm<RealmRepresentation>();
useEffect(() => {
reset();
}, [realm]);
const save = async (form: RealmRepresentation) => {
try {
const savedRealm = { ...realm, ...form };
await adminClient.realms.update({ realm: realmName }, savedRealm);
setRealm(savedRealm);
addAlert(t("saveSuccess"), AlertVariant.success);
} catch (error) {
addAlert(
t("saveError", { error: error.response?.data?.errorMessage || error }),
AlertVariant.danger
);
}
};
const reset = () => {
if (realm) {
resetForm(realm);
Object.entries(realm).map((entry) => setValue(entry[0], entry[1]));
}
};
return ( return (
<> <>
@ -50,23 +85,20 @@ export const RealmSettingsEmailTab = ({
label={t("from")} label={t("from")}
fieldId="kc-display-name" fieldId="kc-display-name"
isRequired isRequired
validated={ validated={errors.smtpServer?.from ? "error" : "default"}
errors.attributes?.from?.type === "pattern"
? "error"
: "default"
}
helperTextInvalid={t("users:emailInvalid")} helperTextInvalid={t("users:emailInvalid")}
> >
<TextInput <TextInput
type="email" type="email"
id="kc-sender-email-address" id="kc-sender-email-address"
data-testid="sender-email-address" data-testid="sender-email-address"
name="attributes.from" name="smtpServer.from"
ref={register({ ref={register({
pattern: emailRegexPattern, pattern: emailRegexPattern,
required: true, required: true,
})} })}
placeholder="Sender email address" placeholder="Sender email address"
validated={errors.smtpServer?.from ? "error" : "default"}
/> />
</FormGroup> </FormGroup>
<FormGroup <FormGroup
@ -84,7 +116,7 @@ export const RealmSettingsEmailTab = ({
type="text" type="text"
id="kc-from-display-name" id="kc-from-display-name"
data-testid="from-display-name" data-testid="from-display-name"
name="attributes.fromDisplayName" name="smtpServer.fromDisplayName"
ref={register} ref={register}
placeholder="Display name for Sender email address" placeholder="Display name for Sender email address"
/> />
@ -92,21 +124,18 @@ export const RealmSettingsEmailTab = ({
<FormGroup <FormGroup
label={t("replyTo")} label={t("replyTo")}
fieldId="kc-reply-to" fieldId="kc-reply-to"
validated={ validated={errors.smtpServer?.replyTo ? "error" : "default"}
errors.attributes?.replyTo?.type === "pattern"
? "error"
: "default"
}
helperTextInvalid={t("users:emailInvalid")} helperTextInvalid={t("users:emailInvalid")}
> >
<TextInput <TextInput
type="email" type="email"
id="kc-reply-to" id="kc-reply-to"
name="attributes.replyTo" name="smtpServer.replyTo"
ref={register({ ref={register({
pattern: emailRegexPattern, pattern: emailRegexPattern,
})} })}
placeholder="Reply to email address" placeholder="Reply to email address"
validated={errors.smtpServer?.replyTo ? "error" : "default"}
/> />
</FormGroup> </FormGroup>
<FormGroup <FormGroup
@ -123,7 +152,7 @@ export const RealmSettingsEmailTab = ({
<TextInput <TextInput
type="text" type="text"
id="kc-reply-to-display-name" id="kc-reply-to-display-name"
name="attributes.replyToDisplayName" name="smtpServer.replyToDisplayName"
ref={register} ref={register}
placeholder='Display name for "reply to" email address' placeholder='Display name for "reply to" email address'
/> />
@ -142,7 +171,7 @@ export const RealmSettingsEmailTab = ({
<TextInput <TextInput
type="text" type="text"
id="kc-envelope-from" id="kc-envelope-from"
name="attributes.envelopeFrom" name="smtpServer.envelopeFrom"
ref={register} ref={register}
placeholder="Sender envelope email address" placeholder="Sender envelope email address"
/> />
@ -159,34 +188,40 @@ export const RealmSettingsEmailTab = ({
className="pf-u-mt-lg" className="pf-u-mt-lg"
onSubmit={handleSubmit(save)} onSubmit={handleSubmit(save)}
> >
<FormGroup label={t("host")} fieldId="kc-host" isRequired> <FormGroup
label={t("host")}
fieldId="kc-host"
isRequired
validated={errors.smtpServer?.host ? "error" : "default"}
helperTextInvalid={t("common:required")}
>
<TextInput <TextInput
type="text" type="text"
id="kc-host" id="kc-host"
name="attributes.host" name="smtpServer.host"
ref={register({ required: true })} ref={register({ required: true })}
placeholder="SMTP host" placeholder="SMTP host"
validated={errors.smtpServer?.host ? "error" : "default"}
/> />
</FormGroup> </FormGroup>
<FormGroup label={t("port")} fieldId="kc-port"> <FormGroup label={t("port")} fieldId="kc-port">
<TextInput <TextInput
type="text" type="text"
id="kc-port" id="kc-port"
name="attributes.port" name="smtpServer.port"
ref={register} ref={register}
placeholder="SMTP port (defaults to 25)" placeholder="SMTP port (defaults to 25)"
/> />
</FormGroup> </FormGroup>
<FormGroup label={t("encryption")} fieldId="kc-html-display-name"> <FormGroup label={t("encryption")} fieldId="kc-html-display-name">
<Controller <Controller
name="attributes.enableSsl" name="smtpServer.ssl"
control={control} control={control}
defaultValue="false" defaultValue="false"
render={({ onChange, value }) => ( render={({ onChange, value }) => (
<Checkbox <Checkbox
id="kc-enable-ssl" id="kc-enable-ssl"
data-testid="enable-ssl" data-testid="enable-ssl"
name="attributes.enableSsl"
label={t("enableSSL")} label={t("enableSSL")}
ref={register} ref={register}
isChecked={value === "true"} isChecked={value === "true"}
@ -195,14 +230,13 @@ export const RealmSettingsEmailTab = ({
)} )}
/> />
<Controller <Controller
name="attributes.enableStartTls" name="smtpServer.starttls"
control={control} control={control}
defaultValue="false" defaultValue="false"
render={({ onChange, value }) => ( render={({ onChange, value }) => (
<Checkbox <Checkbox
id="kc-enable-start-tls" id="kc-enable-start-tls"
data-testid="enable-start-tls" data-testid="enable-start-tls"
name="attributes.startTls"
label={t("enableStartTLS")} label={t("enableStartTLS")}
ref={register} ref={register}
isChecked={value === "true"} isChecked={value === "true"}
@ -217,7 +251,7 @@ export const RealmSettingsEmailTab = ({
fieldId="kc-authentication" fieldId="kc-authentication"
> >
<Controller <Controller
name="attributes.authentication" name="smtpServer.authentication"
control={control} control={control}
defaultValue="true" defaultValue="true"
render={({ onChange, value }) => ( render={({ onChange, value }) => (
@ -240,24 +274,29 @@ export const RealmSettingsEmailTab = ({
<FormGroup <FormGroup
label={t("username")} label={t("username")}
fieldId="kc-username" fieldId="kc-username"
isRequired={isAuthenticationEnabled === "true"} isRequired
validated={errors.smtpServer?.user ? "error" : "default"}
helperTextInvalid={t("common:required")}
> >
<TextInput <TextInput
type="text" type="text"
id="kc-username" id="kc-username"
data-testid="username-input" data-testid="username-input"
name="attributes.loginUsername" name="smtpServer.user"
ref={register({ required: true })} ref={register({ required: true })}
placeholder="Login username" placeholder="Login username"
validated={errors.smtpServer?.user ? "error" : "default"}
/> />
</FormGroup> </FormGroup>
<FormGroup <FormGroup
label={t("password")} label={t("password")}
fieldId="kc-username" fieldId="kc-username"
isRequired={isAuthenticationEnabled === "true"} isRequired
validated={errors.smtpServer?.password ? "error" : "default"}
helperTextInvalid={t("common:required")}
labelIcon={ labelIcon={
<HelpItem <HelpItem
helpText="realm-settings-help:frontendUrl" helpText="realm-settings-help:password"
forLabel={t("password")} forLabel={t("password")}
forID="kc-password" forID="kc-password"
/> />
@ -267,9 +306,12 @@ export const RealmSettingsEmailTab = ({
type="password" type="password"
id="kc-password" id="kc-password"
data-testid="password-input" data-testid="password-input"
name="attributes.loginPassword" name="smtpServer.password"
ref={register} ref={register({ required: true })}
placeholder="Login password" placeholder="Login password"
validated={
errors.smtpServer?.password ? "error" : "default"
}
/> />
</FormGroup> </FormGroup>
</> </>

View file

@ -2,42 +2,51 @@ import React, { useState } from "react";
import { useHistory, useRouteMatch } from "react-router-dom"; import { useHistory, useRouteMatch } from "react-router-dom";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { Button, ButtonVariant, PageSection } from "@patternfly/react-core"; import { Button, ButtonVariant, PageSection } from "@patternfly/react-core";
import { cellWidth } from "@patternfly/react-table";
import type { KeyMetadataRepresentation } from "keycloak-admin/lib/defs/keyMetadataRepresentation"; import type { KeyMetadataRepresentation } from "keycloak-admin/lib/defs/keyMetadataRepresentation";
import type ComponentRepresentation from "keycloak-admin/lib/defs/componentRepresentation";
import { ListEmptyState } from "../components/list-empty-state/ListEmptyState"; import { ListEmptyState } from "../components/list-empty-state/ListEmptyState";
import { KeycloakDataTable } from "../components/table-toolbar/KeycloakDataTable"; import { KeycloakDataTable } from "../components/table-toolbar/KeycloakDataTable";
import { useConfirmDialog } from "../components/confirm-dialog/ConfirmDialog"; import { useConfirmDialog } from "../components/confirm-dialog/ConfirmDialog";
import { emptyFormatter } from "../util"; import { emptyFormatter } from "../util";
import type ComponentRepresentation from "keycloak-admin/lib/defs/componentRepresentation"; import { useAdminClient } from "../context/auth/AdminClient";
import { useRealm } from "../context/realm-context/RealmContext";
import "./RealmSettingsSection.css"; import "./RealmSettingsSection.css";
import { cellWidth } from "@patternfly/react-table";
type KeyData = KeyMetadataRepresentation & { type KeyData = KeyMetadataRepresentation & {
provider?: string; provider?: string;
type?: string;
}; };
type KeysTabInnerProps = { type KeysListTabProps = {
keys: KeyData[]; realmComponents: ComponentRepresentation[];
}; };
export const KeysTabInner = ({ keys }: KeysTabInnerProps) => { export const KeysListTab = ({ realmComponents }: KeysListTabProps) => {
const { t } = useTranslation("roles"); const { t } = useTranslation("roles");
const history = useHistory(); const history = useHistory();
const { url } = useRouteMatch(); const { url } = useRouteMatch();
const [key, setKey] = useState(0);
const refresh = () => setKey(new Date().getTime());
const [publicKey, setPublicKey] = useState(""); const [publicKey, setPublicKey] = useState("");
const [certificate, setCertificate] = useState(""); const [certificate, setCertificate] = useState("");
const loader = async () => { const adminClient = useAdminClient();
return keys; const { realm: realmName } = useRealm();
};
React.useEffect(() => { const loader = async () => {
refresh(); const keysMetaData = await adminClient.realms.getKeys({
}, [keys]); realm: realmName,
});
const keys = keysMetaData.keys;
return keys?.map((key) => {
const provider = realmComponents.find(
(component: ComponentRepresentation) => component.id === key.providerId
);
return { ...key, provider: provider?.name } as KeyData;
})!;
};
const [togglePublicKeyDialog, PublicKeyDialog] = useConfirmDialog({ const [togglePublicKeyDialog, PublicKeyDialog] = useConfirmDialog({
titleKey: t("realm-settings:publicKeys").slice(0, -1), titleKey: t("realm-settings:publicKeys").slice(0, -1),
@ -115,7 +124,6 @@ export const KeysTabInner = ({ keys }: KeysTabInnerProps) => {
<PublicKeyDialog /> <PublicKeyDialog />
<CertificateDialog /> <CertificateDialog />
<KeycloakDataTable <KeycloakDataTable
key={key}
isNotCompact={true} isNotCompact={true}
loader={loader} loader={loader}
ariaLabelKey="realm-settings:keysList" ariaLabelKey="realm-settings:keysList"
@ -166,23 +174,3 @@ export const KeysTabInner = ({ keys }: KeysTabInnerProps) => {
</> </>
); );
}; };
type KeysProps = {
keys: KeyMetadataRepresentation[];
realmComponents: ComponentRepresentation[];
};
export const KeysListTab = ({ keys, realmComponents, ...props }: KeysProps) => {
return (
<KeysTabInner
keys={keys?.map((key) => {
const provider = realmComponents.find(
(component: ComponentRepresentation) =>
component.id === key.providerId
);
return { ...key, provider: provider?.name };
})}
{...props}
/>
);
};

View file

@ -19,12 +19,13 @@ import {
ToolbarGroup, ToolbarGroup,
ToolbarItem, ToolbarItem,
} from "@patternfly/react-core"; } from "@patternfly/react-core";
import { SearchIcon } from "@patternfly/react-icons";
import type { KeyMetadataRepresentation } from "keycloak-admin/lib/defs/keyMetadataRepresentation"; import type { KeyMetadataRepresentation } from "keycloak-admin/lib/defs/keyMetadataRepresentation";
import type ComponentRepresentation from "keycloak-admin/lib/defs/componentRepresentation"; import type ComponentRepresentation from "keycloak-admin/lib/defs/componentRepresentation";
import type ComponentTypeRepresentation from "keycloak-admin/lib/defs/componentTypeRepresentation";
import "./RealmSettingsSection.css"; import "./RealmSettingsSection.css";
import type ComponentTypeRepresentation from "keycloak-admin/lib/defs/componentTypeRepresentation";
import { SearchIcon } from "@patternfly/react-icons";
type ComponentData = KeyMetadataRepresentation & { type ComponentData = KeyMetadataRepresentation & {
providerDescription?: string; providerDescription?: string;
@ -33,8 +34,6 @@ type ComponentData = KeyMetadataRepresentation & {
type KeysTabInnerProps = { type KeysTabInnerProps = {
components: ComponentData[]; components: ComponentData[];
realmComponents: ComponentRepresentation[];
keyProviderComponentTypes: ComponentTypeRepresentation[];
}; };
export const KeysTabInner = ({ components }: KeysTabInnerProps) => { export const KeysTabInner = ({ components }: KeysTabInnerProps) => {
@ -46,7 +45,7 @@ export const KeysTabInner = ({ components }: KeysTabInnerProps) => {
[] []
); );
const itemIds = components.map((item, idx) => "data" + idx); const itemIds = components.map((_, idx) => "data" + idx);
const [itemOrder, setItemOrder] = useState<string[]>([]); const [itemOrder, setItemOrder] = useState<string[]>([]);
@ -222,7 +221,6 @@ export const KeysTabInner = ({ components }: KeysTabInnerProps) => {
type KeysProps = { type KeysProps = {
components: ComponentRepresentation[]; components: ComponentRepresentation[];
realmComponents: ComponentRepresentation[];
keyProviderComponentTypes: ComponentTypeRepresentation[]; keyProviderComponentTypes: ComponentTypeRepresentation[];
}; };
@ -240,7 +238,6 @@ export const KeysProviderTab = ({
); );
return { ...component, providerDescription: provider?.helpText }; return { ...component, providerDescription: provider?.helpText };
})} })}
keyProviderComponentTypes={keyProviderComponentTypes}
{...props} {...props}
/> />
); );

View file

@ -27,7 +27,6 @@ import { PartialImportDialog } from "./PartialImport";
import { RealmSettingsThemesTab } from "./ThemesTab"; import { RealmSettingsThemesTab } from "./ThemesTab";
import { RealmSettingsEmailTab } from "./EmailTab"; import { RealmSettingsEmailTab } from "./EmailTab";
import { KeysListTab } from "./KeysListTab"; import { KeysListTab } from "./KeysListTab";
import type { KeyMetadataRepresentation } from "keycloak-admin/lib/defs/keyMetadataRepresentation";
import type ComponentRepresentation from "keycloak-admin/lib/defs/componentRepresentation"; import type ComponentRepresentation from "keycloak-admin/lib/defs/componentRepresentation";
import { KeysProviderTab } from "./KeysProvidersTab"; import { KeysProviderTab } from "./KeysProvidersTab";
import { useServerInfo } from "../context/server-info/ServerInfoProvider"; import { useServerInfo } from "../context/server-info/ServerInfoProvider";
@ -128,43 +127,40 @@ export const RealmSettingsSection = () => {
const { realm: realmName } = useRealm(); const { realm: realmName } = useRealm();
const { addAlert } = useAlerts(); const { addAlert } = useAlerts();
const form = useForm(); const form = useForm();
const { control, getValues, setValue } = form; const { control, getValues, setValue, reset: resetForm } = form;
const [realm, setRealm] = useState<RealmRepresentation>(); const [realm, setRealm] = useState<RealmRepresentation>();
const [activeTab2, setActiveTab2] = useState(0); const [activeTab, setActiveTab] = useState(0);
const [keys, setKeys] = useState<KeyMetadataRepresentation[]>([]);
const [realmComponents, setRealmComponents] = useState< const [realmComponents, setRealmComponents] = useState<
ComponentRepresentation[] ComponentRepresentation[]
>([]); >();
const kpComponentTypes = useServerInfo().componentTypes![ const kpComponentTypes = useServerInfo().componentTypes![
"org.keycloak.keys.KeyProvider" "org.keycloak.keys.KeyProvider"
]; ];
useFetch( useFetch(
() => adminClient.realms.findOne({ realm: realmName }), async () => {
(realm) => { const realm = await adminClient.realms.findOne({ realm: realmName });
setupForm(realm); const realmComponents = await adminClient.components.find({
setRealm(realm); type: "org.keycloak.keys.KeyProvider",
realm: realmName,
});
return { realm, realmComponents };
},
(result) => {
setRealm(result.realm);
setRealmComponents(result.realmComponents);
}, },
[] []
); );
useEffect(() => { useEffect(() => {
const update = async () => { if (realm) setupForm(realm);
const keysMetaData = await adminClient.realms.getKeys({ }, [realm]);
realm: realmName,
});
setKeys(keysMetaData.keys!);
const realmComponents = await adminClient.components.find({
type: "org.keycloak.keys.KeyProvider",
realm: realmName,
});
setRealmComponents(realmComponents);
};
setTimeout(update, 100);
}, []);
const setupForm = (realm: RealmRepresentation) => { const setupForm = (realm: RealmRepresentation) => {
resetForm(realm);
Object.entries(realm).map((entry) => setValue(entry[0], entry[1])); Object.entries(realm).map((entry) => setValue(entry[0], entry[1]));
}; };
@ -174,7 +170,10 @@ export const RealmSettingsSection = () => {
setRealm(realm); setRealm(realm);
addAlert(t("saveSuccess"), AlertVariant.success); addAlert(t("saveSuccess"), AlertVariant.success);
} catch (error) { } catch (error) {
addAlert(t("saveError", { error }), AlertVariant.danger); addAlert(
t("saveError", { error: error.response?.data?.errorMessage || error }),
AlertVariant.danger
);
} }
}; };
@ -218,10 +217,7 @@ export const RealmSettingsSection = () => {
title={<TabTitleText>{t("realm-settings:email")}</TabTitleText>} title={<TabTitleText>{t("realm-settings:email")}</TabTitleText>}
data-testid="rs-email-tab" data-testid="rs-email-tab"
> >
<RealmSettingsEmailTab {realm && <RealmSettingsEmailTab realm={realm} />}
save={save}
reset={() => setupForm(realm!)}
/>
</Tab> </Tab>
<Tab <Tab
eventKey="themes" eventKey="themes"
@ -238,29 +234,30 @@ export const RealmSettingsSection = () => {
title={<TabTitleText>{t("realm-settings:keys")}</TabTitleText>} title={<TabTitleText>{t("realm-settings:keys")}</TabTitleText>}
data-testid="rs-keys-tab" data-testid="rs-keys-tab"
> >
<Tabs {realmComponents && (
activeKey={activeTab2} <Tabs
onSelect={(_, key) => setActiveTab2(key as number)} activeKey={activeTab}
> onSelect={(_, key) => setActiveTab(key as number)}
<Tab
id="setup"
eventKey={0}
title={<TabTitleText>{t("keysList")}</TabTitleText>}
> >
<KeysListTab keys={keys} realmComponents={realmComponents} /> <Tab
</Tab> id="setup"
<Tab eventKey={0}
id="evaluate" title={<TabTitleText>{t("keysList")}</TabTitleText>}
eventKey={1} >
title={<TabTitleText>{t("providers")}</TabTitleText>} <KeysListTab realmComponents={realmComponents} />
> </Tab>
<KeysProviderTab <Tab
components={realmComponents} id="evaluate"
realmComponents={realmComponents} eventKey={1}
keyProviderComponentTypes={kpComponentTypes} title={<TabTitleText>{t("providers")}</TabTitleText>}
/> >
</Tab> <KeysProviderTab
</Tabs> components={realmComponents}
keyProviderComponentTypes={kpComponentTypes}
/>
</Tab>
</Tabs>
)}
</Tab> </Tab>
</KeycloakTabs> </KeycloakTabs>
</FormProvider> </FormProvider>

View file

@ -3,6 +3,7 @@
"fromDisplayName": "A user-friendly name for the 'From' address (optional).", "fromDisplayName": "A user-friendly name for the 'From' address (optional).",
"replyToDisplayName": "A user-friendly name for the 'Reply-To' address (optional).", "replyToDisplayName": "A user-friendly name for the 'Reply-To' address (optional).",
"envelopeFrom": "An email address used for bounces (optional).", "envelopeFrom": "An email address used for bounces (optional).",
"password": "SMTP password. This field is able to obtain its value from vault, use ${vault.ID} format.",
"frontendUrl": "Set the frontend URL for the realm. Use in combination with the default hostname provider to override the base URL for frontend requests for a specific realm.", "frontendUrl": "Set the frontend URL for the realm. Use in combination with the default hostname provider to override the base URL for frontend requests for a specific realm.",
"requireSsl": "Is HTTPS required? 'None' means HTTPS is not required for any client IP address. 'External requests' means localhost and private IP addresses can access without HTTPS. 'All requests' means HTTPS is required for all IP addresses.", "requireSsl": "Is HTTPS required? 'None' means HTTPS is not required for any client IP address. 'External requests' means localhost and private IP addresses can access without HTTPS. 'All requests' means HTTPS is required for all IP addresses.",
"userManagedAccess": "If enabled, users are allowed to manage their resources and permissions using the Account Management Console.", "userManagedAccess": "If enabled, users are allowed to manage their resources and permissions using the Account Management Console.",