Skip to content

Commit

Permalink
SAML/ OIDC Identity Provider AutoUpdate
Browse files Browse the repository at this point in the history
Closes keycloak#11692

Signed-off-by: cgeorgilakis-grnet <[email protected]>
  • Loading branch information
cgeorgilakis-grnet committed Jan 7, 2025
1 parent 7cb7718 commit f4bc8e4
Show file tree
Hide file tree
Showing 36 changed files with 634 additions and 47 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,8 @@ public class RealmRepresentation {

protected Boolean organizationsEnabled;
private List<OrganizationRepresentation> organizations;
protected Long autoUpdatedIdPsInterval;
protected Long autoUpdatedIdPsLastRefreshTime;

protected Boolean verifiableCredentialsEnabled;

Expand Down Expand Up @@ -1489,7 +1491,23 @@ public void addOrganization(OrganizationRepresentation org) {
organizations.add(org);
}

public enum BruteForceStrategy {
public enum BruteForceStrategy {
LINEAR, MULTIPLE;
}

public Long getAutoUpdatedIdPsInterval() {
return autoUpdatedIdPsInterval;
}

public void setAutoUpdatedIdPsInterval(Long autoUpdatedIdPsInterval) {
this.autoUpdatedIdPsInterval = autoUpdatedIdPsInterval;
}

public Long getAutoUpdatedIdPsLastRefreshTime() {
return autoUpdatedIdPsLastRefreshTime;
}

public void setAutoUpdatedIdPsLastRefreshTime(Long autoUpdatedIdPsLastRefreshTime) {
this.autoUpdatedIdPsLastRefreshTime = autoUpdatedIdPsLastRefreshTime;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,12 @@ image:images/oidc-add-identity-provider.png[Add Identity Provider]
|===
|Configuration|Description

|Auto Update
|When Auto Update is enabling, metadata descriptor URL is required and IdP attributes will be updated based on metadata descriptor URL.

|Metadata descriptor URL
|External URL where Identity Provider publishes the discovery descriptor metadata.

|Authorization URL
|The authorization URL endpoint the OIDC protocol requires.

Expand Down Expand Up @@ -82,4 +88,4 @@ If the user is unauthenticated in the IDP, the client still receives a `login_re

You can import all this configuration data by providing a URL or file that points to OpenID Provider Metadata. If you connect to a {project_name} external IDP, you can import the IDP settings from `<root>{kc_realms_path}/{realm-name}/.well-known/openid-configuration`. This link is a JSON document describing metadata about the IDP.

If you want to use https://datatracker.ietf.org/doc/html/rfc7516[Json Web Encryption (JWE)] ID Tokens or UserInfo responses in the provider, the IDP needs to know the public key to use with {project_name}. The provider uses the <<realm_keys, realm keys>> defined for the different encryption algorithms to decrypt the tokens. {project_name} provides a standard xref:con-server-oidc-uri-endpoints_{context}[JWKS endpoint] which the IDP can use for downloading the keys automatically.
If you want to use https://datatracker.ietf.org/doc/html/rfc7516[Json Web Encryption (JWE)] ID Tokens or UserInfo responses in the provider, the IDP needs to know the public key to use with {project_name}. The provider uses the <<realm_keys, realm keys>> defined for the different encryption algorithms to decrypt the tokens. {project_name} provides a standard xref:con-server-oidc-uri-endpoints_{context}[JWKS endpoint] which the IDP can use for downloading the keys automatically.
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,12 @@ image:images/saml-add-identity-provider.png[Add Identity Provider]
|===
|Configuration|Description

|Auto Update
|When Auto Update is enabling, metadata descriptor URL is required and IdP attributes will be updated based on metadata descriptor URL.

|Metadata descriptor URL
|External URL where Identity Provider publishes the `IDPSSODescriptor` metadata. This URL is used either to update IdP attributes when 'Auto Update' is true either to download the Identity Provider certificates when the `Reload keys` or `Import keys` actions are clicked.

|Service Provider Entity ID
|The SAML Entity ID that the remote Identity Provider uses to identify requests from this Service Provider. By default, this setting is set to the realms base URL `<root>{kc_realms_path}/{realm-name}`.

Expand Down Expand Up @@ -90,9 +96,6 @@ itself.
|Validate Signature
|When *ON*, the realm expects SAML requests and responses from the external IDP to be digitally signed.

|Metadata descriptor URL
|External URL where Identity Provider publishes the `IDPSSODescriptor` metadata. This URL is used to download the Identity Provider certificates when the `Reload keys` or `Import keys` actions are clicked.

|Use metadata descriptor URL
|When *ON*, the certificates to validate signatures are automatically downloaded from the `Metadata descriptor URL` and cached in {project_name}. The SAML provider can validate signatures in two different ways. If a specific certificate is requested (usually in `POST` binding) and it is not in the cache, certificates are automatically refreshed from the URL. If all certificates are requested to validate the signature (`REDIRECT` binding) the refresh is only done after a max cache time (see https://www.keycloak.org/server/all-provider-config[public-key-storage] spi in the all provider config guide for more information about how the cache works). The cache can also be manually updated using the action `Reload Keys` in the identity provider page.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -547,7 +547,7 @@ used.DEFAULT=Default
authenticationCreateFlowHelp=Create flow
credentialResetEmailSuccess=Email sent to user.
sslType.all=All requests
discoveryEndpointHelp=Import metadata from a remote IDP discovery descriptor.
discoveryEndpointHelp=Import metadata from a remote IDP discovery descriptor. This url is used also for auto-update IdP.
excludeSessionStateFromAuthenticationResponse=Exclude Session State From Authentication Response
required=Required field
linkedIdPsText=The identity providers which are already linked to this user account
Expand Down Expand Up @@ -1050,7 +1050,7 @@ importKeys=Import keys
useMetadataDescriptorUrl=Use metadata descriptor URL
useMetadataDescriptorUrlHelp=If the switch is on, the certificates to validate signatures will be downloaded and cached from the given "Metadata descriptor URL". The "Reload keys" action can be used to refresh the certificates in the cache. If the switch is off, certificates from "Validating X509 certificates" option are used, they need to be manually updated when changed in the IDP.
metadataDescriptorUrl=Metadata descriptor URL
metadataDescriptorUrlHelp=External URL where Identity Provider publishes the metadata information needed by the client (certificates, keys, other URLs,...).
metadataDescriptorUrlHelp=External URL where Identity Provider publishes the metadata information needed by the client (certificates, keys, other URLs,...). This url is used for auto-updated IdPs and when use metadata descriptor URL is true.
reloadKeysSuccess=Keys successfully reloaded
reloadKeysError=Error reloading keys. {{error}}
reloadKeysSuccessButFalse=The reload was not executed, maybe the time between request was too short.
Expand Down Expand Up @@ -3333,4 +3333,9 @@ downloadThemeJar=Download theme JAR
themeColorInfo=Here you can set the patternfly color variables and create a "theme jar" file that you can download and put in your providers folder to apply the theme to your realm.
permissionsSubTitle=Fine-grained admin permissions allow assigning detailed, specific access rights, controlling which resources and actions can be managed.
connectionTrace=Connection trace
connectionTraceHelp=If enabled, incoming and outgoing LDAP ASN.1 BER packets will be dumped to the error output stream. Be careful when enabling this option in production as it will expose all data sent to and from the LDAP server.
connectionTraceHelp=If enabled, incoming and outgoing LDAP ASN.1 BER packets will be dumped to the error output stream. Be careful when enabling this option in production as it will expose all data sent to and from the LDAP server.
autoUpdatedIdPsInterval= Auto-updated Identity Providers execution interval
autoUpdatedIdPsIntervalHelp= Every how much time auto-updated Identity Providers will be updated based on metadata url
autoUpdatedIdPsLastRefreshTime= Last execution time of auto-updated Identity Providers task
autoUpdate= Auto Update
autoUpdateHelp= When auto update is true, IdP attributes will be updated based on metadata descriptor URL
38 changes: 25 additions & 13 deletions js/apps/admin-ui/src/identity-providers/add/DescriptorSettings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,11 @@ const Fields = ({ readOnly }: DescriptorSettingsProps) => {
name: "config.validateSignature",
});

const autoUpdated = useWatch({
control,
name: "config.autoUpdate",
});

const useMetadataDescriptorUrl = useWatch({
control,
name: "config.useMetadataDescriptorUrl",
Expand All @@ -51,6 +56,26 @@ const Fields = ({ readOnly }: DescriptorSettingsProps) => {
return (
<div className="pf-v5-c-form pf-m-horizontal">
<FormProvider {...form}>
<DefaultSwitchControl
name="config.autoUpdate"
label={t("autoUpdate")}
labelIcon={t("autoUpdateHelp")}
stringify
/>
<TextControl
name="config.metadataDescriptorUrl"
label={t("metadataDescriptorUrl")}
labelIcon={t("metadataDescriptorUrlHelp")}
type="url"
readOnly={readOnly}
rules={{
required: {
value:
useMetadataDescriptorUrl === "true" || autoUpdated === "true",
message: t("required"),
},
}}
/>
<TextControl
name="config.entityId"
label={t("serviceProviderEntityId")}
Expand Down Expand Up @@ -279,19 +304,6 @@ const Fields = ({ readOnly }: DescriptorSettingsProps) => {
/>
{validateSignature === "true" && (
<>
<TextControl
name="config.metadataDescriptorUrl"
label={t("metadataDescriptorUrl")}
labelIcon={t("metadataDescriptorUrlHelp")}
type="url"
readOnly={readOnly}
rules={{
required: {
value: useMetadataDescriptorUrl === "true",
message: t("required"),
},
}}
/>
<DefaultSwitchControl
name="config.useMetadataDescriptorUrl"
label={t("useMetadataDescriptorUrl")}
Expand Down
23 changes: 23 additions & 0 deletions js/apps/admin-ui/src/identity-providers/add/DiscoverySettings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,32 @@ const Fields = ({ readOnly }: DiscoverySettingsProps) => {
control,
name: "config.pkceEnabled",
});
const autoUpdated = useWatch({
control,
name: "config.autoUpdate",
});

return (
<div className="pf-v5-c-form pf-m-horizontal">
<DefaultSwitchControl
name="config.autoUpdate"
label={t("autoUpdate")}
labelIcon={t("autoUpdateHelp")}
stringify
/>
<TextControl
name="config.metadataDescriptorUrl"
label={t("metadataOfDiscoveryEndpoint")}
labelIcon={t("discoveryEndpointHelp")}
type="url"
readOnly={readOnly}
rules={{
required: {
value: autoUpdated === "true",
message: t("required"),
},
}}
/>
<TextControl
name="config.authorizationUrl"
label={t("authorizationUrl")}
Expand Down
49 changes: 48 additions & 1 deletion js/apps/admin-ui/src/realm-settings/GeneralTab.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import {
StackItem,
} from "@patternfly/react-core";
import { useEffect, useState } from "react";
import { Controller, FormProvider, useForm } from "react-hook-form";
import { Controller, FormProvider, useForm, useWatch } from "react-hook-form";
import { useTranslation } from "react-i18next";
import { useAdminClient } from "../admin-client";
import { DefaultSwitchControl } from "../components/SwitchControl";
Expand All @@ -36,6 +36,7 @@ import {
} from "../util";
import useIsFeatureEnabled, { Feature } from "../utils/useIsFeatureEnabled";
import { UIRealmRepresentation } from "./RealmSettingsTabs";
import { TimeSelector } from "../components/time-selector/TimeSelector";

type RealmSettingsGeneralTabProps = {
realm: UIRealmRepresentation;
Expand Down Expand Up @@ -115,6 +116,16 @@ function RealmSettingsGeneralTabForm({
);
const isOpenid4vciEnabled = isFeatureEnabled(Feature.OpenId4VCI);

const autoUpdatedIdPsInterval = useWatch({
control,
name: "autoUpdatedIdPsInterval",
});

const autoUpdatedIdPsLastRefreshTime = useWatch({
control,
name: "autoUpdatedIdPsLastRefreshTime",
});

const setupForm = () => {
convertToFormValues(realm, setValue);
setValue(
Expand Down Expand Up @@ -256,6 +267,42 @@ function RealmSettingsGeneralTabForm({
value: t(`unmanagedAttributePolicy.${policy}`),
}))}
/>
<FormGroup
label={t("autoUpdatedIdPsInterval")}
fieldId="autoUpdatedIdPsInterval"
labelIcon={
<HelpItem
helpText={t("autoUpdatedIdPsIntervalHelp")}
fieldLabelId="autoUpdatedIdPsInterval"
/>
}
>
<Controller
name="autoUpdatedIdPsInterval"
control={control}
defaultValue={realm.autoUpdatedIdPsInterval}
render={({ field }) => (
<TimeSelector
units={["minute", "hour", "day"]}
value={field.value!}
onChange={field.onChange}
/>
)}
/>
</FormGroup>
{autoUpdatedIdPsInterval && !!autoUpdatedIdPsLastRefreshTime && (
<FormGroup
label={t("autoUpdatedIdPsLastRefreshTime")}
labelIcon={
<HelpItem
helpText={t("autoUpdatedIdPsLastRefreshTime")}
fieldLabelId="autoUpdatedIdPsLastRefreshTime"
/>
}
>
{new Date(autoUpdatedIdPsLastRefreshTime).toLocaleString()}
</FormGroup>
)}
<FormGroup
label={t("endpoints")}
labelIcon={
Expand Down
2 changes: 2 additions & 0 deletions js/libs/keycloak-admin-client/src/defs/realmRepresentation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ export default interface RealmRepresentation {
adminPermissionsEnabled?: boolean;
adminTheme?: string;
attributes?: Record<string, any>;
autoUpdatedIdPsInterval?: number;
autoUpdatedIdPsLastRefreshTime?: number;
// AuthenticationFlowRepresentation
authenticationFlows?: any[];
// AuthenticatorConfigRepresentation
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1865,4 +1865,28 @@ private boolean featureAwareIsEnabled(Profile.Feature feature, boolean isEnabled
if (!Profile.isFeatureEnabled(feature)) return false;
return isEnabled;
}

@Override
public Long getAutoUpdatedIdPsInterval() {
if (isUpdated()) return updated.getAutoUpdatedIdPsInterval();
return cached.getAutoUpdatedIdPsInterval();
}

@Override
public void setAutoUpdatedIdPsInterval(Long autoUpdatedIdPsInterval) {
getDelegateForUpdate();
updated.setAutoUpdatedIdPsInterval(autoUpdatedIdPsInterval);
}

@Override
public Long getAutoUpdatedIdPsLastRefreshTime() {
if (isUpdated()) return updated.getAutoUpdatedIdPsLastRefreshTime();
return cached.getAutoUpdatedIdPsLastRefreshTime();
}

@Override
public void setAutoUpdatedIdPsLastRefreshTime(Long autoUpdatedIdPsLastRefreshTime) {
getDelegateForUpdate();
updated.setAutoUpdatedIdPsLastRefreshTime(autoUpdatedIdPsLastRefreshTime);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,8 @@ public class CachedRealm extends AbstractExtendableRevisioned {
protected boolean organizationsEnabled;
protected boolean adminPermissionsEnabled;
protected boolean verifiableCredentialsEnabled;
protected Long autoUpdatedIdPsInterval;
protected Long autoUpdatedIdPsLastRefreshTime;
//--- brute force settings
protected boolean bruteForceProtected;
protected boolean permanentLockout;
Expand Down Expand Up @@ -192,6 +194,8 @@ public CachedRealm(Long revision, RealmModel model) {
loginWithEmailAllowed = model.isLoginWithEmailAllowed();
duplicateEmailsAllowed = model.isDuplicateEmailsAllowed();
resetPasswordAllowed = model.isResetPasswordAllowed();
autoUpdatedIdPsInterval = model.getAutoUpdatedIdPsInterval();
autoUpdatedIdPsLastRefreshTime = model.getAutoUpdatedIdPsLastRefreshTime();
editUsernameAllowed = model.isEditUsernameAllowed();
organizationsEnabled = model.isOrganizationsEnabled();
adminPermissionsEnabled = model.isAdminPermissionsEnabled();
Expand Down Expand Up @@ -772,4 +776,12 @@ public Map<String, RequiredActionConfigModel> getRequiredActionProviderConfigsBy
public Map<String, RequiredActionConfigModel> getRequiredActionProviderConfigs() {
return requiredActionProviderConfigs;
}

public Long getAutoUpdatedIdPsInterval() {
return autoUpdatedIdPsInterval;
}

public Long getAutoUpdatedIdPsLastRefreshTime() {
return autoUpdatedIdPsLastRefreshTime;
}
}
20 changes: 20 additions & 0 deletions model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java
Original file line number Diff line number Diff line change
Expand Up @@ -1246,6 +1246,26 @@ public void setVerifiableCredentialsEnabled(boolean verifiableCredentialsEnabled
setAttribute(RealmAttributes.VERIFIABLE_CREDENTIALS_ENABLED, verifiableCredentialsEnabled);
}

@Override
public Long getAutoUpdatedIdPsInterval() {
return getAttribute(RealmAttributes.AUTO_UPDATED_IDPS_INTERVAL) == null ? null : Long.valueOf(getAttribute(RealmAttributes.AUTO_UPDATED_IDPS_INTERVAL));
}

@Override
public void setAutoUpdatedIdPsInterval(Long autoUpdatedIdPsInterval) {
setAttribute(RealmAttributes.AUTO_UPDATED_IDPS_INTERVAL, autoUpdatedIdPsInterval);
}

@Override
public Long getAutoUpdatedIdPsLastRefreshTime() {
return getAttribute(RealmAttributes.AUTO_UPDATED_IDPS_REFRESH_TIME) == null ? null : Long.valueOf(getAttribute(RealmAttributes.AUTO_UPDATED_IDPS_REFRESH_TIME));
}

@Override
public void setAutoUpdatedIdPsLastRefreshTime(Long autoUpdatedIdPsLastRefreshTime) {
setAttribute(RealmAttributes.AUTO_UPDATED_IDPS_REFRESH_TIME, autoUpdatedIdPsLastRefreshTime);
}

@Override
public ClientModel getMasterAdminClient() {
String masterAdminClientId = realm.getMasterAdminClient();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,4 +61,6 @@ public interface RealmAttributes {
String ORGANIZATIONS_ENABLED = "organizationsEnabled";
String ADMIN_PERMISSIONS_ENABLED = "adminPermissionsEnabled";
String ADMIN_PERMISSIONS_CLIENT_ID = "adminPermissionsClientId";
String AUTO_UPDATED_IDPS_INTERVAL = "autoUpdatedIdPsInterval";
String AUTO_UPDATED_IDPS_REFRESH_TIME = "autoUpdatedIdPsLastRefreshTime";
}
Loading

0 comments on commit f4bc8e4

Please sign in to comment.