Display POST and REDIRECT bindings in the SPSSODescriptor for the SAML IDP provider

Closes #39596

Signed-off-by: rmartinc <rmartinc@redhat.com>
This commit is contained in:
rmartinc 2025-05-16 11:40:00 +02:00 committed by Pedro Igor
parent 309957033e
commit b4853de5c6
4 changed files with 80 additions and 30 deletions

View File

@ -19,7 +19,9 @@ package org.keycloak.saml;
import java.net.URI;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.ListIterator;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
@ -45,9 +47,17 @@ import static org.keycloak.saml.common.constants.JBossSAMLURIConstants.PROTOCOL_
public class SPMetadataDescriptor {
public static EntityDescriptorType buildSPDescriptor(URI loginBinding, URI logoutBinding, URI assertionEndpoint, URI logoutEndpoint,
boolean wantAuthnRequestsSigned, boolean wantAssertionsSigned, boolean wantAssertionsEncrypted,
String entityId, String nameIDPolicyFormat, List<KeyDescriptorType> signingCerts, List<KeyDescriptorType> encryptionCerts)
{
boolean wantAuthnRequestsSigned, boolean wantAssertionsSigned, boolean wantAssertionsEncrypted,
String entityId, String nameIDPolicyFormat, List<KeyDescriptorType> signingCerts, List<KeyDescriptorType> encryptionCerts) {
return buildSPDescriptor(Collections.singletonList(new EndpointType(loginBinding, assertionEndpoint)),
Collections.singletonList(new EndpointType(logoutBinding, logoutEndpoint)),
wantAuthnRequestsSigned, wantAssertionsSigned, wantAssertionsEncrypted, entityId, nameIDPolicyFormat, signingCerts, encryptionCerts);
}
public static EntityDescriptorType buildSPDescriptor(List<EndpointType> assertionConsumerServices, List<EndpointType> singleLogoutServices,
boolean wantAuthnRequestsSigned, boolean wantAssertionsSigned, boolean wantAssertionsEncrypted,
String entityId, String nameIDPolicyFormat, List<KeyDescriptorType> signingCerts,
List<KeyDescriptorType> encryptionCerts) {
EntityDescriptorType entityDescriptor = new EntityDescriptorType(entityId);
entityDescriptor.setID(IDGenerator.create("ID_"));
@ -55,7 +65,7 @@ public class SPMetadataDescriptor {
spSSODescriptor.setAuthnRequestsSigned(wantAuthnRequestsSigned);
spSSODescriptor.setWantAssertionsSigned(wantAssertionsSigned);
spSSODescriptor.addNameIDFormat(nameIDPolicyFormat);
spSSODescriptor.addSingleLogoutService(new EndpointType(logoutBinding, logoutEndpoint));
singleLogoutServices.forEach(spSSODescriptor::addSingleLogoutService);
if (wantAuthnRequestsSigned && signingCerts != null) {
for (KeyDescriptorType key: signingCerts) {
@ -69,10 +79,13 @@ public class SPMetadataDescriptor {
}
}
IndexedEndpointType assertionConsumerEndpoint = new IndexedEndpointType(loginBinding, assertionEndpoint);
assertionConsumerEndpoint.setIsDefault(true);
assertionConsumerEndpoint.setIndex(1);
spSSODescriptor.addAssertionConsumerService(assertionConsumerEndpoint);
for (ListIterator<EndpointType> iter = assertionConsumerServices.listIterator(); iter.hasNext(); ) {
EndpointType endpoint = iter.next();
IndexedEndpointType assertionConsumerService = new IndexedEndpointType(endpoint.getBinding(), endpoint.getLocation());
assertionConsumerService.setIndex(iter.nextIndex());
assertionConsumerService.setIsDefault(iter.nextIndex() == 1 ? Boolean.TRUE : null);
spSSODescriptor.addAssertionConsumerService(assertionConsumerService);
}
entityDescriptor.addChoiceType(new EntityDescriptorType.EDTChoiceType(Arrays.asList(new EntityDescriptorType.EDTDescriptorChoiceType(spSSODescriptor))));

View File

@ -35,6 +35,7 @@ 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.AttributeConsumingServiceType;
import org.keycloak.dom.saml.v2.metadata.EndpointType;
import org.keycloak.dom.saml.v2.metadata.EntityDescriptorType;
import org.keycloak.dom.saml.v2.metadata.KeyDescriptorType;
import org.keycloak.dom.saml.v2.metadata.KeyTypes;
@ -357,17 +358,6 @@ public class SAMLIdentityProvider extends AbstractIdentityProvider<SAMLIdentityP
public Response export(UriInfo uriInfo, RealmModel realm, String format) {
try
{
URI authnResponseBinding = JBossSAMLURIConstants.SAML_HTTP_REDIRECT_BINDING.getUri();
if (getConfig().isPostBindingAuthnRequest()) {
authnResponseBinding = JBossSAMLURIConstants.SAML_HTTP_POST_BINDING.getUri();
}
URI logoutBinding = JBossSAMLURIConstants.SAML_HTTP_REDIRECT_BINDING.getUri();
if (getConfig().isPostBindingLogout()) {
logoutBinding = JBossSAMLURIConstants.SAML_HTTP_POST_BINDING.getUri();
}
URI endpoint = uriInfo.getBaseUriBuilder()
.path("realms").path(realm.getName())
.path("broker")
@ -375,6 +365,20 @@ public class SAMLIdentityProvider extends AbstractIdentityProvider<SAMLIdentityP
.path("endpoint")
.build();
List<EndpointType> assertionConsumerServices = getConfig().isPostBindingAuthnRequest()
? List.of(new EndpointType(JBossSAMLURIConstants.SAML_HTTP_POST_BINDING.getUri(), endpoint),
new EndpointType(JBossSAMLURIConstants.SAML_HTTP_REDIRECT_BINDING.getUri(), endpoint),
new EndpointType(JBossSAMLURIConstants.SAML_HTTP_ARTIFACT_BINDING.getUri(), endpoint))
: List.of(new EndpointType(JBossSAMLURIConstants.SAML_HTTP_REDIRECT_BINDING.getUri(), endpoint),
new EndpointType(JBossSAMLURIConstants.SAML_HTTP_POST_BINDING.getUri(), endpoint),
new EndpointType(JBossSAMLURIConstants.SAML_HTTP_ARTIFACT_BINDING.getUri(), endpoint));
List<EndpointType> singleLogoutServices = getConfig().isPostBindingLogout()
? List.of(new EndpointType(JBossSAMLURIConstants.SAML_HTTP_POST_BINDING.getUri(), endpoint),
new EndpointType(JBossSAMLURIConstants.SAML_HTTP_REDIRECT_BINDING.getUri(), endpoint))
: List.of(new EndpointType(JBossSAMLURIConstants.SAML_HTTP_REDIRECT_BINDING.getUri(), endpoint),
new EndpointType(JBossSAMLURIConstants.SAML_HTTP_POST_BINDING.getUri(), endpoint));
boolean wantAuthnRequestsSigned = getConfig().isWantAuthnRequestsSigned();
boolean wantAssertionsSigned = getConfig().isWantAssertionsSigned();
boolean wantAssertionsEncrypted = getConfig().isWantAssertionsEncrypted();
@ -419,7 +423,7 @@ public class SAMLIdentityProvider extends AbstractIdentityProvider<SAMLIdentityP
.collect(Collectors.toList());
EntityDescriptorType entityDescriptor = SPMetadataDescriptor.buildSPDescriptor(
authnResponseBinding, logoutBinding, endpoint, endpoint,
assertionConsumerServices, singleLogoutServices,
wantAuthnRequestsSigned, wantAssertionsSigned, wantAssertionsEncrypted,
entityId, nameIDPolicyFormat, signingKeys, encryptionKeys);

View File

@ -104,6 +104,7 @@ import org.keycloak.testsuite.broker.oidc.OverwrittenMappersTestIdentityProvider
import org.keycloak.testsuite.updaters.RealmAttributeUpdater;
import org.keycloak.testsuite.util.AdminEventPaths;
import org.keycloak.testsuite.util.KeyUtils;
import org.keycloak.testsuite.util.oauth.OAuthClient;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
@ -1190,27 +1191,41 @@ public class IdentityProviderTest extends AbstractAdminTest {
Assert.assertEquals("ProtocolSupportEnumeration", expected, actual);
Assert.assertNotNull("AssertionConsumerService not null", desc.getAssertionConsumerService());
Assert.assertEquals("AssertionConsumerService.size", 1, desc.getAssertionConsumerService().size());
Assert.assertEquals("AssertionConsumerService.size", 3, desc.getAssertionConsumerService().size());
IndexedEndpointType endpoint = desc.getAssertionConsumerService().get(0);
final URI samlUri = new URI(OAuthClient.AUTH_SERVER_ROOT + "/realms/admin-client-test/broker/saml/endpoint");
Assert.assertEquals("AssertionConsumerService.Location",
new URI(oauth.AUTH_SERVER_ROOT + "/realms/admin-client-test/broker/saml/endpoint"), endpoint.getLocation());
Assert.assertEquals("AssertionConsumerService.Location", samlUri, endpoint.getLocation());
Assert.assertEquals("AssertionConsumerService.Binding",
postBindingResponse ? JBossSAMLURIConstants.SAML_HTTP_POST_BINDING.getUri() : JBossSAMLURIConstants.SAML_HTTP_REDIRECT_BINDING.getUri(),
endpoint.getBinding());
Assert.assertTrue("AssertionConsumerService.isDefault", endpoint.isIsDefault());
endpoint = desc.getAssertionConsumerService().get(1);
Assert.assertEquals("AssertionConsumerService.Location", samlUri, endpoint.getLocation());
Assert.assertEquals("AssertionConsumerService.Binding",
postBindingResponse ? JBossSAMLURIConstants.SAML_HTTP_REDIRECT_BINDING.getUri() : JBossSAMLURIConstants.SAML_HTTP_POST_BINDING.getUri(),
endpoint.getBinding());
endpoint = desc.getAssertionConsumerService().get(2);
Assert.assertEquals("AssertionConsumerService.Location", samlUri, endpoint.getLocation());
Assert.assertEquals("AssertionConsumerService.Binding", JBossSAMLURIConstants.SAML_HTTP_ARTIFACT_BINDING.getUri(), endpoint.getBinding());
Assert.assertNotNull("SingleLogoutService not null", desc.getSingleLogoutService());
Assert.assertEquals("SingleLogoutService.size", 1, desc.getSingleLogoutService().size());
Assert.assertEquals("SingleLogoutService.size", 2, desc.getSingleLogoutService().size());
EndpointType sloEndpoint = desc.getSingleLogoutService().get(0);
Assert.assertEquals("SingleLogoutService.Location",
new URI(oauth.AUTH_SERVER_ROOT + "/realms/admin-client-test/broker/saml/endpoint"), sloEndpoint.getLocation());
Assert.assertEquals("SingleLogoutService.Binding",
new URI("urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"), sloEndpoint.getBinding());
Assert.assertEquals("SingleLogoutService.Location", samlUri, sloEndpoint.getLocation());
Assert.assertEquals("SingleLogoutService.Binding", JBossSAMLURIConstants.SAML_HTTP_POST_BINDING.getUri(), sloEndpoint.getBinding());
sloEndpoint = desc.getSingleLogoutService().get(1);
Assert.assertEquals("SingleLogoutService.Location", samlUri, sloEndpoint.getLocation());
Assert.assertEquals("SingleLogoutService.Binding", JBossSAMLURIConstants.SAML_HTTP_REDIRECT_BINDING.getUri(), sloEndpoint.getBinding());
Assert.assertNotNull("KeyDescriptor not null", desc.getKeyDescriptor());
Assert.assertEquals("KeyDescriptor.size", 1, desc.getKeyDescriptor().size());

View File

@ -396,11 +396,19 @@ public class KcSamlSpDescriptorTest extends AbstractBrokerTest {
EntityDescriptorType o = (EntityDescriptorType) parser.parse(new StringInputStream(spDescriptorString));
SPSSODescriptorType spDescriptor = o.getChoiceType().get(0).getDescriptors().get(0).getSpDescriptor();
assertThat(spDescriptor.getSingleLogoutService().size(), is(2));
assertThat(spDescriptor.getSingleLogoutService().get(0).getBinding().toString(),
is(JBossSAMLURIConstants.SAML_HTTP_POST_BINDING.get()));
assertThat(spDescriptor.getSingleLogoutService().get(1).getBinding().toString(),
is(JBossSAMLURIConstants.SAML_HTTP_REDIRECT_BINDING.get()));
assertThat(spDescriptor.getAssertionConsumerService().size(), is(3));
assertThat(spDescriptor.getAssertionConsumerService().get(0).getBinding().toString(),
is(JBossSAMLURIConstants.SAML_HTTP_POST_BINDING.get()));
assertThat(spDescriptor.getAssertionConsumerService().get(1).getBinding().toString(),
is(JBossSAMLURIConstants.SAML_HTTP_REDIRECT_BINDING.get()));
assertThat(spDescriptor.getAssertionConsumerService().get(2).getBinding().toString(),
is(JBossSAMLURIConstants.SAML_HTTP_ARTIFACT_BINDING.get()));
}
}
@ -419,11 +427,19 @@ public class KcSamlSpDescriptorTest extends AbstractBrokerTest {
EntityDescriptorType o = (EntityDescriptorType) parser.parse(new StringInputStream(spDescriptorString));
SPSSODescriptorType spDescriptor = o.getChoiceType().get(0).getDescriptors().get(0).getSpDescriptor();
assertThat(spDescriptor.getSingleLogoutService().size(), is(2));
assertThat(spDescriptor.getSingleLogoutService().get(0).getBinding().toString(),
is(JBossSAMLURIConstants.SAML_HTTP_REDIRECT_BINDING.get()));
assertThat(spDescriptor.getSingleLogoutService().get(1).getBinding().toString(),
is(JBossSAMLURIConstants.SAML_HTTP_POST_BINDING.get()));
assertThat(spDescriptor.getAssertionConsumerService().size(), is(3));
assertThat(spDescriptor.getAssertionConsumerService().get(0).getBinding().toString(),
is(JBossSAMLURIConstants.SAML_HTTP_REDIRECT_BINDING.get()));
assertThat(spDescriptor.getAssertionConsumerService().get(1).getBinding().toString(),
is(JBossSAMLURIConstants.SAML_HTTP_POST_BINDING.get()));
assertThat(spDescriptor.getAssertionConsumerService().get(2).getBinding().toString(),
is(JBossSAMLURIConstants.SAML_HTTP_ARTIFACT_BINDING.get()));
}
}
@ -437,6 +453,8 @@ public class KcSamlSpDescriptorTest extends AbstractBrokerTest {
EntityDescriptorType o = (EntityDescriptorType) parser.parse(new StringInputStream(spDescriptorString));
SPSSODescriptorType spDescriptor = o.getChoiceType().get(0).getDescriptors().get(0).getSpDescriptor();
assertThat(spDescriptor.getSingleLogoutService().size(), is(2));
assertThat(spDescriptor.getAssertionConsumerService().size(), is(3));
assertThat(spDescriptor.getSingleLogoutService().get(0).getBinding().toString(),
is(spDescriptor.getAssertionConsumerService().get(0).getBinding().toString()));