From c48d85dda370ce259a584e4680da2c4f20188a67 Mon Sep 17 00:00:00 2001 From: Daniel Pragt Date: Mon, 13 Apr 2026 14:38:05 +0200 Subject: [PATCH 1/4] #10 Handling redirect when DigiD login is cancelled MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit #PR Summary This PR adds configurable error redirect handling to the SAML authentication flow. New configuration options (disabled by default): customErrorRedirectEnabled — enables custom error redirects errorCallbackPath — redirect path for error/denied cases cancelledCallbackPath — redirect path for cancelled/failed cases When enabled, a wrapper (ErrorRedirectCallbackWrapper) intercepts error() and cancelled() callbacks and issues a 302 redirect to the configured paths. All other behavior remains unchanged and falls back to the default implementation if needed. Basic open redirect protections are included (restricted schemes, validated paths, no user-controlled domains). Feel free to adjust this PR to better align with your company’s standards and conventions. --- .../saml/ErrorRedirectCallbackWrapper.java | 149 ++++++++++++++++++ .../broker/saml/SAMLIdentityProvider.java | 6 +- .../saml/SAMLIdentityProviderConfig.java | 30 +++- .../core/saml/v2/writers/BaseWriter.java | 6 +- 4 files changed, 186 insertions(+), 5 deletions(-) create mode 100644 src/main/java/nl/first8/keycloak/broker/saml/ErrorRedirectCallbackWrapper.java diff --git a/src/main/java/nl/first8/keycloak/broker/saml/ErrorRedirectCallbackWrapper.java b/src/main/java/nl/first8/keycloak/broker/saml/ErrorRedirectCallbackWrapper.java new file mode 100644 index 0000000..6429e9a --- /dev/null +++ b/src/main/java/nl/first8/keycloak/broker/saml/ErrorRedirectCallbackWrapper.java @@ -0,0 +1,149 @@ +package nl.first8.keycloak.broker.saml; + +import jakarta.ws.rs.core.Response; +import org.jboss.logging.Logger; +import org.keycloak.broker.provider.BrokeredIdentityContext; +import org.keycloak.broker.provider.IdentityProvider; +import org.keycloak.broker.provider.UserAuthenticationIdentityProvider; +import org.keycloak.models.IdentityProviderModel; +import org.keycloak.models.KeycloakSession; +import org.keycloak.sessions.AuthenticationSessionModel; + +import java.net.URI; +import java.net.URISyntaxException; + +/** + * Wraps an {@link UserAuthenticationIdentityProvider.AuthenticationCallback} to intercept error responses + * and redirect the user to a configurable client-side callback path instead of showing the default Keycloak error page. + */ +public class ErrorRedirectCallbackWrapper implements UserAuthenticationIdentityProvider.AuthenticationCallback { + + private static final Logger logger = Logger.getLogger(ErrorRedirectCallbackWrapper.class); + + private final KeycloakSession session; + private final SAMLIdentityProviderConfig config; + private final UserAuthenticationIdentityProvider.AuthenticationCallback delegate; + + public ErrorRedirectCallbackWrapper(KeycloakSession session, + SAMLIdentityProviderConfig config, + UserAuthenticationIdentityProvider.AuthenticationCallback delegate) { + this.session = session; + this.config = config; + this.delegate = delegate; + } + + @Override + public Response authenticated(BrokeredIdentityContext context) { + return delegate.authenticated(context); + } + + @Override + public Response cancelled(IdentityProviderModel idpConfig) { + Response redirect = buildRedirectResponse(config.getCancelledCallbackPath()); + if (redirect != null) { + return redirect; + } + return delegate.cancelled(idpConfig); + } + + @Override + public Response error(IdentityProviderModel idpConfig, String message) { + logger.warnf("Error callback intercepted with message: %s", message); + + String callbackPath = determineCallbackPath(message); + Response redirect = buildRedirectResponse(callbackPath); + if (redirect != null) { + return redirect; + } + + logger.warn("Could not determine callback URL, falling back to default error handling"); + return delegate.error(idpConfig, message); + } + + @Override + public AuthenticationSessionModel getAndVerifyAuthenticationSession(String encodedCode) { + return delegate.getAndVerifyAuthenticationSession(encodedCode); + } + + @Override + public Response retryLogin(UserAuthenticationIdentityProvider identityProvider, AuthenticationSessionModel authSession) { + return delegate.retryLogin(identityProvider, authSession); + } + + String determineCallbackPath(String message) { + if (message != null) { + if (message.contains("RequestDenied") || message.contains("denied") || message.contains("Denied")) { + return config.getErrorCallbackPath(); + } + } + // Default: treat as cancelled (AuthnFailed, cancelled, or unknown) + return config.getCancelledCallbackPath(); + } + + private Response buildRedirectResponse(String callbackPath) { + AuthenticationSessionModel authSession = session.getContext().getAuthenticationSession(); + if (authSession == null) { + logger.warn("No authentication session available for error redirect"); + return null; + } + + String callbackUrl = buildCallbackUrl(authSession.getRedirectUri(), callbackPath); + if (callbackUrl == null) { + callbackUrl = buildCallbackUrl(authSession.getClient().getBaseUrl(), callbackPath); + } + + if (callbackUrl != null) { + logger.infof("Redirecting to: %s", callbackUrl); + return Response.status(Response.Status.FOUND).location(URI.create(callbackUrl)).build(); + } + + return null; + } + + /** + * Builds a callback URL by extracting scheme, host, and port from the source URL + * and appending the configured callback path. + * Returns null if the source URL is invalid or blank. + */ + static String buildCallbackUrl(String sourceUrl, String callbackPath) { + if (sourceUrl == null || sourceUrl.isBlank()) { + return null; + } + + try { + URI uri = new URI(sourceUrl); + String scheme = uri.getScheme(); + String host = uri.getHost(); + + if (scheme == null || host == null) { + return null; + } + + // Only allow https (or http for localhost development) + if (!"https".equalsIgnoreCase(scheme) && !"http".equalsIgnoreCase(scheme)) { + logger.warnf("Rejected redirect with unsupported scheme: %s", scheme); + return null; + } + + // Sanitize callbackPath: must start with / and not contain query/fragment or path traversal + if (callbackPath == null || !callbackPath.startsWith("/") + || callbackPath.contains("..") || callbackPath.contains("?") + || callbackPath.contains("#") || callbackPath.contains("//")) { + logger.warnf("Rejected invalid callback path: %s", callbackPath); + return null; + } + + StringBuilder sb = new StringBuilder(); + sb.append(scheme).append("://").append(host); + if (uri.getPort() > 0 && uri.getPort() != 443 && uri.getPort() != 80) { + sb.append(":").append(uri.getPort()); + } + sb.append(callbackPath); + + return sb.toString(); + } catch (URISyntaxException e) { + logger.warnf("Failed to parse source URL for error redirect: %s", e.getMessage()); + return null; + } + } +} diff --git a/src/main/java/nl/first8/keycloak/broker/saml/SAMLIdentityProvider.java b/src/main/java/nl/first8/keycloak/broker/saml/SAMLIdentityProvider.java index 7d40aed..be6ac09 100644 --- a/src/main/java/nl/first8/keycloak/broker/saml/SAMLIdentityProvider.java +++ b/src/main/java/nl/first8/keycloak/broker/saml/SAMLIdentityProvider.java @@ -94,7 +94,11 @@ public SAMLIdentityProvider(KeycloakSession session, SAMLIdentityProviderConfig @Override public Object callback(RealmModel realm, AuthenticationCallback callback, EventBuilder event) { - return new SAMLEndpoint(session, this, getConfig(), callback, destinationValidator); + AuthenticationCallback effectiveCallback = callback; + if (getConfig().isCustomErrorRedirectEnabled()) { + effectiveCallback = new ErrorRedirectCallbackWrapper(session, getConfig(), callback); + } + return new SAMLEndpoint(session, this, getConfig(), effectiveCallback, destinationValidator); } @Override diff --git a/src/main/java/nl/first8/keycloak/broker/saml/SAMLIdentityProviderConfig.java b/src/main/java/nl/first8/keycloak/broker/saml/SAMLIdentityProviderConfig.java index a6b4ab5..7724f62 100644 --- a/src/main/java/nl/first8/keycloak/broker/saml/SAMLIdentityProviderConfig.java +++ b/src/main/java/nl/first8/keycloak/broker/saml/SAMLIdentityProviderConfig.java @@ -70,7 +70,9 @@ public class SAMLIdentityProviderConfig extends org.keycloak.broker.saml.SAMLIde public static final String ATTRIBUTE_VALUE = "attributeValue"; public static final String DESCRIPTOR_CACHE_SECONDS = "descriptorCacheSeconds"; - + public static final String CUSTOM_ERROR_REDIRECT_ENABLED = "customErrorRedirectEnabled"; + public static final String ERROR_CALLBACK_PATH = "errorCallbackPath"; + public static final String CANCELLED_CALLBACK_PATH = "cancelledCallbackPath"; public SAMLIdentityProviderConfig() { super(); @@ -277,6 +279,32 @@ public boolean isUseMetadataDescriptorUrl() { return Boolean.parseBoolean(getConfig().get(USE_METADATA_DESCRIPTOR_URL)); } + public boolean isCustomErrorRedirectEnabled() { + return Boolean.parseBoolean(getConfig().get(CUSTOM_ERROR_REDIRECT_ENABLED)); + } + + public void setCustomErrorRedirectEnabled(boolean enabled) { + getConfig().put(CUSTOM_ERROR_REDIRECT_ENABLED, String.valueOf(enabled)); + } + + public String getErrorCallbackPath() { + String path = getConfig().get(ERROR_CALLBACK_PATH); + return path != null && !path.isEmpty() ? path : "/signin-callback/error"; + } + + public void setErrorCallbackPath(String path) { + getConfig().put(ERROR_CALLBACK_PATH, path); + } + + public String getCancelledCallbackPath() { + String path = getConfig().get(CANCELLED_CALLBACK_PATH); + return path != null && !path.isEmpty() ? path : "/signin-callback/cancelled"; + } + + public void setCancelledCallbackPath(String path) { + getConfig().put(CANCELLED_CALLBACK_PATH, path); + } + @Override public void validate(RealmModel realm) { SslRequired sslRequired = realm.getSslRequired(); diff --git a/src/main/java/nl/first8/keycloak/saml/processing/core/saml/v2/writers/BaseWriter.java b/src/main/java/nl/first8/keycloak/saml/processing/core/saml/v2/writers/BaseWriter.java index ecc9d45..7ca027a 100644 --- a/src/main/java/nl/first8/keycloak/saml/processing/core/saml/v2/writers/BaseWriter.java +++ b/src/main/java/nl/first8/keycloak/saml/processing/core/saml/v2/writers/BaseWriter.java @@ -156,9 +156,9 @@ public void writeAttributeTypeWithoutRootTag(AttributeType attributeType) throws writeDateAttributeValue((XMLGregorianCalendar) attributeValue); } else if (attributeValue instanceof Element) { writeElementAttributeValue((Element) attributeValue); - } else if (attributeValue instanceof SamlEncryptedId) { - logger.debug("EncryptedID is not implemented yet."); - } else + } + // Removed check for SamlEncryptedId as it cannot be resolved + else throw logger.writerUnsupportedAttributeValueError(attributeValue.getClass().getName()); } else { writeStringAttributeValue(null); From 65f85d9e85114a32e7606459100a5ed04333f60d Mon Sep 17 00:00:00 2001 From: Daniel Pragt Date: Mon, 13 Apr 2026 14:55:09 +0200 Subject: [PATCH 2/4] fix SamlEncryptedId --- .../saml/processing/core/saml/v2/writers/BaseWriter.java | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/main/java/nl/first8/keycloak/saml/processing/core/saml/v2/writers/BaseWriter.java b/src/main/java/nl/first8/keycloak/saml/processing/core/saml/v2/writers/BaseWriter.java index 7ca027a..a018b66 100644 --- a/src/main/java/nl/first8/keycloak/saml/processing/core/saml/v2/writers/BaseWriter.java +++ b/src/main/java/nl/first8/keycloak/saml/processing/core/saml/v2/writers/BaseWriter.java @@ -9,6 +9,7 @@ import javax.xml.stream.XMLStreamWriter; import nl.first8.keycloak.saml.common.constants.JBossSAMLConstants; +import nl.first8.keycloak.dom.saml.v2.assertion.SamlEncryptedId; import org.keycloak.dom.saml.v2.assertion.*; import org.keycloak.dom.saml.v2.metadata.LocalizedNameType; @@ -25,6 +26,7 @@ import org.keycloak.saml.common.util.StringUtil; import org.keycloak.saml.processing.core.saml.v2.util.StaxWriterUtil; + import org.w3c.dom.Element; import org.w3c.dom.Node; @@ -156,9 +158,9 @@ public void writeAttributeTypeWithoutRootTag(AttributeType attributeType) throws writeDateAttributeValue((XMLGregorianCalendar) attributeValue); } else if (attributeValue instanceof Element) { writeElementAttributeValue((Element) attributeValue); - } - // Removed check for SamlEncryptedId as it cannot be resolved - else + } else if (attributeValue instanceof SamlEncryptedId) { + logger.debug("EncryptedID is not implemented yet."); + } else throw logger.writerUnsupportedAttributeValueError(attributeValue.getClass().getName()); } else { writeStringAttributeValue(null); From b5d6820eff4b875f81b31231c5a9cc5e5589c6e6 Mon Sep 17 00:00:00 2001 From: Dpragt Date: Wed, 1 Jul 2026 15:50:01 +0200 Subject: [PATCH 3/4] Allow absolute error redirect callback URLs --- .../saml/ErrorRedirectCallbackWrapper.java | 75 +++++++++++++++---- .../saml/SAMLIdentityProviderConfig.java | 24 ++++++ 2 files changed, 85 insertions(+), 14 deletions(-) diff --git a/src/main/java/nl/first8/keycloak/broker/saml/ErrorRedirectCallbackWrapper.java b/src/main/java/nl/first8/keycloak/broker/saml/ErrorRedirectCallbackWrapper.java index 6429e9a..43e6571 100644 --- a/src/main/java/nl/first8/keycloak/broker/saml/ErrorRedirectCallbackWrapper.java +++ b/src/main/java/nl/first8/keycloak/broker/saml/ErrorRedirectCallbackWrapper.java @@ -39,7 +39,7 @@ public Response authenticated(BrokeredIdentityContext context) { @Override public Response cancelled(IdentityProviderModel idpConfig) { - Response redirect = buildRedirectResponse(config.getCancelledCallbackPath()); + Response redirect = buildRedirectResponse(config.getCancelledCallbackUrl(), config.getCancelledCallbackPath()); if (redirect != null) { return redirect; } @@ -50,8 +50,7 @@ public Response cancelled(IdentityProviderModel idpConfig) { public Response error(IdentityProviderModel idpConfig, String message) { logger.warnf("Error callback intercepted with message: %s", message); - String callbackPath = determineCallbackPath(message); - Response redirect = buildRedirectResponse(callbackPath); + Response redirect = buildRedirectResponse(determineCallbackUrl(message), determineCallbackPath(message)); if (redirect != null) { return redirect; } @@ -80,26 +79,74 @@ String determineCallbackPath(String message) { return config.getCancelledCallbackPath(); } - private Response buildRedirectResponse(String callbackPath) { - AuthenticationSessionModel authSession = session.getContext().getAuthenticationSession(); - if (authSession == null) { - logger.warn("No authentication session available for error redirect"); - return null; + String determineCallbackUrl(String message) { + if (message != null) { + if (message.contains("RequestDenied") || message.contains("denied") || message.contains("Denied")) { + return config.getErrorCallbackUrl(); + } } + // Default: treat as cancelled (AuthnFailed, cancelled, or unknown) + return config.getCancelledCallbackUrl(); + } + + private Response buildRedirectResponse(String callbackUrl, String callbackPath) { + String resolvedCallbackUrl = normalizeCallbackUrl(callbackUrl); + if (resolvedCallbackUrl == null) { + AuthenticationSessionModel authSession = session.getContext().getAuthenticationSession(); + if (authSession == null) { + logger.warn("No authentication session available for error redirect"); + return null; + } - String callbackUrl = buildCallbackUrl(authSession.getRedirectUri(), callbackPath); - if (callbackUrl == null) { - callbackUrl = buildCallbackUrl(authSession.getClient().getBaseUrl(), callbackPath); + resolvedCallbackUrl = buildCallbackUrl(authSession.getRedirectUri(), callbackPath); + if (resolvedCallbackUrl == null) { + resolvedCallbackUrl = buildCallbackUrl(authSession.getClient().getBaseUrl(), callbackPath); + } } - if (callbackUrl != null) { - logger.infof("Redirecting to: %s", callbackUrl); - return Response.status(Response.Status.FOUND).location(URI.create(callbackUrl)).build(); + if (resolvedCallbackUrl != null) { + logger.infof("Redirecting to: %s", resolvedCallbackUrl); + return Response.status(Response.Status.FOUND).location(URI.create(resolvedCallbackUrl)).build(); } return null; } + /** + * Validates and normalizes a configured absolute callback URL. + * Returns null if the callback URL is invalid or blank. + */ + static String normalizeCallbackUrl(String callbackUrl) { + if (callbackUrl == null || callbackUrl.isBlank()) { + return null; + } + + try { + URI uri = new URI(callbackUrl); + String scheme = uri.getScheme(); + String host = uri.getHost(); + + if (scheme == null || host == null) { + return null; + } + + if (!"https".equalsIgnoreCase(scheme) && !"http".equalsIgnoreCase(scheme)) { + logger.warnf("Rejected redirect with unsupported scheme: %s", scheme); + return null; + } + + if (uri.getUserInfo() != null || uri.getFragment() != null) { + logger.warnf("Rejected invalid callback URL: %s", callbackUrl); + return null; + } + + return uri.toASCIIString(); + } catch (URISyntaxException e) { + logger.warnf("Failed to parse callback URL for error redirect: %s", e.getMessage()); + return null; + } + } + /** * Builds a callback URL by extracting scheme, host, and port from the source URL * and appending the configured callback path. diff --git a/src/main/java/nl/first8/keycloak/broker/saml/SAMLIdentityProviderConfig.java b/src/main/java/nl/first8/keycloak/broker/saml/SAMLIdentityProviderConfig.java index 7724f62..c770700 100644 --- a/src/main/java/nl/first8/keycloak/broker/saml/SAMLIdentityProviderConfig.java +++ b/src/main/java/nl/first8/keycloak/broker/saml/SAMLIdentityProviderConfig.java @@ -71,6 +71,8 @@ public class SAMLIdentityProviderConfig extends org.keycloak.broker.saml.SAMLIde public static final String DESCRIPTOR_CACHE_SECONDS = "descriptorCacheSeconds"; public static final String CUSTOM_ERROR_REDIRECT_ENABLED = "customErrorRedirectEnabled"; + public static final String ERROR_CALLBACK_URL = "errorCallbackUrl"; + public static final String CANCELLED_CALLBACK_URL = "cancelledCallbackUrl"; public static final String ERROR_CALLBACK_PATH = "errorCallbackPath"; public static final String CANCELLED_CALLBACK_PATH = "cancelledCallbackPath"; @@ -287,6 +289,22 @@ public void setCustomErrorRedirectEnabled(boolean enabled) { getConfig().put(CUSTOM_ERROR_REDIRECT_ENABLED, String.valueOf(enabled)); } + public String getErrorCallbackUrl() { + return getConfig().get(ERROR_CALLBACK_URL); + } + + public void setErrorCallbackUrl(String url) { + getConfig().put(ERROR_CALLBACK_URL, url); + } + + public String getCancelledCallbackUrl() { + return getConfig().get(CANCELLED_CALLBACK_URL); + } + + public void setCancelledCallbackUrl(String url) { + getConfig().put(CANCELLED_CALLBACK_URL, url); + } + public String getErrorCallbackPath() { String path = getConfig().get(ERROR_CALLBACK_PATH); return path != null && !path.isEmpty() ? path : "/signin-callback/error"; @@ -322,6 +340,12 @@ public void validate(RealmModel realm) { if (StringUtil.isNotBlank(getArtifactResolutionServiceUrl())) { checkUrl(sslRequired, getArtifactResolutionServiceUrl(), ARTIFACT_RESOLUTION_SERVICE_URL); } + if (StringUtil.isNotBlank(getErrorCallbackUrl())) { + checkUrl(sslRequired, getErrorCallbackUrl(), ERROR_CALLBACK_URL); + } + if (StringUtil.isNotBlank(getCancelledCallbackUrl())) { + checkUrl(sslRequired, getCancelledCallbackUrl(), CANCELLED_CALLBACK_URL); + } //transient name id format is not accepted together with principaltype SubjectnameId if (JBossSAMLURIConstants.NAMEID_FORMAT_TRANSIENT.get().equals(getNameIDPolicyFormat()) && SamlPrincipalType.SUBJECT == getPrincipalType()) throw new IllegalArgumentException("Can not have Transient NameID Policy Format together with SUBJECT Principal Type"); From 7b5dc6b9c9d1b3ba64c1ae6e3f629ac5e8b89322 Mon Sep 17 00:00:00 2001 From: Dpragt Date: Wed, 22 Jul 2026 15:57:25 +0200 Subject: [PATCH 4/4] Refactor error and cancelled callback handling in SAMLIdentityProviderConfig --- .../saml/ErrorRedirectCallbackWrapper.java | 28 ++++------- .../saml/SAMLIdentityProviderConfig.java | 48 +++++-------------- 2 files changed, 21 insertions(+), 55 deletions(-) diff --git a/src/main/java/nl/first8/keycloak/broker/saml/ErrorRedirectCallbackWrapper.java b/src/main/java/nl/first8/keycloak/broker/saml/ErrorRedirectCallbackWrapper.java index 43e6571..4b64fb4 100644 --- a/src/main/java/nl/first8/keycloak/broker/saml/ErrorRedirectCallbackWrapper.java +++ b/src/main/java/nl/first8/keycloak/broker/saml/ErrorRedirectCallbackWrapper.java @@ -39,7 +39,7 @@ public Response authenticated(BrokeredIdentityContext context) { @Override public Response cancelled(IdentityProviderModel idpConfig) { - Response redirect = buildRedirectResponse(config.getCancelledCallbackUrl(), config.getCancelledCallbackPath()); + Response redirect = buildRedirectResponse(config.getCancelledCallback()); if (redirect != null) { return redirect; } @@ -50,7 +50,7 @@ public Response cancelled(IdentityProviderModel idpConfig) { public Response error(IdentityProviderModel idpConfig, String message) { logger.warnf("Error callback intercepted with message: %s", message); - Response redirect = buildRedirectResponse(determineCallbackUrl(message), determineCallbackPath(message)); + Response redirect = buildRedirectResponse(determineCallback(message)); if (redirect != null) { return redirect; } @@ -69,28 +69,18 @@ public Response retryLogin(UserAuthenticationIdentityProvider identityProvide return delegate.retryLogin(identityProvider, authSession); } - String determineCallbackPath(String message) { + String determineCallback(String message) { if (message != null) { if (message.contains("RequestDenied") || message.contains("denied") || message.contains("Denied")) { - return config.getErrorCallbackPath(); + return config.getErrorCallback(); } } // Default: treat as cancelled (AuthnFailed, cancelled, or unknown) - return config.getCancelledCallbackPath(); + return config.getCancelledCallback(); } - String determineCallbackUrl(String message) { - if (message != null) { - if (message.contains("RequestDenied") || message.contains("denied") || message.contains("Denied")) { - return config.getErrorCallbackUrl(); - } - } - // Default: treat as cancelled (AuthnFailed, cancelled, or unknown) - return config.getCancelledCallbackUrl(); - } - - private Response buildRedirectResponse(String callbackUrl, String callbackPath) { - String resolvedCallbackUrl = normalizeCallbackUrl(callbackUrl); + private Response buildRedirectResponse(String callback) { + String resolvedCallbackUrl = normalizeCallbackUrl(callback); if (resolvedCallbackUrl == null) { AuthenticationSessionModel authSession = session.getContext().getAuthenticationSession(); if (authSession == null) { @@ -98,9 +88,9 @@ private Response buildRedirectResponse(String callbackUrl, String callbackPath) return null; } - resolvedCallbackUrl = buildCallbackUrl(authSession.getRedirectUri(), callbackPath); + resolvedCallbackUrl = buildCallbackUrl(authSession.getRedirectUri(), callback); if (resolvedCallbackUrl == null) { - resolvedCallbackUrl = buildCallbackUrl(authSession.getClient().getBaseUrl(), callbackPath); + resolvedCallbackUrl = buildCallbackUrl(authSession.getClient().getBaseUrl(), callback); } } diff --git a/src/main/java/nl/first8/keycloak/broker/saml/SAMLIdentityProviderConfig.java b/src/main/java/nl/first8/keycloak/broker/saml/SAMLIdentityProviderConfig.java index c770700..1f60320 100644 --- a/src/main/java/nl/first8/keycloak/broker/saml/SAMLIdentityProviderConfig.java +++ b/src/main/java/nl/first8/keycloak/broker/saml/SAMLIdentityProviderConfig.java @@ -71,10 +71,8 @@ public class SAMLIdentityProviderConfig extends org.keycloak.broker.saml.SAMLIde public static final String DESCRIPTOR_CACHE_SECONDS = "descriptorCacheSeconds"; public static final String CUSTOM_ERROR_REDIRECT_ENABLED = "customErrorRedirectEnabled"; - public static final String ERROR_CALLBACK_URL = "errorCallbackUrl"; - public static final String CANCELLED_CALLBACK_URL = "cancelledCallbackUrl"; - public static final String ERROR_CALLBACK_PATH = "errorCallbackPath"; - public static final String CANCELLED_CALLBACK_PATH = "cancelledCallbackPath"; + public static final String ERROR_CALLBACK = "errorCallback"; + public static final String CANCELLED_CALLBACK = "cancelledCallback"; public SAMLIdentityProviderConfig() { super(); @@ -289,38 +287,22 @@ public void setCustomErrorRedirectEnabled(boolean enabled) { getConfig().put(CUSTOM_ERROR_REDIRECT_ENABLED, String.valueOf(enabled)); } - public String getErrorCallbackUrl() { - return getConfig().get(ERROR_CALLBACK_URL); + public String getErrorCallback() { + String callback = getConfig().get(ERROR_CALLBACK); + return StringUtil.isNotBlank(callback) ? callback : "/signin-callback/error"; } - public void setErrorCallbackUrl(String url) { - getConfig().put(ERROR_CALLBACK_URL, url); + public void setErrorCallback(String callback) { + getConfig().put(ERROR_CALLBACK, callback); } - public String getCancelledCallbackUrl() { - return getConfig().get(CANCELLED_CALLBACK_URL); + public String getCancelledCallback() { + String callback = getConfig().get(CANCELLED_CALLBACK); + return StringUtil.isNotBlank(callback) ? callback : "/signin-callback/cancelled"; } - public void setCancelledCallbackUrl(String url) { - getConfig().put(CANCELLED_CALLBACK_URL, url); - } - - public String getErrorCallbackPath() { - String path = getConfig().get(ERROR_CALLBACK_PATH); - return path != null && !path.isEmpty() ? path : "/signin-callback/error"; - } - - public void setErrorCallbackPath(String path) { - getConfig().put(ERROR_CALLBACK_PATH, path); - } - - public String getCancelledCallbackPath() { - String path = getConfig().get(CANCELLED_CALLBACK_PATH); - return path != null && !path.isEmpty() ? path : "/signin-callback/cancelled"; - } - - public void setCancelledCallbackPath(String path) { - getConfig().put(CANCELLED_CALLBACK_PATH, path); + public void setCancelledCallback(String callback) { + getConfig().put(CANCELLED_CALLBACK, callback); } @Override @@ -340,12 +322,6 @@ public void validate(RealmModel realm) { if (StringUtil.isNotBlank(getArtifactResolutionServiceUrl())) { checkUrl(sslRequired, getArtifactResolutionServiceUrl(), ARTIFACT_RESOLUTION_SERVICE_URL); } - if (StringUtil.isNotBlank(getErrorCallbackUrl())) { - checkUrl(sslRequired, getErrorCallbackUrl(), ERROR_CALLBACK_URL); - } - if (StringUtil.isNotBlank(getCancelledCallbackUrl())) { - checkUrl(sslRequired, getCancelledCallbackUrl(), CANCELLED_CALLBACK_URL); - } //transient name id format is not accepted together with principaltype SubjectnameId if (JBossSAMLURIConstants.NAMEID_FORMAT_TRANSIENT.get().equals(getNameIDPolicyFormat()) && SamlPrincipalType.SUBJECT == getPrincipalType()) throw new IllegalArgumentException("Can not have Transient NameID Policy Format together with SUBJECT Principal Type");