diff --git a/nachrichten-manager-server/src/main/java/de/ozgcloud/nachrichten/antragraum/Saml2Decrypter.java b/nachrichten-manager-server/src/main/java/de/ozgcloud/nachrichten/antragraum/Saml2Decrypter.java index 9728ddba81bb0105dde77407ea24b44214e0b29d..d2217e9785555a9827682981630c20f9a7a45153 100644 --- a/nachrichten-manager-server/src/main/java/de/ozgcloud/nachrichten/antragraum/Saml2Decrypter.java +++ b/nachrichten-manager-server/src/main/java/de/ozgcloud/nachrichten/antragraum/Saml2Decrypter.java @@ -26,11 +26,13 @@ package de.ozgcloud.nachrichten.antragraum; import java.util.ArrayList; import java.util.Arrays; -import java.util.Collection; +import java.util.List; import jakarta.annotation.PostConstruct; import org.opensaml.core.xml.schema.XSString; +import org.opensaml.saml.saml2.core.Assertion; +import org.opensaml.saml.saml2.core.Attribute; import org.opensaml.saml.saml2.core.AttributeStatement; import org.opensaml.saml.saml2.core.EncryptedAssertion; import org.opensaml.saml.saml2.core.Response; @@ -42,7 +44,6 @@ import org.opensaml.xmlsec.encryption.support.ChainingEncryptedKeyResolver; import org.opensaml.xmlsec.encryption.support.EncryptedKeyResolver; import org.opensaml.xmlsec.encryption.support.InlineEncryptedKeyResolver; import org.opensaml.xmlsec.encryption.support.SimpleRetrievalMethodEncryptedKeyResolver; -import org.opensaml.xmlsec.keyinfo.KeyInfoCredentialResolver; import org.opensaml.xmlsec.keyinfo.impl.CollectionKeyInfoCredentialResolver; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.security.saml2.Saml2Exception; @@ -57,6 +58,8 @@ class Saml2Decrypter { private final BayernIdSamlConfiguration configuration; public static final String LEGACY_POSTKORB_HANDLE_KEY = "legacyPostkorbHandle"; + public static final String TRUST_LEVEL_KEY = "urn:oid:1.2.40.0.10.2.1.1.261.94"; + private Decrypter decrypter; private static final EncryptedKeyResolver encryptedKeyResolver = new ChainingEncryptedKeyResolver( Arrays.asList(new InlineEncryptedKeyResolver(), new EncryptedElementTypeEncryptedKeyResolver(), @@ -64,42 +67,66 @@ class Saml2Decrypter { @PostConstruct void init() { - Collection<Credential> credentials = new ArrayList<>(); + var credentials = new ArrayList<Credential>(); var decryptionX509Credential = configuration.getDecryptionCredential(); - Credential cred = CredentialSupport.getSimpleCredential(decryptionX509Credential.getCertificate(), decryptionX509Credential.getPrivateKey()); + var cred = CredentialSupport.getSimpleCredential(decryptionX509Credential.getCertificate(), decryptionX509Credential.getPrivateKey()); credentials.add(cred); - KeyInfoCredentialResolver resolver = new CollectionKeyInfoCredentialResolver(credentials); + var resolver = new CollectionKeyInfoCredentialResolver(credentials); var setupDecrypter = new Decrypter(null, resolver, encryptedKeyResolver); setupDecrypter.setRootInNewDocument(true); decrypter = setupDecrypter; } + public String decryptPostfachId(Response response) { + decryptResponseElements(response); + + var postfachIdAttribute = getAttributes(response).stream().filter(this::isPostfachId).findFirst(); + + return postfachIdAttribute.map(this::getStringValue).orElseThrow(); + } + + boolean isPostfachId(Attribute attribute) { + return LEGACY_POSTKORB_HANDLE_KEY.equals(attribute.getFriendlyName()); + } + + public String decryptTrustLevel(Response response) { + decryptResponseElements(response); + + var trustLevelAttribute = getAttributes(response).stream().filter(this::isTrustLevel).findFirst(); + + return trustLevelAttribute.map(this::getStringValue).orElseThrow(); + } + void decryptResponseElements(Response response) { - for (EncryptedAssertion encryptedAssertion : response.getEncryptedAssertions()) { - try { - var assertion = decrypter.decrypt(encryptedAssertion); - response.getAssertions().add(assertion); - } catch (Exception ex) { - throw new Saml2Exception(ex); - } + response.getEncryptedAssertions().stream() + .map(this::decryptAssertion) + .forEach(assertion -> response.getAssertions().add(assertion)); + } + + private Assertion decryptAssertion(EncryptedAssertion assertion) { + try { + return decrypter.decrypt(assertion); + } catch (Exception ex) { + throw new Saml2Exception(ex); } } - String decryptPostfachId(Response response) { - decryptResponseElements(response); + private List<Attribute> getAttributes(Response response) { + return getAttributeStatement(response).getAttributes(); + } - var samlAssertion = response.getAssertions().get(0); - var statements = (AttributeStatement) samlAssertion.getStatements().get(1); - var attributes = statements.getAttributes(); - var postfachIdOptional = attributes.stream().filter(attribute -> LEGACY_POSTKORB_HANDLE_KEY.equals(attribute.getFriendlyName())).findFirst(); + private AttributeStatement getAttributeStatement(Response response) { + return (AttributeStatement) response.getAssertions().get(0).getStatements().get(1); + } - return postfachIdOptional.map(postfachIdAttribute -> { - var values = postfachIdAttribute.getAttributeValues(); - return ((XSString) values.get(0)).getValue(); - }).orElseThrow(); + boolean isTrustLevel(Attribute attribute) { + return TRUST_LEVEL_KEY.equals(attribute.getName()); + } + private String getStringValue(Attribute attribute) { + return ((XSString) attribute.getAttributeValues().get(0)).getValue(); } -} +} \ No newline at end of file diff --git a/nachrichten-manager-server/src/test/java/de/ozgcloud/nachrichten/antragraum/Saml2DecrypterTest.java b/nachrichten-manager-server/src/test/java/de/ozgcloud/nachrichten/antragraum/Saml2DecrypterTest.java index 1dd94cea3e8ef54e5b7d62ac936921ed583d748b..d36c6c4365259ddf6bf7c500ae614597cb69abaf 100644 --- a/nachrichten-manager-server/src/test/java/de/ozgcloud/nachrichten/antragraum/Saml2DecrypterTest.java +++ b/nachrichten-manager-server/src/test/java/de/ozgcloud/nachrichten/antragraum/Saml2DecrypterTest.java @@ -21,13 +21,18 @@ package de.ozgcloud.nachrichten.antragraum; import static org.assertj.core.api.Assertions.*; +import static org.mockito.ArgumentMatchers.*; import static org.mockito.Mockito.*; +import java.util.NoSuchElementException; + import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.mockito.Mock; -import org.opensaml.saml.saml2.core.Attribute; +import org.mockito.Mockito; import org.opensaml.saml.saml2.core.AttributeStatement; import org.opensaml.saml.saml2.core.Response; import org.springframework.core.io.InputStreamResource; @@ -59,7 +64,7 @@ class Saml2DecrypterTest { var parser = new Saml2Parser(configuration); samlResponse = parser.parse(samlResponseString); - decrypter = new Saml2Decrypter(configuration); + decrypter = Mockito.spy(new Saml2Decrypter(configuration)); decrypter.init(); } @@ -107,20 +112,41 @@ class Saml2DecrypterTest { assertThat(attributes).hasSize(7); } - @Test - void shouldHavePostfachId() { - decrypter.decryptResponseElements(samlResponse); - var samlAssertion = samlResponse.getAssertions().get(0); - var statements = (AttributeStatement) samlAssertion.getStatements().get(1); - var attributes = statements.getAttributes(); - var postfachIdOptional = attributes.stream().filter(attribute -> "legacyPostkorbHandle".equals(attribute.getFriendlyName())).findFirst(); - assertThat(postfachIdOptional).map(Attribute::getAttributeValues).isNotNull(); + @DisplayName("Decrypt postfachId") + @Nested + class TestDecryptPostfachId { + + @Test + void shouldReturnPostfachIdIfExists() { + var postfachId = decrypter.decryptPostfachId(samlResponse); + + assertThat(postfachId).isEqualTo("28721c6f-b78f-4d5c-a048-19fd2fc429d2"); + } + + @Test + void shouldThrowExceptionIfMissing() { + doReturn(false).when(decrypter).isPostfachId(any()); + + assertThatThrownBy(() -> decrypter.decryptPostfachId(samlResponse)).isInstanceOf(NoSuchElementException.class); + } } - @Test - void shouldGetPostfachId() { - var postfachId = decrypter.decryptPostfachId(samlResponse); + @DisplayName("Decrypt trustLevel") + @Nested + class TestDecryptTrustLevel { + + @Test + void shouldReturnTrustLevelIfExists() { + var trustLevel = decrypter.decryptTrustLevel(samlResponse); + + assertThat(trustLevel).isEqualTo("STORK-QAA-Level-1"); + } + + @Test + void shouldThrowExceptionIfMissing() { + doReturn(false).when(decrypter).isTrustLevel(any()); - assertThat(postfachId).isEqualTo("28721c6f-b78f-4d5c-a048-19fd2fc429d2"); + assertThatThrownBy(() -> decrypter.decryptTrustLevel(samlResponse)).isInstanceOf(NoSuchElementException.class); + } } } \ No newline at end of file