Add UI and partial functionality for creating SAML identity providers (#944)
* fix OIDC string issue * preliminary UI changes * saml create new fields added * use new routing for saml * add msg strings * most fields added * add reqAuthnConstraints section * final help strings and options * add cypress tests * fix legacy cypress cleanup tasks * fix selects and hardcoded strings * most PR review comments incorporated * PR review edits * more PR review comments
This commit is contained in:
parent
c5abf8349e
commit
7efa03dc3f
14 changed files with 1042 additions and 42 deletions
|
@ -14,6 +14,16 @@ describe("Identity provider test", () => {
|
|||
const masthead = new Masthead();
|
||||
const listingPage = new ListingPage();
|
||||
const createProviderPage = new CreateProviderPage();
|
||||
const createSuccessMsg = "Identity provider successfully created";
|
||||
const changeSuccessMsg =
|
||||
"Successfully changed display order of identity providers";
|
||||
const deletePrompt = "Delete provider?";
|
||||
const deleteSuccessMsg = "Provider successfully deleted";
|
||||
|
||||
const keycloakServer = Cypress.env("KEYCLOAK_SERVER");
|
||||
const discoveryUrl = `${keycloakServer}/auth/realms/master/.well-known/openid-configuration`;
|
||||
const authorizationUrl = `${keycloakServer}/auth/realms/master/protocol/openid-connect/auth`;
|
||||
const ssoServiceUrl = `${keycloakServer}/auth/realms/sso`;
|
||||
|
||||
describe("Identity provider creation", () => {
|
||||
const identityProviderName = "github";
|
||||
|
@ -33,9 +43,7 @@ describe("Identity provider test", () => {
|
|||
.clickAdd()
|
||||
.checkClientIdRequiredMessage(true);
|
||||
createProviderPage.fill(identityProviderName, "123").clickAdd();
|
||||
masthead.checkNotificationMessage(
|
||||
"Identity provider successfully created"
|
||||
);
|
||||
masthead.checkNotificationMessage(createSuccessMsg);
|
||||
|
||||
sidebarPage.goToIdentityProviders();
|
||||
listingPage.itemExist(identityProviderName);
|
||||
|
@ -44,9 +52,9 @@ describe("Identity provider test", () => {
|
|||
it("should delete provider", () => {
|
||||
const modalUtils = new ModalUtils();
|
||||
listingPage.deleteItem(identityProviderName);
|
||||
modalUtils.checkModalTitle("Delete provider?").confirmModal();
|
||||
modalUtils.checkModalTitle(deletePrompt).confirmModal();
|
||||
|
||||
masthead.checkNotificationMessage("Provider successfully deleted");
|
||||
masthead.checkNotificationMessage(deleteSuccessMsg);
|
||||
|
||||
createProviderPage.checkGitHubCardVisible();
|
||||
});
|
||||
|
@ -90,50 +98,60 @@ describe("Identity provider test", () => {
|
|||
orderDialog.checkOrder(["facebook", "bitbucket", identityProviderName]);
|
||||
|
||||
orderDialog.clickSave();
|
||||
masthead.checkNotificationMessage(
|
||||
"Successfully changed display order of identity providers"
|
||||
);
|
||||
masthead.checkNotificationMessage(changeSuccessMsg);
|
||||
});
|
||||
|
||||
it("should create a oidc provider using discovery url", () => {
|
||||
const oidcProviderName = "oidc";
|
||||
const keycloakServer = Cypress.env("KEYCLOAK_SERVER");
|
||||
|
||||
createProviderPage
|
||||
.clickCreateDropdown()
|
||||
.clickItem(oidcProviderName)
|
||||
.fillDiscoveryUrl(
|
||||
`${keycloakServer}/auth/realms/master/.well-known/openid-configuration`
|
||||
)
|
||||
.fillDiscoveryUrl(discoveryUrl)
|
||||
.shouldBeSuccessful()
|
||||
.fill("oidc", "123")
|
||||
.clickAdd();
|
||||
masthead.checkNotificationMessage(
|
||||
"Identity provider successfully created"
|
||||
);
|
||||
|
||||
createProviderPage.shouldHaveAuthorizationUrl(
|
||||
`${keycloakServer}/auth/realms/master/protocol/openid-connect/auth`
|
||||
);
|
||||
masthead.checkNotificationMessage(createSuccessMsg);
|
||||
createProviderPage.shouldHaveAuthorizationUrl(authorizationUrl);
|
||||
});
|
||||
|
||||
// it("clean up providers", () => {
|
||||
// const modalUtils = new ModalUtils();
|
||||
// listingPage.deleteItem("bitbucket");
|
||||
// modalUtils.checkModalTitle("Delete provider?").confirmModal();
|
||||
// masthead.checkNotificationMessage("Provider successfully deleted");
|
||||
it("should create a SAML provider using SSO service url", () => {
|
||||
const samlProviderName = "saml";
|
||||
createProviderPage
|
||||
.clickCreateDropdown()
|
||||
.clickItem(samlProviderName)
|
||||
.toggleEntityDescriptor()
|
||||
.fillSsoServiceUrl(ssoServiceUrl)
|
||||
.clickAdd();
|
||||
masthead.checkNotificationMessage(createSuccessMsg);
|
||||
});
|
||||
|
||||
// listingPage.deleteItem("facebook");
|
||||
// modalUtils.checkModalTitle("Delete provider?").confirmModal();
|
||||
// masthead.checkNotificationMessage("Provider successfully deleted");
|
||||
it("clean up providers", () => {
|
||||
const modalUtils = new ModalUtils();
|
||||
|
||||
// listingPage.deleteItem("github");
|
||||
// modalUtils.checkModalTitle("Delete provider?").confirmModal();
|
||||
// masthead.checkNotificationMessage("Provider successfully deleted");
|
||||
sidebarPage.goToIdentityProviders();
|
||||
listingPage.itemExist("bitbucket").deleteItem("bitbucket");
|
||||
modalUtils.checkModalTitle(deletePrompt).confirmModal();
|
||||
masthead.checkNotificationMessage(deleteSuccessMsg);
|
||||
|
||||
// listingPage.deleteItem("oidc");
|
||||
// modalUtils.checkModalTitle("Delete provider?").confirmModal();
|
||||
// masthead.checkNotificationMessage("Provider successfully deleted");
|
||||
// });
|
||||
sidebarPage.goToIdentityProviders();
|
||||
listingPage.itemExist("facebook").deleteItem("facebook");
|
||||
modalUtils.checkModalTitle(deletePrompt).confirmModal();
|
||||
masthead.checkNotificationMessage(deleteSuccessMsg);
|
||||
|
||||
sidebarPage.goToIdentityProviders();
|
||||
listingPage.itemExist("github").deleteItem("github");
|
||||
modalUtils.checkModalTitle(deletePrompt).confirmModal();
|
||||
masthead.checkNotificationMessage(deleteSuccessMsg);
|
||||
|
||||
sidebarPage.goToIdentityProviders();
|
||||
listingPage.itemExist("oidc").deleteItem("oidc");
|
||||
modalUtils.checkModalTitle(deletePrompt).confirmModal();
|
||||
masthead.checkNotificationMessage(deleteSuccessMsg);
|
||||
|
||||
sidebarPage.goToIdentityProviders();
|
||||
listingPage.itemExist("saml").deleteItem("saml");
|
||||
modalUtils.checkModalTitle(deletePrompt).confirmModal();
|
||||
masthead.checkNotificationMessage(deleteSuccessMsg);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -6,7 +6,9 @@ export default class CreateProviderPage {
|
|||
private clientSecretField = "clientSecret";
|
||||
private discoveryEndpoint = "discoveryEndpoint";
|
||||
private authorizationUrl = "authorizationUrl";
|
||||
private useEntityDescriptorSwitch = "useEntityDescriptor";
|
||||
private addButton = "createProvider";
|
||||
private ssoServiceUrl = "sso-service-url";
|
||||
|
||||
checkVisible(name: string) {
|
||||
cy.getId(`${name}-card`).should("exist");
|
||||
|
@ -74,6 +76,12 @@ export default class CreateProviderPage {
|
|||
return this;
|
||||
}
|
||||
|
||||
fillSsoServiceUrl(value: string) {
|
||||
cy.getId(this.ssoServiceUrl).type("x");
|
||||
cy.getId(this.ssoServiceUrl).clear().type(value).blur();
|
||||
return this;
|
||||
}
|
||||
|
||||
shouldBeSuccessful() {
|
||||
cy.getId(this.discoveryEndpoint).should("have.class", "pf-m-success");
|
||||
return this;
|
||||
|
@ -83,4 +91,9 @@ export default class CreateProviderPage {
|
|||
cy.getId(this.authorizationUrl).should("have.value", value);
|
||||
return this;
|
||||
}
|
||||
|
||||
toggleEntityDescriptor() {
|
||||
cy.getId(this.useEntityDescriptorSwitch).click({ force: true });
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
|
90
src/identity-providers/add/AddSamlConnect.tsx
Normal file
90
src/identity-providers/add/AddSamlConnect.tsx
Normal file
|
@ -0,0 +1,90 @@
|
|||
import React from "react";
|
||||
import { useHistory } from "react-router-dom";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { FormProvider, useForm } from "react-hook-form";
|
||||
import {
|
||||
ActionGroup,
|
||||
AlertVariant,
|
||||
Button,
|
||||
PageSection,
|
||||
} from "@patternfly/react-core";
|
||||
|
||||
import type IdentityProviderRepresentation from "keycloak-admin/lib/defs/identityProviderRepresentation";
|
||||
import { FormAccess } from "../../components/form-access/FormAccess";
|
||||
import { ViewHeader } from "../../components/view-header/ViewHeader";
|
||||
import { useAdminClient } from "../../context/auth/AdminClient";
|
||||
import { SamlGeneralSettings } from "./SamlGeneralSettings";
|
||||
import { SamlConnectSettings } from "./SamlConnectSettings";
|
||||
import { useRealm } from "../../context/realm-context/RealmContext";
|
||||
import { useAlerts } from "../../components/alert/Alerts";
|
||||
|
||||
export const AddSamlConnect = () => {
|
||||
const { t } = useTranslation("identity-providers");
|
||||
const history = useHistory();
|
||||
const id = "saml";
|
||||
|
||||
const form = useForm<IdentityProviderRepresentation>({
|
||||
defaultValues: { alias: id },
|
||||
});
|
||||
const {
|
||||
handleSubmit,
|
||||
formState: { isDirty },
|
||||
} = form;
|
||||
|
||||
const adminClient = useAdminClient();
|
||||
const { addAlert } = useAlerts();
|
||||
const { realm } = useRealm();
|
||||
|
||||
const save = async (provider: IdentityProviderRepresentation) => {
|
||||
try {
|
||||
await adminClient.identityProviders.create({
|
||||
...provider,
|
||||
providerId: id,
|
||||
});
|
||||
addAlert(t("createSuccess"), AlertVariant.success);
|
||||
history.push(`/${realm}/identity-providers/${id}/settings`);
|
||||
} catch (error) {
|
||||
addAlert(
|
||||
t("createError", {
|
||||
error: error.response?.data?.errorMessage || error,
|
||||
}),
|
||||
AlertVariant.danger
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<ViewHeader titleKey={t("addSamlProvider")} />
|
||||
<PageSection variant="light">
|
||||
<FormProvider {...form}>
|
||||
<FormAccess
|
||||
role="manage-identity-providers"
|
||||
isHorizontal
|
||||
onSubmit={handleSubmit(save)}
|
||||
>
|
||||
<SamlGeneralSettings id={id} />
|
||||
<SamlConnectSettings />
|
||||
<ActionGroup>
|
||||
<Button
|
||||
isDisabled={!isDirty}
|
||||
variant="primary"
|
||||
type="submit"
|
||||
data-testid="createProvider"
|
||||
>
|
||||
{t("common:add")}
|
||||
</Button>
|
||||
<Button
|
||||
variant="link"
|
||||
data-testid="cancel"
|
||||
onClick={() => history.push(`/${realm}/identity-providers`)}
|
||||
>
|
||||
{t("common:cancel")}
|
||||
</Button>
|
||||
</ActionGroup>
|
||||
</FormAccess>
|
||||
</FormProvider>
|
||||
</PageSection>
|
||||
</>
|
||||
);
|
||||
};
|
|
@ -90,16 +90,25 @@ const LoginFlow = ({
|
|||
};
|
||||
|
||||
const syncModes = ["import", "legacy", "force"];
|
||||
type AdvancedSettingsProps = { isOIDC: boolean; isSAML: boolean };
|
||||
|
||||
export const AdvancedSettings = ({ isOIDC }: { isOIDC: boolean }) => {
|
||||
export const AdvancedSettings = ({ isOIDC, isSAML }: AdvancedSettingsProps) => {
|
||||
const { t } = useTranslation("identity-providers");
|
||||
const { control } = useFormContext();
|
||||
const [syncModeOpen, setSyncModeOpen] = useState(false);
|
||||
return (
|
||||
<>
|
||||
{!isOIDC && <TextField field="config.defaultScope" label="scopes" />}
|
||||
{!isOIDC && !isSAML && (
|
||||
<TextField field="config.defaultScope" label="scopes" />
|
||||
)}
|
||||
<SwitchField field="storeToken" label="storeTokens" fieldType="boolean" />
|
||||
{!isOIDC && (
|
||||
{isSAML && (
|
||||
<SwitchField
|
||||
field="config.addReadTokenRoleOnCreate"
|
||||
label="storedTokensReadable"
|
||||
/>
|
||||
)}
|
||||
{!isOIDC && !isSAML && (
|
||||
<>
|
||||
<SwitchField
|
||||
field="config.acceptsPromptNoneForwardFromClient"
|
||||
|
|
401
src/identity-providers/add/DescriptorSettings.tsx
Normal file
401
src/identity-providers/add/DescriptorSettings.tsx
Normal file
|
@ -0,0 +1,401 @@
|
|||
import React, { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Controller, useFormContext, useWatch } from "react-hook-form";
|
||||
import {
|
||||
ExpandableSection,
|
||||
FormGroup,
|
||||
Select,
|
||||
SelectOption,
|
||||
SelectVariant,
|
||||
TextInput,
|
||||
ValidatedOptions,
|
||||
} from "@patternfly/react-core";
|
||||
import { HelpItem } from "../../components/help-enabler/HelpItem";
|
||||
import { SwitchField } from "../component/SwitchField";
|
||||
import { TextField } from "../component/TextField";
|
||||
|
||||
import "./discovery-settings.css";
|
||||
|
||||
type DescriptorSettingsProps = {
|
||||
readOnly: boolean;
|
||||
};
|
||||
|
||||
const Fields = ({ readOnly }: DescriptorSettingsProps) => {
|
||||
const { t } = useTranslation("identity-providers");
|
||||
const { t: th } = useTranslation("identity-providers-help");
|
||||
|
||||
const { register, control, errors } = useFormContext();
|
||||
const [namedPolicyDropdownOpen, setNamedPolicyDropdownOpen] = useState(false);
|
||||
const [principalTypeDropdownOpen, setPrincipalTypeDropdownOpen] =
|
||||
useState(false);
|
||||
const [signatureAlgorithmDropdownOpen, setSignatureAlgorithmDropdownOpen] =
|
||||
useState(false);
|
||||
const [
|
||||
samlSignatureKeyNameDropdownOpen,
|
||||
setSamlSignatureKeyNameDropdownOpen,
|
||||
] = useState(false);
|
||||
|
||||
const wantAuthnSigned = useWatch({
|
||||
control,
|
||||
name: "config.wantAuthnRequestsSigned",
|
||||
});
|
||||
|
||||
const validateSignature = useWatch({
|
||||
control,
|
||||
name: "config.validateSignature",
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="pf-c-form pf-m-horizontal">
|
||||
<FormGroup
|
||||
label={t("ssoServiceUrl")}
|
||||
labelIcon={
|
||||
<HelpItem
|
||||
helpText={th("ssoServiceUrl")}
|
||||
forLabel={t("ssoServiceUrl")}
|
||||
forID="kc-sso-service-url"
|
||||
/>
|
||||
}
|
||||
fieldId="kc-sso-service-url"
|
||||
isRequired
|
||||
validated={
|
||||
errors.config?.authorizationUrl
|
||||
? ValidatedOptions.error
|
||||
: ValidatedOptions.default
|
||||
}
|
||||
helperTextInvalid={t("common:required")}
|
||||
>
|
||||
<TextInput
|
||||
type="text"
|
||||
data-testid="sso-service-url"
|
||||
id="kc-sso-service-url"
|
||||
name="config.singleSignOnServiceUrl"
|
||||
ref={register({ required: true })}
|
||||
validated={
|
||||
errors.config?.singleSignOnServiceUrl
|
||||
? ValidatedOptions.error
|
||||
: ValidatedOptions.default
|
||||
}
|
||||
isReadOnly={readOnly}
|
||||
/>
|
||||
</FormGroup>
|
||||
|
||||
<FormGroup
|
||||
label={t("singleLogoutServiceUrl")}
|
||||
labelIcon={
|
||||
<HelpItem
|
||||
helpText={th("singleLogoutServiceUrl")}
|
||||
forLabel={t("singleLogoutServiceUrl")}
|
||||
forID="single-logout-service-url"
|
||||
/>
|
||||
}
|
||||
fieldId="single-logout-service-url"
|
||||
validated={
|
||||
errors.config?.singleLogoutServiceUrl
|
||||
? ValidatedOptions.error
|
||||
: ValidatedOptions.default
|
||||
}
|
||||
helperTextInvalid={t("common:required")}
|
||||
>
|
||||
<TextInput
|
||||
type="text"
|
||||
id="single-logout-service-url"
|
||||
name="config.singleLogoutServiceUrl"
|
||||
isReadOnly={readOnly}
|
||||
/>
|
||||
</FormGroup>
|
||||
|
||||
<SwitchField
|
||||
field="config.backchannelSupported"
|
||||
label="backchannelLogout"
|
||||
isReadOnly={readOnly}
|
||||
/>
|
||||
|
||||
<FormGroup
|
||||
label={t("nameIdPolicyFormat")}
|
||||
labelIcon={
|
||||
<HelpItem
|
||||
helpText={th("nameIdPolicyFormat")}
|
||||
forLabel={t("nameIdPolicyFormat")}
|
||||
forID="kc-nameIdPolicyFormat"
|
||||
/>
|
||||
}
|
||||
fieldId="kc-nameIdPolicyFormat"
|
||||
helperTextInvalid={t("common:required")}
|
||||
>
|
||||
<Controller
|
||||
name="config.nameIDPolicyFormat"
|
||||
defaultValue={t("persistent")}
|
||||
control={control}
|
||||
render={({ onChange, value }) => (
|
||||
<Select
|
||||
toggleId="kc-nameIdPolicyFormat"
|
||||
onToggle={(isExpanded) => setNamedPolicyDropdownOpen(isExpanded)}
|
||||
isOpen={namedPolicyDropdownOpen}
|
||||
onSelect={(_, value) => {
|
||||
onChange(value as string);
|
||||
setNamedPolicyDropdownOpen(false);
|
||||
}}
|
||||
selections={value}
|
||||
variant={SelectVariant.single}
|
||||
>
|
||||
<SelectOption
|
||||
data-testid="persistent-option"
|
||||
value={t("persistent")}
|
||||
isPlaceholder
|
||||
/>
|
||||
<SelectOption
|
||||
data-testid="transient-option"
|
||||
value={t("transient")}
|
||||
/>
|
||||
<SelectOption data-testid="email-option" value={t("email")} />
|
||||
<SelectOption
|
||||
data-testid="kerberos-option"
|
||||
value={t("kerberos")}
|
||||
/>
|
||||
<SelectOption data-testid="x509-option" value={t("x509")} />
|
||||
<SelectOption
|
||||
data-testid="windowsDomainQN-option"
|
||||
value={t("windowsDomainQN")}
|
||||
/>
|
||||
<SelectOption
|
||||
data-testid="unspecified-option"
|
||||
value={t("unspecified")}
|
||||
/>
|
||||
</Select>
|
||||
)}
|
||||
></Controller>
|
||||
</FormGroup>
|
||||
|
||||
<FormGroup
|
||||
label={t("principalType")}
|
||||
labelIcon={
|
||||
<HelpItem
|
||||
helpText={th("principalType")}
|
||||
forLabel={t("principalType")}
|
||||
forID="kc-principalType"
|
||||
/>
|
||||
}
|
||||
fieldId="kc-principalType"
|
||||
helperTextInvalid={t("common:required")}
|
||||
>
|
||||
<Controller
|
||||
name="config.principalType"
|
||||
defaultValue={t("subjectNameId")}
|
||||
control={control}
|
||||
render={({ onChange, value }) => (
|
||||
<Select
|
||||
toggleId="kc-principalType"
|
||||
onToggle={(isExpanded) =>
|
||||
setPrincipalTypeDropdownOpen(isExpanded)
|
||||
}
|
||||
isOpen={principalTypeDropdownOpen}
|
||||
onSelect={(_, value) => {
|
||||
onChange(value.toString());
|
||||
setPrincipalTypeDropdownOpen(false);
|
||||
}}
|
||||
selections={value}
|
||||
variant={SelectVariant.single}
|
||||
>
|
||||
<SelectOption
|
||||
data-testid="subjectNameId-option"
|
||||
value={t("subjectNameId")}
|
||||
isPlaceholder
|
||||
/>
|
||||
<SelectOption
|
||||
data-testid="attributeName-option"
|
||||
value={t("attributeName")}
|
||||
/>
|
||||
<SelectOption
|
||||
data-testid="attributeFriendlyName-option"
|
||||
value={t("attributeFriendlyName")}
|
||||
/>
|
||||
</Select>
|
||||
)}
|
||||
></Controller>
|
||||
</FormGroup>
|
||||
|
||||
<SwitchField
|
||||
field="config.postBindingResponse"
|
||||
label="httpPostBindingResponse"
|
||||
isReadOnly={readOnly}
|
||||
/>
|
||||
|
||||
<SwitchField
|
||||
field="config.postBindingAuthnRequest"
|
||||
label="httpPostBindingAuthnRequest"
|
||||
isReadOnly={readOnly}
|
||||
/>
|
||||
|
||||
<SwitchField
|
||||
field="config.postBindingLogout"
|
||||
label="httpPostBindingLogout"
|
||||
isReadOnly={readOnly}
|
||||
/>
|
||||
|
||||
<SwitchField
|
||||
field="config.wantAuthnRequestsSigned"
|
||||
label="wantAuthnRequestsSigned"
|
||||
isReadOnly={readOnly}
|
||||
/>
|
||||
|
||||
{wantAuthnSigned === "true" && (
|
||||
<>
|
||||
<FormGroup
|
||||
label={t("signatureAlgorithm")}
|
||||
labelIcon={
|
||||
<HelpItem
|
||||
helpText={th("signatureAlgorithm")}
|
||||
forLabel={t("signatureAlgorithm")}
|
||||
forID="kc-signatureAlgorithm"
|
||||
/>
|
||||
}
|
||||
fieldId="kc-signatureAlgorithm"
|
||||
>
|
||||
<Controller
|
||||
name="config.signatureAlgorithm"
|
||||
defaultValue="RSA_SHA256"
|
||||
control={control}
|
||||
render={({ onChange, value }) => (
|
||||
<Select
|
||||
toggleId="kc-signatureAlgorithm"
|
||||
onToggle={(isExpanded) =>
|
||||
setSignatureAlgorithmDropdownOpen(isExpanded)
|
||||
}
|
||||
isOpen={signatureAlgorithmDropdownOpen}
|
||||
onSelect={(_, value) => {
|
||||
onChange(value.toString());
|
||||
setSignatureAlgorithmDropdownOpen(false);
|
||||
}}
|
||||
selections={value}
|
||||
variant={SelectVariant.single}
|
||||
>
|
||||
<SelectOption value="RSA_SHA1" />
|
||||
<SelectOption value="RSA_SHA256" isPlaceholder />
|
||||
<SelectOption value="RSA_SHA256_MGF1" />
|
||||
<SelectOption value="RSA_SHA512" />
|
||||
<SelectOption value="RSA_SHA512_MGF1" />
|
||||
<SelectOption value="DSA_SHA1" />
|
||||
</Select>
|
||||
)}
|
||||
></Controller>
|
||||
</FormGroup>
|
||||
<FormGroup
|
||||
label={t("samlSignatureKeyName")}
|
||||
labelIcon={
|
||||
<HelpItem
|
||||
helpText={th("samlSignatureKeyName")}
|
||||
forLabel={t("samlSignatureKeyName")}
|
||||
forID="kc-samlSignatureKeyName"
|
||||
/>
|
||||
}
|
||||
fieldId="kc-samlSignatureKeyName"
|
||||
>
|
||||
<Controller
|
||||
name="config.xmlSigKeyInfoKeyNameTransformer"
|
||||
defaultValue="keyID-option"
|
||||
control={control}
|
||||
render={({ onChange, value }) => (
|
||||
<Select
|
||||
toggleId="kc-samlSignatureKeyName"
|
||||
onToggle={(isExpanded) =>
|
||||
setSamlSignatureKeyNameDropdownOpen(isExpanded)
|
||||
}
|
||||
isOpen={samlSignatureKeyNameDropdownOpen}
|
||||
onSelect={(_, value) => {
|
||||
onChange(value.toString());
|
||||
setSamlSignatureKeyNameDropdownOpen(false);
|
||||
}}
|
||||
selections={value}
|
||||
variant={SelectVariant.single}
|
||||
>
|
||||
<SelectOption value="NONE" />
|
||||
<SelectOption value={t("keyID")} isPlaceholder />
|
||||
<SelectOption value={t("certSubject")} />
|
||||
</Select>
|
||||
)}
|
||||
></Controller>
|
||||
</FormGroup>
|
||||
</>
|
||||
)}
|
||||
|
||||
<SwitchField
|
||||
field="config.wantAssertionsSigned"
|
||||
label="wantAssertionsSigned"
|
||||
isReadOnly={readOnly}
|
||||
/>
|
||||
|
||||
<SwitchField
|
||||
field="config.wantAssertionsEncrypted"
|
||||
label="wantAssertionsEncrypted"
|
||||
isReadOnly={readOnly}
|
||||
/>
|
||||
<SwitchField
|
||||
field="config.forceAuthn"
|
||||
label="forceAuthentication"
|
||||
isReadOnly={readOnly}
|
||||
/>
|
||||
|
||||
<SwitchField
|
||||
field="config.validateSignature"
|
||||
label="validateSignature"
|
||||
isReadOnly={readOnly}
|
||||
/>
|
||||
{validateSignature === "true" && (
|
||||
<TextField
|
||||
field="config.signingCertificate"
|
||||
label="validatingX509Certs"
|
||||
isReadOnly={readOnly}
|
||||
/>
|
||||
)}
|
||||
<SwitchField
|
||||
field="config.signSpMetadata"
|
||||
label="signServiceProviderMetadata"
|
||||
isReadOnly={readOnly}
|
||||
/>
|
||||
<SwitchField
|
||||
field="config.passSubject"
|
||||
label="passSubject"
|
||||
isReadOnly={readOnly}
|
||||
/>
|
||||
|
||||
<FormGroup
|
||||
label={t("allowedClockSkew")}
|
||||
labelIcon={
|
||||
<HelpItem
|
||||
helpText={th("allowedClockSkew")}
|
||||
forLabel={t("allowedClockSkew")}
|
||||
forID="allowedClockSkew"
|
||||
/>
|
||||
}
|
||||
fieldId="allowedClockSkew"
|
||||
helperTextInvalid={t("common:required")}
|
||||
>
|
||||
<TextInput
|
||||
type="text"
|
||||
id="allowedClockSkew"
|
||||
name="config.allowedClockSkew"
|
||||
isReadOnly={readOnly}
|
||||
/>
|
||||
</FormGroup>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const DescriptorSettings = ({ readOnly }: DescriptorSettingsProps) => {
|
||||
const { t } = useTranslation("identity-providers");
|
||||
const [isExpanded, setIsExpanded] = useState(false);
|
||||
|
||||
return readOnly ? (
|
||||
<ExpandableSection
|
||||
className="keycloak__discovery-settings__metadata"
|
||||
toggleText={isExpanded ? t("hideMetaData") : t("showMetaData")}
|
||||
onToggle={(isOpen) => setIsExpanded(isOpen)}
|
||||
isExpanded={isExpanded}
|
||||
>
|
||||
<Fields readOnly={readOnly} />
|
||||
</ExpandableSection>
|
||||
) : (
|
||||
<Fields readOnly={readOnly} />
|
||||
);
|
||||
};
|
|
@ -29,8 +29,11 @@ import { useRealm } from "../../context/realm-context/RealmContext";
|
|||
import { KeycloakTabs } from "../../components/keycloak-tabs/KeycloakTabs";
|
||||
import { ExtendedNonDiscoverySettings } from "./ExtendedNonDiscoverySettings";
|
||||
import { DiscoverySettings } from "./DiscoverySettings";
|
||||
import { DescriptorSettings } from "./DescriptorSettings";
|
||||
import { OIDCGeneralSettings } from "./OIDCGeneralSettings";
|
||||
import { SamlGeneralSettings } from "./SamlGeneralSettings";
|
||||
import { OIDCAuthentication } from "./OIDCAuthentication";
|
||||
import { ReqAuthnConstraints } from "./ReqAuthnConstraintsSettings";
|
||||
|
||||
type HeaderProps = {
|
||||
onChange: (value: boolean) => void;
|
||||
|
@ -133,12 +136,19 @@ export const DetailSettings = () => {
|
|||
});
|
||||
|
||||
const sections = [t("generalSettings"), t("advancedSettings")];
|
||||
|
||||
const isOIDC = id.includes("oidc");
|
||||
const isSAML = id.includes("saml");
|
||||
|
||||
if (isOIDC) {
|
||||
sections.splice(1, 0, t("oidcSettings"));
|
||||
}
|
||||
|
||||
if (isSAML) {
|
||||
sections.splice(1, 0, t("samlSettings"));
|
||||
sections.splice(2, 0, t("reqAuthnConstraints"));
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<DeleteConfirm />
|
||||
|
@ -170,8 +180,11 @@ export const DetailSettings = () => {
|
|||
isHorizontal
|
||||
onSubmit={handleSubmit(save)}
|
||||
>
|
||||
{!isOIDC && <GeneralSettings create={false} id={id} />}
|
||||
{!isOIDC && !isSAML && (
|
||||
<GeneralSettings create={false} id={id} />
|
||||
)}
|
||||
{isOIDC && <OIDCGeneralSettings id={id} />}
|
||||
{isSAML && <SamlGeneralSettings id={id} />}
|
||||
</FormAccess>
|
||||
{isOIDC && (
|
||||
<>
|
||||
|
@ -183,12 +196,21 @@ export const DetailSettings = () => {
|
|||
<ExtendedNonDiscoverySettings />
|
||||
</>
|
||||
)}
|
||||
{isSAML && <DescriptorSettings readOnly={false} />}
|
||||
<FormAccess
|
||||
role="manage-identity-providers"
|
||||
isHorizontal
|
||||
onSubmit={handleSubmit(save)}
|
||||
>
|
||||
<AdvancedSettings isOIDC={isOIDC} />
|
||||
<ReqAuthnConstraints />
|
||||
</FormAccess>
|
||||
<FormAccess
|
||||
role="manage-identity-providers"
|
||||
isHorizontal
|
||||
onSubmit={handleSubmit(save)}
|
||||
>
|
||||
<AdvancedSettings isOIDC={isOIDC} isSAML={isSAML} />
|
||||
|
||||
<ActionGroup className="keycloak__form_actions">
|
||||
<Button data-testid={"save"} type="submit">
|
||||
{t("common:save")}
|
||||
|
|
|
@ -84,7 +84,7 @@ export const OpenIdConnectSettings = () => {
|
|||
return (
|
||||
<>
|
||||
<Title headingLevel="h4" size="xl" className="kc-form-panel__title">
|
||||
{t("OpenID Connect settings")}
|
||||
{t("oidcSettings")}
|
||||
</Title>
|
||||
<FormGroup
|
||||
label={t("useDiscoveryEndpoint")}
|
||||
|
|
75
src/identity-providers/add/ReqAuthnConstraintsSettings.tsx
Normal file
75
src/identity-providers/add/ReqAuthnConstraintsSettings.tsx
Normal file
|
@ -0,0 +1,75 @@
|
|||
import React, { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Controller, useFormContext } from "react-hook-form";
|
||||
import {
|
||||
FormGroup,
|
||||
Select,
|
||||
SelectOption,
|
||||
SelectVariant,
|
||||
} from "@patternfly/react-core";
|
||||
|
||||
import { TextField } from "../component/TextField";
|
||||
import { HelpItem } from "../../components/help-enabler/HelpItem";
|
||||
|
||||
const comparisonValues = ["Exact", "Minimum", "Maximum", "Better"];
|
||||
|
||||
export const ReqAuthnConstraints = () => {
|
||||
const { t } = useTranslation("identity-providers");
|
||||
const { control } = useFormContext();
|
||||
const [syncModeOpen, setSyncModeOpen] = useState(false);
|
||||
return (
|
||||
<>
|
||||
<FormGroup
|
||||
label={t("comparison")}
|
||||
labelIcon={
|
||||
<HelpItem
|
||||
helpText="identity-providers-help:comparison"
|
||||
forLabel={t("comparison")}
|
||||
forID="comparison"
|
||||
/>
|
||||
}
|
||||
fieldId="comparison"
|
||||
>
|
||||
<Controller
|
||||
name="config.comparison"
|
||||
defaultValue={comparisonValues[0]}
|
||||
control={control}
|
||||
render={({ onChange, value }) => (
|
||||
<Select
|
||||
toggleId="comparison"
|
||||
required
|
||||
direction="up"
|
||||
onToggle={() => setSyncModeOpen(!syncModeOpen)}
|
||||
onSelect={(_, value) => {
|
||||
onChange(value.toString());
|
||||
setSyncModeOpen(false);
|
||||
}}
|
||||
selections={value}
|
||||
variant={SelectVariant.single}
|
||||
aria-label={t("syncMode")}
|
||||
isOpen={syncModeOpen}
|
||||
>
|
||||
{comparisonValues.map((option) => (
|
||||
<SelectOption
|
||||
selected={option === value}
|
||||
key={option}
|
||||
value={option}
|
||||
>
|
||||
{t(option)}
|
||||
</SelectOption>
|
||||
))}
|
||||
</Select>
|
||||
)}
|
||||
/>
|
||||
</FormGroup>
|
||||
<TextField
|
||||
field="config.authnContextClassRefs"
|
||||
label="authnContextClassRefs"
|
||||
/>
|
||||
<TextField
|
||||
field="config.authnContextDeclRefs"
|
||||
label="authnContextDeclRefs"
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
213
src/identity-providers/add/SamlConnectSettings.tsx
Normal file
213
src/identity-providers/add/SamlConnectSettings.tsx
Normal file
|
@ -0,0 +1,213 @@
|
|||
import React, { useEffect, useState } from "react";
|
||||
import { useFormContext } from "react-hook-form";
|
||||
import {
|
||||
FormGroup,
|
||||
Switch,
|
||||
TextInput,
|
||||
Title,
|
||||
ValidatedOptions,
|
||||
} from "@patternfly/react-core";
|
||||
|
||||
import { HelpItem } from "../../components/help-enabler/HelpItem";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useAdminClient } from "../../context/auth/AdminClient";
|
||||
import type IdentityProviderRepresentation from "keycloak-admin/lib/defs/identityProviderRepresentation";
|
||||
|
||||
import { JsonFileUpload } from "../../components/json-file-upload/JsonFileUpload";
|
||||
import { useRealm } from "../../context/realm-context/RealmContext";
|
||||
import { DescriptorSettings } from "./DescriptorSettings";
|
||||
import { getBaseUrl } from "../../util";
|
||||
|
||||
type Result = IdentityProviderRepresentation & {
|
||||
error: string;
|
||||
};
|
||||
|
||||
export const SamlConnectSettings = () => {
|
||||
const { t } = useTranslation("identity-providers");
|
||||
const id = "saml";
|
||||
|
||||
const adminClient = useAdminClient();
|
||||
const { realm } = useRealm();
|
||||
const { setValue, register, errors } = useFormContext();
|
||||
|
||||
const [descriptor, setDescriptor] = useState(true);
|
||||
|
||||
const [entityUrl, setEntityUrl] = useState("");
|
||||
const [descriptorUrl, setDescriptorUrl] = useState("");
|
||||
const [discovering, setDiscovering] = useState(false);
|
||||
const [discoveryResult, setDiscoveryResult] = useState<Result>();
|
||||
|
||||
const defaultEntityUrl = `${getBaseUrl(adminClient)}realms/${realm}`;
|
||||
|
||||
const setupForm = (result: IdentityProviderRepresentation) => {
|
||||
Object.entries(result).map(([key, value]) =>
|
||||
setValue(`config.${key}`, value)
|
||||
);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (!discovering) {
|
||||
return;
|
||||
}
|
||||
|
||||
setDiscovering(!!entityUrl);
|
||||
|
||||
if (!entityUrl) {
|
||||
return;
|
||||
}
|
||||
|
||||
(async () => {
|
||||
let result;
|
||||
try {
|
||||
result = await adminClient.identityProviders.importFromUrl({
|
||||
providerId: id,
|
||||
fromUrl: entityUrl,
|
||||
});
|
||||
} catch (error) {
|
||||
result = { error };
|
||||
}
|
||||
|
||||
setDiscoveryResult(result as Result);
|
||||
setupForm(result);
|
||||
setDiscovering(false);
|
||||
})();
|
||||
}, [discovering]);
|
||||
|
||||
const fileUpload = async (obj: object) => {
|
||||
if (obj) {
|
||||
const formData = new FormData();
|
||||
formData.append("providerId", id);
|
||||
formData.append("file", new Blob([JSON.stringify(obj)]));
|
||||
|
||||
try {
|
||||
const response = await fetch(
|
||||
`${getBaseUrl(
|
||||
adminClient
|
||||
)}admin/realms/${realm}/identity-provider/import-config`,
|
||||
{
|
||||
method: "POST",
|
||||
body: formData,
|
||||
headers: {
|
||||
Authorization: `bearer ${await adminClient.getAccessToken()}`,
|
||||
},
|
||||
}
|
||||
);
|
||||
const result = await response.json();
|
||||
setupForm(result);
|
||||
} catch (error) {
|
||||
setDiscoveryResult({ error });
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Title headingLevel="h4" size="xl" className="kc-form-panel__title">
|
||||
{t("samlSettings")}
|
||||
</Title>
|
||||
|
||||
<FormGroup
|
||||
label={t("serviceProviderEntityId")}
|
||||
fieldId="kc-service-provider-entity-id"
|
||||
labelIcon={
|
||||
<HelpItem
|
||||
helpText="identity-providers-help:serviceProviderEntityId"
|
||||
forLabel={t("serviceProviderEntityId")}
|
||||
forID="kc-service-provider-entity-id"
|
||||
/>
|
||||
}
|
||||
isRequired
|
||||
>
|
||||
<TextInput
|
||||
type="text"
|
||||
name="config.entityId"
|
||||
data-testid="serviceProviderEntityId"
|
||||
id="kc-service-provider-entity-id"
|
||||
value={entityUrl || defaultEntityUrl}
|
||||
onChange={setEntityUrl}
|
||||
ref={register({ required: true })}
|
||||
/>
|
||||
</FormGroup>
|
||||
|
||||
<FormGroup
|
||||
label={t("useEntityDescriptor")}
|
||||
fieldId="kc-use-entity-descriptor"
|
||||
labelIcon={
|
||||
<HelpItem
|
||||
helpText="identity-providers-help:useEntityDescriptor"
|
||||
forLabel={t("useEntityDescriptor")}
|
||||
forID="kc-use-entity-descriptor-switch"
|
||||
/>
|
||||
}
|
||||
>
|
||||
<Switch
|
||||
id="kc-use-entity-descriptor-switch"
|
||||
label={t("common:on")}
|
||||
data-testid="useEntityDescriptor"
|
||||
labelOff={t("common:off")}
|
||||
isChecked={descriptor}
|
||||
onChange={setDescriptor}
|
||||
/>
|
||||
</FormGroup>
|
||||
|
||||
{descriptor && (
|
||||
<FormGroup
|
||||
label={t("samlEntityDescriptor")}
|
||||
fieldId="kc-saml-entity-descriptor"
|
||||
labelIcon={
|
||||
<HelpItem
|
||||
helpText="identity-providers-help:samlEntityDescriptor"
|
||||
forLabel={t("samlEntityDescriptor")}
|
||||
forID="kc-saml-entity-descriptor"
|
||||
/>
|
||||
}
|
||||
isRequired
|
||||
>
|
||||
<TextInput
|
||||
type="text"
|
||||
name="samlEntityDescriptor"
|
||||
data-testid="samlEntityDescriptor"
|
||||
id="kc-saml-entity-descriptor"
|
||||
value={descriptorUrl}
|
||||
onChange={setDescriptorUrl}
|
||||
ref={register({ required: true })}
|
||||
validated={
|
||||
errors.samlEntityDescriptor
|
||||
? ValidatedOptions.error
|
||||
: ValidatedOptions.default
|
||||
}
|
||||
/>
|
||||
</FormGroup>
|
||||
)}
|
||||
{!descriptor && (
|
||||
<FormGroup
|
||||
label={t("importConfig")}
|
||||
fieldId="kc-import-config"
|
||||
labelIcon={
|
||||
<HelpItem
|
||||
helpText="identity-providers-help:importConfig"
|
||||
forLabel={t("importConfig")}
|
||||
forID="kc-import-config"
|
||||
/>
|
||||
}
|
||||
validated={discoveryResult?.error ? "error" : "default"}
|
||||
helperTextInvalid={discoveryResult?.error?.toString()}
|
||||
>
|
||||
<JsonFileUpload
|
||||
id="kc-import-config"
|
||||
helpText="identity-providers-help:jsonFileUpload"
|
||||
hideDefaultPreview
|
||||
unWrap
|
||||
validated={discoveryResult?.error ? "error" : "default"}
|
||||
onChange={(value) => fileUpload(value)}
|
||||
/>
|
||||
</FormGroup>
|
||||
)}
|
||||
|
||||
{descriptor && discoveryResult && !discoveryResult.error && (
|
||||
<DescriptorSettings readOnly={true} />
|
||||
)}
|
||||
{!descriptor && <DescriptorSettings readOnly={false} />}
|
||||
</>
|
||||
);
|
||||
};
|
54
src/identity-providers/add/SamlGeneralSettings.tsx
Normal file
54
src/identity-providers/add/SamlGeneralSettings.tsx
Normal file
|
@ -0,0 +1,54 @@
|
|||
import React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useFormContext } from "react-hook-form";
|
||||
import { FormGroup, TextInput, ValidatedOptions } from "@patternfly/react-core";
|
||||
|
||||
import { HelpItem } from "../../components/help-enabler/HelpItem";
|
||||
import { RedirectUrl } from "../component/RedirectUrl";
|
||||
import { TextField } from "../component/TextField";
|
||||
import { DisplayOrder } from "../component/DisplayOrder";
|
||||
|
||||
export const SamlGeneralSettings = ({ id }: { id: string }) => {
|
||||
const { t } = useTranslation("identity-providers");
|
||||
const { t: th } = useTranslation("identity-providers-help");
|
||||
|
||||
const { register, errors } = useFormContext();
|
||||
|
||||
return (
|
||||
<>
|
||||
<RedirectUrl id={id} />
|
||||
|
||||
<FormGroup
|
||||
label={t("alias")}
|
||||
labelIcon={
|
||||
<HelpItem
|
||||
helpText={th("alias")}
|
||||
forLabel={t("alias")}
|
||||
forID="alias"
|
||||
/>
|
||||
}
|
||||
fieldId="alias"
|
||||
isRequired
|
||||
validated={
|
||||
errors.alias ? ValidatedOptions.error : ValidatedOptions.default
|
||||
}
|
||||
helperTextInvalid={t("common:required")}
|
||||
>
|
||||
<TextInput
|
||||
isRequired
|
||||
type="text"
|
||||
id="alias"
|
||||
data-testid="alias"
|
||||
name="alias"
|
||||
validated={
|
||||
errors.alias ? ValidatedOptions.error : ValidatedOptions.default
|
||||
}
|
||||
ref={register({ required: true })}
|
||||
/>
|
||||
</FormGroup>
|
||||
|
||||
<TextField field="displayName" label="displayName" />
|
||||
<DisplayOrder />
|
||||
</>
|
||||
);
|
||||
};
|
|
@ -45,6 +45,8 @@ export default {
|
|||
"The client authentication method (cfr. https://openid.net/specs/openid-connect-core-1_0.html#ClientAuthentication). In case of JWT signed with private key, the realm private key is used.",
|
||||
storeTokens:
|
||||
"Enable/disable if tokens must be stored after authenticating users.",
|
||||
storedTokensReadable:
|
||||
"Enable/disable if new users can read any stored tokens. This assigns the broker.read-token role.",
|
||||
trustEmail:
|
||||
"If enabled, email provided by this provider is not verified even if verification is enabled for the realm.",
|
||||
accountLinkingOnly:
|
||||
|
@ -57,5 +59,48 @@ export default {
|
|||
'Alias of authentication flow, which is triggered after each login with this identity provider. Useful if you want additional verification of each user authenticated with this identity provider (for example OTP). Leave this to "None" if you need no any additional authenticators to be triggered after login with this identity provider. Also note that authenticator implementations must assume that user is already set in ClientSession as identity provider already set it.',
|
||||
syncMode:
|
||||
"Default sync mode for all mappers. The sync mode determines when user data will be synced using the mappers. Possible values are: 'legacy' to keep the behaviour before this option was introduced, 'import' to only import the user once during first login of the user with this identity provider, 'force' to always update the user during every login with this identity provider.",
|
||||
serviceProviderEntityId:
|
||||
"The Entity ID that will be used to uniquely identify this SAML Service Provider.",
|
||||
useEntityDescriptor:
|
||||
"Import metadata from a remote IDP SAML entity descriptor.",
|
||||
samlEntityDescriptor:
|
||||
"Allows you to load external IDP metadata from a config file or to download it from a URL.",
|
||||
ssoServiceUrl:
|
||||
"The Url that must be used to send authentication requests (SAML AuthnRequest).",
|
||||
singleLogoutServiceUrl:
|
||||
"The Url that must be used to send logout requests.",
|
||||
nameIdPolicyFormat:
|
||||
"Specifies the URI reference corresponding to a name identifier format.",
|
||||
principalType:
|
||||
"Way to identify and track external users from the assertion. Default is using Subject NameID, alternatively you can set up identifying attribute.",
|
||||
httpPostBindingResponse:
|
||||
"Indicates whether to respond to requests using HTTP-POST binding. If false, HTTP-REDIRECT binding will be used.",
|
||||
httpPostBindingAuthnRequest:
|
||||
"Indicates whether the AuthnRequest must be sent using HTTP-POST binding. If false, HTTP-REDIRECT binding will be used.",
|
||||
httpPostBindingLogout:
|
||||
"Indicates whether to respond to requests using HTTP-POST binding. If false, HTTP-REDIRECT binding will be used.",
|
||||
wantAuthnRequestsSigned:
|
||||
"Indicates whether the identity provider expects a signed AuthnRequest.",
|
||||
signatureAlgorithm: "The signature algorithm to use to sign documents.",
|
||||
samlSignatureKeyName:
|
||||
"Signed SAML documents contain identification of signing key in KeyName element. For Keycloak / RH-SSO counterparty, use KEY_ID, for MS AD FS use CERT_SUBJECT, for others check and use NONE if no other option works.",
|
||||
wantAssertionsSigned:
|
||||
"Indicates whether this service provider expects a signed Assertion.",
|
||||
wantAssertionsEncrypted:
|
||||
"Indicates whether this service provider expects an encrypted Assertion.",
|
||||
forceAuthentication:
|
||||
"Indicates whether the identity provider must authenticate the presenter directly rather than rely on a previous security context.",
|
||||
validateSignatures:
|
||||
"Enable/disable signature validation of SAML responses.",
|
||||
validatingX509Certs:
|
||||
"The certificate in PEM format that must be used to check for signatures. Multiple certificates can be entered, separated by comma (,).",
|
||||
signServiceProviderMetadata:
|
||||
"Enable/disable signature of the provider SAML metadata.",
|
||||
passSubject:
|
||||
"During login phase, forward an optional login_hint query parameter to SAML AuthnRequest's Subject.",
|
||||
comparison:
|
||||
'Specifies the comparison method used to evaluate the requested context classes or statements. The default is "Exact".',
|
||||
authnContextClassRefs: "Ordered list of requested AuthnContext ClassRefs.",
|
||||
authnContextDeclRefs: "Ordered list of requested AuthnContext DeclRefs.",
|
||||
},
|
||||
};
|
||||
|
|
|
@ -7,6 +7,7 @@ export default {
|
|||
addProvider: "Add provider",
|
||||
addKeycloakOpenIdProvider: "Add Keycloak OpenID Connect provider",
|
||||
addOpenIdProvider: "Add OpenID Connect provider",
|
||||
addSamlProvider: "Add SAML provider",
|
||||
manageDisplayOrder: "Manage display order",
|
||||
deleteProvider: "Delete provider?",
|
||||
deleteConfirm:
|
||||
|
@ -42,6 +43,35 @@ export default {
|
|||
displayName: "Display name",
|
||||
useDiscoveryEndpoint: "Use discovery endpoint",
|
||||
discoveryEndpoint: "Discovery endpoint",
|
||||
useEntityDescriptor: "Use entity descriptor",
|
||||
samlEntityDescriptor: "SAML entity descriptor",
|
||||
ssoServiceUrl: "Single Sign-On service URL",
|
||||
singleLogoutServiceUrl: "Single logout service URL",
|
||||
nameIdPolicyFormat: "NameID policy format",
|
||||
persistent: "Persistent",
|
||||
transient: "Transient",
|
||||
email: "Email",
|
||||
kerberos: "Kerberos",
|
||||
x509: "X.509 Subject Name",
|
||||
windowsDomainQN: "Windows Domain Qualified Name",
|
||||
unspecified: "Unspecified",
|
||||
principalType: "Principal type",
|
||||
subjectNameId: "Subject NameID",
|
||||
attributeName: "Attribute [Name]",
|
||||
attributeFriendlyName: "Attribute [Friendly Name]",
|
||||
httpPostBindingResponse: "HTTP-POST binding response",
|
||||
httpPostBindingAuthnRequest: "HTTP-POST binding for AuthnRequest",
|
||||
httpPostBindingLogout: "HTTP-POST binding logout",
|
||||
wantAuthnRequestsSigned: "Want AuthnRequests signed",
|
||||
signatureAlgorithm: "Signature algorithm",
|
||||
samlSignatureKeyName: "SAML signature key name",
|
||||
wantAssertionsSigned: "Want Assertions signed",
|
||||
wantAssertionsEncrypted: "Want Assertions encrypted",
|
||||
forceAuthentication: "Force authentication",
|
||||
validatingX509Certs: "Validating X509 certificates",
|
||||
signServiceProviderMetadata: "Sign service provider metadata",
|
||||
passSubject: "Pass subject",
|
||||
serviceProviderEntityId: "Service provider entity ID",
|
||||
importConfig: "Import config from file",
|
||||
showMetaData: "Show metadata",
|
||||
hideMetaData: "Hide metadata",
|
||||
|
@ -80,9 +110,18 @@ export default {
|
|||
allowedClockSkew: "Allowed clock skew",
|
||||
forwardParameters: "Forwarded query parameters",
|
||||
generalSettings: "General settings",
|
||||
oidcSettings: "OpenId Connect settings",
|
||||
oidcSettings: "OpenID Connect settings",
|
||||
samlSettings: "SAML settings",
|
||||
advancedSettings: "Advanced settings",
|
||||
reqAuthnConstraints: "Requested AuthnContext Constraints",
|
||||
keyID: "KEY_ID",
|
||||
NONE: "NONE",
|
||||
certSubject: "CERT_SUBJECT",
|
||||
storeTokens: "Store tokens",
|
||||
storedTokensReadable: "Stored tokens readable",
|
||||
comparison: "Comparison",
|
||||
authnContextClassRefs: "AuthnContext ClassRefs",
|
||||
authnContextDeclRefs: "AuthnContext DeclRefs",
|
||||
trustEmail: "Trust Email",
|
||||
accountLinkingOnly: "Account linking only",
|
||||
hideOnLoginPage: "Hide on login page",
|
||||
|
|
|
@ -2,12 +2,14 @@ import type { RouteDef } from "../route-config";
|
|||
import { IdentityProviderRoute } from "./routes/IdentityProvider";
|
||||
import { IdentityProviderKeycloakOidcRoute } from "./routes/IdentityProviderKeycloakOidc";
|
||||
import { IdentityProviderOidcRoute } from "./routes/IdentityProviderOidc";
|
||||
import { IdentityProviderSamlRoute } from "./routes/IdentityProviderSaml";
|
||||
import { IdentityProvidersRoute } from "./routes/IdentityProviders";
|
||||
import { IdentityProviderTabRoute } from "./routes/IdentityProviderTab";
|
||||
|
||||
const routes: RouteDef[] = [
|
||||
IdentityProvidersRoute,
|
||||
IdentityProviderOidcRoute,
|
||||
IdentityProviderSamlRoute,
|
||||
IdentityProviderKeycloakOidcRoute,
|
||||
IdentityProviderRoute,
|
||||
IdentityProviderTabRoute,
|
||||
|
|
19
src/identity-providers/routes/IdentityProviderSaml.ts
Normal file
19
src/identity-providers/routes/IdentityProviderSaml.ts
Normal file
|
@ -0,0 +1,19 @@
|
|||
import type { LocationDescriptorObject } from "history";
|
||||
import { generatePath } from "react-router-dom";
|
||||
import type { RouteDef } from "../../route-config";
|
||||
import { AddSamlConnect } from "../add/AddSamlConnect";
|
||||
|
||||
export type IdentityProviderSamlParams = { realm: string };
|
||||
|
||||
export const IdentityProviderSamlRoute: RouteDef = {
|
||||
path: "/:realm/identity-providers/saml",
|
||||
component: AddSamlConnect,
|
||||
breadcrumb: (t) => t("identity-providers:addSamlProvider"),
|
||||
access: "manage-identity-providers",
|
||||
};
|
||||
|
||||
export const toIdentityProviderSaml = (
|
||||
params: IdentityProviderSamlParams
|
||||
): LocationDescriptorObject => ({
|
||||
pathname: generatePath(IdentityProviderSamlRoute.path, params),
|
||||
});
|
Loading…
Reference in a new issue