Browse Source

refining some tests, preparing test for session service

Signed-off-by: Stephan Richter <s.richter@srsoftware.de>
sqlite
Stephan Richter 2 months ago
parent
commit
f600040c0e
  1. 6
      de.srsoftware.oidc.api/build.gradle
  2. 2
      de.srsoftware.oidc.api/src/main/java/de/srsoftware/oidc/api/UserService.java
  3. 4
      de.srsoftware.oidc.api/src/test/java/de/srsoftware/oidc/api/SessionServiceTest.java
  4. 64
      de.srsoftware.oidc.api/src/test/java/de/srsoftware/oidc/api/UserServiceTest.java
  5. 2
      de.srsoftware.oidc.app/src/main/java/de/srsoftware/oidc/app/Application.java
  6. 2
      de.srsoftware.oidc.backend/src/main/java/de/srsoftware/oidc/backend/UserController.java
  7. 2
      de.srsoftware.oidc.datastore.file/build.gradle
  8. 11
      de.srsoftware.oidc.datastore.file/src/main/java/de/srsoftware/oidc/datastore/file/FileStore.java
  9. 1
      de.srsoftware.oidc.datastore.file/src/main/java/de/srsoftware/oidc/datastore/file/FileStoreProvider.java
  10. 5
      de.srsoftware.oidc.datastore.file/src/test/java/de/srsoftware/oidc/datastore/file/FileStoreUserServiceTest.java
  11. 33
      de.srsoftware.oidc.datastore.file/src/test/java/de/srsoftware/oidc/datastore/file/SessionServiceTest.java
  12. 1
      de.srsoftware.oidc.datastore.sqlite/build.gradle
  13. 39
      de.srsoftware.oidc.datastore.sqlite/src/main/java/de/srsoftware/oidc/datastore/sqlite/SqliteUserService.java
  14. 32
      de.srsoftware.oidc.datastore.sqlite/src/test/java/de/srsoftware/oidc/datastore/sqlite/SqliteUserServiceTest.java
  15. 2
      de.srsoftware.utils/src/main/java/de/srsoftware/utils/PasswordHasher.java
  16. 3
      de.srsoftware.utils/src/main/java/de/srsoftware/utils/UuidHasher.java

6
de.srsoftware.oidc.api/build.gradle

@ -21,15 +21,15 @@ test {
useJUnitPlatform() useJUnitPlatform()
} }
task jarTest (type: Jar) { task jarTests (type: Jar) {
from sourceSets.test.output from sourceSets.test.output
archiveClassifier = 'test' archiveClassifier = 'test'
} }
configurations { configurations {
testOutput testBundle
} }
artifacts { artifacts {
testOutput jarTest testBundle jarTests
} }

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

@ -28,7 +28,7 @@ public interface UserService {
public Set<User> find(String idOrEmail); public Set<User> find(String idOrEmail);
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 password, String hashedPassword); public boolean passwordMatches(String plaintextPassword, User user);
public <T extends UserService> T save(User user); public <T extends UserService> T save(User user);
public <T extends UserService> T updatePassword(User user, String plaintextPassword); public <T extends UserService> T updatePassword(User user, String plaintextPassword);
} }

4
de.srsoftware.oidc.api/src/test/java/de/srsoftware/oidc/api/SessionServiceTest.java

@ -0,0 +1,4 @@
/* © SRSoftware 2024 */
package de.srsoftware.oidc.api;
public class SessionServiceTest {}

64
de.srsoftware.oidc.api/src/test/java/de/srsoftware/oidc/api/UserServiceTest.java

@ -2,9 +2,13 @@
package de.srsoftware.oidc.api; package de.srsoftware.oidc.api;
import static de.srsoftware.oidc.api.data.Permission.*; import static de.srsoftware.oidc.api.data.Permission.*;
import static org.junit.jupiter.api.Assertions.assertTrue;
import de.srsoftware.oidc.api.data.Permission; import de.srsoftware.oidc.api.data.Permission;
import de.srsoftware.oidc.api.data.User; import de.srsoftware.oidc.api.data.User;
import de.srsoftware.utils.PasswordHasher;
import de.srsoftware.utils.UuidHasher;
import java.security.NoSuchAlgorithmException;
import java.util.UUID; import java.util.UUID;
import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
@ -21,8 +25,17 @@ public abstract class UserServiceTest {
protected abstract UserService userService(); protected abstract UserService userService();
private PasswordHasher<String> hasher = null;
protected abstract PasswordHasher<String> hasher(); protected PasswordHasher<String> hasher() {
if (hasher == null) try {
hasher = new UuidHasher();
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException(e);
}
return hasher;
}
@Test @Test
@ -40,7 +53,7 @@ public abstract class UserServiceTest {
var users = userService().list(); var users = userService().list();
Assertions.assertEquals(1, users.size()); Assertions.assertEquals(1, users.size());
var saved = users.get(0); var saved = users.get(0);
Assertions.assertTrue(hasher().matches(PASSWORD, saved.hashedPassword())); assertTrue(hasher().matches(PASSWORD, saved.hashedPassword()));
Assertions.assertEquals(firstUser, saved); Assertions.assertEquals(firstUser, saved);
} }
@ -55,12 +68,12 @@ public abstract class UserServiceTest {
var users = userService().list(); var users = userService().list();
Assertions.assertEquals(1, users.size()); Assertions.assertEquals(1, users.size());
var saved = users.get(0); var saved = users.get(0);
Assertions.assertTrue(hasher().matches(PASSWORD, saved.hashedPassword())); assertTrue(hasher().matches(PASSWORD, saved.hashedPassword()));
Assertions.assertEquals(newUser, saved); Assertions.assertEquals(newUser, saved);
Assertions.assertFalse(saved.hasPermission(Permission.MANAGE_USERS)); Assertions.assertFalse(saved.hasPermission(Permission.MANAGE_USERS));
Assertions.assertFalse(saved.hasPermission(Permission.MANAGE_SMTP)); Assertions.assertFalse(saved.hasPermission(Permission.MANAGE_SMTP));
Assertions.assertTrue(saved.hasPermission(MANAGE_CLIENTS)); assertTrue(saved.hasPermission(MANAGE_CLIENTS));
Assertions.assertTrue(saved.hasPermission(MANAGE_PERMISSIONS)); assertTrue(saved.hasPermission(MANAGE_PERMISSIONS));
} }
@Test @Test
@ -72,7 +85,7 @@ public abstract class UserServiceTest {
newUser.add(MANAGE_PERMISSIONS); newUser.add(MANAGE_PERMISSIONS);
userService().save(newUser); userService().save(newUser);
var saved = userService().load(uuid); var saved = userService().load(uuid);
Assertions.assertTrue(saved.isPresent()); assertTrue(saved.isPresent());
Assertions.assertEquals(newUser, saved.get()); Assertions.assertEquals(newUser, saved.get());
} }
@ -111,17 +124,22 @@ public abstract class UserServiceTest {
userService().init(firstUser); userService().init(firstUser);
var loaded = userService().load(uuid); var loaded = userService().load(uuid);
Assertions.assertTrue(loaded.isPresent()); assertTrue(loaded.isPresent());
var oldPass = loaded.get().hashedPassword(); var oldPass = loaded.get().hashedPassword();
Assertions.assertTrue(hasher().matches(PASSWORD, oldPass)); assertTrue(hasher().matches(PASSWORD, oldPass));
var newPass = hasher().hash(PASSWORD2, uuid); var newPass = hasher().hash(PASSWORD2, uuid);
userService().save(firstUser.hashedPassword(newPass)); userService().save(firstUser.hashedPassword(newPass));
loaded = userService().load(uuid); loaded = userService().load(uuid);
Assertions.assertTrue(loaded.isPresent()); assertTrue(loaded.isPresent());
newPass = loaded.get().hashedPassword(); newPass = loaded.get().hashedPassword();
Assertions.assertTrue(hasher().matches(PASSWORD2, newPass)); assertTrue(hasher().matches(PASSWORD2, newPass));
userService().updatePassword(firstUser, PASSWORD);
loaded = userService().load(uuid);
assertTrue(loaded.isPresent());
assertTrue(userService().passwordMatches(PASSWORD, loaded.get()));
} }
@Test @Test
@ -132,13 +150,13 @@ public abstract class UserServiceTest {
userService().init(firstUser); userService().init(firstUser);
var loaded = userService().load(uuid); var loaded = userService().load(uuid);
Assertions.assertTrue(loaded.isPresent()); assertTrue(loaded.isPresent());
Assertions.assertEquals(USERNAME, loaded.get().username()); Assertions.assertEquals(USERNAME, loaded.get().username());
userService().save(firstUser.username(USERNAME2)); userService().save(firstUser.username(USERNAME2));
loaded = userService().load(uuid); loaded = userService().load(uuid);
Assertions.assertTrue(loaded.isPresent()); assertTrue(loaded.isPresent());
Assertions.assertEquals(USERNAME2, loaded.get().username()); Assertions.assertEquals(USERNAME2, loaded.get().username());
} }
@ -150,13 +168,13 @@ public abstract class UserServiceTest {
userService().init(firstUser); userService().init(firstUser);
var loaded = userService().load(uuid); var loaded = userService().load(uuid);
Assertions.assertTrue(loaded.isPresent()); assertTrue(loaded.isPresent());
Assertions.assertEquals(NAME, loaded.get().realName()); Assertions.assertEquals(NAME, loaded.get().realName());
userService().save(firstUser.realName(NAME2)); userService().save(firstUser.realName(NAME2));
loaded = userService().load(uuid); loaded = userService().load(uuid);
Assertions.assertTrue(loaded.isPresent()); assertTrue(loaded.isPresent());
Assertions.assertEquals(NAME2, loaded.get().realName()); Assertions.assertEquals(NAME2, loaded.get().realName());
} }
@ -168,13 +186,13 @@ public abstract class UserServiceTest {
userService().init(firstUser); userService().init(firstUser);
var loaded = userService().load(uuid); var loaded = userService().load(uuid);
Assertions.assertTrue(loaded.isPresent()); assertTrue(loaded.isPresent());
Assertions.assertEquals(NAME, loaded.get().realName()); Assertions.assertEquals(NAME, loaded.get().realName());
userService().save(firstUser.email(EMAIL2)); userService().save(firstUser.email(EMAIL2));
loaded = userService().load(uuid); loaded = userService().load(uuid);
Assertions.assertTrue(loaded.isPresent()); assertTrue(loaded.isPresent());
Assertions.assertEquals(EMAIL2, loaded.get().email()); Assertions.assertEquals(EMAIL2, loaded.get().email());
} }
@ -186,27 +204,27 @@ public abstract class UserServiceTest {
userService().init(firstUser); userService().init(firstUser);
var opt = userService().load(uuid); var opt = userService().load(uuid);
Assertions.assertTrue(opt.isPresent()); assertTrue(opt.isPresent());
var loaded = opt.get(); var loaded = opt.get();
for (var permission : Permission.values()) Assertions.assertFalse(loaded.hasPermission(permission)); for (var permission : Permission.values()) Assertions.assertFalse(loaded.hasPermission(permission));
userService().save(loaded.add(MANAGE_CLIENTS, MANAGE_PERMISSIONS)); userService().save(loaded.add(MANAGE_CLIENTS, MANAGE_PERMISSIONS));
opt = userService().load(uuid); opt = userService().load(uuid);
Assertions.assertTrue(opt.isPresent()); assertTrue(opt.isPresent());
loaded = opt.get(); loaded = opt.get();
Assertions.assertTrue(loaded.hasPermission(MANAGE_CLIENTS)); assertTrue(loaded.hasPermission(MANAGE_CLIENTS));
Assertions.assertTrue(loaded.hasPermission(MANAGE_PERMISSIONS)); assertTrue(loaded.hasPermission(MANAGE_PERMISSIONS));
Assertions.assertFalse(loaded.hasPermission(MANAGE_SMTP)); Assertions.assertFalse(loaded.hasPermission(MANAGE_SMTP));
Assertions.assertFalse(loaded.hasPermission(MANAGE_USERS)); Assertions.assertFalse(loaded.hasPermission(MANAGE_USERS));
userService().save(loaded.add(MANAGE_SMTP, MANAGE_USERS).drop(MANAGE_CLIENTS, MANAGE_PERMISSIONS)); userService().save(loaded.add(MANAGE_SMTP, MANAGE_USERS).drop(MANAGE_CLIENTS, MANAGE_PERMISSIONS));
opt = userService().load(uuid); opt = userService().load(uuid);
Assertions.assertTrue(opt.isPresent()); assertTrue(opt.isPresent());
loaded = opt.get(); loaded = opt.get();
Assertions.assertFalse(loaded.hasPermission(MANAGE_CLIENTS)); Assertions.assertFalse(loaded.hasPermission(MANAGE_CLIENTS));
Assertions.assertFalse(loaded.hasPermission(MANAGE_PERMISSIONS)); Assertions.assertFalse(loaded.hasPermission(MANAGE_PERMISSIONS));
Assertions.assertTrue(loaded.hasPermission(MANAGE_SMTP)); assertTrue(loaded.hasPermission(MANAGE_SMTP));
Assertions.assertTrue(loaded.hasPermission(MANAGE_USERS)); assertTrue(loaded.hasPermission(MANAGE_USERS));
} }
} }

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

@ -20,10 +20,10 @@ import de.srsoftware.oidc.api.data.User;
import de.srsoftware.oidc.backend.*; import de.srsoftware.oidc.backend.*;
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.file.UuidHasher;
import de.srsoftware.oidc.datastore.sqlite.*; import de.srsoftware.oidc.datastore.sqlite.*;
import de.srsoftware.oidc.web.Forward; import de.srsoftware.oidc.web.Forward;
import de.srsoftware.oidc.web.StaticPages; import de.srsoftware.oidc.web.StaticPages;
import de.srsoftware.utils.UuidHasher;
import java.io.File; import java.io.File;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
import java.nio.file.Path; import java.nio.file.Path;

2
de.srsoftware.oidc.backend/src/main/java/de/srsoftware/oidc/backend/UserController.java

@ -298,7 +298,7 @@ public class UserController extends Controller {
return sendEmptyResponse(HTTP_FORBIDDEN, ex); return sendEmptyResponse(HTTP_FORBIDDEN, ex);
} }
var oldPass = json.getString("oldpass"); var oldPass = json.getString("oldpass");
if (!users.passwordMatches(oldPass, user.hashedPassword())) return badRequest(ex, "wrong password"); if (!users.passwordMatches(oldPass, user)) return badRequest(ex, "wrong password");
var passwords = json.getJSONArray("newpass"); var passwords = json.getJSONArray("newpass");
var newPass = passwords.getString(0); var newPass = passwords.getString(0);

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

@ -12,7 +12,7 @@ repositories {
dependencies { dependencies {
testImplementation platform('org.junit:junit-bom:5.10.0') testImplementation platform('org.junit:junit-bom:5.10.0')
testImplementation 'org.junit.jupiter:junit-jupiter' testImplementation 'org.junit.jupiter:junit-jupiter'
testImplementation project(path: ':de.srsoftware.oidc.api', configuration: "testOutput") testImplementation project(path: ':de.srsoftware.oidc.api', configuration: "testBundle")
implementation project(':de.srsoftware.oidc.api') implementation project(':de.srsoftware.oidc.api')
implementation project(':de.srsoftware.utils') implementation project(':de.srsoftware.utils')
implementation 'org.json:json:20240303' implementation 'org.json:json:20240303'

11
de.srsoftware.oidc.datastore.file/src/main/java/de/srsoftware/oidc/datastore/file/FileStore.java

@ -9,6 +9,7 @@ import static java.util.Optional.empty;
import de.srsoftware.oidc.api.*; import de.srsoftware.oidc.api.*;
import de.srsoftware.oidc.api.data.*; import de.srsoftware.oidc.api.data.*;
import de.srsoftware.utils.PasswordHasher;
import jakarta.mail.Authenticator; import jakarta.mail.Authenticator;
import jakarta.mail.PasswordAuthentication; import jakarta.mail.PasswordAuthentication;
import java.io.File; import java.io.File;
@ -186,8 +187,8 @@ public class FileStore implements AuthorizationService, ClientService, SessionSe
var userData = users.getJSONObject(userId); var userData = users.getJSONObject(userId);
if (KEYS.stream().map(userData::getString).noneMatch(val -> val.equals(user))) continue; if (KEYS.stream().map(userData::getString).noneMatch(val -> val.equals(user))) continue;
var hashedPass = userData.getString(PASSWORD); var loadedUser = User.of(userData, userId).filter(u -> passwordMatches(password, u));
if (passwordMatches(password, hashedPass)) return User.of(userData, userId); if (loadedUser.isPresent()) return loadedUser;
} }
return empty(); return empty();
} catch (Exception e) { } catch (Exception e) {
@ -196,8 +197,8 @@ public class FileStore implements AuthorizationService, ClientService, SessionSe
} }
@Override @Override
public boolean passwordMatches(String password, String hashedPassword) { public boolean passwordMatches(String password, User user) {
return passwordHasher.matches(password, hashedPassword); return passwordHasher.matches(password, user.hashedPassword());
} }
@Override @Override
@ -227,7 +228,7 @@ public class FileStore implements AuthorizationService, ClientService, SessionSe
public Session createSession(User user) { public Session createSession(User user) {
var now = Instant.now(); var now = Instant.now();
var endOfSession = now.plus(user.sessionDuration()); var endOfSession = now.plus(user.sessionDuration());
return save(new Session(user, endOfSession, uuid().toString())); return save(new Session(user, endOfSession, uuid()));
} }
@Override @Override

1
de.srsoftware.oidc.datastore.file/src/main/java/de/srsoftware/oidc/datastore/file/FileStoreProvider.java

@ -1,6 +1,7 @@
/* © SRSoftware 2024 */ /* © SRSoftware 2024 */
package de.srsoftware.oidc.datastore.file; package de.srsoftware.oidc.datastore.file;
import de.srsoftware.utils.UuidHasher;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.util.HashMap; import java.util.HashMap;

5
de.srsoftware.oidc.datastore.file/src/test/java/de/srsoftware/oidc/datastore/file/FileStoreUserServiceTest.java

@ -1,9 +1,10 @@
/* © SRSoftware 2024 */ /* © SRSoftware 2024 */
package de.srsoftware.oidc.datastore.file; package de.srsoftware.oidc.datastore.file;
import de.srsoftware.oidc.api.PasswordHasher;
import de.srsoftware.oidc.api.UserService; import de.srsoftware.oidc.api.UserService;
import de.srsoftware.oidc.api.UserServiceTest; import de.srsoftware.oidc.api.UserServiceTest;
import de.srsoftware.utils.PasswordHasher;
import de.srsoftware.utils.UuidHasher;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.security.NoSuchAlgorithmException; import java.security.NoSuchAlgorithmException;
@ -29,7 +30,7 @@ public class FileStoreUserServiceTest extends UserServiceTest {
@BeforeEach @BeforeEach
public void setup() throws IOException { public void setup() throws IOException {
if (storage.exists()) storage.delete(); if (storage.exists()) storage.delete();
userService = new FileStore(storage, hasher); userService = new FileStore(storage, hasher());
} }
@Override @Override

33
de.srsoftware.oidc.datastore.file/src/test/java/de/srsoftware/oidc/datastore/file/SessionServiceTest.java

@ -0,0 +1,33 @@
/* © SRSoftware 2024 */
package de.srsoftware.oidc.datastore.file;
import de.srsoftware.oidc.api.SessionService;
import de.srsoftware.utils.PasswordHasher;
import de.srsoftware.utils.UuidHasher;
import java.io.File;
import java.io.IOException;
import java.security.NoSuchAlgorithmException;
import java.util.UUID;
import org.junit.jupiter.api.BeforeEach;
public class SessionServiceTest {
private PasswordHasher<String> hasher = null;
private File storage = new File("/tmp/" + UUID.randomUUID());
private SessionService sessionService;
protected PasswordHasher<String> hasher() {
if (hasher == null) try {
hasher = new UuidHasher();
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException(e);
}
return hasher;
}
@BeforeEach
public void setup() throws IOException {
if (storage.exists()) storage.delete();
sessionService = new FileStore(storage, hasher());
}
}

1
de.srsoftware.oidc.datastore.sqlite/build.gradle

@ -12,6 +12,7 @@ repositories {
dependencies { dependencies {
testImplementation platform('org.junit:junit-bom:5.10.0') testImplementation platform('org.junit:junit-bom:5.10.0')
testImplementation 'org.junit.jupiter:junit-jupiter' testImplementation 'org.junit.jupiter:junit-jupiter'
testImplementation project(path: ':de.srsoftware.oidc.api', configuration: "testBundle")
implementation project(':de.srsoftware.oidc.api') implementation project(':de.srsoftware.oidc.api')
implementation project(':de.srsoftware.utils') implementation project(':de.srsoftware.utils')
implementation 'org.bitbucket.b_c:jose4j:0.9.6' implementation 'org.bitbucket.b_c:jose4j:0.9.6'

39
de.srsoftware.oidc.datastore.sqlite/src/main/java/de/srsoftware/oidc/datastore/sqlite/SqliteUserService.java

@ -5,11 +5,11 @@ import static de.srsoftware.utils.Optionals.nullable;
import static de.srsoftware.utils.Strings.uuid; import static de.srsoftware.utils.Strings.uuid;
import static java.util.Optional.empty; import static java.util.Optional.empty;
import de.srsoftware.oidc.api.PasswordHasher;
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.Permission; import de.srsoftware.oidc.api.data.Permission;
import de.srsoftware.oidc.api.data.User; import de.srsoftware.oidc.api.data.User;
import de.srsoftware.utils.PasswordHasher;
import java.sql.Connection; import java.sql.Connection;
import java.sql.ResultSet; import java.sql.ResultSet;
import java.sql.SQLException; import java.sql.SQLException;
@ -24,16 +24,15 @@ public class SqliteUserService extends SqliteStore implements UserService {
private static final String COUNT_USERS = "SELECT count(*) FROM users"; private static final String COUNT_USERS = "SELECT count(*) FROM users";
private static final String LOAD_USER = "SELECT * FROM users WHERE uuid = ?"; private static final String LOAD_USER = "SELECT * FROM users WHERE uuid = ?";
private static final String LOAD_PERMISSIONS = "SELECT permission FROM user_permissions WHERE uuid = ?"; private static final String LOAD_PERMISSIONS = "SELECT permission FROM user_permissions WHERE uuid = ?";
private static final String FIND_USER = "SELECT * FROM users WHERE uuid = ? OR username LIKE ? OR realname LIKE ? ORDER BY COALESCE(uuid, ?), username"; private static final String FIND_USER = "SELECT * FROM users WHERE uuid = ? OR username LIKE ? OR realname LIKE ? OR email = ? ORDER BY COALESCE(uuid, ?), username";
private static final String LIST_USERS = "SELECT * FROM users"; private static final String LIST_USERS = "SELECT * FROM users";
private static final String SELECT_USERSTORE_VERSION = "SELECT * FROM metainfo WHERE key = 'user_store_version'"; private static final String SELECT_USERSTORE_VERSION = "SELECT * FROM metainfo WHERE key = 'user_store_version'";
private static final String SET_USERSTORE_VERSION = "UPDATE metainfo SET value = ? WHERE key = 'user_store_version'"; private static final String SET_USERSTORE_VERSION = "UPDATE metainfo SET value = ? WHERE key = 'user_store_version'";
private static final String INSERT_USER = "INSERT INTO users (uuid,password,email,session_duration,username,realname) VALUES (?,?,?,?,?,?)"; private static final String INSERT_USER = "INSERT INTO users (uuid,password,email,session_duration,username,realname) VALUES (?,?,?,?,?,?) ON CONFLICT DO UPDATE SET password = ?, email = ?, session_duration = ?, username = ?, realname = ?;";
private static final String INSERT_PERMISSIONS = "INSERT INTO user_permissions (uuid, permission) VALUES (?,?)"; private static final String INSERT_PERMISSIONS = "INSERT INTO user_permissions (uuid, permission) VALUES (?,?)";
private static final String DROP_PERMISSIONS = "DELETE FROM user_permissions WHERE uuid = ?";
private static final String DROP_PERMISSIONS = "DELETE FROM user_permissions WHERE uuid = ?"; private static final String DROP_USER = "DELETE FROM users WHERE uuid = ?";
private static final String DROP_USER = "DELETE FROM users WHERE uuid = ?"; private static final String UPDATE_PASSWORD = "UPDATE users SET password = ? WHERE uuid = ?";
private static final String UPDATE_PASSWORD = "UPDATE users SET password = ? WHERE uuid = ?";
private final PasswordHasher<String> hasher; private final PasswordHasher<String> hasher;
private Map<String, AccessToken> accessTokens = new HashMap<>(); private Map<String, AccessToken> accessTokens = new HashMap<>();
@ -169,6 +168,7 @@ public class SqliteUserService extends SqliteStore implements UserService {
stmt.setString(2, "%" + idOrEmail + "%"); stmt.setString(2, "%" + idOrEmail + "%");
stmt.setString(3, "%" + idOrEmail + "%"); stmt.setString(3, "%" + idOrEmail + "%");
stmt.setString(4, idOrEmail); stmt.setString(4, idOrEmail);
stmt.setString(5, idOrEmail);
var rs = stmt.executeQuery(); var rs = stmt.executeQuery();
while (rs.next()) result.add(userFrom(rs)); while (rs.next()) result.add(userFrom(rs));
rs.close(); rs.close();
@ -213,14 +213,14 @@ public class SqliteUserService extends SqliteStore implements UserService {
public Optional<User> load(String username, String password) { public Optional<User> load(String username, String password) {
var candidates = find(username); var candidates = find(username);
for (var user : candidates) { for (var user : candidates) {
if (passwordMatches(password, user.hashedPassword())) return Optional.of(user); if (passwordMatches(password, user)) return Optional.of(user);
} }
return empty(); return empty();
} }
@Override @Override
public boolean passwordMatches(String password, String hashedPassword) { public boolean passwordMatches(String password, User user) {
return hasher.matches(password, hashedPassword); return hasher.matches(password, user.hashedPassword());
} }
@Override @Override
@ -234,6 +234,11 @@ public class SqliteUserService extends SqliteStore implements UserService {
stmt.setLong(4, user.sessionDuration().toMinutes()); stmt.setLong(4, user.sessionDuration().toMinutes());
stmt.setString(5, user.username()); stmt.setString(5, user.username());
stmt.setString(6, user.realName()); stmt.setString(6, user.realName());
stmt.setString(7, user.hashedPassword());
stmt.setString(8, user.email());
stmt.setLong(9, user.sessionDuration().toMinutes());
stmt.setString(10, user.username());
stmt.setString(11, user.realName());
stmt.execute(); stmt.execute();
dropPermissionsOf(user.uuid()); dropPermissionsOf(user.uuid());
@ -246,24 +251,14 @@ public class SqliteUserService extends SqliteStore implements UserService {
} }
} }
conn.commit(); conn.commit();
return this;
} catch (SQLException e) { } catch (SQLException e) {
throw new RuntimeException(e); throw new RuntimeException(e);
} }
return this;
} }
@Override @Override
public SqliteUserService updatePassword(User user, String plaintextPassword) { public SqliteUserService updatePassword(User user, String plaintextPassword) {
try { return save(user.hashedPassword(hasher.hash(plaintextPassword, uuid())));
var stmt = conn.prepareStatement(UPDATE_PASSWORD);
stmt.setString(1, user.uuid());
var hashedPassword = hasher.hash(plaintextPassword, uuid());
stmt.setString(2, hashedPassword);
stmt.execute();
return this;
} catch (SQLException e) {
throw new RuntimeException(e);
}
} }
} }

32
de.srsoftware.oidc.datastore.sqlite/src/test/java/de/srsoftware/oidc/datastore/sqlite/SqliteUserServiceTest.java

@ -0,0 +1,32 @@
/* © SRSoftware 2024 */
package de.srsoftware.oidc.datastore.sqlite;
import de.srsoftware.oidc.api.UserService;
import de.srsoftware.oidc.api.UserServiceTest;
import java.io.File;
import java.sql.SQLException;
import java.util.UUID;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
public class SqliteUserServiceTest extends UserServiceTest {
private File storage = new File("/tmp/" + UUID.randomUUID());
private UserService userService;
@AfterEach
public void tearDown() {
if (storage.exists()) storage.delete();
}
@BeforeEach
public void setup() throws SQLException {
tearDown();
userService = new SqliteUserService(new ConnectionProvider().get(storage), hasher());
}
@Override
protected UserService userService() {
return userService;
}
}

2
de.srsoftware.oidc.api/src/main/java/de/srsoftware/oidc/api/PasswordHasher.java → de.srsoftware.utils/src/main/java/de/srsoftware/utils/PasswordHasher.java

@ -1,5 +1,5 @@
/* © SRSoftware 2024 */ /* © SRSoftware 2024 */
package de.srsoftware.oidc.api; package de.srsoftware.utils;
public interface PasswordHasher<T> { public interface PasswordHasher<T> {
public String hash(String password, String salt); public String hash(String password, String salt);

3
de.srsoftware.oidc.datastore.file/src/main/java/de/srsoftware/oidc/datastore/file/UuidHasher.java → de.srsoftware.utils/src/main/java/de/srsoftware/utils/UuidHasher.java

@ -1,9 +1,8 @@
/* © SRSoftware 2024 */ /* © SRSoftware 2024 */
package de.srsoftware.oidc.datastore.file; package de.srsoftware.utils;
import static java.nio.charset.StandardCharsets.UTF_8; import static java.nio.charset.StandardCharsets.UTF_8;
import de.srsoftware.oidc.api.PasswordHasher;
import java.security.MessageDigest; import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException; import java.security.NoSuchAlgorithmException;
Loading…
Cancel
Save