add OIDC provider to the idp section (#553)
* initial version of oidc * fixed fetch and added missing fields * fixed file upload * added scopse * added details * added disable action and save * updated to use new design based on discovery response * new design * set default value * added tests * fixed tests * fixed labels * changed direction to up
This commit is contained in:
parent
84d621f70a
commit
c5ff588791
26 changed files with 1895 additions and 481 deletions
|
@ -24,7 +24,7 @@ describe("Identity provider test", () => {
|
||||||
sidebarPage.goToIdentityProviders();
|
sidebarPage.goToIdentityProviders();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should create provider", () => {
|
/*it("should create provider", () => {
|
||||||
createProviderPage.checkGitHubCardVisible().clickGitHubCard();
|
createProviderPage.checkGitHubCardVisible().clickGitHubCard();
|
||||||
|
|
||||||
createProviderPage.checkAddButtonDisabled();
|
createProviderPage.checkAddButtonDisabled();
|
||||||
|
@ -37,9 +37,7 @@ describe("Identity provider test", () => {
|
||||||
"Identity provider successfully created"
|
"Identity provider successfully created"
|
||||||
);
|
);
|
||||||
|
|
||||||
//TODO temporary refresh
|
sidebarPage.goToIdentityProviders();
|
||||||
sidebarPage.goToAuthentication().goToIdentityProviders();
|
|
||||||
|
|
||||||
listingPage.itemExist(identityProviderName);
|
listingPage.itemExist(identityProviderName);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -83,6 +81,27 @@ describe("Identity provider test", () => {
|
||||||
masthead.checkNotificationMessage(
|
masthead.checkNotificationMessage(
|
||||||
"Successfully changed display order of identity providers"
|
"Successfully changed display order of identity providers"
|
||||||
);
|
);
|
||||||
|
});*/
|
||||||
|
|
||||||
|
it("should create a oidc provider using discovery url", () => {
|
||||||
|
const oidcProviderName = "oidc";
|
||||||
|
|
||||||
|
createProviderPage
|
||||||
|
.clickCreateDropdown()
|
||||||
|
.clickItem(oidcProviderName)
|
||||||
|
.fillDiscoveryUrl(
|
||||||
|
"http://localhost:8180/auth/realms/master/.well-known/openid-configuration"
|
||||||
|
)
|
||||||
|
.shouldBeSuccessful()
|
||||||
|
.fill("oidc", "123")
|
||||||
|
.clickAdd();
|
||||||
|
masthead.checkNotificationMessage(
|
||||||
|
"Identity provider successfully created"
|
||||||
|
);
|
||||||
|
|
||||||
|
createProviderPage.shouldHaveAuthorizationUrl(
|
||||||
|
"http://localhost:8180/auth/realms/master/protocol/openid-connect/auth"
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("clean up providers", () => {
|
it("clean up providers", () => {
|
||||||
|
|
|
@ -90,7 +90,7 @@ export default class SidebarPage {
|
||||||
}
|
}
|
||||||
|
|
||||||
goToIdentityProviders() {
|
goToIdentityProviders() {
|
||||||
cy.get(this.identityProvidersBtn).click();
|
cy.get(this.identityProvidersBtn).scrollIntoView().click();
|
||||||
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,8 @@ export default class CreateProviderPage {
|
||||||
private clientIdField = "clientId";
|
private clientIdField = "clientId";
|
||||||
private clientIdError = "#kc-client-secret-helper";
|
private clientIdError = "#kc-client-secret-helper";
|
||||||
private clientSecretField = "clientSecret";
|
private clientSecretField = "clientSecret";
|
||||||
|
private discoveryEndpoint = "discoveryEndpoint";
|
||||||
|
private authorizationUrl = "authorizationUrl";
|
||||||
private addButton = "createProvider";
|
private addButton = "createProvider";
|
||||||
|
|
||||||
checkVisible(name: string) {
|
checkVisible(name: string) {
|
||||||
|
@ -65,4 +67,19 @@ export default class CreateProviderPage {
|
||||||
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fillDiscoveryUrl(value: string) {
|
||||||
|
cy.getId(this.discoveryEndpoint).type(value).blur();
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
shouldBeSuccessful() {
|
||||||
|
cy.getId(this.discoveryEndpoint).should("have.class", "pf-m-success");
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
shouldHaveAuthorizationUrl(value: string) {
|
||||||
|
cy.getId(this.authorizationUrl).should("have.value", value);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
{
|
{
|
||||||
"common-help": {
|
"common-help": {
|
||||||
"helpToggleInfo": "This toggle will enable / disable part of the help info in the console. Includes any help text, links and popovers.",
|
"helpToggleInfo": "This toggle will enable / disable part of the help info in the console. Includes any help text, links and popovers.",
|
||||||
"showPassword": "Show password field in clear text"
|
"showPassword": "Show password field in clear text",
|
||||||
|
"helpFileUpload": "Upload a JSON file"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,6 +5,7 @@ import {
|
||||||
Modal,
|
Modal,
|
||||||
ModalVariant,
|
ModalVariant,
|
||||||
Button,
|
Button,
|
||||||
|
FileUploadProps,
|
||||||
} from "@patternfly/react-core";
|
} from "@patternfly/react-core";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
|
@ -20,18 +21,22 @@ export type JsonFileUploadEvent =
|
||||||
| React.ChangeEvent<HTMLTextAreaElement> // User typed in the TextArea
|
| React.ChangeEvent<HTMLTextAreaElement> // User typed in the TextArea
|
||||||
| React.MouseEvent<HTMLButtonElement, MouseEvent>; // User clicked Clear button
|
| React.MouseEvent<HTMLButtonElement, MouseEvent>; // User clicked Clear button
|
||||||
|
|
||||||
export type JsonFileUploadProps = {
|
export type JsonFileUploadProps = FileUploadProps & {
|
||||||
id: string;
|
id: string;
|
||||||
onChange: (
|
onChange: (
|
||||||
value: string | File,
|
value: string | File,
|
||||||
filename: string,
|
filename: string,
|
||||||
event: JsonFileUploadEvent
|
event: JsonFileUploadEvent
|
||||||
) => void;
|
) => void;
|
||||||
|
helpText?: string;
|
||||||
|
unWrap?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const JsonFileUpload = ({
|
export const JsonFileUpload = ({
|
||||||
id,
|
id,
|
||||||
onChange,
|
onChange,
|
||||||
|
helpText = "common-help:helpFileUpload",
|
||||||
|
unWrap = false,
|
||||||
...rest
|
...rest
|
||||||
}: JsonFileUploadProps) => {
|
}: JsonFileUploadProps) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
@ -66,6 +71,23 @@ export const JsonFileUpload = ({
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const JsonFileUploadComp = () => (
|
||||||
|
<FileUpload
|
||||||
|
id={id}
|
||||||
|
{...rest}
|
||||||
|
type="text"
|
||||||
|
value={fileUpload.value}
|
||||||
|
filename={fileUpload.filename}
|
||||||
|
onChange={handleChange}
|
||||||
|
onReadStarted={() => setFileUpload({ ...fileUpload, isLoading: true })}
|
||||||
|
onReadFinished={() => setFileUpload({ ...fileUpload, isLoading: false })}
|
||||||
|
isLoading={fileUpload.isLoading}
|
||||||
|
dropzoneProps={{
|
||||||
|
accept: ".json",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{fileUpload.modal && (
|
{fileUpload.modal && (
|
||||||
|
@ -93,30 +115,16 @@ export const JsonFileUpload = ({
|
||||||
{t("clearFileExplain")}
|
{t("clearFileExplain")}
|
||||||
</Modal>
|
</Modal>
|
||||||
)}
|
)}
|
||||||
<FormGroup
|
{unWrap && <JsonFileUploadComp />}
|
||||||
label={t("resourceFile")}
|
{!unWrap && (
|
||||||
fieldId={id}
|
<FormGroup
|
||||||
helperText="Upload a JSON file"
|
label={t("resourceFile")}
|
||||||
>
|
fieldId={id}
|
||||||
<FileUpload
|
helperText={t(helpText)}
|
||||||
id={id}
|
>
|
||||||
{...rest}
|
<JsonFileUploadComp />
|
||||||
type="text"
|
</FormGroup>
|
||||||
value={fileUpload.value}
|
)}
|
||||||
filename={fileUpload.filename}
|
|
||||||
onChange={handleChange}
|
|
||||||
onReadStarted={() =>
|
|
||||||
setFileUpload({ ...fileUpload, isLoading: true })
|
|
||||||
}
|
|
||||||
onReadFinished={() =>
|
|
||||||
setFileUpload({ ...fileUpload, isLoading: false })
|
|
||||||
}
|
|
||||||
isLoading={fileUpload.isLoading}
|
|
||||||
dropzoneProps={{
|
|
||||||
accept: ".json",
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</FormGroup>
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -7,7 +7,7 @@ exports[`<JsonFileUpload /> render 1`] = `
|
||||||
>
|
>
|
||||||
<FormGroup
|
<FormGroup
|
||||||
fieldId="test"
|
fieldId="test"
|
||||||
helperText="Upload a JSON file"
|
helperText="helpFileUpload"
|
||||||
label="resourceFile"
|
label="resourceFile"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
|
@ -31,56 +31,42 @@ exports[`<JsonFileUpload /> render 1`] = `
|
||||||
<div
|
<div
|
||||||
className="pf-c-form__group-control"
|
className="pf-c-form__group-control"
|
||||||
>
|
>
|
||||||
<FileUpload
|
<JsonFileUploadComp>
|
||||||
dropzoneProps={
|
<FileUpload
|
||||||
Object {
|
dropzoneProps={
|
||||||
"accept": ".json",
|
Object {
|
||||||
|
"accept": ".json",
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
filename=""
|
||||||
filename=""
|
id="test"
|
||||||
id="test"
|
isLoading={false}
|
||||||
isLoading={false}
|
onChange={[Function]}
|
||||||
onChange={[Function]}
|
onReadFinished={[Function]}
|
||||||
onReadFinished={[Function]}
|
onReadStarted={[Function]}
|
||||||
onReadStarted={[Function]}
|
type="text"
|
||||||
type="text"
|
value=""
|
||||||
value=""
|
|
||||||
>
|
|
||||||
<t
|
|
||||||
accept=".json"
|
|
||||||
disabled={false}
|
|
||||||
getDataTransferItems={[Function]}
|
|
||||||
maxSize={Infinity}
|
|
||||||
minSize={0}
|
|
||||||
multiple={false}
|
|
||||||
onDropAccepted={[Function]}
|
|
||||||
onDropRejected={[Function]}
|
|
||||||
preventDropOnDocument={true}
|
|
||||||
>
|
>
|
||||||
<FileUploadField
|
<t
|
||||||
containerRef={[Function]}
|
accept=".json"
|
||||||
filename=""
|
disabled={false}
|
||||||
id="test"
|
getDataTransferItems={[Function]}
|
||||||
isLoading={false}
|
maxSize={Infinity}
|
||||||
onBlur={[Function]}
|
minSize={0}
|
||||||
onBrowseButtonClick={[Function]}
|
multiple={false}
|
||||||
onChange={[Function]}
|
onDropAccepted={[Function]}
|
||||||
onClearButtonClick={[Function]}
|
onDropRejected={[Function]}
|
||||||
onClick={[Function]}
|
preventDropOnDocument={true}
|
||||||
onDragEnter={[Function]}
|
|
||||||
onDragLeave={[Function]}
|
|
||||||
onDragOver={[Function]}
|
|
||||||
onDragStart={[Function]}
|
|
||||||
onDrop={[Function]}
|
|
||||||
onFocus={[Function]}
|
|
||||||
onKeyDown={[Function]}
|
|
||||||
tabIndex={null}
|
|
||||||
type="text"
|
|
||||||
value=""
|
|
||||||
>
|
>
|
||||||
<div
|
<FileUploadField
|
||||||
className="pf-c-file-upload"
|
containerRef={[Function]}
|
||||||
|
filename=""
|
||||||
|
id="test"
|
||||||
|
isLoading={false}
|
||||||
onBlur={[Function]}
|
onBlur={[Function]}
|
||||||
|
onBrowseButtonClick={[Function]}
|
||||||
|
onChange={[Function]}
|
||||||
|
onClearButtonClick={[Function]}
|
||||||
onClick={[Function]}
|
onClick={[Function]}
|
||||||
onDragEnter={[Function]}
|
onDragEnter={[Function]}
|
||||||
onDragLeave={[Function]}
|
onDragLeave={[Function]}
|
||||||
|
@ -90,131 +76,131 @@ exports[`<JsonFileUpload /> render 1`] = `
|
||||||
onFocus={[Function]}
|
onFocus={[Function]}
|
||||||
onKeyDown={[Function]}
|
onKeyDown={[Function]}
|
||||||
tabIndex={null}
|
tabIndex={null}
|
||||||
|
type="text"
|
||||||
|
value=""
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className="pf-c-file-upload__file-select"
|
className="pf-c-file-upload"
|
||||||
|
onBlur={[Function]}
|
||||||
|
onClick={[Function]}
|
||||||
|
onDragEnter={[Function]}
|
||||||
|
onDragLeave={[Function]}
|
||||||
|
onDragOver={[Function]}
|
||||||
|
onDragStart={[Function]}
|
||||||
|
onDrop={[Function]}
|
||||||
|
onFocus={[Function]}
|
||||||
|
onKeyDown={[Function]}
|
||||||
|
tabIndex={null}
|
||||||
>
|
>
|
||||||
<InputGroup>
|
<div
|
||||||
<div
|
className="pf-c-file-upload__file-select"
|
||||||
className="pf-c-input-group"
|
>
|
||||||
>
|
<InputGroup>
|
||||||
<TextInput
|
<div
|
||||||
aria-describedby="test-browse-button"
|
className="pf-c-input-group"
|
||||||
aria-label="Drag a file here or browse to upload"
|
|
||||||
id="test-filename"
|
|
||||||
isDisabled={false}
|
|
||||||
isReadOnly={true}
|
|
||||||
key=".0"
|
|
||||||
name="test-filename"
|
|
||||||
placeholder="Drag a file here or browse to upload"
|
|
||||||
value=""
|
|
||||||
>
|
>
|
||||||
<TextInputBase
|
<TextInput
|
||||||
aria-describedby="test-browse-button"
|
aria-describedby="test-browse-button"
|
||||||
aria-label="Drag a file here or browse to upload"
|
aria-label="Drag a file here or browse to upload"
|
||||||
className=""
|
|
||||||
id="test-filename"
|
id="test-filename"
|
||||||
innerRef={null}
|
|
||||||
isDisabled={false}
|
isDisabled={false}
|
||||||
isLeftTruncated={false}
|
|
||||||
isReadOnly={true}
|
isReadOnly={true}
|
||||||
isRequired={false}
|
key=".0"
|
||||||
name="test-filename"
|
name="test-filename"
|
||||||
onChange={[Function]}
|
|
||||||
placeholder="Drag a file here or browse to upload"
|
placeholder="Drag a file here or browse to upload"
|
||||||
type="text"
|
|
||||||
validated="default"
|
|
||||||
value=""
|
value=""
|
||||||
>
|
>
|
||||||
<input
|
<TextInputBase
|
||||||
aria-describedby="test-browse-button"
|
aria-describedby="test-browse-button"
|
||||||
aria-invalid={false}
|
|
||||||
aria-label="Drag a file here or browse to upload"
|
aria-label="Drag a file here or browse to upload"
|
||||||
className="pf-c-form-control"
|
className=""
|
||||||
disabled={false}
|
|
||||||
id="test-filename"
|
id="test-filename"
|
||||||
|
innerRef={null}
|
||||||
|
isDisabled={false}
|
||||||
|
isLeftTruncated={false}
|
||||||
|
isReadOnly={true}
|
||||||
|
isRequired={false}
|
||||||
name="test-filename"
|
name="test-filename"
|
||||||
onBlur={[Function]}
|
|
||||||
onChange={[Function]}
|
onChange={[Function]}
|
||||||
onFocus={[Function]}
|
|
||||||
placeholder="Drag a file here or browse to upload"
|
placeholder="Drag a file here or browse to upload"
|
||||||
readOnly={true}
|
|
||||||
required={false}
|
|
||||||
type="text"
|
type="text"
|
||||||
|
validated="default"
|
||||||
value=""
|
value=""
|
||||||
/>
|
>
|
||||||
</TextInputBase>
|
<input
|
||||||
</TextInput>
|
aria-describedby="test-browse-button"
|
||||||
<Button
|
aria-invalid={false}
|
||||||
id="test-browse-button"
|
aria-label="Drag a file here or browse to upload"
|
||||||
isDisabled={false}
|
className="pf-c-form-control"
|
||||||
key=".1"
|
disabled={false}
|
||||||
onClick={[Function]}
|
id="test-filename"
|
||||||
variant="control"
|
name="test-filename"
|
||||||
>
|
onBlur={[Function]}
|
||||||
<button
|
onChange={[Function]}
|
||||||
aria-disabled={false}
|
onFocus={[Function]}
|
||||||
aria-label={null}
|
placeholder="Drag a file here or browse to upload"
|
||||||
className="pf-c-button pf-m-control"
|
readOnly={true}
|
||||||
data-ouia-component-id="OUIA-Generated-Button-control-1"
|
required={false}
|
||||||
data-ouia-component-type="PF4/Button"
|
type="text"
|
||||||
data-ouia-safe={true}
|
value=""
|
||||||
disabled={false}
|
/>
|
||||||
|
</TextInputBase>
|
||||||
|
</TextInput>
|
||||||
|
<Button
|
||||||
id="test-browse-button"
|
id="test-browse-button"
|
||||||
|
isDisabled={false}
|
||||||
|
key=".1"
|
||||||
onClick={[Function]}
|
onClick={[Function]}
|
||||||
role={null}
|
variant="control"
|
||||||
type="button"
|
|
||||||
>
|
>
|
||||||
Browse...
|
<button
|
||||||
</button>
|
aria-disabled={false}
|
||||||
</Button>
|
aria-label={null}
|
||||||
<Button
|
className="pf-c-button pf-m-control"
|
||||||
isDisabled={true}
|
data-ouia-component-id="OUIA-Generated-Button-control-1"
|
||||||
key=".2"
|
data-ouia-component-type="PF4/Button"
|
||||||
onClick={[Function]}
|
data-ouia-safe={true}
|
||||||
variant="control"
|
disabled={false}
|
||||||
>
|
id="test-browse-button"
|
||||||
<button
|
onClick={[Function]}
|
||||||
aria-disabled={true}
|
role={null}
|
||||||
aria-label={null}
|
type="button"
|
||||||
className="pf-c-button pf-m-control pf-m-disabled"
|
>
|
||||||
data-ouia-component-id="OUIA-Generated-Button-control-2"
|
Browse...
|
||||||
data-ouia-component-type="PF4/Button"
|
</button>
|
||||||
data-ouia-safe={true}
|
</Button>
|
||||||
disabled={true}
|
<Button
|
||||||
|
isDisabled={true}
|
||||||
|
key=".2"
|
||||||
onClick={[Function]}
|
onClick={[Function]}
|
||||||
role={null}
|
variant="control"
|
||||||
tabIndex={null}
|
|
||||||
type="button"
|
|
||||||
>
|
>
|
||||||
Clear
|
<button
|
||||||
</button>
|
aria-disabled={true}
|
||||||
</Button>
|
aria-label={null}
|
||||||
</div>
|
className="pf-c-button pf-m-control pf-m-disabled"
|
||||||
</InputGroup>
|
data-ouia-component-id="OUIA-Generated-Button-control-2"
|
||||||
</div>
|
data-ouia-component-type="PF4/Button"
|
||||||
<div
|
data-ouia-safe={true}
|
||||||
className="pf-c-file-upload__file-details"
|
disabled={true}
|
||||||
>
|
onClick={[Function]}
|
||||||
<TextArea
|
role={null}
|
||||||
aria-label="File upload"
|
tabIndex={null}
|
||||||
disabled={false}
|
type="button"
|
||||||
id="test"
|
>
|
||||||
isRequired={false}
|
Clear
|
||||||
name="test"
|
</button>
|
||||||
onChange={[Function]}
|
</Button>
|
||||||
readOnly={false}
|
</div>
|
||||||
resizeOrientation="vertical"
|
</InputGroup>
|
||||||
validated="default"
|
</div>
|
||||||
value=""
|
<div
|
||||||
|
className="pf-c-file-upload__file-details"
|
||||||
>
|
>
|
||||||
<TextArea
|
<TextArea
|
||||||
aria-label="File upload"
|
aria-label="File upload"
|
||||||
className=""
|
|
||||||
disabled={false}
|
disabled={false}
|
||||||
id="test"
|
id="test"
|
||||||
innerRef={null}
|
|
||||||
isDisabled={false}
|
|
||||||
isRequired={false}
|
isRequired={false}
|
||||||
name="test"
|
name="test"
|
||||||
onChange={[Function]}
|
onChange={[Function]}
|
||||||
|
@ -223,45 +209,61 @@ exports[`<JsonFileUpload /> render 1`] = `
|
||||||
validated="default"
|
validated="default"
|
||||||
value=""
|
value=""
|
||||||
>
|
>
|
||||||
<textarea
|
<TextArea
|
||||||
aria-invalid={false}
|
|
||||||
aria-label="File upload"
|
aria-label="File upload"
|
||||||
className="pf-c-form-control pf-m-resize-vertical"
|
className=""
|
||||||
disabled={false}
|
disabled={false}
|
||||||
id="test"
|
id="test"
|
||||||
|
innerRef={null}
|
||||||
|
isDisabled={false}
|
||||||
|
isRequired={false}
|
||||||
name="test"
|
name="test"
|
||||||
onChange={[Function]}
|
onChange={[Function]}
|
||||||
readOnly={false}
|
readOnly={false}
|
||||||
required={false}
|
resizeOrientation="vertical"
|
||||||
|
validated="default"
|
||||||
value=""
|
value=""
|
||||||
/>
|
>
|
||||||
|
<textarea
|
||||||
|
aria-invalid={false}
|
||||||
|
aria-label="File upload"
|
||||||
|
className="pf-c-form-control pf-m-resize-vertical"
|
||||||
|
disabled={false}
|
||||||
|
id="test"
|
||||||
|
name="test"
|
||||||
|
onChange={[Function]}
|
||||||
|
readOnly={false}
|
||||||
|
required={false}
|
||||||
|
value=""
|
||||||
|
/>
|
||||||
|
</TextArea>
|
||||||
</TextArea>
|
</TextArea>
|
||||||
</TextArea>
|
</div>
|
||||||
</div>
|
<input
|
||||||
<input
|
accept=".json"
|
||||||
accept=".json"
|
autoComplete="off"
|
||||||
autoComplete="off"
|
multiple={false}
|
||||||
multiple={false}
|
onChange={[Function]}
|
||||||
onChange={[Function]}
|
onClick={[Function]}
|
||||||
onClick={[Function]}
|
style={
|
||||||
style={
|
Object {
|
||||||
Object {
|
"display": "none",
|
||||||
"display": "none",
|
}
|
||||||
}
|
}
|
||||||
}
|
tabIndex={-1}
|
||||||
tabIndex={-1}
|
type="file"
|
||||||
type="file"
|
/>
|
||||||
/>
|
</div>
|
||||||
</div>
|
</FileUploadField>
|
||||||
</FileUploadField>
|
</t>
|
||||||
</t>
|
</FileUpload>
|
||||||
</FileUpload>
|
</JsonFileUploadComp>
|
||||||
<div
|
<div
|
||||||
aria-live="polite"
|
aria-live="polite"
|
||||||
className="pf-c-form__helper-text"
|
className="pf-c-form__helper-text"
|
||||||
id="test-helper"
|
id="test-helper"
|
||||||
>
|
>
|
||||||
Upload a JSON file
|
helpFileUpload
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -276,7 +278,7 @@ exports[`<JsonFileUpload /> upload file 1`] = `
|
||||||
>
|
>
|
||||||
<FormGroup
|
<FormGroup
|
||||||
fieldId="upload"
|
fieldId="upload"
|
||||||
helperText="Upload a JSON file"
|
helperText="helpFileUpload"
|
||||||
label="resourceFile"
|
label="resourceFile"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
|
@ -300,57 +302,43 @@ exports[`<JsonFileUpload /> upload file 1`] = `
|
||||||
<div
|
<div
|
||||||
className="pf-c-form__group-control"
|
className="pf-c-form__group-control"
|
||||||
>
|
>
|
||||||
<FileUpload
|
<JsonFileUploadComp>
|
||||||
dropzoneProps={
|
<FileUpload
|
||||||
Object {
|
dropzoneProps={
|
||||||
"accept": ".json",
|
Object {
|
||||||
|
"accept": ".json",
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
filename=""
|
||||||
filename=""
|
id="upload"
|
||||||
id="upload"
|
isLoading={false}
|
||||||
isLoading={false}
|
onChange={[Function]}
|
||||||
onChange={[Function]}
|
onReadFinished={[Function]}
|
||||||
onReadFinished={[Function]}
|
onReadStarted={[Function]}
|
||||||
onReadStarted={[Function]}
|
type="text"
|
||||||
type="text"
|
value=""
|
||||||
value=""
|
|
||||||
>
|
|
||||||
<t
|
|
||||||
accept=".json"
|
|
||||||
disabled={false}
|
|
||||||
getDataTransferItems={[Function]}
|
|
||||||
maxSize={Infinity}
|
|
||||||
minSize={0}
|
|
||||||
multiple={false}
|
|
||||||
onDropAccepted={[Function]}
|
|
||||||
onDropRejected={[Function]}
|
|
||||||
preventDropOnDocument={true}
|
|
||||||
>
|
>
|
||||||
<FileUploadField
|
<t
|
||||||
containerRef={[Function]}
|
accept=".json"
|
||||||
filename=""
|
disabled={false}
|
||||||
id="upload"
|
getDataTransferItems={[Function]}
|
||||||
isDragActive={false}
|
maxSize={Infinity}
|
||||||
isLoading={false}
|
minSize={0}
|
||||||
onBlur={[Function]}
|
multiple={false}
|
||||||
onBrowseButtonClick={[Function]}
|
onDropAccepted={[Function]}
|
||||||
onChange={[Function]}
|
onDropRejected={[Function]}
|
||||||
onClearButtonClick={[Function]}
|
preventDropOnDocument={true}
|
||||||
onClick={[Function]}
|
|
||||||
onDragEnter={[Function]}
|
|
||||||
onDragLeave={[Function]}
|
|
||||||
onDragOver={[Function]}
|
|
||||||
onDragStart={[Function]}
|
|
||||||
onDrop={[Function]}
|
|
||||||
onFocus={[Function]}
|
|
||||||
onKeyDown={[Function]}
|
|
||||||
tabIndex={null}
|
|
||||||
type="text"
|
|
||||||
value=""
|
|
||||||
>
|
>
|
||||||
<div
|
<FileUploadField
|
||||||
className="pf-c-file-upload"
|
containerRef={[Function]}
|
||||||
|
filename=""
|
||||||
|
id="upload"
|
||||||
|
isDragActive={false}
|
||||||
|
isLoading={false}
|
||||||
onBlur={[Function]}
|
onBlur={[Function]}
|
||||||
|
onBrowseButtonClick={[Function]}
|
||||||
|
onChange={[Function]}
|
||||||
|
onClearButtonClick={[Function]}
|
||||||
onClick={[Function]}
|
onClick={[Function]}
|
||||||
onDragEnter={[Function]}
|
onDragEnter={[Function]}
|
||||||
onDragLeave={[Function]}
|
onDragLeave={[Function]}
|
||||||
|
@ -360,131 +348,131 @@ exports[`<JsonFileUpload /> upload file 1`] = `
|
||||||
onFocus={[Function]}
|
onFocus={[Function]}
|
||||||
onKeyDown={[Function]}
|
onKeyDown={[Function]}
|
||||||
tabIndex={null}
|
tabIndex={null}
|
||||||
|
type="text"
|
||||||
|
value=""
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className="pf-c-file-upload__file-select"
|
className="pf-c-file-upload"
|
||||||
|
onBlur={[Function]}
|
||||||
|
onClick={[Function]}
|
||||||
|
onDragEnter={[Function]}
|
||||||
|
onDragLeave={[Function]}
|
||||||
|
onDragOver={[Function]}
|
||||||
|
onDragStart={[Function]}
|
||||||
|
onDrop={[Function]}
|
||||||
|
onFocus={[Function]}
|
||||||
|
onKeyDown={[Function]}
|
||||||
|
tabIndex={null}
|
||||||
>
|
>
|
||||||
<InputGroup>
|
<div
|
||||||
<div
|
className="pf-c-file-upload__file-select"
|
||||||
className="pf-c-input-group"
|
>
|
||||||
>
|
<InputGroup>
|
||||||
<TextInput
|
<div
|
||||||
aria-describedby="upload-browse-button"
|
className="pf-c-input-group"
|
||||||
aria-label="Drag a file here or browse to upload"
|
|
||||||
id="upload-filename"
|
|
||||||
isDisabled={false}
|
|
||||||
isReadOnly={true}
|
|
||||||
key=".0"
|
|
||||||
name="upload-filename"
|
|
||||||
placeholder="Drag a file here or browse to upload"
|
|
||||||
value=""
|
|
||||||
>
|
>
|
||||||
<TextInputBase
|
<TextInput
|
||||||
aria-describedby="upload-browse-button"
|
aria-describedby="upload-browse-button"
|
||||||
aria-label="Drag a file here or browse to upload"
|
aria-label="Drag a file here or browse to upload"
|
||||||
className=""
|
|
||||||
id="upload-filename"
|
id="upload-filename"
|
||||||
innerRef={null}
|
|
||||||
isDisabled={false}
|
isDisabled={false}
|
||||||
isLeftTruncated={false}
|
|
||||||
isReadOnly={true}
|
isReadOnly={true}
|
||||||
isRequired={false}
|
key=".0"
|
||||||
name="upload-filename"
|
name="upload-filename"
|
||||||
onChange={[Function]}
|
|
||||||
placeholder="Drag a file here or browse to upload"
|
placeholder="Drag a file here or browse to upload"
|
||||||
type="text"
|
|
||||||
validated="default"
|
|
||||||
value=""
|
value=""
|
||||||
>
|
>
|
||||||
<input
|
<TextInputBase
|
||||||
aria-describedby="upload-browse-button"
|
aria-describedby="upload-browse-button"
|
||||||
aria-invalid={false}
|
|
||||||
aria-label="Drag a file here or browse to upload"
|
aria-label="Drag a file here or browse to upload"
|
||||||
className="pf-c-form-control"
|
className=""
|
||||||
disabled={false}
|
|
||||||
id="upload-filename"
|
id="upload-filename"
|
||||||
|
innerRef={null}
|
||||||
|
isDisabled={false}
|
||||||
|
isLeftTruncated={false}
|
||||||
|
isReadOnly={true}
|
||||||
|
isRequired={false}
|
||||||
name="upload-filename"
|
name="upload-filename"
|
||||||
onBlur={[Function]}
|
|
||||||
onChange={[Function]}
|
onChange={[Function]}
|
||||||
onFocus={[Function]}
|
|
||||||
placeholder="Drag a file here or browse to upload"
|
placeholder="Drag a file here or browse to upload"
|
||||||
readOnly={true}
|
|
||||||
required={false}
|
|
||||||
type="text"
|
type="text"
|
||||||
|
validated="default"
|
||||||
value=""
|
value=""
|
||||||
/>
|
>
|
||||||
</TextInputBase>
|
<input
|
||||||
</TextInput>
|
aria-describedby="upload-browse-button"
|
||||||
<Button
|
aria-invalid={false}
|
||||||
id="upload-browse-button"
|
aria-label="Drag a file here or browse to upload"
|
||||||
isDisabled={false}
|
className="pf-c-form-control"
|
||||||
key=".1"
|
disabled={false}
|
||||||
onClick={[Function]}
|
id="upload-filename"
|
||||||
variant="control"
|
name="upload-filename"
|
||||||
>
|
onBlur={[Function]}
|
||||||
<button
|
onChange={[Function]}
|
||||||
aria-disabled={false}
|
onFocus={[Function]}
|
||||||
aria-label={null}
|
placeholder="Drag a file here or browse to upload"
|
||||||
className="pf-c-button pf-m-control"
|
readOnly={true}
|
||||||
data-ouia-component-id="OUIA-Generated-Button-control-3"
|
required={false}
|
||||||
data-ouia-component-type="PF4/Button"
|
type="text"
|
||||||
data-ouia-safe={true}
|
value=""
|
||||||
disabled={false}
|
/>
|
||||||
|
</TextInputBase>
|
||||||
|
</TextInput>
|
||||||
|
<Button
|
||||||
id="upload-browse-button"
|
id="upload-browse-button"
|
||||||
|
isDisabled={false}
|
||||||
|
key=".1"
|
||||||
onClick={[Function]}
|
onClick={[Function]}
|
||||||
role={null}
|
variant="control"
|
||||||
type="button"
|
|
||||||
>
|
>
|
||||||
Browse...
|
<button
|
||||||
</button>
|
aria-disabled={false}
|
||||||
</Button>
|
aria-label={null}
|
||||||
<Button
|
className="pf-c-button pf-m-control"
|
||||||
isDisabled={true}
|
data-ouia-component-id="OUIA-Generated-Button-control-3"
|
||||||
key=".2"
|
data-ouia-component-type="PF4/Button"
|
||||||
onClick={[Function]}
|
data-ouia-safe={true}
|
||||||
variant="control"
|
disabled={false}
|
||||||
>
|
id="upload-browse-button"
|
||||||
<button
|
onClick={[Function]}
|
||||||
aria-disabled={true}
|
role={null}
|
||||||
aria-label={null}
|
type="button"
|
||||||
className="pf-c-button pf-m-control pf-m-disabled"
|
>
|
||||||
data-ouia-component-id="OUIA-Generated-Button-control-4"
|
Browse...
|
||||||
data-ouia-component-type="PF4/Button"
|
</button>
|
||||||
data-ouia-safe={true}
|
</Button>
|
||||||
disabled={true}
|
<Button
|
||||||
|
isDisabled={true}
|
||||||
|
key=".2"
|
||||||
onClick={[Function]}
|
onClick={[Function]}
|
||||||
role={null}
|
variant="control"
|
||||||
tabIndex={null}
|
|
||||||
type="button"
|
|
||||||
>
|
>
|
||||||
Clear
|
<button
|
||||||
</button>
|
aria-disabled={true}
|
||||||
</Button>
|
aria-label={null}
|
||||||
</div>
|
className="pf-c-button pf-m-control pf-m-disabled"
|
||||||
</InputGroup>
|
data-ouia-component-id="OUIA-Generated-Button-control-4"
|
||||||
</div>
|
data-ouia-component-type="PF4/Button"
|
||||||
<div
|
data-ouia-safe={true}
|
||||||
className="pf-c-file-upload__file-details"
|
disabled={true}
|
||||||
>
|
onClick={[Function]}
|
||||||
<TextArea
|
role={null}
|
||||||
aria-label="File upload"
|
tabIndex={null}
|
||||||
disabled={false}
|
type="button"
|
||||||
id="upload"
|
>
|
||||||
isRequired={false}
|
Clear
|
||||||
name="upload"
|
</button>
|
||||||
onChange={[Function]}
|
</Button>
|
||||||
readOnly={false}
|
</div>
|
||||||
resizeOrientation="vertical"
|
</InputGroup>
|
||||||
validated="default"
|
</div>
|
||||||
value=""
|
<div
|
||||||
|
className="pf-c-file-upload__file-details"
|
||||||
>
|
>
|
||||||
<TextArea
|
<TextArea
|
||||||
aria-label="File upload"
|
aria-label="File upload"
|
||||||
className=""
|
|
||||||
disabled={false}
|
disabled={false}
|
||||||
id="upload"
|
id="upload"
|
||||||
innerRef={null}
|
|
||||||
isDisabled={false}
|
|
||||||
isRequired={false}
|
isRequired={false}
|
||||||
name="upload"
|
name="upload"
|
||||||
onChange={[Function]}
|
onChange={[Function]}
|
||||||
|
@ -493,45 +481,61 @@ exports[`<JsonFileUpload /> upload file 1`] = `
|
||||||
validated="default"
|
validated="default"
|
||||||
value=""
|
value=""
|
||||||
>
|
>
|
||||||
<textarea
|
<TextArea
|
||||||
aria-invalid={false}
|
|
||||||
aria-label="File upload"
|
aria-label="File upload"
|
||||||
className="pf-c-form-control pf-m-resize-vertical"
|
className=""
|
||||||
disabled={false}
|
disabled={false}
|
||||||
id="upload"
|
id="upload"
|
||||||
|
innerRef={null}
|
||||||
|
isDisabled={false}
|
||||||
|
isRequired={false}
|
||||||
name="upload"
|
name="upload"
|
||||||
onChange={[Function]}
|
onChange={[Function]}
|
||||||
readOnly={false}
|
readOnly={false}
|
||||||
required={false}
|
resizeOrientation="vertical"
|
||||||
|
validated="default"
|
||||||
value=""
|
value=""
|
||||||
/>
|
>
|
||||||
|
<textarea
|
||||||
|
aria-invalid={false}
|
||||||
|
aria-label="File upload"
|
||||||
|
className="pf-c-form-control pf-m-resize-vertical"
|
||||||
|
disabled={false}
|
||||||
|
id="upload"
|
||||||
|
name="upload"
|
||||||
|
onChange={[Function]}
|
||||||
|
readOnly={false}
|
||||||
|
required={false}
|
||||||
|
value=""
|
||||||
|
/>
|
||||||
|
</TextArea>
|
||||||
</TextArea>
|
</TextArea>
|
||||||
</TextArea>
|
</div>
|
||||||
</div>
|
<input
|
||||||
<input
|
accept=".json"
|
||||||
accept=".json"
|
autoComplete="off"
|
||||||
autoComplete="off"
|
multiple={false}
|
||||||
multiple={false}
|
onChange={[Function]}
|
||||||
onChange={[Function]}
|
onClick={[Function]}
|
||||||
onClick={[Function]}
|
style={
|
||||||
style={
|
Object {
|
||||||
Object {
|
"display": "none",
|
||||||
"display": "none",
|
}
|
||||||
}
|
}
|
||||||
}
|
tabIndex={-1}
|
||||||
tabIndex={-1}
|
type="file"
|
||||||
type="file"
|
/>
|
||||||
/>
|
</div>
|
||||||
</div>
|
</FileUploadField>
|
||||||
</FileUploadField>
|
</t>
|
||||||
</t>
|
</FileUpload>
|
||||||
</FileUpload>
|
</JsonFileUploadComp>
|
||||||
<div
|
<div
|
||||||
aria-live="polite"
|
aria-live="polite"
|
||||||
className="pf-c-form__helper-text"
|
className="pf-c-form__helper-text"
|
||||||
id="upload-helper"
|
id="upload-helper"
|
||||||
>
|
>
|
||||||
Upload a JSON file
|
helpFileUpload
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
39
src/identity-providers/OIDCConfigurationRepresentation.ts
Normal file
39
src/identity-providers/OIDCConfigurationRepresentation.ts
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
export interface OIDCConfigurationRepresentation {
|
||||||
|
issuer?: string;
|
||||||
|
authorization_endpoint?: string;
|
||||||
|
token_endpoint?: string;
|
||||||
|
introspection_endpoint?: string;
|
||||||
|
userinfo_endpoint?: string;
|
||||||
|
end_session_endpoint?: string;
|
||||||
|
jwks_uri?: string;
|
||||||
|
check_session_iframe?: string;
|
||||||
|
grant_types_supported?: string[];
|
||||||
|
response_types_supported?: string[];
|
||||||
|
subject_types_supported?: string[];
|
||||||
|
id_token_signing_alg_values_supported?: string[];
|
||||||
|
id_token_encryption_alg_values_supported?: string[];
|
||||||
|
id_token_encryption_enc_values_supported?: string[];
|
||||||
|
userinfo_signing_alg_values_supported?: string[];
|
||||||
|
request_object_signing_alg_values_supported?: string[];
|
||||||
|
response_modes_supported?: string[];
|
||||||
|
registration_endpoint?: string;
|
||||||
|
token_endpoint_auth_methods_supported?: string[];
|
||||||
|
token_endpoint_auth_signing_alg_values_supported?: string[];
|
||||||
|
introspection_endpoint_auth_methods_supported?: string[];
|
||||||
|
introspection_endpoint_auth_signing_alg_values_supported?: string[];
|
||||||
|
claims_supported?: string[];
|
||||||
|
claim_types_supported?: string[];
|
||||||
|
claims_parameter_supported?: boolean;
|
||||||
|
scopes_supported?: string[];
|
||||||
|
request_parameter_supported?: boolean;
|
||||||
|
request_uri_parameter_supported?: boolean;
|
||||||
|
require_request_uri_registration?: boolean;
|
||||||
|
code_challenge_methods_supported?: string[];
|
||||||
|
tls_client_certificate_bound_access_tokens?: boolean;
|
||||||
|
revocation_endpoint?: string;
|
||||||
|
revocation_endpoint_auth_methods_supported?: string[];
|
||||||
|
revocation_endpoint_auth_signing_alg_values_supported?: string[];
|
||||||
|
backchannel_logout_supported?: boolean;
|
||||||
|
backchannel_logout_session_supported?: boolean;
|
||||||
|
device_authorization_endpoint?: string;
|
||||||
|
}
|
|
@ -1,47 +1,37 @@
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { useHistory, useParams } from "react-router-dom";
|
import { useHistory, useParams } from "react-router-dom";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { Controller, useForm } from "react-hook-form";
|
import { FormProvider, useForm } from "react-hook-form";
|
||||||
import {
|
import {
|
||||||
ActionGroup,
|
ActionGroup,
|
||||||
AlertVariant,
|
AlertVariant,
|
||||||
Button,
|
Button,
|
||||||
ClipboardCopy,
|
|
||||||
FormGroup,
|
|
||||||
NumberInput,
|
|
||||||
PageSection,
|
PageSection,
|
||||||
TextInput,
|
|
||||||
ValidatedOptions,
|
|
||||||
} from "@patternfly/react-core";
|
} from "@patternfly/react-core";
|
||||||
|
|
||||||
import IdentityProviderRepresentation from "keycloak-admin/lib/defs/identityProviderRepresentation";
|
import IdentityProviderRepresentation from "keycloak-admin/lib/defs/identityProviderRepresentation";
|
||||||
import { ViewHeader } from "../../components/view-header/ViewHeader";
|
import { ViewHeader } from "../../components/view-header/ViewHeader";
|
||||||
import { getBaseUrl, toUpperCase } from "../../util";
|
import { toUpperCase } from "../../util";
|
||||||
import { FormAccess } from "../../components/form-access/FormAccess";
|
import { FormAccess } from "../../components/form-access/FormAccess";
|
||||||
import { HelpItem } from "../../components/help-enabler/HelpItem";
|
|
||||||
import { useAdminClient } from "../../context/auth/AdminClient";
|
import { useAdminClient } from "../../context/auth/AdminClient";
|
||||||
import { useRealm } from "../../context/realm-context/RealmContext";
|
import { useRealm } from "../../context/realm-context/RealmContext";
|
||||||
import { useAlerts } from "../../components/alert/Alerts";
|
import { useAlerts } from "../../components/alert/Alerts";
|
||||||
|
import { GeneralSettings } from "./GeneralSettings";
|
||||||
|
|
||||||
export const AddIdentityProvider = () => {
|
export const AddIdentityProvider = () => {
|
||||||
const { t } = useTranslation("identity-providers");
|
const { t } = useTranslation("identity-providers");
|
||||||
const { t: th } = useTranslation("identity-providers-help");
|
|
||||||
const { id } = useParams<{ id: string }>();
|
const { id } = useParams<{ id: string }>();
|
||||||
|
const form = useForm<IdentityProviderRepresentation>();
|
||||||
const {
|
const {
|
||||||
handleSubmit,
|
handleSubmit,
|
||||||
register,
|
|
||||||
errors,
|
|
||||||
control,
|
|
||||||
formState: { isDirty },
|
formState: { isDirty },
|
||||||
} = useForm<IdentityProviderRepresentation>();
|
} = form;
|
||||||
|
|
||||||
const adminClient = useAdminClient();
|
const adminClient = useAdminClient();
|
||||||
const { addAlert } = useAlerts();
|
const { addAlert } = useAlerts();
|
||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
const { realm } = useRealm();
|
const { realm } = useRealm();
|
||||||
|
|
||||||
const callbackUrl = `${getBaseUrl(adminClient)}/realms/${realm}/broker`;
|
|
||||||
|
|
||||||
const save = async (provider: IdentityProviderRepresentation) => {
|
const save = async (provider: IdentityProviderRepresentation) => {
|
||||||
try {
|
try {
|
||||||
await adminClient.identityProviders.create({
|
await adminClient.identityProviders.create({
|
||||||
|
@ -50,7 +40,7 @@ export const AddIdentityProvider = () => {
|
||||||
alias: id,
|
alias: id,
|
||||||
});
|
});
|
||||||
addAlert(t("createSuccess"), AlertVariant.success);
|
addAlert(t("createSuccess"), AlertVariant.success);
|
||||||
history.push(`/${realm}/identity-providers`);
|
history.push(`/${realm}/identity-providers/${id}/settings`);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
addAlert(t("createError", { error }), AlertVariant.danger);
|
addAlert(t("createError", { error }), AlertVariant.danger);
|
||||||
}
|
}
|
||||||
|
@ -67,105 +57,9 @@ export const AddIdentityProvider = () => {
|
||||||
isHorizontal
|
isHorizontal
|
||||||
onSubmit={handleSubmit(save)}
|
onSubmit={handleSubmit(save)}
|
||||||
>
|
>
|
||||||
<FormGroup
|
<FormProvider {...form}>
|
||||||
label={t("redirectURI")}
|
<GeneralSettings />
|
||||||
labelIcon={
|
</FormProvider>
|
||||||
<HelpItem
|
|
||||||
helpText={th("redirectURI")}
|
|
||||||
forLabel={t("redirectURI")}
|
|
||||||
forID="kc-redirect-uri"
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
fieldId="kc-redirect-uri"
|
|
||||||
>
|
|
||||||
<ClipboardCopy
|
|
||||||
isReadOnly
|
|
||||||
>{`${callbackUrl}/${id}/endpoint`}</ClipboardCopy>
|
|
||||||
</FormGroup>
|
|
||||||
<FormGroup
|
|
||||||
label={t("clientId")}
|
|
||||||
labelIcon={
|
|
||||||
<HelpItem
|
|
||||||
helpText={th("clientId")}
|
|
||||||
forLabel={t("clientId")}
|
|
||||||
forID="kc-client-id"
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
fieldId="kc-client-id"
|
|
||||||
isRequired
|
|
||||||
validated={
|
|
||||||
errors.config && errors.config.clientId
|
|
||||||
? ValidatedOptions.error
|
|
||||||
: ValidatedOptions.default
|
|
||||||
}
|
|
||||||
helperTextInvalid={t("common:required")}
|
|
||||||
>
|
|
||||||
<TextInput
|
|
||||||
isRequired
|
|
||||||
type="text"
|
|
||||||
id="kc-client-id"
|
|
||||||
data-testid="clientId"
|
|
||||||
name="config.clientId"
|
|
||||||
ref={register({ required: true })}
|
|
||||||
/>
|
|
||||||
</FormGroup>
|
|
||||||
<FormGroup
|
|
||||||
label={t("clientSecret")}
|
|
||||||
labelIcon={
|
|
||||||
<HelpItem
|
|
||||||
helpText={th("clientSecret")}
|
|
||||||
forLabel={t("clientSecret")}
|
|
||||||
forID="kc-client-secret"
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
fieldId="kc-client-secret"
|
|
||||||
isRequired
|
|
||||||
validated={
|
|
||||||
errors.config && errors.config.clientSecret
|
|
||||||
? ValidatedOptions.error
|
|
||||||
: ValidatedOptions.default
|
|
||||||
}
|
|
||||||
helperTextInvalid={t("common:required")}
|
|
||||||
>
|
|
||||||
<TextInput
|
|
||||||
isRequired
|
|
||||||
type="password"
|
|
||||||
id="kc-client-secret"
|
|
||||||
data-testid="clientSecret"
|
|
||||||
name="config.clientSecret"
|
|
||||||
ref={register({ required: true })}
|
|
||||||
/>
|
|
||||||
</FormGroup>
|
|
||||||
<FormGroup
|
|
||||||
label={t("displayOrder")}
|
|
||||||
labelIcon={
|
|
||||||
<HelpItem
|
|
||||||
helpText={th("displayOrder")}
|
|
||||||
forLabel={t("displayOrder")}
|
|
||||||
forID="kc-display-order"
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
fieldId="kc-display-order"
|
|
||||||
>
|
|
||||||
<Controller
|
|
||||||
name="config.guiOrder"
|
|
||||||
control={control}
|
|
||||||
defaultValue={0}
|
|
||||||
render={({ onChange, value }) => (
|
|
||||||
<NumberInput
|
|
||||||
value={value}
|
|
||||||
data-testid="displayOrder"
|
|
||||||
onMinus={() => onChange(value - 1)}
|
|
||||||
onChange={onChange}
|
|
||||||
onPlus={() => onChange(value + 1)}
|
|
||||||
inputName="input"
|
|
||||||
inputAriaLabel={t("displayOrder")}
|
|
||||||
minusBtnAriaLabel={t("common:minus")}
|
|
||||||
plusBtnAriaLabel={t("common:plus")}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
</FormGroup>
|
|
||||||
<ActionGroup>
|
<ActionGroup>
|
||||||
<Button
|
<Button
|
||||||
isDisabled={!isDirty}
|
isDisabled={!isDirty}
|
||||||
|
|
87
src/identity-providers/add/AddOpenIdConnect.tsx
Normal file
87
src/identity-providers/add/AddOpenIdConnect.tsx
Normal file
|
@ -0,0 +1,87 @@
|
||||||
|
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 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 { OIDCGeneralSettings } from "./OIDCGeneralSettings";
|
||||||
|
import { OpenIdConnectSettings } from "./OpenIdConnectSettings";
|
||||||
|
import { useRealm } from "../../context/realm-context/RealmContext";
|
||||||
|
import { OIDCAuthentication } from "./OIDCAuthentication";
|
||||||
|
import { useAlerts } from "../../components/alert/Alerts";
|
||||||
|
|
||||||
|
export const AddOpenIdConnect = () => {
|
||||||
|
const { t } = useTranslation("identity-providers");
|
||||||
|
const history = useHistory();
|
||||||
|
const id = "oidc";
|
||||||
|
|
||||||
|
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 }), AlertVariant.danger);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<ViewHeader titleKey={t("addOpenIdProvider")} />
|
||||||
|
<PageSection variant="light">
|
||||||
|
<FormProvider {...form}>
|
||||||
|
<FormAccess
|
||||||
|
role="manage-identity-providers"
|
||||||
|
isHorizontal
|
||||||
|
onSubmit={handleSubmit(save)}
|
||||||
|
>
|
||||||
|
<OIDCGeneralSettings />
|
||||||
|
<OpenIdConnectSettings />
|
||||||
|
<OIDCAuthentication />
|
||||||
|
<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>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
186
src/identity-providers/add/AdvancedSettings.tsx
Normal file
186
src/identity-providers/add/AdvancedSettings.tsx
Normal file
|
@ -0,0 +1,186 @@
|
||||||
|
import React, { useEffect, useState } from "react";
|
||||||
|
import { useErrorHandler } from "react-error-boundary";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { Controller, useFormContext } from "react-hook-form";
|
||||||
|
import {
|
||||||
|
ActionGroup,
|
||||||
|
Button,
|
||||||
|
FormGroup,
|
||||||
|
Select,
|
||||||
|
SelectOption,
|
||||||
|
SelectVariant,
|
||||||
|
} from "@patternfly/react-core";
|
||||||
|
|
||||||
|
import AuthenticationFlowRepresentation from "keycloak-admin/lib/defs/authenticationFlowRepresentation";
|
||||||
|
import {
|
||||||
|
asyncStateFetch,
|
||||||
|
useAdminClient,
|
||||||
|
} from "../../context/auth/AdminClient";
|
||||||
|
import { SwitchField } from "../component/SwitchField";
|
||||||
|
import { TextField } from "../component/TextField";
|
||||||
|
import { HelpItem } from "../../components/help-enabler/HelpItem";
|
||||||
|
import { FieldProps } from "../component/FormGroupField";
|
||||||
|
|
||||||
|
const LoginFlow = ({
|
||||||
|
field,
|
||||||
|
label,
|
||||||
|
defaultValue,
|
||||||
|
}: FieldProps & { defaultValue: string }) => {
|
||||||
|
const { t } = useTranslation("identity-providers");
|
||||||
|
const { control } = useFormContext();
|
||||||
|
|
||||||
|
const adminClient = useAdminClient();
|
||||||
|
const errorHandler = useErrorHandler();
|
||||||
|
const [flows, setFlows] = useState<AuthenticationFlowRepresentation[]>();
|
||||||
|
const [open, setOpen] = useState(false);
|
||||||
|
|
||||||
|
useEffect(
|
||||||
|
() =>
|
||||||
|
asyncStateFetch(
|
||||||
|
() => adminClient.authenticationManagement.getFlows(),
|
||||||
|
setFlows,
|
||||||
|
errorHandler
|
||||||
|
),
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<FormGroup
|
||||||
|
label={t(label)}
|
||||||
|
labelIcon={
|
||||||
|
<HelpItem
|
||||||
|
helpText={`identity-providers-help:${label}`}
|
||||||
|
forLabel={t(label)}
|
||||||
|
forID={label}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
fieldId={label}
|
||||||
|
>
|
||||||
|
<Controller
|
||||||
|
name={field}
|
||||||
|
defaultValue={defaultValue}
|
||||||
|
control={control}
|
||||||
|
render={({ onChange, value }) => (
|
||||||
|
<Select
|
||||||
|
toggleId={label}
|
||||||
|
required
|
||||||
|
onToggle={() => setOpen(!open)}
|
||||||
|
onSelect={(_, value) => {
|
||||||
|
onChange(value as string);
|
||||||
|
setOpen(false);
|
||||||
|
}}
|
||||||
|
selections={value}
|
||||||
|
variant={SelectVariant.single}
|
||||||
|
aria-label={t(label)}
|
||||||
|
isOpen={open}
|
||||||
|
>
|
||||||
|
{flows &&
|
||||||
|
flows.map((option) => (
|
||||||
|
<SelectOption
|
||||||
|
selected={option.alias === value}
|
||||||
|
key={option.id}
|
||||||
|
value={option.alias}
|
||||||
|
>
|
||||||
|
{option.alias}
|
||||||
|
</SelectOption>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</FormGroup>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const syncModes = ["import", "legacy", "force"];
|
||||||
|
|
||||||
|
export const AdvancedSettings = ({ isOIDC }: { isOIDC: boolean }) => {
|
||||||
|
const { t } = useTranslation("identity-providers");
|
||||||
|
const { control } = useFormContext();
|
||||||
|
const [syncModeOpen, setSyncModeOpen] = useState(false);
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{!isOIDC && <TextField field="config.defaultScope" label="scopes" />}
|
||||||
|
<SwitchField field="storeToken" label="storeTokens" fieldType="boolean" />
|
||||||
|
{!isOIDC && (
|
||||||
|
<>
|
||||||
|
<SwitchField
|
||||||
|
field="config.acceptsPromptNoneForwardFromClient"
|
||||||
|
label="acceptsPromptNone"
|
||||||
|
/>
|
||||||
|
<SwitchField field="config.disableUserInfo" label="disableUserInfo" />
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
<SwitchField field="trustEmail" label="trustEmail" fieldType="boolean" />
|
||||||
|
<SwitchField
|
||||||
|
field="linkOnly"
|
||||||
|
label="accountLinkingOnly"
|
||||||
|
fieldType="boolean"
|
||||||
|
/>
|
||||||
|
<SwitchField field="config.hideOnLoginPage" label="hideOnLoginPage" />
|
||||||
|
|
||||||
|
<LoginFlow
|
||||||
|
field="firstBrokerLoginFlowAlias"
|
||||||
|
label="firstBrokerLoginFlowAlias"
|
||||||
|
defaultValue="fist broker login"
|
||||||
|
/>
|
||||||
|
<LoginFlow
|
||||||
|
field="postBrokerLoginFlowAlias"
|
||||||
|
label="postBrokerLoginFlowAlias"
|
||||||
|
defaultValue=""
|
||||||
|
/>
|
||||||
|
|
||||||
|
<FormGroup
|
||||||
|
label={t("syncMode")}
|
||||||
|
labelIcon={
|
||||||
|
<HelpItem
|
||||||
|
helpText="identity-providers-help:syncMode"
|
||||||
|
forLabel={t("syncMode")}
|
||||||
|
forID="syncMode"
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
fieldId="syncMode"
|
||||||
|
>
|
||||||
|
<Controller
|
||||||
|
name="config.syncMode"
|
||||||
|
defaultValue={syncModes[0]}
|
||||||
|
control={control}
|
||||||
|
render={({ onChange, value }) => (
|
||||||
|
<Select
|
||||||
|
toggleId="syncMode"
|
||||||
|
required
|
||||||
|
direction="up"
|
||||||
|
onToggle={() => setSyncModeOpen(!syncModeOpen)}
|
||||||
|
onSelect={(_, value) => {
|
||||||
|
onChange(value as string);
|
||||||
|
setSyncModeOpen(false);
|
||||||
|
}}
|
||||||
|
selections={t(`syncModes.${value.toLowerCase()}`)}
|
||||||
|
variant={SelectVariant.single}
|
||||||
|
aria-label={t("syncMode")}
|
||||||
|
isOpen={syncModeOpen}
|
||||||
|
>
|
||||||
|
{syncModes.map((option) => (
|
||||||
|
<SelectOption
|
||||||
|
selected={option === value}
|
||||||
|
key={option}
|
||||||
|
value={option.toUpperCase()}
|
||||||
|
>
|
||||||
|
{t(`syncModes.${option}`)}
|
||||||
|
</SelectOption>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</FormGroup>
|
||||||
|
|
||||||
|
<ActionGroup className="keycloak__form_actions">
|
||||||
|
<Button data-testid={"save"} variant="tertiary" type="submit">
|
||||||
|
{t("common:save")}
|
||||||
|
</Button>
|
||||||
|
<Button data-testid={"revert"} variant="link" onClick={() => {}}>
|
||||||
|
{t("common:revert")}
|
||||||
|
</Button>
|
||||||
|
</ActionGroup>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
207
src/identity-providers/add/DetailSettings.tsx
Normal file
207
src/identity-providers/add/DetailSettings.tsx
Normal file
|
@ -0,0 +1,207 @@
|
||||||
|
import React, { useEffect } from "react";
|
||||||
|
import { useHistory, useParams } from "react-router-dom";
|
||||||
|
import { useErrorHandler } from "react-error-boundary";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { Controller, FormProvider, useForm } from "react-hook-form";
|
||||||
|
import {
|
||||||
|
AlertVariant,
|
||||||
|
ButtonVariant,
|
||||||
|
Divider,
|
||||||
|
DropdownItem,
|
||||||
|
Form,
|
||||||
|
PageSection,
|
||||||
|
Tab,
|
||||||
|
TabTitleText,
|
||||||
|
} from "@patternfly/react-core";
|
||||||
|
|
||||||
|
import IdentityProviderRepresentation from "keycloak-admin/lib/defs/identityProviderRepresentation";
|
||||||
|
import { FormAccess } from "../../components/form-access/FormAccess";
|
||||||
|
import { ScrollForm } from "../../components/scroll-form/ScrollForm";
|
||||||
|
import { ViewHeader } from "../../components/view-header/ViewHeader";
|
||||||
|
import {
|
||||||
|
asyncStateFetch,
|
||||||
|
useAdminClient,
|
||||||
|
} from "../../context/auth/AdminClient";
|
||||||
|
import { toUpperCase } from "../../util";
|
||||||
|
import { GeneralSettings } from "./GeneralSettings";
|
||||||
|
import { AdvancedSettings } from "./AdvancedSettings";
|
||||||
|
import { useConfirmDialog } from "../../components/confirm-dialog/ConfirmDialog";
|
||||||
|
import { useAlerts } from "../../components/alert/Alerts";
|
||||||
|
import { useRealm } from "../../context/realm-context/RealmContext";
|
||||||
|
import { KeycloakTabs } from "../../components/keycloak-tabs/KeycloakTabs";
|
||||||
|
import { ExtendedNonDiscoverySettings } from "./ExtendedNonDiscoverySettings";
|
||||||
|
import { DiscoverySettings } from "./DiscoverySettings";
|
||||||
|
import { OIDCGeneralSettings } from "./OIDCGeneralSettings";
|
||||||
|
import { OIDCAuthentication } from "./OIDCAuthentication";
|
||||||
|
|
||||||
|
type HeaderProps = {
|
||||||
|
onChange: (value: boolean) => void;
|
||||||
|
value: boolean;
|
||||||
|
save: () => void;
|
||||||
|
toggleDeleteDialog: () => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
const Header = ({ onChange, value, save, toggleDeleteDialog }: HeaderProps) => {
|
||||||
|
const { t } = useTranslation("identity-providers");
|
||||||
|
const { id } = useParams<{ id: string }>();
|
||||||
|
|
||||||
|
const [toggleDisableDialog, DisableConfirm] = useConfirmDialog({
|
||||||
|
titleKey: "identity-providers:disableProvider",
|
||||||
|
messageKey: t("disableConfirm", { provider: id }),
|
||||||
|
continueButtonLabel: "common:disable",
|
||||||
|
onConfirm: () => {
|
||||||
|
onChange(!value);
|
||||||
|
save();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<DisableConfirm />
|
||||||
|
<ViewHeader
|
||||||
|
titleKey={t("addIdentityProvider", { provider: toUpperCase(id) })}
|
||||||
|
divider={false}
|
||||||
|
dropdownItems={[
|
||||||
|
<DropdownItem key="delete" onClick={() => toggleDeleteDialog()}>
|
||||||
|
{t("common:delete")}
|
||||||
|
</DropdownItem>,
|
||||||
|
]}
|
||||||
|
isEnabled={value}
|
||||||
|
onToggle={(value) => {
|
||||||
|
if (!value) {
|
||||||
|
toggleDisableDialog();
|
||||||
|
} else {
|
||||||
|
onChange(value);
|
||||||
|
save();
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const DetailSettings = () => {
|
||||||
|
const { t } = useTranslation("identity-providers");
|
||||||
|
const { id } = useParams<{ id: string }>();
|
||||||
|
|
||||||
|
const form = useForm<IdentityProviderRepresentation>();
|
||||||
|
const { handleSubmit, setValue, getValues } = form;
|
||||||
|
|
||||||
|
const adminClient = useAdminClient();
|
||||||
|
const { addAlert } = useAlerts();
|
||||||
|
const history = useHistory();
|
||||||
|
const { realm } = useRealm();
|
||||||
|
const errorHandler = useErrorHandler();
|
||||||
|
|
||||||
|
useEffect(
|
||||||
|
() =>
|
||||||
|
asyncStateFetch(
|
||||||
|
() => adminClient.identityProviders.findOne({ alias: id }),
|
||||||
|
(provider) => {
|
||||||
|
Object.entries(provider).map((entry) => setValue(entry[0], entry[1]));
|
||||||
|
},
|
||||||
|
errorHandler
|
||||||
|
),
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
|
||||||
|
const save = async (provider?: IdentityProviderRepresentation) => {
|
||||||
|
const p = provider || getValues();
|
||||||
|
try {
|
||||||
|
await adminClient.identityProviders.update(
|
||||||
|
{ alias: id },
|
||||||
|
{ ...p, alias: id, providerId: id }
|
||||||
|
);
|
||||||
|
addAlert(t("updateSuccess"), AlertVariant.success);
|
||||||
|
} catch (error) {
|
||||||
|
addAlert(
|
||||||
|
t("updateError", {
|
||||||
|
error: error.response?.data?.errorMessage || error,
|
||||||
|
}),
|
||||||
|
AlertVariant.danger
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const [toggleDeleteDialog, DeleteConfirm] = useConfirmDialog({
|
||||||
|
titleKey: "identity-providers:deleteProvider",
|
||||||
|
messageKey: t("identity-providers:deleteConfirm", { provider: id }),
|
||||||
|
continueButtonLabel: "common:delete",
|
||||||
|
continueButtonVariant: ButtonVariant.danger,
|
||||||
|
onConfirm: async () => {
|
||||||
|
try {
|
||||||
|
await adminClient.identityProviders.del({ alias: id });
|
||||||
|
addAlert(t("deletedSuccess"), AlertVariant.success);
|
||||||
|
history.push(`/${realm}/identity-providers`);
|
||||||
|
} catch (error) {
|
||||||
|
addAlert(t("deleteErrorError", { error }), AlertVariant.danger);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const sections = [t("generalSettings"), t("advancedSettings")];
|
||||||
|
const isOIDC = id === "oidc";
|
||||||
|
|
||||||
|
if (isOIDC) {
|
||||||
|
sections.splice(1, 0, t("oidcSettings"));
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<DeleteConfirm />
|
||||||
|
<Controller
|
||||||
|
name="enabled"
|
||||||
|
control={form.control}
|
||||||
|
defaultValue={true}
|
||||||
|
render={({ onChange, value }) => (
|
||||||
|
<Header
|
||||||
|
value={value}
|
||||||
|
onChange={onChange}
|
||||||
|
save={save}
|
||||||
|
toggleDeleteDialog={toggleDeleteDialog}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<PageSection variant="light" className="pf-u-p-0">
|
||||||
|
<FormProvider {...form}>
|
||||||
|
<KeycloakTabs isBox>
|
||||||
|
<Tab
|
||||||
|
id="settings"
|
||||||
|
eventKey="settings"
|
||||||
|
title={<TabTitleText>{t("common:settings")}</TabTitleText>}
|
||||||
|
>
|
||||||
|
<ScrollForm className="pf-u-px-lg" sections={sections}>
|
||||||
|
<FormAccess
|
||||||
|
role="manage-identity-providers"
|
||||||
|
isHorizontal
|
||||||
|
onSubmit={handleSubmit(save)}
|
||||||
|
>
|
||||||
|
{!isOIDC && <GeneralSettings />}
|
||||||
|
{isOIDC && <OIDCGeneralSettings />}
|
||||||
|
</FormAccess>
|
||||||
|
{isOIDC && (
|
||||||
|
<>
|
||||||
|
<DiscoverySettings readOnly={false} />
|
||||||
|
<Form isHorizontal className="pf-u-py-lg">
|
||||||
|
<Divider />
|
||||||
|
<OIDCAuthentication />
|
||||||
|
</Form>
|
||||||
|
<ExtendedNonDiscoverySettings />
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
<FormAccess
|
||||||
|
role="manage-identity-providers"
|
||||||
|
isHorizontal
|
||||||
|
onSubmit={handleSubmit(save)}
|
||||||
|
>
|
||||||
|
<AdvancedSettings isOIDC={isOIDC} />
|
||||||
|
</FormAccess>
|
||||||
|
</ScrollForm>
|
||||||
|
</Tab>
|
||||||
|
</KeycloakTabs>
|
||||||
|
</FormProvider>
|
||||||
|
</PageSection>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
137
src/identity-providers/add/DiscoverySettings.tsx
Normal file
137
src/identity-providers/add/DiscoverySettings.tsx
Normal file
|
@ -0,0 +1,137 @@
|
||||||
|
import React, { useState } from "react";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { useFormContext, useWatch } from "react-hook-form";
|
||||||
|
import {
|
||||||
|
ExpandableSection,
|
||||||
|
FormGroup,
|
||||||
|
TextInput,
|
||||||
|
ValidatedOptions,
|
||||||
|
} from "@patternfly/react-core";
|
||||||
|
|
||||||
|
import { SwitchField } from "../component/SwitchField";
|
||||||
|
import { TextField } from "../component/TextField";
|
||||||
|
|
||||||
|
type DiscoverySettingsProps = {
|
||||||
|
readOnly: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
const Fields = ({ readOnly }: DiscoverySettingsProps) => {
|
||||||
|
const { t } = useTranslation("identity-providers");
|
||||||
|
const { register, control, errors } = useFormContext();
|
||||||
|
|
||||||
|
const validateSignature = useWatch({
|
||||||
|
control: control,
|
||||||
|
name: "config.validateSignature",
|
||||||
|
});
|
||||||
|
const useJwks = useWatch({
|
||||||
|
control: control,
|
||||||
|
name: "config.useJwksUrl",
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="pf-c-form pf-m-horizontal">
|
||||||
|
<FormGroup
|
||||||
|
label={t("authorizationUrl")}
|
||||||
|
fieldId="kc-authorization-url"
|
||||||
|
isRequired
|
||||||
|
validated={
|
||||||
|
errors.config && errors.config.authorizationUrl
|
||||||
|
? ValidatedOptions.error
|
||||||
|
: ValidatedOptions.default
|
||||||
|
}
|
||||||
|
helperTextInvalid={t("common:required")}
|
||||||
|
>
|
||||||
|
<TextInput
|
||||||
|
type="text"
|
||||||
|
data-testid="authorizationUrl"
|
||||||
|
id="kc-authorization-url"
|
||||||
|
name="config.authorizationUrl"
|
||||||
|
ref={register({ required: true })}
|
||||||
|
validated={
|
||||||
|
errors.config && errors.config.authorizationUrl
|
||||||
|
? ValidatedOptions.error
|
||||||
|
: ValidatedOptions.default
|
||||||
|
}
|
||||||
|
isReadOnly={readOnly}
|
||||||
|
/>
|
||||||
|
</FormGroup>
|
||||||
|
|
||||||
|
<FormGroup
|
||||||
|
label={t("tokenUrl")}
|
||||||
|
fieldId="tokenUrl"
|
||||||
|
isRequired
|
||||||
|
validated={
|
||||||
|
errors.config && errors.config.tokenUrl
|
||||||
|
? ValidatedOptions.error
|
||||||
|
: ValidatedOptions.default
|
||||||
|
}
|
||||||
|
helperTextInvalid={t("common:required")}
|
||||||
|
>
|
||||||
|
<TextInput
|
||||||
|
type="text"
|
||||||
|
id="tokenUrl"
|
||||||
|
name="config.tokenUrl"
|
||||||
|
ref={register({ required: true })}
|
||||||
|
validated={
|
||||||
|
errors.config && errors.config.tokenUrl
|
||||||
|
? ValidatedOptions.error
|
||||||
|
: ValidatedOptions.default
|
||||||
|
}
|
||||||
|
isReadOnly={readOnly}
|
||||||
|
/>
|
||||||
|
</FormGroup>
|
||||||
|
<TextField
|
||||||
|
field="config.logoutUrl"
|
||||||
|
label="logoutUrl"
|
||||||
|
isReadOnly={readOnly}
|
||||||
|
/>
|
||||||
|
<TextField
|
||||||
|
field="config.userInfoUrl"
|
||||||
|
label="userInfoUrl"
|
||||||
|
isReadOnly={readOnly}
|
||||||
|
/>
|
||||||
|
<TextField field="config.issuer" label="issuer" isReadOnly={readOnly} />
|
||||||
|
<SwitchField
|
||||||
|
field="config.validateSignature"
|
||||||
|
label="validateSignature"
|
||||||
|
isReadOnly={readOnly}
|
||||||
|
/>
|
||||||
|
{validateSignature === "true" && (
|
||||||
|
<>
|
||||||
|
<SwitchField
|
||||||
|
field="config.useJwksUrl"
|
||||||
|
label="useJwksUrl"
|
||||||
|
isReadOnly={readOnly}
|
||||||
|
/>
|
||||||
|
{useJwks === "true" && (
|
||||||
|
<TextField
|
||||||
|
field="config.jwksUrl"
|
||||||
|
label="jwksUrl"
|
||||||
|
isReadOnly={readOnly}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const DiscoverySettings = ({ readOnly }: DiscoverySettingsProps) => {
|
||||||
|
const { t } = useTranslation("identity-providers");
|
||||||
|
const [isExpanded, setIsExpanded] = useState(false);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{readOnly && (
|
||||||
|
<ExpandableSection
|
||||||
|
toggleText={isExpanded ? t("hideMetaData") : t("showMetaData")}
|
||||||
|
onToggle={() => setIsExpanded(!isExpanded)}
|
||||||
|
isExpanded={isExpanded}
|
||||||
|
>
|
||||||
|
<Fields readOnly={readOnly} />
|
||||||
|
</ExpandableSection>
|
||||||
|
)}
|
||||||
|
{!readOnly && <Fields readOnly={readOnly} />}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
126
src/identity-providers/add/ExtendedNonDiscoverySettings.tsx
Normal file
126
src/identity-providers/add/ExtendedNonDiscoverySettings.tsx
Normal file
|
@ -0,0 +1,126 @@
|
||||||
|
import React, { useState } from "react";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { Controller, useFormContext } from "react-hook-form";
|
||||||
|
import {
|
||||||
|
ExpandableSection,
|
||||||
|
Form,
|
||||||
|
FormGroup,
|
||||||
|
NumberInput,
|
||||||
|
Select,
|
||||||
|
SelectOption,
|
||||||
|
SelectVariant,
|
||||||
|
} from "@patternfly/react-core";
|
||||||
|
|
||||||
|
import { SwitchField } from "../component/SwitchField";
|
||||||
|
import { TextField } from "../component/TextField";
|
||||||
|
import { FormGroupField } from "../component/FormGroupField";
|
||||||
|
import { HelpItem } from "../../components/help-enabler/HelpItem";
|
||||||
|
|
||||||
|
const promptOptions = [
|
||||||
|
"unspecified",
|
||||||
|
"none",
|
||||||
|
"consent",
|
||||||
|
"login",
|
||||||
|
"select_account",
|
||||||
|
];
|
||||||
|
|
||||||
|
export const ExtendedNonDiscoverySettings = () => {
|
||||||
|
const { t } = useTranslation("identity-providers");
|
||||||
|
const { control } = useFormContext();
|
||||||
|
|
||||||
|
const [isExpanded, setIsExpanded] = useState(false);
|
||||||
|
const [promptOpen, setPromptOpen] = useState(false);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<ExpandableSection
|
||||||
|
toggleText={t("advanced")}
|
||||||
|
onToggle={() => setIsExpanded(!isExpanded)}
|
||||||
|
isExpanded={isExpanded}
|
||||||
|
>
|
||||||
|
<Form isHorizontal>
|
||||||
|
<SwitchField label="passLoginHint" field="config.loginHint" />
|
||||||
|
<SwitchField label="passCurrentLocale" field="config.uiLocales" />
|
||||||
|
<SwitchField
|
||||||
|
field="config.backchannelSupported"
|
||||||
|
label="backchannelLogout"
|
||||||
|
/>
|
||||||
|
<SwitchField field="config.disableUserInfo" label="disableUserInfo" />
|
||||||
|
<TextField field="config.defaultScope" label="scopes" />
|
||||||
|
<FormGroupField label="prompt">
|
||||||
|
<Controller
|
||||||
|
name="config.prompt"
|
||||||
|
defaultValue={promptOptions[0]}
|
||||||
|
control={control}
|
||||||
|
render={({ onChange, value }) => (
|
||||||
|
<Select
|
||||||
|
toggleId="prompt"
|
||||||
|
required
|
||||||
|
onToggle={() => setPromptOpen(!promptOpen)}
|
||||||
|
onSelect={(_, value) => {
|
||||||
|
onChange(value as string);
|
||||||
|
setPromptOpen(false);
|
||||||
|
}}
|
||||||
|
selections={value}
|
||||||
|
variant={SelectVariant.single}
|
||||||
|
aria-label={t("prompt")}
|
||||||
|
isOpen={promptOpen}
|
||||||
|
>
|
||||||
|
{promptOptions.map((option) => (
|
||||||
|
<SelectOption
|
||||||
|
selected={option === value}
|
||||||
|
key={option}
|
||||||
|
value={option}
|
||||||
|
>
|
||||||
|
{t(`prompts.${option}`)}
|
||||||
|
</SelectOption>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</FormGroupField>
|
||||||
|
<SwitchField
|
||||||
|
field="config.acceptsPromptNoneForwardFromClient"
|
||||||
|
label="acceptsPromptNone"
|
||||||
|
/>
|
||||||
|
<FormGroup
|
||||||
|
label={t("allowedClockSkew")}
|
||||||
|
labelIcon={
|
||||||
|
<HelpItem
|
||||||
|
helpText={"identity-providers-help:allowedClockSkew"}
|
||||||
|
forLabel={t("allowedClockSkew")}
|
||||||
|
forID="allowedClockSkew"
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
fieldId="allowedClockSkew"
|
||||||
|
>
|
||||||
|
<Controller
|
||||||
|
name="config.allowedClockSkew"
|
||||||
|
control={control}
|
||||||
|
defaultValue={0}
|
||||||
|
render={({ onChange, value }) => (
|
||||||
|
<NumberInput
|
||||||
|
value={value}
|
||||||
|
data-testid="allowedClockSkew"
|
||||||
|
onMinus={() => onChange(value - 1)}
|
||||||
|
onChange={onChange}
|
||||||
|
onPlus={() => onChange(value + 1)}
|
||||||
|
inputName="input"
|
||||||
|
inputAriaLabel={t("allowedClockSkew")}
|
||||||
|
minusBtnAriaLabel={t("common:minus")}
|
||||||
|
plusBtnAriaLabel={t("common:plus")}
|
||||||
|
min={0}
|
||||||
|
unit={t("common:times.seconds")}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</FormGroup>
|
||||||
|
<TextField
|
||||||
|
field="config.forwardParameters"
|
||||||
|
label="forwardParameters"
|
||||||
|
/>
|
||||||
|
</Form>
|
||||||
|
</ExpandableSection>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
13
src/identity-providers/add/GeneralSettings.tsx
Normal file
13
src/identity-providers/add/GeneralSettings.tsx
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
import { RedirectUrl } from "../component/RedirectUrl";
|
||||||
|
import { ClientIdSecret } from "../component/ClientIdSecret";
|
||||||
|
import { DisplayOrder } from "../component/DisplayOrder";
|
||||||
|
|
||||||
|
export const GeneralSettings = () => (
|
||||||
|
<>
|
||||||
|
<RedirectUrl />
|
||||||
|
<ClientIdSecret />
|
||||||
|
<DisplayOrder />
|
||||||
|
</>
|
||||||
|
);
|
82
src/identity-providers/add/OIDCAuthentication.tsx
Normal file
82
src/identity-providers/add/OIDCAuthentication.tsx
Normal file
|
@ -0,0 +1,82 @@
|
||||||
|
import React, { useState } from "react";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { Controller, useFormContext, useWatch } from "react-hook-form";
|
||||||
|
import {
|
||||||
|
FormGroup,
|
||||||
|
Select,
|
||||||
|
SelectOption,
|
||||||
|
SelectVariant,
|
||||||
|
} from "@patternfly/react-core";
|
||||||
|
|
||||||
|
import { ClientIdSecret } from "../component/ClientIdSecret";
|
||||||
|
import { HelpItem } from "../../components/help-enabler/HelpItem";
|
||||||
|
|
||||||
|
const clientAuthenticationTypes = [
|
||||||
|
"clientAuth_post",
|
||||||
|
"clientAuth_basic",
|
||||||
|
"clientAuth_secret_jwt",
|
||||||
|
"clientAuth_privatekey_jwt",
|
||||||
|
];
|
||||||
|
|
||||||
|
export const OIDCAuthentication = () => {
|
||||||
|
const { t } = useTranslation("identity-providers");
|
||||||
|
const { t: th } = useTranslation("identity-providers-help");
|
||||||
|
|
||||||
|
const { control } = useFormContext();
|
||||||
|
const [openClientAuth, setOpenClientAuth] = useState(false);
|
||||||
|
|
||||||
|
const clientAuthMethod = useWatch({
|
||||||
|
control: control,
|
||||||
|
name: "config.clientAuthMethod",
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<FormGroup
|
||||||
|
label={t("clientAuthentication")}
|
||||||
|
labelIcon={
|
||||||
|
<HelpItem
|
||||||
|
helpText={th("clientAuthentication")}
|
||||||
|
forLabel={t("clientAuthentication")}
|
||||||
|
forID="clientAuthentication"
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
fieldId="clientAuthentication"
|
||||||
|
>
|
||||||
|
<Controller
|
||||||
|
name="config.clientAuthMethod"
|
||||||
|
defaultValue={clientAuthenticationTypes[0]}
|
||||||
|
control={control}
|
||||||
|
render={({ onChange, value }) => (
|
||||||
|
<Select
|
||||||
|
toggleId="clientAuthMethod"
|
||||||
|
required
|
||||||
|
onToggle={() => setOpenClientAuth(!openClientAuth)}
|
||||||
|
onSelect={(_, value) => {
|
||||||
|
onChange(value as string);
|
||||||
|
setOpenClientAuth(false);
|
||||||
|
}}
|
||||||
|
selections={value}
|
||||||
|
variant={SelectVariant.single}
|
||||||
|
aria-label={t("prompt")}
|
||||||
|
isOpen={openClientAuth}
|
||||||
|
>
|
||||||
|
{clientAuthenticationTypes.map((option) => (
|
||||||
|
<SelectOption
|
||||||
|
selected={option === value}
|
||||||
|
key={option}
|
||||||
|
value={option}
|
||||||
|
>
|
||||||
|
{t(`clientAuthentications.${option}`)}
|
||||||
|
</SelectOption>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</FormGroup>
|
||||||
|
<ClientIdSecret
|
||||||
|
secretRequired={clientAuthMethod !== "clientAuth_privatekey_jwt"}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
51
src/identity-providers/add/OIDCGeneralSettings.tsx
Normal file
51
src/identity-providers/add/OIDCGeneralSettings.tsx
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
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 OIDCGeneralSettings = () => {
|
||||||
|
const { t } = useTranslation("identity-providers");
|
||||||
|
const { t: th } = useTranslation("identity-providers-help");
|
||||||
|
|
||||||
|
const { register, errors } = useFormContext();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<RedirectUrl />
|
||||||
|
|
||||||
|
<FormGroup
|
||||||
|
label={t("alias")}
|
||||||
|
labelIcon={
|
||||||
|
<HelpItem
|
||||||
|
helpText={th("alias")}
|
||||||
|
forLabel={t("alias")}
|
||||||
|
forID="alias"
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
fieldId="alias"
|
||||||
|
isRequired
|
||||||
|
validated={
|
||||||
|
errors.errors ? ValidatedOptions.error : ValidatedOptions.default
|
||||||
|
}
|
||||||
|
helperTextInvalid={t("common:required")}
|
||||||
|
>
|
||||||
|
<TextInput
|
||||||
|
isRequired
|
||||||
|
type="text"
|
||||||
|
id="alias"
|
||||||
|
data-testid="alias"
|
||||||
|
name="alias"
|
||||||
|
ref={register({ required: true })}
|
||||||
|
/>
|
||||||
|
</FormGroup>
|
||||||
|
|
||||||
|
<TextField field="displayName" label="displayName" />
|
||||||
|
<DisplayOrder />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
186
src/identity-providers/add/OpenIdConnectSettings.tsx
Normal file
186
src/identity-providers/add/OpenIdConnectSettings.tsx
Normal file
|
@ -0,0 +1,186 @@
|
||||||
|
import React, { useEffect, useState } from "react";
|
||||||
|
import { useFormContext } from "react-hook-form";
|
||||||
|
import { FormGroup, Switch, TextInput, Title } from "@patternfly/react-core";
|
||||||
|
|
||||||
|
import { HelpItem } from "../../components/help-enabler/HelpItem";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { useAdminClient } from "../../context/auth/AdminClient";
|
||||||
|
import { OIDCConfigurationRepresentation } from "../OIDCConfigurationRepresentation";
|
||||||
|
import { JsonFileUpload } from "../../components/json-file-upload/JsonFileUpload";
|
||||||
|
import { useRealm } from "../../context/realm-context/RealmContext";
|
||||||
|
import { DiscoverySettings } from "./DiscoverySettings";
|
||||||
|
import { getBaseUrl } from "../../util";
|
||||||
|
|
||||||
|
type Result = OIDCConfigurationRepresentation & {
|
||||||
|
error: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const OpenIdConnectSettings = () => {
|
||||||
|
const { t } = useTranslation("identity-providers");
|
||||||
|
const id = "oidc";
|
||||||
|
|
||||||
|
const adminClient = useAdminClient();
|
||||||
|
const { realm } = useRealm();
|
||||||
|
const { setValue } = useFormContext();
|
||||||
|
|
||||||
|
const [discovery, setDiscovery] = useState(true);
|
||||||
|
const [discoveryUrl, setDiscoveryUrl] = useState("");
|
||||||
|
const [discovering, setDiscovering] = useState(false);
|
||||||
|
const [discoveryResult, setDiscoveryResult] = useState<Result>();
|
||||||
|
|
||||||
|
const setupForm = (result: any) => {
|
||||||
|
Object.keys(result).map((k) => setValue(`config.${k}`, result[k]));
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (discovering) {
|
||||||
|
setDiscovering(!!discoveryUrl);
|
||||||
|
if (discoveryUrl)
|
||||||
|
(async () => {
|
||||||
|
let result;
|
||||||
|
try {
|
||||||
|
result = await adminClient.identityProviders.importFromUrl({
|
||||||
|
providerId: id,
|
||||||
|
fromUrl: discoveryUrl,
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
result = { error };
|
||||||
|
}
|
||||||
|
|
||||||
|
setDiscoveryResult(result as Result);
|
||||||
|
setupForm(result);
|
||||||
|
setDiscovering(false);
|
||||||
|
})();
|
||||||
|
}
|
||||||
|
}, [discovering]);
|
||||||
|
|
||||||
|
const fileUpload = async (value: string) => {
|
||||||
|
if (value !== "") {
|
||||||
|
const formData = new FormData();
|
||||||
|
formData.append("providerId", id);
|
||||||
|
formData.append("file", new Blob([value]));
|
||||||
|
|
||||||
|
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("OpenID Connect settings")}
|
||||||
|
</Title>
|
||||||
|
<FormGroup
|
||||||
|
label={t("useDiscoveryEndpoint")}
|
||||||
|
fieldId="kc-discovery-endpoint-switch"
|
||||||
|
labelIcon={
|
||||||
|
<HelpItem
|
||||||
|
helpText="identity-providers-help:useDiscoveryEndpoint"
|
||||||
|
forLabel={t("useDiscoveryEndpoint")}
|
||||||
|
forID="kc-discovery-endpoint-switch"
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Switch
|
||||||
|
id="kc-discovery-endpoint-switch"
|
||||||
|
label={t("common:on")}
|
||||||
|
labelOff={t("common:off")}
|
||||||
|
isChecked={discovery}
|
||||||
|
onChange={setDiscovery}
|
||||||
|
/>
|
||||||
|
</FormGroup>
|
||||||
|
{discovery && (
|
||||||
|
<FormGroup
|
||||||
|
label={t("discoveryEndpoint")}
|
||||||
|
fieldId="kc-discovery-endpoint"
|
||||||
|
labelIcon={
|
||||||
|
<HelpItem
|
||||||
|
helpText="identity-providers-help:discoveryEndpoint"
|
||||||
|
forLabel={t("discoveryEndpoint")}
|
||||||
|
forID="kc-discovery-endpoint"
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
validated={
|
||||||
|
discoveryResult && discoveryResult.error
|
||||||
|
? "error"
|
||||||
|
: !discoveryResult
|
||||||
|
? "default"
|
||||||
|
: "success"
|
||||||
|
}
|
||||||
|
helperTextInvalid={t("noValidMetaDataFound")}
|
||||||
|
isRequired
|
||||||
|
>
|
||||||
|
<TextInput
|
||||||
|
type="text"
|
||||||
|
data-testid="discoveryEndpoint"
|
||||||
|
id="kc-discovery-endpoint"
|
||||||
|
placeholder="https://hostname/.well-known/openid-configuration"
|
||||||
|
value={discoveryUrl}
|
||||||
|
onChange={setDiscoveryUrl}
|
||||||
|
onBlur={() => setDiscovering(!discovering)}
|
||||||
|
validated={
|
||||||
|
discoveryResult && discoveryResult.error
|
||||||
|
? "error"
|
||||||
|
: !discoveryResult
|
||||||
|
? "default"
|
||||||
|
: "success"
|
||||||
|
}
|
||||||
|
customIconUrl={
|
||||||
|
discovering
|
||||||
|
? 'data:image/svg+xml;charset=utf8,%3Csvg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 100 100" preserveAspectRatio="xMidYMid"%3E%3Ccircle cx="50" cy="50" fill="none" stroke="%230066cc" stroke-width="10" r="35" stroke-dasharray="164.93361431346415 56.97787143782138"%3E%3CanimateTransform attributeName="transform" type="rotate" repeatCount="indefinite" dur="1s" values="0 50 50;360 50 50" keyTimes="0;1"%3E%3C/animateTransform%3E%3C/circle%3E%3C/svg%3E'
|
||||||
|
: ""
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</FormGroup>
|
||||||
|
)}
|
||||||
|
{!discovery && (
|
||||||
|
<FormGroup
|
||||||
|
label={t("importConfig")}
|
||||||
|
fieldId="kc-import-config"
|
||||||
|
labelIcon={
|
||||||
|
<HelpItem
|
||||||
|
helpText="identity-providers-help:importConfig"
|
||||||
|
forLabel={t("importConfig")}
|
||||||
|
forID="kc-import-config"
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
validated={
|
||||||
|
discoveryResult && discoveryResult.error ? "error" : "default"
|
||||||
|
}
|
||||||
|
helperTextInvalid={discoveryResult?.error?.toString()}
|
||||||
|
>
|
||||||
|
<JsonFileUpload
|
||||||
|
id="kc-import-config"
|
||||||
|
helpText="identity=providers-help:jsonFileUpload"
|
||||||
|
hideDefaultPreview
|
||||||
|
unWrap
|
||||||
|
validated={
|
||||||
|
discoveryResult && discoveryResult.error ? "error" : "default"
|
||||||
|
}
|
||||||
|
onChange={(value) => fileUpload(value as string)}
|
||||||
|
/>
|
||||||
|
</FormGroup>
|
||||||
|
)}
|
||||||
|
{discovery && discoveryResult && !discoveryResult.error && (
|
||||||
|
<DiscoverySettings readOnly={true} />
|
||||||
|
)}
|
||||||
|
{!discovery && <DiscoverySettings readOnly={false} />}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
76
src/identity-providers/component/ClientIdSecret.tsx
Normal file
76
src/identity-providers/component/ClientIdSecret.tsx
Normal file
|
@ -0,0 +1,76 @@
|
||||||
|
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";
|
||||||
|
|
||||||
|
export const ClientIdSecret = ({
|
||||||
|
secretRequired = true,
|
||||||
|
}: {
|
||||||
|
secretRequired?: boolean;
|
||||||
|
}) => {
|
||||||
|
const { t } = useTranslation("identity-providers");
|
||||||
|
const { t: th } = useTranslation("identity-providers-help");
|
||||||
|
|
||||||
|
const { register, errors } = useFormContext();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<FormGroup
|
||||||
|
label={t("clientId")}
|
||||||
|
labelIcon={
|
||||||
|
<HelpItem
|
||||||
|
helpText={th("clientId")}
|
||||||
|
forLabel={t("clientId")}
|
||||||
|
forID="kc-client-id"
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
fieldId="kc-client-id"
|
||||||
|
isRequired
|
||||||
|
validated={
|
||||||
|
errors.config && errors.config.clientId
|
||||||
|
? ValidatedOptions.error
|
||||||
|
: ValidatedOptions.default
|
||||||
|
}
|
||||||
|
helperTextInvalid={t("common:required")}
|
||||||
|
>
|
||||||
|
<TextInput
|
||||||
|
isRequired
|
||||||
|
type="text"
|
||||||
|
id="kc-client-id"
|
||||||
|
data-testid="clientId"
|
||||||
|
name="config.clientId"
|
||||||
|
ref={register({ required: true })}
|
||||||
|
/>
|
||||||
|
</FormGroup>
|
||||||
|
<FormGroup
|
||||||
|
label={t("clientSecret")}
|
||||||
|
labelIcon={
|
||||||
|
<HelpItem
|
||||||
|
helpText={th("clientSecret")}
|
||||||
|
forLabel={t("clientSecret")}
|
||||||
|
forID="kc-client-secret"
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
fieldId="kc-client-secret"
|
||||||
|
isRequired={secretRequired}
|
||||||
|
validated={
|
||||||
|
errors.config && errors.config.clientSecret
|
||||||
|
? ValidatedOptions.error
|
||||||
|
: ValidatedOptions.default
|
||||||
|
}
|
||||||
|
helperTextInvalid={t("common:required")}
|
||||||
|
>
|
||||||
|
<TextInput
|
||||||
|
isRequired={secretRequired}
|
||||||
|
type="password"
|
||||||
|
id="kc-client-secret"
|
||||||
|
data-testid="clientSecret"
|
||||||
|
name="config.clientSecret"
|
||||||
|
ref={register({ required: secretRequired })}
|
||||||
|
/>
|
||||||
|
</FormGroup>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
46
src/identity-providers/component/DisplayOrder.tsx
Normal file
46
src/identity-providers/component/DisplayOrder.tsx
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
import React from "react";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { Controller, useFormContext } from "react-hook-form";
|
||||||
|
import { FormGroup, NumberInput } from "@patternfly/react-core";
|
||||||
|
|
||||||
|
import { HelpItem } from "../../components/help-enabler/HelpItem";
|
||||||
|
|
||||||
|
export const DisplayOrder = () => {
|
||||||
|
const { t } = useTranslation("identity-providers");
|
||||||
|
const { t: th } = useTranslation("identity-providers-help");
|
||||||
|
|
||||||
|
const { control } = useFormContext();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<FormGroup
|
||||||
|
label={t("displayOrder")}
|
||||||
|
labelIcon={
|
||||||
|
<HelpItem
|
||||||
|
helpText={th("displayOrder")}
|
||||||
|
forLabel={t("displayOrder")}
|
||||||
|
forID="kc-display-order"
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
fieldId="kc-display-order"
|
||||||
|
>
|
||||||
|
<Controller
|
||||||
|
name="config.guiOrder"
|
||||||
|
control={control}
|
||||||
|
defaultValue={0}
|
||||||
|
render={({ onChange, value }) => (
|
||||||
|
<NumberInput
|
||||||
|
value={value}
|
||||||
|
data-testid="displayOrder"
|
||||||
|
onMinus={() => onChange(value - 1)}
|
||||||
|
onChange={onChange}
|
||||||
|
onPlus={() => onChange(value + 1)}
|
||||||
|
inputName="input"
|
||||||
|
inputAriaLabel={t("displayOrder")}
|
||||||
|
minusBtnAriaLabel={t("common:minus")}
|
||||||
|
plusBtnAriaLabel={t("common:plus")}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</FormGroup>
|
||||||
|
);
|
||||||
|
};
|
27
src/identity-providers/component/FormGroupField.tsx
Normal file
27
src/identity-providers/component/FormGroupField.tsx
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
import React, { ReactNode } from "react";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { FormGroup } from "@patternfly/react-core";
|
||||||
|
|
||||||
|
import { HelpItem } from "../../components/help-enabler/HelpItem";
|
||||||
|
|
||||||
|
export type FieldProps = { label: string; field: string; isReadOnly?: boolean };
|
||||||
|
export type FormGroupFieldProps = { label: string; children: ReactNode };
|
||||||
|
|
||||||
|
export const FormGroupField = ({ label, children }: FormGroupFieldProps) => {
|
||||||
|
const { t } = useTranslation("identity-providers");
|
||||||
|
return (
|
||||||
|
<FormGroup
|
||||||
|
label={t(label)}
|
||||||
|
fieldId={label}
|
||||||
|
labelIcon={
|
||||||
|
<HelpItem
|
||||||
|
helpText={`identity-providers-help:${label}`}
|
||||||
|
forLabel={t(label)}
|
||||||
|
forID={label}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</FormGroup>
|
||||||
|
);
|
||||||
|
};
|
37
src/identity-providers/component/RedirectUrl.tsx
Normal file
37
src/identity-providers/component/RedirectUrl.tsx
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
import React from "react";
|
||||||
|
import { useParams } from "react-router-dom";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { ClipboardCopy, FormGroup } from "@patternfly/react-core";
|
||||||
|
|
||||||
|
import { getBaseUrl } from "../../util";
|
||||||
|
import { HelpItem } from "../../components/help-enabler/HelpItem";
|
||||||
|
import { useRealm } from "../../context/realm-context/RealmContext";
|
||||||
|
import { useAdminClient } from "../../context/auth/AdminClient";
|
||||||
|
|
||||||
|
export const RedirectUrl = () => {
|
||||||
|
const { t } = useTranslation("identity-providers");
|
||||||
|
const { t: th } = useTranslation("identity-providers-help");
|
||||||
|
const { id } = useParams<{ id: string }>();
|
||||||
|
|
||||||
|
const adminClient = useAdminClient();
|
||||||
|
const { realm } = useRealm();
|
||||||
|
const callbackUrl = `${getBaseUrl(adminClient)}/realms/${realm}/broker`;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<FormGroup
|
||||||
|
label={t("redirectURI")}
|
||||||
|
labelIcon={
|
||||||
|
<HelpItem
|
||||||
|
helpText={th("redirectURI")}
|
||||||
|
forLabel={t("redirectURI")}
|
||||||
|
forID="kc-redirect-uri"
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
fieldId="kc-redirect-uri"
|
||||||
|
>
|
||||||
|
<ClipboardCopy
|
||||||
|
isReadOnly
|
||||||
|
>{`${callbackUrl}/${id}/endpoint`}</ClipboardCopy>
|
||||||
|
</FormGroup>
|
||||||
|
);
|
||||||
|
};
|
45
src/identity-providers/component/SwitchField.tsx
Normal file
45
src/identity-providers/component/SwitchField.tsx
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
import React from "react";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { Controller, useFormContext } from "react-hook-form";
|
||||||
|
import { Switch } from "@patternfly/react-core";
|
||||||
|
|
||||||
|
import { FieldProps, FormGroupField } from "./FormGroupField";
|
||||||
|
|
||||||
|
type FieldType = "boolean" | "string";
|
||||||
|
|
||||||
|
type SwitchFieldProps = FieldProps & {
|
||||||
|
fieldType?: FieldType;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const SwitchField = ({
|
||||||
|
label,
|
||||||
|
field,
|
||||||
|
fieldType = "string",
|
||||||
|
isReadOnly = false,
|
||||||
|
}: SwitchFieldProps) => {
|
||||||
|
const { t } = useTranslation("identity-providers");
|
||||||
|
const { control } = useFormContext();
|
||||||
|
return (
|
||||||
|
<FormGroupField label={label}>
|
||||||
|
<Controller
|
||||||
|
name={field}
|
||||||
|
defaultValue={fieldType === "string" ? "false" : false}
|
||||||
|
control={control}
|
||||||
|
render={({ onChange, value }) => (
|
||||||
|
<Switch
|
||||||
|
id={label}
|
||||||
|
label={t("common:on")}
|
||||||
|
labelOff={t("common:off")}
|
||||||
|
isChecked={
|
||||||
|
fieldType === "string" ? value === "true" : (value as boolean)
|
||||||
|
}
|
||||||
|
onChange={(value) =>
|
||||||
|
onChange(fieldType === "string" ? "" + value : value)
|
||||||
|
}
|
||||||
|
readOnly={isReadOnly}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</FormGroupField>
|
||||||
|
);
|
||||||
|
};
|
21
src/identity-providers/component/TextField.tsx
Normal file
21
src/identity-providers/component/TextField.tsx
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
import React from "react";
|
||||||
|
import { useFormContext } from "react-hook-form";
|
||||||
|
import { TextInput } from "@patternfly/react-core";
|
||||||
|
|
||||||
|
import { FieldProps, FormGroupField } from "./FormGroupField";
|
||||||
|
|
||||||
|
export const TextField = ({ label, field, isReadOnly = false }: FieldProps) => {
|
||||||
|
const { register } = useFormContext();
|
||||||
|
return (
|
||||||
|
<FormGroupField label={label}>
|
||||||
|
<TextInput
|
||||||
|
type="text"
|
||||||
|
id={label}
|
||||||
|
data-testid={label}
|
||||||
|
name={field}
|
||||||
|
ref={register}
|
||||||
|
isReadOnly={isReadOnly}
|
||||||
|
/>
|
||||||
|
</FormGroupField>
|
||||||
|
);
|
||||||
|
};
|
|
@ -1,8 +1,36 @@
|
||||||
{
|
{
|
||||||
"identity-providers-help": {
|
"identity-providers-help": {
|
||||||
"redirectURI": "The redirect uri to use when configuring the identity provider.",
|
"redirectURI": "The redirect uri to use when configuring the identity provider.",
|
||||||
|
"alias": "The alias uniquely identifies an identity provider and it is also used to build the redirect uri.",
|
||||||
|
"displayName": "Friendly name for Identity Providers.",
|
||||||
"clientId": "The client identifier registered with the identity provider.",
|
"clientId": "The client identifier registered with the identity provider.",
|
||||||
"clientSecret": "The client secret registered with the identity provider. This field is able to obtain its value from vault, use ${vault.ID} format.",
|
"clientSecret": "The client secret registered with the identity provider. This field is able to obtain its value from vault, use ${vault.ID} format.",
|
||||||
"displayOrder": "Number defining order of the provider in GUI (for example, on Login page)."
|
"displayOrder": "Number defining order of the provider in GUI (for example, on Login page).",
|
||||||
|
"useDiscoveryEndpoint": "If this setting is enabled, the discovery endpoint will be used to fetch the provider config. Keycloak can load the config from the endpoint and automatically update the config if the source has any updates",
|
||||||
|
"discoveryEndpoint": "Import metadata from a remote IDP discovery descriptor.",
|
||||||
|
"importConfig": "Import metadata from a downloaded IDP discovery descriptor.",
|
||||||
|
"passLoginHint": "Pass login_hint to identity provider.",
|
||||||
|
"passCurrentLocale": "Pass the current locale to the identity provider as a ui_locales parameter.",
|
||||||
|
"logoutUrl": "End session endpoint to use to logout user from external IDP.",
|
||||||
|
"backchannelLogout": "Does the external IDP support backchannel logout?",
|
||||||
|
"disableUserInfo": "Disable usage of User Info service to obtain additional user information? Default is to use this OIDC service.",
|
||||||
|
"userInfoUrl": "The User Info Url. This is optional.",
|
||||||
|
"issuer": "The issuer identifier for the issuer of the response. If not provided, no validation will be performed.",
|
||||||
|
"scopes": "The scopes to be sent when asking for authorization. It can be a space-separated list of scopes. Defaults to 'openid'.",
|
||||||
|
"prompt": "Specifies whether the Authorization Server prompts the End-User for reauthentication and consent.",
|
||||||
|
"acceptsPromptNone": "This is just used together with Identity Provider Authenticator or when kc_idp_hint points to this identity provider. In case that client sends a request with prompt=none and user is not yet authenticated, the error will not be directly returned to client, but the request with prompt=none will be forwarded to this identity provider.",
|
||||||
|
"validateSignature": "Enable/disable signature validation of external IDP signatures.",
|
||||||
|
"useJwksUrl": "If the switch is on, identity provider public keys will be downloaded from given JWKS URL. This allows great flexibility because new keys will be always re-downloaded again when identity provider generates new keypair. If the switch is off, public key (or certificate) from the Keycloak DB is used, so when the identity provider keypair changes, you always need to import the new key to the Keycloak DB as well.",
|
||||||
|
"jwksUrl": "URL where identity provider keys in JWK format are stored. See JWK specification for more details. If you use external Keycloak identity provider, you can use URL like 'http://broker-keycloak:8180/auth/realms/test/protocol/openid-connect/certs' assuming your brokered Keycloak is running on 'http://broker-keycloak:8180' and its realm is 'test' .",
|
||||||
|
"allowedClockSkew": "Clock skew in seconds that is tolerated when validating identity provider tokens. Default value is zero.",
|
||||||
|
"forwardParameters": "Non OpenID Connect/OAuth standard query parameters to be forwarded to external IDP from the initial application request to Authorization Endpoint. Multiple parameters can be entered, separated by comma (,).",
|
||||||
|
"clientAuthentication": "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.",
|
||||||
|
"trustEmail": "If enabled, email provided by this provider is not verified even if verification is enabled for the realm.",
|
||||||
|
"accountLinkingOnly": "If true, users cannot log in through this provider. They can only link to this provider. This is useful if you don't want to allow login from the provider, but want to integrate with a provider",
|
||||||
|
"hideOnLoginPage": "If hidden, login with this provider is possible only if requested explicitly, for example using the 'kc_idp_hint' parameter.",
|
||||||
|
"firstBrokerLoginFlowAlias": "Alias of authentication flow, which is triggered after first login with this identity provider. Term 'First Login' means that no Keycloak account is currently linked to the authenticated identity provider account.",
|
||||||
|
"postBrokerLoginFlowAlias": "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 empty 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."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,11 +4,18 @@
|
||||||
"searchForProvider": "Search for provider",
|
"searchForProvider": "Search for provider",
|
||||||
"provider": "Provider",
|
"provider": "Provider",
|
||||||
"addProvider": "Add provider",
|
"addProvider": "Add provider",
|
||||||
|
"addOpenIdProvider": "Add OpenId Connect provider",
|
||||||
"manageDisplayOrder": "Manage display order",
|
"manageDisplayOrder": "Manage display order",
|
||||||
"deleteProvider": "Delete provider?",
|
"deleteProvider": "Delete provider?",
|
||||||
"deleteConfirm": "Are you sure you want to permanently delete the provider '{{provider}}'",
|
"deleteConfirm": "Are you sure you want to permanently delete the provider '{{provider}}'",
|
||||||
"deletedSuccess": "Provider successfully deleted",
|
"deletedSuccess": "Provider successfully deleted",
|
||||||
"deleteError": "Could not delete the provider {{error}}",
|
"deleteError": "Could not delete the provider {{error}}",
|
||||||
|
"disableProvider": "Disable provider?",
|
||||||
|
"disableConfirm": "Are you sure you want to disable the provider '{{provider}}'",
|
||||||
|
"disableSuccess": "Provider successfully disabled",
|
||||||
|
"disableError": "Could not disable the provider {{error}}",
|
||||||
|
"updateSuccess": "Provider successfully updated",
|
||||||
|
"updateError": "Could not update the provider {{error}}",
|
||||||
"getStarted": "To get started, select a provider from the list below.",
|
"getStarted": "To get started, select a provider from the list below.",
|
||||||
"addIdentityProvider": "Add {{provider}} provider",
|
"addIdentityProvider": "Add {{provider}} provider",
|
||||||
"redirectURI": "Redirect URI",
|
"redirectURI": "Redirect URI",
|
||||||
|
@ -25,6 +32,62 @@
|
||||||
"onDragCancel": "Dragging cancelled. List is unchanged.",
|
"onDragCancel": "Dragging cancelled. List is unchanged.",
|
||||||
"onDragFinish": "Dragging finished {{list}}",
|
"onDragFinish": "Dragging finished {{list}}",
|
||||||
"orderChangeSuccess": "Successfully changed display order of identity providers",
|
"orderChangeSuccess": "Successfully changed display order of identity providers",
|
||||||
"orderChangeError": "Could not change display order of identity providers {{error}}"
|
"orderChangeError": "Could not change display order of identity providers {{error}}",
|
||||||
|
"alias": "Alias",
|
||||||
|
"displayName": "Display name",
|
||||||
|
"useDiscoveryEndpoint": "Use discovery endpoint",
|
||||||
|
"discoveryEndpoint": "Discovery endpoint",
|
||||||
|
"importConfig": "Import config from file",
|
||||||
|
"showMetaData": "Show metadata",
|
||||||
|
"hideMetaData": "Hide metadata",
|
||||||
|
"noValidMetaDataFound": "No valid metadata was found at this URL",
|
||||||
|
"advanced": "Advanced",
|
||||||
|
"metadataOfDiscoveryEndpoint": "Metadata of the discovery endpoint",
|
||||||
|
"authorizationUrl": "Authorization URL",
|
||||||
|
"passLoginHint": "Pass login_hint",
|
||||||
|
"passCurrentLocale": "Pass current locale",
|
||||||
|
"tokenUrl": "Token URL",
|
||||||
|
"logoutUrl": "Logout URL",
|
||||||
|
"backchannelLogout": "Backchannel logout",
|
||||||
|
"disableUserInfo": "Disable user info",
|
||||||
|
"userInfoUrl": "User Info URL",
|
||||||
|
"issuer": "Issuer",
|
||||||
|
"scopes": "Default scopes",
|
||||||
|
"prompt": "Prompt",
|
||||||
|
"prompts": {
|
||||||
|
"unspecified": "Unspecified",
|
||||||
|
"none": "None",
|
||||||
|
"consent": "Consent",
|
||||||
|
"login": "Login",
|
||||||
|
"select_account": "Select account"
|
||||||
|
},
|
||||||
|
"clientAuthentication" : "Client authentication",
|
||||||
|
"clientAuthentications": {
|
||||||
|
"clientAuth_post": "Client secret sent as post",
|
||||||
|
"clientAuth_basic" : "Client secret sent as basic auth",
|
||||||
|
"clientAuth_secret_jwt" : "Client secret as jwt",
|
||||||
|
"clientAuth_privatekey_jwt" : "JWT signed with private key"
|
||||||
|
},
|
||||||
|
"acceptsPromptNone": "Accepts prompt=none forward from client",
|
||||||
|
"validateSignature": "Validate Signatures",
|
||||||
|
"useJwksUrl": "Use JWKS URL",
|
||||||
|
"jwksUrl": "JWKS URL",
|
||||||
|
"allowedClockSkew": "Allowed clock skew",
|
||||||
|
"forwardParameters": "Forwarded query parameters",
|
||||||
|
"generalSettings": "General settings",
|
||||||
|
"oidcSettings": "OpenId Connect settings",
|
||||||
|
"advancedSettings": "Advanced settings",
|
||||||
|
"storeTokens": "Store tokens",
|
||||||
|
"trustEmail": "Trust Email",
|
||||||
|
"accountLinkingOnly": "Account linking only",
|
||||||
|
"hideOnLoginPage": "Hide on login page",
|
||||||
|
"firstBrokerLoginFlowAlias": "First login flow",
|
||||||
|
"postBrokerLoginFlowAlias": "Post login flow",
|
||||||
|
"syncMode": "Sync mode",
|
||||||
|
"syncModes": {
|
||||||
|
"import": "Import",
|
||||||
|
"legacy": "Legacy",
|
||||||
|
"force": "Force"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,6 +31,8 @@ import { SearchGroups } from "./groups/SearchGroups";
|
||||||
import { CreateInitialAccessToken } from "./clients/initial-access/CreateInitialAccessToken";
|
import { CreateInitialAccessToken } from "./clients/initial-access/CreateInitialAccessToken";
|
||||||
import { LdapMapperDetails } from "./user-federation/ldap/mappers/LdapMapperDetails";
|
import { LdapMapperDetails } from "./user-federation/ldap/mappers/LdapMapperDetails";
|
||||||
import { AddIdentityProvider } from "./identity-providers/add/AddIdentityProvider";
|
import { AddIdentityProvider } from "./identity-providers/add/AddIdentityProvider";
|
||||||
|
import { AddOpenIdConnect } from "./identity-providers/add/AddOpenIdConnect";
|
||||||
|
import { DetailSettings } from "./identity-providers/add/DetailSettings";
|
||||||
|
|
||||||
export type RouteDef = BreadcrumbsRoute & {
|
export type RouteDef = BreadcrumbsRoute & {
|
||||||
access: AccessType;
|
access: AccessType;
|
||||||
|
@ -190,12 +192,24 @@ export const routes: RoutesFn = (t: TFunction) => [
|
||||||
breadcrumb: t("identityProviders"),
|
breadcrumb: t("identityProviders"),
|
||||||
access: "view-identity-providers",
|
access: "view-identity-providers",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: "/:realm/identity-providers/oidc",
|
||||||
|
component: AddOpenIdConnect,
|
||||||
|
breadcrumb: t("identity-providers:addOpenIdProvider"),
|
||||||
|
access: "manage-identity-providers",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: "/:realm/identity-providers/:id",
|
path: "/:realm/identity-providers/:id",
|
||||||
component: AddIdentityProvider,
|
component: AddIdentityProvider,
|
||||||
breadcrumb: t("identity-providers:provider"),
|
breadcrumb: t("identity-providers:provider"),
|
||||||
access: "manage-identity-providers",
|
access: "manage-identity-providers",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: "/:realm/identity-providers/:id/settings",
|
||||||
|
component: DetailSettings,
|
||||||
|
breadcrumb: null,
|
||||||
|
access: "manage-identity-providers",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: "/:realm/user-federation",
|
path: "/:realm/user-federation",
|
||||||
component: UserFederationSection,
|
component: UserFederationSection,
|
||||||
|
|
Loading…
Reference in a new issue