JAMES-2525 Guice provider for the PayloadCodecs With this final step the object storage gains symetric key encryption capability.
Project: http://git-wip-us.apache.org/repos/asf/james-project/repo Commit: http://git-wip-us.apache.org/repos/asf/james-project/commit/7eb3811c Tree: http://git-wip-us.apache.org/repos/asf/james-project/tree/7eb3811c Diff: http://git-wip-us.apache.org/repos/asf/james-project/diff/7eb3811c Branch: refs/heads/master Commit: 7eb3811ce38c77be65798ad224839687bf833255 Parents: 3d1981d Author: Jean Helou <[email protected]> Authored: Thu Sep 13 17:11:09 2018 +0200 Committer: Benoit Tellier <[email protected]> Committed: Wed Oct 31 08:48:47 2018 +0700 ---------------------------------------------------------------------- .../ObjectStorageBlobsDAOBuilder.java | 1 + .../ObjectStorageBlobStoreModule.java | 4 +- .../objectstorage/PayloadCodecProvider.java | 59 +++++ .../modules/objectstorage/PayloadCodecs.java | 59 +++++ .../objectstorage/MapConfigurationBuilder.java | 41 ++++ .../objectstorage/PayloadCodecProviderTest.java | 222 +++++++++++++++++++ 6 files changed, 385 insertions(+), 1 deletion(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/james-project/blob/7eb3811c/server/blob/blob-objectstorage/src/main/java/org/apache/james/blob/objectstorage/ObjectStorageBlobsDAOBuilder.java ---------------------------------------------------------------------- diff --git a/server/blob/blob-objectstorage/src/main/java/org/apache/james/blob/objectstorage/ObjectStorageBlobsDAOBuilder.java b/server/blob/blob-objectstorage/src/main/java/org/apache/james/blob/objectstorage/ObjectStorageBlobsDAOBuilder.java index 535741a..25534d0 100644 --- a/server/blob/blob-objectstorage/src/main/java/org/apache/james/blob/objectstorage/ObjectStorageBlobsDAOBuilder.java +++ b/server/blob/blob-objectstorage/src/main/java/org/apache/james/blob/objectstorage/ObjectStorageBlobsDAOBuilder.java @@ -53,6 +53,7 @@ public class ObjectStorageBlobsDAOBuilder { this.payloadCodec = Optional.of(payloadCodec); return this; } + public ObjectStorageBlobsDAOBuilder payloadCodec(Optional<PayloadCodec> payloadCodec) { this.payloadCodec = payloadCodec; return this; http://git-wip-us.apache.org/repos/asf/james-project/blob/7eb3811c/server/container/guice/blob-objectstorage-guice/src/main/java/org/apache/james/modules/objectstorage/ObjectStorageBlobStoreModule.java ---------------------------------------------------------------------- diff --git a/server/container/guice/blob-objectstorage-guice/src/main/java/org/apache/james/modules/objectstorage/ObjectStorageBlobStoreModule.java b/server/container/guice/blob-objectstorage-guice/src/main/java/org/apache/james/modules/objectstorage/ObjectStorageBlobStoreModule.java index 85cebf3..b6cba09 100644 --- a/server/container/guice/blob-objectstorage-guice/src/main/java/org/apache/james/modules/objectstorage/ObjectStorageBlobStoreModule.java +++ b/server/container/guice/blob-objectstorage-guice/src/main/java/org/apache/james/modules/objectstorage/ObjectStorageBlobStoreModule.java @@ -21,6 +21,7 @@ package org.apache.james.modules.objectstorage; import org.apache.james.blob.api.BlobStore; import org.apache.james.blob.objectstorage.ObjectStorageBlobsDAO; +import org.apache.james.blob.objectstorage.PayloadCodec; import com.google.inject.AbstractModule; import com.google.inject.Scopes; @@ -29,7 +30,8 @@ public class ObjectStorageBlobStoreModule extends AbstractModule { @Override protected void configure() { + bind(PayloadCodec.class).toProvider(PayloadCodecProvider.class).in(Scopes.SINGLETON); bind(ObjectStorageBlobsDAO.class).toProvider(ObjectStorageBlobsDAOProvider.class).in(Scopes.SINGLETON); - bind(BlobStore.class).to(ObjectStorageBlobsDAO.class); + bind(BlobStore.class).toProvider(ObjectStorageBlobsDAOProvider.class).in(Scopes.SINGLETON); } } http://git-wip-us.apache.org/repos/asf/james-project/blob/7eb3811c/server/container/guice/blob-objectstorage-guice/src/main/java/org/apache/james/modules/objectstorage/PayloadCodecProvider.java ---------------------------------------------------------------------- diff --git a/server/container/guice/blob-objectstorage-guice/src/main/java/org/apache/james/modules/objectstorage/PayloadCodecProvider.java b/server/container/guice/blob-objectstorage-guice/src/main/java/org/apache/james/modules/objectstorage/PayloadCodecProvider.java new file mode 100644 index 0000000..55f0d6c --- /dev/null +++ b/server/container/guice/blob-objectstorage-guice/src/main/java/org/apache/james/modules/objectstorage/PayloadCodecProvider.java @@ -0,0 +1,59 @@ +/**************************************************************** + * 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.james.modules.objectstorage; + +import java.io.FileNotFoundException; + +import javax.inject.Inject; +import javax.inject.Provider; + +import org.apache.commons.configuration.Configuration; +import org.apache.commons.configuration.ConfigurationException; +import org.apache.james.blob.objectstorage.PayloadCodec; +import org.apache.james.utils.PropertiesProvider; + +import com.amazonaws.util.StringUtils; +import com.google.common.base.Preconditions; + +public class PayloadCodecProvider implements Provider<PayloadCodec> { + private static final String OBJECTSTORAGE_CONFIGURATION_NAME = "objectstorage"; + private static final String OBJECTSTORAGE_PAYLOAD_CODEC = "objectstorage.payload.codec"; + + private final Configuration configuration; + + @Inject + public PayloadCodecProvider(PropertiesProvider propertiesProvider) + throws ConfigurationException { + try { + this.configuration = + propertiesProvider.getConfiguration(OBJECTSTORAGE_CONFIGURATION_NAME); + } catch (FileNotFoundException e) { + throw new ConfigurationException(OBJECTSTORAGE_CONFIGURATION_NAME + "configuration was not found"); + } + } + + @Override + public PayloadCodec get() { + String codecName = configuration.getString(OBJECTSTORAGE_PAYLOAD_CODEC, null); + Preconditions.checkArgument(!StringUtils.isNullOrEmpty(codecName), + OBJECTSTORAGE_PAYLOAD_CODEC + " is a mandatory configuration value"); + return PayloadCodecs.valueOf(codecName).codec(configuration); + } +} http://git-wip-us.apache.org/repos/asf/james-project/blob/7eb3811c/server/container/guice/blob-objectstorage-guice/src/main/java/org/apache/james/modules/objectstorage/PayloadCodecs.java ---------------------------------------------------------------------- diff --git a/server/container/guice/blob-objectstorage-guice/src/main/java/org/apache/james/modules/objectstorage/PayloadCodecs.java b/server/container/guice/blob-objectstorage-guice/src/main/java/org/apache/james/modules/objectstorage/PayloadCodecs.java new file mode 100644 index 0000000..3e468ed --- /dev/null +++ b/server/container/guice/blob-objectstorage-guice/src/main/java/org/apache/james/modules/objectstorage/PayloadCodecs.java @@ -0,0 +1,59 @@ +/**************************************************************** + * 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.james.modules.objectstorage; + +import org.apache.commons.configuration.Configuration; +import org.apache.james.blob.objectstorage.AESPayloadCodec; +import org.apache.james.blob.objectstorage.DefaultPayloadCodec; +import org.apache.james.blob.objectstorage.PayloadCodec; +import org.apache.james.blob.objectstorage.crypto.CryptoConfig; + +import com.amazonaws.util.StringUtils; +import com.google.common.base.Preconditions; + +public enum PayloadCodecs { + DEFAULT { + @Override + public PayloadCodec codec(Configuration configuration) { + return new DefaultPayloadCodec(); + } + }, + AES256 { + @Override + public PayloadCodec codec(Configuration configuration) { + String salt = configuration.getString(OBJECTSTORAGE_AES256_HEXSALT); + String password = configuration.getString(OBJECTSTORAGE_AES256_PASSWORD); + Preconditions.checkArgument(!StringUtils.isNullOrEmpty(salt), + OBJECTSTORAGE_AES256_HEXSALT + " is a " + + "mandatory configuration value"); + Preconditions.checkArgument(!StringUtils.isNullOrEmpty(password), + OBJECTSTORAGE_AES256_PASSWORD + " is a " + + "mandatory configuration value"); + return new AESPayloadCodec(new CryptoConfig(salt, password.toCharArray())); + } + }; + + private static final String OBJECTSTORAGE_AES256_HEXSALT = "objectstorage.aes256.hexsalt"; + private static final String OBJECTSTORAGE_AES256_PASSWORD = "objectstorage.aes256.password"; + + public abstract PayloadCodec codec(Configuration configuration); + + +} http://git-wip-us.apache.org/repos/asf/james-project/blob/7eb3811c/server/container/guice/blob-objectstorage-guice/src/test/java/org/apache/james/modules/objectstorage/MapConfigurationBuilder.java ---------------------------------------------------------------------- diff --git a/server/container/guice/blob-objectstorage-guice/src/test/java/org/apache/james/modules/objectstorage/MapConfigurationBuilder.java b/server/container/guice/blob-objectstorage-guice/src/test/java/org/apache/james/modules/objectstorage/MapConfigurationBuilder.java new file mode 100644 index 0000000..1753b54 --- /dev/null +++ b/server/container/guice/blob-objectstorage-guice/src/test/java/org/apache/james/modules/objectstorage/MapConfigurationBuilder.java @@ -0,0 +1,41 @@ +/**************************************************************** + * 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.james.modules.objectstorage; + +import org.apache.commons.configuration.MapConfiguration; + +import com.google.common.collect.ImmutableMap; + +class MapConfigurationBuilder { + private ImmutableMap.Builder<String, Object> config; + + public MapConfigurationBuilder() { + this.config = new ImmutableMap.Builder<>(); + } + + public MapConfigurationBuilder put(String key, Object value) { + config.put(key, value); + return this; + } + + public MapConfiguration build() { + return new MapConfiguration(config.build()); + } +} http://git-wip-us.apache.org/repos/asf/james-project/blob/7eb3811c/server/container/guice/blob-objectstorage-guice/src/test/java/org/apache/james/modules/objectstorage/PayloadCodecProviderTest.java ---------------------------------------------------------------------- diff --git a/server/container/guice/blob-objectstorage-guice/src/test/java/org/apache/james/modules/objectstorage/PayloadCodecProviderTest.java b/server/container/guice/blob-objectstorage-guice/src/test/java/org/apache/james/modules/objectstorage/PayloadCodecProviderTest.java new file mode 100644 index 0000000..32c0036 --- /dev/null +++ b/server/container/guice/blob-objectstorage-guice/src/test/java/org/apache/james/modules/objectstorage/PayloadCodecProviderTest.java @@ -0,0 +1,222 @@ +/**************************************************************** + * 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.james.modules.objectstorage; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import java.io.FileNotFoundException; +import java.util.HashMap; +import java.util.Map; + +import org.apache.commons.configuration.Configuration; +import org.apache.commons.configuration.ConfigurationException; +import org.apache.commons.configuration.MapConfiguration; +import org.apache.commons.lang3.StringUtils; +import org.apache.james.blob.objectstorage.AESPayloadCodec; +import org.apache.james.blob.objectstorage.DefaultPayloadCodec; +import org.apache.james.blob.objectstorage.PayloadCodec; +import org.apache.james.utils.PropertiesProvider; +import org.junit.jupiter.api.Test; + +import com.google.common.collect.ImmutableMap; + +class PayloadCodecProviderTest { + + private static final FakePropertiesProvider DEFAULT_PROPERTIES_PROVIDER = + FakePropertiesProvider.builder() + .register("objectstorage", + newConfigBuilder() + .put("objectstorage.payload.codec", PayloadCodecs.DEFAULT.name()) + .build()) + .build(); + + private static final FakePropertiesProvider EMPTY_PROPERTIES_PROVIDER = + FakePropertiesProvider.builder() + .register("objectstorage", + newConfigBuilder().build()) + .build(); + + private static final FakePropertiesProvider AES_PROPERTIES_PROVIDER = + FakePropertiesProvider.builder() + .register("objectstorage", + newConfigBuilder() + .put("objectstorage.payload.codec", PayloadCodecs.AES256.name()) + .put("objectstorage.aes256.hexsalt", "12345123451234512345") + .put("objectstorage.aes256.password", "james is great") + .build()) + .build(); + + private static final FakePropertiesProvider MISSING_SALT_PROPERTIES_PROVIDER = + FakePropertiesProvider.builder() + .register("objectstorage", + newConfigBuilder() + .put("objectstorage.payload.codec", PayloadCodecs.AES256.name()) + .put("objectstorage.aes256.password", "james is great") + .build()) + .build(); + + private static final FakePropertiesProvider EMPTY_SALT_PROPERTIES_PROVIDER = + FakePropertiesProvider.builder() + .register("objectstorage", + newConfigBuilder() + .put("objectstorage.payload.codec", PayloadCodecs.AES256.name()) + .put("objectstorage.aes256.hexsalt", "") + .put("objectstorage.aes256.password", "james is great") + .build()) + .build(); + + private static final FakePropertiesProvider MISSING_PASSWORD_PROPERTIES_PROVIDER = + FakePropertiesProvider.builder() + .register("objectstorage", + newConfigBuilder() + .put("objectstorage.payload.codec", PayloadCodecs.AES256.name()) + .put("objectstorage.aes256.hexsalt", "12345123451234512345") + .build()) + .build(); + + private static final FakePropertiesProvider EMPTY_PASSWORD_PROPERTIES_PROVIDER = + FakePropertiesProvider.builder() + .register("objectstorage", + newConfigBuilder() + .put("objectstorage.payload.codec", PayloadCodecs.AES256.name()) + .put("objectstorage.aes256.hexsalt", "12345123451234512345") + .put("objectstorage.aes256.password", "") + .build()) + .build(); + + @Test + void shouldBuildADefaultPayloadCodecForDefaultConfig() throws Exception { + PayloadCodec payloadCodec = new PayloadCodecProvider(DEFAULT_PROPERTIES_PROVIDER).get(); + assertThat(payloadCodec).isInstanceOf(DefaultPayloadCodec.class); + } + + @Test + void shouldBuildAnAESPayloadCodecForAESConfig() throws Exception { + PayloadCodec payloadCodec = new PayloadCodecProvider(AES_PROPERTIES_PROVIDER).get(); + assertThat(payloadCodec).isInstanceOf(AESPayloadCodec.class); + } + + @Test + void shouldFailIfCodecKeyIsMissing() throws Exception { + assertThatThrownBy(() -> new PayloadCodecProvider(EMPTY_PROPERTIES_PROVIDER).get()).isInstanceOf(IllegalArgumentException.class); + } + + @Test + void shouldFailIfCodecKeyIsIncorrect() throws Exception { + FakePropertiesProvider propertiesWithTypo = FakePropertiesProvider.builder() + .register("objectstorage", + newConfigBuilder() + .put("objectstorage.payload.codec", "aes255") + .put("objectstorage.aes256.password", "james is great") + .build()) + .build(); + assertThatThrownBy(() -> new PayloadCodecProvider(propertiesWithTypo).get()).isInstanceOf(IllegalArgumentException.class); + } + + @Test + void shouldFailForAESCodecWhenSaltKeyIsMissing() throws Exception { + assertThatThrownBy(() -> new PayloadCodecProvider(MISSING_SALT_PROPERTIES_PROVIDER).get()).isInstanceOf(IllegalArgumentException.class); + } + + @Test + void shouldFailForAESCodecWhenSaltKeyIsEmpty() throws Exception { + assertThatThrownBy(() -> new PayloadCodecProvider(EMPTY_SALT_PROPERTIES_PROVIDER).get()).isInstanceOf(IllegalArgumentException.class); + } + + @Test + void shouldFailForAESCodecWhenPasswordKeyIsMissing() throws Exception { + + assertThatThrownBy(() -> new PayloadCodecProvider(MISSING_PASSWORD_PROPERTIES_PROVIDER).get()).isInstanceOf(IllegalArgumentException.class); + } + + @Test + void shouldFailForAESCodecWhenPasswordKeyIsEmpty() throws Exception { + + assertThatThrownBy(() -> new PayloadCodecProvider(EMPTY_PASSWORD_PROPERTIES_PROVIDER).get()).isInstanceOf(IllegalArgumentException.class); + } + + private static MapConfigurationBuilder newConfigBuilder() { + return new MapConfigurationBuilder(); + } + + private static class FakePropertiesProvider extends PropertiesProvider { + private Map<String, Configuration> configurations; + + public FakePropertiesProvider(Map<String, Configuration> configurations) { + super(null); + this.configurations = configurations; + } + + + @Override + public Configuration getConfiguration(String fileName) throws FileNotFoundException, ConfigurationException { + if (configurations.containsKey(fileName)) { + return configurations.get(fileName); + } else { + throw new FileNotFoundException( + "no configuration defined for " + + fileName + + " know configurations are (" + + StringUtils.join(configurations.keySet(), ",") + + ")"); + } + } + + public static FakePropertiesProviderBuilder builder() { + return new FakePropertiesProviderBuilder(); + } + + static class FakePropertiesProviderBuilder { + private final Map<String, Configuration> configurations; + + public FakePropertiesProviderBuilder() { + configurations = new HashMap<>(); + } + + public FakePropertiesProviderBuilder register(String objectstorage, Configuration configuration) { + configurations.put(objectstorage, configuration); + return this; + } + + public FakePropertiesProvider build() { + return new FakePropertiesProvider(configurations); + } + } + } + + private static class MapConfigurationBuilder { + private ImmutableMap.Builder<String, Object> config; + + public MapConfigurationBuilder() { + this.config = new ImmutableMap.Builder<>(); + } + + public MapConfigurationBuilder put(String key, Object value) { + config.put(key, value); + return this; + } + + public MapConfiguration build() { + return new MapConfiguration(config.build()); + } + } +} + --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
