From 3a4e0312341f37071f33c239ddf9fbf42c3eb809 Mon Sep 17 00:00:00 2001 From: OZGCloud <ozgcloud@mgm-tp.com> Date: Mon, 2 Dec 2024 17:52:11 +0100 Subject: [PATCH] OZG-7092 [test] add tests --- .../token/saml/SamlTrustEngineFactory.java | 4 +- .../saml/SamlTrustEngineFactoryTest.java | 540 +++++++++++++++++- 2 files changed, 528 insertions(+), 16 deletions(-) diff --git a/token-checker-server/src/main/java/de/ozgcloud/token/saml/SamlTrustEngineFactory.java b/token-checker-server/src/main/java/de/ozgcloud/token/saml/SamlTrustEngineFactory.java index b1b3de4..fd9567a 100644 --- a/token-checker-server/src/main/java/de/ozgcloud/token/saml/SamlTrustEngineFactory.java +++ b/token-checker-server/src/main/java/de/ozgcloud/token/saml/SamlTrustEngineFactory.java @@ -81,8 +81,8 @@ class SamlTrustEngineFactory { return new CollectionCredentialResolver(credentials); } - Stream<Saml2X509Credential> getCertificatesFromMetadata(Resource metadataResource) { - try (var metadataInputStream = metadataResource.getInputStream()) { + Stream<Saml2X509Credential> getCertificatesFromMetadata(Resource metadata) { + try (var metadataInputStream = metadata.getInputStream()) { return findEntityDescriptor(getXmlObject(metadataInputStream)) .map(this::getVerificationCertificates) .orElseThrow(() -> new TechnicalException("No IDPSSO Descriptors found")); diff --git a/token-checker-server/src/test/java/de/ozgcloud/token/saml/SamlTrustEngineFactoryTest.java b/token-checker-server/src/test/java/de/ozgcloud/token/saml/SamlTrustEngineFactoryTest.java index 09e569c..5260ac4 100644 --- a/token-checker-server/src/test/java/de/ozgcloud/token/saml/SamlTrustEngineFactoryTest.java +++ b/token-checker-server/src/test/java/de/ozgcloud/token/saml/SamlTrustEngineFactoryTest.java @@ -24,29 +24,56 @@ package de.ozgcloud.token.saml; import static org.assertj.core.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.*; import static org.mockito.ArgumentMatchers.*; import static org.mockito.Mockito.*; +import java.io.InputStream; +import java.security.cert.X509Certificate; +import java.util.List; +import java.util.Optional; +import java.util.UUID; import java.util.stream.Stream; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.EnumSource; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.Spy; +import org.opensaml.core.xml.XMLObject; import org.opensaml.core.xml.config.XMLObjectProviderRegistry; +import org.opensaml.core.xml.io.Unmarshaller; +import org.opensaml.core.xml.io.UnmarshallerFactory; +import org.opensaml.saml.common.xml.SAMLConstants; +import org.opensaml.saml.saml2.metadata.EntitiesDescriptor; +import org.opensaml.saml.saml2.metadata.EntityDescriptor; +import org.opensaml.saml.saml2.metadata.IDPSSODescriptor; +import org.opensaml.saml.saml2.metadata.KeyDescriptor; +import org.opensaml.security.credential.UsageType; import org.opensaml.security.credential.impl.CollectionCredentialResolver; +import org.opensaml.security.x509.BasicX509Credential; +import org.opensaml.security.x509.X509Credential; +import org.opensaml.xmlsec.keyinfo.KeyInfoSupport; +import org.opensaml.xmlsec.signature.KeyInfo; import org.opensaml.xmlsec.signature.support.impl.ExplicitKeySignatureTrustEngine; import org.springframework.core.io.Resource; import org.springframework.security.saml2.core.Saml2X509Credential; +import org.w3c.dom.Document; +import org.w3c.dom.Element; import de.ozgcloud.common.errorhandling.TechnicalException; import de.ozgcloud.token.TokenValidationProperties.TokenValidationProperty; +import lombok.SneakyThrows; import net.shibboleth.utilities.java.support.xml.ParserPool; class SamlTrustEngineFactoryTest { + private static final String IDP_ENTITY_ID = UUID.randomUUID().toString(); + @Spy @InjectMocks private SamlTrustEngineFactory factory; @@ -101,36 +128,521 @@ class SamlTrustEngineFactoryTest { @Mock private Resource metadata; + @Nested + class TestBuildSuccessfully { + + @Mock + private X509Credential x509Credential; + + @BeforeEach + void init() { + when(tokenValidationProperty.getMetadata()).thenReturn(metadata); + when(tokenValidationProperty.getIdpEntityId()).thenReturn(IDP_ENTITY_ID); + doReturn(Stream.of(credential)).when(factory).getCertificatesFromMetadata(any()); + doReturn(x509Credential).when(factory).buildBasicX509Credential(any(), any()); + } + + @Test + void shouldCallGetCertificatesFromMetadata() { + buildCredentialResolver(); + + verify(factory).getCertificatesFromMetadata(metadata); + } + + @Test + void shouldCallBuildBasicX509Credential() { + buildCredentialResolver(); + + verify(factory).buildBasicX509Credential(credential, IDP_ENTITY_ID); + } + + @Test + void shouldBuildCredentialResolver() { + var result = buildCredentialResolver(); + + assertThat(result.getCollection()).containsExactly(x509Credential); + } + } + + @Nested + class TestThrowException { + + @Test + void shouldThrowExceptionWhenNoCertificates() { + doReturn(Stream.empty()).when(factory).getCertificatesFromMetadata(any()); + + assertThrows(TechnicalException.class, TestBuildCredentialResolver.this::buildCredentialResolver); + } + } + + private CollectionCredentialResolver buildCredentialResolver() { + return factory.buildCredentialResolver(tokenValidationProperty); + } + } + + @Nested + class TestGetCertificatesFromMetadata { + + @Mock + private Resource metadata; + @Mock + private InputStream inputStream; + @Mock + private XMLObject xmlObject; + @Mock + private Saml2X509Credential credential; + @Mock + private EntityDescriptor entityDescriptor; + + @BeforeEach + @SneakyThrows + void init() { + when(metadata.getInputStream()).thenReturn(inputStream); + doReturn(xmlObject).when(factory).getXmlObject(any()); + } + + @Nested + class TestSuccessfully { + + @BeforeEach + void init() { + doReturn(Optional.of(entityDescriptor)).when(factory).findEntityDescriptor(any()); + doReturn(Stream.of(credential)).when(factory).getVerificationCertificates(any()); + } + + @Test + void shouldCallGetXmlObject() { + getCertificatesFromMetadata(); + + verify(factory).getXmlObject(inputStream); + } + + @Test + void shouldCallFindEntityDescriptor() { + getCertificatesFromMetadata(); + + verify(factory).findEntityDescriptor(xmlObject); + } + + @Test + void shouldGetVerificationCertificates() { + getCertificatesFromMetadata(); + + verify(factory).getVerificationCertificates(entityDescriptor); + } + + @Test + void shouldReturnResult() { + var result = getCertificatesFromMetadata(); + + assertThat(result).containsExactly(credential); + } + } + + @Nested + class TestWithException { + + @Test + void shouldThrowException() { + doReturn(Optional.empty()).when(factory).findEntityDescriptor(any()); + + assertThrows(TechnicalException.class, TestGetCertificatesFromMetadata.this::getCertificatesFromMetadata); + } + } + + private List<Saml2X509Credential> getCertificatesFromMetadata() { + return factory.getCertificatesFromMetadata(metadata).toList(); + } + + } + + @Nested + class TestGetXmlObject { + + @Mock + private InputStream inputStream; + @Mock + private Unmarshaller unmarshaller; + @Mock + private XMLObject xmlObject; + @Mock + private Document document; + @Mock + private Element element; + @BeforeEach + @SneakyThrows void init() { - when(tokenValidationProperty.getMetadata()).thenReturn(metadata); + when(parserPool.parse(any(InputStream.class))).thenReturn(document); + when(document.getDocumentElement()).thenReturn(element); + doReturn(unmarshaller).when(factory).getUnmarshaller(any()); + when(unmarshaller.unmarshall(element)).thenReturn(xmlObject); } @Test - void shouldCallGetCertificatesFromMetadata() { - when(factory.getCertificatesFromMetadata(any())).thenReturn(Stream.of(credential)); + @SneakyThrows + void shouldCallParse() { + getXmlObject(); - factory.buildCredentialResolver(tokenValidationProperty); + verify(parserPool).parse(inputStream); + } + + @Test + void shouldCallGetUnmarshaller() { + getXmlObject(); - verify(factory).getCertificatesFromMetadata(metadata); + verify(factory).getUnmarshaller(element); } @Test - void shouldThrowExceptionWhenNoCertificates() { - when(factory.getCertificatesFromMetadata(any())).thenReturn(Stream.empty()); + @SneakyThrows + void shouldCallUnmarshall() { + getXmlObject(); - assertThatThrownBy(() -> factory.buildCredentialResolver(tokenValidationProperty)).isInstanceOf(TechnicalException.class) - .hasMessage("Metadata response is missing verification certificates, necessary for verifying SAML assertions"); + verify(unmarshaller).unmarshall(element); } @Test - void shouldBuildCredentialResolver() { - when(factory.getCertificatesFromMetadata(any())).thenReturn(Stream.of("key")); - doReturn(new CollectionCredentialResolver(null)).when(factory).buildCredentialResolver(any()); + void shouldReturnResult() { + var result = getXmlObject(); + + assertThat(result).isEqualTo(xmlObject); + } + + private XMLObject getXmlObject() { + return factory.getXmlObject(inputStream); + } + } + + @Nested + class TestGetUnmarshaller { + + @Mock + private Element element; + @Mock + private UnmarshallerFactory unmarshallerFactory; + @Mock + private Unmarshaller unmarshaller; + + @BeforeEach + void init() { + when(registry.getUnmarshallerFactory()).thenReturn(unmarshallerFactory); + } + + @Nested + class TestSuccessfully { + + @BeforeEach + void init() { + when(unmarshallerFactory.getUnmarshaller(any(Element.class))).thenReturn(unmarshaller); + } + + @Test + void shouldCallGetUnmarshallerFactory() { + getUnmarshaller(); + + verify(registry).getUnmarshallerFactory(); + } + + @Test + void shouldCallGetUnmarshaller() { + getUnmarshaller(); + + verify(unmarshallerFactory).getUnmarshaller(element); + } - var result = factory.buildCredentialResolver(tokenValidationProperty); + @Test + void shouldReturnResult() { + var result = getUnmarshaller(); + + assertThat(result).isEqualTo(unmarshaller); + } + } + + @Nested + class TestNoUnmarshaller { + + @Test + void shouldThrowException() { + assertThrows(TechnicalException.class, TestGetUnmarshaller.this::getUnmarshaller); + } + } + + private Unmarshaller getUnmarshaller() { + return factory.getUnmarshaller(element); + } + } + + @Nested + class TestFindEntityDescriptor { + + @Mock + private XMLObject xmlObject; + @Mock + private EntityDescriptor entityDescriptor; + @Mock + private IDPSSODescriptor descriptor; + + @BeforeEach + void init() { + when(factory.extractEntityDescriptor(any())).thenReturn(Optional.of(entityDescriptor)); + } + + @Test + void shouldCallExtractEntityDescriptor() { + findEntityDescriptor(); + + verify(factory).extractEntityDescriptor(xmlObject); + } + + @Test + void shouldFilterDescriptor() { + when(entityDescriptor.getIDPSSODescriptor(anyString())).thenReturn(descriptor); + + findEntityDescriptor(); + + verify(entityDescriptor).getIDPSSODescriptor(SAMLConstants.SAML20P_NS); + } + + @Test + void shouldReturnResult() { + when(entityDescriptor.getIDPSSODescriptor(anyString())).thenReturn(descriptor); + + var result = findEntityDescriptor(); + + assertThat(result).contains(entityDescriptor); + } + + private Optional<EntityDescriptor> findEntityDescriptor() { + return factory.findEntityDescriptor(xmlObject); + } + } + + @Nested + class TestExtractEntityDescriptor { + + @Mock + private XMLObject xmlObject; + + @Test + void shouldReturnEmpty() { + var result = factory.extractEntityDescriptor(xmlObject); + + assertThat(result).isEmpty(); + } + + @Nested + class TestEntityDescriptor { + + @Mock + private EntityDescriptor entityDescriptor; + + @Test + void shouldReturnEntityDescriptor() { + var result = factory.extractEntityDescriptor(entityDescriptor); + + assertThat(result).contains(entityDescriptor); + } + } + + @Nested + class TestEntitiesDescriptor { + + @Mock + private EntitiesDescriptor entitiesDescriptor; + @Mock + private EntityDescriptor entityDescriptor; + + @BeforeEach + void init() { + when(entitiesDescriptor.getEntityDescriptors()).thenReturn(List.of(entityDescriptor)); + } + + @Test + void shouldReturnEntityDescriptor() { + var result = factory.extractEntityDescriptor(entitiesDescriptor); + + assertThat(result).contains(entityDescriptor); + } + } + } + + @Nested + class TestGetVerificationCertificates { + + @Mock + private EntityDescriptor descriptor; + + @Nested + class TestSuccessfully { + + @Mock + private IDPSSODescriptor idpSsoDescriptor; + @Mock + private KeyDescriptor keyDescriptor; + @Mock + private X509Certificate certificate; + + @BeforeEach + void init() { + when(descriptor.getIDPSSODescriptor(anyString())).thenReturn(idpSsoDescriptor); + when(idpSsoDescriptor.getKeyDescriptors()).thenReturn(List.of(keyDescriptor)); + doReturn(true).when(factory).isSignatureKey(any()); + doReturn(Stream.of(certificate)).when(factory).getCertificates(any()); + } + + @Test + void shouldCallGetKeyDescriptors() { + getVerificationCertificates(); + + verify(idpSsoDescriptor).getKeyDescriptors(); + } + + @Test + void shouldCallIsSignatureKey() { + getVerificationCertificates(); + + verify(factory).isSignatureKey(keyDescriptor); + } + + @Test + void shouldCallGetCertificates() { + getVerificationCertificates(); + + verify(factory).getCertificates(keyDescriptor); + } + + @Test + void shouldCallVerification() { + try (var credentialMock = mockStatic(Saml2X509Credential.class)) { + getVerificationCertificates(); + + credentialMock.verify(() -> Saml2X509Credential.verification(certificate)); + } + } + } + + @Nested + class TestWithException { + + @Test + void shouldThrowException() { + assertThrows(TechnicalException.class, TestGetVerificationCertificates.this::getVerificationCertificates); + } + } + + private List<Saml2X509Credential> getVerificationCertificates() { + return factory.getVerificationCertificates(descriptor).toList(); + } + } + + @Nested + class TestIsSignatureKey { + + @Mock + private KeyDescriptor keyDescriptor; + + @Test + void shouldReturnTrue() { + when(keyDescriptor.getUse()).thenReturn(UsageType.SIGNING); + + var result = isSignatureKey(); + + assertThat(result).isTrue(); + } + + @DisplayName("should return false") + @ParameterizedTest(name = "when keyDescriptor use is {0}") + @EnumSource(value = UsageType.class, names = { "SIGNING" }, mode = EnumSource.Mode.EXCLUDE) + void shouldReturnFalse(UsageType use) { + when(keyDescriptor.getUse()).thenReturn(use); + + var result = isSignatureKey(); + + assertThat(result).isFalse(); + } + + private boolean isSignatureKey() { + return factory.isSignatureKey(keyDescriptor); + } + } + + @Nested + class TestGetCertificates { + + @Mock + private KeyDescriptor keyDescriptor; + @Mock + private KeyInfo keyInfo; + @Mock + private X509Certificate certificate; + + @BeforeEach + void init() { + when(keyDescriptor.getKeyInfo()).thenReturn(keyInfo); + } + + @Test + void shouldCallGetCertificates() { + try (var keyInfoSupportMock = mockStatic(KeyInfoSupport.class)) { + getCertificates(); + + keyInfoSupportMock.verify(() -> KeyInfoSupport.getCertificates(keyInfo)); + } + } + + @Test + void shouldReturnResult() { + try (var keyInfoSupportMock = mockStatic(KeyInfoSupport.class)) { + keyInfoSupportMock.when(() -> KeyInfoSupport.getCertificates(any(KeyInfo.class))).thenReturn(List.of(certificate)); + + var result = getCertificates(); + + assertThat(result).containsExactly(certificate); + } + } + + private List<X509Certificate> getCertificates() { + return factory.getCertificates(keyDescriptor).toList(); + } + } + + @Nested + class TestBuildBasicX509Credential { + + @Mock + private Saml2X509Credential samlKey; + @Mock + private X509Certificate certificate; + + @BeforeEach + void init() { + when(samlKey.getCertificate()).thenReturn(certificate); + } + + @Test + void shouldSetEntityCertificate() { + var result = buildBasicX509Credential(); + + assertThat(result.getEntityCertificate()).isEqualTo(certificate); + } + + @Test + void shouldSetUsageType() { + var result = buildBasicX509Credential(); + + assertThat(result.getUsageType()).isEqualTo(UsageType.SIGNING); + } + + @Test + void shouldSetEntityId() { + var result = buildBasicX509Credential(); + + assertThat(result.getEntityId()).isEqualTo(IDP_ENTITY_ID); + } - assertThat(result).isNotNull(); + private BasicX509Credential buildBasicX509Credential() { + return (BasicX509Credential) factory.buildBasicX509Credential(samlKey, IDP_ENTITY_ID); } } } \ No newline at end of file -- GitLab