diff --git a/Readme.md b/Readme.md index 04d10e9..b8863db 100644 --- a/Readme.md +++ b/Readme.md @@ -1,15 +1,49 @@ # LightOIDC + + + + + + + + + +
DeutschEnglish
+ +LightOIDC ist ein [Spezifikations][specification]-treuer OpenID-Connect-Provider mit minimalem Fußabdruck. + +Dieses Ziel wird durch Minimierung der externen Abhängigkeiten auf ein absolutes Minimum erreicht: + +Im Moment baut das Projekt nur auf die folgenden Laufzeit-Bibliotheken auf: + +* [org.json:json](https://github.com/douglascrockford/JSON-java) +* [org.bitbucket.b_c:jose4j](https://bitbucket.org/b_c/jose4j) +* [com.sun.mail:jakarta.mail](https://projects.eclipse.org/projects/ee4j.mail) +* [com.sun.activation:jakarta.activation](https://projects.eclipse.org/projects/ee4j.jaf) + +Im Moment haben diese Bibliotheken keine weiteren (transitiven) Anhängigkeiten, so dass das Projekt nicht durch eine Kaskade von Libraries aufgeblasen wird. + +Das Ermöglicht es, dass die compilierte JAR-Datei weniger als 1,5 MB groß ist! + + + This aims to be a [specification] compliant OpenID connect provider with minimal footprint. This goal is achieved by reducing external library dependiencies to an absolute minimum. -Currently, this project only depends on the following libraries: +Currently, this project only depends on the following runtime libraries: * [org.json:json](https://github.com/douglascrockford/JSON-java) * [org.bitbucket.b_c:jose4j](https://bitbucket.org/b_c/jose4j) +* [com.sun.mail:jakarta.mail](https://projects.eclipse.org/projects/ee4j.mail) +* [com.sun.activation:jakarta.activation](https://projects.eclipse.org/projects/ee4j.jaf) + +At the time of writing, these libraries have no further transitive dependencies, thus mitigating any bloat from the project. + +As a result, the compiled jar has a size of less than 1.5 MB! -At the time of writing, these libraries have no further transitive dependencies, this mitigating any bloat from the project. +
## build diff --git a/de.srsoftware.oidc.api/build.gradle b/de.srsoftware.oidc.api/build.gradle index be3cae6..62628d3 100644 --- a/de.srsoftware.oidc.api/build.gradle +++ b/de.srsoftware.oidc.api/build.gradle @@ -11,10 +11,10 @@ repositories { dependencies { testImplementation platform('org.junit:junit-bom:5.10.0') testImplementation 'org.junit.jupiter:junit-jupiter' + implementation project(':de.srsoftware.utils') implementation 'org.json:json:20240303' implementation 'org.bitbucket.b_c:jose4j:0.9.6' implementation 'com.sun.mail:jakarta.mail:2.0.1' - implementation project(':de.srsoftware.utils') } test { diff --git a/de.srsoftware.oidc.app/build.gradle b/de.srsoftware.oidc.app/build.gradle index 5bf4ebf..54cb89b 100644 --- a/de.srsoftware.oidc.app/build.gradle +++ b/de.srsoftware.oidc.app/build.gradle @@ -12,16 +12,14 @@ repositories { dependencies { testImplementation platform('org.junit:junit-bom:5.10.0') testImplementation 'org.junit.jupiter:junit-jupiter' - implementation 'org.xerial:sqlite-jdbc:3.46.0.0' implementation project(':de.srsoftware.http') implementation project(':de.srsoftware.logging') implementation project(':de.srsoftware.oidc.api') implementation project(':de.srsoftware.oidc.backend') - implementation project(':de.srsoftware.oidc.web') - implementation project(':de.srsoftware.utils') implementation project(':de.srsoftware.oidc.datastore.encrypted') implementation project(':de.srsoftware.oidc.datastore.file') - implementation project(':de.srsoftware.oidc.datastore.sqlite') + implementation project(':de.srsoftware.oidc.web') + implementation project(':de.srsoftware.utils') implementation 'org.json:json:20240303' } 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 dac7a28..84ac47e 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 @@ -6,7 +6,6 @@ import static de.srsoftware.oidc.api.Constants.*; import static de.srsoftware.oidc.api.data.Permission.*; import static de.srsoftware.utils.Optionals.emptyIfBlank; import static de.srsoftware.utils.Paths.configDir; -import static de.srsoftware.utils.Paths.extension; import static de.srsoftware.utils.Strings.uuid; import static java.lang.System.Logger.Level.DEBUG; import static java.lang.System.Logger.Level.ERROR; @@ -24,7 +23,6 @@ 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.PlaintextKeyStore; -import de.srsoftware.oidc.datastore.sqlite.*; import de.srsoftware.oidc.web.Forward; import de.srsoftware.oidc.web.StaticPages; import de.srsoftware.utils.UuidHasher; @@ -47,12 +45,11 @@ public class Application { public static final String ROOT = "/"; public static final String STATIC_PATH = "/web"; - private static final String BASE_PATH = "basePath"; - private static final String FAVICON = "/favicon.ico"; - private static final String INDEX = STATIC_PATH + "/index.html"; - private static final String WELL_KNOWN = "/.well-known"; - private static System.Logger LOG = new ColorLogger("Application").setLogLevel(DEBUG); - private static ConnectionProvider connectionProvider = new ConnectionProvider(); + private static final String BASE_PATH = "basePath"; + private static final String FAVICON = "/favicon.ico"; + private static final String INDEX = STATIC_PATH + "/index.html"; + private static final String WELL_KNOWN = "/.well-known"; + private static System.Logger LOG = new ColorLogger("Application").setLogLevel(DEBUG); public static void main(String[] args) throws Exception { var argMap = map(args); @@ -89,84 +86,63 @@ public class Application { } private static ClientService setupClientService(Configuration config, Path defaultFile, FileStoreProvider fileStoreProvider) throws SQLException { - 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); - }; + var clientStore = new File(config.getOrDefault("client_store", defaultFile)); + ClientService clientService = fileStoreProvider.get(clientStore); - Optional encryptionKey = config.get(ENCRYPTION_KEY); + Optional encryptionKey = config.get(ENCRYPTION_KEY); - if (encryptionKey.isPresent()){ - var salt = config.getOrDefault(SALT,uuid()); - clientService = new EncryptedClientService(encryptionKey.get(),salt,clientService); + 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 { - var authServiceLocation = new File(config.getOrDefault("auth_store",defaultFile)); - return switch (extension(authServiceLocation)){ - case "db", "sqlite", "sqlite3" -> new SqliteAuthService(connectionProvider.get(authServiceLocation)); - default -> fileStoreProvider.get(authServiceLocation); - }; + var authServiceLocation = new File(config.getOrDefault("auth_store", defaultFile)); + return fileStoreProvider.get(authServiceLocation); } - private static SessionService setupSessionService(Configuration config, Path defaultFile, FileStoreProvider fileStoreProvider) throws SQLException { - var sessionStore = new File(config.getOrDefault("session_storage",defaultFile)); - return switch (extension(sessionStore)){ - case "db", "sqlite", "sqlite3" -> new SqliteSessionService(connectionProvider.get(sessionStore)); - default -> fileStoreProvider.get(sessionStore); - }; + private static SessionService setupSessionService(Configuration config, Path defaultFile, FileStoreProvider fileStoreProvider) { + var sessionStore = new File(config.getOrDefault("session_storage", defaultFile)); + return fileStoreProvider.get(sessionStore); } private static MailConfig setupMailConfig(Configuration config, Path defaultFile, FileStoreProvider fileStoreProvider) throws SQLException { - var mailConfigLocation = new File(config.getOrDefault("mail_config_storage",defaultFile)); - var mailConfig = switch (extension(mailConfigLocation)){ - case "db", "sqlite", "sqlite3" -> new SqliteMailConfig(connectionProvider.get(mailConfigLocation)); - default -> fileStoreProvider.get(mailConfigLocation); - }; + var mailConfigLocation = new File(config.getOrDefault("mail_config_storage", defaultFile)); + MailConfig mailConfig = fileStoreProvider.get(mailConfigLocation); - Optional encryptionKey = config.get(ENCRYPTION_KEY); + Optional encryptionKey = config.get(ENCRYPTION_KEY); - if (encryptionKey.isPresent()){ - var salt = config.getOrDefault(SALT,uuid()); - mailConfig = new EncryptedMailConfig(mailConfig,encryptionKey.get(),salt); + if (encryptionKey.isPresent()) { + var salt = config.getOrDefault(SALT, uuid()); + mailConfig = new EncryptedMailConfig(mailConfig, encryptionKey.get(), salt); } return mailConfig; } private static UserService setupUserService(Configuration config, Path defaultFile, FileStoreProvider fileStoreProvider, UuidHasher passHasher) throws SQLException { - var userStorageLocation = new File(config.getOrDefault("user_storage",defaultFile)); - var userService = switch (extension(userStorageLocation).toLowerCase()){ - case "db", "sqlite", "sqlite3" -> new SqliteUserService(connectionProvider.get(userStorageLocation),passHasher); - default -> fileStoreProvider.get(userStorageLocation); - }; + var userStorageLocation = new File(config.getOrDefault("user_storage", defaultFile)); + UserService userService = fileStoreProvider.get(userStorageLocation); - Optional encryptionKey = config.get(ENCRYPTION_KEY); + Optional encryptionKey = config.get(ENCRYPTION_KEY); - if (encryptionKey.isPresent()){ - var salt = config.getOrDefault(SALT,uuid()); - userService = new EncryptedUserService(userService,encryptionKey.get(),salt,passHasher); + 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 { var keyStorageLocation = new File(config.getOrDefault("key_storage", defaultConfigDir.resolve("keys"))); - KeyStorage keyStore = null; - if ((keyStorageLocation.exists() && keyStorageLocation.isDirectory()) || !keyStorageLocation.getName().contains(".")) { - keyStore = new PlaintextKeyStore(keyStorageLocation.toPath()); - } else { // SQLite - var conn = connectionProvider.get(keyStorageLocation); - keyStore = new SqliteKeyStore(conn); - } + KeyStorage keyStore = new PlaintextKeyStore(keyStorageLocation.toPath()); - Optional encryptionKey = config.get(ENCRYPTION_KEY); + Optional encryptionKey = config.get(ENCRYPTION_KEY); - if (encryptionKey.isPresent()){ - var salt = config.getOrDefault(SALT,uuid()); - keyStore = new EncryptedKeyStore(encryptionKey.get(),salt,keyStore); + if (encryptionKey.isPresent()) { + var salt = config.getOrDefault(SALT, uuid()); + keyStore = new EncryptedKeyStore(encryptionKey.get(), salt, keyStore); } return keyStore; } @@ -183,18 +159,18 @@ public class Application { var token = tokens.remove(0); switch (token) { case "--base": - if (tokens.isEmpty()) throw new IllegalArgumentException("--base option requires second argument!"); - map.put(BASE_PATH, Path.of(tokens.remove(0))); - break; - case "--config": - if (tokens.isEmpty()) throw new IllegalArgumentException("--config option requires second argument!"); - map.put(CONFIG_PATH, Path.of(tokens.remove(0))); - break; - default: - LOG.log(ERROR, "Unknown option: {0}", token); + if (tokens.isEmpty()) throw new IllegalArgumentException("--base option requires second argument!"); + map.put(BASE_PATH, Path.of(tokens.remove(0))); + break; + case "--config": + if (tokens.isEmpty()) throw new IllegalArgumentException("--config option requires second argument!"); + map.put(CONFIG_PATH, Path.of(tokens.remove(0))); + break; + default: + LOG.log(ERROR, "Unknown option: {0}", token); + } } - } - return map; -} + return map; + } } diff --git a/de.srsoftware.oidc.datastore.encrypted/build.gradle b/de.srsoftware.oidc.datastore.encrypted/build.gradle index d3adee1..e94d2df 100644 --- a/de.srsoftware.oidc.datastore.encrypted/build.gradle +++ b/de.srsoftware.oidc.datastore.encrypted/build.gradle @@ -12,12 +12,11 @@ repositories { dependencies { testImplementation platform('org.junit:junit-bom:5.10.0') testImplementation 'org.junit.jupiter:junit-jupiter' + testImplementation project(path: ':de.srsoftware.oidc.api', configuration: "testBundle") implementation project(':de.srsoftware.oidc.api') - implementation 'com.sun.mail:jakarta.mail:2.0.1' implementation project(':de.srsoftware.utils') - testImplementation project(path: ':de.srsoftware.oidc.api', configuration: "testBundle") + implementation 'com.sun.mail:jakarta.mail:2.0.1' implementation 'org.bitbucket.b_c:jose4j:0.9.6' - } test { diff --git a/de.srsoftware.oidc.datastore.sqlite/build.gradle b/de.srsoftware.oidc.datastore.sqlite/build.gradle deleted file mode 100644 index f2ffd5c..0000000 --- a/de.srsoftware.oidc.datastore.sqlite/build.gradle +++ /dev/null @@ -1,26 +0,0 @@ -plugins { - id 'java' -} - -group = 'de.srsoftware' -version = '1.0-SNAPSHOT' - -repositories { - mavenCentral() -} - -dependencies { - testImplementation platform('org.junit:junit-bom:5.10.0') - 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.utils') - implementation 'org.bitbucket.b_c:jose4j:0.9.6' - implementation 'org.xerial:sqlite-jdbc:3.46.0.0' - implementation 'com.sun.mail:jakarta.mail:2.0.1' - -} - -test { - useJUnitPlatform() -} \ No newline at end of file diff --git a/de.srsoftware.oidc.datastore.sqlite/src/main/java/de/srsoftware/oidc/datastore/sqlite/ConnectionProvider.java b/de.srsoftware.oidc.datastore.sqlite/src/main/java/de/srsoftware/oidc/datastore/sqlite/ConnectionProvider.java deleted file mode 100644 index 2928562..0000000 --- a/de.srsoftware.oidc.datastore.sqlite/src/main/java/de/srsoftware/oidc/datastore/sqlite/ConnectionProvider.java +++ /dev/null @@ -1,27 +0,0 @@ -/* © SRSoftware 2024 */ -package de.srsoftware.oidc.datastore.sqlite; - -import java.io.File; -import java.sql.Connection; -import java.sql.SQLException; -import java.util.HashMap; -import org.sqlite.SQLiteDataSource; - -public class ConnectionProvider extends HashMap { - public Connection get(Object o) { - if (o instanceof File dbFile) try { - var conn = super.get(dbFile); - if (conn == null) put(dbFile, conn = open(dbFile)); - return conn; - } catch (SQLException sqle) { - throw new RuntimeException(sqle); - } - return null; - } - - private Connection open(File dbFile) throws SQLException { - SQLiteDataSource dataSource = new SQLiteDataSource(); - dataSource.setUrl("jdbc:sqlite:%s".formatted(dbFile)); - return dataSource.getConnection(); - } -} diff --git a/de.srsoftware.oidc.datastore.sqlite/src/main/java/de/srsoftware/oidc/datastore/sqlite/SqliteAuthService.java b/de.srsoftware.oidc.datastore.sqlite/src/main/java/de/srsoftware/oidc/datastore/sqlite/SqliteAuthService.java deleted file mode 100644 index aea83cd..0000000 --- a/de.srsoftware.oidc.datastore.sqlite/src/main/java/de/srsoftware/oidc/datastore/sqlite/SqliteAuthService.java +++ /dev/null @@ -1,170 +0,0 @@ -/* © SRSoftware 2024 */ -package de.srsoftware.oidc.datastore.sqlite; - -import static de.srsoftware.utils.Optionals.nullable; -import static de.srsoftware.utils.Strings.uuid; - -import de.srsoftware.oidc.api.AuthorizationService; -import de.srsoftware.oidc.api.data.AuthResult; -import de.srsoftware.oidc.api.data.Authorization; -import de.srsoftware.oidc.api.data.AuthorizedScopes; -import java.sql.Connection; -import java.sql.SQLException; -import java.time.Instant; -import java.time.temporal.ChronoUnit; -import java.util.*; -import java.util.stream.Collectors; - -public class SqliteAuthService extends SqliteStore implements AuthorizationService { - private static final String STORE_VERSION = "auth_store_version"; - private static final String CREATE_STORE_VERSION = "INSERT INTO metainfo (key,value) VALUES ('" + STORE_VERSION + "','0')"; - private static final String SELECT_STORE_VERSION = "SELECT * FROM metainfo WHERE key = '" + STORE_VERSION + "'"; - private static final String SET_STORE_VERSION = "UPDATE metainfo SET value = ? WHERE key = '" + STORE_VERSION + "'"; - - private static final String CREATE_AUTHSTORE_TABLE = "CREATE TABLE IF NOT EXISTS authorizations(userId VARCHAR(255), clientId VARCHAR(255), scope VARCHAR(255), expiration LONG, PRIMARY KEY(userId, clientId, scope));"; - private static final String SAVE_AUTHORIZATION = "INSERT INTO authorizations(userId, clientId, scope, expiration) VALUES (?,?,?,?) ON CONFLICT DO UPDATE SET expiration = ?"; - private static final String SELECT_AUTH = "SELECT * FROM authorizations WHERE userId = ? AND clientId = ? AND scope IN"; - private static final String SELECT_USER_CLIENTS = "SELECT DISTINCT clientId FROM authorizations WHERE userId = ?"; - private Map authCodes = new HashMap<>(); - - private Map nonceMap = new HashMap<>(); - - public SqliteAuthService(Connection connection) throws SQLException { - super(connection); - } - - private void createStoreTables() throws SQLException { - conn.prepareStatement(CREATE_AUTHSTORE_TABLE).execute(); - } - @Override - protected void initTables() throws SQLException { - var rs = conn.prepareStatement(SELECT_STORE_VERSION).executeQuery(); - int availableVersion = 1; - int currentVersion; - if (rs.next()) { - currentVersion = rs.getInt("value"); - rs.close(); - } else { - rs.close(); - conn.prepareStatement(CREATE_STORE_VERSION).execute(); - currentVersion = 0; - } - - conn.setAutoCommit(false); - var stmt = conn.prepareStatement(SET_STORE_VERSION); - while (currentVersion < availableVersion) { - try { - switch (currentVersion) { - case 0: - createStoreTables(); - break; - } - stmt.setInt(1, ++currentVersion); - stmt.execute(); - conn.commit(); - } catch (Exception e) { - conn.rollback(); - LOG.log(System.Logger.Level.ERROR, "Failed to update at {} = {}", STORE_VERSION, currentVersion); - break; - } - } - conn.setAutoCommit(true); - } - - private String authCode(Authorization authorization) { - var code = uuid(); - authCodes.put(code, authorization); - return code; - } - - @Override - public AuthorizationService authorize(String userId, String clientId, Collection scopes, Instant expiration) { - try { - conn.setAutoCommit(false); - var stmt = conn.prepareStatement(SAVE_AUTHORIZATION); - stmt.setString(1, userId); - stmt.setString(2, clientId); - stmt.setLong(4, expiration.getEpochSecond()); - stmt.setLong(5, expiration.getEpochSecond()); - for (var scope : scopes) { - stmt.setString(3, scope); - stmt.execute(); - } - conn.commit(); - conn.setAutoCommit(true); - return this; - } catch (SQLException e) { - throw new RuntimeException(e); - } - } - - @Override - public List authorizedClients(String userId) { - try { - var stmt = conn.prepareStatement(SELECT_USER_CLIENTS); - stmt.setString(1, userId); - var rs = stmt.executeQuery(); - var result = new ArrayList(); - while (rs.next()) result.add(rs.getString(1)); - rs.close(); - return result; - } catch (SQLException e) { - throw new RuntimeException(e); - } - } - - @Override - public Optional consumeAuthorization(String authCode) { - return nullable(authCodes.remove(authCode)); - } - - @Override - public Optional consumeNonce(String userId, String clientId) { - var nonceKey = String.join("@", userId, clientId); - return nullable(nonceMap.get(nonceKey)); - } - - @Override - public AuthResult getAuthorization(String userId, String clientId, Collection scopes) { - try { - var scopeList = "(" + scopes.stream().map(s -> "?").collect(Collectors.joining(", ")) + ")"; - var sql = SELECT_AUTH + scopeList; - var stmt = conn.prepareStatement(sql); - stmt.setString(1, userId); - stmt.setString(2, clientId); - int i = 3; - for (var scope : scopes) stmt.setString(i++, scope); - var rs = stmt.executeQuery(); - var unauthorized = new HashSet(scopes); - var authorized = new HashSet(); - var now = Instant.now(); - Instant earliestExp = null; - while (rs.next()) { - long expiration = rs.getLong("expiration"); - String scope = rs.getString("scope"); - Instant ex = Instant.ofEpochSecond(expiration).truncatedTo(ChronoUnit.SECONDS); - if (ex.isAfter(now)) { - unauthorized.remove(scope); - authorized.add(scope); - if (earliestExp == null || ex.isBefore(earliestExp)) earliestExp = ex; - } - } - rs.close(); - if (authorized.isEmpty()) return new AuthResult(null, unauthorized, null); - var authorizedScopes = new AuthorizedScopes(authorized, earliestExp); - var authorization = new Authorization(clientId, userId, authorizedScopes); - return new AuthResult(authorizedScopes, unauthorized, authCode(authorization)); - } catch (SQLException e) { - throw new RuntimeException(e); - } - } - - @Override - public void nonce(String userId, String clientId, String nonce) { - var nonceKey = String.join("@", userId, clientId); - if (nonce != null) { - nonceMap.put(nonceKey, nonce); - } else - nonceMap.remove(nonceKey); - } -} diff --git a/de.srsoftware.oidc.datastore.sqlite/src/main/java/de/srsoftware/oidc/datastore/sqlite/SqliteClientService.java b/de.srsoftware.oidc.datastore.sqlite/src/main/java/de/srsoftware/oidc/datastore/sqlite/SqliteClientService.java deleted file mode 100644 index cf92fed..0000000 --- a/de.srsoftware.oidc.datastore.sqlite/src/main/java/de/srsoftware/oidc/datastore/sqlite/SqliteClientService.java +++ /dev/null @@ -1,176 +0,0 @@ -/* © SRSoftware 2024 */ -package de.srsoftware.oidc.datastore.sqlite; - -import static de.srsoftware.oidc.api.Constants.*; - -import de.srsoftware.oidc.api.ClientService; -import de.srsoftware.oidc.api.data.Client; -import java.sql.Connection; -import java.sql.SQLException; -import java.util.*; -import java.util.stream.Collectors; - -public class SqliteClientService extends SqliteStore implements ClientService { - private static final String STORE_VERSION = "client_store_version"; - private static final String CREATE_STORE_VERSION = "INSERT INTO metainfo (key,value) VALUES ('" + STORE_VERSION + "','0')"; - private static final String SELECT_STORE_VERSION = "SELECT * FROM metainfo WHERE key = '" + STORE_VERSION + "'"; - private static final String SET_STORE_VERSION = "UPDATE metainfo SET value = ? WHERE key = '" + STORE_VERSION + "'"; - - private static final String CREATE_CLIENT_TABLE = "CREATE TABLE IF NOT EXISTS clients(id VARCHAR(255) NOT NULL PRIMARY KEY, name VARCHAR(255), secret VARCHAR(255), landing_page VARCHAR(255));"; - private static final String CREATE_REDIRECT_TABLE = "CREATE TABLE IF NOT EXISTS client_redirects(clientId VARCHAR(255), uri VARCHAR(255), PRIMARY KEY(clientId, uri));"; - private static final String SAVE_CLIENT = "INSERT INTO clients (id, name, secret, landing_page) VALUES (?,?,?,?) ON CONFLICT DO UPDATE SET name = ?, secret = ?, landing_page = ?;"; - private static final String SAVE_REDIRECT = "INSERT OR IGNORE INTO client_redirects(clientId, uri) VALUES (?, ?)"; - private static final String DROP_OTHER_REDIRECTS = "DELETE FROM client_redirects WHERE clientId = ? AND uri NOT IN"; - private static final String SELECT_CLIENT = "SELECT * FROM clients WHERE id = ?"; - private static final String SELECT_CLIENT_REDIRECTS = "SELECT uri FROM client_redirects WHERE clientId = ?"; - private static final String LIST_CLIENT_REDIRECTS = "SELECT * FROM client_redirects"; - private static final String LIST_CLIENTS = "SELECT * FROM clients"; - private static final String DELETE_CLIENT = "DELETE FROM clients WHERE id = ?"; - private static final String DELETE_CLIENT_REDIRECTS = "DELETE FROM client_redirects WHERE clientId = ?"; - - public SqliteClientService(Connection connection) throws SQLException { - super(connection); - } - - private void createStoreTables() throws SQLException { - conn.prepareStatement(CREATE_CLIENT_TABLE).execute(); - conn.prepareStatement(CREATE_REDIRECT_TABLE).execute(); - } - - @Override - protected void initTables() throws SQLException { - var rs = conn.prepareStatement(SELECT_STORE_VERSION).executeQuery(); - int availableVersion = 1; - int currentVersion; - if (rs.next()) { - currentVersion = rs.getInt("value"); - rs.close(); - } else { - rs.close(); - conn.prepareStatement(CREATE_STORE_VERSION).execute(); - currentVersion = 0; - } - - conn.setAutoCommit(false); - var stmt = conn.prepareStatement(SET_STORE_VERSION); - while (currentVersion < availableVersion) { - try { - switch (currentVersion) { - case 0: - createStoreTables(); - break; - } - stmt.setInt(1, ++currentVersion); - stmt.execute(); - conn.commit(); - } catch (Exception e) { - conn.rollback(); - LOG.log(System.Logger.Level.ERROR, "Failed to update at {} = {}", STORE_VERSION, currentVersion); - break; - } - } - conn.setAutoCommit(true); - } - - @Override - public Optional getClient(String clientId) { - Optional result = Optional.empty(); - try { - var stmt = conn.prepareStatement(SELECT_CLIENT_REDIRECTS); - stmt.setString(1, clientId); - var rs = stmt.executeQuery(); - var uris = new HashSet(); - while (rs.next()) uris.add(rs.getString("uri")); - rs.close(); - stmt = conn.prepareStatement(SELECT_CLIENT); - stmt.setString(1, clientId); - rs = stmt.executeQuery(); - if (rs.next()) { - var name = rs.getString(NAME); - var secret = rs.getString(SECRET); - var landing = rs.getString(LANDING_PAGE); - result = Optional.of(new Client(clientId, name, secret, uris).landingPage(landing)); - } - rs.close(); - return result; - } catch (SQLException e) { - throw new RuntimeException(e); - } - } - - @Override - public List listClients() { - try { - var stmt = conn.prepareStatement(LIST_CLIENT_REDIRECTS); - var rs = stmt.executeQuery(); - var redirects = new HashMap>(); - while (rs.next()) { - var clientId = rs.getString("clientId"); - var uri = rs.getString("uri"); - var set = redirects.computeIfAbsent(clientId, k -> new HashSet<>()); - set.add(uri); - } - rs.close(); - stmt = conn.prepareStatement(LIST_CLIENTS); - rs = stmt.executeQuery(); - var result = new ArrayList(); - while (rs.next()) { - var id = rs.getString("id"); - var name = rs.getString(NAME); - var secret = rs.getString(SECRET); - var landing = rs.getString(LANDING_PAGE); - result.add(new Client(id, name, secret, redirects.get(id)).landingPage(landing)); - } - rs.close(); - return result; - } catch (SQLException e) { - throw new RuntimeException(e); - } - } - - @Override - public ClientService remove(String clientId) { - try { - var stmt = conn.prepareStatement(DELETE_CLIENT); - stmt.setString(1, clientId); - stmt.execute(); - stmt = conn.prepareStatement(DELETE_CLIENT_REDIRECTS); - stmt.setString(1, clientId); - stmt.execute(); - return this; - } catch (SQLException e) { - throw new RuntimeException(e); - } - } - - @Override - public ClientService save(Client client) { - try { - var stmt = conn.prepareStatement(SAVE_CLIENT); - stmt.setString(1, client.id()); - stmt.setString(2, client.name()); - stmt.setString(3, client.secret()); - stmt.setString(4, client.landingPage()); - stmt.setString(5, client.name()); - stmt.setString(6, client.secret()); - stmt.setString(7, client.landingPage()); - stmt.execute(); - stmt = conn.prepareStatement(SAVE_REDIRECT); - stmt.setString(1, client.id()); - for (var redirect : client.redirectUris()) { - stmt.setString(2, redirect); - stmt.execute(); - } - var where = "(" + client.redirectUris().stream().map(u -> "?").collect(Collectors.joining(", ")) + ")"; - var sql = DROP_OTHER_REDIRECTS + where; - stmt = conn.prepareStatement(sql); - stmt.setString(1, client.id()); - int i = 2; - for (var redirect : client.redirectUris()) stmt.setString(i++, redirect); - stmt.execute(); - return this; - } catch (SQLException e) { - throw new RuntimeException(e); - } - } -} diff --git a/de.srsoftware.oidc.datastore.sqlite/src/main/java/de/srsoftware/oidc/datastore/sqlite/SqliteKeyStore.java b/de.srsoftware.oidc.datastore.sqlite/src/main/java/de/srsoftware/oidc/datastore/sqlite/SqliteKeyStore.java deleted file mode 100644 index d41888a..0000000 --- a/de.srsoftware.oidc.datastore.sqlite/src/main/java/de/srsoftware/oidc/datastore/sqlite/SqliteKeyStore.java +++ /dev/null @@ -1,125 +0,0 @@ -/* © SRSoftware 2024 */ -package de.srsoftware.oidc.datastore.sqlite; - - -import de.srsoftware.oidc.api.KeyStorage; -import java.io.IOException; -import java.sql.Connection; -import java.sql.SQLException; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import org.jose4j.jwk.PublicJsonWebKey; - -public class SqliteKeyStore extends SqliteStore implements KeyStorage { - private static final String STORE_VERSION = "key_store_version"; - private static final String CREATE_STORE_VERSION = "INSERT INTO metainfo (key,value) VALUES ('" + STORE_VERSION + "','0')"; - private static final String SELECT_STORE_VERSION = "SELECT * FROM metainfo WHERE key = '" + STORE_VERSION + "'"; - private static final String SET_STORE_VERSION = "UPDATE metainfo SET value = ? WHERE key = '" + STORE_VERSION + "'"; - - private static final String SET_KEYSTORE_VERSION = "UPDATE metainfo SET value = ? WHERE key = 'key_store_version'"; - private static final String CREATE_KEYSTORE_TABLE = "CREATE TABLE IF NOT EXISTS keystore(key_id VARCHAR(255) PRIMARY KEY, json TEXT NOT NULL);"; - private static final String SAVE_KEY = "INSERT INTO keystore(key_id, json) values (?,?) ON CONFLICT(key_id) DO UPDATE SET json = ?"; - private static final String SELECT_KEY_IDS = "SELECT key_id FROM keystore"; - private static final String LOAD_KEY = "SELECT json FROM keystore WHERE key_id = ?"; - private static final String DROP_KEY = "DELETE FROM keystore WHERE key_id = ?"; - - private HashMap loaded = new HashMap<>(); - - public SqliteKeyStore(Connection connection) throws SQLException { - super(connection); - } - - private void createStoreTables() throws SQLException { - conn.prepareStatement(CREATE_KEYSTORE_TABLE).execute(); - } - - @Override - public KeyStorage drop(String keyId) { - try { - var stmt = conn.prepareStatement(DROP_KEY); - stmt.setString(1, keyId); - stmt.execute(); - } catch (SQLException e) { - LOG.log(System.Logger.Level.WARNING, "Failed to drop key {0} from database:", keyId, e); - } - return this; - } - - @Override - protected void initTables() throws SQLException { - var rs = conn.prepareStatement(SELECT_STORE_VERSION).executeQuery(); - int availableVersion = 1; - int currentVersion; - if (rs.next()) { - currentVersion = rs.getInt("value"); - rs.close(); - } else { - rs.close(); - conn.prepareStatement(CREATE_STORE_VERSION).execute(); - currentVersion = 0; - } - - conn.setAutoCommit(false); - var stmt = conn.prepareStatement(SET_STORE_VERSION); - while (currentVersion < availableVersion) { - try { - switch (currentVersion) { - case 0: - createStoreTables(); - break; - } - stmt.setInt(1, ++currentVersion); - stmt.execute(); - conn.commit(); - } catch (Exception e) { - conn.rollback(); - LOG.log(System.Logger.Level.ERROR, "Failed to update at {} = {}", STORE_VERSION, currentVersion); - break; - } - } - conn.setAutoCommit(true); - } - - @Override - public List listKeys() { - var result = new ArrayList(); - try { - var rs = conn.prepareStatement(SELECT_KEY_IDS).executeQuery(); - while (rs.next()) result.add(rs.getString(1)); - rs.close(); - } catch (SQLException e) { - LOG.log(System.Logger.Level.WARNING, "Failed to read key ids from table!"); - } - return result; - } - - @Override - public String loadJson(String keyId) throws IOException { - try { - var stmt = conn.prepareStatement(LOAD_KEY); - stmt.setString(1, keyId); - var rs = stmt.executeQuery(); - String json = null; - if (rs.next()) json = rs.getString(1); - rs.close(); - return json; - } catch (SQLException e) { - throw new RuntimeException(e); - } - } - - @Override - public KeyStorage store(String keyId, String json) throws IOException { - try { - var stmt = conn.prepareStatement(SAVE_KEY); - stmt.setString(1, keyId); - stmt.setString(2, json); - stmt.setString(3, json); - stmt.execute(); - } catch (SQLException e) { - throw new RuntimeException(e); - } - return this; - } -} diff --git a/de.srsoftware.oidc.datastore.sqlite/src/main/java/de/srsoftware/oidc/datastore/sqlite/SqliteMailConfig.java b/de.srsoftware.oidc.datastore.sqlite/src/main/java/de/srsoftware/oidc/datastore/sqlite/SqliteMailConfig.java deleted file mode 100644 index 37da680..0000000 --- a/de.srsoftware.oidc.datastore.sqlite/src/main/java/de/srsoftware/oidc/datastore/sqlite/SqliteMailConfig.java +++ /dev/null @@ -1,183 +0,0 @@ -/* © SRSoftware 2024 */ -package de.srsoftware.oidc.datastore.sqlite; - -import static de.srsoftware.oidc.api.Constants.*; - -import de.srsoftware.oidc.api.MailConfig; -import jakarta.mail.Authenticator; -import jakarta.mail.PasswordAuthentication; -import java.sql.Connection; -import java.sql.SQLException; - -public class SqliteMailConfig extends SqliteStore implements MailConfig { - private static final String STORE_VERSION = "mail_config_store_version"; - private static final String CREATE_STORE_VERSION = "INSERT INTO metainfo (key,value) VALUES ('" + STORE_VERSION + "','0')"; - private static final String SELECT_STORE_VERSION = "SELECT * FROM metainfo WHERE key = '" + STORE_VERSION + "'"; - private static final String SET_STORE_VERSION = "UPDATE metainfo SET value = ? WHERE key = '" + STORE_VERSION + "'"; - private static final String CREATE_MAIL_CONFIG_TABLE = "CREATE TABLE mail_config (key VARCHAR(64) PRIMARY KEY, value VARCHAR(255));"; - private static final String SAVE_MAILCONFIG = "INSERT INTO mail_config (key, value) VALUES (?, ?) ON CONFLICT DO UPDATE SET value = ?"; - private static final String SELECT_MAILCONFIG = "SELECT * FROM mail_config"; - private String smtpHost, senderAddress, password; - - private int smtpPort; - private boolean smtpAuth, startTls; - private Authenticator auth; - - public SqliteMailConfig(Connection connection) throws SQLException { - super(connection); - smtpHost = ""; - smtpPort = 0; - senderAddress = ""; - password = ""; - smtpAuth = true; - startTls = true; - var rs = conn.prepareStatement(SELECT_MAILCONFIG).executeQuery(); - while (rs.next()) { - var key = rs.getString(1); - switch (key) { - case SMTP_PORT -> smtpPort = rs.getInt(2); - case SMTP_HOST -> smtpHost = rs.getString(2); - case START_TLS -> startTls = rs.getBoolean(2); - case SMTP_AUTH -> smtpAuth = rs.getBoolean(2); - case SMTP_PASSWORD -> password = rs.getString(2); - case SMTP_USER -> senderAddress = rs.getString(2); - } - } - } - - private void createStoreTables() throws SQLException { - conn.prepareStatement(CREATE_MAIL_CONFIG_TABLE).execute(); - } - - - @Override - protected void initTables() throws SQLException { - var rs = conn.prepareStatement(SELECT_STORE_VERSION).executeQuery(); - int availableVersion = 1; - int currentVersion; - if (rs.next()) { - currentVersion = rs.getInt("value"); - rs.close(); - } else { - rs.close(); - conn.prepareStatement(CREATE_STORE_VERSION).execute(); - currentVersion = 0; - } - - conn.setAutoCommit(false); - var stmt = conn.prepareStatement(SET_STORE_VERSION); - while (currentVersion < availableVersion) { - try { - switch (currentVersion) { - case 0: - createStoreTables(); - break; - } - stmt.setInt(1, ++currentVersion); - stmt.execute(); - conn.commit(); - } - catch (Exception e) { - conn.rollback(); - LOG.log(System.Logger.Level.ERROR, "Failed to update at {} = {}", STORE_VERSION, currentVersion); - break; - } - } - conn.setAutoCommit(true); -} - -@Override -public String smtpHost() { - return smtpHost; -} - -@Override -public MailConfig smtpHost(String newValue) { - smtpHost = newValue; - return this; -} - -@Override -public int smtpPort() { - return smtpPort; -} - -@Override -public MailConfig smtpPort(int newValue) { - smtpPort = newValue; - return this; -} - -@Override -public String senderAddress() { - return senderAddress; -} - -@Override -public MailConfig senderAddress(String newValue) { - senderAddress = newValue; - return this; -} - -@Override -public String senderPassword() { - return password; -} - -@Override -public MailConfig senderPassword(String newValue) { - password = newValue; - return this; -} - -@Override -public boolean startTls() { - return startTls; -} - -@Override -public boolean smtpAuth() { - return smtpAuth; -} - -@Override -public MailConfig startTls(boolean newValue) { - startTls = newValue; - return this; -} - -@Override -public MailConfig smtpAuth(boolean newValue) { - smtpAuth = newValue; - return this; -} - -@Override -public Authenticator authenticator() { - if (auth == null) { - auth = new Authenticator() { - // override the getPasswordAuthentication method - protected PasswordAuthentication getPasswordAuthentication() { - return new PasswordAuthentication(senderAddress(), senderPassword()); - } - }; - } - return auth; -} - -@Override -public MailConfig save() { - try { - var stmt = conn.prepareStatement(SAVE_MAILCONFIG); - for (var entry : map().entrySet()) { - stmt.setString(1, entry.getKey()); - stmt.setObject(2, entry.getValue()); - stmt.setObject(3, entry.getValue()); - stmt.execute(); - } - return this; - } catch (SQLException e) { - throw new RuntimeException(e); - } -} -} diff --git a/de.srsoftware.oidc.datastore.sqlite/src/main/java/de/srsoftware/oidc/datastore/sqlite/SqliteSessionService.java b/de.srsoftware.oidc.datastore.sqlite/src/main/java/de/srsoftware/oidc/datastore/sqlite/SqliteSessionService.java deleted file mode 100644 index 86f3adc..0000000 --- a/de.srsoftware.oidc.datastore.sqlite/src/main/java/de/srsoftware/oidc/datastore/sqlite/SqliteSessionService.java +++ /dev/null @@ -1,130 +0,0 @@ -/* © SRSoftware 2024 */ -package de.srsoftware.oidc.datastore.sqlite; - -import static de.srsoftware.utils.Strings.uuid; -import static java.time.temporal.ChronoUnit.SECONDS; - -import de.srsoftware.oidc.api.SessionService; -import de.srsoftware.oidc.api.data.Session; -import de.srsoftware.oidc.api.data.User; -import java.sql.Connection; -import java.sql.SQLException; -import java.time.Instant; -import java.util.Optional; - -public class SqliteSessionService extends SqliteStore implements SessionService { - private static final String STORE_VERSION = "session_store_version"; - private static final String CREATE_STORE_VERSION = "INSERT INTO metainfo (key,value) VALUES ('" + STORE_VERSION + "','0')"; - private static final String SELECT_STORE_VERSION = "SELECT * FROM metainfo WHERE key = '" + STORE_VERSION + "'"; - private static final String SET_STORE_VERSION = "UPDATE metainfo SET value = ? WHERE key = '" + STORE_VERSION + "'"; - - private static final String CREATE_SESSION_TABLE = "CREATE TABLE sessions (id VARCHAR(64) PRIMARY KEY, userId VARCHAR(64) NOT NULL, expiration LONG NOT NULL, trust_browser BOOLEAN DEFAULT false)"; - private static final String SAVE_SESSION = "INSERT INTO sessions (id, userId, expiration, trust_browser) VALUES (?,?,?, ?) ON CONFLICT DO UPDATE SET expiration = ?, trust_browser = ?;"; - private static final String DROP_SESSION = "DELETE FROM sessions WHERE id = ?"; - private static final String SELECT_SESSION = "SELECT * FROM sessions WHERE id = ?"; - - public SqliteSessionService(Connection connection) throws SQLException { - super(connection); - } - - @Override - public Session createSession(User user, boolean trustBrowser) { - var now = Instant.now(); - var endOfSession = now.plus(user.sessionDuration()).truncatedTo(SECONDS); - return save(new Session(user.uuid(), endOfSession, uuid(), trustBrowser)); - } - - private void createStoreTables() throws SQLException { - conn.prepareStatement(CREATE_SESSION_TABLE).execute(); - } - - @Override - public SessionService dropSession(String sessionId) { - try { - var stmt = conn.prepareStatement(DROP_SESSION); - stmt.setString(1, sessionId); - stmt.execute(); - } catch (SQLException e) { - throw new RuntimeException(e); - } - return this; - } - - @Override - public Session extend(Session session, User user) { - var endOfSession = Instant.now().plus(user.sessionDuration()); - return save(new Session(user.uuid(), endOfSession, session.id(), session.trustBrowser())); - } - - @Override - protected void initTables() throws SQLException { - var rs = conn.prepareStatement(SELECT_STORE_VERSION).executeQuery(); - int availableVersion = 1; - int currentVersion; - if (rs.next()) { - currentVersion = rs.getInt("value"); - rs.close(); - } else { - rs.close(); - conn.prepareStatement(CREATE_STORE_VERSION).execute(); - currentVersion = 0; - } - - conn.setAutoCommit(false); - var stmt = conn.prepareStatement(SET_STORE_VERSION); - while (currentVersion < availableVersion) { - try { - switch (currentVersion) { - case 0: - createStoreTables(); - break; - } - stmt.setInt(1, ++currentVersion); - stmt.execute(); - conn.commit(); - } catch (Exception e) { - conn.rollback(); - LOG.log(System.Logger.Level.ERROR, "Failed to update at {} = {}", STORE_VERSION, currentVersion); - break; - } - } - conn.setAutoCommit(true); - } - - @Override - public Optional retrieve(String sessionId) { - try { - var stmt = conn.prepareStatement(SELECT_SESSION); - stmt.setString(1, sessionId); - var rs = stmt.executeQuery(); - Optional result = Optional.empty(); - if (rs.next()) { - var userID = rs.getString("userId"); - var expiration = Instant.ofEpochSecond(rs.getLong("expiration")); - var trustBrowser = rs.getBoolean("trust_browser"); - if (expiration.isAfter(Instant.now())) result = Optional.of(new Session(userID, expiration, sessionId, trustBrowser)); - } - rs.close(); - return result; - } catch (SQLException e) { - throw new RuntimeException(e); - } - } - - private Session save(Session session) { - try { - var stmt = conn.prepareStatement(SAVE_SESSION); - var expiration = session.expiration().getEpochSecond(); - stmt.setString(1, session.id()); - stmt.setString(2, session.userId()); - stmt.setLong(3, expiration); - stmt.setBoolean(4, session.trustBrowser()); - stmt.setLong(5, expiration); - stmt.setBoolean(6, session.trustBrowser()); - stmt.execute(); - return session; - } catch (SQLException e) { - throw new RuntimeException(e); - } - } -} diff --git a/de.srsoftware.oidc.datastore.sqlite/src/main/java/de/srsoftware/oidc/datastore/sqlite/SqliteStore.java b/de.srsoftware.oidc.datastore.sqlite/src/main/java/de/srsoftware/oidc/datastore/sqlite/SqliteStore.java deleted file mode 100644 index 19d7b5d..0000000 --- a/de.srsoftware.oidc.datastore.sqlite/src/main/java/de/srsoftware/oidc/datastore/sqlite/SqliteStore.java +++ /dev/null @@ -1,20 +0,0 @@ -/* © SRSoftware 2024 */ -package de.srsoftware.oidc.datastore.sqlite; - -import java.sql.Connection; -import java.sql.SQLException; - -public abstract class SqliteStore { - public static System.Logger LOG = System.getLogger(SqliteStore.class.getSimpleName()); - private static final String CREATE_MIGRATION_TABLE = "CREATE TABLE IF NOT EXISTS metainfo(key VARCHAR(255) PRIMARY KEY, value TEXT);"; - - protected final Connection conn; - - public SqliteStore(Connection connection) throws SQLException { - conn = connection; - conn.prepareStatement(CREATE_MIGRATION_TABLE).execute(); - initTables(); - } - - protected abstract void initTables() throws SQLException; -} diff --git a/de.srsoftware.oidc.datastore.sqlite/src/main/java/de/srsoftware/oidc/datastore/sqlite/SqliteUserService.java b/de.srsoftware.oidc.datastore.sqlite/src/main/java/de/srsoftware/oidc/datastore/sqlite/SqliteUserService.java deleted file mode 100644 index 33f88b9..0000000 --- a/de.srsoftware.oidc.datastore.sqlite/src/main/java/de/srsoftware/oidc/datastore/sqlite/SqliteUserService.java +++ /dev/null @@ -1,273 +0,0 @@ -/* © SRSoftware 2024 */ -package de.srsoftware.oidc.datastore.sqlite; - -import static de.srsoftware.utils.Optionals.nullable; -import static de.srsoftware.utils.Strings.uuid; -import static java.util.Optional.empty; - -import de.srsoftware.oidc.api.UserService; -import de.srsoftware.oidc.api.data.AccessToken; -import de.srsoftware.oidc.api.data.Permission; -import de.srsoftware.oidc.api.data.User; -import de.srsoftware.utils.PasswordHasher; -import java.sql.Connection; -import java.sql.ResultSet; -import java.sql.SQLException; -import java.time.Duration; -import java.time.Instant; -import java.time.temporal.ChronoUnit; -import java.util.*; - -public class SqliteUserService extends SqliteStore implements UserService { - private static final String STORE_VERSION = "user_store_version"; - private static final String CREATE_STORE_VERSION = "INSERT INTO metainfo (key,value) VALUES ('" + STORE_VERSION + "','0')"; - private static final String SELECT_STORE_VERSION = "SELECT * FROM metainfo WHERE key = '" + STORE_VERSION + "'"; - private static final String SET_STORE_VERSION = "UPDATE metainfo SET value = ? WHERE key = '" + STORE_VERSION + "'"; - - private static final String CREATE_USER_TABLE = "CREATE TABLE IF NOT EXISTS users(uuid VARCHAR(255) NOT NULL PRIMARY KEY, password VARCHAR(255), email VARCHAR(255), session_duration INT NOT NULL DEFAULT 10, username VARCHAR(255), realname VARCHAR(255));"; - private static final String CREATE_USER_PERMISSION_TABLE = "CREATE TABLE IF NOT EXISTS user_permissions(uuid VARCHAR(255), permission VARCHAR(50), PRIMARY KEY(uuid,permission));"; - 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_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 ? OR email = ? ORDER BY COALESCE(uuid, ?), username"; - private static final String LIST_USERS = "SELECT * FROM users"; - private static final String SAVE_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 DROP_PERMISSIONS = "DELETE FROM user_permissions 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 final PasswordHasher hasher; - - private Map accessTokens = new HashMap<>(); - - - public SqliteUserService(Connection connection, PasswordHasher passHasher) throws SQLException { - super(connection); - hasher = passHasher; - } - - @Override - public AccessToken accessToken(User user) { - var token = new AccessToken(uuid(), Objects.requireNonNull(user), Instant.now().plus(1, ChronoUnit.HOURS)); - accessTokens.put(token.id(), token); - return token; - } - - private User addPermissions(User user) { - try { - var stmt = conn.prepareStatement(LOAD_PERMISSIONS); - stmt.setString(1, user.uuid()); - var rs = stmt.executeQuery(); - while (rs.next()) try { - user.add(Permission.valueOf(rs.getString("permission"))); - } catch (IllegalArgumentException ignored) { - } - rs.close(); - return user; - } catch (SQLException e) { - throw new RuntimeException(e); - } - } - - @Override - public Optional consumeToken(String id) { - var user = forToken(id); - accessTokens.remove(id); - return user; - } - - private void createStoreTables() throws SQLException { - conn.prepareStatement(CREATE_USER_TABLE).execute(); - conn.prepareStatement(CREATE_USER_PERMISSION_TABLE).execute(); - } - - @Override - public UserService delete(User user) { - try { - conn.setAutoCommit(false); - dropPermissionsOf(user.uuid()); - var stmt = conn.prepareStatement(DROP_USER); - stmt.setString(1, user.uuid()); - stmt.execute(); - conn.commit(); - } catch (SQLException e) { - throw new RuntimeException(e); - } - return this; - } - - private void dropPermissionsOf(String uuid) throws SQLException { - var stmt = conn.prepareStatement(DROP_PERMISSIONS); - stmt.setString(1, uuid); - stmt.execute(); - } - - @Override - public Set find(String idOrEmail) { - try { - var result = new HashSet(); - var stmt = conn.prepareStatement(FIND_USER); // TODO: implement test for this query - stmt.setString(1, idOrEmail); - stmt.setString(2, "%" + idOrEmail + "%"); - stmt.setString(3, "%" + idOrEmail + "%"); - stmt.setString(4, idOrEmail); - stmt.setString(5, idOrEmail); - var rs = stmt.executeQuery(); - while (rs.next()) result.add(userFrom(rs)); - rs.close(); - return result; - } catch (SQLException e) { - throw new RuntimeException(e); - } - } - - @Override - public Optional forToken(String id) { - AccessToken token = accessTokens.get(id); - if (token == null) return empty(); - if (token.valid()) return Optional.of(token.user()); - accessTokens.remove(id); - return empty(); - } - - @Override - public UserService init(User defaultUser) { - try { - var rs = conn.prepareStatement(COUNT_USERS).executeQuery(); - var count = rs.next() ? rs.getInt(1) : 0; - rs.close(); - if (count < 1) save(defaultUser); - } catch (SQLException e) { - throw new RuntimeException(e); - } - return this; - } - - @Override - protected void initTables() throws SQLException { - var rs = conn.prepareStatement(SELECT_STORE_VERSION).executeQuery(); - int availableVersion = 1; - int currentVersion; - if (rs.next()) { - currentVersion = rs.getInt("value"); - rs.close(); - } else { - rs.close(); - conn.prepareStatement(CREATE_STORE_VERSION).execute(); - currentVersion = 0; - } - - conn.setAutoCommit(false); - var stmt = conn.prepareStatement(SET_STORE_VERSION); - while (currentVersion < availableVersion) { - try { - switch (currentVersion) { - case 0: - createStoreTables(); - break; - } - stmt.setInt(1, ++currentVersion); - stmt.execute(); - conn.commit(); - } catch (Exception e) { - conn.rollback(); - LOG.log(System.Logger.Level.ERROR, "Failed to update at {} = {}", STORE_VERSION, currentVersion); - break; - } - } - conn.setAutoCommit(true); - } - - - @Override - public List list() { - try { - List result = new ArrayList<>(); - var rs = conn.prepareStatement(LIST_USERS).executeQuery(); - while (rs.next()) result.add(userFrom(rs)); - rs.close(); - for (User user : result) addPermissions(user); - return result; - } catch (SQLException e) { - throw new RuntimeException(e); - } - } - - @Override - public Optional load(String id) { - try { - User user = null; - var stmt = conn.prepareStatement(LOAD_USER); - stmt.setString(1, id); - var rs = stmt.executeQuery(); - if (rs.next()) user = userFrom(rs); - rs.close(); - return nullable(user).map(this::addPermissions); - } catch (SQLException e) { - throw new RuntimeException(e); - } - } - - @Override - public Optional load(String username, String password) { - var candidates = find(username); - for (var user : candidates) { - if (passwordMatches(password, user)) return Optional.of(user); - } - return empty(); - } - - @Override - public boolean passwordMatches(String password, User user) { - return hasher.matches(password, user.hashedPassword()); - } - - @Override - public SqliteUserService save(User user) { - try { - conn.setAutoCommit(false); - var stmt = conn.prepareStatement(SAVE_USER); - stmt.setString(1, user.uuid()); - stmt.setString(2, user.hashedPassword()); - stmt.setString(3, user.email()); - stmt.setLong(4, user.sessionDuration().toMinutes()); - stmt.setString(5, user.username()); - 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(); - dropPermissionsOf(user.uuid()); - - stmt = conn.prepareStatement(INSERT_PERMISSIONS); - stmt.setString(1, user.uuid()); - for (Permission perm : Permission.values()) { - if (user.hasPermission(perm)) { - stmt.setString(2, perm.toString()); - stmt.execute(); - } - } - conn.commit(); - return this; - } catch (SQLException e) { - throw new RuntimeException(e); - } - } - - @Override - public SqliteUserService updatePassword(User user, String plaintextPassword) { - return save(user.hashedPassword(hasher.hash(plaintextPassword, uuid()))); - } - - private User userFrom(ResultSet rs) throws SQLException { - var uuid = rs.getString("uuid"); - var pass = rs.getString("password"); - var user = rs.getString("username"); - var name = rs.getString("realname"); - var mail = rs.getString("email"); - var mins = rs.getLong("session_duration"); - return new User(user, pass, name, mail, uuid).sessionDuration(Duration.ofMinutes(mins)); - } -} diff --git a/de.srsoftware.oidc.datastore.sqlite/src/test/java/de/srsoftware/oidc/datastore/sqlite/SqliteAuthServiceTest.java b/de.srsoftware.oidc.datastore.sqlite/src/test/java/de/srsoftware/oidc/datastore/sqlite/SqliteAuthServiceTest.java deleted file mode 100644 index b5a5561..0000000 --- a/de.srsoftware.oidc.datastore.sqlite/src/test/java/de/srsoftware/oidc/datastore/sqlite/SqliteAuthServiceTest.java +++ /dev/null @@ -1,26 +0,0 @@ -/* © SRSoftware 2024 */ -package de.srsoftware.oidc.datastore.sqlite; - -import static de.srsoftware.utils.Strings.uuid; - -import de.srsoftware.oidc.api.AuthServiceTest; -import de.srsoftware.oidc.api.AuthorizationService; -import java.io.File; -import java.sql.SQLException; -import org.junit.jupiter.api.BeforeEach; - -public class SqliteAuthServiceTest extends AuthServiceTest { - private AuthorizationService authorizationService; - - @Override - protected AuthorizationService authorizationService() { - return authorizationService; - } - - @BeforeEach - public void setup() throws SQLException { - var dbFile = new File("/tmp/" + uuid() + ".sqlite"); - var conn = new ConnectionProvider().get(dbFile); - authorizationService = new SqliteAuthService(conn); - } -} diff --git a/de.srsoftware.oidc.datastore.sqlite/src/test/java/de/srsoftware/oidc/datastore/sqlite/SqliteClientServiceTest.java b/de.srsoftware.oidc.datastore.sqlite/src/test/java/de/srsoftware/oidc/datastore/sqlite/SqliteClientServiceTest.java deleted file mode 100644 index 6fac3b9..0000000 --- a/de.srsoftware.oidc.datastore.sqlite/src/test/java/de/srsoftware/oidc/datastore/sqlite/SqliteClientServiceTest.java +++ /dev/null @@ -1,26 +0,0 @@ -/* © SRSoftware 2024 */ -package de.srsoftware.oidc.datastore.sqlite; - -import static de.srsoftware.utils.Strings.uuid; - -import de.srsoftware.oidc.api.ClientService; -import de.srsoftware.oidc.api.ClientServiceTest; -import java.io.File; -import java.sql.SQLException; -import org.junit.jupiter.api.BeforeEach; - -public class SqliteClientServiceTest extends ClientServiceTest { - private ClientService clientService; - - @Override - protected ClientService clientService() { - return clientService; - } - - @BeforeEach - public void setup() throws SQLException { - var dbFile = new File("/tmp/" + uuid() + ".sqlite"); - var conn = new ConnectionProvider().get(dbFile); - clientService = new SqliteClientService(conn); - } -} diff --git a/de.srsoftware.oidc.datastore.sqlite/src/test/java/de/srsoftware/oidc/datastore/sqlite/SqliteKeyStoreTest.java b/de.srsoftware.oidc.datastore.sqlite/src/test/java/de/srsoftware/oidc/datastore/sqlite/SqliteKeyStoreTest.java deleted file mode 100644 index d762200..0000000 --- a/de.srsoftware.oidc.datastore.sqlite/src/test/java/de/srsoftware/oidc/datastore/sqlite/SqliteKeyStoreTest.java +++ /dev/null @@ -1,26 +0,0 @@ -/* © SRSoftware 2024 */ -package de.srsoftware.oidc.datastore.sqlite; - -import static de.srsoftware.utils.Strings.uuid; - -import de.srsoftware.oidc.api.KeyStorage; -import de.srsoftware.oidc.api.KeyStoreTest; -import java.io.File; -import java.sql.SQLException; -import org.junit.jupiter.api.BeforeEach; - -public class SqliteKeyStoreTest extends KeyStoreTest { - private KeyStorage keyStore; - - @Override - protected KeyStorage keyStore() { - return keyStore; - } - - @BeforeEach - public void setup() throws SQLException { - var dbFile = new File("/tmp/" + uuid() + ".sqlite"); - var conn = new ConnectionProvider().get(dbFile); - keyStore = new SqliteKeyStore(conn); - } -} diff --git a/de.srsoftware.oidc.datastore.sqlite/src/test/java/de/srsoftware/oidc/datastore/sqlite/SqliteMailConfigTest.java b/de.srsoftware.oidc.datastore.sqlite/src/test/java/de/srsoftware/oidc/datastore/sqlite/SqliteMailConfigTest.java deleted file mode 100644 index cde07c3..0000000 --- a/de.srsoftware.oidc.datastore.sqlite/src/test/java/de/srsoftware/oidc/datastore/sqlite/SqliteMailConfigTest.java +++ /dev/null @@ -1,37 +0,0 @@ -/* © SRSoftware 2024 */ -package de.srsoftware.oidc.datastore.sqlite; - -import static de.srsoftware.utils.Strings.uuid; - -import de.srsoftware.oidc.api.MailConfig; -import de.srsoftware.oidc.api.MailConfigTest; -import java.io.File; -import java.sql.SQLException; -import org.junit.jupiter.api.BeforeEach; - -public class SqliteMailConfigTest extends MailConfigTest { - private SqliteMailConfig mailConfig; - private File dbFile; - - @Override - protected MailConfig mailConfig() { - return mailConfig; - } - - @Override - protected void reOpen() { - try { - var conn = new ConnectionProvider().get(dbFile); - mailConfig = new SqliteMailConfig(conn); - } catch (SQLException e) { - throw new RuntimeException(e); - } - } - - @BeforeEach - public void setup() throws SQLException { - dbFile = new File("/tmp/" + uuid() + ".sqlite"); - var conn = new ConnectionProvider().get(dbFile); - mailConfig = new SqliteMailConfig(conn); - } -} diff --git a/de.srsoftware.oidc.datastore.sqlite/src/test/java/de/srsoftware/oidc/datastore/sqlite/SqliteSessionServiceTest.java b/de.srsoftware.oidc.datastore.sqlite/src/test/java/de/srsoftware/oidc/datastore/sqlite/SqliteSessionServiceTest.java deleted file mode 100644 index 1110f67..0000000 --- a/de.srsoftware.oidc.datastore.sqlite/src/test/java/de/srsoftware/oidc/datastore/sqlite/SqliteSessionServiceTest.java +++ /dev/null @@ -1,32 +0,0 @@ -/* © SRSoftware 2024 */ -package de.srsoftware.oidc.datastore.sqlite; - - -import de.srsoftware.oidc.api.SessionService; -import de.srsoftware.oidc.api.SessionServiceTest; -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 SqliteSessionServiceTest extends SessionServiceTest { - private File storage = new File("/tmp/" + UUID.randomUUID()); - private SessionService sessionService = null; - - @AfterEach - public void tearDown() { - if (storage.exists()) storage.delete(); - } - - @BeforeEach - public void setup() throws SQLException { - tearDown(); - sessionService = new SqliteSessionService(new ConnectionProvider().get(storage)); - } - - @Override - protected SessionService sessionService() { - return sessionService; - } -} diff --git a/de.srsoftware.oidc.datastore.sqlite/src/test/java/de/srsoftware/oidc/datastore/sqlite/SqliteUserServiceTest.java b/de.srsoftware.oidc.datastore.sqlite/src/test/java/de/srsoftware/oidc/datastore/sqlite/SqliteUserServiceTest.java deleted file mode 100644 index 8e9a646..0000000 --- a/de.srsoftware.oidc.datastore.sqlite/src/test/java/de/srsoftware/oidc/datastore/sqlite/SqliteUserServiceTest.java +++ /dev/null @@ -1,32 +0,0 @@ -/* © 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; - } -} diff --git a/settings.gradle b/settings.gradle index f0ebac6..6fd4b19 100644 --- a/settings.gradle +++ b/settings.gradle @@ -4,9 +4,8 @@ include 'de.srsoftware.logging' include 'de.srsoftware.oidc.api' include 'de.srsoftware.oidc.app' include 'de.srsoftware.oidc.backend' +include 'de.srsoftware.oidc.datastore.encrypted' include 'de.srsoftware.oidc.datastore.file' include 'de.srsoftware.oidc.web' include 'de.srsoftware.utils' -include 'de.srsoftware.oidc.datastore.sqlite' -include 'de.srsoftware.oidc.datastore.encrypted'