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

Reply via email to