KEYCLOAK-9936 Added optional hooks for preprocessing SAML authentication

Co-Authored-By: Hynek Mlnarik <hmlnarik@redhat.com>
This commit is contained in:
Gideon Caranzo 2019-05-16 00:58:15 -05:00 committed by Hynek Mlnařík
parent e018ca3e29
commit e07fd9ffa3
11 changed files with 272 additions and 35 deletions

View file

@ -90,27 +90,31 @@ public class SAML2AuthnRequestBuilder implements SamlProtocolExtensionsAwareBuil
public Document toDocument() { public Document toDocument() {
try { try {
AuthnRequestType authnRequestType = this.authnRequestType; AuthnRequestType authnRequestType = createAuthnRequest();
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);
}
return new SAML2Request().convert(authnRequestType); return new SAML2Request().convert(authnRequestType);
} catch (Exception e) { } catch (Exception e) {
throw new RuntimeException("Could not convert " + authnRequestType + " to a document.", 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;
}
} }

View file

@ -104,7 +104,7 @@ public class SAML2LogoutRequestBuilder implements SamlProtocolExtensionsAwareBui
return document; return document;
} }
private LogoutRequestType createLogoutRequest() throws ConfigurationException { public LogoutRequestType createLogoutRequest() throws ConfigurationException {
LogoutRequestType lort = SAML2Request.createLogoutRequest(issuer); LogoutRequestType lort = SAML2Request.createLogoutRequest(issuer);
lort.setNameID(nameId); lort.setNameID(nameId);

View file

@ -46,6 +46,8 @@ import org.keycloak.models.UserSessionModel;
import org.keycloak.protocol.saml.JaxrsSAML2BindingBuilder; import org.keycloak.protocol.saml.JaxrsSAML2BindingBuilder;
import org.keycloak.protocol.saml.SamlProtocol; import org.keycloak.protocol.saml.SamlProtocol;
import org.keycloak.protocol.saml.SamlProtocolUtils; 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.SAML2LogoutResponseBuilder;
import org.keycloak.saml.SAMLRequestParser; import org.keycloak.saml.SAMLRequestParser;
import org.keycloak.saml.common.constants.GeneralConstants; 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) { if (userSession.getState() == UserSessionModel.State.LOGGING_OUT || userSession.getState() == UserSessionModel.State.LOGGED_OUT) {
continue; continue;
} }
for(Iterator<SamlAuthenticationPreprocessor> it = SamlSessionUtils.getSamlAuthenticationPreprocessorIterator(session); it.hasNext();) {
request = it.next().beforeProcessingLogoutRequest(request, userSession, null);
}
try { try {
AuthenticationManager.backchannelLogout(session, realm, userSession, session.getContext().getUri(), clientConnection, headers, false); AuthenticationManager.backchannelLogout(session, realm, userSession, session.getContext().getUri(), clientConnection, headers, false);
} catch (Exception e) { } catch (Exception e) {
@ -306,6 +313,11 @@ public class SAMLEndpoint {
if (userSession.getState() == UserSessionModel.State.LOGGING_OUT || userSession.getState() == UserSessionModel.State.LOGGED_OUT) { if (userSession.getState() == UserSessionModel.State.LOGGING_OUT || userSession.getState() == UserSessionModel.State.LOGGED_OUT) {
continue; continue;
} }
for(Iterator<SamlAuthenticationPreprocessor> it = SamlSessionUtils.getSamlAuthenticationPreprocessorIterator(session); it.hasNext();) {
request = it.next().beforeProcessingLogoutRequest(request, userSession, null);
}
try { try {
AuthenticationManager.backchannelLogout(session, realm, userSession, session.getContext().getUri(), clientConnection, headers, false); AuthenticationManager.backchannelLogout(session, realm, userSession, session.getContext().getUri(), clientConnection, headers, false);
} catch (Exception e) { } catch (Exception e) {

View file

@ -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.NameIDType;
import org.keycloak.dom.saml.v2.assertion.SubjectType; import org.keycloak.dom.saml.v2.assertion.SubjectType;
import org.keycloak.dom.saml.v2.metadata.KeyTypes; 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.dom.saml.v2.protocol.ResponseType;
import org.keycloak.events.EventBuilder; import org.keycloak.events.EventBuilder;
import org.keycloak.keys.RsaKeyMetadata; import org.keycloak.keys.RsaKeyMetadata;
import org.keycloak.models.*; import org.keycloak.models.*;
import org.keycloak.protocol.saml.JaxrsSAML2BindingBuilder; 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.*;
import org.keycloak.saml.SamlProtocolExtensionsAwareBuilder.NodeGenerator;
import org.keycloak.saml.common.constants.GeneralConstants; import org.keycloak.saml.common.constants.GeneralConstants;
import org.keycloak.saml.common.constants.JBossSAMLURIConstants; 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.processing.core.util.KeycloakKeySamlExtensionGenerator;
import org.keycloak.saml.validators.DestinationValidator; import org.keycloak.saml.validators.DestinationValidator;
import org.keycloak.sessions.AuthenticationSessionModel; 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.UriBuilder;
import javax.ws.rs.core.UriInfo; import javax.ws.rs.core.UriInfo;
import java.security.KeyPair; import java.security.KeyPair;
import java.util.Iterator;
import java.util.Set; import java.util.Set;
import java.util.TreeSet; import java.util.TreeSet;
@ -108,6 +116,11 @@ public class SAMLIdentityProvider extends AbstractIdentityProvider<SAMLIdentityP
} }
} }
AuthnRequestType authnRequest = authnRequestBuilder.createAuthnRequest();
for(Iterator<SamlAuthenticationPreprocessor> it = SamlSessionUtils.getSamlAuthenticationPreprocessorIterator(session); it.hasNext(); ) {
authnRequest = it.next().beforeSendingLoginRequest(authnRequest, request.getAuthenticationSession());
}
if (postBinding) { if (postBinding) {
return binding.postBinding(authnRequestBuilder.toDocument()).request(destinationUrl); return binding.postBinding(authnRequestBuilder.toDocument()).request(destinationUrl);
} else { } else {
@ -146,11 +159,11 @@ public class SAMLIdentityProvider extends AbstractIdentityProvider<SAMLIdentityP
public void backchannelLogout(KeycloakSession session, UserSessionModel userSession, UriInfo uriInfo, RealmModel realm) { public void backchannelLogout(KeycloakSession session, UserSessionModel userSession, UriInfo uriInfo, RealmModel realm) {
String singleLogoutServiceUrl = getConfig().getSingleLogoutServiceUrl(); String singleLogoutServiceUrl = getConfig().getSingleLogoutServiceUrl();
if (singleLogoutServiceUrl == null || singleLogoutServiceUrl.trim().equals("") || !getConfig().isBackchannelSupported()) return; if (singleLogoutServiceUrl == null || singleLogoutServiceUrl.trim().equals("") || !getConfig().isBackchannelSupported()) return;
SAML2LogoutRequestBuilder logoutBuilder = buildLogoutRequest(userSession, uriInfo, realm, singleLogoutServiceUrl);
JaxrsSAML2BindingBuilder binding = buildLogoutBinding(session, userSession, realm); JaxrsSAML2BindingBuilder binding = buildLogoutBinding(session, userSession, realm);
try { try {
LogoutRequestType logoutRequest = buildLogoutRequest(userSession, uriInfo, realm, singleLogoutServiceUrl);
int status = SimpleHttp.doPost(singleLogoutServiceUrl, session) int status = SimpleHttp.doPost(singleLogoutServiceUrl, session)
.param(GeneralConstants.SAML_REQUEST_KEY, binding.postBinding(logoutBuilder.buildDocument()).encoded()) .param(GeneralConstants.SAML_REQUEST_KEY, binding.postBinding(SAML2Request.convert(logoutRequest)).encoded())
.param(GeneralConstants.RELAY_STATE, userSession.getId()).asStatus(); .param(GeneralConstants.RELAY_STATE, userSession.getId()).asStatus();
boolean success = status >=200 && status < 400; boolean success = status >=200 && status < 400;
if (!success) { if (!success) {
@ -172,12 +185,12 @@ public class SAMLIdentityProvider extends AbstractIdentityProvider<SAMLIdentityP
return null; return null;
} else { } else {
try { try {
SAML2LogoutRequestBuilder logoutBuilder = buildLogoutRequest(userSession, uriInfo, realm, singleLogoutServiceUrl); LogoutRequestType logoutRequest = buildLogoutRequest(userSession, uriInfo, realm, singleLogoutServiceUrl);
JaxrsSAML2BindingBuilder binding = buildLogoutBinding(session, userSession, realm); JaxrsSAML2BindingBuilder binding = buildLogoutBinding(session, userSession, realm);
if (getConfig().isPostBindingLogout()) { if (getConfig().isPostBindingLogout()) {
return binding.postBinding(logoutBuilder.buildDocument()).request(singleLogoutServiceUrl); return binding.postBinding(SAML2Request.convert(logoutRequest)).request(singleLogoutServiceUrl);
} else { } else {
return binding.redirectBinding(logoutBuilder.buildDocument()).request(singleLogoutServiceUrl); return binding.redirectBinding(SAML2Request.convert(logoutRequest)).request(singleLogoutServiceUrl);
} }
} catch (Exception e) { } catch (Exception e) {
throw new RuntimeException(e); throw new RuntimeException(e);
@ -185,14 +198,21 @@ public class SAMLIdentityProvider extends AbstractIdentityProvider<SAMLIdentityP
} }
} }
protected SAML2LogoutRequestBuilder buildLogoutRequest(UserSessionModel userSession, UriInfo uriInfo, RealmModel realm, String singleLogoutServiceUrl) { protected LogoutRequestType buildLogoutRequest(UserSessionModel userSession, UriInfo uriInfo, RealmModel realm, String singleLogoutServiceUrl, NodeGenerator... extensions) throws ConfigurationException {
SAML2LogoutRequestBuilder logoutBuilder = new SAML2LogoutRequestBuilder() SAML2LogoutRequestBuilder logoutBuilder = new SAML2LogoutRequestBuilder()
.assertionExpiration(realm.getAccessCodeLifespan()) .assertionExpiration(realm.getAccessCodeLifespan())
.issuer(getEntityId(uriInfo, realm)) .issuer(getEntityId(uriInfo, realm))
.sessionIndex(userSession.getNote(SAMLEndpoint.SAML_FEDERATED_SESSION_INDEX)) .sessionIndex(userSession.getNote(SAMLEndpoint.SAML_FEDERATED_SESSION_INDEX))
.nameId(NameIDType.deserializeFromString(userSession.getNote(SAMLEndpoint.SAML_FEDERATED_SUBJECT_NAMEID))) .nameId(NameIDType.deserializeFromString(userSession.getNote(SAMLEndpoint.SAML_FEDERATED_SUBJECT_NAMEID)))
.destination(singleLogoutServiceUrl); .destination(singleLogoutServiceUrl);
return logoutBuilder; LogoutRequestType logoutRequest = logoutBuilder.createLogoutRequest();
for (NodeGenerator extension : extensions) {
logoutBuilder.addExtension(extension);
}
for (Iterator<SamlAuthenticationPreprocessor> it = SamlSessionUtils.getSamlAuthenticationPreprocessorIterator(session); it.hasNext();) {
logoutRequest = it.next().beforeSendingLogoutRequest(logoutRequest, userSession, null);
}
return logoutRequest;
} }
private JaxrsSAML2BindingBuilder buildLogoutBinding(KeycloakSession session, UserSessionModel userSession, RealmModel realm) { private JaxrsSAML2BindingBuilder buildLogoutBinding(KeycloakSession session, UserSessionModel userSession, RealmModel realm) {

View file

@ -28,6 +28,7 @@ import org.jboss.logging.Logger;
import org.keycloak.connections.httpclient.HttpClientProvider; import org.keycloak.connections.httpclient.HttpClientProvider;
import org.keycloak.dom.saml.v2.assertion.AssertionType; import org.keycloak.dom.saml.v2.assertion.AssertionType;
import org.keycloak.dom.saml.v2.assertion.AttributeStatementType; 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.dom.saml.v2.protocol.ResponseType;
import org.keycloak.events.Details; import org.keycloak.events.Details;
import org.keycloak.events.EventBuilder; 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.SAMLAttributeStatementMapper;
import org.keycloak.protocol.saml.mappers.SAMLLoginResponseMapper; import org.keycloak.protocol.saml.mappers.SAMLLoginResponseMapper;
import org.keycloak.protocol.saml.mappers.SAMLRoleListMapper; import org.keycloak.protocol.saml.mappers.SAMLRoleListMapper;
import org.keycloak.protocol.saml.preprocessor.SamlAuthenticationPreprocessor;
import org.keycloak.saml.SAML2ErrorResponseBuilder; import org.keycloak.saml.SAML2ErrorResponseBuilder;
import org.keycloak.saml.SAML2LoginResponseBuilder; import org.keycloak.saml.SAML2LoginResponseBuilder;
import org.keycloak.saml.SAML2LogoutRequestBuilder; import org.keycloak.saml.SAML2LogoutRequestBuilder;
import org.keycloak.saml.SAML2LogoutResponseBuilder; import org.keycloak.saml.SAML2LogoutResponseBuilder;
import org.keycloak.saml.SamlProtocolExtensionsAwareBuilder.NodeGenerator;
import org.keycloak.saml.SignatureAlgorithm; import org.keycloak.saml.SignatureAlgorithm;
import org.keycloak.saml.common.constants.GeneralConstants; import org.keycloak.saml.common.constants.GeneralConstants;
import org.keycloak.saml.common.constants.JBossSAMLURIConstants; 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.ParsingException;
import org.keycloak.saml.common.exceptions.ProcessingException; import org.keycloak.saml.common.exceptions.ProcessingException;
import org.keycloak.saml.common.util.XmlKeyInfoKeyNameTransformer; 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.saml.processing.core.util.KeycloakKeySamlExtensionGenerator;
import org.keycloak.services.ErrorPage; import org.keycloak.services.ErrorPage;
import org.keycloak.services.managers.AuthenticationSessionManager; import org.keycloak.services.managers.AuthenticationSessionManager;
@ -78,6 +82,7 @@ import java.net.URI;
import java.security.PublicKey; import java.security.PublicKey;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@ -526,6 +531,11 @@ public class SamlProtocol implements LoginProtocol {
for (ProtocolMapperProcessor<SAMLLoginResponseMapper> processor : mappers) { for (ProtocolMapperProcessor<SAMLLoginResponseMapper> processor : mappers) {
response = processor.mapper.transformLoginResponse(response, processor.model, session, userSession, clientSession); response = processor.mapper.transformLoginResponse(response, processor.model, session, userSession, clientSession);
} }
for (Iterator<SamlAuthenticationPreprocessor> it = SamlSessionUtils.getSamlAuthenticationPreprocessorIterator(session); it.hasNext(); ) {
response = (ResponseType) it.next().beforeSendingResponse(response, clientSession);
}
return response; return response;
} }
@ -564,20 +574,23 @@ public class SamlProtocol implements LoginProtocol {
} }
if (postBinding) { 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 <samlp:Extensions> element // This is POST binding, hence KeyID is included in dsig:KeyInfo/dsig:KeyName, no need to add <samlp:Extensions> element
JaxrsSAML2BindingBuilder binding = createBindingBuilder(samlClient); JaxrsSAML2BindingBuilder binding = createBindingBuilder(samlClient);
return binding.postBinding(logoutBuilder.buildDocument()).request(bindingUri); return binding.postBinding(SAML2Request.convert(logoutRequest)).request(bindingUri);
} else { } else {
logger.debug("frontchannel redirect binding"); logger.debug("frontchannel redirect binding");
SAML2LogoutRequestBuilder logoutBuilder = createLogoutRequest(bindingUri, clientSession, client); NodeGenerator[] extensions;
if (samlClient.requiresRealmSignature() && samlClient.addExtensionsElementWithKeyInfo()) { if (samlClient.requiresRealmSignature() && samlClient.addExtensionsElementWithKeyInfo()) {
KeyManager.ActiveRsaKey keys = session.keys().getActiveRsaKey(realm); KeyManager.ActiveRsaKey keys = session.keys().getActiveRsaKey(realm);
String keyName = samlClient.getXmlSigKeyInfoKeyNameTransformer().getKeyName(keys.getKid(), keys.getCertificate()); 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); JaxrsSAML2BindingBuilder binding = createBindingBuilder(samlClient);
return binding.redirectBinding(logoutBuilder.buildDocument()).request(bindingUri); return binding.redirectBinding(SAML2Request.convert(logoutRequest)).request(bindingUri);
} }
} catch (ConfigurationException e) { } catch (ConfigurationException e) {
throw new RuntimeException(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()); logger.warnf("Can't do backchannel logout. No SingleLogoutService POST Binding registered for client: %s", client.getClientId());
return; return;
} }
SAML2LogoutRequestBuilder logoutBuilder = createLogoutRequest(logoutUrl, clientSession, client);
String logoutRequestString = null; String logoutRequestString = null;
try { try {
LogoutRequestType logoutRequest = createLogoutRequest(logoutUrl, clientSession, client);
JaxrsSAML2BindingBuilder binding = createBindingBuilder(samlClient); JaxrsSAML2BindingBuilder binding = createBindingBuilder(samlClient);
// This is POST binding, hence KeyID is included in dsig:KeyInfo/dsig:KeyName, no need to add <samlp:Extensions> element // This is POST binding, hence KeyID is included in dsig:KeyInfo/dsig:KeyName, no need to add <samlp:Extensions> element
logoutRequestString = binding.postBinding(logoutBuilder.buildDocument()).encoded(); logoutRequestString = binding.postBinding(SAML2Request.convert(logoutRequest)).encoded();
} catch (Exception e) { } catch (Exception e) {
logger.warn("failed to send saml logout", e); logger.warn("failed to send saml logout", e);
return; 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 // build userPrincipal with subject used at login
SAML2LogoutRequestBuilder logoutBuilder = new SAML2LogoutRequestBuilder().assertionExpiration(realm.getAccessCodeLifespan()).issuer(getResponseIssuer(realm)) SAML2LogoutRequestBuilder logoutBuilder = new SAML2LogoutRequestBuilder().assertionExpiration(realm.getAccessCodeLifespan()).issuer(getResponseIssuer(realm))
.userPrincipal(clientSession.getNote(SAML_NAME_ID), clientSession.getNote(SAML_NAME_ID_FORMAT)).destination(logoutUrl); .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); String sessionIndex = SamlSessionUtils.getSessionIndex(clientSession);
logoutBuilder.sessionIndex(sessionIndex); logoutBuilder.sessionIndex(sessionIndex);
return logoutBuilder; for (NodeGenerator extension : extensions) {
logoutBuilder.addExtension(extension);
}
LogoutRequestType logoutRequest = logoutBuilder.createLogoutRequest();
for (Iterator<SamlAuthenticationPreprocessor> it = SamlSessionUtils.getSamlAuthenticationPreprocessorIterator(session); it.hasNext();) {
logoutRequest = it.next().beforeSendingLogoutRequest(logoutRequest, clientSession.getUserSession(), clientSession);
}
return logoutRequest;
} }
@Override @Override

View file

@ -46,6 +46,7 @@ import org.keycloak.models.UserSessionModel;
import org.keycloak.protocol.AuthorizationEndpointBase; import org.keycloak.protocol.AuthorizationEndpointBase;
import org.keycloak.protocol.oidc.OIDCLoginProtocol; import org.keycloak.protocol.oidc.OIDCLoginProtocol;
import org.keycloak.protocol.oidc.utils.RedirectUtils; 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.protocol.saml.profile.ecp.SamlEcpProfileService;
import org.keycloak.saml.SAML2LogoutResponseBuilder; import org.keycloak.saml.SAML2LogoutResponseBuilder;
import org.keycloak.saml.SAMLRequestParser; import org.keycloak.saml.SAMLRequestParser;
@ -74,6 +75,7 @@ import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.net.URI; import java.net.URI;
import java.security.PublicKey; import java.security.PublicKey;
import java.util.Iterator;
import java.util.Objects; import java.util.Objects;
import java.util.Properties; import java.util.Properties;
import java.util.Set; import java.util.Set;
@ -357,6 +359,12 @@ public class SamlService extends AuthorizationEndpointBase {
&& requestAbstractType.isForceAuthn()) { && requestAbstractType.isForceAuthn()) {
authSession.setAuthNote(SamlProtocol.SAML_LOGIN_REQUEST_FORCEAUTHN, SamlProtocol.SAML_FORCEAUTHN_REQUIREMENT); authSession.setAuthNote(SamlProtocol.SAML_LOGIN_REQUEST_FORCEAUTHN, SamlProtocol.SAML_FORCEAUTHN_REQUIREMENT);
} }
for(Iterator<SamlAuthenticationPreprocessor> it = SamlSessionUtils.getSamlAuthenticationPreprocessorIterator(session); it.hasNext();) {
requestAbstractType = it.next().beforeProcessingLoginRequest(requestAbstractType, authSession);
}
//If unset we fall back to default "false" //If unset we fall back to default "false"
final boolean isPassive = (null == requestAbstractType.isIsPassive() ? final boolean isPassive = (null == requestAbstractType.isIsPassive() ?
false : requestAbstractType.isIsPassive().booleanValue()); false : requestAbstractType.isIsPassive().booleanValue());
@ -430,6 +438,11 @@ public class SamlService extends AuthorizationEndpointBase {
if (clientSession != null) { if (clientSession != null) {
clientSession.setAction(AuthenticationSessionModel.Action.LOGGED_OUT.name()); clientSession.setAction(AuthenticationSessionModel.Action.LOGGED_OUT.name());
} }
for(Iterator<SamlAuthenticationPreprocessor> it = SamlSessionUtils.getSamlAuthenticationPreprocessorIterator(session); it.hasNext();) {
logoutRequest = it.next().beforeProcessingLogoutRequest(logoutRequest, userSession, clientSession);
}
logger.debug("browser Logout"); logger.debug("browser Logout");
return authManager.browserLogout(session, realm, userSession, session.getContext().getUri(), clientConnection, headers, null); return authManager.browserLogout(session, realm, userSession, session.getContext().getUri(), clientConnection, headers, null);
} else if (logoutRequest.getSessionIndex() != null) { } else if (logoutRequest.getSessionIndex() != null) {
@ -444,6 +457,10 @@ public class SamlService extends AuthorizationEndpointBase {
clientSession.setAction(AuthenticationSessionModel.Action.LOGGED_OUT.name()); clientSession.setAction(AuthenticationSessionModel.Action.LOGGED_OUT.name());
} }
for(Iterator<SamlAuthenticationPreprocessor> it = SamlSessionUtils.getSamlAuthenticationPreprocessorIterator(session); it.hasNext();) {
logoutRequest = it.next().beforeProcessingLogoutRequest(logoutRequest, userSession, clientSession);
}
try { try {
authManager.backchannelLogout(session, realm, userSession, session.getContext().getUri(), clientConnection, headers, true); authManager.backchannelLogout(session, realm, userSession, session.getContext().getUri(), clientConnection, headers, true);
} catch (Exception e) { } catch (Exception e) {

View file

@ -17,6 +17,8 @@
package org.keycloak.protocol.saml; package org.keycloak.protocol.saml;
import java.util.Iterator;
import java.util.Objects;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import org.keycloak.models.AuthenticatedClientSessionModel; import org.keycloak.models.AuthenticatedClientSessionModel;
@ -24,6 +26,7 @@ import org.keycloak.models.ClientModel;
import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel; import org.keycloak.models.RealmModel;
import org.keycloak.models.UserSessionModel; import org.keycloak.models.UserSessionModel;
import org.keycloak.protocol.saml.preprocessor.SamlAuthenticationPreprocessor;
import org.keycloak.services.managers.UserSessionCrossDCManager; import org.keycloak.services.managers.UserSessionCrossDCManager;
/** /**
@ -65,4 +68,11 @@ public class SamlSessionUtils {
return userSession.getAuthenticatedClientSessionByClient(clientUUID); return userSession.getAuthenticatedClientSessionByClient(clientUUID);
} }
public static Iterator<SamlAuthenticationPreprocessor> getSamlAuthenticationPreprocessorIterator(KeycloakSession session) {
return session.getKeycloakSessionFactory().getProviderFactories(SamlAuthenticationPreprocessor.class).stream()
.filter(Objects::nonNull)
.map(SamlAuthenticationPreprocessor.class::cast)
.iterator();
}
} }

View file

@ -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 <a href="mailto:gideon.caranzo@thalesgroup.com">Gideon Caranzo</a>
*
*/
public interface SamlAuthenticationPreprocessor extends Provider, ProviderFactory<SamlAuthenticationPreprocessor> {
/**
* 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;
}
}

View file

@ -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 <a href="mailto:gideon.caranzo@thalesgroup.com">Gideon Caranzo</a>
*
*/
public class SamlAuthenticationPreprocessorSpi implements Spi {
@Override
public boolean isInternal() {
return false;
}
@Override
public String getName() {
return "saml-authentication-preprocessor";
}
@Override
public Class<? extends Provider> getProviderClass() {
return SamlAuthenticationPreprocessor.class;
}
@SuppressWarnings("rawtypes")
@Override
public Class<? extends ProviderFactory> getProviderFactoryClass() {
return SamlAuthenticationPreprocessor.class;
}
}

View file

@ -33,11 +33,14 @@ import org.keycloak.broker.provider.IdentityProviderFactory;
import org.keycloak.broker.provider.IdentityProviderMapper; import org.keycloak.broker.provider.IdentityProviderMapper;
import org.keycloak.broker.provider.util.IdentityBrokerState; import org.keycloak.broker.provider.util.IdentityBrokerState;
import org.keycloak.broker.saml.SAMLEndpoint; import org.keycloak.broker.saml.SAMLEndpoint;
import org.keycloak.broker.saml.SAMLIdentityProvider;
import org.keycloak.broker.social.SocialIdentityProvider; import org.keycloak.broker.social.SocialIdentityProvider;
import org.keycloak.common.ClientConnection; import org.keycloak.common.ClientConnection;
import org.keycloak.common.util.Base64Url; import org.keycloak.common.util.Base64Url;
import org.keycloak.common.util.ObjectUtil; import org.keycloak.common.util.ObjectUtil;
import org.keycloak.common.util.Time; 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.Details;
import org.keycloak.events.Errors; import org.keycloak.events.Errors;
import org.keycloak.events.EventBuilder; 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.oidc.utils.RedirectUtils;
import org.keycloak.protocol.saml.SamlProtocol; import org.keycloak.protocol.saml.SamlProtocol;
import org.keycloak.protocol.saml.SamlService; 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.provider.ProviderFactory;
import org.keycloak.representations.AccessToken; import org.keycloak.representations.AccessToken;
import org.keycloak.services.ErrorPage; import org.keycloak.services.ErrorPage;
@ -106,6 +111,7 @@ import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException; import java.security.NoSuchAlgorithmException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Objects; import java.util.Objects;
@ -510,6 +516,13 @@ public class IdentityBrokerService implements IdentityProvider.AuthenticationCal
AuthenticationSessionModel authenticationSession = clientCode.getClientSession(); AuthenticationSessionModel authenticationSession = clientCode.getClientSession();
context.setAuthenticationSession(authenticationSession); context.setAuthenticationSession(authenticationSession);
StatusResponseType loginResponse = (StatusResponseType) context.getContextData().get(SAMLEndpoint.SAML_LOGIN_RESPONSE);
if (loginResponse != null) {
for(Iterator<SamlAuthenticationPreprocessor> it = SamlSessionUtils.getSamlAuthenticationPreprocessorIterator(session); it.hasNext();) {
loginResponse = it.next().beforeProcessingLoginResponse(loginResponse, authenticationSession);
}
}
session.getContext().setClient(authenticationSession.getClient()); session.getContext().setClient(authenticationSession.getClient());
context.getIdp().preprocessFederatedIdentity(session, realmModel, context); context.getIdp().preprocessFederatedIdentity(session, realmModel, context);

View file

@ -22,3 +22,4 @@ org.keycloak.services.clientregistration.policy.ClientRegistrationPolicySpi
org.keycloak.authentication.actiontoken.ActionTokenHandlerSpi org.keycloak.authentication.actiontoken.ActionTokenHandlerSpi
org.keycloak.services.x509.X509ClientCertificateLookupSpi org.keycloak.services.x509.X509ClientCertificateLookupSpi
org.keycloak.protocol.oidc.ext.OIDCExtSPI org.keycloak.protocol.oidc.ext.OIDCExtSPI
org.keycloak.protocol.saml.preprocessor.SamlAuthenticationPreprocessorSpi