diff --git a/de.srsoftware.oidc.app/src/main/java/de/srsoftware/oidc/app/Application.java b/de.srsoftware.oidc.app/src/main/java/de/srsoftware/oidc/app/Application.java index a44ec03..a1b23e6 100644 --- a/de.srsoftware.oidc.app/src/main/java/de/srsoftware/oidc/app/Application.java +++ b/de.srsoftware.oidc.app/src/main/java/de/srsoftware/oidc/app/Application.java @@ -18,6 +18,7 @@ import de.srsoftware.logging.ColorLogger; import de.srsoftware.oidc.api.*; import de.srsoftware.oidc.api.data.User; import de.srsoftware.oidc.backend.*; +import de.srsoftware.oidc.datastore.encrypted.EncryptedClientService; import de.srsoftware.oidc.datastore.encrypted.EncryptedMailConfig; import de.srsoftware.oidc.datastore.encrypted.EncryptedUserService; import de.srsoftware.oidc.datastore.file.FileStoreProvider; @@ -87,11 +88,19 @@ public class Application { } private static ClientService setupClientService(Configuration config, Path defaultFile, FileStoreProvider fileStoreProvider) throws SQLException { - var clientStore = new File(config.getOrDefault("client_store", defaultFile)); - return switch (extension(clientStore)) { + var clientStore = new File(config.getOrDefault("client_store", defaultFile)); + var clientService = switch (extension(clientStore)) { case "db", "sqlite", "sqlite3" -> new SqliteClientService(connectionProvider.get(clientStore)); default -> fileStoreProvider.get(clientStore); }; + + Optional encryptionKey = config.get(ENCRYPTION_KEY); + + if (encryptionKey.isPresent()){ + var salt = config.getOrDefault(SALT,uuid()); + clientService = new EncryptedClientService(encryptionKey.get(),salt,clientService); + } + return clientService; } private static AuthorizationService setupAuthService(Configuration config, Path defaultFile, FileStoreProvider fileStoreProvider) throws SQLException { @@ -119,7 +128,6 @@ public class Application { Optional encryptionKey = config.get(ENCRYPTION_KEY); - if (encryptionKey.isPresent()){ var salt = config.getOrDefault(SALT,uuid()); mailConfig = new EncryptedMailConfig(mailConfig,encryptionKey.get(),salt); diff --git a/de.srsoftware.oidc.datastore.encrypted/src/main/java/de/srsoftware/oidc/datastore/encrypted/EncryptedClientService.java b/de.srsoftware.oidc.datastore.encrypted/src/main/java/de/srsoftware/oidc/datastore/encrypted/EncryptedClientService.java new file mode 100644 index 0000000..05a1884 --- /dev/null +++ b/de.srsoftware.oidc.datastore.encrypted/src/main/java/de/srsoftware/oidc/datastore/encrypted/EncryptedClientService.java @@ -0,0 +1,64 @@ +/* © SRSoftware 2024 */ +package de.srsoftware.oidc.datastore.encrypted; + +import static java.util.Optional.empty; + +import de.srsoftware.oidc.api.ClientService; +import de.srsoftware.oidc.api.data.Client; +import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; + +public class EncryptedClientService extends EncryptedConfig implements ClientService { + private final ClientService backend; + + public EncryptedClientService(String key, String salt, ClientService backend) { + super(key, salt); + this.backend = backend; + } + + public Client decrypt(Client client) { + var decryptedUrls = client.redirectUris().stream().map(this::decrypt).collect(Collectors.toSet()); + return new Client(decrypt(client.id()), decrypt(client.name()), decrypt(client.secret()), decryptedUrls).landingPage(decrypt(client.landingPage())); + } + + public Client encrypt(Client client) { + var encryptedUrls = client.redirectUris().stream().map(this::encrypt).collect(Collectors.toSet()); + return new Client(encrypt(client.id()), encrypt(client.name()), encrypt(client.secret()), encryptedUrls).landingPage(encrypt(client.landingPage())); + } + + @Override + public Optional getClient(String clientId) { + if (clientId == null || clientId.isBlank()) return empty(); + for (var encrypted : backend.listClients()) { + var decrypted = decrypt(encrypted); + if (decrypted.id().equals(clientId)) return Optional.of(decrypted); + } + return empty(); + } + + @Override + public List listClients() { + return backend.listClients().stream().map(this::decrypt).toList(); + } + + @Override + public ClientService remove(String clientId) { + if (clientId == null || clientId.isBlank()) return this; + for (var encrypted : backend.listClients()) { + var decrypted = decrypt(encrypted); + if (decrypted.id().equals(clientId)) { + backend.remove(encrypted.id()); + break; + } + } + return this; + } + + @Override + public ClientService save(Client client) { + remove(client.id()); + backend.save(encrypt(client)); + return this; + } +} diff --git a/de.srsoftware.oidc.datastore.encrypted/src/test/java/EncryptedClientServiceTest.java b/de.srsoftware.oidc.datastore.encrypted/src/test/java/EncryptedClientServiceTest.java new file mode 100644 index 0000000..1ae533c --- /dev/null +++ b/de.srsoftware.oidc.datastore.encrypted/src/test/java/EncryptedClientServiceTest.java @@ -0,0 +1,55 @@ +/* © SRSoftware 2024 */ +import static de.srsoftware.utils.Optionals.nullable; +import static de.srsoftware.utils.Strings.uuid; + +import de.srsoftware.oidc.api.ClientService; +import de.srsoftware.oidc.api.ClientServiceTest; +import de.srsoftware.oidc.api.data.Client; +import de.srsoftware.oidc.datastore.encrypted.EncryptedClientService; +import java.sql.SQLException; +import java.util.HashMap; +import java.util.List; +import java.util.Optional; +import org.junit.jupiter.api.BeforeEach; + +public class EncryptedClientServiceTest extends ClientServiceTest { + private static class InMemoryClientService implements ClientService { + private HashMap clients = new HashMap<>(); + + @Override + public Optional getClient(String clientId) { + return nullable(clients.get(clientId)); + } + + @Override + public List listClients() { + return List.copyOf(clients.values()); + } + + @Override + public ClientService remove(String clientId) { + clients.remove(clientId); + return this; + } + + @Override + public ClientService save(Client client) { + clients.put(client.id(), client); + return this; + } + } + private ClientService clientService; + + @Override + protected ClientService clientService() { + return clientService; + } + + @BeforeEach + public void setup() throws SQLException { + var secret = uuid(); + var salt = uuid(); + var backend = new InMemoryClientService(); + clientService = new EncryptedClientService(secret, salt, backend); + } +}