Add extended fields for identity providers (#3167)
This commit is contained in:
parent
b9e2ba2a2f
commit
a9e767da57
5 changed files with 317 additions and 6 deletions
|
@ -90,5 +90,24 @@
|
||||||
"attributeValue": "Value the attribute must have. If the attribute is a list, then the value must be contained in the list.",
|
"attributeValue": "Value the attribute must have. If the attribute is a list, then the value must be contained in the list.",
|
||||||
"attributes": "Name and (regex) value of the attributes to search for in token. The configured name of an attribute is searched in SAML attribute name and attribute friendly name fields. Every given attribute description must be met to set the role. If the attribute is an array, then the value must be contained in the array. If an attribute can be found several times, then one match is sufficient.",
|
"attributes": "Name and (regex) value of the attributes to search for in token. The configured name of an attribute is searched in SAML attribute name and attribute friendly name fields. Every given attribute description must be met to set the role. If the attribute is an array, then the value must be contained in the array. If an attribute can be found several times, then one match is sufficient.",
|
||||||
"regexAttributeValues": "If enabled attribute values are interpreted as regular expressions.",
|
"regexAttributeValues": "If enabled attribute values are interpreted as regular expressions.",
|
||||||
"role": "Role to grant to user if all attributes are present. Click 'Select Role' button to browse roles, or just type it in the textbox. To reference a client role the syntax is clientname.clientrole, i.e. myclient.myrole"
|
"role": "Role to grant to user if all attributes are present. Click 'Select Role' button to browse roles, or just type it in the textbox. To reference a client role the syntax is clientname.clientrole, i.e. myclient.myrole",
|
||||||
|
"baseUrl": "Override the default Base URL for this identity provider.",
|
||||||
|
"apiUrl": "Override the default API URL for this identity provider.",
|
||||||
|
"facebook": {
|
||||||
|
"fetchedFields": "Provide additional fields which would be fetched using the profile request. This will be appended to the default set of 'id,name,email,first_name,last_name'."
|
||||||
|
},
|
||||||
|
"google": {
|
||||||
|
"hostedDomain": "Set 'hd' query parameter when logging in with Google. Google will list accounts only for this domain. Keycloak validates that the returned identity token has a claim for this domain. When '*' is entered, any hosted account can be used.",
|
||||||
|
"userIp": "Set 'userIp' query parameter when invoking on Google's User Info service. This will use the user's ip address. Useful if Google is throttling access to the User Info service.",
|
||||||
|
"offlineAccess": "Set 'access_type' query parameter to 'offline' when redirecting to google authorization endpoint, to get a refresh token back. Useful if planning to use Token Exchange to retrieve Google token to access Google APIs when the user is not at the browser."
|
||||||
|
},
|
||||||
|
"openshift": {
|
||||||
|
"baseUrl": "Base Url to OpenShift Online API"
|
||||||
|
},
|
||||||
|
"paypal": {
|
||||||
|
"sandbox": "Target PayPal's sandbox environment"
|
||||||
|
},
|
||||||
|
"stackoverflow": {
|
||||||
|
"key": "The Key obtained from Stack Overflow client registration."
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -173,5 +173,21 @@
|
||||||
"local": "LOCAL",
|
"local": "LOCAL",
|
||||||
"brokerId": "BROKER_ID",
|
"brokerId": "BROKER_ID",
|
||||||
"brokerUsername": "BROKER_USERNAME"
|
"brokerUsername": "BROKER_USERNAME"
|
||||||
|
},
|
||||||
|
"baseUrl": "Base URL",
|
||||||
|
"apiUrl": "API URL",
|
||||||
|
"facebook": {
|
||||||
|
"fetchedFields": "Additional user's profile fields"
|
||||||
|
},
|
||||||
|
"google": {
|
||||||
|
"hostedDomain": "Hosted Domain",
|
||||||
|
"userIp": "Use userIp Param",
|
||||||
|
"offlineAccess": "Request refresh token"
|
||||||
|
},
|
||||||
|
"paypal": {
|
||||||
|
"sandbox": "Target Sandbox"
|
||||||
|
},
|
||||||
|
"stackoverflow": {
|
||||||
|
"key": "Key"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,6 +10,7 @@ import {
|
||||||
} from "@patternfly/react-core";
|
} from "@patternfly/react-core";
|
||||||
|
|
||||||
import type IdentityProviderRepresentation from "@keycloak/keycloak-admin-client/lib/defs/identityProviderRepresentation";
|
import type IdentityProviderRepresentation from "@keycloak/keycloak-admin-client/lib/defs/identityProviderRepresentation";
|
||||||
|
import { ExtendedFieldsForm } from "../component/ExtendedFieldsForm";
|
||||||
import { ViewHeader } from "../../components/view-header/ViewHeader";
|
import { ViewHeader } from "../../components/view-header/ViewHeader";
|
||||||
import { toUpperCase } from "../../util";
|
import { toUpperCase } from "../../util";
|
||||||
import { FormAccess } from "../../components/form-access/FormAccess";
|
import { FormAccess } from "../../components/form-access/FormAccess";
|
||||||
|
@ -46,8 +47,8 @@ export default function AddIdentityProvider() {
|
||||||
navigate(
|
navigate(
|
||||||
toIdentityProvider({
|
toIdentityProvider({
|
||||||
realm,
|
realm,
|
||||||
providerId: providerId!,
|
providerId,
|
||||||
alias: providerId!,
|
alias: providerId,
|
||||||
tab: "settings",
|
tab: "settings",
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
@ -60,7 +61,7 @@ export default function AddIdentityProvider() {
|
||||||
<>
|
<>
|
||||||
<ViewHeader
|
<ViewHeader
|
||||||
titleKey={t("addIdentityProvider", {
|
titleKey={t("addIdentityProvider", {
|
||||||
provider: toUpperCase(providerId!),
|
provider: toUpperCase(providerId),
|
||||||
})}
|
})}
|
||||||
/>
|
/>
|
||||||
<PageSection variant="light">
|
<PageSection variant="light">
|
||||||
|
@ -70,7 +71,8 @@ export default function AddIdentityProvider() {
|
||||||
onSubmit={handleSubmit(save)}
|
onSubmit={handleSubmit(save)}
|
||||||
>
|
>
|
||||||
<FormProvider {...form}>
|
<FormProvider {...form}>
|
||||||
<GeneralSettings id={providerId!} />
|
<GeneralSettings id={providerId} />
|
||||||
|
<ExtendedFieldsForm providerId={providerId} />
|
||||||
</FormProvider>
|
</FormProvider>
|
||||||
<ActionGroup>
|
<ActionGroup>
|
||||||
<Button
|
<Button
|
||||||
|
|
|
@ -55,6 +55,7 @@ import {
|
||||||
toIdentityProvider,
|
toIdentityProvider,
|
||||||
} from "../routes/IdentityProvider";
|
} from "../routes/IdentityProvider";
|
||||||
import { PermissionsTab } from "../../components/permission-tab/PermissionTab";
|
import { PermissionsTab } from "../../components/permission-tab/PermissionTab";
|
||||||
|
import { ExtendedFieldsForm } from "../component/ExtendedFieldsForm";
|
||||||
|
|
||||||
type HeaderProps = {
|
type HeaderProps = {
|
||||||
onChange: (value: boolean) => void;
|
onChange: (value: boolean) => void;
|
||||||
|
@ -276,7 +277,12 @@ export default function DetailSettings() {
|
||||||
isHorizontal
|
isHorizontal
|
||||||
onSubmit={handleSubmit(save)}
|
onSubmit={handleSubmit(save)}
|
||||||
>
|
>
|
||||||
{!isOIDC && !isSAML && <GeneralSettings create={false} id={alias} />}
|
{!isOIDC && !isSAML && (
|
||||||
|
<>
|
||||||
|
<GeneralSettings create={false} id={alias} />
|
||||||
|
<ExtendedFieldsForm providerId={alias} />
|
||||||
|
</>
|
||||||
|
)}
|
||||||
{isOIDC && <OIDCGeneralSettings id={alias} />}
|
{isOIDC && <OIDCGeneralSettings id={alias} />}
|
||||||
{isSAML && <SamlGeneralSettings id={alias} />}
|
{isSAML && <SamlGeneralSettings id={alias} />}
|
||||||
</FormAccess>
|
</FormAccess>
|
||||||
|
|
268
src/identity-providers/component/ExtendedFieldsForm.tsx
Normal file
268
src/identity-providers/component/ExtendedFieldsForm.tsx
Normal file
|
@ -0,0 +1,268 @@
|
||||||
|
import { FormGroup, Switch, ValidatedOptions } from "@patternfly/react-core";
|
||||||
|
import { Controller, useFormContext } from "react-hook-form";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { HelpItem } from "../../components/help-enabler/HelpItem";
|
||||||
|
import { KeycloakTextInput } from "../../components/keycloak-text-input/KeycloakTextInput";
|
||||||
|
|
||||||
|
type ExtendedFieldsFormProps = {
|
||||||
|
providerId: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ExtendedFieldsForm = ({ providerId }: ExtendedFieldsFormProps) => {
|
||||||
|
switch (providerId) {
|
||||||
|
case "facebook":
|
||||||
|
return <FacebookFields />;
|
||||||
|
case "github":
|
||||||
|
return <GithubFields />;
|
||||||
|
case "google":
|
||||||
|
return <GoogleFields />;
|
||||||
|
case "openshift-v3":
|
||||||
|
case "openshift-v4":
|
||||||
|
return <OpenshiftFields />;
|
||||||
|
case "paypal":
|
||||||
|
return <PaypalFields />;
|
||||||
|
case "stackoverflow":
|
||||||
|
return <StackoverflowFields />;
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const FacebookFields = () => {
|
||||||
|
const { t } = useTranslation("identity-providers");
|
||||||
|
const { register } = useFormContext();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<FormGroup
|
||||||
|
label={t("facebook.fetchedFields")}
|
||||||
|
labelIcon={
|
||||||
|
<HelpItem
|
||||||
|
helpText="identity-providers-help:facebook:fetchedFields"
|
||||||
|
fieldLabelId="identity-providers:facebook:fetchedFields"
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
fieldId="facebookFetchedFields"
|
||||||
|
>
|
||||||
|
<KeycloakTextInput
|
||||||
|
id="facebookFetchedFields"
|
||||||
|
name="config.fetchedFields"
|
||||||
|
ref={register()}
|
||||||
|
/>
|
||||||
|
</FormGroup>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const GithubFields = () => {
|
||||||
|
const { t } = useTranslation("identity-providers");
|
||||||
|
const { register } = useFormContext();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<FormGroup
|
||||||
|
label={t("baseUrl")}
|
||||||
|
labelIcon={
|
||||||
|
<HelpItem
|
||||||
|
helpText="identity-providers-help:baseUrl"
|
||||||
|
fieldLabelId="identity-providers:baseUrl"
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
fieldId="baseUrl"
|
||||||
|
>
|
||||||
|
<KeycloakTextInput
|
||||||
|
id="baseUrl"
|
||||||
|
name="config.baseUrl"
|
||||||
|
ref={register()}
|
||||||
|
/>
|
||||||
|
</FormGroup>
|
||||||
|
<FormGroup
|
||||||
|
label={t("apiUrl")}
|
||||||
|
labelIcon={
|
||||||
|
<HelpItem
|
||||||
|
helpText="identity-providers-help:apiUrl"
|
||||||
|
fieldLabelId="identity-providers:apiUrl"
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
fieldId="apiUrl"
|
||||||
|
>
|
||||||
|
<KeycloakTextInput id="apiUrl" name="config.apiUrl" ref={register()} />
|
||||||
|
</FormGroup>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const GoogleFields = () => {
|
||||||
|
const { t } = useTranslation("identity-providers");
|
||||||
|
const { register, control } = useFormContext();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<FormGroup
|
||||||
|
label={t("google.hostedDomain")}
|
||||||
|
labelIcon={
|
||||||
|
<HelpItem
|
||||||
|
helpText="identity-providers-help:google:hostedDomain"
|
||||||
|
fieldLabelId="identity-providers:google:hostedDomain"
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
fieldId="googleHostedDomain"
|
||||||
|
>
|
||||||
|
<KeycloakTextInput
|
||||||
|
id="googleHostedDomain"
|
||||||
|
name="config.hostedDomain"
|
||||||
|
ref={register()}
|
||||||
|
/>
|
||||||
|
</FormGroup>
|
||||||
|
<FormGroup
|
||||||
|
label={t("google.userIp")}
|
||||||
|
labelIcon={
|
||||||
|
<HelpItem
|
||||||
|
helpText="identity-providers-help:google:userIp"
|
||||||
|
fieldLabelId="identity-providers:google:userIp"
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
fieldId="googleUserIp"
|
||||||
|
>
|
||||||
|
<Controller
|
||||||
|
name="config.userIp"
|
||||||
|
defaultValue="false"
|
||||||
|
control={control}
|
||||||
|
render={({ onChange, value }) => (
|
||||||
|
<Switch
|
||||||
|
id="googleUserIp"
|
||||||
|
label={t("common:on")}
|
||||||
|
labelOff={t("common:off")}
|
||||||
|
isChecked={value === "true"}
|
||||||
|
onChange={(value) => onChange(value.toString())}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</FormGroup>
|
||||||
|
<FormGroup
|
||||||
|
label={t("google.offlineAccess")}
|
||||||
|
labelIcon={
|
||||||
|
<HelpItem
|
||||||
|
helpText="identity-providers-help:google:offlineAccess"
|
||||||
|
fieldLabelId="identity-providers:google:offlineAccess"
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
fieldId="googleOfflineAccess"
|
||||||
|
>
|
||||||
|
<Controller
|
||||||
|
name="config.offlineAccess"
|
||||||
|
defaultValue="false"
|
||||||
|
control={control}
|
||||||
|
render={({ onChange, value }) => (
|
||||||
|
<Switch
|
||||||
|
id="googleOfflineAccess"
|
||||||
|
label={t("common:on")}
|
||||||
|
labelOff={t("common:off")}
|
||||||
|
isChecked={value === "true"}
|
||||||
|
onChange={(value) => onChange(value.toString())}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</FormGroup>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const OpenshiftFields = () => {
|
||||||
|
const { t } = useTranslation("identity-providers");
|
||||||
|
const {
|
||||||
|
register,
|
||||||
|
formState: { errors },
|
||||||
|
} = useFormContext();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<FormGroup
|
||||||
|
label={t("baseUrl")}
|
||||||
|
labelIcon={
|
||||||
|
<HelpItem
|
||||||
|
helpText="identity-providers-help:openshift:baseUrl"
|
||||||
|
fieldLabelId="identity-providers:baseUrl"
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
fieldId="baseUrl"
|
||||||
|
isRequired
|
||||||
|
validated={
|
||||||
|
errors.config?.baseUrl
|
||||||
|
? ValidatedOptions.error
|
||||||
|
: ValidatedOptions.default
|
||||||
|
}
|
||||||
|
helperTextInvalid={t("common:required")}
|
||||||
|
>
|
||||||
|
<KeycloakTextInput
|
||||||
|
id="baseUrl"
|
||||||
|
name="config.baseUrl"
|
||||||
|
ref={register({ required: true })}
|
||||||
|
isRequired
|
||||||
|
/>
|
||||||
|
</FormGroup>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const PaypalFields = () => {
|
||||||
|
const { t } = useTranslation("identity-providers");
|
||||||
|
const { control } = useFormContext();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<FormGroup
|
||||||
|
label={t("paypal.sandbox")}
|
||||||
|
labelIcon={
|
||||||
|
<HelpItem
|
||||||
|
helpText="identity-providers-help:paypal:sandbox"
|
||||||
|
fieldLabelId="identity-providers:paypal:sandbox"
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
fieldId="paypalSandbox"
|
||||||
|
>
|
||||||
|
<Controller
|
||||||
|
name="config.sandbox"
|
||||||
|
defaultValue="false"
|
||||||
|
control={control}
|
||||||
|
render={({ onChange, value }) => (
|
||||||
|
<Switch
|
||||||
|
id="paypalSandbox"
|
||||||
|
label={t("common:on")}
|
||||||
|
labelOff={t("common:off")}
|
||||||
|
isChecked={value === "true"}
|
||||||
|
onChange={(value) => onChange(value.toString())}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</FormGroup>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const StackoverflowFields = () => {
|
||||||
|
const { t } = useTranslation("identity-providers");
|
||||||
|
const {
|
||||||
|
register,
|
||||||
|
formState: { errors },
|
||||||
|
} = useFormContext();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<FormGroup
|
||||||
|
label={t("stackoverflow.key")}
|
||||||
|
labelIcon={
|
||||||
|
<HelpItem
|
||||||
|
helpText="identity-providers-help:stackoverflow:key"
|
||||||
|
fieldLabelId="identity-providers:stackoverflow:key"
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
fieldId="stackoverflowKey"
|
||||||
|
isRequired
|
||||||
|
validated={
|
||||||
|
errors.config?.key ? ValidatedOptions.error : ValidatedOptions.default
|
||||||
|
}
|
||||||
|
helperTextInvalid={t("common:required")}
|
||||||
|
>
|
||||||
|
<KeycloakTextInput
|
||||||
|
id="stackoverflowKey"
|
||||||
|
name="config.key"
|
||||||
|
ref={register({ required: true })}
|
||||||
|
isRequired
|
||||||
|
/>
|
||||||
|
</FormGroup>
|
||||||
|
);
|
||||||
|
};
|
Loading…
Reference in a new issue