diff --git a/.github/workflows/maven.yml b/.github/workflows/maven.yml
index 657189d5..22b7bdd6 100644
--- a/.github/workflows/maven.yml
+++ b/.github/workflows/maven.yml
@@ -21,10 +21,10 @@ jobs:
steps:
- uses: actions/checkout@v4
- - name: Set up JDK 17
+ - name: Set up JDK 25
uses: actions/setup-java@v4
with:
- java-version: '17'
+ java-version: '25'
distribution: 'temurin'
cache: maven
- name: Build with Maven
diff --git a/.gitignore b/.gitignore
index c3d37923..85e0c1a5 100644
--- a/.gitignore
+++ b/.gitignore
@@ -18,3 +18,4 @@
# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
hs_err_pid*
+.vscode
diff --git a/cosmo-api/pom.xml b/cosmo-api/pom.xml
index 6447b0cc..c805bab9 100644
--- a/cosmo-api/pom.xml
+++ b/cosmo-api/pom.xml
@@ -4,7 +4,7 @@
net.oneandone.cosmo
cosmo-multimodule
- 7.1.2-SNAPSHOT
+ 8.0.0-SNAPSHOT
cosmo-api
diff --git a/cosmo-core/pom.xml b/cosmo-core/pom.xml
index 25248950..9d68cf48 100644
--- a/cosmo-core/pom.xml
+++ b/cosmo-core/pom.xml
@@ -12,7 +12,7 @@
net.oneandone.cosmo
cosmo-multimodule
- 7.1.2-SNAPSHOT
+ 8.0.0-SNAPSHOT
4.0.0
cosmo-core
@@ -32,6 +32,11 @@
org.springframework.boot
spring-boot-starter-data-jpa
+
+
+ org.springframework.boot
+ spring-boot-starter-validation
+
org.springframework.boot
diff --git a/cosmo-core/src/main/java/org/unitedinternet/cosmo/acegisecurity/providers/ticket/ExtraTicketProcessingFilter.java b/cosmo-core/src/main/java/org/unitedinternet/cosmo/acegisecurity/providers/ticket/ExtraTicketProcessingFilter.java
deleted file mode 100644
index 1f508aa6..00000000
--- a/cosmo-core/src/main/java/org/unitedinternet/cosmo/acegisecurity/providers/ticket/ExtraTicketProcessingFilter.java
+++ /dev/null
@@ -1,153 +0,0 @@
-/*
- * Copyright 2008 Open Source Applications Foundation
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.unitedinternet.cosmo.acegisecurity.providers.ticket;
-
-import java.io.IOException;
-import java.util.Enumeration;
-import java.util.HashSet;
-import java.util.Set;
-
-import jakarta.servlet.Filter;
-import jakarta.servlet.FilterChain;
-import jakarta.servlet.FilterConfig;
-import jakarta.servlet.ServletException;
-import jakarta.servlet.ServletRequest;
-import jakarta.servlet.ServletResponse;
-import jakarta.servlet.http.HttpServletRequest;
-
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.springframework.stereotype.Component;
-import org.unitedinternet.cosmo.dao.ContentDao;
-import org.unitedinternet.cosmo.model.Ticket;
-import org.unitedinternet.cosmo.security.CosmoSecurityManager;
-
-/**
- * Servlet filter that examines request for additional ticket keys to include in the security context.
- */
-@Component
-public class ExtraTicketProcessingFilter implements Filter {
- private static final Logger LOG = LoggerFactory.getLogger(ExtraTicketProcessingFilter.class);
-
- public static final String TICKET_HEADER = "X-Cosmo-Ticket";
- public static final String MORSE_CODE_TICKET_HEADER = "X-MorseCode-Ticket";
- public static final String PARAM_TICKET = "ticket";
-
- private ContentDao contentDao = null;
- private CosmoSecurityManager securityManager = null;
-
- /**
- *
- * @param contentDao
- * @param securityManager
- */
- public ExtraTicketProcessingFilter(ContentDao contentDao, CosmoSecurityManager securityManager) {
- super();
- this.contentDao = contentDao;
- this.securityManager = securityManager;
- }
-
- /**
- * Does nothing - we use IoC lifecycle methods instead
- *
- * @param filterConfig
- * The filter config.
- * @throws ServletException
- * - if something is wrong this exception is thrown.
- */
- public void init(FilterConfig filterConfig) throws ServletException {
- }
-
- /**
- * Examines HTTP servlet requests for extra ticket keys, and register them with the security manager.
- *
- * @param request
- * The servlet request.
- * @param response
- * The servlet response.
- * @param chain
- * The filter chain.
- * @throws IOException
- * - if something is wrong this exception is thrown.
- * @throws ServletException
- * - if something is wrong this exception is thrown.
- */
- public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
- throws IOException, ServletException {
- if (!(request instanceof HttpServletRequest)) {
- throw new IllegalStateException("Received request is of type [" + request.getClass().getName()
- + "]. Expected type: [" + HttpServletRequest.class.getName() + "].");
- }
- HttpServletRequest httpRequest = (HttpServletRequest) request;
-
- if (LOG.isDebugEnabled()) {
- LOG.debug("looking for tickets in request headers");
- }
-
- Set tickets = new HashSet();
-
- // Look for tickets in header in the format:
- // X-Cosmo-Ticket: slkdfjsdf, slkdjfsdf, sdlfkjsfsdf
- Enumeration ticketKeys = httpRequest.getHeaders(TICKET_HEADER);
- while (ticketKeys.hasMoreElements()) {
- String ticketKeyValue = ticketKeys.nextElement();
- for (String ticketKey : ticketKeyValue.split(",")) {
- Ticket ticket = contentDao.findTicket(ticketKey.trim());
- if (ticket != null) {
- tickets.add(ticket);
- }
- }
- }
-
- // Look for tickets in header in the format:
- // X-MorseCode-Ticket: slkdfjsdf, slkdjfsdf, sdlfkjsfsdf
- ticketKeys = httpRequest.getHeaders(MORSE_CODE_TICKET_HEADER);
- while (ticketKeys.hasMoreElements()) {
- String ticketKeyValue = ticketKeys.nextElement();
- for (String ticketKey : ticketKeyValue.split(",")) {
- Ticket ticket = contentDao.findTicket(ticketKey.trim());
- if (ticket != null) {
- tickets.add(ticket);
- }
- }
- }
-
- // look for tickets in request parameters
- String[] paramTicketKeys = httpRequest.getParameterValues(PARAM_TICKET);
- if (paramTicketKeys != null) {
- for (String ticketKey : paramTicketKeys) {
- Ticket ticket = contentDao.findTicket(ticketKey);
- if (ticket != null) {
- tickets.add(ticket);
- }
- }
- }
-
- try {
- // register tickets
- securityManager.registerTickets(tickets);
- chain.doFilter(request, response);
- } finally {
- // clear tickets
- securityManager.unregisterTickets();
- }
- }
-
- @Override
- public void destroy() {
- // Nothing to do
- }
-}
diff --git a/cosmo-core/src/main/java/org/unitedinternet/cosmo/acegisecurity/providers/ticket/TicketAuthenticationConverter.java b/cosmo-core/src/main/java/org/unitedinternet/cosmo/acegisecurity/providers/ticket/TicketAuthenticationConverter.java
new file mode 100644
index 00000000..08c72086
--- /dev/null
+++ b/cosmo-core/src/main/java/org/unitedinternet/cosmo/acegisecurity/providers/ticket/TicketAuthenticationConverter.java
@@ -0,0 +1,33 @@
+package org.unitedinternet.cosmo.acegisecurity.providers.ticket;
+
+import java.util.Set;
+
+import org.jspecify.annotations.Nullable;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.web.authentication.AuthenticationConverter;
+import org.unitedinternet.cosmo.server.ServerUtils;
+
+import jakarta.servlet.http.HttpServletRequest;
+
+public class TicketAuthenticationConverter implements AuthenticationConverter {
+
+ private static final String SLASH = "/";
+
+ @Override
+ public @Nullable Authentication convert(HttpServletRequest httpRequest) {
+
+ Set keys = ServerUtils.findTicketKeys(httpRequest);
+
+ if (!keys.isEmpty()) {
+ String path = httpRequest.getPathInfo();
+ if (path == null || path.isEmpty()) {
+ path = SLASH;
+ }
+ if (!path.equals(SLASH) && path.endsWith(SLASH)) {
+ path = path.substring(0, path.length() - 1);
+ }
+ return new TicketAuthenticationToken(path, keys);
+ }
+ return null;
+ }
+}
\ No newline at end of file
diff --git a/cosmo-core/src/main/java/org/unitedinternet/cosmo/acegisecurity/providers/ticket/TicketProcessingFilter.java b/cosmo-core/src/main/java/org/unitedinternet/cosmo/acegisecurity/providers/ticket/TicketProcessingFilter.java
deleted file mode 100644
index 8ceb5134..00000000
--- a/cosmo-core/src/main/java/org/unitedinternet/cosmo/acegisecurity/providers/ticket/TicketProcessingFilter.java
+++ /dev/null
@@ -1,124 +0,0 @@
-/*
- * Copyright 2005-2006 Open Source Applications Foundation
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.unitedinternet.cosmo.acegisecurity.providers.ticket;
-
-import java.io.IOException;
-import java.util.Set;
-
-import jakarta.servlet.Filter;
-import jakarta.servlet.FilterChain;
-import jakarta.servlet.FilterConfig;
-import jakarta.servlet.ServletException;
-import jakarta.servlet.ServletRequest;
-import jakarta.servlet.ServletResponse;
-import jakarta.servlet.http.HttpServletRequest;
-
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.springframework.security.core.Authentication;
-import org.springframework.security.core.context.SecurityContext;
-import org.springframework.security.core.context.SecurityContextHolder;
-import org.springframework.stereotype.Component;
-import org.unitedinternet.cosmo.server.ServerUtils;
-
-/**
- * Servlet filter that populates the
- * {@link org.springframework.security.ContextHolder} with a
- * {@link TicketAuthenticationToken} if needed.
- */
-@Component
-public class TicketProcessingFilter implements Filter {
-
- private static final Logger LOG = LoggerFactory.getLogger(TicketProcessingFilter.class);
-
- // Filter methods
-
- /**
- * Does nothing - we use IoC lifecycle methods instead.
- * @param filterConfig The filter config.
- * @throws ServletException - if something is wrong this exception is thrown.
- */
- public void init(FilterConfig filterConfig) throws ServletException {
- }
-
- /**
- * Examines HTTP servlet requests for ticket keys, creating a
- * {@link TicketAuthenticationToken} if any are found.
- *
- * Tokens are created with the
- * {@link #createAuthentication(String, Set)} method.
- *
- * A token's path is the path info of the request URI less any
- * trailing "/", or "/" if the URI represents the root resource.
- * @param request The servlet request.
- * @param response The servlet response.
- * @param chain The filter chain.
- * @throws IOException - if something is wrong this exception is thrown.
- * @throws ServletException - if something is wrong this exception is thrown.
- */
- public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
- throws IOException, ServletException {
- SecurityContext sc = SecurityContextHolder.getContext();
- if (sc.getAuthentication() == null && request instanceof HttpServletRequest) {
- HttpServletRequest httpRequest = (HttpServletRequest) request;
- Set keys = ServerUtils.findTicketKeys(httpRequest);
-
- if (! keys.isEmpty()) {
- String path = httpRequest.getPathInfo();
- if (path == null || path.equals("")) {
- path = "/";
- }
- if (! path.equals("/") && path.endsWith("/")) {
- path = path.substring(0, path.length()-1);
- }
- // XXX: refactor so this path prefix is not
- // hardcoded .. or look at making security happen
- // after url-rewriting, not before
- if (path.startsWith("/atom/1.0")) {
- path = path.substring(9);
- }
-
- Authentication token = createAuthentication(path, keys);
- sc.setAuthentication(token);
- if (LOG.isDebugEnabled()) {
- LOG.debug("Replaced ContextHolder with ticket token: " +
- sc.getAuthentication());
- }
- }
- }
-
- chain.doFilter(request, response);
- }
-
- /**
- * Does nothing - we use IoC lifecycle methods instead
- */
- public void destroy() {
- }
-
- // our methods
-
- /**
- * Returns a {@link TicketAuthenticationToken} for the given
- * path and ticket keys.
- * @param path The given path.
- * @param keys The ticket keys.
- * @return The authentication.
- */
- protected Authentication createAuthentication(String path, Set keys) {
- return new TicketAuthenticationToken(path, keys);
- }
-}
diff --git a/cosmo-core/src/main/java/org/unitedinternet/cosmo/acegisecurity/providers/ticket/TicketVoter.java b/cosmo-core/src/main/java/org/unitedinternet/cosmo/acegisecurity/providers/ticket/TicketVoter.java
deleted file mode 100644
index b60fd086..00000000
--- a/cosmo-core/src/main/java/org/unitedinternet/cosmo/acegisecurity/providers/ticket/TicketVoter.java
+++ /dev/null
@@ -1,101 +0,0 @@
-/*
- * Copyright 2005-2006 Open Source Applications Foundation
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.unitedinternet.cosmo.acegisecurity.providers.ticket;
-
-import java.util.Collection;
-
-import org.springframework.security.access.AccessDecisionVoter;
-import org.springframework.security.access.ConfigAttribute;
-import org.springframework.security.core.Authentication;
-import org.springframework.security.web.FilterInvocation;
-import org.unitedinternet.cosmo.dav.CaldavMethodType;
-import org.unitedinternet.cosmo.model.Ticket;
-import org.unitedinternet.cosmo.model.TicketType;
-
-/**
- * Votes affirmatively if the authenticated principal is a ticket and
- * the ticket has the privilege required by the requested WebDAV
- * method.
- *
- * This is a temporary approach until a full ACL system is in place.
- */
-public class TicketVoter implements AccessDecisionVoter