This is an automated email from the ASF dual-hosted git repository. kwin pushed a commit to branch feature/encryption in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-caconfig-impl.git
commit 9ab87f1c3da0a69298db82af544d4f05d1d10cc3 Author: Konrad Windszus <[email protected]> AuthorDate: Thu Jul 30 18:18:48 2026 +0200 SLING-13282 Support automatic decryption in Context-Aware Configurations Leverage Sling Commons Crypto --- bnd.bnd | 1 + pom.xml | 10 ++++- .../caconfig/impl/ConfigurationBuilderImpl.java | 46 ++++++++++++++++------ .../caconfig/impl/ConfigurationResolverImpl.java | 6 +++ .../apache/sling/caconfig/impl/DecryptService.java | 38 ++++++++++++++++++ .../impl/metadata/AnnotationClassParser.java | 3 +- 6 files changed, 88 insertions(+), 16 deletions(-) diff --git a/bnd.bnd b/bnd.bnd index 6d91515..8c1caeb 100644 --- a/bnd.bnd +++ b/bnd.bnd @@ -1,3 +1,4 @@ Import-Package:\ org.apache.felix.webconsole;resolution:=optional,\ + org.apache.sling.commons.crypto.*;resolution:=optional,\ * diff --git a/pom.xml b/pom.xml index ccb0e49..f4b0077 100644 --- a/pom.xml +++ b/pom.xml @@ -43,8 +43,8 @@ <properties> <project.build.outputTimestamp>2025-03-12T12:14:50Z</project.build.outputTimestamp> <sling.java.version>11</sling.java.version> - <caconfig.api.version>1.2.0</caconfig.api.version> - <caconfig.spi.version>1.4.0</caconfig.spi.version> + <caconfig.api.version>1.4.0-SNAPSHOT</caconfig.api.version> + <caconfig.spi.version>1.5.0-SNAPSHOT</caconfig.spi.version> <!-- integration tests --> <sling.starter.version>14</sling.starter.version> <starter.min.bundles.count>200</starter.min.bundles.count> @@ -86,6 +86,12 @@ <version>${caconfig.spi.version}</version> <scope>compile</scope> </dependency> + <dependency> + <groupId>org.apache.sling</groupId> + <artifactId>org.apache.sling.commons.crypto</artifactId> + <version>1.2.0</version> + <scope>compile</scope> + </dependency> <dependency> <groupId>jakarta.json</groupId> <artifactId>jakarta.json-api</artifactId> diff --git a/src/main/java/org/apache/sling/caconfig/impl/ConfigurationBuilderImpl.java b/src/main/java/org/apache/sling/caconfig/impl/ConfigurationBuilderImpl.java index 9181ea0..bddcda4 100644 --- a/src/main/java/org/apache/sling/caconfig/impl/ConfigurationBuilderImpl.java +++ b/src/main/java/org/apache/sling/caconfig/impl/ConfigurationBuilderImpl.java @@ -25,6 +25,8 @@ import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; +import java.util.Optional; +import java.util.function.UnaryOperator; import org.apache.commons.collections4.IteratorUtils; import org.apache.commons.collections4.ResettableListIterator; @@ -63,6 +65,7 @@ class ConfigurationBuilderImpl implements ConfigurationBuilder { private final ConfigurationInheritanceStrategy configurationInheritanceStrategy; private final ConfigurationOverrideMultiplexer configurationOverrideMultiplexer; private final ConfigurationMetadataProvider configurationMetadataProvider; + private final Optional<UnaryOperator<String>> decryptOperator; private final Collection<String> configBucketNames; private final String configName; @@ -76,6 +79,7 @@ class ConfigurationBuilderImpl implements ConfigurationBuilder { final ConfigurationInheritanceStrategy configurationInheritanceStrategy, final ConfigurationOverrideMultiplexer configurationOverrideMultiplexer, final ConfigurationMetadataProvider configurationMetadataProvider, + final Optional<UnaryOperator<String>> decryptOperator, final Collection<String> configBucketNames) { this( resource, @@ -85,6 +89,7 @@ class ConfigurationBuilderImpl implements ConfigurationBuilder { configurationInheritanceStrategy, configurationOverrideMultiplexer, configurationMetadataProvider, + decryptOperator, configBucketNames, null); } @@ -97,6 +102,7 @@ class ConfigurationBuilderImpl implements ConfigurationBuilder { final ConfigurationInheritanceStrategy configurationInheritanceStrategy, final ConfigurationOverrideMultiplexer configurationOverrideMultiplexer, final ConfigurationMetadataProvider configurationMetadataProvider, + final Optional<UnaryOperator<String>> decryptOperator, final Collection<String> configBucketNames, final String configName) { this.contentResource = resource; @@ -106,6 +112,7 @@ class ConfigurationBuilderImpl implements ConfigurationBuilder { this.configurationInheritanceStrategy = configurationInheritanceStrategy; this.configurationOverrideMultiplexer = configurationOverrideMultiplexer; this.configurationMetadataProvider = configurationMetadataProvider; + this.decryptOperator = decryptOperator; this.configBucketNames = configBucketNames; this.configName = configName; } @@ -121,6 +128,7 @@ class ConfigurationBuilderImpl implements ConfigurationBuilder { configurationInheritanceStrategy, configurationOverrideMultiplexer, configurationMetadataProvider, + decryptOperator, configBucketNames, configName); } @@ -254,16 +262,16 @@ class ConfigurationBuilderImpl implements ConfigurationBuilder { } /** - * Apply default values from configuration metadata (where no real data is present). + * Apply transformations on the underlying resource, e.g. to add default values from configuration metadata (where no real data is present) or to decrypt encrypted values. * @param resource Resource * @param configName Configuration name - * @return null if no default values found, or a wrapped resource with added default properties. + * @return the resource with transformed properties. */ - private Resource applyDefaultValues(Resource resource, String configName) { + private Resource applyTransformations(Resource resource, String configName) { if (resource == null) { return null; } - Map<String, Object> updatedMap = applyDefaultValues(resource.getValueMap(), configName); + Map<String, Object> updatedMap = applyPropertyMetadata(resource.getValueMap(), configName); if (updatedMap == null) { return resource; } @@ -271,12 +279,12 @@ class ConfigurationBuilderImpl implements ConfigurationBuilder { } /** - * Apply default values from configuration metadata (where no real data is present). + * Apply default values from configuration metadata (where no real data is present) and decrypt encrypted values. * @param props Properties * @param configName Configuration name - * @return null if no default values found, or a new map with added default properties. + * @return null if no transformations applied, or a new map with transformed properties. */ - private Map<String, Object> applyDefaultValues(Map<String, Object> props, String configName) { + private Map<String, Object> applyPropertyMetadata(Map<String, Object> props, String configName) { ConfigurationMetadata metadata = configurationMetadataProvider.getConfigurationMetadata(configName); if (metadata == null) { // probably a configuration list - remove item name from end @@ -288,17 +296,29 @@ class ConfigurationBuilderImpl implements ConfigurationBuilder { return null; } } - Map<String, Object> updatedMap = new HashMap<>(); + Map<String, Object> updatedMap = new HashMap<>(props); + boolean isTransformed = false; for (PropertyMetadata<?> propertyMetadata : metadata.getPropertyMetadata().values()) { - if (propertyMetadata.getDefaultValue() != null) { + if (propertyMetadata.getDefaultValue() != null && !props.containsKey(propertyMetadata.getName())) { updatedMap.put(propertyMetadata.getName(), propertyMetadata.getDefaultValue()); + isTransformed = true; + } + if (propertyMetadata.isEncrypted() && props.containsKey(propertyMetadata.getName())) { + Object value = props.get(propertyMetadata.getName()); + if (value instanceof String) { + String decryptedValue = decryptOperator + .map(operator -> operator.apply((String) value)) + .orElseThrow(() -> new IllegalStateException( + "CryptoService not available to decrypt property " + propertyMetadata.getName())); + updatedMap.put(propertyMetadata.getName(), decryptedValue); + isTransformed = true; + } } } - if (updatedMap.isEmpty()) { + if (!isTransformed) { return null; } - updatedMap.putAll(props); return updatedMap; } @@ -397,7 +417,7 @@ class ConfigurationBuilderImpl implements ConfigurationBuilder { @Override public ValueMap convert(Resource resource, Class<ValueMap> clazz, String configName, boolean isCollection) { ValueMap props = ResourceUtil.getValueMap(resource); - Map<String, Object> updatedMap = applyDefaultValues(props, configName); + Map<String, Object> updatedMap = applyPropertyMetadata(props, configName); if (updatedMap != null) { return new ValueMapDecorator(updatedMap); } else { @@ -439,7 +459,7 @@ class ConfigurationBuilderImpl implements ConfigurationBuilder { if (resource == null || clazz == ConfigurationBuilder.class) { return null; } - return applyDefaultValues(resource, configName).adaptTo(clazz); + return applyTransformations(resource, configName).adaptTo(clazz); } } diff --git a/src/main/java/org/apache/sling/caconfig/impl/ConfigurationResolverImpl.java b/src/main/java/org/apache/sling/caconfig/impl/ConfigurationResolverImpl.java index f0c3795..e341967 100644 --- a/src/main/java/org/apache/sling/caconfig/impl/ConfigurationResolverImpl.java +++ b/src/main/java/org/apache/sling/caconfig/impl/ConfigurationResolverImpl.java @@ -21,6 +21,7 @@ package org.apache.sling.caconfig.impl; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; +import java.util.Optional; import org.apache.commons.lang3.ArrayUtils; import org.apache.sling.api.resource.Resource; @@ -36,6 +37,7 @@ import org.jetbrains.annotations.NotNull; import org.osgi.service.component.annotations.Activate; import org.osgi.service.component.annotations.Component; import org.osgi.service.component.annotations.Reference; +import org.osgi.service.component.annotations.ReferenceCardinality; import org.osgi.service.metatype.annotations.AttributeDefinition; import org.osgi.service.metatype.annotations.Designate; import org.osgi.service.metatype.annotations.ObjectClassDefinition; @@ -63,6 +65,9 @@ public class ConfigurationResolverImpl implements ConfigurationResolver, Configu @Reference private ConfigurationMetadataProviderMultiplexer configurationMetadataProvider; + @Reference(cardinality = ReferenceCardinality.OPTIONAL) + private DecryptService decryptService; + @ObjectClassDefinition( name = "Apache Sling Context-Aware Configuration Resolver", description = "Getting context-aware configurations for a given resource context.") @@ -99,6 +104,7 @@ public class ConfigurationResolverImpl implements ConfigurationResolver, Configu configurationInheritanceStrategy, configurationOverrideMultiplexer, configurationMetadataProvider, + Optional.ofNullable(decryptService), configBucketNames); } diff --git a/src/main/java/org/apache/sling/caconfig/impl/DecryptService.java b/src/main/java/org/apache/sling/caconfig/impl/DecryptService.java new file mode 100644 index 0000000..2f40ead --- /dev/null +++ b/src/main/java/org/apache/sling/caconfig/impl/DecryptService.java @@ -0,0 +1,38 @@ +/* + * 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.sling.caconfig.impl; + +import java.util.function.UnaryOperator; + +import org.apache.sling.commons.crypto.CryptoService; +import org.osgi.service.component.annotations.Component; +import org.osgi.service.component.annotations.Reference; +import org.osgi.service.component.annotations.ReferenceCardinality; + +@Component(service = DecryptService.class) +public class DecryptService implements UnaryOperator<String> { + + @Reference(cardinality = ReferenceCardinality.OPTIONAL) + private CryptoService cryptoService; + + @Override + public String apply(String t) { + return cryptoService.decrypt(t); + } +} diff --git a/src/main/java/org/apache/sling/caconfig/impl/metadata/AnnotationClassParser.java b/src/main/java/org/apache/sling/caconfig/impl/metadata/AnnotationClassParser.java index f9ea4a9..050f66f 100644 --- a/src/main/java/org/apache/sling/caconfig/impl/metadata/AnnotationClassParser.java +++ b/src/main/java/org/apache/sling/caconfig/impl/metadata/AnnotationClassParser.java @@ -196,7 +196,8 @@ public final class AnnotationClassParser { .label(emptyToNull(propertyAnnotation.label())) .description(emptyToNull(propertyAnnotation.description())) .properties(propsArrayToMap(propertyAnnotation.property())) - .order(propertyAnnotation.order()); + .order(propertyAnnotation.order()) + .encrypted(propertyAnnotation.encrypted()); } else { Map<String, String> emptyMap = Collections.emptyMap(); propertyMetadata.properties(emptyMap);
