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 41af418e3b6a5174bd0527a141cf999c9a1b8669
Author: Richard Zowalla <[email protected]>
AuthorDate: Tue Oct 13 08:48:15 2026 +0200

    Verify Tomcat user identity store passwords with the realm 
CredentialHandler (#3074)
    
    Digested passwords in tomcat-users.xml now verify against the cleartext;
    the stored digest is rejected and wrong passwords return INVALID.
    
    (cherry picked from commit 41bdd0bb9bf2907729761d04a65616341508b06a)
---
 .../arquillian-tomee-security-tests/pom.xml        |  43 ++++
 .../AbstractTomcatUserIdentityStoreTest.java       |  74 +++++++
 .../arquillian/tests/security/CallerServlet.java   |  36 ++++
 .../tests/security/DigestUserDatabaseConfig.java   |  26 +++
 .../tests/security/PlainUserDatabaseConfig.java    |  26 +++
 .../security/StandaloneUserDatabaseConfig.java     |  26 +++
 .../TomcatUserIdentityStoreDigestTest.java         |  66 +++++++
 .../security/TomcatUserIdentityStorePlainTest.java |  53 +++++
 .../TomcatUserIdentityStoreStandaloneTest.java     |  46 +++++
 .../src/test/resources/arquillian.xml              |  54 +++++
 .../src/test/resources/security-conf.properties    |  17 ++
 .../src/test/resources/security-conf/server.xml    |  55 ++++++
 .../security-conf/tomcat-users-digest.xml          |  22 +++
 .../security-conf/tomcat-users-standalone.xml      |  21 ++
 .../test/resources/security-conf/tomcat-users.xml  |  21 ++
 .../src/test/tomee/server.xml                      |  56 ++++++
 arquillian/arquillian-tomee-tests/pom.xml          |   1 +
 .../identitystore/TomEEDefaultIdentityStore.java   |  95 ++++++++-
 .../TomEEDefaultIdentityStoreTest.java             | 217 +++++++++++++++++++++
 19 files changed, 947 insertions(+), 8 deletions(-)

diff --git 
a/arquillian/arquillian-tomee-tests/arquillian-tomee-security-tests/pom.xml 
b/arquillian/arquillian-tomee-tests/arquillian-tomee-security-tests/pom.xml
new file mode 100644
index 0000000000..06606d35e4
--- /dev/null
+++ b/arquillian/arquillian-tomee-tests/arquillian-tomee-security-tests/pom.xml
@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+    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.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0"; 
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"; 
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
http://maven.apache.org/xsd/maven-4.0.0.xsd";>
+  <modelVersion>4.0.0</modelVersion>
+
+  <parent>
+    <artifactId>arquillian-tomee-tests</artifactId>
+    <groupId>org.apache.tomee</groupId>
+    <version>10.3.0-SNAPSHOT</version>
+  </parent>
+
+  <artifactId>arquillian-tomee-security-tests</artifactId>
+  <name>TomEE :: Arquillian Adaptors Parent :: Tests :: Security</name>
+
+  <properties>
+    <profile-under-test>webprofile</profile-under-test>
+  </properties>
+
+  <dependencies>
+    <dependency>
+      <groupId>${project.groupId}</groupId>
+      <artifactId>tomee-security</artifactId>
+      <version>${project.version}</version>
+      <scope>test</scope>
+    </dependency>
+  </dependencies>
+
+</project>
diff --git 
a/arquillian/arquillian-tomee-tests/arquillian-tomee-security-tests/src/test/java/org/apache/openejb/arquillian/tests/security/AbstractTomcatUserIdentityStoreTest.java
 
b/arquillian/arquillian-tomee-tests/arquillian-tomee-security-tests/src/test/java/org/apache/openejb/arquillian/tests/security/AbstractTomcatUserIdentityStoreTest.java
new file mode 100644
index 0000000000..c169b42936
--- /dev/null
+++ 
b/arquillian/arquillian-tomee-tests/arquillian-tomee-security-tests/src/test/java/org/apache/openejb/arquillian/tests/security/AbstractTomcatUserIdentityStoreTest.java
@@ -0,0 +1,74 @@
+/*
+ * 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.openejb.arquillian.tests.security;
+
+import org.jboss.arquillian.test.api.ArquillianResource;
+import org.jboss.shrinkwrap.api.ShrinkWrap;
+import org.jboss.shrinkwrap.api.asset.EmptyAsset;
+import org.jboss.shrinkwrap.api.spec.WebArchive;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.HttpURLConnection;
+import java.net.URL;
+import java.util.Base64;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+import static org.junit.Assert.assertEquals;
+
+public abstract class AbstractTomcatUserIdentityStoreTest {
+
+    @ArquillianResource
+    private URL base;
+
+    protected static WebArchive war(final String name, final Class<?> config) {
+        return ShrinkWrap.create(WebArchive.class, name + ".war")
+                .addClasses(CallerServlet.class, config)
+                .addAsWebInfResource(EmptyAsset.INSTANCE, "beans.xml");
+    }
+
+    protected void assertAuthenticated(final String user, final String 
password) throws IOException {
+        final HttpURLConnection connection = call(user, password);
+        try {
+            assertEquals(HttpURLConnection.HTTP_OK, 
connection.getResponseCode());
+            try (final InputStream in = connection.getInputStream()) {
+                assertEquals(user, new String(in.readAllBytes(), UTF_8));
+            }
+        } finally {
+            connection.disconnect();
+        }
+    }
+
+    protected void assertUnauthorized(final String user, final String 
password) throws IOException {
+        final HttpURLConnection connection = call(user, password);
+        try {
+            assertEquals(HttpURLConnection.HTTP_UNAUTHORIZED, 
connection.getResponseCode());
+        } finally {
+            connection.disconnect();
+        }
+    }
+
+    private HttpURLConnection call(final String user, final String password) 
throws IOException {
+        final HttpURLConnection connection = (HttpURLConnection) new URL(base, 
"caller").openConnection();
+        connection.setInstanceFollowRedirects(false);
+        if (user != null) {
+            connection.setRequestProperty("Authorization", "Basic "
+                    + Base64.getEncoder().encodeToString((user + ':' + 
password).getBytes(UTF_8)));
+        }
+        return connection;
+    }
+}
diff --git 
a/arquillian/arquillian-tomee-tests/arquillian-tomee-security-tests/src/test/java/org/apache/openejb/arquillian/tests/security/CallerServlet.java
 
b/arquillian/arquillian-tomee-tests/arquillian-tomee-security-tests/src/test/java/org/apache/openejb/arquillian/tests/security/CallerServlet.java
new file mode 100644
index 0000000000..b852d295c8
--- /dev/null
+++ 
b/arquillian/arquillian-tomee-tests/arquillian-tomee-security-tests/src/test/java/org/apache/openejb/arquillian/tests/security/CallerServlet.java
@@ -0,0 +1,36 @@
+/*
+ * 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.openejb.arquillian.tests.security;
+
+import 
jakarta.security.enterprise.authentication.mechanism.http.BasicAuthenticationMechanismDefinition;
+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 java.io.IOException;
+
+@WebServlet(urlPatterns = "/caller")
+@ServletSecurity(@HttpConstraint(rolesAllowed = "admin"))
+@BasicAuthenticationMechanismDefinition(realmName = "tomcat-users")
+public class CallerServlet extends HttpServlet {
+    @Override
+    protected void doGet(final HttpServletRequest req, final 
HttpServletResponse resp) throws IOException {
+        resp.getWriter().write(req.getUserPrincipal().getName());
+    }
+}
diff --git 
a/arquillian/arquillian-tomee-tests/arquillian-tomee-security-tests/src/test/java/org/apache/openejb/arquillian/tests/security/DigestUserDatabaseConfig.java
 
b/arquillian/arquillian-tomee-tests/arquillian-tomee-security-tests/src/test/java/org/apache/openejb/arquillian/tests/security/DigestUserDatabaseConfig.java
new file mode 100644
index 0000000000..5203863eab
--- /dev/null
+++ 
b/arquillian/arquillian-tomee-tests/arquillian-tomee-security-tests/src/test/java/org/apache/openejb/arquillian/tests/security/DigestUserDatabaseConfig.java
@@ -0,0 +1,26 @@
+/*
+ * 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.openejb.arquillian.tests.security;
+
+import org.apache.tomee.security.cdi.TomcatUserIdentityStoreDefinition;
+
+import jakarta.enterprise.context.ApplicationScoped;
+
+@ApplicationScoped
+@TomcatUserIdentityStoreDefinition(resource = "DigestUserDatabase")
+public class DigestUserDatabaseConfig {
+}
diff --git 
a/arquillian/arquillian-tomee-tests/arquillian-tomee-security-tests/src/test/java/org/apache/openejb/arquillian/tests/security/PlainUserDatabaseConfig.java
 
b/arquillian/arquillian-tomee-tests/arquillian-tomee-security-tests/src/test/java/org/apache/openejb/arquillian/tests/security/PlainUserDatabaseConfig.java
new file mode 100644
index 0000000000..f9ed78198f
--- /dev/null
+++ 
b/arquillian/arquillian-tomee-tests/arquillian-tomee-security-tests/src/test/java/org/apache/openejb/arquillian/tests/security/PlainUserDatabaseConfig.java
@@ -0,0 +1,26 @@
+/*
+ * 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.openejb.arquillian.tests.security;
+
+import org.apache.tomee.security.cdi.TomcatUserIdentityStoreDefinition;
+
+import jakarta.enterprise.context.ApplicationScoped;
+
+@ApplicationScoped
+@TomcatUserIdentityStoreDefinition(resource = "UserDatabase")
+public class PlainUserDatabaseConfig {
+}
diff --git 
a/arquillian/arquillian-tomee-tests/arquillian-tomee-security-tests/src/test/java/org/apache/openejb/arquillian/tests/security/StandaloneUserDatabaseConfig.java
 
b/arquillian/arquillian-tomee-tests/arquillian-tomee-security-tests/src/test/java/org/apache/openejb/arquillian/tests/security/StandaloneUserDatabaseConfig.java
new file mode 100644
index 0000000000..a5b9387dc4
--- /dev/null
+++ 
b/arquillian/arquillian-tomee-tests/arquillian-tomee-security-tests/src/test/java/org/apache/openejb/arquillian/tests/security/StandaloneUserDatabaseConfig.java
@@ -0,0 +1,26 @@
+/*
+ * 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.openejb.arquillian.tests.security;
+
+import org.apache.tomee.security.cdi.TomcatUserIdentityStoreDefinition;
+
+import jakarta.enterprise.context.ApplicationScoped;
+
+@ApplicationScoped
+@TomcatUserIdentityStoreDefinition(resource = "StandaloneUserDatabase")
+public class StandaloneUserDatabaseConfig {
+}
diff --git 
a/arquillian/arquillian-tomee-tests/arquillian-tomee-security-tests/src/test/java/org/apache/openejb/arquillian/tests/security/TomcatUserIdentityStoreDigestTest.java
 
b/arquillian/arquillian-tomee-tests/arquillian-tomee-security-tests/src/test/java/org/apache/openejb/arquillian/tests/security/TomcatUserIdentityStoreDigestTest.java
new file mode 100644
index 0000000000..c42192a9a3
--- /dev/null
+++ 
b/arquillian/arquillian-tomee-tests/arquillian-tomee-security-tests/src/test/java/org/apache/openejb/arquillian/tests/security/TomcatUserIdentityStoreDigestTest.java
@@ -0,0 +1,66 @@
+/*
+ * 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.openejb.arquillian.tests.security;
+
+import org.jboss.arquillian.container.test.api.Deployment;
+import org.jboss.arquillian.junit.Arquillian;
+import org.jboss.shrinkwrap.api.spec.WebArchive;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * The user database holds SHA-256 digests and is bound to a UserDatabaseRealm 
with a
+ * MessageDigestCredentialHandler (nested in a LockOutRealm), so the identity 
store has to
+ * verify the cleartext password against the digest.
+ */
+@RunWith(Arquillian.class)
+public class TomcatUserIdentityStoreDigestTest extends 
AbstractTomcatUserIdentityStoreTest {
+
+    private static final String DIGEST = 
"8b5a996ea6720136098b2ef354a195b696c56782e99b30407fe753a3beb559e0";
+
+    @Deployment(testable = false)
+    public static WebArchive app() {
+        return war("digest", DigestUserDatabaseConfig.class);
+    }
+
+    @Test
+    public void cleartextPassword() throws Exception {
+        assertAuthenticated("digest", "digest-secret");
+    }
+
+    @Test
+    public void storedDigestIsNotAPassword() throws Exception {
+        assertUnauthorized("digest", DIGEST);
+        assertUnauthorized("digest", DIGEST.toUpperCase());
+    }
+
+    @Test
+    public void wrongPassword() throws Exception {
+        assertUnauthorized("digest", "wrong");
+        assertUnauthorized("digest", "");
+    }
+
+    @Test
+    public void unknownUser() throws Exception {
+        assertUnauthorized("plain", "plain-secret");
+    }
+
+    @Test
+    public void noCredentials() throws Exception {
+        assertUnauthorized(null, null);
+    }
+}
diff --git 
a/arquillian/arquillian-tomee-tests/arquillian-tomee-security-tests/src/test/java/org/apache/openejb/arquillian/tests/security/TomcatUserIdentityStorePlainTest.java
 
b/arquillian/arquillian-tomee-tests/arquillian-tomee-security-tests/src/test/java/org/apache/openejb/arquillian/tests/security/TomcatUserIdentityStorePlainTest.java
new file mode 100644
index 0000000000..e39666515a
--- /dev/null
+++ 
b/arquillian/arquillian-tomee-tests/arquillian-tomee-security-tests/src/test/java/org/apache/openejb/arquillian/tests/security/TomcatUserIdentityStorePlainTest.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.openejb.arquillian.tests.security;
+
+import org.jboss.arquillian.container.test.api.Deployment;
+import org.jboss.arquillian.junit.Arquillian;
+import org.jboss.shrinkwrap.api.spec.WebArchive;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * The user database holds plain text passwords and is bound to a 
UserDatabaseRealm using
+ * the default CredentialHandler.
+ */
+@RunWith(Arquillian.class)
+public class TomcatUserIdentityStorePlainTest extends 
AbstractTomcatUserIdentityStoreTest {
+
+    @Deployment(testable = false)
+    public static WebArchive app() {
+        return war("plain", PlainUserDatabaseConfig.class);
+    }
+
+    @Test
+    public void password() throws Exception {
+        assertAuthenticated("plain", "plain-secret");
+    }
+
+    @Test
+    public void wrongPassword() throws Exception {
+        assertUnauthorized("plain", "wrong");
+        assertUnauthorized("plain", "plain-secret ");
+        assertUnauthorized("plain", "");
+    }
+
+    @Test
+    public void unknownUser() throws Exception {
+        assertUnauthorized("digest", "digest-secret");
+    }
+}
diff --git 
a/arquillian/arquillian-tomee-tests/arquillian-tomee-security-tests/src/test/java/org/apache/openejb/arquillian/tests/security/TomcatUserIdentityStoreStandaloneTest.java
 
b/arquillian/arquillian-tomee-tests/arquillian-tomee-security-tests/src/test/java/org/apache/openejb/arquillian/tests/security/TomcatUserIdentityStoreStandaloneTest.java
new file mode 100644
index 0000000000..fc1fba5c8e
--- /dev/null
+++ 
b/arquillian/arquillian-tomee-tests/arquillian-tomee-security-tests/src/test/java/org/apache/openejb/arquillian/tests/security/TomcatUserIdentityStoreStandaloneTest.java
@@ -0,0 +1,46 @@
+/*
+ * 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.openejb.arquillian.tests.security;
+
+import org.jboss.arquillian.container.test.api.Deployment;
+import org.jboss.arquillian.junit.Arquillian;
+import org.jboss.shrinkwrap.api.spec.WebArchive;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * No realm uses this user database, so the identity store falls back to a 
plain text comparison.
+ */
+@RunWith(Arquillian.class)
+public class TomcatUserIdentityStoreStandaloneTest extends 
AbstractTomcatUserIdentityStoreTest {
+
+    @Deployment(testable = false)
+    public static WebArchive app() {
+        return war("standalone", StandaloneUserDatabaseConfig.class);
+    }
+
+    @Test
+    public void password() throws Exception {
+        assertAuthenticated("standalone", "standalone-secret");
+    }
+
+    @Test
+    public void wrongPassword() throws Exception {
+        assertUnauthorized("standalone", "wrong");
+        assertUnauthorized("standalone", "");
+    }
+}
diff --git 
a/arquillian/arquillian-tomee-tests/arquillian-tomee-security-tests/src/test/resources/arquillian.xml
 
b/arquillian/arquillian-tomee-tests/arquillian-tomee-security-tests/src/test/resources/arquillian.xml
new file mode 100644
index 0000000000..774185d205
--- /dev/null
+++ 
b/arquillian/arquillian-tomee-tests/arquillian-tomee-security-tests/src/test/resources/arquillian.xml
@@ -0,0 +1,54 @@
+<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
+<!--
+    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.
+-->
+<arquillian xmlns="http://jboss.org/schema/arquillian";
+            xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance";
+            xsi:schemaLocation="http://jboss.org/schema/arquillian 
http://jboss.org/schema/arquillian/arquillian_1_0.xsd";>
+  <!--
+    Both containers use the conf/ files of src/test/resources/security-conf: 
several global UserDatabase
+    resources, some of them bound to a UserDatabaseRealm (one with a digesting 
CredentialHandler).
+  -->
+  <container qualifier="tomee-embedded" default="true">
+    <configuration>
+      <property name="httpPort">-1</property>
+      <property name="stopPort">-1</property>
+      <property name="dir">target/apache-tomee-embedded</property>
+      <property 
name="appWorkingDir">target/arquillian-test-working-dir</property>
+      <property 
name="classpathConfiguration">security-conf.properties</property>
+      <property name="properties">
+        
openejb.classloader.forced-load=org.apache.openejb.arquillian.tests.security
+      </property>
+    </configuration>
+  </container>
+  <container qualifier="tomee-remote">
+    <configuration>
+      <property name="httpPort">-1</property>
+      <property name="ajpPort">-1</property>
+      <property name="stopPort">-1</property>
+      <property name="dir">target/apache-tomee-remote</property>
+      <property 
name="appWorkingDir">target/arquillian-test-working-dir</property>
+      <property name="cleanOnStartUp">true</property>
+      <property name="simpleLog">true</property>
+      <property name="conf">src/test/resources/security-conf</property>
+      <!-- same as security-conf/server.xml plus the TomEE ServerListener a 
standalone server needs -->
+      <property name="serverXml">src/test/tomee/server.xml</property>
+      <property name="properties">
+        
openejb.classloader.forced-load=org.apache.openejb.arquillian.tests.security
+      </property>
+    </configuration>
+  </container>
+</arquillian>
diff --git 
a/arquillian/arquillian-tomee-tests/arquillian-tomee-security-tests/src/test/resources/security-conf.properties
 
b/arquillian/arquillian-tomee-tests/arquillian-tomee-security-tests/src/test/resources/security-conf.properties
new file mode 100644
index 0000000000..0ee185c0cf
--- /dev/null
+++ 
b/arquillian/arquillian-tomee-tests/arquillian-tomee-security-tests/src/test/resources/security-conf.properties
@@ -0,0 +1,17 @@
+# 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.
+
+# classpath folder copied to conf/ of the embedded server
+conf = security-conf
diff --git 
a/arquillian/arquillian-tomee-tests/arquillian-tomee-security-tests/src/test/resources/security-conf/server.xml
 
b/arquillian/arquillian-tomee-tests/arquillian-tomee-security-tests/src/test/resources/security-conf/server.xml
new file mode 100644
index 0000000000..6ab6898fc9
--- /dev/null
+++ 
b/arquillian/arquillian-tomee-tests/arquillian-tomee-security-tests/src/test/resources/security-conf/server.xml
@@ -0,0 +1,55 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+    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.
+-->
+<Server port="8005" shutdown="SHUTDOWN">
+  <Listener 
className="org.apache.catalina.core.JreMemoryLeakPreventionListener"/>
+  <Listener 
className="org.apache.catalina.mbeans.GlobalResourcesLifecycleListener"/>
+  <Listener 
className="org.apache.catalina.core.ThreadLocalLeakPreventionListener"/>
+
+  <GlobalNamingResources>
+    <!-- plain text passwords, used by a UserDatabaseRealm without explicit 
CredentialHandler -->
+    <Resource name="UserDatabase" auth="Container"
+              type="org.apache.catalina.UserDatabase"
+              factory="org.apache.catalina.users.MemoryUserDatabaseFactory"
+              pathname="conf/tomcat-users.xml"/>
+    <!-- SHA-256 digested passwords, used by a UserDatabaseRealm with a 
MessageDigestCredentialHandler -->
+    <Resource name="DigestUserDatabase" auth="Container"
+              type="org.apache.catalina.UserDatabase"
+              factory="org.apache.catalina.users.MemoryUserDatabaseFactory"
+              pathname="conf/tomcat-users-digest.xml"/>
+    <!-- plain text passwords, not used by any realm -->
+    <Resource name="StandaloneUserDatabase" auth="Container"
+              type="org.apache.catalina.UserDatabase"
+              factory="org.apache.catalina.users.MemoryUserDatabaseFactory"
+              pathname="conf/tomcat-users-standalone.xml"/>
+  </GlobalNamingResources>
+
+  <Service name="Catalina">
+    <Connector port="8080" protocol="HTTP/1.1" connectionTimeout="20000" 
redirectPort="8443"/>
+
+    <Engine name="Catalina" defaultHost="localhost">
+      <Realm className="org.apache.catalina.realm.LockOutRealm">
+        <Realm className="org.apache.catalina.realm.UserDatabaseRealm" 
resourceName="UserDatabase"/>
+        <Realm className="org.apache.catalina.realm.UserDatabaseRealm" 
resourceName="DigestUserDatabase">
+          <CredentialHandler 
className="org.apache.catalina.realm.MessageDigestCredentialHandler" 
algorithm="SHA-256"/>
+        </Realm>
+      </Realm>
+
+      <Host name="localhost" appBase="webapps" unpackWARs="true" 
autoDeploy="true"/>
+    </Engine>
+  </Service>
+</Server>
diff --git 
a/arquillian/arquillian-tomee-tests/arquillian-tomee-security-tests/src/test/resources/security-conf/tomcat-users-digest.xml
 
b/arquillian/arquillian-tomee-tests/arquillian-tomee-security-tests/src/test/resources/security-conf/tomcat-users-digest.xml
new file mode 100644
index 0000000000..632347831e
--- /dev/null
+++ 
b/arquillian/arquillian-tomee-tests/arquillian-tomee-security-tests/src/test/resources/security-conf/tomcat-users-digest.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+    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.
+-->
+<tomcat-users xmlns="http://tomcat.apache.org/xml"; version="1.0">
+  <role rolename="admin"/>
+  <!-- SHA-256 of "digest-secret" -->
+  <user username="digest" 
password="8b5a996ea6720136098b2ef354a195b696c56782e99b30407fe753a3beb559e0" 
roles="admin"/>
+</tomcat-users>
diff --git 
a/arquillian/arquillian-tomee-tests/arquillian-tomee-security-tests/src/test/resources/security-conf/tomcat-users-standalone.xml
 
b/arquillian/arquillian-tomee-tests/arquillian-tomee-security-tests/src/test/resources/security-conf/tomcat-users-standalone.xml
new file mode 100644
index 0000000000..90b1316648
--- /dev/null
+++ 
b/arquillian/arquillian-tomee-tests/arquillian-tomee-security-tests/src/test/resources/security-conf/tomcat-users-standalone.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+    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.
+-->
+<tomcat-users xmlns="http://tomcat.apache.org/xml"; version="1.0">
+  <role rolename="admin"/>
+  <user username="standalone" password="standalone-secret" roles="admin"/>
+</tomcat-users>
diff --git 
a/arquillian/arquillian-tomee-tests/arquillian-tomee-security-tests/src/test/resources/security-conf/tomcat-users.xml
 
b/arquillian/arquillian-tomee-tests/arquillian-tomee-security-tests/src/test/resources/security-conf/tomcat-users.xml
new file mode 100644
index 0000000000..b2f4c7b168
--- /dev/null
+++ 
b/arquillian/arquillian-tomee-tests/arquillian-tomee-security-tests/src/test/resources/security-conf/tomcat-users.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+    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.
+-->
+<tomcat-users xmlns="http://tomcat.apache.org/xml"; version="1.0">
+  <role rolename="admin"/>
+  <user username="plain" password="plain-secret" roles="admin"/>
+</tomcat-users>
diff --git 
a/arquillian/arquillian-tomee-tests/arquillian-tomee-security-tests/src/test/tomee/server.xml
 
b/arquillian/arquillian-tomee-tests/arquillian-tomee-security-tests/src/test/tomee/server.xml
new file mode 100644
index 0000000000..afe603c85d
--- /dev/null
+++ 
b/arquillian/arquillian-tomee-tests/arquillian-tomee-security-tests/src/test/tomee/server.xml
@@ -0,0 +1,56 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+    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.
+-->
+<Server port="8005" shutdown="SHUTDOWN">
+  <Listener className="org.apache.tomee.catalina.ServerListener"/>
+  <Listener 
className="org.apache.catalina.core.JreMemoryLeakPreventionListener"/>
+  <Listener 
className="org.apache.catalina.mbeans.GlobalResourcesLifecycleListener"/>
+  <Listener 
className="org.apache.catalina.core.ThreadLocalLeakPreventionListener"/>
+
+  <GlobalNamingResources>
+    <!-- plain text passwords, used by a UserDatabaseRealm without explicit 
CredentialHandler -->
+    <Resource name="UserDatabase" auth="Container"
+              type="org.apache.catalina.UserDatabase"
+              factory="org.apache.catalina.users.MemoryUserDatabaseFactory"
+              pathname="conf/tomcat-users.xml"/>
+    <!-- SHA-256 digested passwords, used by a UserDatabaseRealm with a 
MessageDigestCredentialHandler -->
+    <Resource name="DigestUserDatabase" auth="Container"
+              type="org.apache.catalina.UserDatabase"
+              factory="org.apache.catalina.users.MemoryUserDatabaseFactory"
+              pathname="conf/tomcat-users-digest.xml"/>
+    <!-- plain text passwords, not used by any realm -->
+    <Resource name="StandaloneUserDatabase" auth="Container"
+              type="org.apache.catalina.UserDatabase"
+              factory="org.apache.catalina.users.MemoryUserDatabaseFactory"
+              pathname="conf/tomcat-users-standalone.xml"/>
+  </GlobalNamingResources>
+
+  <Service name="Catalina">
+    <Connector port="8080" protocol="HTTP/1.1" connectionTimeout="20000" 
redirectPort="8443"/>
+
+    <Engine name="Catalina" defaultHost="localhost">
+      <Realm className="org.apache.catalina.realm.LockOutRealm">
+        <Realm className="org.apache.catalina.realm.UserDatabaseRealm" 
resourceName="UserDatabase"/>
+        <Realm className="org.apache.catalina.realm.UserDatabaseRealm" 
resourceName="DigestUserDatabase">
+          <CredentialHandler 
className="org.apache.catalina.realm.MessageDigestCredentialHandler" 
algorithm="SHA-256"/>
+        </Realm>
+      </Realm>
+
+      <Host name="localhost" appBase="webapps" unpackWARs="true" 
autoDeploy="true"/>
+    </Engine>
+  </Service>
+</Server>
diff --git a/arquillian/arquillian-tomee-tests/pom.xml 
b/arquillian/arquillian-tomee-tests/pom.xml
index 15630efdba..86be3b881a 100644
--- a/arquillian/arquillian-tomee-tests/pom.xml
+++ b/arquillian/arquillian-tomee-tests/pom.xml
@@ -30,6 +30,7 @@
     <module>arquillian-tomee-jaxws-tests</module>
     <module>arquillian-tomee-jms-tests</module>
     <module>arquillian-tomee-config-tests</module>
+    <module>arquillian-tomee-security-tests</module>
   </modules>
 
   <dependencies>
diff --git 
a/tomee/tomee-security/src/main/java/org/apache/tomee/security/identitystore/TomEEDefaultIdentityStore.java
 
b/tomee/tomee-security/src/main/java/org/apache/tomee/security/identitystore/TomEEDefaultIdentityStore.java
index 599ef03902..60e23b7192 100644
--- 
a/tomee/tomee-security/src/main/java/org/apache/tomee/security/identitystore/TomEEDefaultIdentityStore.java
+++ 
b/tomee/tomee-security/src/main/java/org/apache/tomee/security/identitystore/TomEEDefaultIdentityStore.java
@@ -16,10 +16,20 @@
  */
 package org.apache.tomee.security.identitystore;
 
+import org.apache.catalina.Container;
+import org.apache.catalina.CredentialHandler;
+import org.apache.catalina.Engine;
+import org.apache.catalina.Realm;
+import org.apache.catalina.Server;
+import org.apache.catalina.Service;
 import org.apache.catalina.User;
 import org.apache.catalina.UserDatabase;
 import org.apache.catalina.core.StandardServer;
 import org.apache.catalina.deploy.NamingResourcesImpl;
+import org.apache.catalina.realm.CombinedRealm;
+import org.apache.catalina.realm.UserDatabaseRealm;
+import org.apache.openejb.util.LogCategory;
+import org.apache.openejb.util.Logger;
 import org.apache.tomcat.util.descriptor.web.ContextResource;
 import org.apache.tomee.loader.TomcatHelper;
 import org.apache.tomee.security.cdi.TomcatUserIdentityStoreDefinition;
@@ -32,6 +42,8 @@ import 
jakarta.security.enterprise.credential.UsernamePasswordCredential;
 import jakarta.security.enterprise.identitystore.CredentialValidationResult;
 import jakarta.security.enterprise.identitystore.IdentityStore;
 import jakarta.security.enterprise.identitystore.IdentityStorePermission;
+import java.nio.charset.StandardCharsets;
+import java.security.MessageDigest;
 import java.util.HashSet;
 import java.util.Set;
 import java.util.function.Supplier;
@@ -41,12 +53,17 @@ import static java.util.Collections.emptySet;
 @ApplicationScoped
 public class TomEEDefaultIdentityStore implements IdentityStore {
 
+    private static final Logger LOGGER = 
Logger.getInstance(LogCategory.TOMEE_SECURITY, TomEEDefaultIdentityStore.class);
+
     @Inject
     private Supplier<TomcatUserIdentityStoreDefinition> definitionSupplier;
     private TomcatUserIdentityStoreDefinition definition;
 
     private UserDatabase userDatabase;
 
+    // null means no UserDatabaseRealm is configured for the resource: 
passwords are compared as plain text
+    private CredentialHandler credentialHandler;
+
     @PostConstruct
     private void init() throws Exception {
         definition = definitionSupplier.get();
@@ -55,6 +72,59 @@ public class TomEEDefaultIdentityStore implements 
IdentityStore {
         final NamingResourcesImpl resources = 
server.getGlobalNamingResources();
         final ContextResource userDataBaseResource = 
resources.findResource(definition.resource());
         userDatabase = (UserDatabase) 
server.getGlobalNamingContext().lookup(userDataBaseResource.getName());
+
+        credentialHandler = findCredentialHandler(server, 
definition.resource());
+        if (credentialHandler == null) {
+            LOGGER.warning("No UserDatabaseRealm found for resource '" + 
definition.resource()
+                    + "', passwords are compared as plain text. To use 
digested passwords configure a UserDatabaseRealm"
+                    + " with a CredentialHandler for this resource in 
server.xml");
+        }
+    }
+
+    /**
+     * Looks up the credential handler of the {@link UserDatabaseRealm} 
(possibly nested in a
+     * {@link CombinedRealm} or {@link 
org.apache.catalina.realm.LockOutRealm}) of an engine or host
+     * that is backed by the given global user database resource.
+     */
+    static CredentialHandler findCredentialHandler(final Server server, final 
String resourceName) {
+        for (final Service service : server.findServices()) {
+            final Engine engine = service.getContainer();
+            if (engine == null) {
+                continue;
+            }
+
+            CredentialHandler handler = 
findCredentialHandler(engine.getRealm(), resourceName);
+            if (handler != null) {
+                return handler;
+            }
+
+            for (final Container host : engine.findChildren()) {
+                handler = findCredentialHandler(host.getRealm(), resourceName);
+                if (handler != null) {
+                    return handler;
+                }
+            }
+        }
+        return null;
+    }
+
+    private static CredentialHandler findCredentialHandler(final Realm realm, 
final String resourceName) {
+        if (realm instanceof CombinedRealm combinedRealm) {
+            for (final Realm nested : combinedRealm.getNestedRealms()) {
+                final CredentialHandler handler = 
findCredentialHandler(nested, resourceName);
+                if (handler != null) {
+                    return handler;
+                }
+            }
+            return null;
+        }
+
+        if (realm instanceof UserDatabaseRealm userDatabaseRealm
+                && !userDatabaseRealm.getLocalJndiResource()
+                && resourceName.equals(userDatabaseRealm.getResourceName())) {
+            return userDatabaseRealm.getCredentialHandler();
+        }
+        return null;
     }
 
     @Override
@@ -69,17 +139,26 @@ public class TomEEDefaultIdentityStore implements 
IdentityStore {
             return CredentialValidationResult.INVALID_RESULT;
         }
 
-        // deal with hashed passwords in tomcat-users.xml
-        if 
(user.getPassword().equals(usernamePasswordCredential.getPasswordAsString())) {
-            Set<String> groups = emptySet();
-            if (validationTypes().contains(ValidationType.PROVIDE_GROUPS)) {
-                groups = new HashSet<>(getUserRoles(user));
-            }
+        if (!passwordMatches(user.getPassword(), 
usernamePasswordCredential.getPasswordAsString())) {
+            return CredentialValidationResult.INVALID_RESULT;
+        }
 
-            return new 
CredentialValidationResult(usernamePasswordCredential.getCaller(), groups);
+        Set<String> groups = emptySet();
+        if (validationTypes().contains(ValidationType.PROVIDE_GROUPS)) {
+            groups = new HashSet<>(getUserRoles(user));
         }
 
-        return CredentialValidationResult.NOT_VALIDATED_RESULT;
+        return new 
CredentialValidationResult(usernamePasswordCredential.getCaller(), groups);
+    }
+
+    private boolean passwordMatches(final String stored, final String 
supplied) {
+        if (stored == null || supplied == null) {
+            return false;
+        }
+        if (credentialHandler != null) {
+            return credentialHandler.matches(supplied, stored);
+        }
+        return MessageDigest.isEqual(stored.getBytes(StandardCharsets.UTF_8), 
supplied.getBytes(StandardCharsets.UTF_8));
     }
 
     private User getUser(final String callerPrincipal) {
diff --git 
a/tomee/tomee-security/src/test/java/org/apache/tomee/security/identitystore/TomEEDefaultIdentityStoreTest.java
 
b/tomee/tomee-security/src/test/java/org/apache/tomee/security/identitystore/TomEEDefaultIdentityStoreTest.java
new file mode 100644
index 0000000000..1c1e656629
--- /dev/null
+++ 
b/tomee/tomee-security/src/test/java/org/apache/tomee/security/identitystore/TomEEDefaultIdentityStoreTest.java
@@ -0,0 +1,217 @@
+/*
+ * 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.identitystore;
+
+import org.apache.catalina.CredentialHandler;
+import org.apache.catalina.Realm;
+import org.apache.catalina.User;
+import org.apache.catalina.core.StandardEngine;
+import org.apache.catalina.core.StandardHost;
+import org.apache.catalina.core.StandardServer;
+import org.apache.catalina.core.StandardService;
+import org.apache.catalina.realm.LockOutRealm;
+import org.apache.catalina.realm.MemoryRealm;
+import org.apache.catalina.realm.MessageDigestCredentialHandler;
+import org.apache.catalina.realm.UserDatabaseRealm;
+import org.apache.catalina.users.MemoryUserDatabase;
+import org.junit.Before;
+import org.junit.Test;
+
+import jakarta.security.enterprise.credential.Credential;
+import jakarta.security.enterprise.credential.UsernamePasswordCredential;
+import jakarta.security.enterprise.identitystore.CredentialValidationResult;
+import java.lang.reflect.Field;
+import java.util.Set;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
+
+public class TomEEDefaultIdentityStoreTest {
+
+    private MemoryUserDatabase database;
+    private TomEEDefaultIdentityStore store;
+
+    @Before
+    public void setUp() throws Exception {
+        database = new MemoryUserDatabase("test");
+        final User alice = database.createUser("alice", "s3cret", null);
+        alice.addRole(database.createRole("admin", null));
+
+        store = new TomEEDefaultIdentityStore();
+        setField("userDatabase", database);
+    }
+
+    @Test
+    public void validPassword() {
+        final CredentialValidationResult result = store.validate(new 
UsernamePasswordCredential("alice", "s3cret"));
+
+        assertEquals(CredentialValidationResult.Status.VALID, 
result.getStatus());
+        assertEquals("alice", result.getCallerPrincipal().getName());
+        assertEquals(Set.of("admin"), result.getCallerGroups());
+    }
+
+    @Test
+    public void wrongPassword() {
+        assertInvalid(store.validate(new UsernamePasswordCredential("alice", 
"wrong")));
+        assertInvalid(store.validate(new UsernamePasswordCredential("alice", 
"s3cre")));
+        assertInvalid(store.validate(new UsernamePasswordCredential("alice", 
"s3cret ")));
+        assertInvalid(store.validate(new UsernamePasswordCredential("alice", 
"")));
+    }
+
+    @Test
+    public void plainTextCredentialHandler() throws Exception {
+        setField("credentialHandler", new MessageDigestCredentialHandler());
+
+        assertEquals(CredentialValidationResult.Status.VALID,
+                store.validate(new UsernamePasswordCredential("alice", 
"s3cret")).getStatus());
+        assertInvalid(store.validate(new UsernamePasswordCredential("alice", 
"wrong")));
+    }
+
+    @Test
+    public void digestedPassword() throws Exception {
+        final MessageDigestCredentialHandler handler = new 
MessageDigestCredentialHandler();
+        handler.setAlgorithm("SHA-256");
+        final String digest = handler.mutate("s3cret");
+        database.createUser("carol", digest, null);
+        setField("credentialHandler", handler);
+
+        final CredentialValidationResult result = store.validate(new 
UsernamePasswordCredential("carol", "s3cret"));
+        assertEquals(CredentialValidationResult.Status.VALID, 
result.getStatus());
+        assertEquals("carol", result.getCallerPrincipal().getName());
+
+        assertInvalid(store.validate(new UsernamePasswordCredential("carol", 
digest)));
+        assertInvalid(store.validate(new UsernamePasswordCredential("carol", 
"wrong")));
+    }
+
+    @Test
+    public void saltedDigestedPassword() throws Exception {
+        final MessageDigestCredentialHandler handler = new 
MessageDigestCredentialHandler();
+        handler.setAlgorithm("SHA-256");
+        handler.setSaltLength(16);
+        handler.setIterations(100);
+        final String digest = handler.mutate("s3cret");
+        database.createUser("carol", digest, null);
+        setField("credentialHandler", handler);
+
+        assertEquals(CredentialValidationResult.Status.VALID,
+                store.validate(new UsernamePasswordCredential("carol", 
"s3cret")).getStatus());
+        assertInvalid(store.validate(new UsernamePasswordCredential("carol", 
digest)));
+    }
+
+    @Test
+    public void unknownUser() {
+        assertEquals(CredentialValidationResult.INVALID_RESULT,
+                store.validate(new UsernamePasswordCredential("bob", 
"s3cret")));
+    }
+
+    @Test
+    public void nullStoredPassword() throws Exception {
+        database.createUser("nopass", null, null);
+
+        assertInvalid(store.validate(new UsernamePasswordCredential("nopass", 
"")));
+        assertInvalid(store.validate(new UsernamePasswordCredential("nopass", 
"anything")));
+
+        setField("credentialHandler", new MessageDigestCredentialHandler());
+        assertInvalid(store.validate(new UsernamePasswordCredential("nopass", 
"")));
+        assertInvalid(store.validate(new UsernamePasswordCredential("nopass", 
"anything")));
+    }
+
+    @Test
+    public void unsupportedCredential() {
+        assertEquals(CredentialValidationResult.NOT_VALIDATED_RESULT, 
store.validate(new Credential() {
+        }));
+    }
+
+    @Test
+    public void credentialHandlerOfEngineRealm() {
+        final CredentialHandler handler = new MessageDigestCredentialHandler();
+        final UserDatabaseRealm realm = userDatabaseRealm("UserDatabase", 
handler);
+        final StandardServer server = server(realm, null);
+
+        assertSame(handler, 
TomEEDefaultIdentityStore.findCredentialHandler(server, "UserDatabase"));
+        assertNull(TomEEDefaultIdentityStore.findCredentialHandler(server, 
"OtherDatabase"));
+    }
+
+    @Test
+    public void credentialHandlerOfNestedRealm() {
+        final CredentialHandler handler = new MessageDigestCredentialHandler();
+        final CredentialHandler other = new MessageDigestCredentialHandler();
+        final LockOutRealm lockOut = new LockOutRealm();
+        lockOut.addRealm(new MemoryRealm());
+        lockOut.addRealm(userDatabaseRealm("OtherDatabase", other));
+        lockOut.addRealm(userDatabaseRealm("UserDatabase", handler));
+
+        final StandardServer server = server(lockOut, null);
+
+        assertSame(handler, 
TomEEDefaultIdentityStore.findCredentialHandler(server, "UserDatabase"));
+        assertSame(other, 
TomEEDefaultIdentityStore.findCredentialHandler(server, "OtherDatabase"));
+    }
+
+    @Test
+    public void credentialHandlerOfHostRealm() {
+        final CredentialHandler handler = new MessageDigestCredentialHandler();
+        final StandardServer server = server(new MemoryRealm(), 
userDatabaseRealm("UserDatabase", handler));
+
+        assertSame(handler, 
TomEEDefaultIdentityStore.findCredentialHandler(server, "UserDatabase"));
+    }
+
+    @Test
+    public void noRealmForResource() {
+        final UserDatabaseRealm local = userDatabaseRealm("UserDatabase", new 
MessageDigestCredentialHandler());
+        local.setLocalJndiResource(true);
+
+        
assertNull(TomEEDefaultIdentityStore.findCredentialHandler(server(null, null), 
"UserDatabase"));
+        assertNull(TomEEDefaultIdentityStore.findCredentialHandler(server(new 
MemoryRealm(), null), "UserDatabase"));
+        
assertNull(TomEEDefaultIdentityStore.findCredentialHandler(server(local, null), 
"UserDatabase"));
+    }
+
+    private static UserDatabaseRealm userDatabaseRealm(final String 
resourceName, final CredentialHandler handler) {
+        final UserDatabaseRealm realm = new UserDatabaseRealm();
+        realm.setResourceName(resourceName);
+        realm.setCredentialHandler(handler);
+        return realm;
+    }
+
+    private static StandardServer server(final Realm engineRealm, final Realm 
hostRealm) {
+        final StandardEngine engine = new StandardEngine();
+        engine.setName("Catalina");
+        engine.setRealm(engineRealm);
+
+        final StandardHost host = new StandardHost();
+        host.setName("localhost");
+        host.setRealm(hostRealm);
+        engine.addChild(host);
+
+        final StandardService service = new StandardService();
+        service.setContainer(engine);
+
+        final StandardServer server = new StandardServer();
+        server.addService(service);
+        return server;
+    }
+
+    private void setField(final String name, final Object value) throws 
Exception {
+        final Field field = 
TomEEDefaultIdentityStore.class.getDeclaredField(name);
+        field.setAccessible(true);
+        field.set(store, value);
+    }
+
+    private static void assertInvalid(final CredentialValidationResult result) 
{
+        assertEquals(CredentialValidationResult.Status.INVALID, 
result.getStatus());
+    }
+}

Reply via email to