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