Skip to content

Commit

Permalink
Merge branch 'dev' into 644-apierror-durch-wlsexception-ersetzen
Browse files Browse the repository at this point in the history
  • Loading branch information
vjohnslhm authored Dec 16, 2024
2 parents dda34a3 + 779db40 commit 70dd896
Show file tree
Hide file tree
Showing 14 changed files with 254 additions and 195 deletions.
2 changes: 1 addition & 1 deletion wls-auth-service/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -393,7 +393,7 @@
<configuration>
<environmentVariables>
<SPRING_PROFILES_ACTIVE>db-h2,dummy.clients</SPRING_PROFILES_ACTIVE>
<SERVICEAUTH.CRYPTO.KEY>secret</SERVICEAUTH.CRYPTO.KEY>
<SERVICE.CONFIG.CRYPTO.KEY>please change me</SERVICE.CONFIG.CRYPTO.KEY>
<SERVICE.CONFIG.OAUTH2.JWK.RSA.INIT.SEED>seed</SERVICE.CONFIG.OAUTH2.JWK.RSA.INIT.SEED>
</environmentVariables>
</configuration>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package de.muenchen.oss.wahllokalsystem.wls.common.security;
package de.muenchen.oss.wahllokalsystem.authservice.configuration;

import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
Expand All @@ -12,7 +12,7 @@ public class AESEncryptionConfiguration {

private static final String AES = "AES";

@Value("${app.crypto.key}")
@Value("${service.config.crypto.key}")
String key;

@Bean
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class ExceptionConstants {

public static final String CRYPTO_EXCEPTION_CODE = "399";

public static ExceptionDataWrapper KOMMUNIKATIONSFEHLER_MIT_KONFIGSERVICE = new ExceptionDataWrapper(
"100", "Bei der Kommunikation mit dem Konfigurationsservice kam es zu einem Fehler.");
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/**
*
*/
package de.muenchen.oss.wahllokalsystem.wls.common.security;
package de.muenchen.oss.wahllokalsystem.authservice.security;

import de.muenchen.oss.wahllokalsystem.wls.common.exception.TechnischeWlsException;
import de.muenchen.oss.wahllokalsystem.wls.common.exception.util.ServiceIDFormatter;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,37 +1,85 @@
package de.muenchen.oss.wahllokalsystem.authservice.service;

import de.muenchen.oss.wahllokalsystem.wls.common.security.EncryptionBuilder;
import lombok.RequiredArgsConstructor;
import de.muenchen.oss.wahllokalsystem.authservice.exception.ExceptionConstants;
import de.muenchen.oss.wahllokalsystem.wls.common.exception.TechnischeWlsException;
import de.muenchen.oss.wahllokalsystem.wls.common.exception.util.ServiceIDFormatter;
import java.util.Base64;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import lombok.val;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

@Service
@RequiredArgsConstructor
@Slf4j
@Getter
@Setter
public class CryptoService {

private final EncryptionBuilder encryptionBuilder;
private final ServiceIDFormatter formatter;
private final Cipher encryptionCipher;
private final Cipher decryptionCipher;

@Value("${service.config.crypto.encryptionPrefix}")
private String encryptedPrefix = "";

public CryptoService(ServiceIDFormatter formatter,
@Qualifier("encryptionCipher") Cipher encryptionCipher,
@Qualifier("decryptionCipher") Cipher decryptionCipher) {
this.formatter = formatter;
this.encryptionCipher = encryptionCipher;
this.decryptionCipher = decryptionCipher;
}

public boolean isEncrypted(final String value) {
return value.startsWith(encryptedPrefix);
}

public String encrypt(final String value) {
return encryptedPrefix + encryptionBuilder.encryptValue(value);
return encryptedPrefix + encryptValue(value);
}

public String decrypt(final String value) {
if (value.startsWith(encryptedPrefix)) {
if (isEncrypted(value)) {
val encryptedSubstring = value.substring(encryptedPrefix.length());
return encryptionBuilder.decryptValue(encryptedSubstring);
return decryptValue(encryptedSubstring);
} else {
log.warn("value was already decrypted");
return value;
}
}

private String decryptValue(String value) {
if (value != null && !value.isEmpty()) {
try {
val decode = Base64.getUrlDecoder().decode(value.getBytes());
val finalized = decryptionCipher.doFinal(decode);
return new String(finalized);
} catch (IllegalBlockSizeException | BadPaddingException e) {
log.error("Unable to decrypt the value due to " + e.getClass().getSimpleName() + ". Using direct object reference!", e);
throw TechnischeWlsException.withCode(ExceptionConstants.CRYPTO_EXCEPTION_CODE).inService(formatter.getId())
.buildWithMessage("Problem bei der Entschlüsselung von Objekt-Referenzen");
}
}
return value;
}

private String encryptValue(String value) {
if (value != null && !value.isEmpty()) {
try {
val finalized = encryptionCipher.doFinal(value.getBytes());
value = Base64.getUrlEncoder().encodeToString(finalized);
} catch (IllegalBlockSizeException | BadPaddingException e) {
log.error("Unable to encrypt the value due to " + e.getClass().getSimpleName() + ". Using direct object reference!", e);
throw TechnischeWlsException.withCode(ExceptionConstants.CRYPTO_EXCEPTION_CODE).inService(formatter.getId())
.buildWithMessage("Problem bei der Verschlüsselung von Objekt-Referenzen");
}
}
return value;
}
}
7 changes: 3 additions & 4 deletions wls-auth-service/src/main/resources/application-local.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,10 @@ server:

service:
config:
crypto:
key: "please change me"
oauth2:
jwk:
rsa:
init:
seed: change_me
serviceauth:
crypto:
key: "please change me"
seed: change_me
2 changes: 2 additions & 0 deletions wls-auth-service/src/main/resources/application-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ spring:

service:
config:
crypto:
key: "please change me"
oauth2:
jwk:
rsa:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,12 @@
import org.springframework.test.context.ActiveProfiles;
import org.springframework.transaction.support.TransactionTemplate;

@SpringBootTest(classes = MicroServiceApplication.class, properties = { "serviceauth.crypto.key=secret" })
@SpringBootTest(classes = MicroServiceApplication.class, properties = { "service.config.crypto.key=veryLongAndVerySaveKeyIHopeXXXabc123!!" })
@ActiveProfiles({ TestConstants.SPRING_TEST_PROFILE, Profiles.DUMMY_CLIENTS })
class UserRepositoryImplIntegrationTest {

private static final String USERNAME_UNENCRYPTED = "username";
private static final String USERNAME_ENCRYPTED = "ENCRYPTED:AcZZ7iVGyYoE-DTb9rNwgQ==";
private static final String USERNAME_ENCRYPTED = "ENCRYPTED:TLXm2wsx1kcDLHHU8ZWptQ==";

@Autowired
UserRepositoryImpl userRepository;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@

@SpringBootTest(
classes = MicroServiceApplication.class,
properties = { "service.config.crypto.key=secret", "service.config.user.authority.wahlvorstand=" + PROP_USER_AUTHORITY_WAHLVORSTAND }
properties = { "service.config.user.authority.wahlvorstand=" + PROP_USER_AUTHORITY_WAHLVORSTAND }
)
@AutoConfigureMockMvc
@ActiveProfiles({ TestConstants.SPRING_TEST_PROFILE, TestConstants.SPRING_NO_SECURITY_PROFILE, Profiles.DUMMY_CLIENTS })
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package de.muenchen.oss.wahllokalsystem.authservice.service;

import de.muenchen.oss.wahllokalsystem.authservice.configuration.AESEncryptionConfiguration;
import de.muenchen.oss.wahllokalsystem.wls.common.exception.util.ServiceIDFormatter;
import lombok.val;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest(
classes = { AESEncryptionConfiguration.class, CryptoService.class, ServiceIDFormatter.class },
properties = { "service.config.crypto.key = 770A8A65DA156D24EE2A093277530142" }
)
class CryptoServiceIntegrationTest {

@Autowired
CryptoService cryptoService;

@Test
void should_useProvidedBeans_when_startingContext() {
val valueToEncrypt = "Mzc2NTI2NzIzQUZEQUIzRA==";
Assertions.assertThat(cryptoService.decrypt(cryptoService.encrypt(valueToEncrypt))).isEqualTo(valueToEncrypt);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
package de.muenchen.oss.wahllokalsystem.authservice.service;

import de.muenchen.oss.wahllokalsystem.authservice.exception.ExceptionConstants;
import de.muenchen.oss.wahllokalsystem.wls.common.exception.TechnischeWlsException;
import de.muenchen.oss.wahllokalsystem.wls.common.exception.util.ServiceIDFormatter;
import java.util.Base64;
import java.util.Set;
import java.util.stream.Stream;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import lombok.val;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.junit.jupiter.MockitoExtension;

@ExtendWith(MockitoExtension.class)
class CryptoServiceTest {

private static final String ENCRYPTION_PREFIX = "encryptionPrefix";

@Mock
ServiceIDFormatter idFormatter;

@Mock
Cipher cipher;

@InjectMocks
CryptoService unitUnderTest;

@BeforeEach
void setUp() {
unitUnderTest.setEncryptedPrefix(ENCRYPTION_PREFIX);
}

@Nested
class IsEncrypted {

@Test
void should_returnTrue_when_valueStartsWithPrefix() {
Assertions.assertThat(unitUnderTest.isEncrypted(ENCRYPTION_PREFIX + "the encrypted value")).isTrue();
}

@Test
void should_returnFalse_when_valueDoesNotStartWithPrefix() {
unitUnderTest.setEncryptedPrefix("prefix");
Assertions.assertThat(unitUnderTest.isEncrypted("values without encryption prefix")).isFalse();
}
}

@Nested
class Encrypt {

@Test
void should_returnEncryptedValueWithPrefix_when_valueIsGiven() throws Exception {
val valueToEncrypt = "hello world";

val mockedEncryptedValue = "encrypted value".getBytes();
Mockito.when(cipher.doFinal(valueToEncrypt.getBytes())).thenReturn(mockedEncryptedValue);

val result = unitUnderTest.encrypt(valueToEncrypt);

val expectedResult = ENCRYPTION_PREFIX + Base64.getEncoder().encodeToString(mockedEncryptedValue);
Assertions.assertThat(result).isEqualTo(expectedResult);
}

@Test
void should_returnEncryptionPrefix_when_emptyStringValueIsGiven() {
Assertions.assertThat(unitUnderTest.encrypt("")).isEqualTo(ENCRYPTION_PREFIX);
}

@Test
void should_returnEncryptionPrefix_when_nullIsGiven() {
Assertions.assertThat(unitUnderTest.encrypt(null)).isEqualTo(ENCRYPTION_PREFIX + null);
}

@ParameterizedTest
@MethodSource("de.muenchen.oss.wahllokalsystem.authservice.service.CryptoServiceTest#exceptionsMappedToWlsException")
void should_throwTechnischeWlsException_when_cipherThrowsException(final Exception exceptionThrownByCipher) throws Exception {
val valueToEncrypt = "hello world";

val mockedServiceID = "authService";
Mockito.when(idFormatter.getId()).thenReturn(mockedServiceID);
Mockito.doThrow(exceptionThrownByCipher).when(cipher).doFinal(valueToEncrypt.getBytes());

val expectedException = TechnischeWlsException.withCode(ExceptionConstants.CRYPTO_EXCEPTION_CODE).inService(mockedServiceID)
.buildWithMessage("");

Assertions.assertThatThrownBy(() -> unitUnderTest.encrypt(valueToEncrypt))
.satisfies(exception -> {
Assertions.assertThat(exception)
.usingRecursiveComparison()
.ignoringFields("message")
.isEqualTo(expectedException);
Assertions.assertThat(exception).hasNoNullFieldsOrProperties();
});
}
}

@Nested
class Decrypt {

@Test
void should_returnValue_when_valueIsNotEncrypted() {
val notEncryptedValue = Base64.getEncoder().encodeToString("not encrypted value".getBytes());
Assertions.assertThat(unitUnderTest.decrypt(notEncryptedValue)).isEqualTo(notEncryptedValue);
}

@Test
void should_returnDecryptedValue_when_valueIsGiven() throws Exception {
val encryptedValue = "the encrypted value";
val encryptedValueAsBase64WithPrefix = ENCRYPTION_PREFIX + Base64.getEncoder().encodeToString(encryptedValue.getBytes());

val mockDecrypted = "decrypted value";
Mockito.when(cipher.doFinal(encryptedValue.getBytes())).thenReturn(mockDecrypted.getBytes());

val result = unitUnderTest.decrypt(encryptedValueAsBase64WithPrefix);

Assertions.assertThat(result).isEqualTo(mockDecrypted);
}

@ParameterizedTest
@MethodSource("de.muenchen.oss.wahllokalsystem.authservice.service.CryptoServiceTest#exceptionsMappedToWlsException")
void should_throwTechnischeWlsException_when_cipherThrowsException(final Exception exceptionThrownByCipher) throws Exception {
val encryptedValue = "the encrypted value";
val encryptedValueAsBase64WithPrefix = ENCRYPTION_PREFIX + Base64.getEncoder().encodeToString(encryptedValue.getBytes());

val mockedServiceID = "authService";
Mockito.when(idFormatter.getId()).thenReturn(mockedServiceID);
Mockito.doThrow(exceptionThrownByCipher).when(cipher).doFinal(encryptedValue.getBytes());

val expectedException = TechnischeWlsException.withCode(ExceptionConstants.CRYPTO_EXCEPTION_CODE).inService(mockedServiceID)
.buildWithMessage("");

Assertions.assertThatThrownBy(() -> unitUnderTest.decrypt(encryptedValueAsBase64WithPrefix))
.satisfies(exception -> {
Assertions.assertThat(exception)
.usingRecursiveComparison()
.ignoringFields("message")
.isEqualTo(expectedException);
Assertions.assertThat(exception).hasNoNullFieldsOrProperties();
});
}
}

public static Stream<Arguments> exceptionsMappedToWlsException() {
val exceptions = Set.of(new IllegalBlockSizeException(), new BadPaddingException());

return exceptions.stream().map(exception -> Arguments.of(exception, exception.getClass().getName()));
}
}
Loading

0 comments on commit 70dd896

Please sign in to comment.