You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@tomee.apache.org by ju...@apache.org on 2026/07/23 19:33:45 UTC

(tomee) branch claude/tomee-4648-fix-79009f created (now 9ceece76dd)

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

jungm pushed a change to branch claude/tomee-4648-fix-79009f
in repository https://gitbox.apache.org/repos/asf/tomee.git


      at 9ceece76dd TOMEE-4648 - BASIC mechanism: validate with UsernamePasswordCredential

This branch includes the following new commits:

     new 9ceece76dd TOMEE-4648 - BASIC mechanism: validate with UsernamePasswordCredential

The 1 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.



(tomee) 01/01: TOMEE-4648 - BASIC mechanism: validate with UsernamePasswordCredential

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

jungm pushed a commit to branch claude/tomee-4648-fix-79009f
in repository https://gitbox.apache.org/repos/asf/tomee.git

commit 9ceece76dd2725273515f9f715938dff212e3b15
Author: Markus Jung <ju...@apache.org>
AuthorDate: Thu Jul 23 21:33:28 2026 +0200

    TOMEE-4648 - BASIC mechanism: validate with UsernamePasswordCredential
    
    The BASIC mechanism passed a BasicAuthenticationCredential to
    IdentityStoreHandler.validate(Credential). The spec's default
    IdentityStore.validate(Credential) dispatches to a validate(...) overload
    only on an exact parameter-type match, and BasicAuthenticationCredential
    is a subclass of UsernamePasswordCredential, so an application store
    declaring the idiomatic validate(UsernamePasswordCredential) overload was
    never invoked and returned NOT_VALIDATED - producing a 401 for valid
    credentials. Built-in stores were unaffected, so it only surfaced with an
    application-supplied IdentityStore.
    
    Hand the identity store a plain UsernamePasswordCredential (reusing the
    already-parsed Password) while still parsing the header via
    BasicAuthenticationCredential. Adds a test mirroring the TCK app-mem-basic
    identity store.
---
 .../security/cdi/BasicAuthenticationMechanism.java |   8 +-
 .../security/servlet/Tomee4648BasicGroupsTest.java | 117 +++++++++++++++++++++
 2 files changed, 124 insertions(+), 1 deletion(-)

diff --git a/tomee/tomee-security/src/main/java/org/apache/tomee/security/cdi/BasicAuthenticationMechanism.java b/tomee/tomee-security/src/main/java/org/apache/tomee/security/cdi/BasicAuthenticationMechanism.java
index 00c0037586..6c9d68c866 100644
--- a/tomee/tomee-security/src/main/java/org/apache/tomee/security/cdi/BasicAuthenticationMechanism.java
+++ b/tomee/tomee-security/src/main/java/org/apache/tomee/security/cdi/BasicAuthenticationMechanism.java
@@ -24,6 +24,7 @@ import jakarta.security.enterprise.authentication.mechanism.http.BasicAuthentica
 import jakarta.security.enterprise.authentication.mechanism.http.HttpAuthenticationMechanism;
 import jakarta.security.enterprise.authentication.mechanism.http.HttpMessageContext;
 import jakarta.security.enterprise.credential.BasicAuthenticationCredential;
+import jakarta.security.enterprise.credential.UsernamePasswordCredential;
 import jakarta.security.enterprise.identitystore.CredentialValidationResult;
 import jakarta.security.enterprise.identitystore.IdentityStoreHandler;
 import jakarta.servlet.http.HttpServletRequest;
@@ -53,7 +54,12 @@ public class BasicAuthenticationMechanism implements HttpAuthenticationMechanism
 
         try {
             final BasicAuthenticationCredential credential = parseAuthenticationHeader(request.getHeader(AUTHORIZATION));
-            final CredentialValidationResult result = identityStoreHandler.validate(credential);
+
+            // IdentityStore#validate(Credential) dispatches to an overload only on an *exact* parameter
+            // type match, so a store declaring the idiomatic validate(UsernamePasswordCredential) would
+            // never be called with the BasicAuthenticationCredential subclass. Hand it the base type.
+            final CredentialValidationResult result = identityStoreHandler.validate(
+                    new UsernamePasswordCredential(credential.getCaller(), credential.getPassword()));
 
             if (result.getStatus().equals(VALID)) {
                 return httpMessageContext.notifyContainerAboutLogin(result);
diff --git a/tomee/tomee-security/src/test/java/org/apache/tomee/security/servlet/Tomee4648BasicGroupsTest.java b/tomee/tomee-security/src/test/java/org/apache/tomee/security/servlet/Tomee4648BasicGroupsTest.java
new file mode 100644
index 0000000000..f75c996430
--- /dev/null
+++ b/tomee/tomee-security/src/test/java/org/apache/tomee/security/servlet/Tomee4648BasicGroupsTest.java
@@ -0,0 +1,117 @@
+/*
+ * 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.security.servlet;
+
+import org.apache.tomee.security.AbstractTomEESecurityTest;
+import org.apache.tomee.security.client.BasicAuthFilter;
+import org.junit.Test;
+
+import jakarta.annotation.security.DeclareRoles;
+import jakarta.enterprise.context.ApplicationScoped;
+import jakarta.enterprise.util.AnnotationLiteral;
+import jakarta.inject.Qualifier;
+import jakarta.security.enterprise.authentication.mechanism.http.BasicAuthenticationMechanismDefinition;
+import jakarta.security.enterprise.credential.UsernamePasswordCredential;
+import jakarta.security.enterprise.identitystore.CredentialValidationResult;
+import jakarta.security.enterprise.identitystore.IdentityStore;
+import jakarta.servlet.ServletException;
+import jakarta.servlet.annotation.HttpConstraint;
+import jakarta.servlet.annotation.ServletSecurity;
+import jakarta.servlet.annotation.WebServlet;
+import jakarta.servlet.http.HttpServlet;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+import jakarta.ws.rs.client.ClientBuilder;
+import jakarta.ws.rs.core.Response;
+import java.io.IOException;
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+import java.util.HashSet;
+
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.PARAMETER;
+import static java.lang.annotation.ElementType.TYPE;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+import static java.util.Arrays.asList;
+import static jakarta.security.enterprise.identitystore.CredentialValidationResult.INVALID_RESULT;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * Mirrors the Jakarta Security TCK app-mem-basic module: an application supplied
+ * {@link IdentityStore} that only declares the {@code validate(UsernamePasswordCredential)}
+ * overload and returns the caller groups from that validation.
+ */
+public class Tomee4648BasicGroupsTest extends AbstractTomEESecurityTest {
+
+    @Test
+    public void authenticatedCallerKeepsGroups() {
+        final Response response = ClientBuilder.newBuilder()
+                                               .register(new BasicAuthFilter("reza", "secret1"))
+                                               .build()
+                                               .target(getAppUrl() + "/tomee4648")
+                                               .request()
+                                               .get();
+
+        assertEquals(200, response.getStatus());
+
+        final String body = response.readEntity(String.class);
+        assertTrue(body, body.contains("web username: reza"));
+        assertTrue(body, body.contains("web user has role \"foo\": true"));
+        assertTrue(body, body.contains("web user has role \"bar\": true"));
+    }
+
+    @ApplicationScoped
+    public static class TestIdentityStore implements IdentityStore {
+        public CredentialValidationResult validate(final UsernamePasswordCredential credential) {
+            if (credential.compareTo("reza", "secret1")) {
+                return new CredentialValidationResult("reza", new HashSet<>(asList("foo", "bar")));
+            }
+            return INVALID_RESULT;
+        }
+    }
+
+    @Qualifier
+    @Retention(RUNTIME)
+    @Target({FIELD, METHOD, TYPE, PARAMETER})
+    public @interface Tomee4648Mechanism {
+        final class Literal extends AnnotationLiteral<Tomee4648Mechanism> implements Tomee4648Mechanism {
+            private static final long serialVersionUID = 1L;
+            public static final Literal INSTANCE = new Literal();
+        }
+    }
+
+    @WebServlet(urlPatterns = "/tomee4648")
+    @DeclareRoles({"foo", "bar", "kaz"})
+    @ServletSecurity(@HttpConstraint(rolesAllowed = "foo"))
+    @BasicAuthenticationMechanismDefinition(
+            realmName = "test realm",
+            qualifiers = Tomee4648Mechanism.class)
+    public static class TestServlet extends HttpServlet {
+        @Override
+        protected void doGet(final HttpServletRequest req, final HttpServletResponse resp)
+                throws ServletException, IOException {
+            resp.getWriter().write("This is a servlet \n");
+            resp.getWriter().write("web username: "
+                    + (req.getUserPrincipal() == null ? null : req.getUserPrincipal().getName()) + "\n");
+            resp.getWriter().write("web user has role \"foo\": " + req.isUserInRole("foo") + "\n");
+            resp.getWriter().write("web user has role \"bar\": " + req.isUserInRole("bar") + "\n");
+            resp.getWriter().write("web user has role \"kaz\": " + req.isUserInRole("kaz") + "\n");
+        }
+    }
+}