Browse Source

divided Backend into several controllers

Signed-off-by: Stephan Richter <s.richter@srsoftware.de>
sqlite
Stephan Richter 4 months ago
parent
commit
0c1baee8e0
  1. 14
      de.srsoftware.cookies/src/main/java/de/srsoftware/cookies/Cookie.java
  2. 10
      de.srsoftware.cookies/src/main/java/de/srsoftware/cookies/SessionToken.java
  3. 4
      de.srsoftware.logging/src/main/java/de/srsoftware/logging/ColorLogger.java
  4. 10
      de.srsoftware.oidc.api/src/main/java/de/srsoftware/oidc/api/PathHandler.java
  5. 28
      de.srsoftware.oidc.app/src/main/java/de/srsoftware/oidc/app/Application.java
  6. 244
      de.srsoftware.oidc.backend/src/main/java/de/srsoftware/oidc/backend/Backend.java
  7. 126
      de.srsoftware.oidc.backend/src/main/java/de/srsoftware/oidc/backend/ClientController.java
  8. 21
      de.srsoftware.oidc.backend/src/main/java/de/srsoftware/oidc/backend/Controller.java
  9. 41
      de.srsoftware.oidc.backend/src/main/java/de/srsoftware/oidc/backend/TokenController.java
  10. 114
      de.srsoftware.oidc.backend/src/main/java/de/srsoftware/oidc/backend/UserController.java
  11. 27
      de.srsoftware.oidc.backend/src/main/java/de/srsoftware/oidc/backend/WellKnownController.java
  12. 2
      de.srsoftware.oidc.web/src/main/resources/en/authorization.js
  13. 4
      de.srsoftware.oidc.web/src/main/resources/en/clients.js
  14. 2
      de.srsoftware.oidc.web/src/main/resources/en/common.js
  15. 2
      de.srsoftware.oidc.web/src/main/resources/en/login.js
  16. 2
      de.srsoftware.oidc.web/src/main/resources/en/logout.js
  17. 2
      de.srsoftware.oidc.web/src/main/resources/en/newclient.js
  18. 4
      de.srsoftware.oidc.web/src/main/resources/en/settings.js
  19. 2
      de.srsoftware.oidc.web/src/main/resources/en/user.js

14
de.srsoftware.cookies/src/main/java/de/srsoftware/cookies/Cookie.java

@ -8,7 +8,11 @@ import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Optional; import java.util.Optional;
import static java.lang.System.Logger.Level.ERROR;
import static java.lang.System.Logger.Level.WARNING;
public abstract class Cookie implements Map.Entry<String, String> { public abstract class Cookie implements Map.Entry<String, String> {
static final System.Logger LOG = System.getLogger(SessionToken.class.getSimpleName());
private final String key; private final String key;
private String value = null; private String value = null;
@ -18,6 +22,7 @@ public abstract class Cookie implements Map.Entry<String, String> {
} }
public <T extends Cookie> T addTo(Headers headers) { public <T extends Cookie> T addTo(Headers headers) {
LOG.log(ERROR,"sending cookie {0}={1}",key,value);
headers.add("Set-Cookie", "%s=%s".formatted(key, value)); headers.add("Set-Cookie", "%s=%s".formatted(key, value));
return (T)this; return (T)this;
} }
@ -37,7 +42,14 @@ public abstract class Cookie implements Map.Entry<String, String> {
} }
protected static List<String> of(HttpExchange ex) { protected static List<String> of(HttpExchange ex) {
return Optional.ofNullable(ex.getRequestHeaders().get("Cookie")).stream().flatMap(List::stream).flatMap(s -> Arrays.stream(s.split(";"))).map(String::trim).toList(); return Optional.ofNullable(ex.getRequestHeaders()
.get("Cookie"))
.stream()
.flatMap(List::stream)
.flatMap(s -> Arrays.stream(s.split(";")))
.map(String::trim)
.peek(cookie -> LOG.log(WARNING,"received cookie {0}",cookie))
.toList();
} }
@Override @Override

10
de.srsoftware.cookies/src/main/java/de/srsoftware/cookies/SessionToken.java

@ -4,17 +4,23 @@ package de.srsoftware.cookies;
import com.sun.net.httpserver.HttpExchange; import com.sun.net.httpserver.HttpExchange;
import java.util.Optional; import java.util.Optional;
import java.util.logging.Logger;
import static java.lang.System.Logger.Level.DEBUG;
import static java.lang.System.Logger.Level.WARNING;
public class SessionToken extends Cookie { public class SessionToken extends Cookie {
private final String sessionId; private final String sessionId;
public SessionToken(String sessionId) { public SessionToken(String sessionId) {
super("sessionToken", sessionId); super("sessionToken", sessionId+"; Path=/api");
this.sessionId = sessionId; this.sessionId = sessionId;
} }
public static Optional<SessionToken> from(HttpExchange ex) { public static Optional<SessionToken> from(HttpExchange ex) {
return Cookie.of(ex).stream().filter(cookie -> cookie.startsWith("sessionToken=")).map(cookie -> cookie.split("=", 2)[1]).map(id -> new SessionToken(id)).findAny(); return Cookie.of(ex).stream().filter(cookie -> cookie.startsWith("sessionToken="))
.map(cookie -> cookie.split("=", 2)[1]).map(id -> new SessionToken(id)).findAny();
} }
public String sessionId() { public String sessionId() {

4
de.srsoftware.logging/src/main/java/de/srsoftware/logging/ColorLogger.java

@ -54,9 +54,9 @@ public class ColorLogger implements System.Logger {
private static String colorize(String message, int severity) { private static String colorize(String message, int severity) {
var color = severity >= ERROR.getSeverity() ? RED : severity >= WARNING.getSeverity() ? YELLOW : severity >= INFO.getSeverity() ? WHITE_BRIGHT : WHITE; var color = severity >= ERROR.getSeverity() ? RED : severity >= WARNING.getSeverity() ? YELLOW : severity >= INFO.getSeverity() ? WHITE_BRIGHT : WHITE;
var date = new Date(); var date = new Date();
var day =DATE.format(date); var day = DATE.format(date);
StringBuilder sb = new StringBuilder(); StringBuilder sb = new StringBuilder();
if (!day.equals(lastDate)){ if (!day.equals(lastDate)) {
lastDate = day; lastDate = day;
sb.append(WHITE).append(day).append("\n"); sb.append(WHITE).append(day).append("\n");
} }

10
de.srsoftware.oidc.api/src/main/java/de/srsoftware/oidc/api/PathHandler.java

@ -1,8 +1,7 @@
/* © SRSoftware 2024 */ /* © SRSoftware 2024 */
package de.srsoftware.oidc.api; package de.srsoftware.oidc.api;
import static java.lang.System.Logger.Level.DEBUG; import static java.lang.System.Logger.Level.*;
import static java.lang.System.Logger.Level.INFO;
import static java.net.HttpURLConnection.*; import static java.net.HttpURLConnection.*;
import static java.nio.charset.StandardCharsets.UTF_8; import static java.nio.charset.StandardCharsets.UTF_8;
@ -101,7 +100,7 @@ public abstract class PathHandler implements HttpHandler {
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);
return host == null ? null : "https://"+host; return host == null ? null : "https://" + host;
} }
public static JSONObject json(HttpExchange ex) throws IOException { public static JSONObject json(HttpExchange ex) throws IOException {
@ -112,6 +111,11 @@ public abstract class PathHandler implements HttpHandler {
return getHeader(ex, "Accept-Language").map(s -> Arrays.stream(s.split(","))).flatMap(Stream::findFirst); return getHeader(ex, "Accept-Language").map(s -> Arrays.stream(s.split(","))).flatMap(Stream::findFirst);
} }
public static boolean notFound(HttpExchange ex) throws IOException {
LOG.log(WARNING, "not implemented");
return sendEmptyResponse(HTTP_NOT_FOUND, ex);
}
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;

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

@ -9,7 +9,10 @@ import static java.lang.System.Logger.Level.ERROR;
import com.sun.net.httpserver.HttpServer; import com.sun.net.httpserver.HttpServer;
import de.srsoftware.logging.ColorLogger; import de.srsoftware.logging.ColorLogger;
import de.srsoftware.oidc.api.User; import de.srsoftware.oidc.api.User;
import de.srsoftware.oidc.backend.Backend; import de.srsoftware.oidc.backend.ClientController;
import de.srsoftware.oidc.backend.TokenController;
import de.srsoftware.oidc.backend.UserController;
import de.srsoftware.oidc.backend.WellKnownController;
import de.srsoftware.oidc.datastore.file.FileStore; import de.srsoftware.oidc.datastore.file.FileStore;
import de.srsoftware.oidc.datastore.file.UuidHasher; import de.srsoftware.oidc.datastore.file.UuidHasher;
import de.srsoftware.oidc.web.Forward; import de.srsoftware.oidc.web.Forward;
@ -21,16 +24,19 @@ import java.util.*;
import java.util.concurrent.Executors; import java.util.concurrent.Executors;
public class Application { public class Application {
public static final String BACKEND = "/api"; public static final String API_CLIENT = "/api/client";
private static final String FAVICON = "/favicon.ico"; private static final String API_TOKEN = "/api/token";
public static final String ROOT = "/"; public static final String API_USER = "/api/user";
public static final String STATIC_PATH = "/web";
private static final String WELL_KNOWN = "/.well-known";
public static final String FIRST_USER = "admin"; public static final String FIRST_USER = "admin";
public static final String FIRST_USER_PASS = "admin"; public static final String FIRST_USER_PASS = "admin";
public static final String FIRST_UUID = UUID.randomUUID().toString(); public static final String FIRST_UUID = UUID.randomUUID().toString();
public static final String INDEX = STATIC_PATH + "/index.html"; public static final String ROOT = "/";
public static final String STATIC_PATH = "/web";
private static final String BASE_PATH = "basePath"; 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 System.Logger LOG = new ColorLogger("Application").setLogLevel(DEBUG);
public static void main(String[] args) throws Exception { public static void main(String[] args) throws Exception {
@ -44,8 +50,12 @@ public class Application {
HttpServer server = HttpServer.create(new InetSocketAddress(8080), 0); HttpServer server = HttpServer.create(new InetSocketAddress(8080), 0);
new StaticPages(basePath).bindPath(STATIC_PATH, FAVICON).on(server); new StaticPages(basePath).bindPath(STATIC_PATH, FAVICON).on(server);
new Forward(INDEX).bindPath(ROOT).on(server); new Forward(INDEX).bindPath(ROOT).on(server);
new Backend(fileStore, fileStore, fileStore, fileStore).bindPath(BACKEND, WELL_KNOWN).on(server); new WellKnownController().bindPath(WELL_KNOWN).on(server);
server.setExecutor(Executors.newCachedThreadPool()); new UserController(fileStore, fileStore).bindPath(API_USER).on(server);
new TokenController().bindPath(API_TOKEN).on(server);
new ClientController(fileStore, fileStore, fileStore).bindPath(API_CLIENT).on(server);
//server.setExecutor(Executors.newCachedThreadPool());
server.setExecutor(Executors.newSingleThreadExecutor());
server.start(); server.start();
} }

244
de.srsoftware.oidc.backend/src/main/java/de/srsoftware/oidc/backend/Backend.java

@ -1,244 +0,0 @@
/* © SRSoftware 2024 */
package de.srsoftware.oidc.backend;
import static de.srsoftware.oidc.api.Constants.*;
import static de.srsoftware.oidc.api.Permission.MANAGE_CLIENTS;
import static de.srsoftware.oidc.api.User.*;
import static java.lang.System.Logger.Level.ERROR;
import static java.lang.System.Logger.Level.WARNING;
import static java.net.HttpURLConnection.*;
import com.sun.net.httpserver.HttpExchange;
import de.srsoftware.cookies.SessionToken;
import de.srsoftware.oidc.api.*;
import java.io.IOException;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;
import org.json.JSONObject;
public class Backend extends PathHandler {
private static final System.Logger LOG = System.getLogger(Backend.class.getSimpleName());
private final AuthorizationService authorizations;
private final SessionService sessions;
private final UserService users;
private final ClientService clients;
public Backend(AuthorizationService authorizationService, ClientService clientService, SessionService sessionService, UserService userService) {
authorizations = authorizationService;
clients = clientService;
sessions = sessionService;
users = userService;
}
private boolean addClient(HttpExchange ex, Session session) throws IOException {
if (!session.user().hasPermission(MANAGE_CLIENTS)) return badRequest(ex, "NOT ALLOWED");
var json = json(ex);
var redirects = new HashSet<String>();
for (Object o : json.getJSONArray(REDIRECT_URIS)) {
if (o instanceof String s) redirects.add(s);
}
var client = new Client(json.getString(CLIENT_ID), json.getString(NAME), json.getString(SECRET), redirects);
clients.add(client);
return sendContent(ex, client);
}
private boolean authorize(HttpExchange ex, Session session) throws IOException {
var user = session.user();
var json = json(ex);
var clientId = json.getString(CLIENT_ID);
var redirect = json.getString(REDIRECT_URI);
var optClient = clients.getClient(clientId);
if (optClient.isEmpty()) return badRequest(ex, Map.of(CAUSE, "unknown client", CLIENT_ID, clientId));
var client = optClient.get();
if (!client.redirectUris().contains(redirect)) return badRequest(ex, Map.of(CAUSE, "unknown redirect uri", REDIRECT_URI, redirect));
if (!authorizations.isAuthorized(client, session.user())) {
if (json.has(CONFIRMED) && json.getBoolean(CONFIRMED)) {
authorizations.authorize(client, user, null);
} else {
return sendContent(ex, Map.of(CONFIRMED, false, NAME, client.name()));
}
}
var state = json.getString(STATE);
var code = client.generateCode();
return sendContent(ex, Map.of(CONFIRMED, true, CODE, code, REDIRECT_URI, redirect, STATE, state));
}
private boolean clients(HttpExchange ex, Session session) throws IOException {
var user = session.user();
if (!user.hasPermission(MANAGE_CLIENTS)) return sendEmptyResponse(HTTP_FORBIDDEN, ex);
var json = new JSONObject();
clients.listClients().forEach(client -> json.put(client.id(), Map.of("name", client.name(), "redirect_uris", client.redirectUris())));
return sendContent(ex, json);
}
private boolean deleteClient(HttpExchange ex, Session session) throws IOException {
if (!session.user().hasPermission(MANAGE_CLIENTS)) return badRequest(ex, "NOT ALLOWED");
var json = json(ex);
var id = json.getString(CLIENT_ID);
clients.getClient(id).ifPresent(clients::remove);
return sendEmptyResponse(HTTP_OK, ex);
}
private boolean doLogin(HttpExchange ex) throws IOException {
var body = json(ex);
var username = body.has(USERNAME) ? body.getString(USERNAME) : null;
var password = body.has(PASSWORD) ? body.getString(PASSWORD) : null;
Optional<User> user = users.load(username, password);
if (user.isPresent()) return sendUserAndCookie(ex, sessions.createSession(user.get()));
return sendEmptyResponse(HTTP_UNAUTHORIZED, ex);
}
@Override
public boolean doDelete(String path, HttpExchange ex) throws IOException {
var optSession = getSession(ex);
if (optSession.isEmpty()) return sendEmptyResponse(HTTP_UNAUTHORIZED, ex);
// post-login paths
var session = optSession.get();
switch (path) {
case "/client":
return deleteClient(ex, session);
}
LOG.log(ERROR, "not implemented");
return sendEmptyResponse(HTTP_NOT_FOUND, ex);
}
@Override
public boolean doGet(String path, HttpExchange ex) throws IOException {
// pre-login paths
switch (path) {
case "/openid-configuration":
return openidConfig(ex);
}
var optSession = getSession(ex);
if (optSession.isEmpty()) return sendEmptyResponse(HTTP_UNAUTHORIZED, ex);
// post-login paths
var session = optSession.get();
switch (path) {
case "/logout":
return logout(ex, session);
}
LOG.log(WARNING, "not implemented");
return sendEmptyResponse(HTTP_NOT_FOUND, ex);
}
@Override
public boolean doPost(String path, HttpExchange ex) throws IOException {
// pre-login paths
switch (path) {
case "/login":
return doLogin(ex);
case "/token":
return provideToken(ex);
}
var optSession = getSession(ex);
if (optSession.isEmpty()) return sendEmptyResponse(HTTP_UNAUTHORIZED, ex);
// post-login paths
var session = optSession.get();
switch (path) {
case "/add/client":
return addClient(ex, session);
case "/authorize":
return authorize(ex, session);
case "/client":
return loadClient(ex, session);
case "/clients":
return clients(ex, session);
case "/update/password":
return updatePassword(ex, session);
case "/update/user":
return updateUser(ex, session);
case "/user":
return sendUserAndCookie(ex, session);
}
LOG.log(WARNING, "not implemented");
return sendEmptyResponse(HTTP_NOT_FOUND, ex);
}
private Optional<Session> getSession(HttpExchange ex) {
return SessionToken.from(ex).map(SessionToken::sessionId).flatMap(sessions::retrieve);
}
private boolean loadClient(HttpExchange ex, Session session) throws IOException {
if (!session.user().hasPermission(MANAGE_CLIENTS)) return sendEmptyResponse(HTTP_FORBIDDEN, ex);
var json = json(ex);
if (json.has(CLIENT_ID)) {
var clientID = json.getString(CLIENT_ID);
var client = clients.getClient(clientID).map(Client::map).map(JSONObject::new);
if (client.isPresent()) return sendContent(ex, client.get());
}
return sendEmptyResponse(HTTP_NOT_FOUND, ex);
}
private boolean logout(HttpExchange ex, Session session) throws IOException {
sessions.dropSession(session.id());
new SessionToken("").addTo(ex);
return sendEmptyResponse(HTTP_OK, ex);
}
private boolean provideToken(HttpExchange ex) throws IOException {
var map = deserialize(body(ex));
LOG.log(WARNING, "map: {0}", map);
LOG.log(ERROR, "{0}.provideToken(ex) not implemented!", getClass().getSimpleName());
var grantType = map.get(GRANT_TYPE);
if (!ATUH_CODE.equals(grantType)) sendContent(ex, HTTP_BAD_REQUEST, Map.of(ERROR, "unknown grant type", GRANT_TYPE, grantType));
return sendEmptyResponse(HTTP_NOT_FOUND, ex);
}
private Map<String, String> deserialize(String body) {
return Arrays.stream(body.split("&")).map(s -> s.split("=")).collect(Collectors.toMap(arr -> arr[0], arr -> arr[1]));
}
private boolean openidConfig(HttpExchange ex) throws IOException {
var host = hostname(ex);
return sendContent(ex, Map.of("token_endpoint", host + "/api/token", "authorization_endpoint", host + "/web/authorization.html", "userinfo_endpoint", host + "/api/userinfo", "jwks_uri", host + "/api/jwks"));
}
private boolean sendUserAndCookie(HttpExchange ex, Session session) throws IOException {
new SessionToken(session.id()).addTo(ex);
return sendContent(ex, session.user().map(false));
}
private boolean updatePassword(HttpExchange ex, Session session) throws IOException {
var user = session.user();
var json = json(ex);
var uuid = json.getString(UUID);
if (!uuid.equals(user.uuid())) {
return sendEmptyResponse(HTTP_FORBIDDEN, ex);
}
var oldPass = json.getString("oldpass");
if (!users.passwordMatches(oldPass, user.hashedPassword())) return badRequest(ex, "wrong password");
var newpass = json.getJSONArray("newpass");
var newPass1 = newpass.getString(0);
if (!newPass1.equals(newpass.getString(1))) {
return badRequest(ex, "password mismatch");
}
users.updatePassword(user, newPass1);
return sendContent(ex, user.map(false));
}
private boolean updateUser(HttpExchange ex, Session session) throws IOException {
var user = session.user();
var json = json(ex);
var uuid = json.getString(UUID);
if (!uuid.equals(user.uuid())) {
return sendEmptyResponse(HTTP_FORBIDDEN, ex);
}
user.username(json.getString(USERNAME));
user.email(json.getString(EMAIL));
users.save(user);
return sendContent(ex, user.map(false));
}
}

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

@ -0,0 +1,126 @@
/* © SRSoftware 2024 */
package de.srsoftware.oidc.backend;
import static de.srsoftware.oidc.api.Constants.*;
import static de.srsoftware.oidc.api.Permission.MANAGE_CLIENTS;
import static java.lang.System.Logger.Level.ERROR;
import static java.net.HttpURLConnection.*;
import com.sun.net.httpserver.HttpExchange;
import de.srsoftware.oidc.api.*;
import java.io.IOException;
import java.util.HashSet;
import java.util.Map;
import org.json.JSONObject;
public class ClientController extends Controller {
private static final System.Logger LOG = System.getLogger(ClientController.class.getSimpleName());
private final AuthorizationService authorizations;
private final ClientService clients;
public ClientController(AuthorizationService authorizationService, ClientService clientService, SessionService sessionService) {
super(sessionService);
authorizations = authorizationService;
clients = clientService;
}
private boolean add(HttpExchange ex, Session session) throws IOException {
if (!session.user().hasPermission(MANAGE_CLIENTS)) return badRequest(ex, "NOT ALLOWED");
var json = json(ex);
var redirects = new HashSet<String>();
for (Object o : json.getJSONArray(REDIRECT_URIS)) {
if (o instanceof String s) redirects.add(s);
}
var client = new Client(json.getString(CLIENT_ID), json.getString(NAME), json.getString(SECRET), redirects);
clients.add(client);
return sendContent(ex, client);
}
private boolean authorize(HttpExchange ex, Session session) throws IOException {
var user = session.user();
var json = json(ex);
var clientId = json.getString(CLIENT_ID);
var redirect = json.getString(REDIRECT_URI);
var optClient = clients.getClient(clientId);
if (optClient.isEmpty()) return badRequest(ex, Map.of(CAUSE, "unknown client", CLIENT_ID, clientId));
var client = optClient.get();
if (!client.redirectUris().contains(redirect)) return badRequest(ex, Map.of(CAUSE, "unknown redirect uri", REDIRECT_URI, redirect));
if (!authorizations.isAuthorized(client, session.user())) {
if (json.has(CONFIRMED) && json.getBoolean(CONFIRMED)) {
authorizations.authorize(client, user, null);
} else {
return sendContent(ex, Map.of(CONFIRMED, false, NAME, client.name()));
}
}
var state = json.getString(STATE);
var code = client.generateCode();
return sendContent(ex, Map.of(CONFIRMED, true, CODE, code, REDIRECT_URI, redirect, STATE, state));
}
private boolean deleteClient(HttpExchange ex, Session session) throws IOException {
if (!session.user().hasPermission(MANAGE_CLIENTS)) return badRequest(ex, "NOT ALLOWED");
var json = json(ex);
var id = json.getString(CLIENT_ID);
clients.getClient(id).ifPresent(clients::remove);
return sendEmptyResponse(HTTP_OK, ex);
}
@Override
public boolean doDelete(String path, HttpExchange ex) throws IOException {
var optSession = getSession(ex);
if (optSession.isEmpty()) return sendEmptyResponse(HTTP_UNAUTHORIZED, ex);
// post-login paths
var session = optSession.get();
switch (path) {
case "/":
return deleteClient(ex, session);
}
LOG.log(ERROR, "not implemented");
return sendEmptyResponse(HTTP_NOT_FOUND, ex);
}
@Override
public boolean doPost(String path, HttpExchange ex) throws IOException {
var optSession = getSession(ex);
if (optSession.isEmpty()) return sendEmptyResponse(HTTP_UNAUTHORIZED, ex);
// post-login paths
var session = optSession.get();
switch (path) {
case "/":
return load(ex, session);
case "/add":
return add(ex, session);
case "/authorize":
return authorize(ex, session);
case "/list":
return list(ex, session);
}
return notFound(ex);
}
private boolean list(HttpExchange ex, Session session) throws IOException {
var user = session.user();
if (!user.hasPermission(MANAGE_CLIENTS)) return sendEmptyResponse(HTTP_FORBIDDEN, ex);
var json = new JSONObject();
clients.listClients().forEach(client -> json.put(client.id(), Map.of("name", client.name(), "redirect_uris", client.redirectUris())));
return sendContent(ex, json);
}
private boolean load(HttpExchange ex, Session session) throws IOException {
if (!session.user().hasPermission(MANAGE_CLIENTS)) return sendEmptyResponse(HTTP_FORBIDDEN, ex);
var json = json(ex);
if (json.has(CLIENT_ID)) {
var clientID = json.getString(CLIENT_ID);
var client = clients.getClient(clientID).map(Client::map).map(JSONObject::new);
if (client.isPresent()) return sendContent(ex, client.get());
}
return sendEmptyResponse(HTTP_NOT_FOUND, ex);
}
}

21
de.srsoftware.oidc.backend/src/main/java/de/srsoftware/oidc/backend/Controller.java

@ -0,0 +1,21 @@
/* © SRSoftware 2024 */
package de.srsoftware.oidc.backend;
import com.sun.net.httpserver.HttpExchange;
import de.srsoftware.cookies.SessionToken;
import de.srsoftware.oidc.api.PathHandler;
import de.srsoftware.oidc.api.Session;
import de.srsoftware.oidc.api.SessionService;
import java.util.Optional;
public abstract class Controller extends PathHandler {
protected final SessionService sessions;
Controller(SessionService sessionService) {
sessions = sessionService;
}
protected Optional<Session> getSession(HttpExchange ex) {
return SessionToken.from(ex).map(SessionToken::sessionId).flatMap(sessions::retrieve);
}
}

41
de.srsoftware.oidc.backend/src/main/java/de/srsoftware/oidc/backend/TokenController.java

@ -0,0 +1,41 @@
/* © SRSoftware 2024 */
package de.srsoftware.oidc.backend;
import static de.srsoftware.oidc.api.Constants.ATUH_CODE;
import static de.srsoftware.oidc.api.Constants.GRANT_TYPE;
import static java.lang.System.Logger.Level.ERROR;
import static java.lang.System.Logger.Level.WARNING;
import static java.net.HttpURLConnection.HTTP_BAD_REQUEST;
import static java.net.HttpURLConnection.HTTP_NOT_FOUND;
import com.sun.net.httpserver.HttpExchange;
import de.srsoftware.oidc.api.PathHandler;
import java.io.IOException;
import java.util.Arrays;
import java.util.Map;
import java.util.stream.Collectors;
public class TokenController extends PathHandler {
private Map<String, String> deserialize(String body) {
return Arrays.stream(body.split("&")).map(s -> s.split("=")).collect(Collectors.toMap(arr -> arr[0], arr -> arr[1]));
}
@Override
public boolean doPost(String path, HttpExchange ex) throws IOException {
// pre-login paths
switch (path) {
case "/":
return provideToken(ex);
}
return notFound(ex);
}
private boolean provideToken(HttpExchange ex) throws IOException {
var map = deserialize(body(ex));
LOG.log(WARNING, "post data: {0}", map);
LOG.log(ERROR, "{0}.provideToken(ex) not implemented!", getClass().getSimpleName());
var grantType = map.get(GRANT_TYPE);
if (!ATUH_CODE.equals(grantType)) sendContent(ex, HTTP_BAD_REQUEST, Map.of(ERROR, "unknown grant type", GRANT_TYPE, grantType));
return sendEmptyResponse(HTTP_NOT_FOUND, ex);
}
}

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

@ -0,0 +1,114 @@
/* © SRSoftware 2024 */
package de.srsoftware.oidc.backend;
import static de.srsoftware.oidc.api.User.*;
import static java.lang.System.Logger.Level.WARNING;
import static java.net.HttpURLConnection.*;
import com.sun.net.httpserver.HttpExchange;
import de.srsoftware.cookies.SessionToken;
import de.srsoftware.oidc.api.*;
import java.io.IOException;
import java.util.Optional;
public class UserController extends Controller {
private final UserService users;
public UserController(SessionService sessionService, UserService userService) {
super(sessionService);
users = userService;
}
@Override
public boolean doGet(String path, HttpExchange ex) throws IOException {
var optSession = getSession(ex);
if (optSession.isEmpty()) return sendEmptyResponse(HTTP_UNAUTHORIZED, ex);
// post-login paths
var session = optSession.get();
switch (path) {
case "/logout":
return logout(ex, session);
}
LOG.log(WARNING, "not implemented");
return sendEmptyResponse(HTTP_NOT_FOUND, ex);
}
@Override
public boolean doPost(String path, HttpExchange ex) throws IOException {
switch (path) {
case "/login":
return login(ex);
}
var optSession = getSession(ex);
if (optSession.isEmpty()) return sendEmptyResponse(HTTP_UNAUTHORIZED, ex);
// post-login paths
var session = optSession.get();
switch (path) {
case "/":
return sendUserAndCookie(ex, session);
case "/password":
return updatePassword(ex, session);
case "/update":
return updateUser(ex, session);
}
return notFound(ex);
}
private boolean login(HttpExchange ex) throws IOException {
var body = json(ex);
var username = body.has(USERNAME) ? body.getString(USERNAME) : null;
var password = body.has(PASSWORD) ? body.getString(PASSWORD) : null;
Optional<User> user = users.load(username, password);
if (user.isPresent()) return sendUserAndCookie(ex, sessions.createSession(user.get()));
return sendEmptyResponse(HTTP_UNAUTHORIZED, ex);
}
private boolean logout(HttpExchange ex, Session session) throws IOException {
sessions.dropSession(session.id());
new SessionToken("").addTo(ex);
return sendEmptyResponse(HTTP_OK, ex);
}
private boolean sendUserAndCookie(HttpExchange ex, Session session) throws IOException {
new SessionToken(session.id()).addTo(ex);
return sendContent(ex, session.user().map(false));
}
private boolean updatePassword(HttpExchange ex, Session session) throws IOException {
var user = session.user();
var json = json(ex);
var uuid = json.getString(UUID);
if (!uuid.equals(user.uuid())) {
return sendEmptyResponse(HTTP_FORBIDDEN, ex);
}
var oldPass = json.getString("oldpass");
if (!users.passwordMatches(oldPass, user.hashedPassword())) return badRequest(ex, "wrong password");
var newpass = json.getJSONArray("newpass");
var newPass1 = newpass.getString(0);
if (!newPass1.equals(newpass.getString(1))) {
return badRequest(ex, "password mismatch");
}
users.updatePassword(user, newPass1);
return sendContent(ex, user.map(false));
}
private boolean updateUser(HttpExchange ex, Session session) throws IOException {
var user = session.user();
var json = json(ex);
var uuid = json.getString(UUID);
if (!uuid.equals(user.uuid())) {
return sendEmptyResponse(HTTP_FORBIDDEN, ex);
}
user.username(json.getString(USERNAME));
user.email(json.getString(EMAIL));
users.save(user);
return sendContent(ex, user.map(false));
}
}

27
de.srsoftware.oidc.backend/src/main/java/de/srsoftware/oidc/backend/WellKnownController.java

@ -0,0 +1,27 @@
/* © SRSoftware 2024 */
package de.srsoftware.oidc.backend;
import static java.lang.System.Logger.Level.WARNING;
import static java.net.HttpURLConnection.HTTP_NOT_FOUND;
import com.sun.net.httpserver.HttpExchange;
import de.srsoftware.oidc.api.PathHandler;
import java.io.IOException;
import java.util.Map;
public class WellKnownController extends PathHandler {
@Override
public boolean doGet(String path, HttpExchange ex) throws IOException {
switch (path) {
case "/openid-configuration":
return openidConfig(ex);
}
LOG.log(WARNING, "not implemented");
return sendEmptyResponse(HTTP_NOT_FOUND, ex);
}
private boolean openidConfig(HttpExchange ex) throws IOException {
var host = hostname(ex);
return sendContent(ex, Map.of("token_endpoint", host + "/api/token", "authorization_endpoint", host + "/web/authorization.html", "userinfo_endpoint", host + "/api/userinfo", "jwks_uri", host + "/api/jwks"));
}
}

2
de.srsoftware.oidc.web/src/main/resources/en/authorization.js

@ -34,7 +34,7 @@ function denyAutorization(){
} }
function backendAutorization(){ function backendAutorization(){
fetch(api+"/authorize",{ fetch(client_controller+"/authorize",{
method: 'POST', method: 'POST',
body: JSON.stringify(json), body: JSON.stringify(json),
headers: { headers: {

4
de.srsoftware.oidc.web/src/main/resources/en/clients.js

@ -20,7 +20,7 @@ function handleRemove(response){
function remove(clientId){ function remove(clientId){
var message = document.getElementById('message').innerHTML; var message = document.getElementById('message').innerHTML;
if (confirm(message.replace("{}",clientId))) { if (confirm(message.replace("{}",clientId))) {
fetch(api+"/client",{ fetch(client_controller+"/delete",{
method: 'DELETE', method: 'DELETE',
body : JSON.stringify({ client_id : clientId }) body : JSON.stringify({ client_id : clientId })
}).then(handleRemove); }).then(handleRemove);
@ -31,4 +31,4 @@ function edit(clientId){
redirect("edit_client.html?id="+clientId); redirect("edit_client.html?id="+clientId);
} }
fetch(api+"/clients",{method:'POST'}).then(handleClients); fetch(client_controller+"/list",{method:'POST'}).then(handleClients);

2
de.srsoftware.oidc.web/src/main/resources/en/common.js

@ -1,4 +1,6 @@
var api = "/api"; var api = "/api";
var client_controller = "/api/client"
var user_controller = "/api/user"
var web = "/web"; var web = "/web";
const UNAUTHORIZED = 401; const UNAUTHORIZED = 401;

2
de.srsoftware.oidc.web/src/main/resources/en/login.js

@ -17,7 +17,7 @@ function doRedirect(){
function tryLogin(){ function tryLogin(){
var username = getValue('username'); var username = getValue('username');
var password = getValue('password'); var password = getValue('password');
fetch(api+"/login",{ fetch(user_controller+"/login",{
method: 'POST', method: 'POST',
headers: { headers: {
Accept: 'application/json', Accept: 'application/json',

2
de.srsoftware.oidc.web/src/main/resources/en/logout.js

@ -2,4 +2,4 @@ function handleLogout(response){
if (response.ok) document.body.innerHTML += 'success'; if (response.ok) document.body.innerHTML += 'success';
redirect('index.html') redirect('index.html')
} }
fetch(api+"/logout").then(handleLogout) fetch(user_controller+"/logout").then(handleLogout)

2
de.srsoftware.oidc.web/src/main/resources/en/newclient.js

@ -6,7 +6,7 @@ function addClient(){
secret : getValue('client-secret'), secret : getValue('client-secret'),
redirect_uris : getValue('redirect-urls').split("\n") redirect_uris : getValue('redirect-urls').split("\n")
}; };
fetch(api+'/add/client',{ fetch(client_controller+'/add',{
method : 'POST', method : 'POST',
headers : { headers : {
'Content-Type': 'application/json' 'Content-Type': 'application/json'

4
de.srsoftware.oidc.web/src/main/resources/en/settings.js

@ -31,7 +31,7 @@ function update(){
email : getValue('email'), email : getValue('email'),
uuid : getValue('uuid') uuid : getValue('uuid')
} }
fetch(api+'/update/user',{ fetch(user_controller+'/update',{
method : 'POST', method : 'POST',
headers : { headers : {
'Content-Type': 'application/json' 'Content-Type': 'application/json'
@ -68,7 +68,7 @@ function updatePass(){
newpass : [getValue('newpass1'),getValue('newpass2')], newpass : [getValue('newpass1'),getValue('newpass2')],
uuid : getValue('uuid') uuid : getValue('uuid')
} }
fetch(api+'/update/password',{ fetch(user_controller+'/password',{
method : 'POST', method : 'POST',
headers : { headers : {
'Content-Type': 'application/json' 'Content-Type': 'application/json'

2
de.srsoftware.oidc.web/src/main/resources/en/user.js

@ -24,4 +24,4 @@ async function handleNavigation(response){
} }
} }
fetch(api+"/user",{method:'POST'}).then(handleUser); fetch(user_controller+"/",{method:'POST'}).then(handleUser);
Loading…
Cancel
Save