Sure. Maybe the fix can be improved. Let's discuss
Le 30 oct. 2015 17:37, "Romain Manni-Bucau" <[email protected]> a
écrit :

> It prevents to reset the pwd at runtime if i read it correctly. It should
> be fixed since it is used at least through jmx.
> ---------- Message transféré ----------
> De : <[email protected]>
> Date : 30 oct. 2015 16:14
> Objet : tomee git commit: TOMEE-1648: The password cipher is called each
> time the datasource is built even if we already have the clear password
> À : <[email protected]>
> Cc :
>
> Repository: tomee
> Updated Branches:
>   refs/heads/master 34c4cc7a2 -> 436089b65
>
>
> TOMEE-1648: The password cipher is called each time the datasource is built
> even if we already have the clear password
>
>
> Project: http://git-wip-us.apache.org/repos/asf/tomee/repo
> Commit: http://git-wip-us.apache.org/repos/asf/tomee/commit/436089b6
> Tree: http://git-wip-us.apache.org/repos/asf/tomee/tree/436089b6
> Diff: http://git-wip-us.apache.org/repos/asf/tomee/diff/436089b6
>
> Branch: refs/heads/master
> Commit: 436089b658e239757e0a51b49be01974ca339627
> Parents: 34c4cc7
> Author: Jean-Louis Monteiro <[email protected]>
> Authored: Fri Oct 30 16:13:56 2015 -0700
> Committer: Jean-Louis Monteiro <[email protected]>
> Committed: Fri Oct 30 16:13:56 2015 -0700
>
> ----------------------------------------------------------------------
>  .../resource/jdbc/dbcp/BasicDataSource.java     |  20 +-
>  .../jdbc/dbcp/BasicManagedDataSource.java       |  23 ++-
>  .../jdbc/CipheredPasswordDataSourceTest.java    | 193 +++++++++++++++++++
>  3 files changed, 233 insertions(+), 3 deletions(-)
> ----------------------------------------------------------------------
>
>
>
> http://git-wip-us.apache.org/repos/asf/tomee/blob/436089b6/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 ffb5bba..995221c 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
> @@ -55,6 +55,10 @@ public class BasicDataSource extends
> org.apache.commons.dbcp2.BasicDataSource im
>       * not ciphered. The {@link
> org.apache.openejb.cipher.PlainTextPasswordCipher} can also be used.
>       */
>      private String passwordCipher;
> +
> +    // keep tracking the user configured password in case we need it to be
> decrypted again
> +    private String initialPassword;
> +
>      private JMXBasicDataSource jmxDs;
>      private CommonDataSource delegate;
>      private String name;
> @@ -128,6 +132,18 @@ public class BasicDataSource extends
> org.apache.commons.dbcp2.BasicDataSource im
>          }
>      }
>
> +    @Override
> +    public void setPassword(final String password) {
> +        final ReentrantLock l = lock;
> +        l.lock();
> +        try {
> +            // keep the encrypted value if it's encrypted
> +            this.initialPassword = password;
> +            super.setPassword(password);
> +        } finally {
> +            l.unlock();
> +        }
> +    }
>
>      public String getUserName() {
>          final ReentrantLock l = lock;
> @@ -226,7 +242,9 @@ public class BasicDataSource extends
> org.apache.commons.dbcp2.BasicDataSource im
>              // check password codec if available
>              if (null != passwordCipher &&
> !"PlainText".equals(passwordCipher)) {
>                  final PasswordCipher cipher =
> PasswordCipherFactory.getPasswordCipher(passwordCipher);
> -                final String plainPwd =
> cipher.decrypt(getPassword().toCharArray());
> +
> +                // always use the initial encrypted value
> +                final String plainPwd =
> cipher.decrypt(initialPassword.toCharArray());
>
>                  // override previous password value
>                  super.setPassword(plainPwd);
>
>
> http://git-wip-us.apache.org/repos/asf/tomee/blob/436089b6/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 ec604b0..0846f21 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
> @@ -54,6 +54,10 @@ public class BasicManagedDataSource extends
> org.apache.commons.dbcp2.managed.Bas
>       * not ciphered. The {@link
> org.apache.openejb.cipher.PlainTextPasswordCipher} can also be used.
>       */
>      private String passwordCipher;
> +
> +    // keep tracking the user configured password in case we need it to be
> decrypted again
> +    private String initialPassword;
> +
>      private JMXBasicDataSource jmxDs;
>
>      public BasicManagedDataSource(final String name) {
> @@ -85,7 +89,7 @@ public class BasicManagedDataSource extends
> org.apache.commons.dbcp2.managed.Bas
>
>      private void setJndiXaDataSource(final String xaDataSource) {
>          setXaDataSourceInstance( // proxy cause we don't know if this
> datasource was created before or not the delegate
> -            XADataSourceResource.proxy(getDriverClassLoader() != null ?
> getDriverClassLoader() : Thread.currentThread().getContextClassLoader(),
> xaDataSource));
> +                XADataSourceResource.proxy(getDriverClassLoader() != null
> ? getDriverClassLoader() : Thread.currentThread().getContextClassLoader(),
> xaDataSource));
>
>          if (getTransactionManager() == null) {
>              setTransactionManager(OpenEJB.getTransactionManager());
> @@ -133,6 +137,19 @@ public class BasicManagedDataSource extends
> org.apache.commons.dbcp2.managed.Bas
>          }
>      }
>
> +    @Override
> +    public void setPassword(final String password) {
> +        final ReentrantLock l = lock;
> +        l.lock();
> +        try {
> +            // keep the encrypted value if it's encrypted
> +            this.initialPassword = password;
> +            super.setPassword(password);
> +        } finally {
> +            l.unlock();
> +        }
> +    }
> +
>      public String getUserName() {
>          final ReentrantLock l = lock;
>          l.lock();
> @@ -229,7 +246,9 @@ public class BasicManagedDataSource extends
> org.apache.commons.dbcp2.managed.Bas
>              // check password codec if available
>              if (null != passwordCipher) {
>                  final PasswordCipher cipher =
> PasswordCipherFactory.getPasswordCipher(passwordCipher);
> -                final String plainPwd =
> cipher.decrypt(getPassword().toCharArray());
> +
> +                // always use the initial encrypted value
> +                final String plainPwd =
> cipher.decrypt(initialPassword.toCharArray());
>
>                  // override previous password value
>                  super.setPassword(plainPwd);
>
>
> http://git-wip-us.apache.org/repos/asf/tomee/blob/436089b6/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..0f1224f
> --- /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.Before;
> +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.Startup;
> +import javax.ejb.TransactionAttribute;
> +import javax.ejb.TransactionAttributeType;
> +import javax.sql.DataSource;
> +import java.sql.Connection;
> +import java.sql.DriverManager;
> +import java.sql.DriverPropertyInfo;
> +import java.sql.ResultSet;
> +import java.sql.SQLException;
> +import java.sql.SQLFeatureNotSupportedException;
> +import java.sql.Statement;
> +import java.util.Properties;
> +import java.util.logging.Logger;
> +
> +import static org.junit.Assert.assertFalse;
> +import static org.junit.Assert.assertTrue;
> +
> +@RunWith(ApplicationComposer.class)
> +public class CipheredPasswordDataSourceTest {
> +    private static final String URL = "jdbc:fake://sthg:3306";
> +    private static final String USER = "pete";
> +    private static final String ENCRYPTED_PASSWORD = "This is the
> encrypted value.";
> +
> +    @EJB
> +    private Persister persistManager;
> +
> +    @Resource
> +    private DataSource ds;
> +
> +    @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", FakeDriver.class.getName());
> +        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", "10");
> +        p.put("managed.maxActive", "10");
> +        p.put("managed.maxIdle", "10");
> +        p.put("managed.minIdle", "10");
> +        p.put("managed.maxWait", "200");
> +        p.put("managed.defaultAutoCommit", "false");
> +        p.put("managed.defaultReadOnly", "false");
> +        p.put("managed.testOnBorrow", "true");
> +        p.put("managed.testOnReturn", "true");
> +        p.put("managed.testWhileIdle", "true");
> +
> +        return p;
> +    }
> +
> +    @Module
> +    public EjbJar app() throws Exception {
> +        return new EjbJar()
> +                .enterpriseBean(new
> SingletonBean(Persister.class).localBean());
> +
> +    }
> +
> +    @Before
> +    public void createDatasource(){
> +        // This is to make sure TomEE created the data source.
> +        persistManager.save();
> +    }
> +
> +    @Test
> +    public void rebuild() {
> +        // because of the exception at startup, the pool creating aborted
> +        // before fixing this bug, the password was already decrypted, but
> the data source field
> +        // wasn't yet initialized, so this.data source == null
> +        // but the next time we try to initialize it, it tries to decrypt
> and already decrypted password
> +
> +        try {
> +            ds.getConnection();
> +
> +        } catch (final SQLException e) {
> +            System.out.println(e.getMessage());
> +            // success - it throws the SQLException from the connect
> +            // but it does not fail with the IllegalArgumentException from
> the Password Cipher
> +        }
> +    }
> +
> +    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)));
> +
> +            // we want to know if the password as already been called
> +            if (encryptedPassword.length == 0) {
> +                throw new IllegalArgumentException("Can only decrypt a non
> empty string.");
> +            }
> +
> +            return "";
> +        }
> +    }
> +
> +    public static class FakeDriver implements java.sql.Driver {
> +        public boolean acceptsURL(final String url) throws SQLException {
> +            return false;
> +        }
> +
> +        public Logger getParentLogger() throws
> SQLFeatureNotSupportedException {
> +            return null;
> +        }
> +
> +        public Connection connect(final String url, final Properties info)
> throws SQLException {
> +            throw new SQLException("Pete said it first fails here!");
> +        }
> +
> +        public int getMajorVersion() {
> +            return 0;
> +        }
> +
> +        public int getMinorVersion() {
> +            return 0;
> +        }
> +
> +        public DriverPropertyInfo[] getPropertyInfo(final String url,
> final Properties info) throws SQLException {
> +            return new DriverPropertyInfo[0];
> +        }
> +
> +        public boolean jdbcCompliant() {
> +            return false;
> +        }
> +    }
> +
> +    @LocalBean
> +    @Singleton
> +    @TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
> +    public static class Persister {
> +
> +        @Resource(name = "managed")
> +        private DataSource ds;
> +
> +        public void save() {
> +            try {
> +                ds.getConnection();
> +
> +            } catch (SQLException e) {
> +                System.err.println("Generated SQL error > " +
> e.getMessage());
> +            }
> +        }
> +    }
> +
> +
> +}
>

Reply via email to