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()); + } +}
