From 9e0409e510b17db0afc7d8a9693fbdcd78913884 Mon Sep 17 00:00:00 2001 From: Schlaumeier5 Date: Sun, 5 Jul 2026 17:10:28 +0200 Subject: [PATCH 1/7] Added events --- .../igslandstuhl/database/events/Event.java | 18 +++++++ .../database/events/EventFilter.java | 17 +++++++ .../database/events/EventListener.java | 50 +++++++++++++++++++ .../database/events/EventType.java | 8 +++ .../database/events/ListenerPriority.java | 9 ++++ 5 files changed, 102 insertions(+) create mode 100644 src/main/java/de/igslandstuhl/database/events/Event.java create mode 100644 src/main/java/de/igslandstuhl/database/events/EventFilter.java create mode 100644 src/main/java/de/igslandstuhl/database/events/EventListener.java create mode 100644 src/main/java/de/igslandstuhl/database/events/EventType.java create mode 100644 src/main/java/de/igslandstuhl/database/events/ListenerPriority.java diff --git a/src/main/java/de/igslandstuhl/database/events/Event.java b/src/main/java/de/igslandstuhl/database/events/Event.java new file mode 100644 index 0000000..5f3497c --- /dev/null +++ b/src/main/java/de/igslandstuhl/database/events/Event.java @@ -0,0 +1,18 @@ +package de.igslandstuhl.database.events; + +public abstract class Event { + private boolean cancelled = false; + + public abstract EventType getType(); + protected void onCancel() { + // Override this method to handle cancellation logic in subclasses + } + + public boolean isCancelled() { + return this.cancelled; + } + public void cancel() { + this.cancelled = true; + onCancel(); + } +} diff --git a/src/main/java/de/igslandstuhl/database/events/EventFilter.java b/src/main/java/de/igslandstuhl/database/events/EventFilter.java new file mode 100644 index 0000000..ef40815 --- /dev/null +++ b/src/main/java/de/igslandstuhl/database/events/EventFilter.java @@ -0,0 +1,17 @@ +package de.igslandstuhl.database.events; + +@FunctionalInterface +public interface EventFilter { + public boolean filter(T event); + @SafeVarargs + public static EventFilter linked(EventFilter... filters) { + return event -> { + for (EventFilter filter : filters) { + if (!filter.filter(event)) { + return false; + } + } + return true; + }; + } +} diff --git a/src/main/java/de/igslandstuhl/database/events/EventListener.java b/src/main/java/de/igslandstuhl/database/events/EventListener.java new file mode 100644 index 0000000..23d82ab --- /dev/null +++ b/src/main/java/de/igslandstuhl/database/events/EventListener.java @@ -0,0 +1,50 @@ +package de.igslandstuhl.database.events; + +import java.util.HashSet; +import java.util.LinkedList; +import java.util.Set; + +import de.igslandstuhl.database.Registry; + +public abstract class EventListener { + private static final Registry, Set>> listeners = new Registry<>(); + + private final ListenerPriority priority; + + public EventListener(ListenerPriority priority) { + this.priority = priority; + } + public abstract void onEvent(T event); + public abstract EventType getEventType(); + + public ListenerPriority getPriority() { + return priority; + } + public void register() { + listeners.get(getEventType()).add(this); + } + + public static void register(EventType type) { + listeners.register(type, new HashSet<>()); + } + + @SuppressWarnings("unchecked") + private static Set> getListeners(EventType type) { + return (Set>) (Set) listeners.get(type); + } + public static void fireEvent(T event) { + Set> eventListeners = getListeners(EventType.of(event)); + if (eventListeners != null) { + new LinkedList<>(eventListeners).stream() + .sorted((a, b) -> a.getPriority().compareTo(b.getPriority())) + .forEach(listener -> { + if (!event.isCancelled()) { + listener.onEvent(event); + if (event.isCancelled() && listener.getPriority() == ListenerPriority.MONITOR) { + throw new IllegalStateException("Event was cancelled but a monitor listener was called."); + } + } + }); + } + } +} diff --git a/src/main/java/de/igslandstuhl/database/events/EventType.java b/src/main/java/de/igslandstuhl/database/events/EventType.java new file mode 100644 index 0000000..0e3735e --- /dev/null +++ b/src/main/java/de/igslandstuhl/database/events/EventType.java @@ -0,0 +1,8 @@ +package de.igslandstuhl.database.events; + +public record EventType(String name) { + @SuppressWarnings("unchecked") + public static EventType of(T event) { + return (EventType) event.getType(); + } +} diff --git a/src/main/java/de/igslandstuhl/database/events/ListenerPriority.java b/src/main/java/de/igslandstuhl/database/events/ListenerPriority.java new file mode 100644 index 0000000..9d7c30c --- /dev/null +++ b/src/main/java/de/igslandstuhl/database/events/ListenerPriority.java @@ -0,0 +1,9 @@ +package de.igslandstuhl.database.events; + +public enum ListenerPriority { + LOWEST, + LOW, + HIGH, + HIGHEST, + MONITOR +} From 26a2e018fe69d6a3824a11eded8caa775222f0c8 Mon Sep 17 00:00:00 2001 From: Schlaumeier5 Date: Sun, 5 Jul 2026 17:23:12 +0200 Subject: [PATCH 2/7] Removed deprecated methods from AccessManager --- .../server/webserver/AccessManager.java | 39 ------------------- 1 file changed, 39 deletions(-) diff --git a/src/main/java/de/igslandstuhl/database/server/webserver/AccessManager.java b/src/main/java/de/igslandstuhl/database/server/webserver/AccessManager.java index 53ca04a..578d160 100644 --- a/src/main/java/de/igslandstuhl/database/server/webserver/AccessManager.java +++ b/src/main/java/de/igslandstuhl/database/server/webserver/AccessManager.java @@ -106,45 +106,6 @@ private AccessManager() { } - - /** - * Checks if a user has access to a specific resource. - * - * @param user the username of the user, or null if not authenticated - * @param resource the ResourceLocation representing the resource to check access for - * @return true if the user has access to the resource, false otherwise - * @deprecated Use hasAccess(String user, String path) - */ - @Deprecated - public boolean hasAccess(String user, ResourceLocation resource) { - return hasAccess(User.getUser(user), resource); - } - /** - * Checks if a user has access to a specific resource. - * - * @param user the user, or null if not authenticated - * @param resource the ResourceLocation representing the resource to check access for - * @return true if the user has access to the resource, false otherwise - * @deprecated Use hasAccess(User user, String path) - */ - @Deprecated - public boolean hasAccess(User user, ResourceLocation resource) { - if (Arrays.asList(PUBLIC_SPACES).contains(resource.namespace()) || Arrays.asList(PUBLIC_LOCATIONS).contains(resource.resource())) { - return true; - } else if (user != null) { - if (resource.namespace().equals(USER_SPACE) || resource.resource().startsWith("my") && !(user == User.ANONYMOUS)) { - return true; - } else if (resource.namespace().equals(TEACHER_SPACE)) { - return user.isTeacher() || user.isAdmin(); - } else if (resource.namespace().equals(ADMIN_SPACE) || Arrays.asList(ADMIN_LOCATIONS).contains(resource.resource())) { - return user.isAdmin(); - } else { - return false; - } - } else { - return false; - } - } /** * Checks if a user has access to a specific access level * @param user the user, can be null to indicate no user logged in From 52ff3eb2de2ff4acb2db9f2effb0bf0ed9d2053a Mon Sep 17 00:00:00 2001 From: Schlaumeier5 Date: Sun, 5 Jul 2026 18:28:38 +0200 Subject: [PATCH 3/7] Added new event type: AccessManagerEvent Is used to modify access manager behavior --- .../database/events/EventListener.java | 5 + .../events/access/AccessManagerEvent.java | 47 ++++++++ .../database/events/access/AccessState.java | 9 ++ .../events/access/AccessStateFilter.java | 16 +++ .../server/webserver/AccessManager.java | 111 +++--------------- src/main/resources/meta/paths/spaces.json | 22 ---- .../server/webserver/AccessManagerTest.java | 49 ++++---- 7 files changed, 114 insertions(+), 145 deletions(-) create mode 100644 src/main/java/de/igslandstuhl/database/events/access/AccessManagerEvent.java create mode 100644 src/main/java/de/igslandstuhl/database/events/access/AccessState.java create mode 100644 src/main/java/de/igslandstuhl/database/events/access/AccessStateFilter.java delete mode 100644 src/main/resources/meta/paths/spaces.json diff --git a/src/main/java/de/igslandstuhl/database/events/EventListener.java b/src/main/java/de/igslandstuhl/database/events/EventListener.java index 23d82ab..190de5f 100644 --- a/src/main/java/de/igslandstuhl/database/events/EventListener.java +++ b/src/main/java/de/igslandstuhl/database/events/EventListener.java @@ -14,8 +14,12 @@ public abstract class EventListener { public EventListener(ListenerPriority priority) { this.priority = priority; } + public abstract void onEvent(T event); public abstract EventType getEventType(); + public EventFilter getFilter() { + return event -> true; + } public ListenerPriority getPriority() { return priority; @@ -36,6 +40,7 @@ public static void fireEvent(T event) { Set> eventListeners = getListeners(EventType.of(event)); if (eventListeners != null) { new LinkedList<>(eventListeners).stream() + .filter(listener -> listener.getFilter().filter(event)) .sorted((a, b) -> a.getPriority().compareTo(b.getPriority())) .forEach(listener -> { if (!event.isCancelled()) { diff --git a/src/main/java/de/igslandstuhl/database/events/access/AccessManagerEvent.java b/src/main/java/de/igslandstuhl/database/events/access/AccessManagerEvent.java new file mode 100644 index 0000000..0bd5f05 --- /dev/null +++ b/src/main/java/de/igslandstuhl/database/events/access/AccessManagerEvent.java @@ -0,0 +1,47 @@ +package de.igslandstuhl.database.events.access; + +import java.util.Optional; + +import de.igslandstuhl.database.events.Event; +import de.igslandstuhl.database.events.EventType; + +public class AccessManagerEvent extends Event { + public static final EventType TYPE = new EventType<>("AccessManagerEvent"); + + private final AccessState accessState; + + private Optional changedAccessState = Optional.empty(); + + public AccessManagerEvent(AccessState accessState) { + this.accessState = accessState; + } + + public AccessState getAccessState() { + return accessState; + } + public Optional getChangedAccessState() { + return changedAccessState; + } + @Override + public EventType getType() { + return TYPE; + } + + public void changeAccessState(AccessState newAccessState) { + this.changedAccessState = Optional.of(newAccessState); + this.cancel(); + } + + public static AccessManagerEvent unauthorized() { + return new AccessManagerEvent(AccessState.UNAUTHORIZED); + } + public static AccessManagerEvent authorized() { + return new AccessManagerEvent(AccessState.AUTHORIZED); + } + public static AccessManagerEvent restricted() { + return new AccessManagerEvent(AccessState.RESTRICTED); + } + public static AccessManagerEvent pending() { + return new AccessManagerEvent(AccessState.PENDING); + } +} diff --git a/src/main/java/de/igslandstuhl/database/events/access/AccessState.java b/src/main/java/de/igslandstuhl/database/events/access/AccessState.java new file mode 100644 index 0000000..8405aa1 --- /dev/null +++ b/src/main/java/de/igslandstuhl/database/events/access/AccessState.java @@ -0,0 +1,9 @@ +package de.igslandstuhl.database.events.access; + +public enum AccessState { + UNAUTHORIZED, + AUTHORIZED, + RESTRICTED, + PENDING, + PERMITTED +} diff --git a/src/main/java/de/igslandstuhl/database/events/access/AccessStateFilter.java b/src/main/java/de/igslandstuhl/database/events/access/AccessStateFilter.java new file mode 100644 index 0000000..857b8a6 --- /dev/null +++ b/src/main/java/de/igslandstuhl/database/events/access/AccessStateFilter.java @@ -0,0 +1,16 @@ +package de.igslandstuhl.database.events.access; + +import de.igslandstuhl.database.events.EventFilter; + +public class AccessStateFilter implements EventFilter { + private final AccessState state; + + public AccessStateFilter(AccessState state) { + this.state = state; + } + + @Override + public boolean filter(AccessManagerEvent event) { + return event.getAccessState() == state; + } +} diff --git a/src/main/java/de/igslandstuhl/database/server/webserver/AccessManager.java b/src/main/java/de/igslandstuhl/database/server/webserver/AccessManager.java index 578d160..672efd1 100644 --- a/src/main/java/de/igslandstuhl/database/server/webserver/AccessManager.java +++ b/src/main/java/de/igslandstuhl/database/server/webserver/AccessManager.java @@ -1,17 +1,13 @@ package de.igslandstuhl.database.server.webserver; -import java.io.IOException; -import java.util.Arrays; -import java.util.List; -import java.util.Map; - import org.slf4j.Logger; import org.slf4j.LoggerFactory; import de.igslandstuhl.database.Registry; import de.igslandstuhl.database.api.User; -import de.igslandstuhl.database.server.Server; -import de.igslandstuhl.database.server.resources.ResourceLocation; +import de.igslandstuhl.database.events.EventListener; +import de.igslandstuhl.database.events.access.AccessManagerEvent; +import de.igslandstuhl.database.events.access.AccessState; /** * AccessManager is responsible for managing access to resources based on user roles and resource locations. @@ -23,88 +19,9 @@ public class AccessManager { public static AccessManager getInstance() { return INSTANCE; } - /** - * Public spaces and locations that are accessible without authentication. - * These resources can be accessed by anyone, regardless of their authentication status. - */ - private final String[] PUBLIC_SPACES; - /** - * The user space is restricted to authenticated users. - */ - private final String USER_SPACE; - /** - * The teacher space is restricted to authenticated teachers. - */ - private final String TEACHER_SPACE; - /** - * The admin space is restricted to authenticated admins. - */ - private final String ADMIN_SPACE; - /** - * Public locations that are accessible without authentication. - * These resources can be accessed by anyone, regardless of their authentication status. - */ - private final String[] PUBLIC_LOCATIONS; - /** - * User locations that are accessible only to authenticated users. - * These resources require user login for access. - */ - private final String[] USER_LOCATIONS; - /** - * Teacher locations that are accessible only to authenticated teachers. - * These resources require teacher privileges for access. - */ - private final String[] TEACHER_LOCATIONS; - /** - * Admin locations that are accessible only to authenticated admins. - * These resources require admin privileges for access. - */ - private final String[] ADMIN_LOCATIONS; - - @SuppressWarnings("unchecked") private AccessManager() { LOGGER.info("Setting up AccessManager..."); - ResourceLocation metaLocation = new ResourceLocation("meta", "paths", "spaces.json"); - String userSpace = "user"; - String teacherSpace = "teacher"; - String adminSpace = "admin"; - - String[] publicSpaces = {"error", "site", "icons"}; - String[] publicLocations = {"rooms", "subjects"}; - String[] userLocations = {}; - String[] teacherLocations = {}; - String[] adminLocations = {"students", "teachers", "classes"}; - try { - LOGGER.debug("Trying to read spaces metadata..."); - Map pathData = Server.getInstance().getResourceManager().readJsonResourceAsMap(metaLocation); - List publicSpacesList = (List) pathData.get("public_spaces"); - List publicLocationsList = (List) pathData.get("public_locations"); - List userLocationsList = (List) pathData.get("user_locations"); - List teacherLocationsList = (List) pathData.get("teacher_locations"); - List adminLocationsList = (List) pathData.get("admin_locations"); - userSpace = (String) pathData.get("user_space"); - teacherSpace = (String) pathData.get("teacher_space"); - adminSpace = (String) pathData.get("admin_space"); - publicSpaces = publicSpacesList.toArray(new String[publicSpacesList.size()]); - publicLocations = publicLocationsList.toArray(new String[publicLocationsList.size()]); - userLocations = userLocationsList.toArray(new String[userLocationsList.size()]); - teacherLocations = teacherLocationsList.toArray(new String[teacherLocationsList.size()]); - adminLocations = adminLocationsList.toArray(new String[adminLocationsList.size()]); - } catch (IOException e) { - LOGGER.error("Could not read spaces metadata!", e); - } finally { - USER_SPACE = userSpace; - TEACHER_SPACE = teacherSpace; - ADMIN_SPACE = adminSpace; - PUBLIC_SPACES = publicSpaces; - PUBLIC_LOCATIONS = publicLocations; - USER_LOCATIONS = userLocations; - TEACHER_LOCATIONS = teacherLocations; - ADMIN_LOCATIONS = adminLocations; - } - - } /** * Checks if a user has access to a specific access level @@ -113,23 +30,29 @@ private AccessManager() { * @return true, if the user has access, otherwise false */ public boolean hasAccess(User user, AccessLevel accessLevel) { + AccessState result; if (accessLevel == AccessLevel.PUBLIC) { - return true; + result = AccessState.PERMITTED; } else if (user == null || user == User.ANONYMOUS) { - return false; + result = AccessState.UNAUTHORIZED; } else if (accessLevel == AccessLevel.NONE) { - return false; + result = AccessState.RESTRICTED; } else if (accessLevel == AccessLevel.USER) { - return true; + result = AccessState.AUTHORIZED; } else if (accessLevel == AccessLevel.STUDENT) { - return user.isStudent(); + result = user.isStudent() ? AccessState.AUTHORIZED : AccessState.UNAUTHORIZED; } else if (user.isStudent()) { - return false; + result = AccessState.UNAUTHORIZED; } else if (accessLevel == AccessLevel.TEACHER) { - return true; + result = AccessState.AUTHORIZED; } else { - return user.isAdmin(); // Must be AccessLevel.ADMIN + result = user.isAdmin() ? AccessState.AUTHORIZED : AccessState.UNAUTHORIZED; // Must be AccessLevel.ADMIN } + // Fire an AccessManagerEvent to allow for external modifications of the access decision + AccessManagerEvent event = new AccessManagerEvent(result); + EventListener.fireEvent(event); + result = event.getChangedAccessState().orElse(result); + return result == AccessState.AUTHORIZED || result == AccessState.PERMITTED; } /** * Checks if a user has access to a specific web path diff --git a/src/main/resources/meta/paths/spaces.json b/src/main/resources/meta/paths/spaces.json deleted file mode 100644 index e1f899a..0000000 --- a/src/main/resources/meta/paths/spaces.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "public_spaces": [ - "error", - "site", - "icons" - ], - "user_space": "user", - "teacher_space": "teacher", - "admin_space": "admin", - "public_locations": [ - "rooms", - "subjects" - ], - "user_locations": [], - "teacher_locations": [], - "admin_locations": [ - "students", - "teachers", - "classes", - "all-student-results" - ] -} \ No newline at end of file diff --git a/src/test/java/de/igslandstuhl/database/server/webserver/AccessManagerTest.java b/src/test/java/de/igslandstuhl/database/server/webserver/AccessManagerTest.java index 940bcc4..8a18377 100644 --- a/src/test/java/de/igslandstuhl/database/server/webserver/AccessManagerTest.java +++ b/src/test/java/de/igslandstuhl/database/server/webserver/AccessManagerTest.java @@ -11,8 +11,6 @@ import org.junit.jupiter.api.Test; import de.igslandstuhl.database.api.User; -import de.igslandstuhl.database.server.resources.ResourceLocation; -import de.igslandstuhl.database.server.webserver.handlers.WebResourceHandler; public class AccessManagerTest { private User teacher; @@ -120,45 +118,38 @@ public String getUsername() { anonymous = User.ANONYMOUS; } @Test - public void testErrorMessageAccess() { - assertTrue(AccessManager.getInstance().hasAccess(anonymous, ResourceLocation.get("html", "error:404"))); - assertTrue(AccessManager.getInstance().hasAccess(student, ResourceLocation.get("html", "error:404"))); - assertTrue(AccessManager.getInstance().hasAccess(teacher, ResourceLocation.get("html", "error:404"))); - assertTrue(AccessManager.getInstance().hasAccess(admin, ResourceLocation.get("html", "error:404"))); - } - @Test public void testIconAccess() { - assertTrue(AccessManager.getInstance().hasAccess(anonymous, ResourceLocation.get("imgs", "icons:favicon.ico"))); - assertTrue(AccessManager.getInstance().hasAccess(student, ResourceLocation.get("imgs", "icons:favicon.ico"))); - assertTrue(AccessManager.getInstance().hasAccess(teacher, ResourceLocation.get("imgs", "icons:favicon.ico"))); - assertTrue(AccessManager.getInstance().hasAccess(admin, ResourceLocation.get("imgs", "icons:favicon.ico"))); + assertTrue(AccessManager.getInstance().hasAccess(anonymous, "/favicon.ico")); + assertTrue(AccessManager.getInstance().hasAccess(student, "/favicon.ico")); + assertTrue(AccessManager.getInstance().hasAccess(teacher, "/favicon.ico")); + assertTrue(AccessManager.getInstance().hasAccess(admin, "/favicon.ico")); } @Test public void testLoginAccess() { - assertTrue(AccessManager.getInstance().hasAccess(anonymous, ResourceLocation.get("html", "site:login"))); - assertTrue(AccessManager.getInstance().hasAccess(student, ResourceLocation.get("html", "site:login"))); - assertTrue(AccessManager.getInstance().hasAccess(teacher, ResourceLocation.get("html", "site:login"))); - assertTrue(AccessManager.getInstance().hasAccess(admin, ResourceLocation.get("html", "site:login"))); + assertTrue(AccessManager.getInstance().hasAccess(anonymous, "/login")); + assertTrue(AccessManager.getInstance().hasAccess(student, "/login")); + assertTrue(AccessManager.getInstance().hasAccess(teacher, "/login")); + assertTrue(AccessManager.getInstance().hasAccess(admin, "/login")); } @Test public void testDashboardAccess() { - assertFalse(AccessManager.getInstance().hasAccess(anonymous, WebResourceHandler.locationFromPath("/dashboard", anonymous))); - assertTrue(AccessManager.getInstance().hasAccess(student, WebResourceHandler.locationFromPath("/dashboard", student))); - assertTrue(AccessManager.getInstance().hasAccess(teacher, WebResourceHandler.locationFromPath("/dashboard", teacher))); - assertTrue(AccessManager.getInstance().hasAccess(admin, WebResourceHandler.locationFromPath("/dashboard", admin))); + assertFalse(AccessManager.getInstance().hasAccess(anonymous, "/dashboard")); + assertTrue(AccessManager.getInstance().hasAccess(student, "/dashboard")); + assertTrue(AccessManager.getInstance().hasAccess(teacher, "/dashboard")); + assertTrue(AccessManager.getInstance().hasAccess(admin, "/dashboard")); } @Test public void testStudentManagementAccess() { - assertFalse(AccessManager.getInstance().hasAccess(anonymous, WebResourceHandler.locationFromPath("/student", anonymous))); - assertFalse(AccessManager.getInstance().hasAccess(student, WebResourceHandler.locationFromPath("/student", student))); - assertTrue(AccessManager.getInstance().hasAccess(teacher, WebResourceHandler.locationFromPath("/student", teacher))); - assertTrue(AccessManager.getInstance().hasAccess(admin, WebResourceHandler.locationFromPath("/student", admin))); + assertFalse(AccessManager.getInstance().hasAccess(anonymous, "/student")); + assertFalse(AccessManager.getInstance().hasAccess(student, "/student")); + assertTrue(AccessManager.getInstance().hasAccess(teacher, "/student")); + assertTrue(AccessManager.getInstance().hasAccess(admin, "/student")); } @Test public void testTeacherManagementAccess() { - assertFalse(AccessManager.getInstance().hasAccess(anonymous, WebResourceHandler.locationFromPath("/teacher", anonymous))); - assertFalse(AccessManager.getInstance().hasAccess(student, WebResourceHandler.locationFromPath("/teacher", student))); - assertFalse(AccessManager.getInstance().hasAccess(teacher, WebResourceHandler.locationFromPath("/teacher", teacher))); - assertTrue(AccessManager.getInstance().hasAccess(admin, WebResourceHandler.locationFromPath("/teacher", admin))); + assertFalse(AccessManager.getInstance().hasAccess(anonymous, "/teacher")); + assertFalse(AccessManager.getInstance().hasAccess(student, "/teacher")); + assertFalse(AccessManager.getInstance().hasAccess(teacher, "/teacher")); + assertTrue(AccessManager.getInstance().hasAccess(admin, "/teacher")); } } From fa46f3d0058b21565b1d6930cb1bc1772ebbbd25 Mon Sep 17 00:00:00 2001 From: Schlaumeier5 Date: Mon, 6 Jul 2026 13:14:42 +0200 Subject: [PATCH 4/7] Improved AccessManagerEvent system --- .../database/events/access/AccessState.java | 9 ------ .../database/server/webserver/WebPath.java | 1 + .../webserver/{ => access}/AccessLevel.java | 2 +- .../webserver/{ => access}/AccessManager.java | 28 ++++++++++--------- .../webserver}/access/AccessManagerEvent.java | 26 ++++++++++------- .../server/webserver/access/AccessState.java | 24 ++++++++++++++++ .../webserver}/access/AccessStateFilter.java | 2 +- .../webserver/handlers/HttpHandler.java | 2 +- .../handlers/PostRequestHandler.java | 2 +- .../webserver/responses/GetResponse.java | 2 +- .../webserver/responses/PostResponse.java | 2 +- .../server/webserver/AccessManagerTest.java | 1 + 12 files changed, 63 insertions(+), 38 deletions(-) delete mode 100644 src/main/java/de/igslandstuhl/database/events/access/AccessState.java rename src/main/java/de/igslandstuhl/database/server/webserver/{ => access}/AccessLevel.java (90%) rename src/main/java/de/igslandstuhl/database/server/webserver/{ => access}/AccessManager.java (85%) rename src/main/java/de/igslandstuhl/database/{events => server/webserver}/access/AccessManagerEvent.java (53%) create mode 100644 src/main/java/de/igslandstuhl/database/server/webserver/access/AccessState.java rename src/main/java/de/igslandstuhl/database/{events => server/webserver}/access/AccessStateFilter.java (86%) diff --git a/src/main/java/de/igslandstuhl/database/events/access/AccessState.java b/src/main/java/de/igslandstuhl/database/events/access/AccessState.java deleted file mode 100644 index 8405aa1..0000000 --- a/src/main/java/de/igslandstuhl/database/events/access/AccessState.java +++ /dev/null @@ -1,9 +0,0 @@ -package de.igslandstuhl.database.events.access; - -public enum AccessState { - UNAUTHORIZED, - AUTHORIZED, - RESTRICTED, - PENDING, - PERMITTED -} diff --git a/src/main/java/de/igslandstuhl/database/server/webserver/WebPath.java b/src/main/java/de/igslandstuhl/database/server/webserver/WebPath.java index 075401d..062b1f9 100644 --- a/src/main/java/de/igslandstuhl/database/server/webserver/WebPath.java +++ b/src/main/java/de/igslandstuhl/database/server/webserver/WebPath.java @@ -10,6 +10,7 @@ import de.igslandstuhl.database.Registry; import de.igslandstuhl.database.server.Server; import de.igslandstuhl.database.server.resources.ResourceLocation; +import de.igslandstuhl.database.server.webserver.access.AccessLevel; import de.igslandstuhl.database.server.webserver.requests.RequestType; public record WebPath(RequestType type, String handlerType, List namespaces, String context, AccessLevel accessLevel) { diff --git a/src/main/java/de/igslandstuhl/database/server/webserver/AccessLevel.java b/src/main/java/de/igslandstuhl/database/server/webserver/access/AccessLevel.java similarity index 90% rename from src/main/java/de/igslandstuhl/database/server/webserver/AccessLevel.java rename to src/main/java/de/igslandstuhl/database/server/webserver/access/AccessLevel.java index 9119790..f18b5f3 100644 --- a/src/main/java/de/igslandstuhl/database/server/webserver/AccessLevel.java +++ b/src/main/java/de/igslandstuhl/database/server/webserver/access/AccessLevel.java @@ -1,4 +1,4 @@ -package de.igslandstuhl.database.server.webserver; +package de.igslandstuhl.database.server.webserver.access; import de.igslandstuhl.database.api.User; diff --git a/src/main/java/de/igslandstuhl/database/server/webserver/AccessManager.java b/src/main/java/de/igslandstuhl/database/server/webserver/access/AccessManager.java similarity index 85% rename from src/main/java/de/igslandstuhl/database/server/webserver/AccessManager.java rename to src/main/java/de/igslandstuhl/database/server/webserver/access/AccessManager.java index 672efd1..687605b 100644 --- a/src/main/java/de/igslandstuhl/database/server/webserver/AccessManager.java +++ b/src/main/java/de/igslandstuhl/database/server/webserver/access/AccessManager.java @@ -1,4 +1,4 @@ -package de.igslandstuhl.database.server.webserver; +package de.igslandstuhl.database.server.webserver.access; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -6,8 +6,6 @@ import de.igslandstuhl.database.Registry; import de.igslandstuhl.database.api.User; import de.igslandstuhl.database.events.EventListener; -import de.igslandstuhl.database.events.access.AccessManagerEvent; -import de.igslandstuhl.database.events.access.AccessState; /** * AccessManager is responsible for managing access to resources based on user roles and resource locations. @@ -27,9 +25,9 @@ private AccessManager() { * Checks if a user has access to a specific access level * @param user the user, can be null to indicate no user logged in * @param accessLevel the access level - * @return true, if the user has access, otherwise false + * @return the access state of the user for the given access level */ - public boolean hasAccess(User user, AccessLevel accessLevel) { + private AccessState getAccessState(User user, AccessLevel accessLevel) { AccessState result; if (accessLevel == AccessLevel.PUBLIC) { result = AccessState.PERMITTED; @@ -40,19 +38,16 @@ public boolean hasAccess(User user, AccessLevel accessLevel) { } else if (accessLevel == AccessLevel.USER) { result = AccessState.AUTHORIZED; } else if (accessLevel == AccessLevel.STUDENT) { - result = user.isStudent() ? AccessState.AUTHORIZED : AccessState.UNAUTHORIZED; + result = user.isStudent() ? AccessState.AUTHORIZED : AccessState.RESTRICTED; } else if (user.isStudent()) { - result = AccessState.UNAUTHORIZED; + result = AccessState.RESTRICTED; } else if (accessLevel == AccessLevel.TEACHER) { result = AccessState.AUTHORIZED; } else { - result = user.isAdmin() ? AccessState.AUTHORIZED : AccessState.UNAUTHORIZED; // Must be AccessLevel.ADMIN + result = user.isAdmin() ? AccessState.AUTHORIZED : AccessState.RESTRICTED; // Must be AccessLevel.ADMIN } // Fire an AccessManagerEvent to allow for external modifications of the access decision - AccessManagerEvent event = new AccessManagerEvent(result); - EventListener.fireEvent(event); - result = event.getChangedAccessState().orElse(result); - return result == AccessState.AUTHORIZED || result == AccessState.PERMITTED; + return result; } /** * Checks if a user has access to a specific web path @@ -71,6 +66,13 @@ public boolean hasAccess(String user, String path) { */ public boolean hasAccess(User user, String path) { AccessLevel accessLevel = Registry.webPathRegistry().get(path).accessLevel(); - return hasAccess(user, accessLevel); + AccessState result = getAccessState(user, accessLevel); + + AccessManagerEvent event = new AccessManagerEvent(result, path); + EventListener.fireEvent(event); + + result = event.getChangedAccessState().orElse(result); + + return result == AccessState.AUTHORIZED || result == AccessState.PERMITTED; } } diff --git a/src/main/java/de/igslandstuhl/database/events/access/AccessManagerEvent.java b/src/main/java/de/igslandstuhl/database/server/webserver/access/AccessManagerEvent.java similarity index 53% rename from src/main/java/de/igslandstuhl/database/events/access/AccessManagerEvent.java rename to src/main/java/de/igslandstuhl/database/server/webserver/access/AccessManagerEvent.java index 0bd5f05..3615a0a 100644 --- a/src/main/java/de/igslandstuhl/database/events/access/AccessManagerEvent.java +++ b/src/main/java/de/igslandstuhl/database/server/webserver/access/AccessManagerEvent.java @@ -1,4 +1,4 @@ -package de.igslandstuhl.database.events.access; +package de.igslandstuhl.database.server.webserver.access; import java.util.Optional; @@ -10,15 +10,21 @@ public class AccessManagerEvent extends Event { private final AccessState accessState; + private final String path; + private Optional changedAccessState = Optional.empty(); - public AccessManagerEvent(AccessState accessState) { + public AccessManagerEvent(AccessState accessState, String path) { this.accessState = accessState; + this.path = path; } public AccessState getAccessState() { return accessState; } + public String getPath() { + return path; + } public Optional getChangedAccessState() { return changedAccessState; } @@ -32,16 +38,16 @@ public void changeAccessState(AccessState newAccessState) { this.cancel(); } - public static AccessManagerEvent unauthorized() { - return new AccessManagerEvent(AccessState.UNAUTHORIZED); + public static AccessManagerEvent unauthorized(String path) { + return new AccessManagerEvent(AccessState.UNAUTHORIZED, path); } - public static AccessManagerEvent authorized() { - return new AccessManagerEvent(AccessState.AUTHORIZED); + public static AccessManagerEvent authorized(String path) { + return new AccessManagerEvent(AccessState.AUTHORIZED, path); } - public static AccessManagerEvent restricted() { - return new AccessManagerEvent(AccessState.RESTRICTED); + public static AccessManagerEvent restricted(String path) { + return new AccessManagerEvent(AccessState.RESTRICTED, path); } - public static AccessManagerEvent pending() { - return new AccessManagerEvent(AccessState.PENDING); + public static AccessManagerEvent pending(String path) { + return new AccessManagerEvent(AccessState.PENDING, path); } } diff --git a/src/main/java/de/igslandstuhl/database/server/webserver/access/AccessState.java b/src/main/java/de/igslandstuhl/database/server/webserver/access/AccessState.java new file mode 100644 index 0000000..c3d40a0 --- /dev/null +++ b/src/main/java/de/igslandstuhl/database/server/webserver/access/AccessState.java @@ -0,0 +1,24 @@ +package de.igslandstuhl.database.server.webserver.access; + +public enum AccessState { + /** + * The user is not logged in and does not have access to the resource. + */ + UNAUTHORIZED, + /** + * The user is logged in and has access to the resource. + */ + AUTHORIZED, + /** + * The user is logged in but does not have access to the resource. + */ + RESTRICTED, + /** + * The user is logged in and has requested access to the resource. + */ + PENDING, + /** + * The user is logged in and has been granted access to the resource. + */ + PERMITTED +} diff --git a/src/main/java/de/igslandstuhl/database/events/access/AccessStateFilter.java b/src/main/java/de/igslandstuhl/database/server/webserver/access/AccessStateFilter.java similarity index 86% rename from src/main/java/de/igslandstuhl/database/events/access/AccessStateFilter.java rename to src/main/java/de/igslandstuhl/database/server/webserver/access/AccessStateFilter.java index 857b8a6..1c88008 100644 --- a/src/main/java/de/igslandstuhl/database/events/access/AccessStateFilter.java +++ b/src/main/java/de/igslandstuhl/database/server/webserver/access/AccessStateFilter.java @@ -1,4 +1,4 @@ -package de.igslandstuhl.database.events.access; +package de.igslandstuhl.database.server.webserver.access; import de.igslandstuhl.database.events.EventFilter; diff --git a/src/main/java/de/igslandstuhl/database/server/webserver/handlers/HttpHandler.java b/src/main/java/de/igslandstuhl/database/server/webserver/handlers/HttpHandler.java index d64604b..2ab3eba 100644 --- a/src/main/java/de/igslandstuhl/database/server/webserver/handlers/HttpHandler.java +++ b/src/main/java/de/igslandstuhl/database/server/webserver/handlers/HttpHandler.java @@ -5,8 +5,8 @@ import de.igslandstuhl.database.Registry; import de.igslandstuhl.database.server.Server; -import de.igslandstuhl.database.server.webserver.AccessLevel; import de.igslandstuhl.database.server.webserver.Status; +import de.igslandstuhl.database.server.webserver.access.AccessLevel; import de.igslandstuhl.database.server.webserver.requests.APIPostRequest; import de.igslandstuhl.database.server.webserver.requests.GetRequest; import de.igslandstuhl.database.server.webserver.requests.HttpRequest; diff --git a/src/main/java/de/igslandstuhl/database/server/webserver/handlers/PostRequestHandler.java b/src/main/java/de/igslandstuhl/database/server/webserver/handlers/PostRequestHandler.java index 15cc8fc..594e66f 100644 --- a/src/main/java/de/igslandstuhl/database/server/webserver/handlers/PostRequestHandler.java +++ b/src/main/java/de/igslandstuhl/database/server/webserver/handlers/PostRequestHandler.java @@ -37,8 +37,8 @@ import de.igslandstuhl.database.plugins.config.PluginSetting; import de.igslandstuhl.database.plugins.config.ShortAnswerSetting; import de.igslandstuhl.database.server.Server; -import de.igslandstuhl.database.server.webserver.AccessLevel; import de.igslandstuhl.database.server.webserver.ContentType; +import de.igslandstuhl.database.server.webserver.access.AccessLevel; import de.igslandstuhl.database.server.webserver.requests.APIPostRequest; import de.igslandstuhl.database.server.webserver.requests.PostRequest; import de.igslandstuhl.database.server.webserver.responses.HttpResponse; diff --git a/src/main/java/de/igslandstuhl/database/server/webserver/responses/GetResponse.java b/src/main/java/de/igslandstuhl/database/server/webserver/responses/GetResponse.java index 7189ed0..76c5ece 100644 --- a/src/main/java/de/igslandstuhl/database/server/webserver/responses/GetResponse.java +++ b/src/main/java/de/igslandstuhl/database/server/webserver/responses/GetResponse.java @@ -7,10 +7,10 @@ import de.igslandstuhl.database.client.TemplatingPreprocessor; import de.igslandstuhl.database.server.Server; import de.igslandstuhl.database.server.resources.ResourceLocation; -import de.igslandstuhl.database.server.webserver.AccessManager; import de.igslandstuhl.database.server.webserver.ContentType; import de.igslandstuhl.database.server.webserver.NoWebResourceException; import de.igslandstuhl.database.server.webserver.Status; +import de.igslandstuhl.database.server.webserver.access.AccessManager; import de.igslandstuhl.database.server.webserver.handlers.HttpHandler; import de.igslandstuhl.database.server.webserver.handlers.get.PluginRequestHandler; import de.igslandstuhl.database.server.webserver.requests.HttpRequest; diff --git a/src/main/java/de/igslandstuhl/database/server/webserver/responses/PostResponse.java b/src/main/java/de/igslandstuhl/database/server/webserver/responses/PostResponse.java index f2d373a..2181ca8 100644 --- a/src/main/java/de/igslandstuhl/database/server/webserver/responses/PostResponse.java +++ b/src/main/java/de/igslandstuhl/database/server/webserver/responses/PostResponse.java @@ -8,11 +8,11 @@ import de.igslandstuhl.database.server.Server; import de.igslandstuhl.database.server.WebServer; import de.igslandstuhl.database.server.resources.ResourceLocation; -import de.igslandstuhl.database.server.webserver.AccessManager; import de.igslandstuhl.database.server.webserver.ContentType; import de.igslandstuhl.database.server.webserver.Cookie; import de.igslandstuhl.database.server.webserver.NoWebResourceException; import de.igslandstuhl.database.server.webserver.Status; +import de.igslandstuhl.database.server.webserver.access.AccessManager; import de.igslandstuhl.database.server.webserver.handlers.HttpHandler; import de.igslandstuhl.database.server.webserver.requests.HttpRequest; import de.igslandstuhl.database.server.webserver.requests.PostRequest; diff --git a/src/test/java/de/igslandstuhl/database/server/webserver/AccessManagerTest.java b/src/test/java/de/igslandstuhl/database/server/webserver/AccessManagerTest.java index 8a18377..d9815a0 100644 --- a/src/test/java/de/igslandstuhl/database/server/webserver/AccessManagerTest.java +++ b/src/test/java/de/igslandstuhl/database/server/webserver/AccessManagerTest.java @@ -11,6 +11,7 @@ import org.junit.jupiter.api.Test; import de.igslandstuhl.database.api.User; +import de.igslandstuhl.database.server.webserver.access.AccessManager; public class AccessManagerTest { private User teacher; From e6563c9a4f2293ab3e4e9cb2d4523b6c42a4eccc Mon Sep 17 00:00:00 2001 From: Schlaumeier5 Date: Mon, 6 Jul 2026 13:27:16 +0200 Subject: [PATCH 5/7] Added javadoc to AccessState --- .../database/server/webserver/access/AccessState.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/main/java/de/igslandstuhl/database/server/webserver/access/AccessState.java b/src/main/java/de/igslandstuhl/database/server/webserver/access/AccessState.java index c3d40a0..8ccc019 100644 --- a/src/main/java/de/igslandstuhl/database/server/webserver/access/AccessState.java +++ b/src/main/java/de/igslandstuhl/database/server/webserver/access/AccessState.java @@ -1,5 +1,8 @@ package de.igslandstuhl.database.server.webserver.access; +/** + * AccessState represents the different states of access a user can have to a resource. + */ public enum AccessState { /** * The user is not logged in and does not have access to the resource. From ca2adb6d10f042e7883e6640a760df2d01a3368d Mon Sep 17 00:00:00 2001 From: Schlaumeier5 Date: Mon, 6 Jul 2026 13:56:23 +0200 Subject: [PATCH 6/7] Now using github packages as Repo for prereleases --- .github/workflows/publish.yml | 18 ++++++++++++++---- build.gradle.kts | 12 ++++++++++++ 2 files changed, 26 insertions(+), 4 deletions(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 8c4a6e3..d10dc41 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -1,13 +1,16 @@ # .github/workflows/publish.yml - name: Publish on: release: types: [released, prereleased] + jobs: publish: name: Release build and publish runs-on: macOS-latest + permissions: + contents: read + packages: write steps: - name: Check out code uses: actions/checkout@v4 @@ -16,12 +19,19 @@ jobs: with: distribution: 'zulu' java-version: 21 - - name: Publish to MavenCentral + - name: Publish SNAPSHOT to snapshot repo + if: github.event.action == 'prereleased' + run: ./gradlew publishAllPublicationsToSnapshotsRepository --no-configuration-cache + env: + GITHUB_ACTOR: ${{ github.actor }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Publish release to Maven Central + if: github.event.action == 'released' run: ./gradlew publishToMavenCentral --no-configuration-cache env: ORG_GRADLE_PROJECT_mavenCentralUsername: ${{ secrets.MAVEN_CENTRAL_USERNAME }} ORG_GRADLE_PROJECT_mavenCentralPassword: ${{ secrets.MAVEN_CENTRAL_PASSWORD }} ORG_GRADLE_PROJECT_signingInMemoryKeyId: ${{ secrets.SIGNING_KEY_ID }} ORG_GRADLE_PROJECT_signingInMemoryKeyPassword: ${{ secrets.SIGNING_PASSWORD }} - ORG_GRADLE_PROJECT_signingInMemoryKey: ${{ secrets.GPG_KEY_CONTENTS }} - + ORG_GRADLE_PROJECT_signingInMemoryKey: ${{ secrets.GPG_KEY_CONTENTS }} \ No newline at end of file diff --git a/build.gradle.kts b/build.gradle.kts index 396e1ed..2039ca3 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -96,4 +96,16 @@ mavenPublishing { } tasks.withType().configureEach { dependsOn(tasks.withType()) +} +publishing { + repositories { + maven { + name = "snapshots" + url = uri("https://maven.pkg.github.com/Learn-Monitor/student-database") + credentials { + username = System.getenv("GITHUB_ACTOR") ?: findProperty("gpr.user") as String? + password = System.getenv("GITHUB_TOKEN") ?: findProperty("gpr.key") as String? + } + } + } } \ No newline at end of file From 1430c601f0d93c8f6c579a87aa5dfa39ce6fda83 Mon Sep 17 00:00:00 2001 From: Schlaumeier5 Date: Mon, 6 Jul 2026 13:57:24 +0200 Subject: [PATCH 7/7] New versioning system for snapshots: sYYMMb(build) --- build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle.kts b/build.gradle.kts index 2039ca3..165af06 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -7,7 +7,7 @@ plugins { group = "io.github.learn-monitor" -version = "v2.0.0-SNAPSHOT-3" +version = "s2607b1-SNAPSHOT" application { mainClass.set("de.igslandstuhl.database.Application")