KEYCLOAK-9936 Added optional hooks for preprocessing SAML authentication
Co-Authored-By: Hynek Mlnarik <hmlnarik@redhat.com>
This commit is contained in:
parent
e018ca3e29
commit
e07fd9ffa3
11 changed files with 272 additions and 35 deletions
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
|
|
|
@ -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<SamlAuthenticationPreprocessor> 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<SamlAuthenticationPreprocessor> 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) {
|
||||
|
|
|
@ -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<SAMLIdentityP
|
|||
}
|
||||
}
|
||||
|
||||
AuthnRequestType authnRequest = authnRequestBuilder.createAuthnRequest();
|
||||
for(Iterator<SamlAuthenticationPreprocessor> 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<SAMLIdentityP
|
|||
public void backchannelLogout(KeycloakSession session, UserSessionModel userSession, UriInfo uriInfo, RealmModel realm) {
|
||||
String singleLogoutServiceUrl = getConfig().getSingleLogoutServiceUrl();
|
||||
if (singleLogoutServiceUrl == null || singleLogoutServiceUrl.trim().equals("") || !getConfig().isBackchannelSupported()) return;
|
||||
SAML2LogoutRequestBuilder logoutBuilder = buildLogoutRequest(userSession, uriInfo, realm, singleLogoutServiceUrl);
|
||||
JaxrsSAML2BindingBuilder binding = buildLogoutBinding(session, userSession, realm);
|
||||
try {
|
||||
LogoutRequestType logoutRequest = buildLogoutRequest(userSession, uriInfo, realm, singleLogoutServiceUrl);
|
||||
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();
|
||||
boolean success = status >=200 && status < 400;
|
||||
if (!success) {
|
||||
|
@ -172,12 +185,12 @@ public class SAMLIdentityProvider extends AbstractIdentityProvider<SAMLIdentityP
|
|||
return null;
|
||||
} else {
|
||||
try {
|
||||
SAML2LogoutRequestBuilder logoutBuilder = buildLogoutRequest(userSession, uriInfo, realm, singleLogoutServiceUrl);
|
||||
LogoutRequestType logoutRequest = buildLogoutRequest(userSession, uriInfo, realm, singleLogoutServiceUrl);
|
||||
JaxrsSAML2BindingBuilder binding = buildLogoutBinding(session, userSession, realm);
|
||||
if (getConfig().isPostBindingLogout()) {
|
||||
return binding.postBinding(logoutBuilder.buildDocument()).request(singleLogoutServiceUrl);
|
||||
return binding.postBinding(SAML2Request.convert(logoutRequest)).request(singleLogoutServiceUrl);
|
||||
} else {
|
||||
return binding.redirectBinding(logoutBuilder.buildDocument()).request(singleLogoutServiceUrl);
|
||||
return binding.redirectBinding(SAML2Request.convert(logoutRequest)).request(singleLogoutServiceUrl);
|
||||
}
|
||||
} catch (Exception 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()
|
||||
.assertionExpiration(realm.getAccessCodeLifespan())
|
||||
.issuer(getEntityId(uriInfo, realm))
|
||||
.sessionIndex(userSession.getNote(SAMLEndpoint.SAML_FEDERATED_SESSION_INDEX))
|
||||
.nameId(NameIDType.deserializeFromString(userSession.getNote(SAMLEndpoint.SAML_FEDERATED_SUBJECT_NAMEID)))
|
||||
.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) {
|
||||
|
|
|
@ -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<SAMLLoginResponseMapper> processor : mappers) {
|
||||
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;
|
||||
}
|
||||
|
||||
|
@ -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 <samlp:Extensions> 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 <samlp:Extensions> 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<SamlAuthenticationPreprocessor> it = SamlSessionUtils.getSamlAuthenticationPreprocessorIterator(session); it.hasNext();) {
|
||||
logoutRequest = it.next().beforeSendingLogoutRequest(logoutRequest, clientSession.getUserSession(), clientSession);
|
||||
}
|
||||
|
||||
return logoutRequest;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -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<SamlAuthenticationPreprocessor> 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<SamlAuthenticationPreprocessor> 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<SamlAuthenticationPreprocessor> 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) {
|
||||
|
|
|
@ -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<SamlAuthenticationPreprocessor> getSamlAuthenticationPreprocessorIterator(KeycloakSession session) {
|
||||
return session.getKeycloakSessionFactory().getProviderFactories(SamlAuthenticationPreprocessor.class).stream()
|
||||
.filter(Objects::nonNull)
|
||||
.map(SamlAuthenticationPreprocessor.class::cast)
|
||||
.iterator();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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<SamlAuthenticationPreprocessor> it = SamlSessionUtils.getSamlAuthenticationPreprocessorIterator(session); it.hasNext();) {
|
||||
loginResponse = it.next().beforeProcessingLoginResponse(loginResponse, authenticationSession);
|
||||
}
|
||||
}
|
||||
|
||||
session.getContext().setClient(authenticationSession.getClient());
|
||||
|
||||
|
|
|
@ -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
|
||||
org.keycloak.protocol.oidc.ext.OIDCExtSPI
|
||||
org.keycloak.protocol.saml.preprocessor.SamlAuthenticationPreprocessorSpi
|
Loading…
Reference in a new issue