Browse Source

improved UI

Signed-off-by: Stephan Richter <s.richter@srsoftware.de>
Stephan Richter 6 months ago
parent
commit
8cccd8b4e8
  1. 184
      de.srsoftware.http/src/main/java/de/srsoftware/http/PathHandler.java
  2. 46
      de.srsoftware.oidc.app/src/main/java/de/srsoftware/oidc/app/Application.java
  3. 2
      de.srsoftware.oidc.backend/src/main/java/de/srsoftware/oidc/backend/ClientController.java
  4. 179
      de.srsoftware.oidc.datastore.sqlite/src/main/java/de/srsoftware/oidc/datastore/sqlite/SqliteMailConfig.java
  5. 4
      de.srsoftware.oidc.web/src/main/resources/de/reset.html
  6. 5
      de.srsoftware.oidc.web/src/main/resources/en/reset.html
  7. 14
      de.srsoftware.oidc.web/src/main/resources/en/scripts/index.js
  8. 12
      de.srsoftware.oidc.web/src/main/resources/en/scripts/reset.js
  9. 1
      settings.gradle

184
de.srsoftware.http/src/main/java/de/srsoftware/http/PathHandler.java

@ -76,9 +76,9 @@ public abstract class PathHandler implements HttpHandler {
String method = ex.getRequestMethod(); String method = ex.getRequestMethod();
LOG.log(INFO, "{0} {1}", method, path); LOG.log(INFO, "{0} {1}", method, path);
boolean ignored = switch (method) { boolean ignored = switch (method) {
case DELETE -> doDelete(path,ex); case DELETE -> doDelete(path, ex);
case GET -> doGet(path,ex); case GET -> doGet(path, ex);
case POST -> doPost(path,ex); case POST -> doPost(path, ex);
default -> false; default -> false;
}; };
ex.getResponseBody().close(); ex.getResponseBody().close();
@ -86,116 +86,116 @@ public abstract class PathHandler implements HttpHandler {
public String relativePath(HttpExchange ex) { public String relativePath(HttpExchange ex) {
var requestPath = ex.getRequestURI().toString(); var requestPath = ex.getRequestURI().toString();
for (var path : paths){ for (var path : paths) {
if (requestPath.startsWith(path)) { if (requestPath.startsWith(path)) {
requestPath = requestPath.substring(path.length()); requestPath = requestPath.substring(path.length());
break; break;
} }
} }
if (!requestPath.startsWith("/")) requestPath = "/" + requestPath; if (!requestPath.startsWith("/")) requestPath = "/" + requestPath;
var pos = requestPath.indexOf("?"); var pos = requestPath.indexOf("?");
if (pos >= 0) requestPath = requestPath.substring(0, pos); if (pos >= 0) requestPath = requestPath.substring(0, pos);
return requestPath; return requestPath;
} }
/******* begin of static methods *************/ /******* begin of static methods *************/
public static String body(HttpExchange ex) throws IOException { public static String body(HttpExchange ex) throws IOException {
return new String(ex.getRequestBody().readAllBytes(), UTF_8); return new String(ex.getRequestBody().readAllBytes(), UTF_8);
} }
public static Optional<String> getAuthToken(HttpExchange ex) { public static Optional<String> getAuthToken(HttpExchange ex) {
return getHeader(ex, AUTHORIZATION); return getHeader(ex, AUTHORIZATION);
} }
public static Optional<BasicAuth> getBasicAuth(HttpExchange ex) { public static Optional<BasicAuth> getBasicAuth(HttpExchange ex) {
return getAuthToken(ex) return getAuthToken(ex)
.filter(token -> token.startsWith("Basic ")) // .filter(token -> token.startsWith("Basic ")) //
.map(token -> token.substring(6)) .map(token -> token.substring(6))
.map(Base64.getDecoder()::decode) .map(Base64.getDecoder()::decode)
.map(bytes -> new String(bytes, UTF_8)) .map(bytes -> new String(bytes, UTF_8))
.map(token -> token.split(":", 2)) .map(token -> token.split(":", 2))
.map(arr -> new BasicAuth(arr[0], arr[1])); .map(arr -> new BasicAuth(arr[0], arr[1]));
} }
public static Optional<String> getBearer(HttpExchange ex) { public static Optional<String> getBearer(HttpExchange ex) {
return getAuthToken(ex).filter(token -> token.startsWith("Bearer ")).map(token -> token.substring(7)); return getAuthToken(ex).filter(token -> token.startsWith("Bearer ")).map(token -> token.substring(7));
} }
public static Optional<String> getHeader(HttpExchange ex, String key) { public static Optional<String> getHeader(HttpExchange ex, String key) {
return nullable(ex.getRequestHeaders().get(key)).map(List::stream).flatMap(Stream::findFirst); return nullable(ex.getRequestHeaders().get(key)).map(List::stream).flatMap(Stream::findFirst);
} }
public static String hostname(HttpExchange ex) { public static String hostname(HttpExchange ex) {
var headers = ex.getRequestHeaders(); var headers = ex.getRequestHeaders();
var host = headers.getFirst(FORWARDED_HOST); var host = headers.getFirst(FORWARDED_HOST);
if (host == null) host = headers.getFirst(HOST); if (host == null) host = headers.getFirst(HOST);
var proto = nullable(headers.getFirst("X-forwarded-proto")).orElseGet(() -> ex instanceof HttpsExchange ? "https" : "http"); var proto = nullable(headers.getFirst("X-forwarded-proto")).orElseGet(() -> ex instanceof HttpsExchange ? "https" : "http");
return host == null ? null : proto + "://" + host; return host == null ? null : proto + "://" + host;
} }
public static JSONObject json(HttpExchange ex) throws IOException { public static JSONObject json(HttpExchange ex) throws IOException {
return new JSONObject(body(ex)); return new JSONObject(body(ex));
} }
public static String language(HttpExchange ex) { public static String language(HttpExchange ex) {
return getHeader(ex, "Accept-Language") // return getHeader(ex, "Accept-Language") //
.map(s -> Arrays.stream(s.split(","))) .map(s -> Arrays.stream(s.split(",")))
.flatMap(Stream::findFirst) .flatMap(Stream::findFirst)
.orElse(DEFAULT_LANGUAGE); .orElse(DEFAULT_LANGUAGE);
} }
public static boolean notFound(HttpExchange ex) throws IOException { public static boolean notFound(HttpExchange ex) throws IOException {
LOG.log(ERROR, "not implemented"); LOG.log(ERROR, "not implemented");
return sendEmptyResponse(HTTP_NOT_FOUND, ex); return sendEmptyResponse(HTTP_NOT_FOUND, ex);
} }
public Map<String, String> queryParam(HttpExchange ex) { public Map<String, String> queryParam(HttpExchange ex) {
return Arrays return Arrays
.stream(ex.getRequestURI().getQuery().split("&")) // .stream(ex.getRequestURI().getQuery().split("&")) //
.map(s -> s.split("=", 2)) .map(s -> s.split("=", 2))
.collect(Collectors.toMap(arr -> arr[0], arr -> arr[1])); .collect(Collectors.toMap(arr -> arr[0], arr -> arr[1]));
} }
public static boolean sendEmptyResponse(int statusCode, HttpExchange ex) throws IOException { public static boolean sendEmptyResponse(int statusCode, HttpExchange ex) throws IOException {
ex.sendResponseHeaders(statusCode, 0); ex.sendResponseHeaders(statusCode, 0);
return false; return false;
} }
public static boolean sendRedirect(HttpExchange ex, String url) throws IOException { public static boolean sendRedirect(HttpExchange ex, String url) throws IOException {
ex.getResponseHeaders().add("Location", url); ex.getResponseHeaders().add("Location", url);
return sendEmptyResponse(HTTP_MOVED_TEMP, ex); return sendEmptyResponse(HTTP_MOVED_TEMP, ex);
} }
public static boolean sendContent(HttpExchange ex, int status, byte[] bytes) throws IOException { public static boolean sendContent(HttpExchange ex, int status, byte[] bytes) throws IOException {
LOG.log(DEBUG, "sending {0} response…", status); LOG.log(DEBUG, "sending {0} response…", status);
ex.sendResponseHeaders(status, bytes.length); ex.sendResponseHeaders(status, bytes.length);
ex.getResponseBody().write(bytes); ex.getResponseBody().write(bytes);
return true; return true;
} }
public static boolean sendContent(HttpExchange ex, int status, Object o) throws IOException { public static boolean sendContent(HttpExchange ex, int status, Object o) throws IOException {
if (o instanceof List<?> list) o = new JSONArray(list); if (o instanceof List<?> list) o = new JSONArray(list);
if (o instanceof Map<?, ?> map) o = new JSONObject(map); if (o instanceof Map<?, ?> map) o = new JSONObject(map);
if (o instanceof Error<?> error) o = error.json(); if (o instanceof Error<?> error) o = error.json();
return sendContent(ex, status, o.toString().getBytes(UTF_8)); return sendContent(ex, status, o.toString().getBytes(UTF_8));
} }
public static boolean sendContent(HttpExchange ex, byte[] bytes) throws IOException { public static boolean sendContent(HttpExchange ex, byte[] bytes) throws IOException {
return sendContent(ex, HTTP_OK, bytes); return sendContent(ex, HTTP_OK, bytes);
} }
public static boolean sendContent(HttpExchange ex, Object o) throws IOException { public static boolean sendContent(HttpExchange ex, Object o) throws IOException {
return sendContent(ex, HTTP_OK, o); return sendContent(ex, HTTP_OK, o);
} }
public static boolean serverError(HttpExchange ex, Object o) throws IOException { public static boolean serverError(HttpExchange ex, Object o) throws IOException {
sendContent(ex, HTTP_INTERNAL_ERROR, o); sendContent(ex, HTTP_INTERNAL_ERROR, o);
return false; return false;
} }
public static String url(HttpExchange ex) { public static String url(HttpExchange ex) {
return hostname(ex) + ex.getRequestURI(); return hostname(ex) + ex.getRequestURI();
}
} }
}

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

@ -106,7 +106,7 @@ public class Application {
private static AuthorizationService setupAuthService(Configuration config, Path defaultFile, FileStoreProvider fileStoreProvider) throws SQLException { private static AuthorizationService setupAuthService(Configuration config, Path defaultFile, FileStoreProvider fileStoreProvider) throws SQLException {
var authServiceLocation = new File(config.getOrDefault("auth_store", defaultFile)); var authServiceLocation = new File(config.getOrDefault("auth_store", defaultFile));
return switch (extension(authServiceLocation)){ return switch (extension(authServiceLocation)) {
case "db", "sqlite", "sqlite3" -> new SqliteAuthService(connectionProvider.get(authServiceLocation)); case "db", "sqlite", "sqlite3" -> new SqliteAuthService(connectionProvider.get(authServiceLocation));
default -> fileStoreProvider.get(authServiceLocation); default -> fileStoreProvider.get(authServiceLocation);
}; };
@ -114,17 +114,17 @@ public class Application {
private static SessionService setupSessionService(Configuration config, Path defaultFile, FileStoreProvider fileStoreProvider) throws SQLException { private static SessionService setupSessionService(Configuration config, Path defaultFile, FileStoreProvider fileStoreProvider) throws SQLException {
var sessionStore = new File(config.getOrDefault("session_storage", defaultFile)); var sessionStore = new File(config.getOrDefault("session_storage", defaultFile));
return switch (extension(sessionStore)){ return switch (extension(sessionStore)) {
case "db", "sqlite", "sqlite3" -> new SqliteSessionService(connectionProvider.get(sessionStore)); case "db", "sqlite", "sqlite3" -> new SqliteSessionService(connectionProvider.get(sessionStore));
default -> fileStoreProvider.get(sessionStore); default -> fileStoreProvider.get(sessionStore);
}; };
} }
private static MailConfig setupMailConfig(Configuration config, Path defaultFile, FileStoreProvider fileStoreProvider) throws SQLException { private static MailConfig setupMailConfig(Configuration config, Path defaultFile, FileStoreProvider fileStoreProvider) throws SQLException {
var mailConfigLocation = new File(config.getOrDefault("mail_config_storage", defaultFile)); var mailConfigLocation = new File(config.getOrDefault("mail_config_storage", defaultFile));
var mailConfig = switch (extension(mailConfigLocation)){ var mailConfig = switch (extension(mailConfigLocation)) {
case "db", "sqlite", "sqlite3" -> new SqliteMailConfig(connectionProvider.get(mailConfigLocation)); case "db", "sqlite", "sqlite3" -> new SqliteMailConfig(connectionProvider.get(mailConfigLocation));
default -> fileStoreProvider.get(mailConfigLocation); default -> fileStoreProvider.get(mailConfigLocation);
}; };
Optional<String> encryptionKey = config.get(ENCRYPTION_KEY); Optional<String> encryptionKey = config.get(ENCRYPTION_KEY);
@ -137,10 +137,10 @@ public class Application {
} }
private static UserService setupUserService(Configuration config, Path defaultFile, FileStoreProvider fileStoreProvider, UuidHasher passHasher) throws SQLException { private static UserService setupUserService(Configuration config, Path defaultFile, FileStoreProvider fileStoreProvider, UuidHasher passHasher) throws SQLException {
var userStorageLocation = new File(config.getOrDefault("user_storage", defaultFile)); var userStorageLocation = new File(config.getOrDefault("user_storage", defaultFile));
var userService = switch (extension(userStorageLocation).toLowerCase()){ var userService = switch (extension(userStorageLocation).toLowerCase()) {
case "db", "sqlite", "sqlite3" -> new SqliteUserService(connectionProvider.get(userStorageLocation),passHasher); case "db", "sqlite", "sqlite3" -> new SqliteUserService(connectionProvider.get(userStorageLocation), passHasher);
default -> fileStoreProvider.get(userStorageLocation); default -> fileStoreProvider.get(userStorageLocation);
}; };
Optional<String> encryptionKey = config.get(ENCRYPTION_KEY); Optional<String> encryptionKey = config.get(ENCRYPTION_KEY);
@ -154,7 +154,7 @@ public class Application {
private static KeyStorage setupKeyStore(Configuration config, Path defaultConfigDir) throws SQLException { private static KeyStorage setupKeyStore(Configuration config, Path defaultConfigDir) throws SQLException {
var keyStorageLocation = new File(config.getOrDefault("key_storage", defaultConfigDir.resolve("keys"))); var keyStorageLocation = new File(config.getOrDefault("key_storage", defaultConfigDir.resolve("keys")));
KeyStorage keyStore = null; KeyStorage keyStore = null;
if ((keyStorageLocation.exists() && keyStorageLocation.isDirectory()) || !keyStorageLocation.getName().contains(".")) { if ((keyStorageLocation.exists() && keyStorageLocation.isDirectory()) || !keyStorageLocation.getName().contains(".")) {
keyStore = new PlaintextKeyStore(keyStorageLocation.toPath()); keyStore = new PlaintextKeyStore(keyStorageLocation.toPath());
} else { // SQLite } else { // SQLite
@ -183,18 +183,18 @@ public class Application {
var token = tokens.remove(0); var token = tokens.remove(0);
switch (token) { switch (token) {
case "--base": case "--base":
if (tokens.isEmpty()) throw new IllegalArgumentException("--base option requires second argument!"); if (tokens.isEmpty()) throw new IllegalArgumentException("--base option requires second argument!");
map.put(BASE_PATH, Path.of(tokens.remove(0))); map.put(BASE_PATH, Path.of(tokens.remove(0)));
break; break;
case "--config": case "--config":
if (tokens.isEmpty()) throw new IllegalArgumentException("--config option requires second argument!"); if (tokens.isEmpty()) throw new IllegalArgumentException("--config option requires second argument!");
map.put(CONFIG_PATH, Path.of(tokens.remove(0))); map.put(CONFIG_PATH, Path.of(tokens.remove(0)));
break; break;
default: default:
LOG.log(ERROR, "Unknown option: {0}", token); LOG.log(ERROR, "Unknown option: {0}", token);
}
} }
}
return map; return map;
} }
} }

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

@ -158,7 +158,7 @@ public class ClientController extends Controller {
.stream() .stream()
.map(clients::getClient) .map(clients::getClient)
.flatMap(Optional::stream) .flatMap(Optional::stream)
.sorted(Comparator.comparing(Client::name)) .sorted(Comparator.comparing(Client::name, String.CASE_INSENSITIVE_ORDER))
.map(Client::safeMap) .map(Client::safeMap)
.toList(); .toList();
return sendContent(ex, Map.of(AUTHORZED, authorizedClients, NAME, user.realName())); return sendContent(ex, Map.of(AUTHORZED, authorizedClients, NAME, user.realName()));

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

@ -70,114 +70,113 @@ public class SqliteMailConfig extends SqliteStore implements MailConfig {
try { try {
switch (currentVersion) { switch (currentVersion) {
case 0: case 0:
createStoreTables(); createStoreTables();
break; 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;
} }
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);
} }
conn.setAutoCommit(true);
}
@Override @Override
public String smtpHost() { public String smtpHost() {
return smtpHost; return smtpHost;
} }
@Override @Override
public MailConfig smtpHost(String newValue) { public MailConfig smtpHost(String newValue) {
smtpHost = newValue; smtpHost = newValue;
return this; return this;
} }
@Override @Override
public int smtpPort() { public int smtpPort() {
return smtpPort; return smtpPort;
} }
@Override @Override
public MailConfig smtpPort(int newValue) { public MailConfig smtpPort(int newValue) {
smtpPort = newValue; smtpPort = newValue;
return this; return this;
} }
@Override @Override
public String senderAddress() { public String senderAddress() {
return senderAddress; return senderAddress;
} }
@Override @Override
public MailConfig senderAddress(String newValue) { public MailConfig senderAddress(String newValue) {
senderAddress = newValue; senderAddress = newValue;
return this; return this;
} }
@Override @Override
public String senderPassword() { public String senderPassword() {
return password; return password;
} }
@Override @Override
public MailConfig senderPassword(String newValue) { public MailConfig senderPassword(String newValue) {
password = newValue; password = newValue;
return this; return this;
} }
@Override @Override
public boolean startTls() { public boolean startTls() {
return startTls; return startTls;
} }
@Override @Override
public boolean smtpAuth() { public boolean smtpAuth() {
return smtpAuth; return smtpAuth;
} }
@Override @Override
public MailConfig startTls(boolean newValue) { public MailConfig startTls(boolean newValue) {
startTls = newValue; startTls = newValue;
return this; return this;
} }
@Override @Override
public MailConfig smtpAuth(boolean newValue) { public MailConfig smtpAuth(boolean newValue) {
smtpAuth = newValue; smtpAuth = newValue;
return this; return this;
} }
@Override @Override
public Authenticator authenticator() { public Authenticator authenticator() {
if (auth == null) { if (auth == null) {
auth = new Authenticator() { auth = new Authenticator() {
// override the getPasswordAuthentication method // override the getPasswordAuthentication method
protected PasswordAuthentication getPasswordAuthentication() { protected PasswordAuthentication getPasswordAuthentication() {
return new PasswordAuthentication(senderAddress(), senderPassword()); return new PasswordAuthentication(senderAddress(), senderPassword());
} }
}; };
}
return auth;
} }
return auth;
}
@Override @Override
public MailConfig save() { public MailConfig save() {
try { try {
var stmt = conn.prepareStatement(SAVE_MAILCONFIG); var stmt = conn.prepareStatement(SAVE_MAILCONFIG);
for (var entry : map().entrySet()) { for (var entry : map().entrySet()) {
stmt.setString(1, entry.getKey()); stmt.setString(1, entry.getKey());
stmt.setObject(2, entry.getValue()); stmt.setObject(2, entry.getValue());
stmt.setObject(3, entry.getValue()); stmt.setObject(3, entry.getValue());
stmt.execute(); stmt.execute();
}
return this;
} catch (SQLException e) {
throw new RuntimeException(e);
} }
return this;
} catch (SQLException e) {
throw new RuntimeException(e);
} }
} }
}

4
de.srsoftware.oidc.web/src/main/resources/de/reset.html

@ -42,6 +42,10 @@
<th>Error</th> <th>Error</th>
<td class="warning">Zugriffs-Token gefunden, ist aber ungültig!</td> <td class="warning">Zugriffs-Token gefunden, ist aber ungültig!</td>
</tr> </tr>
<tr id="other_error" style="display: none">
<th>Error</th>
<td class="warning" id="other_error_text">Unbekannter Fehler!</td>
</tr>
<tr> <tr>
<td></td> <td></td>
<td><button id="passBtn" type="button" onClick="updatePass()">Aktualisieren</button></td> <td><button id="passBtn" type="button" onClick="updatePass()">Aktualisieren</button></td>

5
de.srsoftware.oidc.web/src/main/resources/en/reset.html

@ -42,6 +42,11 @@
<th>Error</th> <th>Error</th>
<td class="warning">I received an access token, but it is invalid!</td> <td class="warning">I received an access token, but it is invalid!</td>
</tr> </tr>
<tr id="other_error" style="display: none">
<th>Error</th>
<td class="warning" id="other_error_text">Unknown error!</td>
</tr>
<tr> <tr>
<td></td> <td></td>
<td><button id="passBtn" type="button" onClick="updatePass()">Update</button></td> <td><button id="passBtn" type="button" onClick="updatePass()">Update</button></td>

14
de.srsoftware.oidc.web/src/main/resources/en/scripts/index.js

@ -11,12 +11,18 @@ function handleDash(response){
var clients = data.authorized; var clients = data.authorized;
var content = document.getElementById('content'); var content = document.getElementById('content');
var any = false; var any = false;
var lastLetter = null;
for (let id in clients){ for (let id in clients){
var client = clients[id]; var client = clients[id];
if (client.landing_page){ if (client.landing_page){
var div = document.createElement("div"); var initialLetter = client.name.charAt(0).toUpperCase();
div.innerHTML = `<button onclick="window.location.href='${client.landing_page}';">${client.name}</button>`; if (initialLetter != lastLetter) {
content.append(div); if (lastLetter) content.append(document.createElement("br"));
lastLetter = initialLetter;
}
var span = document.createElement("span");
span.innerHTML = `<button onclick="window.location.href='${client.landing_page}';">${client.name}</button>`;
content.append(span);
any = true; any = true;
} }
} }

12
de.srsoftware.oidc.web/src/main/resources/en/scripts/reset.js

@ -10,10 +10,14 @@ function handlePasswordResponse(response){
} else { } else {
setText('passBtn', 'Update failed!'); setText('passBtn', 'Update failed!');
response.text().then(text => { response.text().then(text => {
if (text == 'invalid token') show('invalid_token'); if (text == 'invalid token') show('invalid_token'); else
if (text == 'token missing') show('missing_token'); if (text == 'token missing') show('missing_token'); else
if (text == 'password mismatch') show('password_mismatch'); if (text == 'password mismatch') show('password_mismatch'); else
if (text == 'weak password') show('weak_password'); if (text == 'weak password') show('weak_password'); else {
setText('other_error_text',text);
show('other_error');
}
}); });
} }
enable('passBtn'); enable('passBtn');

1
settings.gradle

@ -9,4 +9,5 @@ include 'de.srsoftware.oidc.datastore.encrypted'
include 'de.srsoftware.oidc.datastore.file' include 'de.srsoftware.oidc.datastore.file'
include 'de.srsoftware.oidc.web' include 'de.srsoftware.oidc.web'
include 'de.srsoftware.utils' include 'de.srsoftware.utils'
include 'de.srsoftware.slf4j2syslog'

Loading…
Cancel
Save