From ca829cebd38b735f5b7a111aad4f4db63bbb1faa Mon Sep 17 00:00:00 2001 From: OZGCloud <ozgcloud@mgm-tp.com> Date: Fri, 15 Mar 2024 14:51:19 +0100 Subject: [PATCH] OZG-4383 Profil umbenannt, damit es zum bestehenden passt. Properties inbestehenden Classe verschoben --- .../antragraum/AntragraumProperties.java | 22 ++++++ .../antragraum/BayernIdProperties.java | 42 ----------- .../antragraum/BayernIdSamlConfiguration.java | 66 +++++++++++++----- .../nachrichten/antragraum/Saml2Parser.java | 69 +++++-------------- src/main/resources/application-bayern.yaml | 8 +++ src/main/resources/application-bayernId.yaml | 8 --- .../BayernIdSamlConfigurationTest.java | 15 +++- .../antragraum/Saml2DecrypterTest.java | 9 ++- .../antragraum/Saml2ParserTest.java | 18 +++-- .../antragraum/Saml2VerifierTest.java | 10 ++- src/test/resources/application-bayern.yaml | 9 +++ src/test/resources/application-bayernId.yaml | 9 --- 12 files changed, 142 insertions(+), 143 deletions(-) delete mode 100644 src/main/java/de/ozgcloud/nachrichten/antragraum/BayernIdProperties.java create mode 100644 src/main/resources/application-bayern.yaml delete mode 100644 src/main/resources/application-bayernId.yaml create mode 100644 src/test/resources/application-bayern.yaml delete mode 100644 src/test/resources/application-bayernId.yaml diff --git a/src/main/java/de/ozgcloud/nachrichten/antragraum/AntragraumProperties.java b/src/main/java/de/ozgcloud/nachrichten/antragraum/AntragraumProperties.java index 860818f..9d2b8a0 100644 --- a/src/main/java/de/ozgcloud/nachrichten/antragraum/AntragraumProperties.java +++ b/src/main/java/de/ozgcloud/nachrichten/antragraum/AntragraumProperties.java @@ -5,6 +5,7 @@ import jakarta.validation.constraints.NotEmpty; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.context.annotation.Configuration; +import org.springframework.core.io.Resource; import org.springframework.validation.annotation.Validated; import lombok.Getter; @@ -23,4 +24,25 @@ public class AntragraumProperties { @NotEmpty private String url; + /** + * The entityId as defined the BayernId SAML Metadata + */ + @NotEmpty + private String entityId; + /** + * The uri where to load the idp Metadata from + */ + @NotEmpty + private Resource metadataUri; + /** + * The location of the private key for decrypting the saml token data + */ + @NotEmpty + private Resource decryptionPrivateKey; + /** + * The location of the certificate for decrypting the saml token data + */ + @NotEmpty + private Resource decryptionCertificate; + } diff --git a/src/main/java/de/ozgcloud/nachrichten/antragraum/BayernIdProperties.java b/src/main/java/de/ozgcloud/nachrichten/antragraum/BayernIdProperties.java deleted file mode 100644 index fca321c..0000000 --- a/src/main/java/de/ozgcloud/nachrichten/antragraum/BayernIdProperties.java +++ /dev/null @@ -1,42 +0,0 @@ -package de.ozgcloud.nachrichten.antragraum; - -import jakarta.validation.constraints.NotEmpty; - -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; -import org.springframework.boot.context.properties.ConfigurationProperties; -import org.springframework.context.annotation.Configuration; -import org.springframework.core.io.Resource; -import org.springframework.validation.annotation.Validated; - -import lombok.Getter; -import lombok.Setter; - -@Getter -@Setter -@Configuration -@ConditionalOnProperty(AntragraumProperties.PROPERTY_ANTRAGSRAUM_URL) -@ConfigurationProperties(prefix = "ozgcloud.bayernid.saml") -@Validated -public class BayernIdProperties { - /** - * The entityId as defined the BayernId SAML Metadata - */ - @NotEmpty - private String entityId; - /** - * The uri where to load the idp Metadata from - */ - @NotEmpty - private Resource metadataUri; - /** - * The location of the private key for decrypting the saml token data - */ - @NotEmpty - private Resource decryptionPrivateKey; - /** - * The location of the cretificate for decrypting the saml token data - */ - @NotEmpty - private Resource decryptionCertificate; - -} diff --git a/src/main/java/de/ozgcloud/nachrichten/antragraum/BayernIdSamlConfiguration.java b/src/main/java/de/ozgcloud/nachrichten/antragraum/BayernIdSamlConfiguration.java index 10382df..b3159af 100644 --- a/src/main/java/de/ozgcloud/nachrichten/antragraum/BayernIdSamlConfiguration.java +++ b/src/main/java/de/ozgcloud/nachrichten/antragraum/BayernIdSamlConfiguration.java @@ -20,8 +20,6 @@ package de.ozgcloud.nachrichten.antragraum; -import static de.ozgcloud.nachrichten.antragraum.Saml2Parser.*; - import java.io.IOException; import java.io.InputStream; import java.security.cert.CertificateException; @@ -30,8 +28,10 @@ import java.security.cert.X509Certificate; import java.security.interfaces.RSAPrivateKey; import java.util.ArrayList; import java.util.Collection; +import java.util.HashMap; import java.util.HashSet; import java.util.List; +import java.util.Map; import java.util.Set; import jakarta.annotation.PostConstruct; @@ -59,37 +59,73 @@ import org.opensaml.xmlsec.config.impl.DefaultSecurityConfigurationBootstrap; import org.opensaml.xmlsec.keyinfo.KeyInfoSupport; import org.opensaml.xmlsec.signature.support.SignatureTrustEngine; import org.opensaml.xmlsec.signature.support.impl.ExplicitKeySignatureTrustEngine; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Configuration; import org.springframework.core.io.Resource; import org.springframework.security.converter.RsaKeyConverters; import org.springframework.security.saml2.Saml2Exception; import org.springframework.security.saml2.core.Saml2X509Credential; -import org.springframework.util.Assert; -import lombok.RequiredArgsConstructor; +import lombok.Getter; import net.shibboleth.utilities.java.support.component.ComponentInitializationException; import net.shibboleth.utilities.java.support.resolver.CriteriaSet; +import net.shibboleth.utilities.java.support.xml.BasicParserPool; +import net.shibboleth.utilities.java.support.xml.ParserPool; import net.shibboleth.utilities.java.support.xml.XMLParserException; -@RequiredArgsConstructor @Configuration public class BayernIdSamlConfiguration { - private final BayernIdProperties bayernIdProperties; private XMLObjectProviderRegistry registry; + @Getter + private ParserPool parserPool; + @Autowired + private AntragraumProperties antragraumProperties; @PostConstruct void initOpenSAML() { try { registry = new XMLObjectProviderRegistry(); ConfigurationService.register(XMLObjectProviderRegistry.class, registry); + parserPool = initParserPool(); - registry.setParserPool(getParserPool()); + registry.setParserPool(parserPool); InitializationService.initialize(); } catch (Exception e) { throw new RuntimeException("Initialization failed"); } } + private ParserPool initParserPool() throws ComponentInitializationException { + var localParserPool = new BasicParserPool(); + localParserPool.setMaxPoolSize(100); + localParserPool.setCoalescing(true); + localParserPool.setIgnoreComments(true); + localParserPool.setIgnoreElementContentWhitespace(true); + localParserPool.setNamespaceAware(true); + localParserPool.setExpandEntityReferences(false); + localParserPool.setXincludeAware(false); + + final var features = createFeatureMap(); + + localParserPool.setBuilderFeatures(features); + + localParserPool.setBuilderAttributes(new HashMap<>()); + + localParserPool.initialize(); + + return localParserPool; + } + + private Map<String, Boolean> createFeatureMap() { + final Map<String, Boolean> features = new HashMap<>(); + features.put("http://xml.org/sax/features/external-general-entities", Boolean.FALSE); + features.put("http://xml.org/sax/features/external-parameter-entities", Boolean.FALSE); + features.put("http://apache.org/xml/features/disallow-doctype-decl", Boolean.TRUE); + features.put("http://apache.org/xml/features/validation/schema/normalized-value", Boolean.FALSE); + features.put("http://javax.xml.XMLConstants/feature/secure-processing", Boolean.TRUE); + return features; + } + SignatureTrustEngine getTrustEngine() { Set<Credential> credentials = new HashSet<>(); Collection<Saml2X509Credential> keys = getCertificatesFromMetadata(); @@ -97,7 +133,7 @@ public class BayernIdSamlConfiguration { for (Saml2X509Credential key : keys) { var cred = new BasicX509Credential(key.getCertificate()); cred.setUsageType(UsageType.SIGNING); - cred.setEntityId(bayernIdProperties.getEntityId()); + cred.setEntityId(antragraumProperties.getEntityId()); credentials.add(cred); } @@ -108,22 +144,19 @@ public class BayernIdSamlConfiguration { CriteriaSet getVerificationCriteria() { var criteria = new CriteriaSet(); - criteria.add(new EvaluableEntityIDCredentialCriterion(new EntityIdCriterion(bayernIdProperties.getEntityId()))); + criteria.add(new EvaluableEntityIDCredentialCriterion(new EntityIdCriterion(antragraumProperties.getEntityId()))); criteria.add(new EvaluableProtocolRoleDescriptorCriterion(new ProtocolCriterion("urn:oasis:names:tc:SAML:2.0:protocol"))); criteria.add(new EvaluableUsageCredentialCriterion(new UsageCriterion(UsageType.SIGNING))); return criteria; } Saml2X509Credential getDecryptionCredential() { - var privateKey = readPrivateKey(bayernIdProperties.getDecryptionPrivateKey()); - var certificate = readCertificateFromResource(bayernIdProperties.getDecryptionCertificate()); + var privateKey = readPrivateKey(antragraumProperties.getDecryptionPrivateKey()); + var certificate = readCertificateFromResource(antragraumProperties.getDecryptionCertificate()); return new Saml2X509Credential(privateKey, certificate, Saml2X509Credential.Saml2X509CredentialType.DECRYPTION); } private RSAPrivateKey readPrivateKey(Resource location) { - Assert.state(location != null, "No private key location specified"); - Assert.state(location.exists(), () -> "Private key location '" + location + "' does not exist"); - try (var inputStream = location.getInputStream()) { return RsaKeyConverters.pkcs8().convert(inputStream); } catch (IOException e) { @@ -132,9 +165,6 @@ public class BayernIdSamlConfiguration { } private X509Certificate readCertificateFromResource(Resource location) { - Assert.state(location != null, "No certificate location specified"); - Assert.state(location.exists(), () -> "Certificate location '" + location + "' does not exist"); - try (var inputStream = location.getInputStream()) { return (X509Certificate) CertificateFactory.getInstance("X.509").generateCertificate(inputStream); @@ -144,7 +174,7 @@ public class BayernIdSamlConfiguration { } List<Saml2X509Credential> getCertificatesFromMetadata() { - try (var metadata = bayernIdProperties.getMetadataUri().getInputStream()) { + try (var metadata = antragraumProperties.getMetadataUri().getInputStream()) { var xmlObject = xmlObject(metadata); if (xmlObject instanceof EntitiesDescriptor descriptors) { for (EntityDescriptor descriptor : descriptors.getEntityDescriptors()) { diff --git a/src/main/java/de/ozgcloud/nachrichten/antragraum/Saml2Parser.java b/src/main/java/de/ozgcloud/nachrichten/antragraum/Saml2Parser.java index 8c0a82f..e134f7e 100644 --- a/src/main/java/de/ozgcloud/nachrichten/antragraum/Saml2Parser.java +++ b/src/main/java/de/ozgcloud/nachrichten/antragraum/Saml2Parser.java @@ -23,8 +23,7 @@ package de.ozgcloud.nachrichten.antragraum; import java.io.ByteArrayInputStream; import java.io.InputStream; import java.nio.charset.StandardCharsets; -import java.util.HashMap; -import java.util.Map; +import java.util.Objects; import org.opensaml.core.xml.XMLObject; import org.opensaml.core.xml.config.XMLObjectProviderRegistrySupport; @@ -34,72 +33,36 @@ import org.opensaml.saml.saml2.core.impl.ResponseUnmarshaller; import org.springframework.security.saml2.Saml2Exception; import org.springframework.stereotype.Service; -import net.shibboleth.utilities.java.support.component.ComponentInitializationException; -import net.shibboleth.utilities.java.support.xml.BasicParserPool; -import net.shibboleth.utilities.java.support.xml.ParserPool; +import lombok.RequiredArgsConstructor; import net.shibboleth.utilities.java.support.xml.XMLParserException; @Service +@RequiredArgsConstructor class Saml2Parser { - private ParserPool parserPool; + private final BayernIdSamlConfiguration configuration; private ResponseUnmarshaller unmarshaller; - Saml2Parser() { - init(); - } - - void init() { - try { - this.parserPool = getParserPool(); - unmarshaller = (ResponseUnmarshaller) XMLObjectProviderRegistrySupport.getUnmarshallerFactory() - .getUnmarshaller(Response.DEFAULT_ELEMENT_NAME); - } catch (Exception e) { - throw new RuntimeException(e); - } - } - - static ParserPool getParserPool() throws ComponentInitializationException { - var parserPool = new BasicParserPool(); - parserPool.setMaxPoolSize(100); - parserPool.setCoalescing(true); - parserPool.setIgnoreComments(true); - parserPool.setIgnoreElementContentWhitespace(true); - parserPool.setNamespaceAware(true); - parserPool.setExpandEntityReferences(false); - parserPool.setXincludeAware(false); - - final var features = createFeatureMap(); - - parserPool.setBuilderFeatures(features); - - parserPool.setBuilderAttributes(new HashMap<>()); - - parserPool.initialize(); - - return parserPool; - } - - private static Map<String, Boolean> createFeatureMap() { - final Map<String, Boolean> features = new HashMap<>(); - features.put("http://xml.org/sax/features/external-general-entities", Boolean.FALSE); - features.put("http://xml.org/sax/features/external-parameter-entities", Boolean.FALSE); - features.put("http://apache.org/xml/features/disallow-doctype-decl", Boolean.TRUE); - features.put("http://apache.org/xml/features/validation/schema/normalized-value", Boolean.FALSE); - features.put("http://javax.xml.XMLConstants/feature/secure-processing", Boolean.TRUE); - return features; - } - Response parse(String request) { return (Response) xmlObject(new ByteArrayInputStream(request.getBytes(StandardCharsets.UTF_8))); } XMLObject xmlObject(InputStream inputStream) throws Saml2Exception { try { - var document = parserPool.parse(inputStream); + var document = configuration.getParserPool().parse(inputStream); var element = document.getDocumentElement(); - return unmarshaller.unmarshall(element); + return getUnmarshaller().unmarshall(element); } catch (XMLParserException | UnmarshallingException e) { throw new Saml2Exception("Failed to deserialize LogoutRequest", e); } } + + private ResponseUnmarshaller getUnmarshaller() { + if (Objects.nonNull(unmarshaller)) { + return unmarshaller; + } + + unmarshaller = (ResponseUnmarshaller) XMLObjectProviderRegistrySupport.getUnmarshallerFactory() + .getUnmarshaller(Response.DEFAULT_ELEMENT_NAME); + return unmarshaller; + } } diff --git a/src/main/resources/application-bayern.yaml b/src/main/resources/application-bayern.yaml new file mode 100644 index 0000000..d5f7dec --- /dev/null +++ b/src/main/resources/application-bayern.yaml @@ -0,0 +1,8 @@ +ozgcloud: + antragraum: + bayernid: + saml: + entityId: https://antragsraum.ozgcloud.de/ + metadataUri: "classpath:/bayernid/metadata/bayernid-idp-infra.xml" + decryptionPrivateKey: "classpath:/bayernid/bayernid-test-enc.key" + decryptionCertificate: "classpath:/bayernid/bayernid-test-enc.crt" \ No newline at end of file diff --git a/src/main/resources/application-bayernId.yaml b/src/main/resources/application-bayernId.yaml deleted file mode 100644 index 8c90a64..0000000 --- a/src/main/resources/application-bayernId.yaml +++ /dev/null @@ -1,8 +0,0 @@ -ozgcloud: - bayernid: - saml: - entity-id: https://antragsraum.ozgcloud.de/ - metadata-uri: "classpath:/bayernid/metadata/bayernid-idp-infra.xml" - decryption: - private-key-location: "classpath:/bayernid/bayernid-test-enc.key" - certificate-location: "classpath:/bayernid/bayernid-test-enc.crt" \ No newline at end of file diff --git a/src/test/java/de/ozgcloud/nachrichten/antragraum/BayernIdSamlConfigurationTest.java b/src/test/java/de/ozgcloud/nachrichten/antragraum/BayernIdSamlConfigurationTest.java index efe6f24..045f5ca 100644 --- a/src/test/java/de/ozgcloud/nachrichten/antragraum/BayernIdSamlConfigurationTest.java +++ b/src/test/java/de/ozgcloud/nachrichten/antragraum/BayernIdSamlConfigurationTest.java @@ -34,10 +34,23 @@ import de.ozgcloud.common.test.TestUtils; class BayernIdSamlConfigurationTest { @Mock - private BayernIdProperties properties; + private AntragraumProperties properties; @InjectMocks private BayernIdSamlConfiguration bayernIdSamlConfiguration; + @Nested + class TestCreationOfParserPool { + @BeforeEach + void setup() throws Exception { + bayernIdSamlConfiguration.initOpenSAML(); + } + + @Test + void shouldCreateParserPool() { + assertThat(bayernIdSamlConfiguration.getParserPool()).isNotNull(); + } + } + @Nested class TestCreatingTrustEngine { @BeforeEach diff --git a/src/test/java/de/ozgcloud/nachrichten/antragraum/Saml2DecrypterTest.java b/src/test/java/de/ozgcloud/nachrichten/antragraum/Saml2DecrypterTest.java index 5c5d511..6e688c6 100644 --- a/src/test/java/de/ozgcloud/nachrichten/antragraum/Saml2DecrypterTest.java +++ b/src/test/java/de/ozgcloud/nachrichten/antragraum/Saml2DecrypterTest.java @@ -30,6 +30,7 @@ import org.opensaml.saml.saml2.core.Attribute; import org.opensaml.saml.saml2.core.AttributeStatement; import org.opensaml.saml.saml2.core.Response; import org.springframework.core.io.InputStreamResource; +import org.springframework.test.util.ReflectionTestUtils; import de.ozgcloud.common.test.TestUtils; @@ -37,7 +38,7 @@ class Saml2DecrypterTest { private Response samlResponse; @Mock - private BayernIdProperties properties; + private AntragraumProperties properties; private BayernIdSamlConfiguration configuration; @@ -48,11 +49,13 @@ class Saml2DecrypterTest { when(properties.getDecryptionCertificate()).thenReturn(new InputStreamResource(TestUtils.loadFile("bayernid-test-enc.crt"))); when(properties.getDecryptionPrivateKey()).thenReturn(new InputStreamResource(TestUtils.loadFile("bayernid-test-enc.key"))); - configuration = new BayernIdSamlConfiguration(properties); + configuration = new BayernIdSamlConfiguration(); + ReflectionTestUtils.setField(configuration, "antragraumProperties", properties); configuration.initOpenSAML(); var samlResponseString = TestUtils.loadTextFile("SamlResponse.xml"); - samlResponse = new Saml2Parser().parse(samlResponseString); + var parser = new Saml2Parser(configuration); + samlResponse = parser.parse(samlResponseString); decrypter = new Saml2Decrypter(configuration); decrypter.init(); diff --git a/src/test/java/de/ozgcloud/nachrichten/antragraum/Saml2ParserTest.java b/src/test/java/de/ozgcloud/nachrichten/antragraum/Saml2ParserTest.java index c9efc05..afcca2f 100644 --- a/src/test/java/de/ozgcloud/nachrichten/antragraum/Saml2ParserTest.java +++ b/src/test/java/de/ozgcloud/nachrichten/antragraum/Saml2ParserTest.java @@ -7,19 +7,25 @@ import static org.mockito.Mockito.*; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.opensaml.saml.saml2.core.Response; +import org.springframework.test.util.ReflectionTestUtils; import de.ozgcloud.common.test.TestUtils; class Saml2ParserTest { + private BayernIdSamlConfiguration configuration; + @BeforeEach void setup() { - var properties = mock(BayernIdProperties.class); - new BayernIdSamlConfiguration(properties).initOpenSAML(); + var properties = mock(AntragraumProperties.class); + configuration = new BayernIdSamlConfiguration(); + ReflectionTestUtils.setField(configuration, "antragraumProperties", properties); + configuration.initOpenSAML(); } @Test void shouldInit() { - var parser = new Saml2Parser(); + var parser = new Saml2Parser(configuration); + assertThat(parser).isNotNull(); } @@ -49,16 +55,16 @@ class Saml2ParserTest { @Test void shouldGetXMLObject() throws Exception { - var parser = new Saml2Parser(); + var parser = new Saml2Parser(configuration); try (var tokenStream = TestUtils.loadFile("SamlResponse.xml")) { assertThat(parser.xmlObject(tokenStream)).isNotNull(); } } - private static Response getResponse() throws Exception { + private Response getResponse() throws Exception { var token = TestUtils.loadTextFile("SamlResponse.xml"); - var parser = new Saml2Parser(); + var parser = new Saml2Parser(configuration); return parser.parse(token); } diff --git a/src/test/java/de/ozgcloud/nachrichten/antragraum/Saml2VerifierTest.java b/src/test/java/de/ozgcloud/nachrichten/antragraum/Saml2VerifierTest.java index 7e94a28..03dbcb5 100644 --- a/src/test/java/de/ozgcloud/nachrichten/antragraum/Saml2VerifierTest.java +++ b/src/test/java/de/ozgcloud/nachrichten/antragraum/Saml2VerifierTest.java @@ -27,6 +27,7 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.mockito.Mock; import org.springframework.core.io.InputStreamResource; +import org.springframework.test.util.ReflectionTestUtils; import de.ozgcloud.common.test.TestUtils; @@ -36,7 +37,7 @@ class Saml2VerifierTest { private Saml2Verifier verifier; @Mock - private BayernIdProperties properties; + private AntragraumProperties properties; private BayernIdSamlConfiguration configuration; @@ -46,10 +47,13 @@ class Saml2VerifierTest { when(properties.getEntityId()).thenReturn("https://antragsraum.ozgcloud.de"); samlResponse = TestUtils.loadTextFile("SamlResponse.xml"); - configuration = new BayernIdSamlConfiguration(properties); + configuration = new BayernIdSamlConfiguration(); + ReflectionTestUtils.setField(configuration, "antragraumProperties", properties); configuration.initOpenSAML(); - verifier = new Saml2Verifier(new Saml2Parser(), configuration); + var parser = new Saml2Parser(configuration); + + verifier = new Saml2Verifier(parser, configuration); verifier.init(); } diff --git a/src/test/resources/application-bayern.yaml b/src/test/resources/application-bayern.yaml new file mode 100644 index 0000000..d6e6ed1 --- /dev/null +++ b/src/test/resources/application-bayern.yaml @@ -0,0 +1,9 @@ +ozgcloud: + antragraum: + url: "https://antragsraum.de" + bayernid: + saml: + entityId: https://antragsraum.ozgcloud.de/ + metadataUri: "classpath:/bayernid/metadata/bayernid-idp-infra.xml" + decryptionPrivateKey: "classpath:/bayernid/bayernid-test-enc.key" + decryptionCertificate: "classpath:/bayernid/bayernid-test-enc.crt" \ No newline at end of file diff --git a/src/test/resources/application-bayernId.yaml b/src/test/resources/application-bayernId.yaml deleted file mode 100644 index 6b68c25..0000000 --- a/src/test/resources/application-bayernId.yaml +++ /dev/null @@ -1,9 +0,0 @@ -ozgcloud: - antragraum.url: "https://antragsraum.de" - bayernid: - saml: - entityid: https://antragsraum.ozgcloud.de/ - metadatauri: "classpath:/bayernid/metadata/bayernid-idp-infra.xml" - decryption: - private-key-location: "classpath:/bayernid/bayernid-test-enc.key" - certificate-location: "classpath:/bayernid/bayernid-test-enc.crt" \ No newline at end of file -- GitLab