Browse Source

preparing sqlite-based services

Signed-off-by: Stephan Richter <s.richter@srsoftware.de>
sqlite
Stephan Richter 3 months ago
parent
commit
06cb6abdc6
  1. 2
      de.srsoftware.oidc.app/build.gradle
  2. 98
      de.srsoftware.oidc.app/src/main/java/de/srsoftware/oidc/app/Application.java
  3. 41
      de.srsoftware.oidc.app/src/main/java/de/srsoftware/oidc/app/Configuration.java
  4. 28
      de.srsoftware.oidc.datastore.file/src/main/java/de/srsoftware/oidc/datastore/file/FileStoreProvider.java
  5. 2
      de.srsoftware.oidc.datastore.sqlite/build.gradle
  6. 27
      de.srsoftware.oidc.datastore.sqlite/src/main/java/de/srsoftware/oidc/datastore/sqlite/ConnectionProvider.java
  7. 32
      de.srsoftware.oidc.datastore.sqlite/src/main/java/de/srsoftware/oidc/datastore/sqlite/SqliteAuthService.java
  8. 33
      de.srsoftware.oidc.datastore.sqlite/src/main/java/de/srsoftware/oidc/datastore/sqlite/SqliteClientService.java
  9. 71
      de.srsoftware.oidc.datastore.sqlite/src/main/java/de/srsoftware/oidc/datastore/sqlite/SqliteMailConfig.java
  10. 39
      de.srsoftware.oidc.datastore.sqlite/src/main/java/de/srsoftware/oidc/datastore/sqlite/SqliteSessionService.java
  11. 77
      de.srsoftware.oidc.datastore.sqlite/src/main/java/de/srsoftware/oidc/datastore/sqlite/SqliteUserService.java
  12. 6
      de.srsoftware.utils/src/main/java/de/srsoftware/utils/Paths.java

2
de.srsoftware.oidc.app/build.gradle

@ -21,6 +21,8 @@ dependencies { @@ -21,6 +21,8 @@ dependencies {
implementation project(':de.srsoftware.utils')
implementation project(':de.srsoftware.oidc.datastore.file')
implementation project(':de.srsoftware.oidc.datastore.sqlite')
implementation 'org.json:json:20240303'
}
test {

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

@ -6,6 +6,7 @@ import static de.srsoftware.oidc.api.Constants.*; @@ -6,6 +6,7 @@ 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;
@ -14,21 +15,21 @@ import static java.util.Optional.empty; @@ -14,21 +15,21 @@ import static java.util.Optional.empty;
import com.sun.net.httpserver.HttpServer;
import de.srsoftware.logging.ColorLogger;
import de.srsoftware.oidc.api.KeyManager;
import de.srsoftware.oidc.api.KeyStorage;
import de.srsoftware.oidc.api.*;
import de.srsoftware.oidc.api.data.User;
import de.srsoftware.oidc.backend.*;
import de.srsoftware.oidc.datastore.file.FileStore;
import de.srsoftware.oidc.datastore.file.FileStoreProvider;
import de.srsoftware.oidc.datastore.file.PlaintextKeyStore;
import de.srsoftware.oidc.datastore.file.UuidHasher;
import de.srsoftware.oidc.datastore.sqlite.SqliteKeyStore;
import de.srsoftware.oidc.datastore.sqlite.*;
import de.srsoftware.oidc.web.Forward;
import de.srsoftware.oidc.web.StaticPages;
import java.io.File;
import java.net.InetSocketAddress;
import java.nio.file.Path;
import java.util.*;
import java.util.concurrent.Executors;
import org.sqlite.SQLiteDataSource;
public class Application {
public static final String API_CLIENT = "/api/client";
@ -42,41 +43,76 @@ public class Application { @@ -42,41 +43,76 @@ 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);
public static void main(String[] args) throws Exception {
var argMap = map(args);
Optional<Path> basePath = argMap.get(BASE_PATH) instanceof Path p ? Optional.of(p) : empty();
var configFile = (argMap.get(CONFIG_PATH) instanceof Path p ? p : configDir(APP_NAME).resolve("config.json")).toFile();
var storageFile = configDir(APP_NAME).resolve("data.json").toFile();
var keyDir = storageFile.getParentFile().toPath().resolve("keys");
var passwordHasher = new UuidHasher();
var firstHash = passwordHasher.hash(FIRST_USER_PASS, FIRST_UUID);
var firstUser = new User(FIRST_USER, firstHash, FIRST_USER, "%s@internal".formatted(FIRST_USER), FIRST_UUID).add(MANAGE_CLIENTS, MANAGE_PERMISSIONS, MANAGE_SMTP, MANAGE_USERS);
KeyStorage keyStore = new PlaintextKeyStore(keyDir);
{ // SQLite
SQLiteDataSource dataSource = new SQLiteDataSource();
var dbFile = storageFile.getParentFile().toPath().resolve("db.sqlite3").toFile();
dataSource.setUrl("jdbc:sqlite:%s".formatted(dbFile));
var conn = dataSource.getConnection();
keyStore = new SqliteKeyStore(conn);
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();
public static void main(String[] args) throws Exception {
var argMap = map(args);
Optional<Path> basePath = argMap.get(BASE_PATH) instanceof Path p ? Optional.of(p) : empty();
var configFile = (argMap.get(CONFIG_PATH) instanceof Path p ? p : configDir(APP_NAME).resolve("config.json")).toFile();
var config = new Configuration(configFile);
var defaultConfigDir = configDir(APP_NAME);
var passwordHasher = new UuidHasher();
var firstHash = passwordHasher.hash(FIRST_USER_PASS, FIRST_UUID);
var firstUser = new User(FIRST_USER, firstHash, FIRST_USER, "%s@internal".formatted(FIRST_USER), FIRST_UUID).add(MANAGE_CLIENTS, MANAGE_PERMISSIONS, MANAGE_SMTP, MANAGE_USERS);
var defaultFile = defaultConfigDir.resolve("data.json");
var keyStorageLocation = new File(config.getOrDefault("key_storage", defaultConfigDir.resolve("keys")));
KeyStorage keyStore;
if ((keyStorageLocation.exists() && keyStorageLocation.isDirectory()) || !keyStorageLocation.getName().contains(".")) {
keyStore = new PlaintextKeyStore(keyStorageLocation.toPath());
} else { // SQLite
var conn = connectionProvider.get(keyStorageLocation);
keyStore = new SqliteKeyStore(conn);
}
KeyManager keyManager = new RotatingKeyManager(keyStore);
FileStore fileStore = new FileStore(storageFile, passwordHasher).init(firstUser);
FileStoreProvider fileStoreProvider = new FileStoreProvider(passwordHasher);
var userStorageLocation = new File(config.getOrDefault("user_storage",defaultFile));
var userService = switch (extension(userStorageLocation).toLowerCase()){
case "db", "sqlite", "sqlite3" -> new SqliteUserService(connectionProvider.get(userStorageLocation));
default -> fileStoreProvider.get(userStorageLocation);
};
userService.init(firstUser);
var mailConfigLocation = new File(config.getOrDefault("mail_config_storage",defaultFile));
var mailConfig = switch (extension(mailConfigLocation)){
case "db", "sqlite", "sqlite3" -> new SqliteMailConfig(connectionProvider.get(userStorageLocation));
default -> fileStoreProvider.get(mailConfigLocation);
};
var sessionStore = new File(config.getOrDefault("session_storage",defaultFile));
var sessionService = switch (extension(sessionStore)){
case "db", "sqlite", "sqlite3" -> new SqliteSessionService(connectionProvider.get(sessionStore));
default -> fileStoreProvider.get(sessionStore);
};
var authServiceLocation = new File(config.getOrDefault("auth_store",defaultFile));
AuthorizationService authService = switch (extension(authServiceLocation)){
case "db", "sqlite", "sqlite3" -> new SqliteAuthService(connectionProvider.get(sessionStore));
default -> fileStoreProvider.get(sessionStore);
};
var clientStore = new File(config.getOrDefault("client_store",defaultFile));
ClientService clientService = switch (extension(clientStore)){
case "db", "sqlite", "sqlite3" -> new SqliteClientService(connectionProvider.get(sessionStore));
default -> fileStoreProvider.get(sessionStore);
};
HttpServer server = HttpServer.create(new InetSocketAddress(8080), 0);
var staticPages = (StaticPages) new StaticPages(basePath).bindPath(STATIC_PATH, FAVICON).on(server);
new Forward(INDEX).bindPath(ROOT).on(server);
new WellKnownController().bindPath(WELL_KNOWN).on(server);
new UserController(fileStore, fileStore, fileStore, staticPages).bindPath(API_USER).on(server);
new UserController(mailConfig, sessionService, userService, staticPages).bindPath(API_USER).on(server);
var tokenControllerConfig = new TokenController.Configuration("https://lightoidc.srsoftware.de", 10); // TODO configure or derive from hostname
new TokenController(fileStore, fileStore, keyManager, fileStore, tokenControllerConfig).bindPath(API_TOKEN).on(server);
new ClientController(fileStore, fileStore, fileStore).bindPath(API_CLIENT).on(server);
new TokenController(authService, clientService, keyManager, userService, tokenControllerConfig).bindPath(API_TOKEN).on(server);
new ClientController(authService, clientService, sessionService).bindPath(API_CLIENT).on(server);
new KeyStoreController(keyStore).bindPath(JWKS).on(server);
new EmailController(fileStore, fileStore).bindPath(API_EMAIL).on(server);
new EmailController(mailConfig, sessionService).bindPath(API_EMAIL).on(server);
server.setExecutor(Executors.newCachedThreadPool());
server.start();
}

41
de.srsoftware.oidc.app/src/main/java/de/srsoftware/oidc/app/Configuration.java

@ -0,0 +1,41 @@ @@ -0,0 +1,41 @@
/* © SRSoftware 2024 */
package de.srsoftware.oidc.app;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import org.json.JSONObject;
public class Configuration {
private final JSONObject json;
private final Path storageFile;
public Configuration(File storage) throws IOException {
storageFile = storage.toPath();
if (!storage.exists()) {
var parent = storage.getParentFile();
if (!parent.exists() && !parent.mkdirs()) throw new FileNotFoundException("Failed to create directory %s".formatted(parent));
Files.writeString(storageFile, "{}");
}
json = new JSONObject(Files.readString(storageFile));
}
public String getOrDefault(String key, Object defaultValue) {
if (!json.has(key)) {
json.put(key, defaultValue.toString());
save();
}
return json.getString(key);
}
public Configuration save() {
try {
Files.writeString(storageFile, json.toString(2));
return this;
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}

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

@ -0,0 +1,28 @@ @@ -0,0 +1,28 @@
/* © SRSoftware 2024 */
package de.srsoftware.oidc.datastore.file;
import java.io.File;
import java.io.IOException;
import java.util.HashMap;
public class FileStoreProvider extends HashMap<File, FileStore> {
private UuidHasher hasher;
public FileStoreProvider(UuidHasher passwordHasher) {
hasher = passwordHasher;
}
@Override
public FileStore get(Object o) {
if (o instanceof File storageFile) try {
var fileStore = super.get(storageFile);
if (fileStore == null) put(storageFile, fileStore = new FileStore(storageFile, hasher));
return fileStore;
} catch (IOException ioex) {
throw new RuntimeException(ioex);
}
return null;
}
}

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

@ -16,6 +16,8 @@ dependencies { @@ -16,6 +16,8 @@ dependencies {
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 {

27
de.srsoftware.oidc.datastore.sqlite/src/main/java/de/srsoftware/oidc/datastore/sqlite/ConnectionProvider.java

@ -0,0 +1,27 @@ @@ -0,0 +1,27 @@
/* © 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<File, Connection> {
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();
}
}

32
de.srsoftware.oidc.datastore.sqlite/src/main/java/de/srsoftware/oidc/datastore/sqlite/SqliteAuthService.java

@ -0,0 +1,32 @@ @@ -0,0 +1,32 @@
/* © SRSoftware 2024 */
package de.srsoftware.oidc.datastore.sqlite;
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.Client;
import de.srsoftware.oidc.api.data.User;
import java.sql.Connection;
import java.time.Instant;
import java.util.Collection;
import java.util.Optional;
public class SqliteAuthService implements AuthorizationService {
public SqliteAuthService(Connection connection) {
}
@Override
public AuthorizationService authorize(User user, Client client, Collection<String> scopes, Instant expiration) {
return null;
}
@Override
public Optional<Authorization> consumeAuthorization(String authCode) {
return Optional.empty();
}
@Override
public AuthResult getAuthorization(User user, Client client, Collection<String> scopes) {
return null;
}
}

33
de.srsoftware.oidc.datastore.sqlite/src/main/java/de/srsoftware/oidc/datastore/sqlite/SqliteClientService.java

@ -0,0 +1,33 @@ @@ -0,0 +1,33 @@
/* © SRSoftware 2024 */
package de.srsoftware.oidc.datastore.sqlite;
import de.srsoftware.oidc.api.ClientService;
import de.srsoftware.oidc.api.data.Client;
import java.sql.Connection;
import java.util.List;
import java.util.Optional;
public class SqliteClientService implements ClientService {
public SqliteClientService(Connection connection) {
}
@Override
public Optional<Client> getClient(String clientId) {
return Optional.empty();
}
@Override
public List<Client> listClients() {
return List.of();
}
@Override
public ClientService remove(Client client) {
return null;
}
@Override
public ClientService save(Client client) {
return null;
}
}

71
de.srsoftware.oidc.datastore.sqlite/src/main/java/de/srsoftware/oidc/datastore/sqlite/SqliteMailConfig.java

@ -0,0 +1,71 @@ @@ -0,0 +1,71 @@
/* © SRSoftware 2024 */
package de.srsoftware.oidc.datastore.sqlite;
import de.srsoftware.oidc.api.MailConfig;
import jakarta.mail.Authenticator;
import java.sql.Connection;
public class SqliteMailConfig implements MailConfig {
public SqliteMailConfig(Connection connection) {
}
@Override
public String smtpHost() {
return "";
}
@Override
public MailConfig smtpHost(String newValue) {
return null;
}
@Override
public int smtpPort() {
return 0;
}
@Override
public MailConfig smtpPort(int newValue) {
return null;
}
@Override
public String senderAddress() {
return "";
}
@Override
public MailConfig senderAddress(String newValue) {
return null;
}
@Override
public String senderPassword() {
return "";
}
@Override
public MailConfig senderPassword(String newValue) {
return null;
}
@Override
public MailConfig startTls(boolean newValue) {
return null;
}
@Override
public MailConfig smtpAuth(boolean newValue) {
return null;
}
@Override
public Authenticator authenticator() {
return null;
}
@Override
public MailConfig save() {
return null;
}
}

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

@ -0,0 +1,39 @@ @@ -0,0 +1,39 @@
/* © SRSoftware 2024 */
package de.srsoftware.oidc.datastore.sqlite;
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.time.Duration;
import java.util.Optional;
public class SqliteSessionService implements SessionService {
public SqliteSessionService(Connection connection) {
}
@Override
public Session createSession(User user) {
return null;
}
@Override
public SessionService dropSession(String sessionId) {
return null;
}
@Override
public Session extend(Session session) {
return null;
}
@Override
public Optional<Session> retrieve(String sessionId) {
return Optional.empty();
}
@Override
public SessionService setDuration(Duration duration) {
return null;
}
}

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

@ -0,0 +1,77 @@ @@ -0,0 +1,77 @@
/* © SRSoftware 2024 */
package de.srsoftware.oidc.datastore.sqlite;
import de.srsoftware.oidc.api.UserService;
import de.srsoftware.oidc.api.data.AccessToken;
import de.srsoftware.oidc.api.data.User;
import java.sql.Connection;
import java.util.List;
import java.util.Optional;
import java.util.Set;
public class SqliteUserService implements UserService {
private final Connection conn;
public SqliteUserService(Connection connection) {
conn = connection;
}
@Override
public AccessToken accessToken(User user) {
return null;
}
@Override
public Optional<User> consumeToken(String accessToken) {
return Optional.empty();
}
@Override
public UserService delete(User user) {
return null;
}
@Override
public Optional<User> forToken(String accessToken) {
return Optional.empty();
}
@Override
public UserService init(User defaultUser) {
return null;
}
@Override
public List<User> list() {
return List.of();
}
@Override
public Set<User> find(String key) {
return Set.of();
}
@Override
public Optional<User> load(String id) {
return Optional.empty();
}
@Override
public Optional<User> load(String username, String password) {
return Optional.empty();
}
@Override
public boolean passwordMatches(String password, String hashedPassword) {
return false;
}
@Override
public <T extends UserService> T save(User user) {
return null;
}
@Override
public <T extends UserService> T updatePassword(User user, String plaintextPassword) {
return null;
}
}

6
de.srsoftware.utils/src/main/java/de/srsoftware/utils/Paths.java

@ -1,6 +1,7 @@ @@ -1,6 +1,7 @@
/* © SRSoftware 2024 */
package de.srsoftware.utils;
import java.io.File;
import java.nio.file.Path;
public class Paths {
@ -16,4 +17,9 @@ public class Paths { @@ -16,4 +17,9 @@ public class Paths {
public static Path configDir(Object clazz) {
return configDir(clazz.getClass());
}
public static String extension(File file) {
var parts = file.getName().split("\\.");
return parts.length == 1 ? "" : parts[parts.length - 1];
}
}

Loading…
Cancel
Save