From e07fd9ffa339ae24ffa97a56f56d86919e5e26d1 Mon Sep 17 00:00:00 2001 From: Gideon Caranzo Date: Thu, 16 May 2019 00:58:15 -0500 Subject: [PATCH] KEYCLOAK-9936 Added optional hooks for preprocessing SAML authentication Co-Authored-By: Hynek Mlnarik --- .../saml/SAML2AuthnRequestBuilder.java | 38 ++++---- .../saml/SAML2LogoutRequestBuilder.java | 2 +- .../keycloak/broker/saml/SAMLEndpoint.java | 12 +++ .../broker/saml/SAMLIdentityProvider.java | 34 +++++-- .../keycloak/protocol/saml/SamlProtocol.java | 39 ++++++-- .../keycloak/protocol/saml/SamlService.java | 17 ++++ .../protocol/saml/SamlSessionUtils.java | 10 +++ .../SamlAuthenticationPreprocessor.java | 88 +++++++++++++++++++ .../SamlAuthenticationPreprocessorSpi.java | 51 +++++++++++ .../resources/IdentityBrokerService.java | 13 +++ .../services/org.keycloak.provider.Spi | 3 +- 11 files changed, 272 insertions(+), 35 deletions(-) create mode 100644 services/src/main/java/org/keycloak/protocol/saml/preprocessor/SamlAuthenticationPreprocessor.java create mode 100644 services/src/main/java/org/keycloak/protocol/saml/preprocessor/SamlAuthenticationPreprocessorSpi.java diff --git a/saml-core/src/main/java/org/keycloak/saml/SAML2AuthnRequestBuilder.java b/saml-core/src/main/java/org/keycloak/saml/SAML2AuthnRequestBuilder.java index 7eebdadcf5..a4867ddd58 100755 --- a/saml-core/src/main/java/org/keycloak/saml/SAML2AuthnRequestBuilder.java +++ b/saml-core/src/main/java/org/keycloak/saml/SAML2AuthnRequestBuilder.java @@ -90,27 +90,31 @@ public class SAML2AuthnRequestBuilder implements SamlProtocolExtensionsAwareBuil public Document toDocument() { try { - AuthnRequestType authnRequestType = this.authnRequestType; - - NameIDType nameIDType = new NameIDType(); - - nameIDType.setValue(this.issuer); - - authnRequestType.setIssuer(nameIDType); - - authnRequestType.setDestination(URI.create(this.destination)); - - if (! this.extensions.isEmpty()) { - ExtensionsType extensionsType = new ExtensionsType(); - for (NodeGenerator extension : this.extensions) { - extensionsType.addExtension(extension); - } - authnRequestType.setExtensions(extensionsType); - } + AuthnRequestType authnRequestType = createAuthnRequest(); return new SAML2Request().convert(authnRequestType); } catch (Exception e) { throw new RuntimeException("Could not convert " + authnRequestType + " to a document.", e); } } + + public AuthnRequestType createAuthnRequest() { + AuthnRequestType res = this.authnRequestType; + NameIDType nameIDType = new NameIDType(); + nameIDType.setValue(this.issuer); + + res.setIssuer(nameIDType); + + res.setDestination(URI.create(this.destination)); + + if (! this.extensions.isEmpty()) { + ExtensionsType extensionsType = new ExtensionsType(); + for (NodeGenerator extension : this.extensions) { + extensionsType.addExtension(extension); + } + res.setExtensions(extensionsType); + } + + return res; + } } \ No newline at end of file diff --git a/saml-core/src/main/java/org/keycloak/saml/SAML2LogoutRequestBuilder.java b/saml-core/src/main/java/org/keycloak/saml/SAML2LogoutRequestBuilder.java index 5b0d7b3828..27063242c9 100755 --- a/saml-core/src/main/java/org/keycloak/saml/SAML2LogoutRequestBuilder.java +++ b/saml-core/src/main/java/org/keycloak/saml/SAML2LogoutRequestBuilder.java @@ -104,7 +104,7 @@ public class SAML2LogoutRequestBuilder implements SamlProtocolExtensionsAwareBui return document; } - private LogoutRequestType createLogoutRequest() throws ConfigurationException { + public LogoutRequestType createLogoutRequest() throws ConfigurationException { LogoutRequestType lort = SAML2Request.createLogoutRequest(issuer); lort.setNameID(nameId); diff --git a/services/src/main/java/org/keycloak/broker/saml/SAMLEndpoint.java b/services/src/main/java/org/keycloak/broker/saml/SAMLEndpoint.java index 2ad1a01d12..df4986cb98 100755 --- a/services/src/main/java/org/keycloak/broker/saml/SAMLEndpoint.java +++ b/services/src/main/java/org/keycloak/broker/saml/SAMLEndpoint.java @@ -46,6 +46,8 @@ import org.keycloak.models.UserSessionModel; import org.keycloak.protocol.saml.JaxrsSAML2BindingBuilder; import org.keycloak.protocol.saml.SamlProtocol; import org.keycloak.protocol.saml.SamlProtocolUtils; +import org.keycloak.protocol.saml.SamlSessionUtils; +import org.keycloak.protocol.saml.preprocessor.SamlAuthenticationPreprocessor; import org.keycloak.saml.SAML2LogoutResponseBuilder; import org.keycloak.saml.SAMLRequestParser; import org.keycloak.saml.common.constants.GeneralConstants; @@ -291,6 +293,11 @@ public class SAMLEndpoint { if (userSession.getState() == UserSessionModel.State.LOGGING_OUT || userSession.getState() == UserSessionModel.State.LOGGED_OUT) { continue; } + + for(Iterator it = SamlSessionUtils.getSamlAuthenticationPreprocessorIterator(session); it.hasNext();) { + request = it.next().beforeProcessingLogoutRequest(request, userSession, null); + } + try { AuthenticationManager.backchannelLogout(session, realm, userSession, session.getContext().getUri(), clientConnection, headers, false); } catch (Exception e) { @@ -306,6 +313,11 @@ public class SAMLEndpoint { if (userSession.getState() == UserSessionModel.State.LOGGING_OUT || userSession.getState() == UserSessionModel.State.LOGGED_OUT) { continue; } + + for(Iterator it = SamlSessionUtils.getSamlAuthenticationPreprocessorIterator(session); it.hasNext();) { + request = it.next().beforeProcessingLogoutRequest(request, userSession, null); + } + try { AuthenticationManager.backchannelLogout(session, realm, userSession, session.getContext().getUri(), clientConnection, headers, false); } catch (Exception e) { diff --git a/services/src/main/java/org/keycloak/broker/saml/SAMLIdentityProvider.java b/services/src/main/java/org/keycloak/broker/saml/SAMLIdentityProvider.java index a86bfd3ace..5fb9a721b0 100755 --- a/services/src/main/java/org/keycloak/broker/saml/SAMLIdentityProvider.java +++ b/services/src/main/java/org/keycloak/broker/saml/SAMLIdentityProvider.java @@ -26,14 +26,21 @@ import org.keycloak.dom.saml.v2.assertion.AuthnStatementType; import org.keycloak.dom.saml.v2.assertion.NameIDType; import org.keycloak.dom.saml.v2.assertion.SubjectType; import org.keycloak.dom.saml.v2.metadata.KeyTypes; +import org.keycloak.dom.saml.v2.protocol.AuthnRequestType; +import org.keycloak.dom.saml.v2.protocol.LogoutRequestType; import org.keycloak.dom.saml.v2.protocol.ResponseType; import org.keycloak.events.EventBuilder; import org.keycloak.keys.RsaKeyMetadata; import org.keycloak.models.*; import org.keycloak.protocol.saml.JaxrsSAML2BindingBuilder; +import org.keycloak.protocol.saml.SamlSessionUtils; +import org.keycloak.protocol.saml.preprocessor.SamlAuthenticationPreprocessor; import org.keycloak.saml.*; +import org.keycloak.saml.SamlProtocolExtensionsAwareBuilder.NodeGenerator; import org.keycloak.saml.common.constants.GeneralConstants; import org.keycloak.saml.common.constants.JBossSAMLURIConstants; +import org.keycloak.saml.common.exceptions.ConfigurationException; +import org.keycloak.saml.processing.api.saml.v2.request.SAML2Request; import org.keycloak.saml.processing.core.util.KeycloakKeySamlExtensionGenerator; import org.keycloak.saml.validators.DestinationValidator; import org.keycloak.sessions.AuthenticationSessionModel; @@ -43,6 +50,7 @@ import javax.ws.rs.core.Response; import javax.ws.rs.core.UriBuilder; import javax.ws.rs.core.UriInfo; import java.security.KeyPair; +import java.util.Iterator; import java.util.Set; import java.util.TreeSet; @@ -108,6 +116,11 @@ public class SAMLIdentityProvider extends AbstractIdentityProvider it = SamlSessionUtils.getSamlAuthenticationPreprocessorIterator(session); it.hasNext(); ) { + authnRequest = it.next().beforeSendingLoginRequest(authnRequest, request.getAuthenticationSession()); + } + if (postBinding) { return binding.postBinding(authnRequestBuilder.toDocument()).request(destinationUrl); } else { @@ -146,11 +159,11 @@ public class SAMLIdentityProvider extends AbstractIdentityProvider=200 && status < 400; if (!success) { @@ -172,12 +185,12 @@ public class SAMLIdentityProvider extends AbstractIdentityProvider it = SamlSessionUtils.getSamlAuthenticationPreprocessorIterator(session); it.hasNext();) { + logoutRequest = it.next().beforeSendingLogoutRequest(logoutRequest, userSession, null); + } + return logoutRequest; } private JaxrsSAML2BindingBuilder buildLogoutBinding(KeycloakSession session, UserSessionModel userSession, RealmModel realm) { diff --git a/services/src/main/java/org/keycloak/protocol/saml/SamlProtocol.java b/services/src/main/java/org/keycloak/protocol/saml/SamlProtocol.java index 7cf3649c02..016d02b13f 100755 --- a/services/src/main/java/org/keycloak/protocol/saml/SamlProtocol.java +++ b/services/src/main/java/org/keycloak/protocol/saml/SamlProtocol.java @@ -28,6 +28,7 @@ import org.jboss.logging.Logger; import org.keycloak.connections.httpclient.HttpClientProvider; import org.keycloak.dom.saml.v2.assertion.AssertionType; import org.keycloak.dom.saml.v2.assertion.AttributeStatementType; +import org.keycloak.dom.saml.v2.protocol.LogoutRequestType; import org.keycloak.dom.saml.v2.protocol.ResponseType; import org.keycloak.events.Details; import org.keycloak.events.EventBuilder; @@ -47,10 +48,12 @@ import org.keycloak.protocol.ProtocolMapperUtils; import org.keycloak.protocol.saml.mappers.SAMLAttributeStatementMapper; import org.keycloak.protocol.saml.mappers.SAMLLoginResponseMapper; import org.keycloak.protocol.saml.mappers.SAMLRoleListMapper; +import org.keycloak.protocol.saml.preprocessor.SamlAuthenticationPreprocessor; import org.keycloak.saml.SAML2ErrorResponseBuilder; import org.keycloak.saml.SAML2LoginResponseBuilder; import org.keycloak.saml.SAML2LogoutRequestBuilder; import org.keycloak.saml.SAML2LogoutResponseBuilder; +import org.keycloak.saml.SamlProtocolExtensionsAwareBuilder.NodeGenerator; import org.keycloak.saml.SignatureAlgorithm; import org.keycloak.saml.common.constants.GeneralConstants; import org.keycloak.saml.common.constants.JBossSAMLURIConstants; @@ -58,6 +61,7 @@ import org.keycloak.saml.common.exceptions.ConfigurationException; import org.keycloak.saml.common.exceptions.ParsingException; import org.keycloak.saml.common.exceptions.ProcessingException; import org.keycloak.saml.common.util.XmlKeyInfoKeyNameTransformer; +import org.keycloak.saml.processing.api.saml.v2.request.SAML2Request; import org.keycloak.saml.processing.core.util.KeycloakKeySamlExtensionGenerator; import org.keycloak.services.ErrorPage; import org.keycloak.services.managers.AuthenticationSessionManager; @@ -78,6 +82,7 @@ import java.net.URI; import java.security.PublicKey; import java.util.ArrayList; import java.util.HashMap; +import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Map; @@ -526,6 +531,11 @@ public class SamlProtocol implements LoginProtocol { for (ProtocolMapperProcessor processor : mappers) { response = processor.mapper.transformLoginResponse(response, processor.model, session, userSession, clientSession); } + + for (Iterator it = SamlSessionUtils.getSamlAuthenticationPreprocessorIterator(session); it.hasNext(); ) { + response = (ResponseType) it.next().beforeSendingResponse(response, clientSession); + } + return response; } @@ -564,20 +574,23 @@ public class SamlProtocol implements LoginProtocol { } if (postBinding) { - SAML2LogoutRequestBuilder logoutBuilder = createLogoutRequest(bindingUri, clientSession, client); + LogoutRequestType logoutRequest = createLogoutRequest(bindingUri, clientSession, client); // This is POST binding, hence KeyID is included in dsig:KeyInfo/dsig:KeyName, no need to add element JaxrsSAML2BindingBuilder binding = createBindingBuilder(samlClient); - return binding.postBinding(logoutBuilder.buildDocument()).request(bindingUri); + return binding.postBinding(SAML2Request.convert(logoutRequest)).request(bindingUri); } else { logger.debug("frontchannel redirect binding"); - SAML2LogoutRequestBuilder logoutBuilder = createLogoutRequest(bindingUri, clientSession, client); + NodeGenerator[] extensions; if (samlClient.requiresRealmSignature() && samlClient.addExtensionsElementWithKeyInfo()) { KeyManager.ActiveRsaKey keys = session.keys().getActiveRsaKey(realm); String keyName = samlClient.getXmlSigKeyInfoKeyNameTransformer().getKeyName(keys.getKid(), keys.getCertificate()); - logoutBuilder.addExtension(new KeycloakKeySamlExtensionGenerator(keyName)); + extensions = new NodeGenerator[] { new KeycloakKeySamlExtensionGenerator(keyName) }; + } else { + extensions = new NodeGenerator[] {}; } + LogoutRequestType logoutRequest = createLogoutRequest(bindingUri, clientSession, client, extensions); JaxrsSAML2BindingBuilder binding = createBindingBuilder(samlClient); - return binding.redirectBinding(logoutBuilder.buildDocument()).request(bindingUri); + return binding.redirectBinding(SAML2Request.convert(logoutRequest)).request(bindingUri); } } catch (ConfigurationException e) { throw new RuntimeException(e); @@ -664,13 +677,13 @@ public class SamlProtocol implements LoginProtocol { logger.warnf("Can't do backchannel logout. No SingleLogoutService POST Binding registered for client: %s", client.getClientId()); return; } - SAML2LogoutRequestBuilder logoutBuilder = createLogoutRequest(logoutUrl, clientSession, client); String logoutRequestString = null; try { + LogoutRequestType logoutRequest = createLogoutRequest(logoutUrl, clientSession, client); JaxrsSAML2BindingBuilder binding = createBindingBuilder(samlClient); // This is POST binding, hence KeyID is included in dsig:KeyInfo/dsig:KeyName, no need to add element - logoutRequestString = binding.postBinding(logoutBuilder.buildDocument()).encoded(); + logoutRequestString = binding.postBinding(SAML2Request.convert(logoutRequest)).encoded(); } catch (Exception e) { logger.warn("failed to send saml logout", e); return; @@ -715,7 +728,7 @@ public class SamlProtocol implements LoginProtocol { } - protected SAML2LogoutRequestBuilder createLogoutRequest(String logoutUrl, AuthenticatedClientSessionModel clientSession, ClientModel client) { + protected LogoutRequestType createLogoutRequest(String logoutUrl, AuthenticatedClientSessionModel clientSession, ClientModel client, NodeGenerator... extensions) throws ConfigurationException { // build userPrincipal with subject used at login SAML2LogoutRequestBuilder logoutBuilder = new SAML2LogoutRequestBuilder().assertionExpiration(realm.getAccessCodeLifespan()).issuer(getResponseIssuer(realm)) .userPrincipal(clientSession.getNote(SAML_NAME_ID), clientSession.getNote(SAML_NAME_ID_FORMAT)).destination(logoutUrl); @@ -723,7 +736,15 @@ public class SamlProtocol implements LoginProtocol { String sessionIndex = SamlSessionUtils.getSessionIndex(clientSession); logoutBuilder.sessionIndex(sessionIndex); - return logoutBuilder; + for (NodeGenerator extension : extensions) { + logoutBuilder.addExtension(extension); + } + LogoutRequestType logoutRequest = logoutBuilder.createLogoutRequest(); + for (Iterator it = SamlSessionUtils.getSamlAuthenticationPreprocessorIterator(session); it.hasNext();) { + logoutRequest = it.next().beforeSendingLogoutRequest(logoutRequest, clientSession.getUserSession(), clientSession); + } + + return logoutRequest; } @Override diff --git a/services/src/main/java/org/keycloak/protocol/saml/SamlService.java b/services/src/main/java/org/keycloak/protocol/saml/SamlService.java index 430c37065f..b5a969c89b 100755 --- a/services/src/main/java/org/keycloak/protocol/saml/SamlService.java +++ b/services/src/main/java/org/keycloak/protocol/saml/SamlService.java @@ -46,6 +46,7 @@ import org.keycloak.models.UserSessionModel; import org.keycloak.protocol.AuthorizationEndpointBase; import org.keycloak.protocol.oidc.OIDCLoginProtocol; import org.keycloak.protocol.oidc.utils.RedirectUtils; +import org.keycloak.protocol.saml.preprocessor.SamlAuthenticationPreprocessor; import org.keycloak.protocol.saml.profile.ecp.SamlEcpProfileService; import org.keycloak.saml.SAML2LogoutResponseBuilder; import org.keycloak.saml.SAMLRequestParser; @@ -74,6 +75,7 @@ import java.io.IOException; import java.io.InputStream; import java.net.URI; import java.security.PublicKey; +import java.util.Iterator; import java.util.Objects; import java.util.Properties; import java.util.Set; @@ -357,6 +359,12 @@ public class SamlService extends AuthorizationEndpointBase { && requestAbstractType.isForceAuthn()) { authSession.setAuthNote(SamlProtocol.SAML_LOGIN_REQUEST_FORCEAUTHN, SamlProtocol.SAML_FORCEAUTHN_REQUIREMENT); } + + + for(Iterator it = SamlSessionUtils.getSamlAuthenticationPreprocessorIterator(session); it.hasNext();) { + requestAbstractType = it.next().beforeProcessingLoginRequest(requestAbstractType, authSession); + } + //If unset we fall back to default "false" final boolean isPassive = (null == requestAbstractType.isIsPassive() ? false : requestAbstractType.isIsPassive().booleanValue()); @@ -430,6 +438,11 @@ public class SamlService extends AuthorizationEndpointBase { if (clientSession != null) { clientSession.setAction(AuthenticationSessionModel.Action.LOGGED_OUT.name()); } + + for(Iterator it = SamlSessionUtils.getSamlAuthenticationPreprocessorIterator(session); it.hasNext();) { + logoutRequest = it.next().beforeProcessingLogoutRequest(logoutRequest, userSession, clientSession); + } + logger.debug("browser Logout"); return authManager.browserLogout(session, realm, userSession, session.getContext().getUri(), clientConnection, headers, null); } else if (logoutRequest.getSessionIndex() != null) { @@ -444,6 +457,10 @@ public class SamlService extends AuthorizationEndpointBase { clientSession.setAction(AuthenticationSessionModel.Action.LOGGED_OUT.name()); } + for(Iterator it = SamlSessionUtils.getSamlAuthenticationPreprocessorIterator(session); it.hasNext();) { + logoutRequest = it.next().beforeProcessingLogoutRequest(logoutRequest, userSession, clientSession); + } + try { authManager.backchannelLogout(session, realm, userSession, session.getContext().getUri(), clientConnection, headers, true); } catch (Exception e) { diff --git a/services/src/main/java/org/keycloak/protocol/saml/SamlSessionUtils.java b/services/src/main/java/org/keycloak/protocol/saml/SamlSessionUtils.java index f9354b4a1c..92f51f8650 100644 --- a/services/src/main/java/org/keycloak/protocol/saml/SamlSessionUtils.java +++ b/services/src/main/java/org/keycloak/protocol/saml/SamlSessionUtils.java @@ -17,6 +17,8 @@ package org.keycloak.protocol.saml; +import java.util.Iterator; +import java.util.Objects; import java.util.regex.Pattern; import org.keycloak.models.AuthenticatedClientSessionModel; @@ -24,6 +26,7 @@ import org.keycloak.models.ClientModel; import org.keycloak.models.KeycloakSession; import org.keycloak.models.RealmModel; import org.keycloak.models.UserSessionModel; +import org.keycloak.protocol.saml.preprocessor.SamlAuthenticationPreprocessor; import org.keycloak.services.managers.UserSessionCrossDCManager; /** @@ -64,5 +67,12 @@ public class SamlSessionUtils { return userSession.getAuthenticatedClientSessionByClient(clientUUID); } + + public static Iterator getSamlAuthenticationPreprocessorIterator(KeycloakSession session) { + return session.getKeycloakSessionFactory().getProviderFactories(SamlAuthenticationPreprocessor.class).stream() + .filter(Objects::nonNull) + .map(SamlAuthenticationPreprocessor.class::cast) + .iterator(); + } } diff --git a/services/src/main/java/org/keycloak/protocol/saml/preprocessor/SamlAuthenticationPreprocessor.java b/services/src/main/java/org/keycloak/protocol/saml/preprocessor/SamlAuthenticationPreprocessor.java new file mode 100644 index 0000000000..5ed70137eb --- /dev/null +++ b/services/src/main/java/org/keycloak/protocol/saml/preprocessor/SamlAuthenticationPreprocessor.java @@ -0,0 +1,88 @@ +/* + * Copyright 2016 Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.keycloak.protocol.saml.preprocessor; + +import org.keycloak.dom.saml.v2.protocol.AuthnRequestType; +import org.keycloak.dom.saml.v2.protocol.LogoutRequestType; +import org.keycloak.dom.saml.v2.protocol.StatusResponseType; +import org.keycloak.models.AuthenticatedClientSessionModel; +import org.keycloak.models.UserSessionModel; +import org.keycloak.provider.Provider; +import org.keycloak.provider.ProviderFactory; +import org.keycloak.sessions.AuthenticationSessionModel; + +/** + * Provider interface for SAML authentication preprocessing. + * + * @author Gideon Caranzo + * + */ +public interface SamlAuthenticationPreprocessor extends Provider, ProviderFactory { + + /** + * Called before a login request is processed. + */ + default AuthnRequestType beforeProcessingLoginRequest(AuthnRequestType authnRequest, + AuthenticationSessionModel authSession) { + return authnRequest; + } + + /** + * Called before a logout request is processed. + * + * @param clientSession can be null if client is not applicable (e.g. when used within identity broker) + */ + default LogoutRequestType beforeProcessingLogoutRequest(LogoutRequestType logoutRequest, + UserSessionModel authSession, AuthenticatedClientSessionModel clientSession) { + return logoutRequest; + } + + /** + * Called before a login request is sent. + */ + default AuthnRequestType beforeSendingLoginRequest(AuthnRequestType authnRequest, + AuthenticationSessionModel clientSession) { + return authnRequest; + } + + /** + * Called before a logout request is sent. + * + * @param clientSession can be null if client is not applicable (e.g. when used within identity broker) + */ + default LogoutRequestType beforeSendingLogoutRequest(LogoutRequestType logoutRequest, + UserSessionModel authSession, AuthenticatedClientSessionModel clientSession) { + return logoutRequest; + } + + /** + * Called before a login response is processed. + */ + default StatusResponseType beforeProcessingLoginResponse(StatusResponseType statusResponse, + AuthenticationSessionModel authSession) { + return statusResponse; + } + + /** + * Called before a response is sent back to the client. + */ + default StatusResponseType beforeSendingResponse(StatusResponseType statusResponse, + AuthenticatedClientSessionModel clientSession) { + return statusResponse; + } + +} diff --git a/services/src/main/java/org/keycloak/protocol/saml/preprocessor/SamlAuthenticationPreprocessorSpi.java b/services/src/main/java/org/keycloak/protocol/saml/preprocessor/SamlAuthenticationPreprocessorSpi.java new file mode 100644 index 0000000000..8fd5ae8f91 --- /dev/null +++ b/services/src/main/java/org/keycloak/protocol/saml/preprocessor/SamlAuthenticationPreprocessorSpi.java @@ -0,0 +1,51 @@ +/* + * Copyright 2016 Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.keycloak.protocol.saml.preprocessor; + +import org.keycloak.provider.Provider; +import org.keycloak.provider.ProviderFactory; +import org.keycloak.provider.Spi; + +/** + * + * @author Gideon Caranzo + * + */ +public class SamlAuthenticationPreprocessorSpi implements Spi { + + @Override + public boolean isInternal() { + return false; + } + + @Override + public String getName() { + return "saml-authentication-preprocessor"; + } + + @Override + public Class getProviderClass() { + return SamlAuthenticationPreprocessor.class; + } + + @SuppressWarnings("rawtypes") + @Override + public Class getProviderFactoryClass() { + return SamlAuthenticationPreprocessor.class; + } + +} diff --git a/services/src/main/java/org/keycloak/services/resources/IdentityBrokerService.java b/services/src/main/java/org/keycloak/services/resources/IdentityBrokerService.java index 9414e2ed3b..485b2ce96d 100755 --- a/services/src/main/java/org/keycloak/services/resources/IdentityBrokerService.java +++ b/services/src/main/java/org/keycloak/services/resources/IdentityBrokerService.java @@ -33,11 +33,14 @@ import org.keycloak.broker.provider.IdentityProviderFactory; import org.keycloak.broker.provider.IdentityProviderMapper; import org.keycloak.broker.provider.util.IdentityBrokerState; import org.keycloak.broker.saml.SAMLEndpoint; +import org.keycloak.broker.saml.SAMLIdentityProvider; import org.keycloak.broker.social.SocialIdentityProvider; import org.keycloak.common.ClientConnection; import org.keycloak.common.util.Base64Url; import org.keycloak.common.util.ObjectUtil; import org.keycloak.common.util.Time; +import org.keycloak.dom.saml.v2.SAML2Object; +import org.keycloak.dom.saml.v2.protocol.StatusResponseType; import org.keycloak.events.Details; import org.keycloak.events.Errors; import org.keycloak.events.EventBuilder; @@ -66,6 +69,8 @@ import org.keycloak.protocol.oidc.TokenManager; import org.keycloak.protocol.oidc.utils.RedirectUtils; import org.keycloak.protocol.saml.SamlProtocol; import org.keycloak.protocol.saml.SamlService; +import org.keycloak.protocol.saml.SamlSessionUtils; +import org.keycloak.protocol.saml.preprocessor.SamlAuthenticationPreprocessor; import org.keycloak.provider.ProviderFactory; import org.keycloak.representations.AccessToken; import org.keycloak.services.ErrorPage; @@ -106,6 +111,7 @@ import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.ArrayList; import java.util.HashMap; +import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Objects; @@ -509,6 +515,13 @@ public class IdentityBrokerService implements IdentityProvider.AuthenticationCal AuthenticationSessionModel authenticationSession = clientCode.getClientSession(); context.setAuthenticationSession(authenticationSession); + + StatusResponseType loginResponse = (StatusResponseType) context.getContextData().get(SAMLEndpoint.SAML_LOGIN_RESPONSE); + if (loginResponse != null) { + for(Iterator it = SamlSessionUtils.getSamlAuthenticationPreprocessorIterator(session); it.hasNext();) { + loginResponse = it.next().beforeProcessingLoginResponse(loginResponse, authenticationSession); + } + } session.getContext().setClient(authenticationSession.getClient()); diff --git a/services/src/main/resources/META-INF/services/org.keycloak.provider.Spi b/services/src/main/resources/META-INF/services/org.keycloak.provider.Spi index 20e9747130..efc42fa49c 100755 --- a/services/src/main/resources/META-INF/services/org.keycloak.provider.Spi +++ b/services/src/main/resources/META-INF/services/org.keycloak.provider.Spi @@ -21,4 +21,5 @@ org.keycloak.services.clientregistration.ClientRegistrationSpi org.keycloak.services.clientregistration.policy.ClientRegistrationPolicySpi org.keycloak.authentication.actiontoken.ActionTokenHandlerSpi org.keycloak.services.x509.X509ClientCertificateLookupSpi -org.keycloak.protocol.oidc.ext.OIDCExtSPI \ No newline at end of file +org.keycloak.protocol.oidc.ext.OIDCExtSPI +org.keycloak.protocol.saml.preprocessor.SamlAuthenticationPreprocessorSpi \ No newline at end of file