Repository: tomee Updated Branches: refs/heads/tomee-1.7.x ec2245232 -> 23749f574
TOMEE-1648: The password cipher is called each time the datasource is built even if we already have the clear password Not sure when it really happens, but it happened at least one for a user Project: http://git-wip-us.apache.org/repos/asf/tomee/repo Commit: http://git-wip-us.apache.org/repos/asf/tomee/commit/4a4b6721 Tree: http://git-wip-us.apache.org/repos/asf/tomee/tree/4a4b6721 Diff: http://git-wip-us.apache.org/repos/asf/tomee/diff/4a4b6721 Branch: refs/heads/tomee-1.7.x Commit: 4a4b6721c2c3765b3094db29038bfc21b17a2a79 Parents: ec22452 Author: Jean-Louis Monteiro <[email protected]> Authored: Fri Oct 30 14:14:51 2015 -0700 Committer: Jean-Louis Monteiro <[email protected]> Committed: Fri Oct 30 14:14:51 2015 -0700 ---------------------------------------------------------------------- .../resource/jdbc/dbcp/BasicDataSource.java | 3 + .../jdbc/dbcp/BasicManagedDataSource.java | 3 + .../jdbc/CipheredPasswordDataSourceTest.java | 193 +++++++++++++++++++ 3 files changed, 199 insertions(+) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/tomee/blob/4a4b6721/container/openejb-core/src/main/java/org/apache/openejb/resource/jdbc/dbcp/BasicDataSource.java ---------------------------------------------------------------------- diff --git a/container/openejb-core/src/main/java/org/apache/openejb/resource/jdbc/dbcp/BasicDataSource.java b/container/openejb-core/src/main/java/org/apache/openejb/resource/jdbc/dbcp/BasicDataSource.java index 018f7c4..a00393b 100644 --- a/container/openejb-core/src/main/java/org/apache/openejb/resource/jdbc/dbcp/BasicDataSource.java +++ b/container/openejb-core/src/main/java/org/apache/openejb/resource/jdbc/dbcp/BasicDataSource.java @@ -223,6 +223,9 @@ public class BasicDataSource extends org.apache.commons.dbcp.BasicDataSource { // override previous password value super.setPassword(plainPwd); + + // as soon as we already decrypted the password, we don't want it to be decrypted again + passwordCipher = null; } // get the plugin http://git-wip-us.apache.org/repos/asf/tomee/blob/4a4b6721/container/openejb-core/src/main/java/org/apache/openejb/resource/jdbc/dbcp/BasicManagedDataSource.java ---------------------------------------------------------------------- diff --git a/container/openejb-core/src/main/java/org/apache/openejb/resource/jdbc/dbcp/BasicManagedDataSource.java b/container/openejb-core/src/main/java/org/apache/openejb/resource/jdbc/dbcp/BasicManagedDataSource.java index 1f472f8..d7cfcb9 100644 --- a/container/openejb-core/src/main/java/org/apache/openejb/resource/jdbc/dbcp/BasicManagedDataSource.java +++ b/container/openejb-core/src/main/java/org/apache/openejb/resource/jdbc/dbcp/BasicManagedDataSource.java @@ -226,6 +226,9 @@ public class BasicManagedDataSource extends org.apache.commons.dbcp.managed.Basi // override previous password value super.setPassword(plainPwd); + + // as soon as we already decrypted the password, we don't want it to be decrypted again + passwordCipher = null; } // get the plugin http://git-wip-us.apache.org/repos/asf/tomee/blob/4a4b6721/container/openejb-core/src/test/java/org/apache/openejb/resource/jdbc/CipheredPasswordDataSourceTest.java ---------------------------------------------------------------------- diff --git a/container/openejb-core/src/test/java/org/apache/openejb/resource/jdbc/CipheredPasswordDataSourceTest.java b/container/openejb-core/src/test/java/org/apache/openejb/resource/jdbc/CipheredPasswordDataSourceTest.java new file mode 100644 index 0000000..f78c942 --- /dev/null +++ b/container/openejb-core/src/test/java/org/apache/openejb/resource/jdbc/CipheredPasswordDataSourceTest.java @@ -0,0 +1,193 @@ +/* + * 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.resource.jdbc; + +import org.apache.commons.dbcp.PoolableConnection; +import org.apache.openejb.cipher.PasswordCipher; +import org.apache.openejb.jee.EjbJar; +import org.apache.openejb.jee.SingletonBean; +import org.apache.openejb.junit.ApplicationComposer; +import org.apache.openejb.resource.jdbc.dbcp.BasicManagedDataSource; +import org.apache.openejb.testing.Configuration; +import org.apache.openejb.testing.Module; +import org.apache.openejb.util.Strings; +import org.apache.openejb.util.reflection.Reflections; +import org.junit.BeforeClass; +import org.junit.Test; +import org.junit.runner.RunWith; + +import javax.annotation.Resource; +import javax.ejb.EJB; +import javax.ejb.EJBContext; +import javax.ejb.LocalBean; +import javax.ejb.Singleton; +import javax.ejb.TransactionAttribute; +import javax.ejb.TransactionAttributeType; +import javax.sql.DataSource; +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.Properties; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +@RunWith(ApplicationComposer.class) +public class CipheredPasswordDataSourceTest { + private static final String URL = "jdbc:hsqldb:mem:managed;hsqldb.tx=MVCC"; // mvcc otherwise multiple transaction tests will fail + private static final String USER = "sa"; + private static final String PASSWORD = ""; + private static final String ENCRYPTED_PASSWORD = "This is the encrypted value."; + private static final String TABLE = "PUBLIC.MANAGED_DATASOURCE_TEST"; + + @EJB + private Persister persistManager; + + @Resource + private DataSource ds; + + @BeforeClass + public static void createTable() throws SQLException, ClassNotFoundException { + Class.forName("org.hsqldb.jdbcDriver"); + + final Connection connection = DriverManager.getConnection(URL, USER, PASSWORD); + final Statement statement = connection.createStatement(); + statement.execute("CREATE TABLE " + TABLE + "(ID INTEGER)"); + statement.close(); + connection.commit(); + connection.close(); + } + + @Configuration + public Properties config() { + final Properties p = new Properties(); + p.put("openejb.jdbc.datasource-creator", "dbcp"); + + p.put("managed", "new://Resource?type=DataSource"); + p.put("managed.JdbcDriver", "org.hsqldb.jdbcDriver"); + p.put("managed.JdbcUrl", URL); + p.put("managed.UserName", USER); + p.put("managed.Password", ENCRYPTED_PASSWORD); + p.put("managed.PasswordCipher", EmptyPasswordCipher.class.getName()); + p.put("managed.JtaManaged", "true"); + p.put("managed.initialSize", "1"); + p.put("managed.maxActive", "10"); + p.put("managed.maxIdle", "1"); + p.put("managed.minIdle", "1"); + p.put("managed.maxWait", "200"); + return p; + } + + @Module + public EjbJar app() throws Exception { + return new EjbJar() + .enterpriseBean(new SingletonBean(Persister.class).localBean()); + + } + + @Test + public void rebuild() throws SQLException { + connection(true); + } + + @Test + public void noRebuild() throws SQLException { + connection(false); + } + + + public void connection(final boolean rebuild) throws SQLException { + // the first time the pool is created, the password is decrypted + // but the BasicManagedDataSource did not recall that so next time + // it tries to decrypt again the password + ds.getConnection(); + + // not sure how that is possible, but that happened to reproduce the issue + if (rebuild) { + Reflections.set(ds, "dataSource", null); + } + + // this should re create the pool and try to call again the password cipher #decrypt + // on an already clear text password + ds.getConnection(); + } + + public static class EmptyPasswordCipher implements PasswordCipher { + + @Override + public char[] encrypt(String plainPassword) { + throw new RuntimeException("Should never be called in this test."); + } + + @Override + public String decrypt(char[] encryptedPassword) { + System.out.println(String.format(">>> Decrypt password '%s'", new String(encryptedPassword))); + if (encryptedPassword.length == 0) { + throw new IllegalArgumentException("Can only decrypt a non empty string."); + } + return ""; + } + } + + private static boolean exists(final int id) throws SQLException { + final Connection connection = DriverManager.getConnection(URL, USER, PASSWORD); + final Statement statement = connection.createStatement(); + final ResultSet result = statement.executeQuery("SELECT count(*) AS NB FROM " + TABLE + " WHERE ID = " + id); + try { + assertTrue(result.next()); + return result.getInt(1) == 1; + } finally { + statement.close(); + connection.close(); + } + } + + private static void save(final DataSource ds, final int id) throws SQLException { + execute(ds, "INSERT INTO " + TABLE + "(ID) VALUES(" + id + ")"); + } + + private static void execute(final DataSource ds, final String sql) throws SQLException { + final Connection connection = ds.getConnection(); + final Statement statement = connection.createStatement(); + statement.executeUpdate(sql); + statement.close(); + connection.close(); + } + + @LocalBean + @Singleton + @TransactionAttribute(TransactionAttributeType.REQUIRES_NEW) + public static class Persister { + @Resource(name = "managed") + private DataSource ds; + + @Resource + private EJBContext context; + + public void save() throws SQLException { + CipheredPasswordDataSourceTest.save(ds, 10); + } + + public void saveAndRollback() throws SQLException { + CipheredPasswordDataSourceTest.save(ds, 11); + context.setRollbackOnly(); + } + } + +}
