Repository: tomee Updated Branches: refs/heads/master 3f79e41b8 -> 7066eaa47
TOMEE-1603 cdi password cipher basic support Project: http://git-wip-us.apache.org/repos/asf/tomee/repo Commit: http://git-wip-us.apache.org/repos/asf/tomee/commit/7066eaa4 Tree: http://git-wip-us.apache.org/repos/asf/tomee/tree/7066eaa4 Diff: http://git-wip-us.apache.org/repos/asf/tomee/diff/7066eaa4 Branch: refs/heads/master Commit: 7066eaa4731379e9db11a009c667b5726505f701 Parents: 3f79e41 Author: Romain Manni-Bucau <[email protected]> Authored: Thu Jun 11 05:34:58 2015 +0200 Committer: Romain Manni-Bucau <[email protected]> Committed: Thu Jun 11 05:34:58 2015 +0200 ---------------------------------------------------------------------- .../openejb/assembler/classic/Assembler.java | 18 ++++- .../openejb/cipher/CdiPasswordCipher.java | 66 ++++++++++++++++ .../openejb/testing/ApplicationComposers.java | 4 +- .../openejb/util/PropertyPlaceHolderHelper.java | 18 ++++- .../cdi | 1 + .../openejb/cipher/CdiPasswordCipherTest.java | 82 ++++++++++++++++++++ 6 files changed, 182 insertions(+), 7 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/tomee/blob/7066eaa4/container/openejb-core/src/main/java/org/apache/openejb/assembler/classic/Assembler.java ---------------------------------------------------------------------- diff --git a/container/openejb-core/src/main/java/org/apache/openejb/assembler/classic/Assembler.java b/container/openejb-core/src/main/java/org/apache/openejb/assembler/classic/Assembler.java index d3af5f2..33e10c2 100644 --- a/container/openejb-core/src/main/java/org/apache/openejb/assembler/classic/Assembler.java +++ b/container/openejb-core/src/main/java/org/apache/openejb/assembler/classic/Assembler.java @@ -2605,9 +2605,13 @@ public class Assembler extends AssemblerTool implements org.apache.openejb.spi.A } public void createResource(final ResourceInfo serviceInfo) throws OpenEJBException { - final Object service = "true".equalsIgnoreCase(String.valueOf(serviceInfo.properties.remove("Lazy"))) ? + final boolean usesCdiPwdCipher = usesCdiPwdCipher(serviceInfo); + final Object service = "true".equalsIgnoreCase(String.valueOf(serviceInfo.properties.remove("Lazy"))) || usesCdiPwdCipher ? newLazyResource(serviceInfo) : doCreateResource(serviceInfo); + if (usesCdiPwdCipher && !serviceInfo.properties.contains("InitializeAfterDeployment")) { + serviceInfo.properties.put("InitializeAfterDeployment", "true"); + } bindResource(serviceInfo.id, service, false); for (final String alias : serviceInfo.aliases) { @@ -2630,6 +2634,15 @@ public class Assembler extends AssemblerTool implements org.apache.openejb.spi.A } } + private boolean usesCdiPwdCipher(final ResourceInfo serviceInfo) { + for (final Object val : serviceInfo.properties.values()) { + if (String.valueOf(val).startsWith("cipher:cdi:")) { + return true; + } + } + return false; + } + private LazyResource newLazyResource(final ResourceInfo serviceInfo) { final ClassLoader loader = Thread.currentThread().getContextClassLoader(); return new LazyResource(new Callable<Object>() { @@ -2684,7 +2697,6 @@ public class Assembler extends AssemblerTool implements org.apache.openejb.spi.A } }); - final Properties props = PropertyPlaceHolderHelper.holds(serviceInfo.properties); if (serviceInfo.properties.containsKey("Definition")) { try { // we catch classcast etc..., if it fails it is not important final InputStream is = new ByteArrayInputStream(serviceInfo.properties.getProperty("Definition").getBytes()); @@ -3236,7 +3248,7 @@ public class Assembler extends AssemblerTool implements org.apache.openejb.spi.A final ObjectRecipe serviceRecipe = prepareRecipe(info); final Object value = info.properties.remove("SkipImplicitAttributes"); // we don't want this one to go in recipe - serviceRecipe.setAllProperties(info.properties); + serviceRecipe.setAllProperties(PropertyPlaceHolderHelper.simpleHolds(info.properties)); if (value != null) { info.properties.put("SkipImplicitAttributes", value); } http://git-wip-us.apache.org/repos/asf/tomee/blob/7066eaa4/container/openejb-core/src/main/java/org/apache/openejb/cipher/CdiPasswordCipher.java ---------------------------------------------------------------------- diff --git a/container/openejb-core/src/main/java/org/apache/openejb/cipher/CdiPasswordCipher.java b/container/openejb-core/src/main/java/org/apache/openejb/cipher/CdiPasswordCipher.java new file mode 100644 index 0000000..0e8077e --- /dev/null +++ b/container/openejb-core/src/main/java/org/apache/openejb/cipher/CdiPasswordCipher.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.cipher; + +import org.apache.webbeans.config.WebBeansContext; +import org.apache.webbeans.container.BeanManagerImpl; + +import javax.enterprise.context.spi.CreationalContext; +import javax.enterprise.inject.spi.Bean; + +// use: cipher:cdi:<your bean class>:<encrypted pwd> +public final class CdiPasswordCipher implements PasswordCipher { + @Override + public char[] encrypt(final String plainPassword) { + throw new UnsupportedOperationException("cdi password cipher only supports decryption"); + } + + @Override + public String decrypt(final char[] encryptedPassword) { + final String string = new String(encryptedPassword); + final WebBeansContext wbc = WebBeansContext.currentInstance(); + final BeanManagerImpl mgr = wbc.getBeanManagerImpl(); + if (!mgr.isInUse()) { // not yet the time to use CDI, container is not started + // would be cool to log a warning here but would pollute the logs with false positives + return "cipher:cdi:" + string; + } + + final int split = string.indexOf(':'); + final String delegate = string.substring(0, split); + final String pwdStr = string.substring(split + 1, string.length()); + final char[] pwd = pwdStr.toCharArray(); + + try { + final Class<?> beanType = Thread.currentThread().getContextClassLoader().loadClass(delegate); + final Bean<?> bean = mgr.resolve(mgr.getBeans(beanType)); + if (bean == null) { + throw new IllegalArgumentException("No bean for " + delegate); + } + + final CreationalContext<?> cc = mgr.createCreationalContext(null); + try { + return PasswordCipher.class.cast(mgr.getReference(bean, PasswordCipher.class, cc)).decrypt(pwd); + } finally { + if (!mgr.isNormalScope(bean.getScope())) { + cc.release(); + } + } + } catch (final ClassNotFoundException e) { + throw new IllegalArgumentException("Can't find " + delegate, e); + } + } +} http://git-wip-us.apache.org/repos/asf/tomee/blob/7066eaa4/container/openejb-core/src/main/java/org/apache/openejb/testing/ApplicationComposers.java ---------------------------------------------------------------------- diff --git a/container/openejb-core/src/main/java/org/apache/openejb/testing/ApplicationComposers.java b/container/openejb-core/src/main/java/org/apache/openejb/testing/ApplicationComposers.java index 99fd7a2..495e7e9 100644 --- a/container/openejb-core/src/main/java/org/apache/openejb/testing/ApplicationComposers.java +++ b/container/openejb-core/src/main/java/org/apache/openejb/testing/ApplicationComposers.java @@ -414,6 +414,7 @@ public class ApplicationComposers { // Invoke the @Module producer methods to build out the AppModule int moduleNumber = 0; + int notBusinessModuleNumber = 0; // we dont consider resources.xml to set an app as standalone or not final Map<Object, List<Method>> moduleMethods = new HashMap<>(); findAnnotatedMethods(moduleMethods, Module.class); findAnnotatedMethods(moduleMethods, org.apache.openejb.junit.Module.class); @@ -601,6 +602,7 @@ public class ApplicationComposers { final Resources asResources = Resources.class.cast(obj); appModule.getResources().addAll(asResources.getResource()); appModule.getContainers().addAll(asResources.getContainer()); + notBusinessModuleNumber++; } else if (obj instanceof AppModule) { // we can probably go further here final AppModule module = (AppModule) obj; @@ -685,7 +687,7 @@ public class ApplicationComposers { } } - if (moduleNumber == 1 && webModulesNb == 1) { + if (moduleNumber - notBusinessModuleNumber == 1 && webModulesNb == 1) { appModule.setStandloneWebModule(); } http://git-wip-us.apache.org/repos/asf/tomee/blob/7066eaa4/container/openejb-core/src/main/java/org/apache/openejb/util/PropertyPlaceHolderHelper.java ---------------------------------------------------------------------- diff --git a/container/openejb-core/src/main/java/org/apache/openejb/util/PropertyPlaceHolderHelper.java b/container/openejb-core/src/main/java/org/apache/openejb/util/PropertyPlaceHolderHelper.java index b218d82..61cbc7c 100644 --- a/container/openejb-core/src/main/java/org/apache/openejb/util/PropertyPlaceHolderHelper.java +++ b/container/openejb-core/src/main/java/org/apache/openejb/util/PropertyPlaceHolderHelper.java @@ -85,8 +85,11 @@ public final class PropertyPlaceHolderHelper { } public static String value(final String aw) { - if (aw == null || !aw.contains(PREFIX) || !aw.contains(SUFFIX)) { - return aw; + if (aw == null) { + return null; + } + if (!aw.contains(PREFIX) || !aw.contains(SUFFIX)) { + return decryptIfNeeded(aw); } String value = CACHE.getProperty(aw); @@ -99,7 +102,15 @@ public final class PropertyPlaceHolderHelper { return value; } + public static Properties simpleHolds(final Properties properties) { + return holds(properties, false); + } + public static Properties holds(final Properties properties) { + return holds(properties, true); + } + + private static Properties holds(final Properties properties, final boolean cache) { // we can put null values in SuperProperties, since properties is often of this type we need to tolerate it final Properties updated = new SuperProperties(); if (properties == null) { @@ -109,7 +120,8 @@ public final class PropertyPlaceHolderHelper { for (final Map.Entry<Object, Object> entry : properties.entrySet()) { final Object rawValue = entry.getValue(); if (rawValue instanceof String) { - updated.put(entry.getKey(), value((String) rawValue)); + final String value = (String) rawValue; + updated.put(entry.getKey(), cache ? value(value) : simpleValue(value)); } else { updated.put(entry.getKey(), rawValue); } http://git-wip-us.apache.org/repos/asf/tomee/blob/7066eaa4/container/openejb-core/src/main/resources/META-INF/org.apache.openejb.cipher.PasswordCipher/cdi ---------------------------------------------------------------------- diff --git a/container/openejb-core/src/main/resources/META-INF/org.apache.openejb.cipher.PasswordCipher/cdi b/container/openejb-core/src/main/resources/META-INF/org.apache.openejb.cipher.PasswordCipher/cdi new file mode 100644 index 0000000..f8a99c1 --- /dev/null +++ b/container/openejb-core/src/main/resources/META-INF/org.apache.openejb.cipher.PasswordCipher/cdi @@ -0,0 +1 @@ +org.apache.openejb.cipher.CdiPasswordCipher http://git-wip-us.apache.org/repos/asf/tomee/blob/7066eaa4/container/openejb-core/src/test/java/org/apache/openejb/cipher/CdiPasswordCipherTest.java ---------------------------------------------------------------------- diff --git a/container/openejb-core/src/test/java/org/apache/openejb/cipher/CdiPasswordCipherTest.java b/container/openejb-core/src/test/java/org/apache/openejb/cipher/CdiPasswordCipherTest.java new file mode 100644 index 0000000..e42cb2b --- /dev/null +++ b/container/openejb-core/src/test/java/org/apache/openejb/cipher/CdiPasswordCipherTest.java @@ -0,0 +1,82 @@ +/** + * 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.cipher; + +import org.apache.openejb.config.sys.Resource; +import org.apache.openejb.config.sys.Resources; +import org.apache.openejb.junit.ApplicationComposer; +import org.apache.openejb.testing.Classes; +import org.apache.openejb.testing.Module; +import org.apache.openejb.testing.SimpleLog; +import org.junit.Test; +import org.junit.runner.RunWith; + +import javax.enterprise.context.ApplicationScoped; +import javax.inject.Inject; + +import static org.junit.Assert.assertEquals; + +@SimpleLog +@Classes(innerClassesAsBean = true, cdi = true) +@RunWith(ApplicationComposer.class) +public class CdiPasswordCipherTest { + @javax.annotation.Resource + private AResource resource; + + @Test + public void checkPwd() { + assertEquals("decrypted", resource.thePassword); + } + + @Module + public Resources resources() { + final Resource resource = new Resource(); + resource.setClassName(AResource.class.getName()); + resource.setId("the"); + resource.getProperties().setProperty("thePassword", "cipher:cdi:" + AdvancedAlgorithm.class.getName() + ":this"); + + final Resources resources = new Resources(); + resources.getResource().add(resource); + return resources; + } + + public static class AResource { + private String thePassword; + } + + public static class AdvancedAlgorithm implements PasswordCipher { + @Inject + private Decrypter decrypter; + + @Override + public char[] encrypt(final String plainPassword) { + throw new UnsupportedOperationException(); + } + + @Override + public String decrypt(final char[] encryptedPassword) { + return decrypter.decrypt(encryptedPassword); + } + } + + @ApplicationScoped + public static class Decrypter { + public String decrypt(final char[] encryptedPassword) { + return new String(encryptedPassword).equals("this") ? "decrypted" : "failed"; + } + } +}
