diff --git a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/web/Saml2MetadataFilter.java b/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/web/Saml2MetadataFilter.java index 9e328cb6c3..23714af1e7 100644 --- a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/web/Saml2MetadataFilter.java +++ b/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/web/Saml2MetadataFilter.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * 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. @@ -42,10 +42,14 @@ import org.springframework.web.filter.OncePerRequestFilter; */ public final class Saml2MetadataFilter extends OncePerRequestFilter { + public static final String DEFAULT_METADATA_FILE_NAME = "saml-{registrationId}-metadata.xml"; + private final Converter relyingPartyRegistrationConverter; private final Saml2MetadataResolver saml2MetadataResolver; + private String metadataFilename = DEFAULT_METADATA_FILE_NAME; + private RequestMatcher requestMatcher = new AntPathRequestMatcher( "/saml2/service-provider-metadata/{registrationId}"); @@ -78,8 +82,9 @@ public final class Saml2MetadataFilter extends OncePerRequestFilter { private void writeMetadataToResponse(HttpServletResponse response, String registrationId, String metadata) throws IOException { response.setContentType(MediaType.APPLICATION_XML_VALUE); - response.setHeader(HttpHeaders.CONTENT_DISPOSITION, - "attachment; filename=\"saml-" + registrationId + "-metadata.xml\""); + String fileName = this.metadataFilename.replace("{registrationId}", registrationId); + String format = "attachment; filename=\"%s\""; + response.setHeader(HttpHeaders.CONTENT_DISPOSITION, String.format(format, fileName)); response.setContentLength(metadata.length()); response.getWriter().write(metadata); } @@ -94,4 +99,20 @@ public final class Saml2MetadataFilter extends OncePerRequestFilter { this.requestMatcher = requestMatcher; } + /** + * Sets the metadata filename template containing the {@code {registrationId}} + * template variable. + * + *
+ * The default value is {@code saml-{registrationId}-metadata.xml} + * @param metadataFilename metadata filename, must contain a {registrationId} + * @since 5.5 + */ + public void setMetadataFilename(String metadataFilename) { + Assert.hasText(metadataFilename, "metadataFilename cannot be empty"); + Assert.isTrue(metadataFilename.contains("{registrationId}"), + "metadataFilename must contain a {registrationId} match variable"); + this.metadataFilename = metadataFilename; + } + } diff --git a/saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/provider/service/web/Saml2MetadataFilterTests.java b/saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/provider/service/web/Saml2MetadataFilterTests.java index 12e024a3a1..b411f1925e 100644 --- a/saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/provider/service/web/Saml2MetadataFilterTests.java +++ b/saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/provider/service/web/Saml2MetadataFilterTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * 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. @@ -16,11 +16,15 @@ package org.springframework.security.saml2.provider.service.web; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; + import javax.servlet.FilterChain; import org.junit.Before; import org.junit.Test; +import org.springframework.http.HttpHeaders; import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.mock.web.MockHttpServletResponse; import org.springframework.security.saml2.core.TestSaml2X509Credentials; @@ -31,6 +35,7 @@ import org.springframework.security.saml2.provider.service.registration.TestRely import org.springframework.security.web.util.matcher.AntPathRequestMatcher; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatExceptionOfType; import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.mock; @@ -120,4 +125,43 @@ public class Saml2MetadataFilterTests { assertThatIllegalArgumentException().isThrownBy(() -> this.filter.setRequestMatcher(null)); } + @Test + public void setMetadataFilenameWhenEmptyThenThrowsException() { + assertThatExceptionOfType(IllegalArgumentException.class).isThrownBy(() -> this.filter.setMetadataFilename(" ")) + .withMessage("metadataFilename cannot be empty"); + } + + @Test + public void setMetadataFilenameWhenMissingRegistrationIdVariableThenThrowsException() { + assertThatExceptionOfType(IllegalArgumentException.class) + .isThrownBy(() -> this.filter.setMetadataFilename("metadata-filename.xml")) + .withMessage("metadataFilename must contain a {registrationId} match variable"); + } + + @Test + public void doFilterWhenSetMetadataFilenameThenUses() throws Exception { + String testMetadataFilename = "test-{registrationId}-metadata.xml"; + this.request.setPathInfo("/saml2/service-provider-metadata/validRegistration"); + RelyingPartyRegistration validRegistration = TestRelyingPartyRegistrations.noCredentials() + .assertingPartyDetails((party) -> party.verificationX509Credentials( + (c) -> c.add(TestSaml2X509Credentials.relyingPartyVerifyingCredential()))) + .build(); + String generatedMetadata = "test"; + given(this.resolver.resolve(validRegistration)).willReturn(generatedMetadata); + + this.filter = new Saml2MetadataFilter((request) -> validRegistration, this.resolver); + this.filter.setMetadataFilename(testMetadataFilename); + this.filter.doFilter(this.request, this.response, this.chain); + + verifyNoInteractions(this.chain); + assertThat(this.response.getStatus()).isEqualTo(200); + assertThat(this.response.getContentAsString()).isEqualTo(generatedMetadata); + + String fileName = testMetadataFilename.replace("{registrationId}", validRegistration.getRegistrationId()); + String encodedFileName = URLEncoder.encode(fileName, StandardCharsets.UTF_8.name()); + assertThat(this.response.getHeaderValue(HttpHeaders.CONTENT_DISPOSITION)).asString() + .isEqualTo("attachment; filename=\"%s\"; filename*=UTF-8''%s", fileName, encodedFileName); + verify(this.resolver).resolve(validRegistration); + } + }