Browse Source

implemented EncryptedUserService

Signed-off-by: Stephan Richter <s.richter@srsoftware.de>
sqlite
Stephan Richter 2 months ago
parent
commit
7bbf4be984
  1. 4
      de.srsoftware.oidc.api/src/main/java/de/srsoftware/oidc/api/UserService.java
  2. 4
      de.srsoftware.oidc.api/src/main/java/de/srsoftware/oidc/api/data/User.java
  3. 11
      de.srsoftware.oidc.app/src/main/java/de/srsoftware/oidc/app/Application.java
  4. 2
      de.srsoftware.oidc.datastore.encrypted/build.gradle
  5. 67
      de.srsoftware.oidc.datastore.encrypted/src/main/java/de/srsoftware/oidc/datastore/encrypted/EncryptedUserService.java
  6. 113
      de.srsoftware.oidc.datastore.encrypted/src/test/java/EncryptedUserServiceTest.java

4
de.srsoftware.oidc.api/src/main/java/de/srsoftware/oidc/api/UserService.java

@ -29,6 +29,6 @@ public interface UserService {
public Optional<User> load(String id); public Optional<User> load(String id);
public Optional<User> load(String username, String password); public Optional<User> load(String username, String password);
public boolean passwordMatches(String plaintextPassword, User user); public boolean passwordMatches(String plaintextPassword, User user);
public <T extends UserService> T save(User user); public UserService save(User user);
public <T extends UserService> T updatePassword(User user, String plaintextPassword); public UserService updatePassword(User user, String plaintextPassword);
} }

4
de.srsoftware.oidc.api/src/main/java/de/srsoftware/oidc/api/data/User.java

@ -96,6 +96,10 @@ public final class User {
return Optional.of(user); return Optional.of(user);
} }
public Set<Permission> permissions() {
return Set.copyOf(permissions);
}
public String realName() { public String realName() {
return realName; return realName;
} }

11
de.srsoftware.oidc.app/src/main/java/de/srsoftware/oidc/app/Application.java

@ -19,6 +19,7 @@ import de.srsoftware.oidc.api.*;
import de.srsoftware.oidc.api.data.User; import de.srsoftware.oidc.api.data.User;
import de.srsoftware.oidc.backend.*; import de.srsoftware.oidc.backend.*;
import de.srsoftware.oidc.datastore.encrypted.EncryptedMailConfig; import de.srsoftware.oidc.datastore.encrypted.EncryptedMailConfig;
import de.srsoftware.oidc.datastore.encrypted.EncryptedUserService;
import de.srsoftware.oidc.datastore.file.FileStoreProvider; import de.srsoftware.oidc.datastore.file.FileStoreProvider;
import de.srsoftware.oidc.datastore.file.PlaintextKeyStore; import de.srsoftware.oidc.datastore.file.PlaintextKeyStore;
import de.srsoftware.oidc.datastore.sqlite.*; import de.srsoftware.oidc.datastore.sqlite.*;
@ -128,10 +129,18 @@ public class Application {
private static UserService setupUserService(Configuration config, Path defaultFile, FileStoreProvider fileStoreProvider, UuidHasher passHasher) throws SQLException { private static UserService setupUserService(Configuration config, Path defaultFile, FileStoreProvider fileStoreProvider, UuidHasher passHasher) throws SQLException {
var userStorageLocation = new File(config.getOrDefault("user_storage",defaultFile)); var userStorageLocation = new File(config.getOrDefault("user_storage",defaultFile));
return switch (extension(userStorageLocation).toLowerCase()){ var userService = switch (extension(userStorageLocation).toLowerCase()){
case "db", "sqlite", "sqlite3" -> new SqliteUserService(connectionProvider.get(userStorageLocation),passHasher); case "db", "sqlite", "sqlite3" -> new SqliteUserService(connectionProvider.get(userStorageLocation),passHasher);
default -> fileStoreProvider.get(userStorageLocation); default -> fileStoreProvider.get(userStorageLocation);
}; };
Optional<String> encryptionKey = config.get(ENCRYPTION_KEY);
if (encryptionKey.isPresent()){
var salt = config.getOrDefault(SALT,uuid());
userService = new EncryptedUserService(userService,encryptionKey.get(),salt,passHasher);
}
return userService;
} }
private static KeyStorage setupKeyStore(Configuration config, Path defaultConfigDir) throws SQLException { private static KeyStorage setupKeyStore(Configuration config, Path defaultConfigDir) throws SQLException {

2
de.srsoftware.oidc.datastore.encrypted/build.gradle

@ -15,7 +15,7 @@ dependencies {
implementation project(':de.srsoftware.oidc.api') implementation project(':de.srsoftware.oidc.api')
implementation 'com.sun.mail:jakarta.mail:2.0.1' implementation 'com.sun.mail:jakarta.mail:2.0.1'
implementation project(':de.srsoftware.utils') implementation project(':de.srsoftware.utils')
testImplementation project(path: ':de.srsoftware.oidc.api', configuration: "testBundle")
} }
test { test {

67
de.srsoftware.oidc.datastore.encrypted/src/main/java/de/srsoftware/oidc/datastore/encrypted/EncryptedUserService.java

@ -1,21 +1,25 @@
/* © SRSoftware 2024 */
package de.srsoftware.oidc.datastore.encrypted; package de.srsoftware.oidc.datastore.encrypted;
import static java.util.Optional.empty;
import de.srsoftware.oidc.api.UserService; import de.srsoftware.oidc.api.UserService;
import de.srsoftware.oidc.api.data.AccessToken; import de.srsoftware.oidc.api.data.AccessToken;
import de.srsoftware.oidc.api.data.User; import de.srsoftware.oidc.api.data.User;
import de.srsoftware.utils.Optionals; import de.srsoftware.utils.PasswordHasher;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Optional; import java.util.Optional;
import java.util.Set; import java.util.Set;
import java.util.stream.Collectors;
public class EncryptedUserService extends EncryptedConfig implements UserService { public class EncryptedUserService extends EncryptedConfig implements UserService {
private final UserService backend; private final UserService backend;
private final PasswordHasher hasher;
EncryptedUserService(UserService backend, String key, String salt){ public EncryptedUserService(UserService backend, String key, String salt, PasswordHasher passHasher) {
super(key,salt); super(key, salt);
this.backend = backend; this.backend = backend;
hasher = passHasher;
} }
@Override @Override
@ -28,18 +32,28 @@ public class EncryptedUserService extends EncryptedConfig implements UserService
return backend.consumeToken(accessToken).map(this::decrypt); return backend.consumeToken(accessToken).map(this::decrypt);
} }
public User decrypt(User secret){ public User decrypt(User secret) {
return secret; var decrypted = new User(decrypt(secret.username()), decrypt(secret.hashedPassword()), decrypt(secret.realName()), decrypt(secret.email()), decrypt(secret.uuid())).sessionDuration(secret.sessionDuration());
secret.permissions().forEach(decrypted::add);
return decrypted;
} }
@Override @Override
public UserService delete(User user) { public UserService delete(User user) {
backend.delete(encrypt(user)); for (var encryptedUser : backend.list()) {
var decryptedUser = decrypt(encryptedUser);
if (decryptedUser.uuid().equals(user.uuid())) {
backend.delete(encryptedUser);
break;
}
}
return this; return this;
} }
public User encrypt(User plain){ public User encrypt(User plain) {
return plain; var encrypted = new User(encrypt(plain.username()), encrypt(plain.hashedPassword()), encrypt(plain.realName()), encrypt(plain.email()), encrypt(plain.uuid())).sessionDuration(plain.sessionDuration());
plain.permissions().forEach(encrypted::add);
return encrypted;
} }
@Override @Override
@ -60,33 +74,50 @@ return secret;
@Override @Override
public Set<User> find(String idOrEmail) { public Set<User> find(String idOrEmail) {
return backend.find(idOrEmail).stream().map(this::decrypt).collect(Collectors.toSet()); if (idOrEmail == null || idOrEmail.isBlank()) return Set.of();
var matching = new HashMap<String, User>();
for (var encryptedUser : backend.list()) {
var decryptedUser = decrypt(encryptedUser);
if (idOrEmail.equals(decryptedUser.uuid()) || idOrEmail.equals(decryptedUser.email()) || idOrEmail.equals(decryptedUser.username()) || decryptedUser.realName().contains(idOrEmail)) matching.put(decryptedUser.uuid(), decryptedUser);
}
return Set.copyOf(matching.values());
} }
@Override @Override
public Optional<User> load(String id) { public Optional<User> load(String id) {
return backend.load(id).map(this::decrypt); if (id == null || id.isBlank()) return empty();
for (var encryptedUser : backend.list()) {
var decryptedUser = decrypt(encryptedUser);
if (id.equals(decryptedUser.uuid())) return Optional.of(decryptedUser);
}
return empty();
} }
@Override @Override
public Optional<User> load(String username, String password) { public Optional<User> load(String username, String password) {
return backend.load(encrypt(username),encrypt(password)); if (username == null || username.isBlank()) return empty();
for (var encryptedUser : backend.list()) {
var decryptedUser = decrypt(encryptedUser);
if (username.equals(decryptedUser.username()) && hasher.matches(password, decryptedUser.hashedPassword())) return Optional.of(decryptedUser);
}
return empty();
} }
@Override @Override
public boolean passwordMatches(String plaintextPassword, User user) { public boolean passwordMatches(String plaintextPassword, User user) {
return backend.passwordMatches(encrypt(plaintextPassword),encrypt(user)); return hasher.matches(plaintextPassword, user.hashedPassword());
} }
@Override @Override
public EncryptedUserService save(User user) { public UserService save(User user) {
delete(user);
backend.save(encrypt(user)); backend.save(encrypt(user));
return this; return this;
} }
@Override @Override
public EncryptedUserService updatePassword(User user, String plaintextPassword) { public UserService updatePassword(User user, String plaintextPassword) {
backend.updatePassword(encrypt(user),encrypt(plaintextPassword)); var pass = hasher.hash(plaintextPassword, user.uuid());
return this; return save(user.hashedPassword(pass));
} }
} }

113
de.srsoftware.oidc.datastore.encrypted/src/test/java/EncryptedUserServiceTest.java

@ -0,0 +1,113 @@
/* © SRSoftware 2024 */
import static de.srsoftware.utils.Optionals.nullable;
import static de.srsoftware.utils.Strings.uuid;
import de.srsoftware.oidc.api.UserService;
import de.srsoftware.oidc.api.UserServiceTest;
import de.srsoftware.oidc.api.data.AccessToken;
import de.srsoftware.oidc.api.data.User;
import de.srsoftware.oidc.datastore.encrypted.EncryptedUserService;
import de.srsoftware.utils.PasswordHasher;
import java.io.File;
import java.util.*;
import java.util.stream.Collectors;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
public class EncryptedUserServiceTest extends UserServiceTest {
private class InMemoryUserService implements UserService {
private final PasswordHasher<String> hasher;
private HashMap<String, User> users = new HashMap<>();
public InMemoryUserService(PasswordHasher<String> hasher) {
this.hasher = hasher;
}
@Override
public AccessToken accessToken(User user) {
return null;
}
@Override
public Optional<User> consumeToken(String accessToken) {
return Optional.empty();
}
@Override
public UserService delete(User user) {
users.remove(user.uuid());
return this;
}
@Override
public Optional<User> forToken(String accessToken) {
return Optional.empty();
}
@Override
public UserService init(User defaultUser) {
if (users.isEmpty()) users.put(defaultUser.uuid(), defaultUser);
return this;
}
@Override
public List<User> list() {
return List.copyOf(users.values());
}
@Override
public Set<User> find(String idOrEmail) {
return list().stream().filter(user -> user.uuid().equals(idOrEmail) || user.email().equals(idOrEmail)).collect(Collectors.toSet());
}
@Override
public Optional<User> load(String id) {
return nullable(users.get(id));
}
@Override
public Optional<User> load(String username, String password) {
return users.values().stream().filter(user -> user.username().equals(username) && passwordMatches(password, user)).findAny();
}
@Override
public boolean passwordMatches(String plaintextPassword, User user) {
return hasher.matches(plaintextPassword, user.hashedPassword());
}
@Override
public UserService save(User user) {
users.put(user.uuid(), user);
return this;
}
@Override
public UserService updatePassword(User user, String plaintextPassword) {
var old = users.get(user.uuid());
save(user.hashedPassword(hasher.hash(plaintextPassword, uuid())));
return this;
}
}
private File storage = new File("/tmp/" + UUID.randomUUID());
private UserService userService;
private String key, salt;
@AfterEach
public void tearDown() {
if (storage.exists()) storage.delete();
}
@BeforeEach
public void setup() {
tearDown();
key = uuid();
salt = uuid();
InMemoryUserService backend = new InMemoryUserService(hasher());
userService = new EncryptedUserService(backend, key, salt, hasher());
}
@Override
protected UserService userService() {
return userService;
}
}
Loading…
Cancel
Save