diff --git a/token-checker-interface/src/main/protobuf/tokencheck.model.proto b/token-checker-interface/src/main/protobuf/tokencheck.model.proto index 715ec6ef7f1f832abdb3ffd05cc02a4763cd13c5..a2edda6189d7b7736fe5c44ffd6d5e87abf384fb 100644 --- a/token-checker-interface/src/main/protobuf/tokencheck.model.proto +++ b/token-checker-interface/src/main/protobuf/tokencheck.model.proto @@ -14,7 +14,7 @@ message GrpcCheckTokenResponse { bool tokenValid = 1; oneof checkTokenResult { GrpcTokenAttributes tokenAttributes = 2; - GrpcCheckErrors checkError = 3; + GrpcCheckErrors checkErrors = 3; } } @@ -30,7 +30,7 @@ message GrpcOtherField { } message GrpcCheckErrors { - repeated GrpcCheckError message = 1; + repeated GrpcCheckError checkError = 1; } message GrpcCheckError { diff --git a/token-checker-server/src/main/java/de/ozgcloud/token/saml/SamlAttributeService.java b/token-checker-server/src/main/java/de/ozgcloud/token/saml/SamlAttributeService.java index fbba32136fc96a2f983d5093833c4293723ab99a..a62ce6cb39d523e132699a09c44b6ae022eb181f 100644 --- a/token-checker-server/src/main/java/de/ozgcloud/token/saml/SamlAttributeService.java +++ b/token-checker-server/src/main/java/de/ozgcloud/token/saml/SamlAttributeService.java @@ -24,15 +24,15 @@ package de.ozgcloud.token.saml; +import java.util.ArrayList; import java.util.Collection; -import java.util.Collections; -import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Optional; -import java.util.Set; import java.util.stream.Collectors; +import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.lang3.StringUtils; import org.opensaml.core.xml.XMLObject; import org.opensaml.core.xml.schema.XSAny; @@ -53,53 +53,63 @@ import org.opensaml.xmlsec.signature.support.SignatureException; import org.opensaml.xmlsec.signature.support.SignatureTrustEngine; import de.ozgcloud.token.TokenAttribute; +import de.ozgcloud.token.TokenAttributes; import de.ozgcloud.token.TokenValidationProperties.TokenValidationProperty; import de.ozgcloud.token.common.errorhandling.TokenVerificationException; +import de.ozgcloud.token.common.errorhandling.ValidationError; import lombok.Builder; import net.shibboleth.utilities.java.support.resolver.CriteriaSet; @Builder public class SamlAttributeService { + private static final String ID_AS_POSTFACH_ID_KEY = "OZG_CLOUD_POSTFACH_ID"; + private final SignatureTrustEngine signatureTrustEngine; private final Decrypter decrypter; private final SAMLSignatureProfileValidator profileValidator; private final TokenValidationProperty tokenValidationProperty; private final CriteriaSet verificationCriteria; - public Set<TokenAttribute> validate(Response token) { + public TokenAttributes getAttributes(Response token) { validateToken(token); - var tokenAttributes = decryptAttributes(token); - return buildTokenAttributes(tokenAttributes, token); + return buildTokenAttributes(decryptSamlAttributes(token), token); } void validateToken(Response token) { if (Objects.isNull(token.getSignature())) { throw new TokenVerificationException("Token signature is missing"); } - validateSignatureProfile(token.getSignature()); - if (!validateSignature(token.getSignature())) { - throw new TokenVerificationException("Invalid token signature"); + var validationErrors = new ArrayList<ValidationError>(); + validateSignatureProfile(token.getSignature()).ifPresent(validationErrors::add); + validateSignature(token.getSignature()).ifPresent(validationErrors::add); + if (CollectionUtils.isNotEmpty(validationErrors)) { + throw new TokenVerificationException("Token validation failed", validationErrors); } } - void validateSignatureProfile(Signature signature) { + Optional<ValidationError> validateSignatureProfile(Signature signature) { try { profileValidator.validate(signature); } catch (SignatureException e) { - throw new TokenVerificationException("Invalid signature profile", e); + return Optional.of(ValidationError.builder().message("Invalid signature profile: " + e.getMessage()).cause(e).build()); } + return Optional.empty(); } - boolean validateSignature(Signature signature) { + Optional<ValidationError> validateSignature(Signature signature) { try { - return signatureTrustEngine.validate(signature, verificationCriteria); + var isValidSignature = signatureTrustEngine.validate(signature, verificationCriteria); + if (!isValidSignature) { + return Optional.of(ValidationError.builder().message("Invalid token signature").build()); + } } catch (SecurityException e) { - throw new TokenVerificationException("Error on validating signature.", e); + return Optional.of(ValidationError.builder().message("Error on signature validation: " + e.getMessage()).cause(e).build()); } + return Optional.empty(); } - public Map<String, String> decryptAttributes(Response token) { + Map<String, String> decryptSamlAttributes(Response token) { return token.getEncryptedAssertions().stream() .map(this::decryptAssertion) .findFirst() @@ -141,37 +151,30 @@ public class SamlAttributeService { }; } - Set<TokenAttribute> buildTokenAttributes(Map<String, String> tokenAttributes, Response token) { - return adjustAttributes(tokenAttributes, token).entrySet().stream().map(this::buildTokenAttribute).collect(Collectors.toSet()); + TokenAttributes buildTokenAttributes(Map<String, String> tokenAttributes, Response token) { + var result = TokenAttributes.builder().postfachId(getPostfachId(tokenAttributes, token)).trustLevel(getTrustLevel(tokenAttributes)); + tokenAttributes.entrySet().stream().filter(this::isNotMappedField).map(this::buildTokenAttribute).forEach(result::otherAttribute); + return result.build(); } - Map<String, String> adjustAttributes(Map<String, String> tokenAttributes, Response token) { - return Collections.unmodifiableMap(adjustPostfachIdAttribute(replaceKeys(new HashMap<>(tokenAttributes)), token)); + String getPostfachId(Map<String, String> tokenAttributes, Response token) { + return tokenValidationProperty.isUseIdAsPostfachId() ? token.getID() : getMappedValue(tokenAttributes, TokenAttributes.POSTFACH_ID_KEY); } - Map<String, String> replaceKeys(Map<String, String> tokenAttributes) { - tokenValidationProperty.getMappings().forEach((key, mappedKey) -> { - var attrValue = tokenAttributes.remove(mappedKey); - if (StringUtils.isNotBlank(attrValue)) { - tokenAttributes.put(key, attrValue); - } - }); - return tokenAttributes; + String getTrustLevel(Map<String, String> tokenAttributes) { + return getMappedValue(tokenAttributes, TokenAttributes.TRUST_LEVEL_KEY); } - Map<String, String> adjustPostfachIdAttribute(Map<String, String> tokenAttributes, Response token) { - if (tokenValidationProperty.isUseIdAsPostfachId()) { - tokenAttributes.put(TokenAttribute.POSTFACH_ID_KEY, token.getID()); - } - return tokenAttributes; + String getMappedValue(Map<String, String> tokenAttributes, String key) { + var mappedKey = tokenValidationProperty.getMappings().getOrDefault(key, key); + return tokenAttributes.get(mappedKey); } - TokenAttribute buildTokenAttribute(Map.Entry<String, String> attribute) { - return TokenAttribute.builder().name(mapAttributeKey(attribute.getKey())).value(attribute.getValue()).build(); + boolean isNotMappedField(Map.Entry<String, String> attributeEntry) { + return !tokenValidationProperty.getMappings().containsValue(attributeEntry.getKey()); } - String mapAttributeKey(String key) { - return tokenValidationProperty.getMappings().entrySet().stream().filter(entry -> StringUtils.equals(entry.getValue(), key)) - .map(Map.Entry::getKey).findFirst().orElse(key); + TokenAttribute buildTokenAttribute(Map.Entry<String, String> attribute) { + return TokenAttribute.builder().name(attribute.getKey()).value(attribute.getValue()).build(); } } diff --git a/token-checker-server/src/test/java/de/ozgcloud/token/TokenAttributesTestFactory.java b/token-checker-server/src/test/java/de/ozgcloud/token/TokenAttributesTestFactory.java new file mode 100644 index 0000000000000000000000000000000000000000..7e4e4d2fc7c5b02f9b8d77f754fd1875c8e94277 --- /dev/null +++ b/token-checker-server/src/test/java/de/ozgcloud/token/TokenAttributesTestFactory.java @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2024 Das Land Schleswig-Holstein vertreten durch den + * Ministerpräsidenten des Landes Schleswig-Holstein + * Staatskanzlei + * Abteilung Digitalisierung und zentrales IT-Management der Landesregierung + * + * Lizenziert unter der EUPL, Version 1.2 oder - sobald + * diese von der Europäischen Kommission genehmigt wurden - + * Folgeversionen der EUPL ("Lizenz"); + * Sie dürfen dieses Werk ausschließlich gemäß + * dieser Lizenz nutzen. + * Eine Kopie der Lizenz finden Sie hier: + * + * https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 + * + * Sofern nicht durch anwendbare Rechtsvorschriften + * gefordert oder in schriftlicher Form vereinbart, wird + * die unter der Lizenz verbreitete Software "so wie sie + * ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN - + * ausdrücklich oder stillschweigend - verbreitet. + * Die sprachspezifischen Genehmigungen und Beschränkungen + * unter der Lizenz sind dem Lizenztext zu entnehmen. + */ + +package de.ozgcloud.token; + +import java.util.Map; +import java.util.UUID; + +import com.thedeanda.lorem.LoremIpsum; + +public class TokenAttributesTestFactory { + + public static final String POSTFACH_ID = UUID.randomUUID().toString(); + public static final String TRUST_LEVEL = LoremIpsum.getInstance().getWords(1); + public static final TokenAttribute OTHER_ATTRIBUTE = TokenAttributeTestFactory.create(); + + public static TokenAttributes create() { + return createBuilder().build(); + } + + public static TokenAttributes.TokenAttributesBuilder createBuilder() { + return TokenAttributes.builder() + .postfachId(POSTFACH_ID) + .trustLevel(TRUST_LEVEL) + .otherAttribute(OTHER_ATTRIBUTE); + } + + public static Map<String, String> asMap() { + return Map.of( + TokenAttributes.POSTFACH_ID_KEY, POSTFACH_ID, + TokenAttributes.TRUST_LEVEL_KEY, TRUST_LEVEL, + TokenAttributeTestFactory.NAME, TokenAttributeTestFactory.VALUE + ); + } +} diff --git a/token-checker-server/src/test/java/de/ozgcloud/token/saml/SamlAttributeServiceTest.java b/token-checker-server/src/test/java/de/ozgcloud/token/saml/SamlAttributeServiceTest.java index 70c33fb0a31bcce22359bcd1b501bde89cd0c1a0..9d22ec18ad28b2e656d23ffbdebe25f8d62c0a07 100644 --- a/token-checker-server/src/test/java/de/ozgcloud/token/saml/SamlAttributeServiceTest.java +++ b/token-checker-server/src/test/java/de/ozgcloud/token/saml/SamlAttributeServiceTest.java @@ -27,12 +27,12 @@ import static org.assertj.core.api.Assertions.*; import static org.junit.jupiter.api.Assertions.*; import static org.mockito.Mockito.*; -import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.Random; -import java.util.Set; +import java.util.SimpleTimeZone; +import java.util.UUID; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; @@ -57,15 +57,22 @@ import org.opensaml.saml.saml2.core.Response; import org.opensaml.saml.saml2.core.Statement; import org.opensaml.saml.saml2.encryption.Decrypter; import org.opensaml.saml.security.impl.SAMLSignatureProfileValidator; +import org.opensaml.security.SecurityException; import org.opensaml.xmlsec.signature.Signature; +import org.opensaml.xmlsec.signature.support.SignatureException; import org.opensaml.xmlsec.signature.support.SignatureTrustEngine; +import org.testcontainers.shaded.org.checkerframework.checker.units.qual.N; import com.thedeanda.lorem.LoremIpsum; import de.ozgcloud.token.TokenAttribute; import de.ozgcloud.token.TokenAttributeTestFactory; +import de.ozgcloud.token.TokenAttributes; +import de.ozgcloud.token.TokenAttributesTestFactory; import de.ozgcloud.token.TokenValidationProperties.TokenValidationProperty; import de.ozgcloud.token.common.errorhandling.TokenVerificationException; +import de.ozgcloud.token.common.errorhandling.ValidationError; +import lombok.Builder; import lombok.SneakyThrows; import net.shibboleth.utilities.java.support.resolver.CriteriaSet; @@ -87,33 +94,34 @@ class SamlAttributeServiceTest { private CriteriaSet verificationCriteria; @Nested - class TestValidate { + class TestGetAttributes { private static final Map<String, String> TOKEN_ATTRIBUTES_MAP = Map.of("key", "value"); - private static final TokenAttribute TOKEN_ATTRIBUTE = TokenAttributeTestFactory.create(); @Mock private Response token; + @Mock + private TokenAttributes tokenAttributes; @BeforeEach void init() { doNothing().when(service).validateToken(any()); - doReturn(TOKEN_ATTRIBUTES_MAP).when(service).decryptAttributes(any()); - doReturn(Set.of(TOKEN_ATTRIBUTE)).when(service).buildTokenAttributes(any(), any()); + doReturn(TOKEN_ATTRIBUTES_MAP).when(service).decryptSamlAttributes(any()); + doReturn(tokenAttributes).when(service).buildTokenAttributes(any(), any()); } @Test void shouldCallValidateToken() { validate(); - verify(service).validate(token); + verify(service).getAttributes(token); } @Test void shouldCallDecryptAttributes() { validate(); - verify(service).decryptAttributes(token); + verify(service).decryptSamlAttributes(token); } @Test @@ -127,11 +135,11 @@ class SamlAttributeServiceTest { void shouldReturnResult() { var result = validate(); - assertThat(result).containsExactly(TOKEN_ATTRIBUTE); + assertThat(result).isSameAs(tokenAttributes); } - private Set<TokenAttribute> validate() { - return service.validate(token); + private TokenAttributes validate() { + return service.getAttributes(token); } } @@ -141,6 +149,11 @@ class SamlAttributeServiceTest { @Mock private Response token; + @Test + void shouldThrowExceptionWhenMissingSignature() { + assertThrows(TokenVerificationException.class, TestValidateToken.this::validateToken); + } + @Nested class TestValidateSuccessfully { @@ -150,7 +163,13 @@ class SamlAttributeServiceTest { @BeforeEach void init() { when(token.getSignature()).thenReturn(signature); - doReturn(true).when(service).validateSignature(any()); + doReturn(Optional.empty()).when(service).validateSignatureProfile(any()); + doReturn(Optional.empty()).when(service).validateSignature(any()); + } + + @Test + void shouldNotThrowException() { + assertDoesNotThrow(TestValidateToken.this::validateToken); } @Test @@ -161,10 +180,10 @@ class SamlAttributeServiceTest { } @Test - void shouldValidateToken() { + void shouldCallValidateSignature() { validateToken(); - Assertions.assertDoesNotThrow(TestValidateToken.this::validateToken); + verify(service).validateSignature(signature); } } @@ -173,18 +192,30 @@ class SamlAttributeServiceTest { @Mock private Signature signature; + @Mock + private ValidationError signatureProfileError; + @Mock + private ValidationError signatureError; + + @BeforeEach + void init() { + when(token.getSignature()).thenReturn(signature); + doReturn(Optional.of(signatureProfileError)).when(service).validateSignatureProfile(any()); + doReturn(Optional.of(signatureError)).when(service).validateSignature(any()); + } @Test - void shouldThrowWhenMissingSignature() { - assertThrows(TokenVerificationException.class, TestValidateToken.this::validateToken); + void shouldAddInvalidSignatureProfileToException() { + var exception = assertThrows(TokenVerificationException.class, TestValidateToken.this::validateToken); + + assertThat(exception.getValidationErrors()).contains(signatureProfileError); } @Test - void shouldThrowWhenInvalidSignatureProfile() { - when(token.getSignature()).thenReturn(signature); - when(service.validateSignature(any())).thenReturn(false); + void shouldAddInvalidSignatureToException() { + var exception = assertThrows(TokenVerificationException.class, TestValidateToken.this::validateToken); - assertThrows(TokenVerificationException.class, TestValidateToken.this::validateToken); + assertThat(exception.getValidationErrors()).contains(signatureError); } } @@ -199,12 +230,53 @@ class SamlAttributeServiceTest { @Mock private Signature signature; - @Test - @SneakyThrows - void shouldCallProfileValidator() { - service.validateSignatureProfile(signature); + @Nested + class TestSignatureProfileValide { - verify(profileValidator).validate(signature); + @Test + @SneakyThrows + void shouldCallProfileValidator() { + service.validateSignatureProfile(signature); + + verify(profileValidator).validate(signature); + } + + @Test + void shouldReturnEmpty() { + var result = service.validateSignatureProfile(signature); + + assertThat(result).isEmpty(); + } + } + + @Nested + class TestInvalidSignatureProfile { + + private static final String EXCEPTION_MESSAGE = LoremIpsum.getInstance().getWords(1); + + private final SignatureException signatureException = new SignatureException(EXCEPTION_MESSAGE); + + @BeforeEach + @SneakyThrows + void init() { + doThrow(signatureException).when(profileValidator).validate(any()); + } + + @Test + void shouldSetMessage() { + var result = service.validateSignatureProfile(signature); + + assertThat(result).get().extracting(ValidationError::getMessage, STRING) + .hasSizeGreaterThan(EXCEPTION_MESSAGE.length()) + .endsWith(EXCEPTION_MESSAGE); + } + + @Test + void shouldSetCause() { + var result = service.validateSignatureProfile(signature); + + assertThat(result).get().extracting(ValidationError::getCause).isEqualTo(signatureException); + } } } @@ -221,6 +293,56 @@ class SamlAttributeServiceTest { verify(signatureTrustEngine).validate(signature, verificationCriteria); } + + @Test + @SneakyThrows + void shouldReturnEmpty() { + doReturn(true).when(signatureTrustEngine).validate(any(), any()); + + var result = service.validateSignature(signature); + + assertThat(result).isEmpty(); + } + + @SneakyThrows + @Test + void shouldSetMessageIfInvalid() { + doReturn(false).when(signatureTrustEngine).validate(any(), any()); + + var result = service.validateSignature(signature); + + assertThat(result).get().extracting(ValidationError::getMessage, STRING).isNotEmpty(); + } + + @Nested + class TestOnException { + + private static final String EXCEPTION_MESSAGE = LoremIpsum.getInstance().getWords(1); + + private final SecurityException signatureException = new SecurityException(EXCEPTION_MESSAGE); + + @BeforeEach + @SneakyThrows + void init() { + doThrow(signatureException).when(signatureTrustEngine).validate(any(), any()); + } + + @Test + void shouldSetMessage() { + var result = service.validateSignature(signature); + + assertThat(result).get().extracting(ValidationError::getMessage, STRING) + .hasSizeGreaterThan(EXCEPTION_MESSAGE.length()) + .endsWith(EXCEPTION_MESSAGE); + } + + @Test + void shouldSetCause() { + var result = service.validateSignature(signature); + + assertThat(result).get().extracting(ValidationError::getCause).isEqualTo(signatureException); + } + } } @Nested @@ -276,7 +398,7 @@ class SamlAttributeServiceTest { } private Map<String, String> decryptAttributes() { - return service.decryptAttributes(token); + return service.decryptSamlAttributes(token); } } @@ -429,145 +551,208 @@ class SamlAttributeServiceTest { @Nested class TestBuildTokenAttributes { - private static final Map.Entry<String, String> ADJUSTED_TOKEN_ATTRIBUTES_ENTRY = Map.entry("key", "value"); - private static final Map<String, String> ADJUSTED_TOKEN_ATTRIBUTES_MAP = Map.ofEntries(ADJUSTED_TOKEN_ATTRIBUTES_ENTRY); - private static final TokenAttribute TOKEN_ATTRIBUTE = TokenAttributeTestFactory.create(); + private static final Map<String, String> TOKEN_ATTRIBUTES_MAP = TokenAttributeTestFactory.asMap(); @Mock private Response token; @BeforeEach void init() { - doReturn(ADJUSTED_TOKEN_ATTRIBUTES_MAP).when(service).adjustAttributes(any(), any()); - doReturn(TOKEN_ATTRIBUTE).when(service).buildTokenAttribute(any()); + doReturn(true).when(service).isNotMappedField(any()); + doReturn(TokenAttributesTestFactory.OTHER_ATTRIBUTE).when(service).buildTokenAttribute(any()); + } + + @Test + void shouldCallGetPostfachId() { + buildTokenAttributes(); + + verify(service).getPostfachId(TOKEN_ATTRIBUTES_MAP, token); + } + + @Test + void shouldSetPostfachId() { + doReturn(TokenAttributesTestFactory.POSTFACH_ID).when(service).getPostfachId(any(), any()); + + var result = buildTokenAttributes(); + + assertThat(result.getPostfachId()).isEqualTo(TokenAttributesTestFactory.POSTFACH_ID); + } + + @Test + void shouldCallGetTrustLevel() { + buildTokenAttributes(); + + verify(service).getTrustLevel(TOKEN_ATTRIBUTES_MAP); + } + + @Test + void shouldSetTrustLevel() { + doReturn(TokenAttributesTestFactory.TRUST_LEVEL).when(service).getTrustLevel(any()); + + var result = buildTokenAttributes(); + + assertThat(result.getTrustLevel()).isEqualTo(TokenAttributesTestFactory.TRUST_LEVEL); } @Test - void shouldCallAdjustAttribute() { + void shouldCallIsNotMappedField() { buildTokenAttributes(); - verify(service).adjustAttributes(TokenAttributeTestFactory.asMap(), token); + verify(service).isNotMappedField(Map.entry(TokenAttributeTestFactory.NAME, TokenAttributeTestFactory.VALUE)); } @Test void shouldCallBuildTokenAttribute() { buildTokenAttributes(); - verify(service).buildTokenAttribute(ADJUSTED_TOKEN_ATTRIBUTES_ENTRY); + verify(service).buildTokenAttribute(Map.entry(TokenAttributeTestFactory.NAME, TokenAttributeTestFactory.VALUE)); } @Test - void shouldReturnTokenAttributes() { + void shouldSetOtherAttributes() { var result = buildTokenAttributes(); - assertThat(result).containsExactly(TOKEN_ATTRIBUTE); + assertThat(result.getOtherAttributes()).containsExactly(TokenAttributesTestFactory.OTHER_ATTRIBUTE); } - private Set<TokenAttribute> buildTokenAttributes() { - return service.buildTokenAttributes(TokenAttributeTestFactory.asMap(), token); + private TokenAttributes buildTokenAttributes() { + return service.buildTokenAttributes(TOKEN_ATTRIBUTES_MAP, token); } } @Nested - class TestAdjustAttributes { - - private static final Map<String, String> TOKEN_ATTRIBUTES_MAP = TokenAttributeTestFactory.asMap(); - private static final Map<String, String> ADJUSTED_TOKEN_ATTRIBUTES_MAP = Map.of("key", "value"); - private static final Map<String, String> REPLACED_TOKEN_ATTRIBUTES_MAP = Map.of("key", "value"); + class TestGetPostfachId { @Mock private Response token; - @Captor - private ArgumentCaptor<Map<String, String>> tokenAttributesCaptor; - @BeforeEach - void init() { - when(service.replaceKeys(any())).thenReturn(REPLACED_TOKEN_ATTRIBUTES_MAP); - when(service.adjustPostfachIdAttribute(any(), any())).thenReturn(ADJUSTED_TOKEN_ATTRIBUTES_MAP); - } + @Nested + class TestTokenId { - @Test - void shouldCallReplaceKeys() { - adjustAttributes(); + private static final String TOKEN_ID = UUID.randomUUID().toString(); - verify(service).replaceKeys(tokenAttributesCaptor.capture()); - assertThat(tokenAttributesCaptor.getValue()).isEqualTo(TOKEN_ATTRIBUTES_MAP); - assertDoesNotThrow(() -> tokenAttributesCaptor.getValue().put("key", "value")); - } + @BeforeEach + void init() { + when(tokenValidationProperty.isUseIdAsPostfachId()).thenReturn(true); + doReturn(TOKEN_ID).when(token).getID(); + } - @Test - void shouldCallAdjustPostfachIdAttributes() { - adjustAttributes(); + @Test + void shouldCallGetTokenId() { + getPostfachId(); + + verify(service).getPostfachId(TokenAttributeTestFactory.asMap(), token); + } - verify(service).adjustPostfachIdAttribute(REPLACED_TOKEN_ATTRIBUTES_MAP, token); + @Test + void shouldReturnTokenId() { + var result = getPostfachId(); + + assertThat(result).contains(TOKEN_ID); + } } - @Test - void shouldReturnUnmodifiableMap() { - var result = adjustAttributes(); + @Nested + class TestTokenAttribute { + + @BeforeEach + void init() { + when(tokenValidationProperty.isUseIdAsPostfachId()).thenReturn(false); + doReturn(TokenAttributeTestFactory.VALUE).when(service).getMappedValue(any(), any()); + } + + @Test + void shouldCallGetMappedValue() { + getPostfachId(); - assertThat(result).isEqualTo(REPLACED_TOKEN_ATTRIBUTES_MAP); - Assertions.assertThrows(UnsupportedOperationException.class, () -> result.put("key", "value")); + verify(service).getMappedValue(TokenAttributeTestFactory.asMap(), TokenAttributes.POSTFACH_ID_KEY); + } + + @Test + void shouldReturnMappedValue() { + var result = getPostfachId(); + + assertThat(result).contains(TokenAttributeTestFactory.VALUE); + } } - private Map<String, String> adjustAttributes() { - return service.adjustAttributes(TOKEN_ATTRIBUTES_MAP, token); + private String getPostfachId() { + return service.getPostfachId(TokenAttributeTestFactory.asMap(), token); } } @Nested - class TestReplaceKeys { + class TestGetTrustLevel { - private static final String MAPPING_KEY = LoremIpsum.getInstance().getWords(1); + @BeforeEach + void init() { + doReturn(TokenAttributeTestFactory.VALUE).when(service).getMappedValue(any(), any()); + } @Test - void shouldReplaceValue() { - when(tokenValidationProperty.getMappings()).thenReturn(Map.of(MAPPING_KEY, TokenAttributeTestFactory.NAME)); + void shouldCallGetMappedValue() { + getTrustLevel(); - var result = service.replaceKeys(TokenAttributeTestFactory.asMap()); + verify(service).getMappedValue(TokenAttributeTestFactory.asMap(), TokenAttributes.TRUST_LEVEL_KEY); + } - assertThat(result).containsOnly(Map.entry(MAPPING_KEY, TokenAttributeTestFactory.VALUE)); + @Test + void shouldReturnMappedValue() { + var result = getTrustLevel(); + + assertThat(result).contains(TokenAttributeTestFactory.VALUE); } + private String getTrustLevel() { + return service.getTrustLevel(TokenAttributeTestFactory.asMap()); + } } @Nested - class TestAdjustPostfachIdAttribute { + class TestGetMappedValue { - private static final String POSTFACH_ID = LoremIpsum.getInstance().getWords(1); - - @Mock - private Response token; + private static final String MAPPED_KEY = UUID.randomUUID().toString(); @Test - void shouldSetPostfachId() { - when(tokenValidationProperty.isUseIdAsPostfachId()).thenReturn(true); - when(token.getID()).thenReturn(POSTFACH_ID); + void shouldReturnValueByMappedKey() { + when(tokenValidationProperty.getMappings()).thenReturn(Map.of(TokenAttributeTestFactory.NAME, MAPPED_KEY)); - var result = service.adjustPostfachIdAttribute(TokenAttributeTestFactory.asMap(), token); + var result = service.getMappedValue(Map.of(MAPPED_KEY, TokenAttributeTestFactory.VALUE), TokenAttributeTestFactory.NAME); - assertThat(result).containsEntry(TokenAttribute.POSTFACH_ID_KEY, POSTFACH_ID); + assertThat(result).isEqualTo(TokenAttributeTestFactory.VALUE); } @Test - void shouldRewritePostfachId() { - when(tokenValidationProperty.isUseIdAsPostfachId()).thenReturn(true); - var attributeMap = new HashMap<>(Map.of(TokenAttribute.POSTFACH_ID_KEY, LoremIpsum.getInstance().getWords(1))); - when(token.getID()).thenReturn(POSTFACH_ID); + void shouldReturnValueByKey() { + var result = service.getMappedValue(TokenAttributeTestFactory.asMap(), TokenAttributeTestFactory.NAME); + + assertThat(result).isEqualTo(TokenAttributeTestFactory.VALUE); + } + } + + @Nested + class TestIsNotMappedField { - var result = service.adjustPostfachIdAttribute(attributeMap, token); + private static final String KEY = UUID.randomUUID().toString(); - assertThat(result).containsEntry(TokenAttribute.POSTFACH_ID_KEY, POSTFACH_ID); + @Test + void shouldReturnTrueWhenNotMapped() { + var result = isNotMappedField(); + + assertThat(result).isTrue(); } @Test - void shouldReturnUnchangedMap() { - when(tokenValidationProperty.isUseIdAsPostfachId()).thenReturn(false); - var attributeMap = TokenAttributeTestFactory.asMap(); + void shouldReturnFalseWhenMapped() { + when(tokenValidationProperty.getMappings()).thenReturn(Map.of(KEY, TokenAttributeTestFactory.NAME)); - var result = service.adjustPostfachIdAttribute(attributeMap, token); + var result = service.isNotMappedField(Map.entry(TokenAttributeTestFactory.NAME, TokenAttributeTestFactory.VALUE)); + + assertThat(result).isFalse(); + } - assertThat(result).isEqualTo(attributeMap); + private boolean isNotMappedField() { + return service.isNotMappedField(Map.entry(TokenAttributeTestFactory.NAME, TokenAttributeTestFactory.VALUE)); } } @@ -578,7 +763,7 @@ class SamlAttributeServiceTest { void shouldBuildTokenAttribute() { var result = service.buildTokenAttribute(Map.entry(TokenAttributeTestFactory.NAME, TokenAttributeTestFactory.VALUE)); - assertThat(result).isEqualTo(TokenAttributeTestFactory.create()); + assertThat(result).usingRecursiveComparison().isEqualTo(TokenAttributeTestFactory.create()); } } } \ No newline at end of file