Allow RelyingPartyRegistration Placeholder Resolution in XML

Closes gh-14645
This commit is contained in:
Josh Cummings 2024-10-25 16:11:39 -06:00
parent 689a4141df
commit 27294b2e11
No known key found for this signature in database
GPG Key ID: A306A51F43B8E5A5
3 changed files with 79 additions and 11 deletions

View File

@ -213,8 +213,10 @@ public final class RelyingPartyRegistrationsBeanDefinitionParser implements Bean
private static RelyingPartyRegistration.Builder getBuilderFromMetadataLocationIfPossible(
Element relyingPartyRegistrationElt, Map<String, Map<String, Object>> assertingParties,
ParserContext parserContext) {
String registrationId = relyingPartyRegistrationElt.getAttribute(ATT_REGISTRATION_ID);
String metadataLocation = relyingPartyRegistrationElt.getAttribute(ATT_METADATA_LOCATION);
String registrationId = resolveAttribute(parserContext,
relyingPartyRegistrationElt.getAttribute(ATT_REGISTRATION_ID));
String metadataLocation = resolveAttribute(parserContext,
relyingPartyRegistrationElt.getAttribute(ATT_METADATA_LOCATION));
RelyingPartyRegistration.Builder builder;
if (StringUtils.hasText(metadataLocation)) {
builder = RelyingPartyRegistrations.fromMetadataLocation(metadataLocation).registrationId(registrationId);
@ -224,20 +226,20 @@ public final class RelyingPartyRegistrationsBeanDefinitionParser implements Bean
.assertingPartyMetadata((apBuilder) -> buildAssertingParty(relyingPartyRegistrationElt,
assertingParties, apBuilder, parserContext));
}
addRemainingProperties(relyingPartyRegistrationElt, builder);
addRemainingProperties(parserContext, relyingPartyRegistrationElt, builder);
return builder;
}
private static void addRemainingProperties(Element relyingPartyRegistrationElt,
private static void addRemainingProperties(ParserContext pc, Element relyingPartyRegistrationElt,
RelyingPartyRegistration.Builder builder) {
String entityId = relyingPartyRegistrationElt.getAttribute(ATT_ENTITY_ID);
String singleLogoutServiceLocation = relyingPartyRegistrationElt
.getAttribute(ATT_SINGLE_LOGOUT_SERVICE_LOCATION);
String singleLogoutServiceResponseLocation = relyingPartyRegistrationElt
.getAttribute(ATT_SINGLE_LOGOUT_SERVICE_RESPONSE_LOCATION);
String entityId = resolveAttribute(pc, relyingPartyRegistrationElt.getAttribute(ATT_ENTITY_ID));
String singleLogoutServiceLocation = resolveAttribute(pc,
relyingPartyRegistrationElt.getAttribute(ATT_SINGLE_LOGOUT_SERVICE_LOCATION));
String singleLogoutServiceResponseLocation = resolveAttribute(pc,
relyingPartyRegistrationElt.getAttribute(ATT_SINGLE_LOGOUT_SERVICE_RESPONSE_LOCATION));
Saml2MessageBinding singleLogoutServiceBinding = getSingleLogoutServiceBinding(relyingPartyRegistrationElt);
String assertionConsumerServiceLocation = relyingPartyRegistrationElt
.getAttribute(ATT_ASSERTION_CONSUMER_SERVICE_LOCATION);
String assertionConsumerServiceLocation = resolveAttribute(pc,
relyingPartyRegistrationElt.getAttribute(ATT_ASSERTION_CONSUMER_SERVICE_LOCATION));
Saml2MessageBinding assertionConsumerServiceBinding = getAssertionConsumerServiceBinding(
relyingPartyRegistrationElt);
if (StringUtils.hasText(entityId)) {
@ -400,4 +402,8 @@ public final class RelyingPartyRegistrationsBeanDefinitionParser implements Bean
}
}
private static String resolveAttribute(ParserContext pc, String value) {
return pc.getReaderContext().getEnvironment().resolvePlaceholders(value);
}
}

View File

@ -35,6 +35,7 @@ import org.springframework.security.saml2.provider.service.registration.InMemory
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration;
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistrationRepository;
import org.springframework.security.saml2.provider.service.registration.Saml2MessageBinding;
import org.springframework.security.saml2.provider.service.registration.TestRelyingPartyRegistrations;
import org.springframework.security.saml2.provider.service.web.authentication.OpenSaml4AuthenticationRequestResolver;
import static org.assertj.core.api.Assertions.assertThat;
@ -288,6 +289,32 @@ public class RelyingPartyRegistrationsBeanDefinitionParserTests {
verify(relayStateResolver).convert(request);
}
@Test
public void parseWhenPlaceholdersThenResolves() throws Exception {
RelyingPartyRegistration sample = TestRelyingPartyRegistrations.relyingPartyRegistration().build();
System.setProperty("registration-id", sample.getRegistrationId());
System.setProperty("entity-id", sample.getEntityId());
System.setProperty("acs-location", sample.getAssertionConsumerServiceLocation());
System.setProperty("slo-location", sample.getSingleLogoutServiceLocation());
System.setProperty("slo-response-location", sample.getSingleLogoutServiceResponseLocation());
try (MockWebServer web = new MockWebServer()) {
web.start();
String serverUrl = web.url("/metadata").toString();
web.enqueue(xmlResponse(METADATA_RESPONSE));
System.setProperty("metadata-location", serverUrl);
this.spring.configLocations(xml("PlaceholderRegistration")).autowire();
}
RelyingPartyRegistration registration = this.relyingPartyRegistrationRepository
.findByRegistrationId(sample.getRegistrationId());
assertThat(registration.getRegistrationId()).isEqualTo(sample.getRegistrationId());
assertThat(registration.getEntityId()).isEqualTo(sample.getEntityId());
assertThat(registration.getAssertionConsumerServiceLocation())
.isEqualTo(sample.getAssertionConsumerServiceLocation());
assertThat(registration.getSingleLogoutServiceLocation()).isEqualTo(sample.getSingleLogoutServiceLocation());
assertThat(registration.getSingleLogoutServiceResponseLocation())
.isEqualTo(sample.getSingleLogoutServiceResponseLocation());
}
private static MockResponse xmlResponse(String xml) {
return new MockResponse().setHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_XML_VALUE).setBody(xml);
}

View File

@ -0,0 +1,35 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ Copyright 2002-2021 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.
-->
<b:beans xmlns:b="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.springframework.org/schema/security"
xsi:schemaLocation="
http://www.springframework.org/schema/security
https://www.springframework.org/schema/security/spring-security.xsd
http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">
<relying-party-registrations>
<relying-party-registration registration-id="${registration-id}"
entity-id="${entity-id}"
metadata-location="${metadata-location}"
assertion-consumer-service-location="${acs-location}"
single-logout-service-location="${slo-location}"
single-logout-service-response-location="${slo-response-location}"/>
</relying-party-registrations>
</b:beans>