You are viewing a plain text version of this content. The canonical link for it is here.
Posted to oak-commits@jackrabbit.apache.org by an...@apache.org on 2022/09/15 11:39:51 UTC

[jackrabbit-oak] branch trunk updated: OAK-9902 : Configuration for ExternalUserValidator (#669)

This is an automated email from the ASF dual-hosted git repository.

angela pushed a commit to branch trunk
in repository https://gitbox.apache.org/repos/asf/jackrabbit-oak.git


The following commit(s) were added to refs/heads/trunk by this push:
     new 6d56bccc0a OAK-9902 : Configuration for ExternalUserValidator (#669)
6d56bccc0a is described below

commit 6d56bccc0a53d14dcf39a81d1c0f5d7e23458305
Author: anchela <an...@apache.org>
AuthorDate: Thu Sep 15 13:39:45 2022 +0200

    OAK-9902 : Configuration for ExternalUserValidator (#669)
---
 .../authentication/external/ProtectionConfig.java  |  50 +++++++++
 .../principal/ExternalPrincipalConfiguration.java  |  15 ++-
 .../principal/ExternalUserValidatorProvider.java   |  38 ++++---
 .../impl/principal/ProtectionConfigImpl.java       |  75 +++++++++++++
 .../impl/principal/ProtectionConfigTracker.java    |  68 ++++++++++++
 .../authentication/external/package-info.java      |   2 +-
 .../principal/AbstractProtectionConfigTest.java    |  50 +++++++++
 .../ExternalPrincipalConfigurationTest.java        |  43 +++++++-
 .../impl/principal/ExternalUserValidatorTest.java  |   3 +-
 .../impl/principal/ProtectionConfigImplTest.java   |  83 ++++++++++++++
 .../principal/ProtectionConfigTrackerTest.java     | 121 +++++++++++++++++++++
 11 files changed, 529 insertions(+), 19 deletions(-)

diff --git a/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/ProtectionConfig.java b/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/ProtectionConfig.java
new file mode 100644
index 0000000000..9ce25d5353
--- /dev/null
+++ b/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/ProtectionConfig.java
@@ -0,0 +1,50 @@
+/*
+ * 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.jackrabbit.oak.spi.security.authentication.external;
+
+import org.apache.jackrabbit.oak.api.PropertyState;
+import org.apache.jackrabbit.oak.api.Tree;
+import org.jetbrains.annotations.NotNull;
+import org.osgi.annotation.versioning.ProviderType;
+
+/**
+ * Interface to mark properties or nodes located underneath a synchronized external identity as being protected (i.e.
+ * prevent modification through regular JCR/Jackrabbit API outside of the regular synchronization during login (or through 
+ * JMX).
+ */
+@ProviderType
+public interface ProtectionConfig {
+    
+    boolean isProtectedProperty(@NotNull Tree parent, @NotNull PropertyState property);
+    
+    boolean isProtectedTree(@NotNull Tree tree);
+
+    /**
+     * Default implementation that marks all properties or nodes as protected.
+     */
+    ProtectionConfig DEFAULT = new ProtectionConfig() {
+        @Override
+        public boolean isProtectedProperty(@NotNull Tree parent, @NotNull PropertyState property) {
+            return true;
+        }
+
+        @Override
+        public boolean isProtectedTree(@NotNull Tree tree) {
+            return true;
+        }
+    };
+}
\ No newline at end of file
diff --git a/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/principal/ExternalPrincipalConfiguration.java b/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/principal/ExternalPrincipalConfiguration.java
index 6c767c404e..156adb3deb 100644
--- a/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/principal/ExternalPrincipalConfiguration.java
+++ b/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/principal/ExternalPrincipalConfiguration.java
@@ -35,6 +35,7 @@ import org.apache.jackrabbit.oak.spi.security.ConfigurationBase;
 import org.apache.jackrabbit.oak.spi.security.ConfigurationParameters;
 import org.apache.jackrabbit.oak.spi.security.SecurityConfiguration;
 import org.apache.jackrabbit.oak.spi.security.SecurityProvider;
+import org.apache.jackrabbit.oak.spi.security.authentication.external.ProtectionConfig;
 import org.apache.jackrabbit.oak.spi.security.authentication.external.impl.ExternalIdentityConstants;
 import org.apache.jackrabbit.oak.spi.security.authentication.external.impl.monitor.ExternalIdentityMonitor;
 import org.apache.jackrabbit.oak.spi.security.authentication.external.impl.monitor.ExternalIdentityMonitorImpl;
@@ -105,6 +106,7 @@ public class ExternalPrincipalConfiguration extends ConfigurationBase implements
     
     private SyncConfigTracker syncConfigTracker;
     private SyncHandlerMappingTracker syncHandlerMappingTracker;
+    private ProtectionConfigTracker protectionConfigTracker;
     
     private ServiceRegistration automembershipRegistration;
     private ServiceRegistration dynamicMembershipRegistration;
@@ -166,7 +168,7 @@ public class ExternalPrincipalConfiguration extends ConfigurationBase implements
         
         IdentityProtectionType ipt = getIdentityProtectionType();
         if (ipt != IdentityProtectionType.NONE && !isSystem) {
-            vps.add(new ExternalUserValidatorProvider(getRootProvider(), getTreeProvider(), getSecurityProvider(), ipt));
+            vps.add(new ExternalUserValidatorProvider(getRootProvider(), getTreeProvider(), getSecurityProvider(), ipt, getProtectionConfig()));
         }
         return vps.build();
     }
@@ -200,6 +202,9 @@ public class ExternalPrincipalConfiguration extends ConfigurationBase implements
 
         syncConfigTracker = new SyncConfigTracker(bundleContext, syncHandlerMappingTracker);
         syncConfigTracker.open();
+
+        protectionConfigTracker = new ProtectionConfigTracker(bundleContext);
+        protectionConfigTracker.open();
         
         automembershipRegistration = bundleContext.registerService(DynamicMembershipService.class.getName(), new AutomembershipService(syncConfigTracker), null);
         dynamicMembershipRegistration = bundleContext.registerService(DynamicMembershipService.class.getName(), new DynamicGroupMembershipService(syncConfigTracker), null);
@@ -214,7 +219,9 @@ public class ExternalPrincipalConfiguration extends ConfigurationBase implements
         if (syncHandlerMappingTracker != null) {
             syncHandlerMappingTracker.close();
         }
-        
+        if (protectionConfigTracker != null) {
+            protectionConfigTracker.close();
+        }
         if (automembershipRegistration != null) {
             automembershipRegistration.unregister();
         }
@@ -241,6 +248,10 @@ public class ExternalPrincipalConfiguration extends ConfigurationBase implements
         return IdentityProtectionType.fromLabel(getParameters().getConfigValue(PARAM_PROTECT_EXTERNAL_IDENTITIES, VALUE_PROTECT_EXTERNAL_IDENTITIES_NONE));
     }
     
+    private @NotNull ProtectionConfig getProtectionConfig() {
+        return (protectionConfigTracker == null) ? ProtectionConfig.DEFAULT : protectionConfigTracker;
+    }
+    
     @NotNull
     private Set<String> getPrincipalNames() {
         return getParameters().getConfigValue(ExternalIdentityConstants.PARAM_SYSTEM_PRINCIPAL_NAMES, Collections.emptySet());
diff --git a/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/principal/ExternalUserValidatorProvider.java b/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/principal/ExternalUserValidatorProvider.java
index fb386f85ec..5eb0c6c701 100644
--- a/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/principal/ExternalUserValidatorProvider.java
+++ b/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/principal/ExternalUserValidatorProvider.java
@@ -33,6 +33,7 @@ import org.apache.jackrabbit.oak.spi.commit.ValidatorProvider;
 import org.apache.jackrabbit.oak.spi.security.Context;
 import org.apache.jackrabbit.oak.spi.security.SecurityConfiguration;
 import org.apache.jackrabbit.oak.spi.security.SecurityProvider;
+import org.apache.jackrabbit.oak.spi.security.authentication.external.ProtectionConfig;
 import org.apache.jackrabbit.oak.spi.security.authentication.external.impl.ExternalIdentityConstants;
 import org.apache.jackrabbit.oak.spi.security.user.AuthorizableType;
 import org.apache.jackrabbit.oak.spi.security.user.UserConfiguration;
@@ -57,18 +58,21 @@ class ExternalUserValidatorProvider extends ValidatorProvider implements Externa
     private final String authorizableRootPath;
     private final Context aggregatedCtx;
     private final IdentityProtectionType protectionType;
+    private final ProtectionConfig protectionConfig;
     
     private Root rootBefore;
     private Root rootAfter;
     
-    ExternalUserValidatorProvider(@NotNull RootProvider rootProvider, 
-                                  @NotNull TreeProvider treeProvider, 
+    ExternalUserValidatorProvider(@NotNull RootProvider rootProvider,
+                                  @NotNull TreeProvider treeProvider,
                                   @NotNull SecurityProvider securityProvider,
-                                  @NotNull IdentityProtectionType protectionType) {
+                                  @NotNull IdentityProtectionType protectionType,
+                                  @NotNull ProtectionConfig protectionConfig) {
         checkArgument(protectionType != IdentityProtectionType.NONE);
         this.rootProvider = rootProvider;
         this.treeProvider = treeProvider;
         this.protectionType = protectionType;
+        this.protectionConfig = protectionConfig;
 
         this.authorizableRootPath = UserUtil.getAuthorizableRootPath(securityProvider.getParameters(UserConfiguration.NAME), AuthorizableType.AUTHORIZABLE);
         aggregatedCtx = new AggregatedContext(securityProvider);
@@ -115,7 +119,7 @@ class ExternalUserValidatorProvider extends ValidatorProvider implements Externa
                 return;
             }
 
-            if (isModifyingExternalIdentity(isExternalIdentity, after)) {
+            if (isModifyingExternalIdentity(isExternalIdentity, afterTree, after)) {
                 String msg = String.format("Attempt to add property '%s' to protected external identity node '%s'", after.getName(), afterTree.getPath());
                 handleViolation(msg);
             }
@@ -127,7 +131,7 @@ class ExternalUserValidatorProvider extends ValidatorProvider implements Externa
             if (definedSecurityContext(beforeTree, before)) {
                 return;
             }
-            if (isModifyingExternalIdentity(isExternalIdentity, before)) {
+            if (isModifyingExternalIdentity(isExternalIdentity, beforeTree, before)) {
                 String msg = String.format("Attempt to modify property '%s' at protected external identity node '%s'", before.getName(), beforeTree.getPath());
                 handleViolation(msg);
             }
@@ -139,7 +143,7 @@ class ExternalUserValidatorProvider extends ValidatorProvider implements Externa
             if (definedSecurityContext(beforeTree, before)) {
                 return;
             }
-            if (isModifyingExternalIdentity(isExternalIdentity, before)) {
+            if (isModifyingExternalIdentity(isExternalIdentity, beforeTree, before)) {
                 String msg = String.format("Attempt to delete property '%s' from protected external identity node '%s'", before.getName(), beforeTree.getPath());
                 handleViolation(msg);
             }
@@ -157,7 +161,7 @@ class ExternalUserValidatorProvider extends ValidatorProvider implements Externa
                 String msg = String.format("Attempt to add protected external identity '%s'", afterTree.getPath());
                 handleViolation(msg);
                 return null;
-            } else if (isModifyingExternalIdentity(isExternalIdentity, null)) {
+            } else if (isModifyingExternalIdentity(isExternalIdentity, afterTree, null)) {
                 String msg = String.format("Attempt to add node '%s' to protected external identity node '%s'", name, afterParent.getPath());
                 handleViolation(msg);
                 return null;
@@ -194,14 +198,14 @@ class ExternalUserValidatorProvider extends ValidatorProvider implements Externa
                 return null;
             }
             
-            if (isModifyingExternalIdentity(isExternalIdentity, null)) {
+            if (isModifyingExternalIdentity(isExternalIdentity, beforeTree, null)) {
                 // attempt to remove a node below an external user/group
                 String msg = String.format("Attempt to remove node '%s' from protected external identity", beforeTree.getPath());
                 handleViolation(msg);
                 return null;
             }
             
-            // decend into subtree to spot any removal of external user/group or it's subtree
+            // descend into subtree to spot any removal of external user/group or it's subtree
             return new ExternalUserValidator(this, beforeTree, true);
         }
         
@@ -231,8 +235,8 @@ class ExternalUserValidatorProvider extends ValidatorProvider implements Externa
             return parentAfter;
         }
         
-        private boolean isModifyingExternalIdentity(boolean insideAuthorizable, @Nullable PropertyState propertyState) {
-            return insideAuthorizable && !isExcludedProperty(propertyState);
+        private boolean isModifyingExternalIdentity(boolean insideAuthorizable, @NotNull Tree tree, @Nullable PropertyState propertyState) {
+            return insideAuthorizable && isProtected(tree, propertyState);
         }
 
         /**
@@ -240,14 +244,20 @@ class ExternalUserValidatorProvider extends ValidatorProvider implements Externa
          * modification of the user/group that is exposed through user-mgt API. Note however, that editing non-security
          * related mixins would still fail as the child items defined by the mixin type cannot be written.
          * 
+         * Any other nodes/properties are considered protected depending on the configured {@link ProtectionConfig}.
+         * 
+         * @param tree The tree
          * @param propertyState The property to be tested
-         * @return {@code true} if the given property is excluded from protection
+         * @return {@code true} if the given property is protected
          */
-        private boolean isExcludedProperty(@Nullable PropertyState propertyState) {
+        private boolean isProtected(@NotNull Tree tree, @Nullable PropertyState propertyState) {
             if (propertyState == null) {
+                return protectionConfig.isProtectedTree(tree);
+            }
+            if (JCR_MIXINTYPES.equals(propertyState.getName())) {
                 return false;
             } else {
-                return JCR_MIXINTYPES.equals(propertyState.getName());
+                return protectionConfig.isProtectedProperty(tree, propertyState);
             }
         }
         
diff --git a/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/principal/ProtectionConfigImpl.java b/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/principal/ProtectionConfigImpl.java
new file mode 100644
index 0000000000..f98efb02ac
--- /dev/null
+++ b/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/principal/ProtectionConfigImpl.java
@@ -0,0 +1,75 @@
+/*
+ * 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.jackrabbit.oak.spi.security.authentication.external.impl.principal;
+
+import com.google.common.collect.ImmutableSet;
+import org.apache.jackrabbit.oak.api.PropertyState;
+import org.apache.jackrabbit.oak.api.Tree;
+import org.apache.jackrabbit.oak.commons.PropertiesUtil;
+import org.apache.jackrabbit.oak.spi.security.authentication.external.ProtectionConfig;
+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.ConfigurationPolicy;
+import org.osgi.service.metatype.annotations.AttributeDefinition;
+import org.osgi.service.metatype.annotations.Designate;
+import org.osgi.service.metatype.annotations.ObjectClassDefinition;
+
+import java.util.Collections;
+import java.util.Map;
+import java.util.Set;
+
+@Component(service = ProtectionConfig.class, immediate = true, configurationPolicy = ConfigurationPolicy.REQUIRE)
+@Designate(ocd = ProtectionConfigImpl.Configuration.class)
+public class ProtectionConfigImpl implements ProtectionConfig {
+
+    @ObjectClassDefinition(name = "Apache Jackrabbit Oak External Identity Protection Exclusion List",
+            description = "Implementation of ProtectionConfig that marks all properties and trees as protected except for those specified matching the names in the 2 allow lists.")
+    @interface Configuration {
+        @AttributeDefinition(
+                name = "Property Allow List",
+                description = "Names of properties that are excluded from the protection status.",
+                cardinality = Integer.MAX_VALUE)
+        String[] propertyNames() default {};
+
+        @AttributeDefinition(
+                name = "Node Allow List",
+                description = "Names of nodes that are excluded from the protection status. Note that the exception is applied to the whole subtree.",
+                cardinality = Integer.MAX_VALUE)
+        String[] nodeNames() default {};
+    }
+    
+    private Set<String> propertyNames = Collections.emptySet();
+    private Set<String> nodeNames = Collections.emptySet();
+
+    @Activate
+    protected void activate(Map<String, Object> properties) {
+        propertyNames = ImmutableSet.copyOf(PropertiesUtil.toStringArray(properties.get("propertyNames"), new String[0]));
+        nodeNames = ImmutableSet.copyOf(PropertiesUtil.toStringArray(properties.get("nodeNames"), new String[0]));
+    }
+    
+    @Override
+    public boolean isProtectedProperty(@NotNull Tree parent, @NotNull PropertyState property) {
+        return !propertyNames.contains(property.getName());
+    }
+
+    @Override
+    public boolean isProtectedTree(@NotNull Tree tree) {
+        return !nodeNames.contains(tree.getName());
+    }
+
+}
\ No newline at end of file
diff --git a/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/principal/ProtectionConfigTracker.java b/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/principal/ProtectionConfigTracker.java
new file mode 100644
index 0000000000..9076296cb1
--- /dev/null
+++ b/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/principal/ProtectionConfigTracker.java
@@ -0,0 +1,68 @@
+/*
+ * 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.jackrabbit.oak.spi.security.authentication.external.impl.principal;
+
+import org.apache.jackrabbit.oak.api.PropertyState;
+import org.apache.jackrabbit.oak.api.Tree;
+import org.apache.jackrabbit.oak.spi.security.authentication.external.ProtectionConfig;
+import org.jetbrains.annotations.NotNull;
+import org.osgi.framework.BundleContext;
+import org.osgi.util.tracker.ServiceTracker;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.stream.Collectors;
+
+class ProtectionConfigTracker extends ServiceTracker implements ProtectionConfig {
+    
+    public ProtectionConfigTracker(@NotNull BundleContext context) {
+        super(context, ProtectionConfig.class.getName(), null);
+    }
+
+    //-------------------------------------------------------------------------------------------< ProtectionConfig >---
+
+    @Override
+    public boolean isProtectedProperty(@NotNull Tree parent, @NotNull PropertyState property) {
+        for (ProtectionConfig pe : getProtectionConfigs()) {
+            if (pe.isProtectedProperty(parent, property)) {
+                return true;
+            }
+        }
+        return false;
+    }
+    
+    @Override
+    public boolean isProtectedTree(@NotNull Tree tree) {
+        for (ProtectionConfig pe : getProtectionConfigs()) {
+            if (pe.isProtectedTree(tree)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    //------------------------------------------------------------------------------------------------------------------
+
+    private @NotNull Iterable<ProtectionConfig> getProtectionConfigs() {
+        Object[] services = getServices();
+        if (services == null) {
+            return Collections.singletonList(ProtectionConfig.DEFAULT);
+        } else {
+            return Arrays.stream(services).map(ProtectionConfig.class::cast).collect(Collectors.toList());
+        }
+    }
+}
\ No newline at end of file
diff --git a/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/package-info.java b/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/package-info.java
index 98630d5d43..112ef6a547 100644
--- a/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/package-info.java
+++ b/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/package-info.java
@@ -14,7 +14,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-@Version("2.3.1")
+@Version("2.4.0")
 package org.apache.jackrabbit.oak.spi.security.authentication.external;
 
 import org.osgi.annotation.versioning.Version;
diff --git a/oak-auth-external/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/principal/AbstractProtectionConfigTest.java b/oak-auth-external/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/principal/AbstractProtectionConfigTest.java
new file mode 100644
index 0000000000..3f4141bb5a
--- /dev/null
+++ b/oak-auth-external/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/principal/AbstractProtectionConfigTest.java
@@ -0,0 +1,50 @@
+/*
+ * 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.jackrabbit.oak.spi.security.authentication.external.impl.principal;
+
+import com.google.common.collect.ImmutableMap;
+import org.apache.jackrabbit.oak.api.PropertyState;
+import org.apache.jackrabbit.oak.api.Tree;
+import org.apache.jackrabbit.oak.spi.security.authentication.external.AbstractExternalAuthTest;
+import org.apache.jackrabbit.oak.spi.security.authentication.external.ProtectionConfig;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.Map;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public abstract class AbstractProtectionConfigTest extends AbstractExternalAuthTest {
+
+    static final Map<String, String[]> PROPERTIES = ImmutableMap.of(
+            "propertyNames", new String[] {"prop1", "prop2"},
+            "nodeNames", new String[] {"node1", "node2"});
+    
+    final ProtectionConfigImpl protectionConfig = new ProtectionConfigImpl();
+    
+    void registerProtectionConfig(@NotNull ProtectionConfig config, @NotNull Map<String, String[]> properties) {
+        context.registerInjectActivateService(config, properties);
+    }
+
+    static Tree mockTree(@NotNull String name) {
+        return when(mock(Tree.class).getName()).thenReturn(name).getMock();
+    }
+
+    static PropertyState mockProperty(@NotNull String name) {
+        return when(mock(PropertyState.class).getName()).thenReturn(name).getMock();
+    }
+}
\ No newline at end of file
diff --git a/oak-auth-external/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/principal/ExternalPrincipalConfigurationTest.java b/oak-auth-external/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/principal/ExternalPrincipalConfigurationTest.java
index 7812b4b5b3..f0e78b10b7 100644
--- a/oak-auth-external/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/principal/ExternalPrincipalConfigurationTest.java
+++ b/oak-auth-external/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/principal/ExternalPrincipalConfigurationTest.java
@@ -26,9 +26,11 @@ import org.apache.jackrabbit.oak.spi.commit.ValidatorProvider;
 import org.apache.jackrabbit.oak.spi.lifecycle.WorkspaceInitializer;
 import org.apache.jackrabbit.oak.spi.security.ConfigurationParameters;
 import org.apache.jackrabbit.oak.spi.security.Context;
+import org.apache.jackrabbit.oak.spi.security.SecurityProvider;
 import org.apache.jackrabbit.oak.spi.security.authentication.SystemSubject;
 import org.apache.jackrabbit.oak.spi.security.authentication.external.AbstractExternalAuthTest;
 import org.apache.jackrabbit.oak.spi.security.authentication.external.ExternalIdentityProvider;
+import org.apache.jackrabbit.oak.spi.security.authentication.external.ProtectionConfig;
 import org.apache.jackrabbit.oak.spi.security.authentication.external.SyncContext;
 import org.apache.jackrabbit.oak.spi.security.authentication.external.SyncHandler;
 import org.apache.jackrabbit.oak.spi.security.authentication.external.SyncedIdentity;
@@ -38,9 +40,11 @@ import org.apache.jackrabbit.oak.spi.security.authentication.external.impl.Defau
 import org.apache.jackrabbit.oak.spi.security.authentication.external.impl.DefaultSyncHandler;
 import org.apache.jackrabbit.oak.spi.security.authentication.external.impl.ExternalIdentityConstants;
 import org.apache.jackrabbit.oak.spi.security.authentication.external.impl.monitor.ExternalIdentityMonitorImpl;
+import org.apache.jackrabbit.oak.spi.security.principal.EveryonePrincipal;
 import org.apache.jackrabbit.oak.spi.security.principal.PrincipalConfiguration;
 import org.apache.jackrabbit.oak.spi.security.principal.PrincipalProvider;
 import org.apache.jackrabbit.oak.spi.security.principal.SystemUserPrincipal;
+import org.apache.jackrabbit.oak.spi.security.user.UserConfiguration;
 import org.apache.jackrabbit.oak.spi.security.user.UserConstants;
 import org.apache.jackrabbit.oak.spi.xml.ProtectedItemImporter;
 import org.apache.jackrabbit.oak.stats.Monitor;
@@ -54,13 +58,16 @@ import org.osgi.framework.BundleContext;
 import org.osgi.framework.ServiceRegistration;
 
 import javax.jcr.ValueFactory;
+import java.lang.reflect.Field;
 import java.security.Principal;
 import java.util.Collections;
 import java.util.Dictionary;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
+import java.util.Optional;
 import java.util.Set;
+import java.util.function.Predicate;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
@@ -68,6 +75,9 @@ import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertSame;
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
 
 public class ExternalPrincipalConfigurationTest extends AbstractExternalAuthTest {
 
@@ -249,7 +259,7 @@ public class ExternalPrincipalConfigurationTest extends AbstractExternalAuthTest
         ContentSession cs = root.getContentSession();
         String workspaceName = cs.getWorkspaceName();
         
-        // upon registering a synchhandler with dynamic-membership the dynamic-group-validator must be present as well
+        // upon registering a sync-handler with dynamic-membership the dynamic-group-validator must be present as well
         registerSyncHandler(true, true);
 
         List<? extends ValidatorProvider> validatorProviders = externalPrincipalConfiguration.getValidators(workspaceName, cs.getAuthInfo().getPrincipals(), new MoveTracker());
@@ -265,6 +275,37 @@ public class ExternalPrincipalConfigurationTest extends AbstractExternalAuthTest
         assertTrue(validatorProviders.get(1) instanceof DynamicGroupValidatorProvider);
     }
     
+    @Test
+    public void testGetValidatorsMissingActivate() throws Exception {
+        ConfigurationParameters config = ConfigurationParameters.of(ExternalIdentityConstants.PARAM_PROTECT_EXTERNAL_IDENTITIES, ExternalIdentityConstants.VALUE_PROTECT_EXTERNAL_IDENTITIES_WARN);
+        
+        SecurityProvider sp = mock(SecurityProvider.class);
+        when(sp.getParameters(PrincipalConfiguration.NAME)).thenReturn(config);
+        when(sp.getParameters(UserConfiguration.NAME)).thenReturn(ConfigurationParameters.EMPTY);
+        
+        ExternalPrincipalConfiguration epc = new ExternalPrincipalConfiguration(sp);
+        epc.setRootProvider(getRootProvider());
+        epc.setTreeProvider(getTreeProvider());
+        
+        List<? extends ValidatorProvider> vps = epc.getValidators(root.getContentSession().getWorkspaceName(), Collections.singleton(EveryonePrincipal.getInstance()), new MoveTracker());
+        assertEquals(2, vps.size());
+        
+        Optional<? extends ValidatorProvider> optional = vps.stream().filter((Predicate<ValidatorProvider>) validatorProvider1 -> validatorProvider1 instanceof ExternalUserValidatorProvider).findFirst();
+        if (optional.isPresent()) {
+            ExternalUserValidatorProvider validatorProvider = (ExternalUserValidatorProvider) optional.get();
+            assertNotNull(validatorProvider);
+            assertDefaultProtectionConfig(validatorProvider);
+        } else {
+            fail("ExternalUserValidatorProvider expected to be present");
+        }
+    }
+    
+    private static void assertDefaultProtectionConfig(@NotNull ExternalUserValidatorProvider vp) throws Exception {
+        Field f = ExternalUserValidatorProvider.class.getDeclaredField("protectionConfig");
+        f.setAccessible(true);
+        assertSame(ProtectionConfig.DEFAULT, f.get(vp));
+    }
+    
     @Test
     public void testGetProtectedItemImporters() {
         List<? extends ProtectedItemImporter> importers = externalPrincipalConfiguration.getProtectedItemImporters();
diff --git a/oak-auth-external/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/principal/ExternalUserValidatorTest.java b/oak-auth-external/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/principal/ExternalUserValidatorTest.java
index c3b48dd7f5..164dffdb49 100644
--- a/oak-auth-external/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/principal/ExternalUserValidatorTest.java
+++ b/oak-auth-external/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/principal/ExternalUserValidatorTest.java
@@ -42,6 +42,7 @@ import org.apache.jackrabbit.oak.spi.security.Context;
 import org.apache.jackrabbit.oak.spi.security.SecurityProvider;
 import org.apache.jackrabbit.oak.spi.security.authentication.external.ExternalLoginTestBase;
 import org.apache.jackrabbit.oak.spi.security.authentication.external.ExternalUser;
+import org.apache.jackrabbit.oak.spi.security.authentication.external.ProtectionConfig;
 import org.apache.jackrabbit.oak.spi.security.authentication.external.basic.DefaultSyncConfig;
 import org.apache.jackrabbit.oak.spi.security.authentication.external.impl.ExternalIdentityConstants;
 import org.apache.jackrabbit.oak.spi.security.authentication.external.impl.ExternalLoginModule;
@@ -515,7 +516,7 @@ public class ExternalUserValidatorTest extends ExternalLoginTestBase {
             TreeProvider tp = mock(TreeProvider.class);
             SecurityProvider sp = mock(SecurityProvider.class);
             try {
-                ExternalUserValidatorProvider vp = new ExternalUserValidatorProvider(rp, tp, sp, type);
+                ExternalUserValidatorProvider vp = new ExternalUserValidatorProvider(rp, tp, sp, type, ProtectionConfig.DEFAULT);
                 fail("IllegalArgumentException expected");
             } catch (IllegalArgumentException e) {
                 // success
diff --git a/oak-auth-external/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/principal/ProtectionConfigImplTest.java b/oak-auth-external/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/principal/ProtectionConfigImplTest.java
new file mode 100644
index 0000000000..3531b35663
--- /dev/null
+++ b/oak-auth-external/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/principal/ProtectionConfigImplTest.java
@@ -0,0 +1,83 @@
+/*
+ * 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.jackrabbit.oak.spi.security.authentication.external.impl.principal;
+
+import org.apache.jackrabbit.oak.api.PropertyState;
+import org.apache.jackrabbit.oak.api.Tree;
+import org.junit.Before;
+import org.junit.Test;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoInteractions;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+
+public class ProtectionConfigImplTest extends AbstractProtectionConfigTest {
+    
+    @Before
+    public void before() throws Exception {
+        super.before();
+
+        registerProtectionConfig(protectionConfig, PROPERTIES);
+    }
+    
+    @Test
+    public void testNotIsProtectedProperty() {
+        Tree tree = mockTree("name");
+        // matching properties
+        for (String name : new String[] {"prop1", "prop2"}) {
+            PropertyState property = mockProperty(name);
+            assertFalse(protectionConfig.isProtectedProperty(tree, property));
+            verify(property).getName();
+            verifyNoMoreInteractions(property);
+        }
+        verifyNoInteractions(tree);
+    }
+
+    @Test
+    public void testIsProtectedProperty() {
+        Tree tree = mockTree("name");
+        // not matching
+        PropertyState property = mockProperty("anotherName");
+        assertTrue(protectionConfig.isProtectedProperty(tree, property));
+        
+        verify(property).getName();
+        verifyNoMoreInteractions(property);
+        verifyNoInteractions(tree);
+    }
+
+    @Test
+    public void testNotIsProtectedTree() {
+        for (String name : new String[] {"node1", "node2"}) {
+            Tree tree = mockTree(name);
+            assertFalse(protectionConfig.isProtectedTree(tree));
+            verify(tree).getName();
+            verifyNoMoreInteractions(tree);
+        }
+    }
+
+    @Test
+    public void testIsProtectedTree() {
+        Tree tree = mockTree("anotherName");
+        // not matching
+        assertTrue(protectionConfig.isProtectedTree(tree));
+
+        verify(tree).getName();
+        verifyNoMoreInteractions(tree);
+    }
+}
\ No newline at end of file
diff --git a/oak-auth-external/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/principal/ProtectionConfigTrackerTest.java b/oak-auth-external/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/principal/ProtectionConfigTrackerTest.java
new file mode 100644
index 0000000000..3e1ab8e7f9
--- /dev/null
+++ b/oak-auth-external/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/principal/ProtectionConfigTrackerTest.java
@@ -0,0 +1,121 @@
+/*
+ * 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.jackrabbit.oak.spi.security.authentication.external.impl.principal;
+
+import org.apache.jackrabbit.oak.api.PropertyState;
+import org.apache.jackrabbit.oak.api.Tree;
+import org.apache.jackrabbit.oak.spi.security.authentication.external.ProtectionConfig;
+import org.jetbrains.annotations.NotNull;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.Collections;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.verifyNoInteractions;
+
+public class ProtectionConfigTrackerTest extends AbstractProtectionConfigTest {
+    
+    private ProtectionConfigTracker tracker;
+
+    @Before
+    public void before() throws Exception {
+        super.before();
+        
+        tracker = new ProtectionConfigTracker(context.bundleContext());
+        tracker.open();
+    }
+    
+    @After
+    public void after() throws Exception {
+        try {
+            if (tracker != null) {
+                tracker.close();
+            }
+        } finally {
+            super.after();
+        }
+    }
+    
+    @Test
+    public void testNoServices() {
+        Tree tree = mockTree("tree");
+        assertEquals(ProtectionConfig.DEFAULT.isProtectedTree(tree), tracker.isProtectedTree(tree));
+        verifyNoInteractions(tree);
+        
+        PropertyState property = mockProperty("property");
+        assertEquals(ProtectionConfig.DEFAULT.isProtectedProperty(tree, property), tracker.isProtectedProperty(tree, property));
+        verifyNoInteractions(tree, property);
+    }
+    
+    @Test
+    public void testIsProtectedTreeRegisteredServices() {
+        // config 1 with allow-list ('node1', 'node2'), every other name is protected
+        registerProtectionConfig(protectionConfig, PROPERTIES);
+        // config 2 with deny-list ('protected' is a protected name), which is already covered by the first config
+        context.registerService(ProtectionConfig.class, new TestProtectionConfig("protected"), Collections.emptyMap());
+        
+        for (String name : new String[] {"node1", "node2"}) {
+            Tree t = mockTree(name);
+            assertFalse(tracker.isProtectedTree(t));
+        }
+        for (String name : new String[] {"someother", "protected"}) {
+            Tree t = mockTree(name);
+            assertTrue(tracker.isProtectedTree(t));
+        }     
+    }
+    
+    @Test
+    public void testIsProtectedPropertyRegisteredServices() {
+        // 2 configurations with deny-list 
+        context.registerService(ProtectionConfig.class, new TestProtectionConfig("protected1"), Collections.emptyMap());
+        context.registerService(ProtectionConfig.class, new TestProtectionConfig("protected2"), Collections.emptyMap());
+
+        for (String name : new String[] {"prop", "someother"}) {
+            Tree t = mockTree("tree");
+            PropertyState property = mockProperty(name);
+            assertFalse(tracker.isProtectedProperty(t, property));
+        }
+
+        for (String name : new String[] {"protected1", "protected2"}) {
+            Tree t = mockTree("tree");
+            PropertyState property = mockProperty(name);
+            assertTrue(tracker.isProtectedProperty(t, property));
+        } 
+    }
+
+    public static final class TestProtectionConfig implements ProtectionConfig {
+        
+        private final String protectedName;
+
+        private TestProtectionConfig(@NotNull String protectedName) {
+            this.protectedName = protectedName;
+        }
+        @Override
+        public boolean isProtectedProperty(@NotNull Tree parent, @NotNull PropertyState property) {
+            return protectedName.equals(property.getName());
+        }
+
+        @Override
+        public boolean isProtectedTree(@NotNull Tree tree) {
+            return protectedName.equals(tree.getName());
+        }
+    }
+}
\ No newline at end of file