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 <[email protected]>
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");
+        }
+    }
+}

Reply via email to