You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@tomee.apache.org by rz...@apache.org on 2026/10/07 18:15:26 UTC

(tomee) branch tomee-10.x updated (e1c3f7c863 -> 7a7f324552)

This is an automated email from the ASF dual-hosted git repository.

rzo1 pushed a change to branch tomee-10.x
in repository https://gitbox.apache.org/repos/asf/tomee.git


    from e1c3f7c863 Minor: Regenerated BOMs for b9d93badb4ae9ddc933a2d2fcc5286cdbc4b8234 (#3038)
     new b2d60dc4bf create Files.tmpdir() directories atomically (#3062)
     new 8837cb6715 protect all http methods on jax-ws endpoint security constraint (#3061)
     new 55c9cd3da3 reject negative mp.jwt clock skew instead of disabling exp checks (#3060)
     new a32a7a3a6f reject null passwords in SQLLoginModule (#3059)
     new 7a7f324552 restore mp-jwt security state when the filter chain throws (#3058)

The 5 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 .../openejb/core/security/jaas/SQLLoginModule.java |   7 +-
 .../openejb/core/security/SQLLoginModuleTest.java  |  26 +++
 .../main/java/org/apache/openejb/loader/Files.java |  12 +-
 .../java/org/apache/openejb/loader/FilesTest.java  |  26 ++-
 .../apache/tomee/microprofile/jwt/MPJWTFilter.java |  40 +++--
 .../jwt/config/JWTAuthConfigurationProperties.java |  10 +-
 .../tomee/microprofile/jwt/ClockSkewTest.java      | 102 +++++++++++
 .../jwt/MPJWTFilterExitWebAppTest.java             | 186 +++++++++++++++++++++
 .../config/JWTAuthConfigurationPropertiesTest.java |  27 +--
 .../apache/tomee/webservices/TomcatWsRegistry.java |  44 +++--
 .../tomee/webservices/TomcatWsRegistryTest.java    |  53 ++++++
 11 files changed, 477 insertions(+), 56 deletions(-)
 create mode 100644 mp-jwt/src/test/java/org/apache/tomee/microprofile/jwt/ClockSkewTest.java
 create mode 100644 mp-jwt/src/test/java/org/apache/tomee/microprofile/jwt/MPJWTFilterExitWebAppTest.java
 copy server/openejb-server/src/test/java/org/apache/openejb/server/FilteredServiceManagerTest.java => mp-jwt/src/test/java/org/apache/tomee/microprofile/jwt/config/JWTAuthConfigurationPropertiesTest.java (51%)
 create mode 100644 tomee/tomee-webservices/src/test/java/org/apache/tomee/webservices/TomcatWsRegistryTest.java


(tomee) 04/05: reject null passwords in SQLLoginModule (#3059)

Posted by rz...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

rzo1 pushed a commit to branch tomee-10.x
in repository https://gitbox.apache.org/repos/asf/tomee.git

commit a32a7a3a6f38ba36b2249a4de2c46dd936752ebb
Author: Richard Zowalla <rz...@apache.org>
AuthorDate: Wed Oct 7 17:34:18 2026 +0200

    reject null passwords in SQLLoginModule (#3059)
    
    A NULL stored password combined with no provided password must not
    authenticate.
    
    (cherry picked from commit fa933a7e63c9da10880a1f90a65c78bacb609f28)
---
 .../openejb/core/security/jaas/SQLLoginModule.java |  7 +++---
 .../openejb/core/security/SQLLoginModuleTest.java  | 26 ++++++++++++++++++++++
 2 files changed, 29 insertions(+), 4 deletions(-)

diff --git a/container/openejb-core/src/main/java/org/apache/openejb/core/security/jaas/SQLLoginModule.java b/container/openejb-core/src/main/java/org/apache/openejb/core/security/jaas/SQLLoginModule.java
index 42878455e8..aeaa1091b1 100644
--- a/container/openejb-core/src/main/java/org/apache/openejb/core/security/jaas/SQLLoginModule.java
+++ b/container/openejb-core/src/main/java/org/apache/openejb/core/security/jaas/SQLLoginModule.java
@@ -349,15 +349,14 @@ public class SQLLoginModule implements LoginModule {
      * This method checks if the provided password is correct. The original
      * password may have been digested.
      *
+     * A missing stored password (NULL column) or a missing provided password
+     * always fails: absence of a credential must never authenticate.
+     *
      * @param real     Original password in digested form if applicable
      * @param provided User provided password in clear text
      * @return true If the password is correct
      */
     private boolean checkPassword(final String real, final String provided) {
-        if (real == null && provided == null) {
-            return true;
-        }
-
         if (real == null || provided == null) {
             return false;
         }
diff --git a/container/openejb-core/src/test/java/org/apache/openejb/core/security/SQLLoginModuleTest.java b/container/openejb-core/src/test/java/org/apache/openejb/core/security/SQLLoginModuleTest.java
index 07764082c8..26031e135d 100644
--- a/container/openejb-core/src/test/java/org/apache/openejb/core/security/SQLLoginModuleTest.java
+++ b/container/openejb-core/src/test/java/org/apache/openejb/core/security/SQLLoginModuleTest.java
@@ -33,6 +33,7 @@ import java.sql.Connection;
 import java.sql.Driver;
 import java.sql.PreparedStatement;
 import java.sql.SQLException;
+import java.sql.Types;
 import java.util.Properties;
 
 import static org.junit.Assert.assertEquals;
@@ -70,6 +71,10 @@ public class SQLLoginModuleTest {
         st.setString(1, "daniel");
         st.setString(2, "password");
         st.execute();
+        // account without a stored password
+        st.setString(1, "nopassword");
+        st.setNull(2, Types.VARCHAR);
+        st.execute();
         st.close();
 
         // Add roles (groups)
@@ -138,4 +143,25 @@ public class SQLLoginModuleTest {
         context.login();
     }
 
+    @Test(expected = FailedLoginException.class)
+    public void testNullStoredAndNullProvidedPasswordLogin() throws LoginException {
+        final LoginContext context = new LoginContext("SQLLogin",
+            new UsernamePasswordCallbackHandler("nopassword", null));
+        context.login();
+    }
+
+    @Test(expected = FailedLoginException.class)
+    public void testNullStoredPasswordLogin() throws LoginException {
+        final LoginContext context = new LoginContext("SQLLogin",
+            new UsernamePasswordCallbackHandler("nopassword", "anything"));
+        context.login();
+    }
+
+    @Test(expected = FailedLoginException.class)
+    public void testNullProvidedPasswordLogin() throws LoginException {
+        final LoginContext context = new LoginContext("SQLLogin",
+            new UsernamePasswordCallbackHandler("jonathan", null));
+        context.login();
+    }
+
 }


(tomee) 05/05: restore mp-jwt security state when the filter chain throws (#3058)

Posted by rz...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

rzo1 pushed a commit to branch tomee-10.x
in repository https://gitbox.apache.org/repos/asf/tomee.git

commit 7a7f32455223c2e46efad5f6ae90249ff74a65db
Author: Richard Zowalla <rz...@apache.org>
AuthorDate: Wed Oct 7 17:34:32 2026 +0200

    restore mp-jwt security state when the filter chain throws (#3058)
    
    Move exitWebApp into a finally block so the caller identity and run-as
    subject are not left on the pooled thread when the chain throws.
    
    (cherry picked from commit 88f709bc993c89cb45aa2fe6e7d0ddd5a7a6ee2f)
---
 .../apache/tomee/microprofile/jwt/MPJWTFilter.java |  31 +++-
 .../jwt/MPJWTFilterExitWebAppTest.java             | 186 +++++++++++++++++++++
 2 files changed, 209 insertions(+), 8 deletions(-)

diff --git a/mp-jwt/src/main/java/org/apache/tomee/microprofile/jwt/MPJWTFilter.java b/mp-jwt/src/main/java/org/apache/tomee/microprofile/jwt/MPJWTFilter.java
index 95a739b0e7..170bcabcef 100644
--- a/mp-jwt/src/main/java/org/apache/tomee/microprofile/jwt/MPJWTFilter.java
+++ b/mp-jwt/src/main/java/org/apache/tomee/microprofile/jwt/MPJWTFilter.java
@@ -80,6 +80,8 @@ public class MPJWTFilter implements Filter {
 
     private static final Logger VALIDATION = Logger.getInstance(JWTLogCategories.VALIDATION, MPJWTFilter.class);
 
+    static final String PRE_LOGIN_STATE = "MP_JWT_PRE_LOGIN_STATE";
+
     @Override
     public void init(final FilterConfig filterConfig) throws ServletException {
     }
@@ -98,13 +100,6 @@ public class MPJWTFilter implements Filter {
         try {
             final MPJWTServletRequestWrapper wrappedRequest = new MPJWTServletRequestWrapper(httpServletRequest, authContextInfo.get());
             chain.doFilter(wrappedRequest, response);
-
-            Object state = request.getAttribute("MP_JWT_PRE_LOGIN_STATE");
-            final SecurityService securityService = SystemInstance.get().getComponent(SecurityService.class);
-            if (TomcatSecurityService.class.isInstance(securityService) && state != null) {
-                final TomcatSecurityService tomcatSecurityService = TomcatSecurityService.class.cast(securityService);
-                tomcatSecurityService.exitWebApp(state);
-            }
         } catch (final Exception e) {
             // this is an alternative to the @Provider bellow which requires registration on the fly
             // or users to add it into their webapp for scanning or into the Application itself
@@ -117,6 +112,26 @@ public class MPJWTFilter implements Filter {
             } else {
                 throw e;
             }
+        } finally {
+            // token validation pushes the caller identity (and the run-as subject, if any) onto
+            // the current thread; restore it on every exit path so a failing request does not
+            // leak its security context to the next request served by this pooled thread
+            exitWebApp(request);
+        }
+    }
+
+    private static void exitWebApp(final ServletRequest request) {
+        final Object state = request.getAttribute(PRE_LOGIN_STATE);
+        if (state == null) {
+            return;
+        }
+
+        // remove it first so the state is never restored twice for the same request
+        request.removeAttribute(PRE_LOGIN_STATE);
+
+        final SecurityService securityService = SystemInstance.get().getComponent(SecurityService.class);
+        if (TomcatSecurityService.class.isInstance(securityService)) {
+            TomcatSecurityService.class.cast(securityService).exitWebApp(state);
         }
     }
 
@@ -362,7 +377,7 @@ public class MPJWTFilter implements Filter {
                 final org.apache.catalina.connector.Request req = OpenEJBSecurityListener.requests.get();
                 Object state = tomcatSecurityService.enterWebApp(req.getWrapper().getRealm(), jsonWebToken, req.getWrapper().getRunAs());
 
-                request.setAttribute("MP_JWT_PRE_LOGIN_STATE", state);
+                request.setAttribute(PRE_LOGIN_STATE, state);
             }
 
             // TODO Also check if it is an async request and add a listener to close off the state
diff --git a/mp-jwt/src/test/java/org/apache/tomee/microprofile/jwt/MPJWTFilterExitWebAppTest.java b/mp-jwt/src/test/java/org/apache/tomee/microprofile/jwt/MPJWTFilterExitWebAppTest.java
new file mode 100644
index 0000000000..675372e1cb
--- /dev/null
+++ b/mp-jwt/src/test/java/org/apache/tomee/microprofile/jwt/MPJWTFilterExitWebAppTest.java
@@ -0,0 +1,186 @@
+/*
+ *     Licensed to the Apache Software Foundation (ASF) under one or more
+ *     contributor license agreements.  See the NOTICE file distributed with
+ *     this work for additional information regarding copyright ownership.
+ *     The ASF licenses this file to You 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.apache.tomee.microprofile.jwt;
+
+import jakarta.enterprise.inject.Instance;
+import jakarta.servlet.FilterChain;
+import jakarta.servlet.ServletException;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+import org.apache.catalina.core.StandardServer;
+import org.apache.openejb.loader.SystemInstance;
+import org.apache.openejb.spi.SecurityService;
+import org.apache.tomee.catalina.TomcatSecurityService;
+import org.apache.tomee.loader.TomcatHelper;
+import org.apache.tomee.microprofile.jwt.config.JWTAuthConfiguration;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Proxy;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+public class MPJWTFilterExitWebAppTest {
+
+    private RecordingSecurityService securityService;
+
+    @Before
+    public void setUp() {
+        SystemInstance.reset();
+        TomcatHelper.setServer(new StandardServer());
+        securityService = new RecordingSecurityService();
+        SystemInstance.get().setComponent(SecurityService.class, securityService);
+    }
+
+    @After
+    public void tearDown() {
+        securityService.clearRunAsStack();
+        SystemInstance.reset();
+    }
+
+    @Test
+    public void exitWebAppRunsWhenChainThrows() throws Exception {
+        final Map<String, Object> attributes = new HashMap<>();
+        final HttpServletRequest request = request(attributes);
+        final IllegalStateException failure = new IllegalStateException("boom");
+
+        final FilterChain chain = (req, res) -> {
+            enterWebApp(req);
+            throw failure;
+        };
+
+        try {
+            filter().doFilter(request, response(), chain);
+            fail("the chain exception must be propagated");
+        } catch (final IllegalStateException e) {
+            assertSame(failure, e);
+        }
+
+        assertExited(attributes);
+    }
+
+    @Test
+    public void exitWebAppRunsWhenChainThrowsCheckedException() throws Exception {
+        final Map<String, Object> attributes = new HashMap<>();
+        final HttpServletRequest request = request(attributes);
+
+        final FilterChain chain = (req, res) -> {
+            enterWebApp(req);
+            throw new ServletException("boom");
+        };
+
+        try {
+            filter().doFilter(request, response(), chain);
+            fail("the chain exception must be propagated");
+        } catch (final ServletException e) {
+            assertEquals("boom", e.getMessage());
+        }
+
+        assertExited(attributes);
+    }
+
+    @Test
+    public void exitWebAppRunsOnSuccess() throws Exception {
+        final Map<String, Object> attributes = new HashMap<>();
+        final HttpServletRequest request = request(attributes);
+
+        filter().doFilter(request, response(), (req, res) -> enterWebApp(req));
+
+        assertExited(attributes);
+    }
+
+    private void enterWebApp(final jakarta.servlet.ServletRequest req) {
+        // simulates what ValidateJSonWebToken does once the token has been validated
+        final Object state = securityService.enterWebApp(null, null, "admin");
+        req.setAttribute(MPJWTFilter.PRE_LOGIN_STATE, state);
+        assertEquals(1, securityService.runAsStackSize());
+    }
+
+    private void assertExited(final Map<String, Object> attributes) {
+        assertEquals(1, securityService.exited.size());
+        assertEquals(0, securityService.runAsStackSize());
+        assertNull(attributes.get(MPJWTFilter.PRE_LOGIN_STATE));
+    }
+
+    private static MPJWTFilter filter() throws Exception {
+        final JWTAuthConfiguration configuration = new JWTAuthConfiguration(LinkedHashMap::new, "https://server.example.com",
+                false, new String[0], LinkedHashMap::new, "Authorization", null, null, null, null, 0);
+
+        final Instance<?> instance = (Instance<?>) Proxy.newProxyInstance(
+                MPJWTFilterExitWebAppTest.class.getClassLoader(), new Class<?>[]{Instance.class},
+                (proxy, method, args) -> switch (method.getName()) {
+                    case "isUnsatisfied" -> false;
+                    case "get" -> configuration;
+                    default -> throw new UnsupportedOperationException(method.getName());
+                });
+
+        final MPJWTFilter filter = new MPJWTFilter();
+        final Field field = MPJWTFilter.class.getDeclaredField("authContextInfo");
+        field.setAccessible(true);
+        field.set(filter, instance);
+        return filter;
+    }
+
+    private static HttpServletRequest request(final Map<String, Object> attributes) {
+        return (HttpServletRequest) Proxy.newProxyInstance(
+                MPJWTFilterExitWebAppTest.class.getClassLoader(), new Class<?>[]{HttpServletRequest.class},
+                (proxy, method, args) -> switch (method.getName()) {
+                    case "getAttribute" -> attributes.get((String) args[0]);
+                    case "setAttribute" -> attributes.put((String) args[0], args[1]);
+                    case "removeAttribute" -> attributes.remove((String) args[0]);
+                    default -> throw new UnsupportedOperationException(method.getName());
+                });
+    }
+
+    private static HttpServletResponse response() {
+        return (HttpServletResponse) Proxy.newProxyInstance(
+                MPJWTFilterExitWebAppTest.class.getClassLoader(), new Class<?>[]{HttpServletResponse.class},
+                (proxy, method, args) -> {
+                    throw new UnsupportedOperationException(method.getName());
+                });
+    }
+
+    private static class RecordingSecurityService extends TomcatSecurityService {
+
+        private final List<Object> exited = new ArrayList<>();
+
+        @Override
+        public void exitWebApp(final Object state) {
+            exited.add(state);
+            super.exitWebApp(state);
+        }
+
+        int runAsStackSize() {
+            return RUN_AS_STACK.get().size();
+        }
+
+        void clearRunAsStack() {
+            RUN_AS_STACK.remove();
+        }
+    }
+}


(tomee) 02/05: protect all http methods on jax-ws endpoint security constraint (#3061)

Posted by rz...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

rzo1 pushed a commit to branch tomee-10.x
in repository https://gitbox.apache.org/repos/asf/tomee.git

commit 8837cb67151542a9bc7cac8488e581621f2d6a04
Author: Richard Zowalla <rz...@apache.org>
AuthorDate: Wed Oct 7 17:33:21 2026 +0200

    protect all http methods on jax-ws endpoint security constraint (#3061)
    
    The generated constraint only covered GET and POST. Cover every method
    and deny uncovered methods on contexts TomEE creates for the endpoint.
    
    (cherry picked from commit ed25e7c03a6c7b7ac5d51b12dee572b73b7ef614)
---
 .../apache/tomee/webservices/TomcatWsRegistry.java | 44 +++++++++++-------
 .../tomee/webservices/TomcatWsRegistryTest.java    | 53 ++++++++++++++++++++++
 2 files changed, 80 insertions(+), 17 deletions(-)

diff --git a/tomee/tomee-webservices/src/main/java/org/apache/tomee/webservices/TomcatWsRegistry.java b/tomee/tomee-webservices/src/main/java/org/apache/tomee/webservices/TomcatWsRegistry.java
index a4212c0338..523733b010 100644
--- a/tomee/tomee-webservices/src/main/java/org/apache/tomee/webservices/TomcatWsRegistry.java
+++ b/tomee/tomee-webservices/src/main/java/org/apache/tomee/webservices/TomcatWsRegistry.java
@@ -286,23 +286,7 @@ public class TomcatWsRegistry implements WsRegistry {
             context.setLoginConfig(loginConfig);
 
             //Setup a default Security Constraint
-            final String securityRole = SystemInstance.get().getProperty(TOMEE_JAXWS_SECURITY_ROLE_PREFIX + name, "default");
-            for (final String role : securityRole.split(",")) {
-                final SecurityCollection collection = new SecurityCollection();
-                collection.addMethod("GET");
-                collection.addMethod("POST");
-                collection.addPattern("/*");
-                collection.setName(role);
-
-                final SecurityConstraint sc = new SecurityConstraint();
-                sc.addAuthRole("*");
-                sc.addCollection(collection);
-                sc.setAuthConstraint(true);
-                sc.setUserConstraint(transportGuarantee);
-
-                context.addConstraint(sc);
-                context.addSecurityRole(role);
-            }
+            addSecurityConstraints(context, name, transportGuarantee);
 
             //Set the proper authenticator
             if ("BASIC".equals(authMethod)) {
@@ -324,6 +308,32 @@ public class TomcatWsRegistry implements WsRegistry {
         return context;
     }
 
+    /**
+     * Protects the whole endpoint context (TomEE owned, never a user webapp) for every HTTP method.
+     * No method is added to the collection on purpose: a collection without methods covers all verbs,
+     * whereas enumerating some (e.g. GET/POST) would leave the others (PUT, DELETE, ...) unauthenticated.
+     */
+    static void addSecurityConstraints(final Context context, final String name, final String transportGuarantee) {
+        final String securityRole = SystemInstance.get().getProperty(TOMEE_JAXWS_SECURITY_ROLE_PREFIX + name, "default");
+        for (final String role : securityRole.split(",")) {
+            final SecurityCollection collection = new SecurityCollection();
+            collection.addPattern("/*");
+            collection.setName(role);
+
+            final SecurityConstraint sc = new SecurityConstraint();
+            sc.addAuthRole("*");
+            sc.addCollection(collection);
+            sc.setAuthConstraint(true);
+            sc.setUserConstraint(transportGuarantee);
+
+            context.addConstraint(sc);
+            context.addSecurityRole(role);
+        }
+
+        // reject any method a constraint would not cover
+        context.setDenyUncoveredHttpMethods(true);
+    }
+
     private void addServlet(final Container host, final Context context, final String mapping, final HttpListener httpListener, final String path,
                             final List<String> addresses, final boolean fakeDeployment, final String moduleId) {
         // build the servlet
diff --git a/tomee/tomee-webservices/src/test/java/org/apache/tomee/webservices/TomcatWsRegistryTest.java b/tomee/tomee-webservices/src/test/java/org/apache/tomee/webservices/TomcatWsRegistryTest.java
new file mode 100644
index 0000000000..678be4ddda
--- /dev/null
+++ b/tomee/tomee-webservices/src/test/java/org/apache/tomee/webservices/TomcatWsRegistryTest.java
@@ -0,0 +1,53 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.apache.tomee.webservices;
+
+import org.apache.catalina.core.StandardContext;
+import org.apache.tomcat.util.descriptor.web.SecurityCollection;
+import org.apache.tomcat.util.descriptor.web.SecurityConstraint;
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+public class TomcatWsRegistryTest {
+
+    @Test
+    public void securityConstraintCoversAllHttpMethods() {
+        final StandardContext context = new StandardContext();
+
+        TomcatWsRegistry.addSecurityConstraints(context, "ws-test", "CONFIDENTIAL");
+
+        final SecurityConstraint[] constraints = context.findConstraints();
+        assertEquals(1, constraints.length);
+        final SecurityConstraint constraint = constraints[0];
+        assertTrue(constraint.getAuthConstraint());
+        assertEquals("CONFIDENTIAL", constraint.getUserConstraint());
+
+        final SecurityCollection[] collections = constraint.findCollections();
+        assertEquals(1, collections.length);
+        final SecurityCollection collection = collections[0];
+        assertTrue(collection.findPattern("/*"));
+        assertEquals(0, collection.findMethods().length);
+        assertEquals(0, collection.findOmittedMethods().length);
+        for (final String method : new String[]{"GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS", "HEAD", "TRACE", "FOO"}) {
+            assertTrue(method + " must be covered", collection.findMethod(method));
+        }
+
+        assertTrue(context.getDenyUncoveredHttpMethods());
+    }
+}


(tomee) 03/05: reject negative mp.jwt clock skew instead of disabling exp checks (#3060)

Posted by rz...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

rzo1 pushed a commit to branch tomee-10.x
in repository https://gitbox.apache.org/repos/asf/tomee.git

commit 55c9cd3da3328a808a7a0ead88b00eb4248d0460
Author: Richard Zowalla <rz...@apache.org>
AuthorDate: Wed Oct 7 17:33:41 2026 +0200

    reject negative mp.jwt clock skew instead of disabling exp checks (#3060)
    
    A negative skew evaluated tokens at epoch 0, accepting expired tokens.
    Fail deployment on a negative value and clamp to zero in the filter.
    
    (cherry picked from commit 86d5ede3d592bf8ea398379a86a809d8e6609718)
---
 .../apache/tomee/microprofile/jwt/MPJWTFilter.java |   9 +-
 .../jwt/config/JWTAuthConfigurationProperties.java |  10 +-
 .../tomee/microprofile/jwt/ClockSkewTest.java      | 102 +++++++++++++++++++++
 .../config/JWTAuthConfigurationPropertiesTest.java |  44 +++++++++
 4 files changed, 158 insertions(+), 7 deletions(-)

diff --git a/mp-jwt/src/main/java/org/apache/tomee/microprofile/jwt/MPJWTFilter.java b/mp-jwt/src/main/java/org/apache/tomee/microprofile/jwt/MPJWTFilter.java
index 5108c49d48..95a739b0e7 100644
--- a/mp-jwt/src/main/java/org/apache/tomee/microprofile/jwt/MPJWTFilter.java
+++ b/mp-jwt/src/main/java/org/apache/tomee/microprofile/jwt/MPJWTFilter.java
@@ -49,7 +49,6 @@ import org.jose4j.jwk.JsonWebKey;
 import org.jose4j.jws.AlgorithmIdentifiers;
 import org.jose4j.jwt.JwtClaims;
 import org.jose4j.jwt.MalformedClaimException;
-import org.jose4j.jwt.NumericDate;
 import org.jose4j.jwt.consumer.InvalidJwtException;
 import org.jose4j.jwt.consumer.JwtConsumer;
 import org.jose4j.jwt.consumer.JwtConsumerBuilder;
@@ -425,11 +424,9 @@ public class MPJWTFilter implements Filter {
                 if (authContextInfo.getIssuer() != null) {
                     builder.setExpectedIssuer(authContextInfo.getIssuer());
                 }
-                if (authContextInfo.getClockSkew()>= 0) {
-                    builder.setAllowedClockSkewInSeconds(authContextInfo.getClockSkew());
-                } else {
-                    builder.setEvaluationTime(NumericDate.fromSeconds(0));
-                }
+                // never let a negative skew weaken or disable the exp/nbf/iat time checks
+                final Integer clockSkew = authContextInfo.getClockSkew();
+                builder.setAllowedClockSkewInSeconds(clockSkew == null ? 0 : Math.max(0, clockSkew));
 
                 final Map<String, Key> publicKeys;
                 try {
diff --git a/mp-jwt/src/main/java/org/apache/tomee/microprofile/jwt/config/JWTAuthConfigurationProperties.java b/mp-jwt/src/main/java/org/apache/tomee/microprofile/jwt/config/JWTAuthConfigurationProperties.java
index f96aaba1a8..d04bd2ab9d 100644
--- a/mp-jwt/src/main/java/org/apache/tomee/microprofile/jwt/config/JWTAuthConfigurationProperties.java
+++ b/mp-jwt/src/main/java/org/apache/tomee/microprofile/jwt/config/JWTAuthConfigurationProperties.java
@@ -120,7 +120,15 @@ public class JWTAuthConfigurationProperties {
                 config.getOptionalValue("mp.jwt.decrypt.key.algorithm", String.class).orElse(null),
                 config.getOptionalValue("mp.jwt.verify.publickey.algorithm", String.class).orElse(null),
                 config.getOptionalValue(TOKEN_AGE, Integer.class).orElse(null),
-                config.getOptionalValue(CLOCK_SKEW, Integer.class).orElse(0));
+                validateClockSkew(config.getOptionalValue(CLOCK_SKEW, Integer.class).orElse(0)));
+    }
+
+    static Integer validateClockSkew(final Integer clockSkew) {
+        if (clockSkew != null && clockSkew < 0) {
+            throw new DeploymentException("Invalid " + CLOCK_SKEW + " value: " + clockSkew +
+                    ". The clock skew must be zero or a positive number of seconds.");
+        }
+        return clockSkew;
     }
   
     private Boolean queryAllowExp(){
diff --git a/mp-jwt/src/test/java/org/apache/tomee/microprofile/jwt/ClockSkewTest.java b/mp-jwt/src/test/java/org/apache/tomee/microprofile/jwt/ClockSkewTest.java
new file mode 100644
index 0000000000..51dd309f45
--- /dev/null
+++ b/mp-jwt/src/test/java/org/apache/tomee/microprofile/jwt/ClockSkewTest.java
@@ -0,0 +1,102 @@
+/*
+ *     Licensed to the Apache Software Foundation (ASF) under one or more
+ *     contributor license agreements.  See the NOTICE file distributed with
+ *     this work for additional information regarding copyright ownership.
+ *     The ASF licenses this file to You 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.apache.tomee.microprofile.jwt;
+
+import org.apache.tomee.microprofile.jwt.config.JWTAuthConfiguration;
+import org.jose4j.jwk.RsaJsonWebKey;
+import org.jose4j.jwk.RsaJwkGenerator;
+import org.jose4j.jws.AlgorithmIdentifiers;
+import org.jose4j.jws.JsonWebSignature;
+import org.jose4j.jwt.JwtClaims;
+import org.jose4j.jwt.NumericDate;
+import org.junit.Test;
+
+import java.security.Key;
+import java.util.Collections;
+import java.util.LinkedHashMap;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+
+public class ClockSkewTest {
+
+    @Test
+    public void expiredTokenIsRejectedWithNegativeClockSkew() throws Exception {
+        final RsaJsonWebKey key = RsaJwkGenerator.generateJwk(2048);
+        final long now = NumericDate.now().getValue();
+        final String expired = sign(key, now - 7200, now - 3600);
+
+        assertRejected(expired, config(key, -1));
+        assertRejected(expired, config(key, Integer.MIN_VALUE));
+    }
+
+    @Test
+    public void expiredTokenIsRejectedWithNullClockSkew() throws Exception {
+        final RsaJsonWebKey key = RsaJwkGenerator.generateJwk(2048);
+        final long now = NumericDate.now().getValue();
+
+        assertRejected(sign(key, now - 7200, now - 3600), config(key, null));
+    }
+
+    @Test
+    public void validTokenIsAcceptedWithNegativeClockSkew() throws Exception {
+        final RsaJsonWebKey key = RsaJwkGenerator.generateJwk(2048);
+        final long now = NumericDate.now().getValue();
+
+        assertEquals("alice", MPJWTFilter.ValidateJSonWebToken.parse(sign(key, now - 60, now + 3600), config(key, -1)).getName());
+    }
+
+    @Test
+    public void positiveClockSkewIsHonoured() throws Exception {
+        final RsaJsonWebKey key = RsaJwkGenerator.generateJwk(2048);
+        final long now = NumericDate.now().getValue();
+        final String recentlyExpired = sign(key, now - 600, now - 30);
+
+        assertEquals("alice", MPJWTFilter.ValidateJSonWebToken.parse(recentlyExpired, config(key, 300)).getName());
+        assertRejected(recentlyExpired, config(key, 0));
+    }
+
+    private static void assertRejected(final String token, final JWTAuthConfiguration config) {
+        try {
+            MPJWTFilter.ValidateJSonWebToken.parse(token, config);
+            fail("expired token must be rejected");
+        } catch (final ParseException expected) {
+            // ok
+        }
+    }
+
+    private static String sign(final RsaJsonWebKey key, final long issuedAt, final long expiresAt) throws Exception {
+        final JwtClaims claims = new JwtClaims();
+        claims.setSubject("alice");
+        claims.setIssuer("https://server.example.com");
+        claims.setIssuedAt(NumericDate.fromSeconds(issuedAt));
+        claims.setExpirationTime(NumericDate.fromSeconds(expiresAt));
+
+        final JsonWebSignature jws = new JsonWebSignature();
+        jws.setPayload(claims.toJson());
+        jws.setKey(key.getPrivateKey());
+        jws.setAlgorithmHeaderValue(AlgorithmIdentifiers.RSA_USING_SHA256);
+        return jws.getCompactSerialization();
+    }
+
+    private static JWTAuthConfiguration config(final RsaJsonWebKey key, final Integer clockSkew) {
+        return new JWTAuthConfiguration(
+                () -> Collections.<String, Key>singletonMap(JWTAuthConfiguration.DEFAULT_KEY, key.getPublicKey()),
+                "https://server.example.com", false, new String[0],
+                LinkedHashMap::new, "Authorization", null, null, null, null, clockSkew);
+    }
+}
diff --git a/mp-jwt/src/test/java/org/apache/tomee/microprofile/jwt/config/JWTAuthConfigurationPropertiesTest.java b/mp-jwt/src/test/java/org/apache/tomee/microprofile/jwt/config/JWTAuthConfigurationPropertiesTest.java
new file mode 100644
index 0000000000..842717696c
--- /dev/null
+++ b/mp-jwt/src/test/java/org/apache/tomee/microprofile/jwt/config/JWTAuthConfigurationPropertiesTest.java
@@ -0,0 +1,44 @@
+/*
+ *     Licensed to the Apache Software Foundation (ASF) under one or more
+ *     contributor license agreements.  See the NOTICE file distributed with
+ *     this work for additional information regarding copyright ownership.
+ *     The ASF licenses this file to You 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.apache.tomee.microprofile.jwt.config;
+
+import jakarta.enterprise.inject.spi.DeploymentException;
+import org.junit.Test;
+
+import static org.eclipse.microprofile.jwt.config.Names.CLOCK_SKEW;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+public class JWTAuthConfigurationPropertiesTest {
+
+    @Test
+    public void nonNegativeClockSkewIsAccepted() {
+        assertEquals(Integer.valueOf(0), JWTAuthConfigurationProperties.validateClockSkew(0));
+        assertEquals(Integer.valueOf(60), JWTAuthConfigurationProperties.validateClockSkew(60));
+    }
+
+    @Test
+    public void negativeClockSkewFailsDeployment() {
+        try {
+            JWTAuthConfigurationProperties.validateClockSkew(-1);
+            fail("a negative clock skew must fail the deployment");
+        } catch (final DeploymentException expected) {
+            assertTrue(expected.getMessage().contains(CLOCK_SKEW));
+        }
+    }
+}


(tomee) 01/05: create Files.tmpdir() directories atomically (#3062)

Posted by rz...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

rzo1 pushed a commit to branch tomee-10.x
in repository https://gitbox.apache.org/repos/asf/tomee.git

commit b2d60dc4bf85ed360f5df35715ba94e9e37ef533
Author: Richard Zowalla <rz...@apache.org>
AuthorDate: Wed Oct 7 17:33:06 2026 +0200

    create Files.tmpdir() directories atomically (#3062)
    
    Use Files.createTempDirectory instead of createTempFile/delete/mkdir,
    which raced in a shared temp dir, and get owner-only permissions.
    
    (cherry picked from commit a99e068e7770d91ffc86d7c255dff47d6a78e07c)
---
 .../main/java/org/apache/openejb/loader/Files.java | 12 +++-------
 .../java/org/apache/openejb/loader/FilesTest.java  | 26 +++++++++++++++++++++-
 2 files changed, 28 insertions(+), 10 deletions(-)

diff --git a/container/openejb-loader/src/main/java/org/apache/openejb/loader/Files.java b/container/openejb-loader/src/main/java/org/apache/openejb/loader/Files.java
index cc97b3b1ef..d045a17aab 100644
--- a/container/openejb-loader/src/main/java/org/apache/openejb/loader/Files.java
+++ b/container/openejb-loader/src/main/java/org/apache/openejb/loader/Files.java
@@ -192,7 +192,8 @@ public class Files {
         try {
             File file;
             try {
-                file = File.createTempFile("temp", "dir");
+                // created atomically with owner-only permissions on POSIX file systems
+                file = java.nio.file.Files.createTempDirectory("temp").toFile();
             } catch (final Throwable e) {
                 //Use a local tmp directory
                 final File tmp = new File("tmp");
@@ -203,16 +204,9 @@ public class Files {
                     throw new IOException("Failed to create local tmp directory: " + tmp.getAbsolutePath());
                 }
 
-                file = File.createTempFile("temp", "dir", tmp);
+                file = java.nio.file.Files.createTempDirectory(tmp.toPath(), "temp").toFile();
             }
 
-            hackJDK4715154();
-
-            if (!file.delete()) {
-                throw new IOException("Failed to create temp dir. Delete failed");
-            }
-
-            mkdir(file);
             deleteOnExit(file);
 
             return file;
diff --git a/container/openejb-loader/src/test/java/org/apache/openejb/loader/FilesTest.java b/container/openejb-loader/src/test/java/org/apache/openejb/loader/FilesTest.java
index daca321feb..86a11a711f 100644
--- a/container/openejb-loader/src/test/java/org/apache/openejb/loader/FilesTest.java
+++ b/container/openejb-loader/src/test/java/org/apache/openejb/loader/FilesTest.java
@@ -20,11 +20,15 @@ import org.junit.Test;
 
 import java.io.File;
 import java.io.IOException;
+import java.nio.file.attribute.PosixFileAttributeView;
+import java.nio.file.attribute.PosixFilePermission;
+import java.util.Set;
 import java.util.concurrent.TimeUnit;
 import java.util.logging.Level;
 import java.util.logging.Logger;
 
 import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertTrue;
 
 public class FilesTest {
@@ -72,4 +76,24 @@ public class FilesTest {
         final long time = TimeUnit.MILLISECONDS.convert(System.nanoTime() - start, TimeUnit.NANOSECONDS);
         Logger.getLogger(this.getClass().getName()).log(Level.INFO, String.format("Completed File.%1$s in %2$sms" , remove ? "remove" : "delete", String.valueOf(time)));
     }
-}
\ No newline at end of file
+
+    @Test
+    public void tmpdirCreatesDistinctPrivateDirectories() throws Exception {
+        final File dir1 = Files.tmpdir();
+        final File dir2 = Files.tmpdir();
+
+        assertTrue(dir1.isDirectory());
+        assertTrue(dir2.isDirectory());
+        assertNotEquals(dir1.getAbsoluteFile(), dir2.getAbsoluteFile());
+
+        for (final File dir : new File[]{dir1, dir2}) {
+            if (java.nio.file.Files.getFileStore(dir.toPath()).supportsFileAttributeView(PosixFileAttributeView.class)) {
+                final Set<PosixFilePermission> permissions = java.nio.file.Files.getPosixFilePermissions(dir.toPath());
+                for (final PosixFilePermission permission : permissions) {
+                    assertFalse("tmpdir must be owner-only but has " + permission,
+                            permission.name().startsWith("GROUP_") || permission.name().startsWith("OTHERS_"));
+                }
+            }
+        }
+    }
+}