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);

Reply via email to