Added extra step to OIDC client wizzard (#4035)

This commit is contained in:
Erik Jan de Wit 2023-01-17 15:29:42 +01:00 committed by GitHub
parent 158f471bea
commit 2e174cd4e8
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 243 additions and 199 deletions

View file

@ -262,6 +262,7 @@ describe("Clients test", () => {
.fillClientData(clientId)
.continue()
.checkCapabilityConfigElements()
.continue()
.save();
commonPage
@ -304,7 +305,7 @@ describe("Clients test", () => {
.continue()
.checkClientIdRequiredMessage();
createClientPage.fillClientData("account").continue().save();
createClientPage.fillClientData("account").continue().continue().save();
// The error should inform about duplicated name/id
commonPage
@ -335,6 +336,7 @@ describe("Clients test", () => {
.clickOidcCibaGrant()
.clickServiceAccountRoles()
.clickStandardFlow()
.continue()
.save();
commonPage
@ -448,7 +450,11 @@ describe("Clients test", () => {
commonPage.sidebar().goToClients();
commonPage.tableToolbarUtils().createClient();
createClientPage.fillClientData(identicalClientId).continue().save();
createClientPage
.fillClientData(identicalClientId)
.continue()
.continue()
.save();
commonPage.masthead().closeAllAlertMessages();
commonPage.sidebar().goToClients();
@ -493,6 +499,7 @@ describe("Clients test", () => {
.selectClientType("openid-connect")
.fillClientData(client)
.continue()
.continue()
.save();
commonPage
.masthead()
@ -705,7 +712,7 @@ describe("Clients test", () => {
commonPage.sidebar().waitForPageLoad();
createClientPage.save();
createClientPage.continue().save();
commonPage
.masthead()
.checkNotificationMessage("Client created successfully");

View file

@ -130,6 +130,7 @@ describe("User Fed LDAP mapper tests", () => {
.selectClientType("openid-connect")
.fillClientData(clientName)
.continue()
.continue()
.save();
masthead.checkNotificationMessage(clientCreatedSuccess);

View file

@ -5,14 +5,11 @@ import { useTranslation } from "react-i18next";
import { FormAccess } from "../../components/form-access/FormAccess";
import { HelpItem } from "../../components/help-enabler/HelpItem";
import { KeycloakTextInput } from "../../components/keycloak-text-input/KeycloakTextInput";
import { MultiLineInput } from "../../components/multi-line-input/hook-form-v7/MultiLineInput";
import { useAccess } from "../../context/access/Access";
import { useRealm } from "../../context/realm-context/RealmContext";
import environment from "../../environment";
import { convertAttributeNameToForm } from "../../util";
import { SaveReset } from "../advanced/SaveReset";
import { FormFields } from "../ClientDetails";
import type { ClientSettingsProps } from "../ClientSettings";
import { LoginSettings } from "./LoginSettings";
export const AccessSettings = ({
client,
@ -21,15 +18,11 @@ export const AccessSettings = ({
}: ClientSettingsProps) => {
const { t } = useTranslation("clients");
const { register, watch } = useFormContext<FormFields>();
const { realm } = useRealm();
const { hasAccess } = useAccess();
const isManager = hasAccess("manage-clients") || client.access?.configure;
const protocol = watch("protocol");
const idpInitiatedSsoUrlName: string = watch(
"attributes.saml_idp_initiated_sso_url_name"
);
return (
<FormAccess
@ -37,154 +30,7 @@ export const AccessSettings = ({
fineGrainedAccess={client.access?.configure}
role="manage-clients"
>
{!client.bearerOnly && (
<>
<FormGroup
label={t("rootUrl")}
fieldId="kc-root-url"
labelIcon={
<HelpItem
helpText="clients-help:rootURL"
fieldLabelId="clients:rootUrl"
/>
}
>
<KeycloakTextInput
id="kc-root-url"
type="url"
{...register("rootUrl")}
/>
</FormGroup>
<FormGroup
label={t("homeURL")}
fieldId="kc-home-url"
labelIcon={
<HelpItem
helpText="clients-help:homeURL"
fieldLabelId="clients:homeURL"
/>
}
>
<KeycloakTextInput
id="kc-home-url"
type="url"
{...register("baseUrl")}
/>
</FormGroup>
<FormGroup
label={t("validRedirectUri")}
fieldId="kc-redirect"
labelIcon={
<HelpItem
helpText="clients-help:validRedirectURIs"
fieldLabelId="clients:validRedirectUri"
/>
}
>
<MultiLineInput
name="redirectUris"
aria-label={t("validRedirectUri")}
addButtonLabel="clients:addRedirectUri"
/>
</FormGroup>
<FormGroup
label={t("validPostLogoutRedirectUri")}
fieldId="kc-postLogoutRedirect"
labelIcon={
<HelpItem
helpText="clients-help:validPostLogoutRedirectURIs"
fieldLabelId="clients:validPostLogoutRedirectUri"
/>
}
>
<MultiLineInput
name={convertAttributeNameToForm(
"attributes.post.logout.redirect.uris"
)}
aria-label={t("validPostLogoutRedirectUri")}
addButtonLabel="clients:addPostLogoutRedirectUri"
stringify
/>
</FormGroup>
{protocol === "saml" && (
<>
<FormGroup
label={t("idpInitiatedSsoUrlName")}
fieldId="idpInitiatedSsoUrlName"
labelIcon={
<HelpItem
helpText="clients-help:idpInitiatedSsoUrlName"
fieldLabelId="clients:idpInitiatedSsoUrlName"
/>
}
helperText={
idpInitiatedSsoUrlName !== "" &&
t("idpInitiatedSsoUrlNameHelp", {
url: `${environment.authServerUrl}/realms/${realm}/protocol/saml/clients/${idpInitiatedSsoUrlName}`,
})
}
>
<KeycloakTextInput
id="idpInitiatedSsoUrlName"
data-testid="idpInitiatedSsoUrlName"
{...register("attributes.saml_idp_initiated_sso_url_name")}
/>
</FormGroup>
<FormGroup
label={t("idpInitiatedSsoRelayState")}
fieldId="idpInitiatedSsoRelayState"
labelIcon={
<HelpItem
helpText="clients-help:idpInitiatedSsoRelayState"
fieldLabelId="clients:idpInitiatedSsoRelayState"
/>
}
>
<KeycloakTextInput
id="idpInitiatedSsoRelayState"
data-testid="idpInitiatedSsoRelayState"
{...register("attributes.saml_idp_initiated_sso_relay_state")}
/>
</FormGroup>
<FormGroup
label={t("masterSamlProcessingUrl")}
fieldId="masterSamlProcessingUrl"
labelIcon={
<HelpItem
helpText="clients-help:masterSamlProcessingUrl"
fieldLabelId="clients:masterSamlProcessingUrl"
/>
}
>
<KeycloakTextInput
id="masterSamlProcessingUrl"
type="url"
data-testid="masterSamlProcessingUrl"
{...register("adminUrl")}
/>
</FormGroup>
</>
)}
{protocol !== "saml" && (
<FormGroup
label={t("webOrigins")}
fieldId="kc-web-origins"
labelIcon={
<HelpItem
helpText="clients-help:webOrigins"
fieldLabelId="clients:webOrigins"
/>
}
>
<MultiLineInput
name="webOrigins"
aria-label={t("webOrigins")}
addButtonLabel="clients:addWebOrigins"
/>
</FormGroup>
)}
</>
)}
{!client.bearerOnly && <LoginSettings protocol={protocol} />}
{protocol !== "saml" && (
<FormGroup
label={t("adminURL")}

View file

@ -0,0 +1,178 @@
import { FormGroup } from "@patternfly/react-core";
import { useFormContext } from "react-hook-form-v7";
import { useTranslation } from "react-i18next";
import { HelpItem } from "../../components/help-enabler/HelpItem";
import { KeycloakTextInput } from "../../components/keycloak-text-input/KeycloakTextInput";
import { MultiLineInput } from "../../components/multi-line-input/hook-form-v7/MultiLineInput";
import { useRealm } from "../../context/realm-context/RealmContext";
import environment from "../../environment";
import { convertAttributeNameToForm } from "../../util";
import { FormFields } from "../ClientDetails";
type LoginSettingsProps = {
protocol?: string;
};
export const LoginSettings = ({
protocol = "openid-connect",
}: LoginSettingsProps) => {
const { t } = useTranslation("clients");
const { register, watch } = useFormContext<FormFields>();
const { realm } = useRealm();
const idpInitiatedSsoUrlName: string = watch(
"attributes.saml_idp_initiated_sso_url_name"
);
return (
<>
<FormGroup
label={t("rootUrl")}
fieldId="kc-root-url"
labelIcon={
<HelpItem
helpText="clients-help:rootURL"
fieldLabelId="clients:rootUrl"
/>
}
>
<KeycloakTextInput
id="kc-root-url"
type="url"
{...register("rootUrl")}
/>
</FormGroup>
<FormGroup
label={t("homeURL")}
fieldId="kc-home-url"
labelIcon={
<HelpItem
helpText="clients-help:homeURL"
fieldLabelId="clients:homeURL"
/>
}
>
<KeycloakTextInput
id="kc-home-url"
type="url"
{...register("baseUrl")}
/>
</FormGroup>
<FormGroup
label={t("validRedirectUri")}
fieldId="kc-redirect"
labelIcon={
<HelpItem
helpText="clients-help:validRedirectURIs"
fieldLabelId="clients:validRedirectUri"
/>
}
>
<MultiLineInput
id="kc-redirect"
name="redirectUris"
aria-label={t("validRedirectUri")}
addButtonLabel="clients:addRedirectUri"
/>
</FormGroup>
<FormGroup
label={t("validPostLogoutRedirectUri")}
fieldId="kc-postLogoutRedirect"
labelIcon={
<HelpItem
helpText="clients-help:validPostLogoutRedirectURIs"
fieldLabelId="clients:validPostLogoutRedirectUri"
/>
}
>
<MultiLineInput
id="kc-postLogoutRedirect"
name={convertAttributeNameToForm(
"attributes.post.logout.redirect.uris"
)}
aria-label={t("validPostLogoutRedirectUri")}
addButtonLabel="clients:addPostLogoutRedirectUri"
stringify
/>
</FormGroup>
{protocol === "saml" && (
<>
<FormGroup
label={t("idpInitiatedSsoUrlName")}
fieldId="idpInitiatedSsoUrlName"
labelIcon={
<HelpItem
helpText="clients-help:idpInitiatedSsoUrlName"
fieldLabelId="clients:idpInitiatedSsoUrlName"
/>
}
helperText={
idpInitiatedSsoUrlName !== "" &&
t("idpInitiatedSsoUrlNameHelp", {
url: `${environment.authServerUrl}/realms/${realm}/protocol/saml/clients/${idpInitiatedSsoUrlName}`,
})
}
>
<KeycloakTextInput
id="idpInitiatedSsoUrlName"
data-testid="idpInitiatedSsoUrlName"
{...register("attributes.saml_idp_initiated_sso_url_name")}
/>
</FormGroup>
<FormGroup
label={t("idpInitiatedSsoRelayState")}
fieldId="idpInitiatedSsoRelayState"
labelIcon={
<HelpItem
helpText="clients-help:idpInitiatedSsoRelayState"
fieldLabelId="clients:idpInitiatedSsoRelayState"
/>
}
>
<KeycloakTextInput
id="idpInitiatedSsoRelayState"
data-testid="idpInitiatedSsoRelayState"
{...register("attributes.saml_idp_initiated_sso_relay_state")}
/>
</FormGroup>
<FormGroup
label={t("masterSamlProcessingUrl")}
fieldId="masterSamlProcessingUrl"
labelIcon={
<HelpItem
helpText="clients-help:masterSamlProcessingUrl"
fieldLabelId="clients:masterSamlProcessingUrl"
/>
}
>
<KeycloakTextInput
id="masterSamlProcessingUrl"
type="url"
data-testid="masterSamlProcessingUrl"
{...register("adminUrl")}
/>
</FormGroup>
</>
)}
{protocol !== "saml" && (
<FormGroup
label={t("webOrigins")}
fieldId="kc-web-origins"
labelIcon={
<HelpItem
helpText="clients-help:webOrigins"
fieldLabelId="clients:webOrigins"
/>
}
>
<MultiLineInput
id="kc-web-origins"
name="webOrigins"
aria-label={t("webOrigins")}
addButtonLabel="clients:addWebOrigins"
/>
</FormGroup>
)}
</>
);
};

View file

@ -1,4 +1,3 @@
import type ClientRepresentation from "@keycloak/keycloak-admin-client/lib/defs/clientRepresentation";
import {
AlertVariant,
Button,
@ -11,7 +10,9 @@ import { useState } from "react";
import { FormProvider, useForm } from "react-hook-form-v7";
import { useTranslation } from "react-i18next";
import { useNavigate } from "react-router-dom-v5-compat";
import { useAlerts } from "../../components/alert/Alerts";
import { FormAccess } from "../../components/form-access/FormAccess";
import { ViewHeader } from "../../components/view-header/ViewHeader";
import { useAdminClient } from "../../context/auth/AdminClient";
import { useRealm } from "../../context/realm-context/RealmContext";
@ -21,6 +22,7 @@ import { toClient } from "../routes/Client";
import { toClients } from "../routes/Clients";
import { CapabilityConfig } from "./CapabilityConfig";
import { GeneralSettings } from "./GeneralSettings";
import { LoginSettings } from "./LoginSettings";
export default function NewClientForm() {
const { t } = useTranslation("clients");
@ -28,25 +30,32 @@ export default function NewClientForm() {
const { adminClient } = useAdminClient();
const navigate = useNavigate();
const [showCapabilityConfig, setShowCapabilityConfig] = useState(false);
const [client, setClient] = useState<ClientRepresentation>({
protocol: "openid-connect",
clientId: "",
name: "",
description: "",
publicClient: true,
authorizationServicesEnabled: false,
serviceAccountsEnabled: false,
implicitFlowEnabled: false,
directAccessGrantsEnabled: true,
standardFlowEnabled: true,
frontchannelLogout: true,
});
const [step, setStep] = useState(0);
const { addAlert, addError } = useAlerts();
const methods = useForm<FormFields>({ defaultValues: client });
const protocol = methods.watch("protocol");
const form = useForm<FormFields>({
defaultValues: {
protocol: "openid-connect",
clientId: "",
name: "",
description: "",
publicClient: true,
authorizationServicesEnabled: false,
serviceAccountsEnabled: false,
implicitFlowEnabled: false,
directAccessGrantsEnabled: true,
standardFlowEnabled: true,
frontchannelLogout: true,
attributes: {
saml_idp_initiated_sso_url_name: "",
},
},
});
const { getValues, watch, trigger } = form;
const protocol = watch("protocol");
const save = async () => {
const client = convertFormValuesToObject(getValues());
try {
const newClient = await adminClient.clients.create({
...client,
@ -60,35 +69,29 @@ export default function NewClientForm() {
};
const forward = async (onNext?: () => void) => {
if (await methods.trigger()) {
setClient({
...client,
...convertFormValuesToObject(methods.getValues()),
});
if (!isFinalStep()) {
setShowCapabilityConfig(true);
}
onNext?.();
if (!(await trigger())) {
return;
}
if (!isFinalStep()) {
setStep(step + 1);
}
onNext?.();
};
const isFinalStep = () =>
showCapabilityConfig || protocol !== "openid-connect";
protocol === "openid-connect" ? step === 2 : step === 1;
const back = () => {
setClient({ ...client, ...convertFormValuesToObject(methods.getValues()) });
methods.reset({
...client,
...convertFormValuesToObject(methods.getValues()),
});
setShowCapabilityConfig(false);
setStep(step - 1);
};
const onGoToStep = (newStep: { id?: string | number }) => {
if (newStep.id === "generalSettings") {
back();
setStep(0);
} else if (newStep.id === "capabilityConfig") {
setStep(1);
} else {
forward();
setStep(2);
}
};
@ -135,7 +138,7 @@ export default function NewClientForm() {
subKey="clients:clientsExplain"
/>
<PageSection variant="light">
<FormProvider {...methods}>
<FormProvider {...form}>
<Wizard
onClose={() => navigate(toClients({ realm }))}
navAriaLabel={`${title} steps`}
@ -146,17 +149,26 @@ export default function NewClientForm() {
name: t("generalSettings"),
component: <GeneralSettings />,
},
...(showCapabilityConfig
...(protocol !== "saml"
? [
{
id: "capabilityConfig",
name: t("capabilityConfig"),
component: (
<CapabilityConfig protocol={client.protocol} />
),
component: <CapabilityConfig protocol={protocol} />,
canJumpTo: step >= 1,
},
]
: []),
{
id: "loginSettings",
name: t("loginSettings"),
component: (
<FormAccess isHorizontal role="manage-clients">
<LoginSettings protocol={protocol} />
</FormAccess>
),
canJumpTo: step >= 1,
},
]}
footer={<Footer />}
onSave={save}