Preserve OpenSamlAssertingPartyDetails Instance

Closes gh-12667
This commit is contained in:
Josh Cummings 2023-02-15 17:53:21 -07:00
parent ddad623abf
commit e28ea6dbad
No known key found for this signature in database
GPG Key ID: A306A51F43B8E5A5
6 changed files with 146 additions and 23 deletions

View File

@ -1,5 +1,36 @@
[[servlet-saml2login-metadata]]
= Producing `<saml2:SPSSODescriptor>` Metadata
= Saml 2.0 Metadata
Spring Security can <<parsing-asserting-party-metadata,parse asserting party metadata>> to produce an `AssertingPartyDetails` instance as well as <<publishing-relying-party-metadata,publish relying party metadata>> from a `RelyingPartyRegistration` instance.
[[parsing-asserting-party-metadata]]
== Parsing `<saml2:IDPSSODescriptor>` metadata
You can parse an asserting party's metadata xref:servlet/saml2/login/overview.adoc#servlet-saml2login-relyingpartyregistrationrepository[using `RelyingPartyRegistrations`].
When using the OpenSAML vendor support, the resulting `AssertingPartyDetails` will be of type `OpenSamlAssertingPartyDetails`.
This means you'll be able to do get the underlying OpenSAML XMLObject by doing the following:
====
.Java
[source,java,role="primary"]
----
OpenSamlAssertingPartyDetails details = (OpenSamlAssertingPartyDetails)
registration.getAssertingPartyDetails();
EntityDescriptor openSamlEntityDescriptor = details.getEntityDescriptor();
----
.Kotlin
[source,kotlin,role="secondary"]
----
val details: OpenSamlAssertingPartyDetails =
registration.getAssertingPartyDetails() as OpenSamlAssertingPartyDetails;
val openSamlEntityDescriptor: EntityDescriptor = details.getEntityDescriptor();
----
====
[[publishing-relying-party-metadata]]
== Producing `<saml2:SPSSODescriptor>` Metadata
You can publish a metadata endpoint by adding the `Saml2MetadataFilter` to the filter chain, as you'll see below:

View File

@ -0,0 +1,35 @@
/*
* Copyright 2002-2023 the original author or authors.
*
* 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
*
* https://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.springframework.security.saml2.provider.service.registration;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collection;
class OpenSamlMetadataRelyingPartyRegistrationConverter {
private final OpenSamlMetadataAssertingPartyDetailsConverter converter = new OpenSamlMetadataAssertingPartyDetailsConverter();
Collection<RelyingPartyRegistration.Builder> convert(InputStream source) {
Collection<RelyingPartyRegistration.Builder> builders = new ArrayList<>();
for (RelyingPartyRegistration.AssertingPartyDetails.Builder builder : this.converter.convert(source)) {
builders.add(new RelyingPartyRegistration.Builder(builder));
}
return builders;
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -89,8 +89,7 @@ public class OpenSamlRelyingPartyRegistrationBuilderHttpMessageConverter
@Override
public RelyingPartyRegistration.Builder read(Class<? extends RelyingPartyRegistration.Builder> clazz,
HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException {
return RelyingPartyRegistration
.withAssertingPartyDetails(this.converter.convert(inputMessage.getBody()).iterator().next().build());
return new RelyingPartyRegistration.Builder(this.converter.convert(inputMessage.getBody()).iterator().next());
}
@Override

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -26,6 +26,7 @@ import java.util.function.Consumer;
import org.opensaml.xmlsec.signature.support.SignatureConstants;
import org.springframework.core.convert.converter.Converter;
import org.springframework.security.saml2.core.Saml2X509Credential;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
@ -737,7 +738,7 @@ public final class RelyingPartyRegistration {
public static final class Builder {
private String registrationId;
private Converter<AssertingPartyDetails, String> registrationId = AssertingPartyDetails::getEntityId;
private String entityId = "{baseUrl}/saml2/service-provider-metadata/{registrationId}";
@ -757,10 +758,15 @@ public final class RelyingPartyRegistration {
private String nameIdFormat = null;
private AssertingPartyDetails.Builder assertingPartyDetailsBuilder = new AssertingPartyDetails.Builder();
private AssertingPartyDetails.Builder assertingPartyDetailsBuilder;
private Builder(String registrationId) {
this.registrationId = registrationId;
this.registrationId = (party) -> registrationId;
this.assertingPartyDetailsBuilder = new AssertingPartyDetails.Builder();
}
Builder(AssertingPartyDetails.Builder builder) {
this.assertingPartyDetailsBuilder = builder;
}
/**
@ -769,7 +775,7 @@ public final class RelyingPartyRegistration {
* @return this object
*/
public Builder registrationId(String id) {
this.registrationId = id;
this.registrationId = (party) -> id;
return this;
}
@ -967,11 +973,12 @@ public final class RelyingPartyRegistration {
this.singleLogoutServiceBindings.add(Saml2MessageBinding.POST);
}
return new RelyingPartyRegistration(this.registrationId, this.entityId,
this.assertionConsumerServiceLocation, this.assertionConsumerServiceBinding,
this.singleLogoutServiceLocation, this.singleLogoutServiceResponseLocation,
this.singleLogoutServiceBindings, this.assertingPartyDetailsBuilder.build(), this.nameIdFormat,
this.decryptionX509Credentials, this.signingX509Credentials);
AssertingPartyDetails party = this.assertingPartyDetailsBuilder.build();
String registrationId = this.registrationId.convert(party);
return new RelyingPartyRegistration(registrationId, this.entityId, this.assertionConsumerServiceLocation,
this.assertionConsumerServiceBinding, this.singleLogoutServiceLocation,
this.singleLogoutServiceResponseLocation, this.singleLogoutServiceBindings, party,
this.nameIdFormat, this.decryptionX509Credentials, this.signingX509Credentials);
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -18,13 +18,11 @@ package org.springframework.security.saml2.provider.service.registration;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collection;
import org.springframework.core.io.DefaultResourceLoader;
import org.springframework.core.io.ResourceLoader;
import org.springframework.security.saml2.Saml2Exception;
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration.AssertingPartyDetails;
/**
* A utility class for constructing instances of {@link RelyingPartyRegistration}
@ -36,7 +34,7 @@ import org.springframework.security.saml2.provider.service.registration.RelyingP
*/
public final class RelyingPartyRegistrations {
private static final OpenSamlMetadataAssertingPartyDetailsConverter assertingPartyMetadataConverter = new OpenSamlMetadataAssertingPartyDetailsConverter();
private static final OpenSamlMetadataRelyingPartyRegistrationConverter relyingPartyRegistrationConverter = new OpenSamlMetadataRelyingPartyRegistrationConverter();
private static final ResourceLoader resourceLoader = new DefaultResourceLoader();
@ -215,11 +213,7 @@ public final class RelyingPartyRegistrations {
* @since 5.7
*/
public static Collection<RelyingPartyRegistration.Builder> collectionFromMetadata(InputStream source) {
Collection<RelyingPartyRegistration.Builder> builders = new ArrayList<>();
for (AssertingPartyDetails.Builder builder : assertingPartyMetadataConverter.convert(source)) {
builders.add(RelyingPartyRegistration.withAssertingPartyDetails(builder.build()));
}
return builders;
return relyingPartyRegistrationConverter.convert(source);
}
}

View File

@ -0,0 +1,57 @@
/*
* Copyright 2002-2023 the original author or authors.
*
* 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
*
* https://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.springframework.security.saml2.provider.service.registration;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.util.stream.Collectors;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.core.io.ClassPathResource;
import static org.assertj.core.api.Assertions.assertThat;
public class OpenSamlMetadataRelyingPartyRegistrationConverterTests {
private OpenSamlMetadataRelyingPartyRegistrationConverter converter = new OpenSamlMetadataRelyingPartyRegistrationConverter();
private String metadata;
@BeforeEach
public void setup() throws Exception {
ClassPathResource resource = new ClassPathResource("test-metadata.xml");
try (BufferedReader reader = new BufferedReader(new InputStreamReader(resource.getInputStream()))) {
this.metadata = reader.lines().collect(Collectors.joining());
}
}
// gh-12667
@Test
public void convertWhenDefaultsThenAssertingPartyInstanceOfOpenSaml() throws Exception {
try (InputStream source = new ByteArrayInputStream(this.metadata.getBytes(StandardCharsets.UTF_8))) {
this.converter.convert(source)
.forEach((registration) -> assertThat(registration.build().getAssertingPartyDetails())
.isInstanceOf(OpenSamlAssertingPartyDetails.class));
}
}
}