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 2016/08/04 14:03:31 UTC

svn commit: r1755191 - in /jackrabbit/oak/trunk: oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/ oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/pr...

Author: angela
Date: Thu Aug  4 14:03:31 2016
New Revision: 1755191

URL: http://svn.apache.org/viewvc?rev=1755191&view=rev
Log:
OAK-4301 : Missing protection for system-maintained rep:externalId

Added:
    jackrabbit/oak/trunk/oak-auth-external/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/principal/ValidatorNoProtectionTest.java
      - copied, changed from r1754940, jackrabbit/oak/trunk/oak-auth-external/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/principal/ExternalIdentityValidatorTest.java
    jackrabbit/oak/trunk/oak-auth-external/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/principal/ValidatorNotDynamicTest.java
Modified:
    jackrabbit/oak/trunk/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/ExternalIdentityConstants.java
    jackrabbit/oak/trunk/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/principal/ExternalIdentityRepositoryInitializer.java
    jackrabbit/oak/trunk/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/principal/ExternalIdentityValidatorProvider.java
    jackrabbit/oak/trunk/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/principal/ExternalPrincipalConfiguration.java
    jackrabbit/oak/trunk/oak-auth-external/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/basic/RepExternalIdTest.java
    jackrabbit/oak/trunk/oak-auth-external/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/DefaultSyncHandlerTest.java
    jackrabbit/oak/trunk/oak-auth-external/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/principal/ExternalIdentityImporterTest.java
    jackrabbit/oak/trunk/oak-auth-external/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/principal/ExternalIdentityRepositoryInitializerTest.java
    jackrabbit/oak/trunk/oak-auth-external/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/principal/ExternalIdentityValidatorTest.java
    jackrabbit/oak/trunk/oak-auth-external/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/principal/ExternalPrincipalConfigurationTest.java
    jackrabbit/oak/trunk/oak-doc/src/site/markdown/security/authentication/external/defaultusersync.md

Modified: jackrabbit/oak/trunk/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/ExternalIdentityConstants.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/ExternalIdentityConstants.java?rev=1755191&r1=1755190&r2=1755191&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/ExternalIdentityConstants.java (original)
+++ jackrabbit/oak/trunk/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/ExternalIdentityConstants.java Thu Aug  4 14:03:31 2016
@@ -67,4 +67,15 @@ public interface ExternalIdentityConstan
      */
     Set<String> RESERVED_PROPERTY_NAMES = ImmutableSet.of(REP_EXTERNAL_ID, REP_EXTERNAL_PRINCIPAL_NAMES);
 
+    /**
+     * Configuration parameter to enable special protection of external IDs
+     *
+     * @see <a href="https://issues.apache.org/jira/browse/OAK-4301">OAK-4301</a>
+     */
+    String PARAM_PROTECT_EXTERNAL_IDS = "protectExternalId";
+
+    /**
+     * Default value for {@link #PARAM_PROTECT_EXTERNAL_IDS}.
+     */
+    boolean DEFAULT_PROTECT_EXTERNAL_IDS = true;
 }
\ No newline at end of file

Modified: jackrabbit/oak/trunk/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/principal/ExternalIdentityRepositoryInitializer.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/principal/ExternalIdentityRepositoryInitializer.java?rev=1755191&r1=1755190&r2=1755191&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/principal/ExternalIdentityRepositoryInitializer.java (original)
+++ jackrabbit/oak/trunk/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/principal/ExternalIdentityRepositoryInitializer.java Thu Aug  4 14:03:31 2016
@@ -60,7 +60,10 @@ class ExternalIdentityRepositoryInitiali
 
     private static final Logger log = LoggerFactory.getLogger(ExternalIdentityRepositoryInitializer.class);
 
-    ExternalIdentityRepositoryInitializer() {
+    private final boolean enforceUniqueIds;
+
+    ExternalIdentityRepositoryInitializer(boolean enforceUniqueIds) {
+        this.enforceUniqueIds = enforceUniqueIds;
     }
 
     @Override
@@ -79,6 +82,12 @@ class ExternalIdentityRepositoryInitiali
             NodeUtil rootTree = checkNotNull(new NodeUtil(root.getTree("/")));
             NodeUtil index = rootTree.getOrAddChild(IndexConstants.INDEX_DEFINITIONS_NAME, JcrConstants.NT_UNSTRUCTURED);
 
+            if (enforceUniqueIds && !index.hasChild("externalId")) {
+                NodeUtil definition = IndexUtils.createIndexDefinition(index, "externalId", true,
+                        new String[]{ExternalIdentityConstants.REP_EXTERNAL_ID}, null);
+                definition.setString("info", "Oak index assuring uniqueness of rep:externalId properties.");
+            }
+
             if (!index.hasChild("externalPrincipalNames")) {
                 NodeUtil definition = IndexUtils.createIndexDefinition(index, "externalPrincipalNames", false,
                         new String[]{ExternalIdentityConstants.REP_EXTERNAL_PRINCIPAL_NAMES}, null);

Modified: jackrabbit/oak/trunk/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/principal/ExternalIdentityValidatorProvider.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/principal/ExternalIdentityValidatorProvider.java?rev=1755191&r1=1755190&r2=1755191&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/principal/ExternalIdentityValidatorProvider.java (original)
+++ jackrabbit/oak/trunk/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/principal/ExternalIdentityValidatorProvider.java Thu Aug  4 14:03:31 2016
@@ -17,9 +17,11 @@
 package org.apache.jackrabbit.oak.spi.security.authentication.external.impl.principal;
 
 import java.security.Principal;
+import java.util.Map;
 import java.util.Set;
 import javax.annotation.Nonnull;
 
+import com.google.common.collect.ImmutableMap;
 import org.apache.jackrabbit.oak.api.CommitFailedException;
 import org.apache.jackrabbit.oak.api.PropertyState;
 import org.apache.jackrabbit.oak.api.Type;
@@ -40,35 +42,60 @@ import org.apache.jackrabbit.oak.spi.sta
  */
 class ExternalIdentityValidatorProvider extends ValidatorProvider implements ExternalIdentityConstants {
 
+    private static final Map<Integer, String> ERROR_MSGS = ImmutableMap.<Integer, String>builder()
+            .put(70, "Attempt to create, modify or remove the system property 'rep:externalPrincipalNames'")
+            .put(71, "Property 'rep:externalPrincipalNames' must be multi-valued of type STRING.")
+            .put(72, "Property 'rep:externalPrincipalNames' requires 'rep:externalId' to be present on the Node.")
+            .put(73, "Property 'rep:externalId' cannot be removed as long as 'rep:externalPrincipalNames' is present.")
+            .put(74, "Attempt to add, modify or remove the system maintained property 'rep:externalId'.")
+            .put(75, "Property 'rep:externalId' may only have a single value of type STRING.").build();
+
     private final boolean isSystem;
+    private final boolean protectedExternalIds;
 
-    ExternalIdentityValidatorProvider(@Nonnull Set<Principal> principals) {
+    ExternalIdentityValidatorProvider(@Nonnull Set<Principal> principals, boolean protectExternalIds) {
         isSystem = principals.contains(SystemPrincipal.INSTANCE);
+        this.protectedExternalIds = protectExternalIds;
 
     }
 
-    private void checkAddModifyProperties(@Nonnull NodeState parent, @Nonnull String name, @Nonnull PropertyState propertyState) throws CommitFailedException {
-        if (REP_EXTERNAL_PRINCIPAL_NAMES.equals(name)) {
-            if (!isSystem) {
-                throw new CommitFailedException(CommitFailedException.CONSTRAINT, 70, "Attempt to create, modify or remove the system property " + name);
-            }
+    private void checkAddModifyProperties(@Nonnull NodeState parent, @Nonnull String name, @Nonnull PropertyState propertyState, boolean isModify) throws CommitFailedException {
+        if (RESERVED_PROPERTY_NAMES.contains(name)) {
             Type<?> type = propertyState.getType();
-            if (!Type.STRINGS.equals(type) || !propertyState.isArray()) {
-                throw new CommitFailedException(CommitFailedException.CONSTRAINT, 71, "Property rep:externalPrincipalNames must be multi-valued of type STRING.");
+            if (REP_EXTERNAL_PRINCIPAL_NAMES.equals(name)) {
+                if (!isSystem) {
+                    throw new CommitFailedException(CommitFailedException.CONSTRAINT, 70, ERROR_MSGS.get(70));
+                }
+                if (!Type.STRINGS.equals(type) || !propertyState.isArray()) {
+                    throw new CommitFailedException(CommitFailedException.CONSTRAINT, 71, ERROR_MSGS.get(71));
+                }
+                if (!parent.hasProperty(REP_EXTERNAL_ID)) {
+                    throw new CommitFailedException(CommitFailedException.CONSTRAINT, 72, ERROR_MSGS.get(72));
+                }
             }
-            if (!parent.hasProperty(REP_EXTERNAL_ID)) {
-                throw new CommitFailedException(CommitFailedException.CONSTRAINT, 72, "Property rep:externalPrincipalNames requires rep:externalId to be present on the Node.");
+            if (REP_EXTERNAL_ID.equals(name) && protectedExternalIds) {
+                if (isModify && !isSystem) {
+                    throw new CommitFailedException(CommitFailedException.CONSTRAINT, 74, ERROR_MSGS.get(74));
+                }
+                if (!Type.STRING.equals(type) || propertyState.isArray()) {
+                    throw new CommitFailedException(CommitFailedException.CONSTRAINT, 75, ERROR_MSGS.get(75));
+                }
             }
         }
     }
 
     private void checkRemoveProperties(@Nonnull NodeState parent, @Nonnull String name) throws CommitFailedException {
         if (RESERVED_PROPERTY_NAMES.contains(name)) {
-            if (REP_EXTERNAL_ID.equals(name) && parent.hasProperty(REP_EXTERNAL_PRINCIPAL_NAMES)) {
-                throw new CommitFailedException(CommitFailedException.CONSTRAINT, 73, "Property rep:externalId cannot be removed as long as rep:externalPrincipalNames is present.");
+            if (REP_EXTERNAL_ID.equals(name)) {
+                if (parent.hasProperty(REP_EXTERNAL_PRINCIPAL_NAMES)) {
+                    throw new CommitFailedException(CommitFailedException.CONSTRAINT, 73, ERROR_MSGS.get(73));
+                }
+                if (protectedExternalIds && !isSystem) {
+                    throw new CommitFailedException(CommitFailedException.CONSTRAINT, 74, ERROR_MSGS.get(74));
+                }
             }
             if (REP_EXTERNAL_PRINCIPAL_NAMES.equals(name) && !isSystem) {
-                throw new CommitFailedException(CommitFailedException.CONSTRAINT, 70, "Attempt to create, modify or remove the system property " + name);
+                throw new CommitFailedException(CommitFailedException.CONSTRAINT, 70, ERROR_MSGS.get(70));
             }
         }
     }
@@ -76,25 +103,27 @@ class ExternalIdentityValidatorProvider
     @Override
     protected Validator getRootValidator(@Nonnull NodeState before, @Nonnull NodeState after,
                                          @Nonnull CommitInfo info) {
-        return new ExternalIdentityValidator(after);
+        return new ExternalIdentityValidator(after, true);
     }
 
     private final class ExternalIdentityValidator extends DefaultValidator {
 
         private final NodeState parent;
+        private final boolean modifiedParent;
 
-        private ExternalIdentityValidator(@Nonnull NodeState parent) {
+        private ExternalIdentityValidator(@Nonnull NodeState parent, boolean modifiedParent) {
             this.parent = parent;
+            this.modifiedParent = modifiedParent;
         }
 
         @Override
         public void propertyAdded(PropertyState after) throws CommitFailedException {
-            checkAddModifyProperties(parent, after.getName(), after);
+            checkAddModifyProperties(parent, after.getName(), after, modifiedParent);
         }
 
         @Override
         public void propertyChanged(PropertyState before, PropertyState after) throws CommitFailedException {
-            checkAddModifyProperties(parent, before.getName(), after);
+            checkAddModifyProperties(parent, before.getName(), after, modifiedParent);
         }
 
         @Override
@@ -103,17 +132,17 @@ class ExternalIdentityValidatorProvider
         }
 
         @Override
-        public Validator childNodeAdded(String name, NodeState after) throws CommitFailedException {
-            return new ExternalIdentityValidator(after);
+        public Validator childNodeAdded(String name, NodeState after) {
+            return new ExternalIdentityValidator(after, false);
         }
 
         @Override
-        public Validator childNodeChanged(String name, NodeState before, NodeState after) throws CommitFailedException {
-            return new ExternalIdentityValidator(after);
+        public Validator childNodeChanged(String name, NodeState before, NodeState after) {
+            return new ExternalIdentityValidator(after, true);
         }
 
         @Override
-        public Validator childNodeDeleted(String name, NodeState before) throws CommitFailedException {
+        public Validator childNodeDeleted(String name, NodeState before) {
             // removal of the parent node containing a reserved property must be possible
             return null;
         }

Modified: jackrabbit/oak/trunk/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/principal/ExternalPrincipalConfiguration.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/principal/ExternalPrincipalConfiguration.java?rev=1755191&r1=1755190&r2=1755191&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/principal/ExternalPrincipalConfiguration.java (original)
+++ jackrabbit/oak/trunk/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/principal/ExternalPrincipalConfiguration.java Thu Aug  4 14:03:31 2016
@@ -34,6 +34,8 @@ import com.google.common.collect.Iterabl
 import org.apache.felix.scr.annotations.Activate;
 import org.apache.felix.scr.annotations.Component;
 import org.apache.felix.scr.annotations.Deactivate;
+import org.apache.felix.scr.annotations.Properties;
+import org.apache.felix.scr.annotations.Property;
 import org.apache.felix.scr.annotations.Service;
 import org.apache.jackrabbit.api.security.principal.PrincipalManager;
 import org.apache.jackrabbit.oak.api.Root;
@@ -48,6 +50,7 @@ import org.apache.jackrabbit.oak.spi.sec
 import org.apache.jackrabbit.oak.spi.security.SecurityProvider;
 import org.apache.jackrabbit.oak.spi.security.authentication.external.SyncHandler;
 import org.apache.jackrabbit.oak.spi.security.authentication.external.impl.DefaultSyncConfigImpl;
+import org.apache.jackrabbit.oak.spi.security.authentication.external.impl.ExternalIdentityConstants;
 import org.apache.jackrabbit.oak.spi.security.authentication.external.impl.ExternalLoginModuleFactory;
 import org.apache.jackrabbit.oak.spi.security.authentication.external.impl.SyncHandlerMapping;
 import org.apache.jackrabbit.oak.spi.security.principal.EmptyPrincipalProvider;
@@ -78,6 +81,12 @@ import org.slf4j.LoggerFactory;
         immediate = true
 )
 @Service({PrincipalConfiguration.class, SecurityConfiguration.class})
+@Properties({
+        @Property(name = ExternalIdentityConstants.PARAM_PROTECT_EXTERNAL_IDS,
+                label = "External Identity Protection",
+                description = "If disabled rep:externalId properties won't be properly protected (backwards compatible behavior). NOTE: for security reasons it is strongly recommend to keep the protection enabled!",
+                boolValue = ExternalIdentityConstants.DEFAULT_PROTECT_EXTERNAL_IDS)
+})
 public class ExternalPrincipalConfiguration extends ConfigurationBase implements PrincipalConfiguration {
 
     private static final Logger log = LoggerFactory.getLogger(ExternalPrincipalConfiguration.class);
@@ -122,13 +131,13 @@ public class ExternalPrincipalConfigurat
     @Nonnull
     @Override
     public RepositoryInitializer getRepositoryInitializer() {
-        return new ExternalIdentityRepositoryInitializer();
+        return new ExternalIdentityRepositoryInitializer(protectedExternalIds());
     }
 
     @Nonnull
     @Override
     public List<? extends ValidatorProvider> getValidators(@Nonnull String workspaceName, @Nonnull Set<Principal> principals, @Nonnull MoveTracker moveTracker) {
-        return ImmutableList.of(new ExternalIdentityValidatorProvider(principals));
+        return ImmutableList.of(new ExternalIdentityValidatorProvider(principals, protectedExternalIds()));
     }
 
     @Nonnull
@@ -168,6 +177,10 @@ public class ExternalPrincipalConfigurat
         return syncConfigTracker != null && syncConfigTracker.isEnabled;
     }
 
+    private boolean protectedExternalIds() {
+        return getParameters().getConfigValue(ExternalIdentityConstants.PARAM_PROTECT_EXTERNAL_IDS, ExternalIdentityConstants.DEFAULT_PROTECT_EXTERNAL_IDS);
+    }
+
     /**
      * {@code ServiceTracker} to detect any {@link SyncHandler} that has
      * dynamic membership enabled.

Modified: jackrabbit/oak/trunk/oak-auth-external/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/basic/RepExternalIdTest.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-auth-external/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/basic/RepExternalIdTest.java?rev=1755191&r1=1755190&r2=1755191&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-auth-external/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/basic/RepExternalIdTest.java (original)
+++ jackrabbit/oak/trunk/oak-auth-external/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/basic/RepExternalIdTest.java Thu Aug  4 14:03:31 2016
@@ -19,7 +19,10 @@ package org.apache.jackrabbit.oak.spi.se
 import javax.annotation.Nonnull;
 
 import org.apache.jackrabbit.api.security.user.Authorizable;
+import org.apache.jackrabbit.api.security.user.UserManager;
+import org.apache.jackrabbit.oak.api.CommitFailedException;
 import org.apache.jackrabbit.oak.api.PropertyState;
+import org.apache.jackrabbit.oak.api.Root;
 import org.apache.jackrabbit.oak.api.Tree;
 import org.apache.jackrabbit.oak.api.Type;
 import org.apache.jackrabbit.oak.spi.security.authentication.external.AbstractExternalAuthTest;
@@ -34,16 +37,21 @@ import static org.junit.Assert.assertFal
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertSame;
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
 
 public class RepExternalIdTest extends AbstractExternalAuthTest {
 
+    private Root r;
+    private UserManager userManager;
     private DefaultSyncContext syncCtx;
 
     @Before
     public void before() throws Exception {
         super.before();
 
-        syncCtx = new DefaultSyncContext(syncConfig, idp, getUserManager(root), getValueFactory());
+        r = getSystemRoot();
+        userManager = getUserManager(r);
+        syncCtx = new DefaultSyncContext(syncConfig, idp, getUserManager(r), getValueFactory(r));
     }
 
     @Override
@@ -61,10 +69,10 @@ public class RepExternalIdTest extends A
         assertNotNull(si);
 
 
-        Authorizable authorizable = getUserManager(root).getAuthorizable(si.getId());
+        Authorizable authorizable = userManager.getAuthorizable(si.getId());
         assertNotNull(authorizable);
 
-        Tree userTree = root.getTree(authorizable.getPath());
+        Tree userTree = r.getTree(authorizable.getPath());
         assertTrue(userTree.hasProperty(DefaultSyncContext.REP_EXTERNAL_ID));
 
         PropertyState ps = userTree.getProperty(DefaultSyncContext.REP_EXTERNAL_ID);
@@ -87,4 +95,59 @@ public class RepExternalIdTest extends A
 
         assertRepExternalId(res);
     }
+
+    @Test
+    public void testUniqueConstraint() throws Exception {
+        SyncResult res = syncCtx.sync(idp.getUser(USER_ID));
+
+        try {
+            Tree t = r.getTree(getTestUser().getPath());
+            t.setProperty(DefaultSyncContext.REP_EXTERNAL_ID, res.getIdentity().getExternalIdRef().getString());
+            r.commit();
+            fail("Duplicate value for rep:externalId must be detected in the default setup.");
+        } catch (CommitFailedException e) {
+            // success: verify nature of the exception
+            assertTrue(e.isConstraintViolation());
+            assertEquals(30, e.getCode());
+        } finally {
+            r.refresh();
+        }
+    }
+
+    @Test
+    public void testUniqueConstraintSubsequentCommit() throws Exception {
+        SyncResult res = syncCtx.sync(idp.getUser(USER_ID));
+        r.commit();
+
+        try {
+            Tree t = r.getTree(getTestUser().getPath());
+            t.setProperty(DefaultSyncContext.REP_EXTERNAL_ID, res.getIdentity().getExternalIdRef().getString());
+            r.commit();
+            fail("Duplicate value for rep:externalId must be detected in the default setup.");
+        } catch (CommitFailedException e) {
+            // success: verify nature of the exception
+            assertTrue(e.isConstraintViolation());
+            assertEquals(30, e.getCode());
+        } finally {
+            r.refresh();
+        }
+    }
+
+    @Test
+    public void testUniqueConstraintNonUserNode() throws Exception {
+        try {
+            SyncResult res = syncCtx.sync(idp.getUser(USER_ID));
+            Tree nonUserTree = r.getTree("/");
+            nonUserTree.setProperty(DefaultSyncContext.REP_EXTERNAL_ID, res.getIdentity().getExternalIdRef().getString());
+            r.commit();
+
+            fail("Duplicate value for rep:externalId must be detected in the default setup.");
+        } catch (CommitFailedException e) {
+            // success: verify nature of the exception
+            assertTrue(e.isConstraintViolation());
+            assertEquals(30, e.getCode());
+        } finally {
+            r.refresh();
+        }
+    }
 }
\ No newline at end of file

Modified: jackrabbit/oak/trunk/oak-auth-external/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/DefaultSyncHandlerTest.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-auth-external/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/DefaultSyncHandlerTest.java?rev=1755191&r1=1755190&r2=1755191&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-auth-external/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/DefaultSyncHandlerTest.java (original)
+++ jackrabbit/oak/trunk/oak-auth-external/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/DefaultSyncHandlerTest.java Thu Aug  4 14:03:31 2016
@@ -26,6 +26,7 @@ import javax.jcr.Value;
 import com.google.common.collect.Sets;
 import org.apache.jackrabbit.api.security.user.Authorizable;
 import org.apache.jackrabbit.api.security.user.UserManager;
+import org.apache.jackrabbit.oak.api.Root;
 import org.apache.jackrabbit.oak.spi.security.authentication.external.ExternalIdentity;
 import org.apache.jackrabbit.oak.spi.security.authentication.external.ExternalIdentityRef;
 import org.apache.jackrabbit.oak.spi.security.authentication.external.ExternalLoginModuleTestBase;
@@ -145,10 +146,12 @@ public class DefaultSyncHandlerTest exte
     public void testFindIdentityWithRemovedExternalId() throws Exception {
         sync(USER_ID, false);
 
-        // NOTE: this is only possible as long the rep:externalId property is not protected
+        // NOTE: use system-root to remove the protected rep:externalId property (since Oak 1.5.8)
         Authorizable authorizable = userManager.getAuthorizable(USER_ID);
-        authorizable.removeProperty(DefaultSyncContext.REP_EXTERNAL_ID);
-        root.commit();
+        Root systemRoot = getSystemRoot();
+        systemRoot.getTree(authorizable.getPath()).removeProperty(DefaultSyncContext.REP_EXTERNAL_ID);
+        systemRoot.commit();
+        root.refresh();
 
         SyncedIdentity si = syncHandler.findIdentity(userManager, USER_ID);
         assertNotNull(si);

Modified: jackrabbit/oak/trunk/oak-auth-external/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/principal/ExternalIdentityImporterTest.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-auth-external/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/principal/ExternalIdentityImporterTest.java?rev=1755191&r1=1755190&r2=1755191&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-auth-external/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/principal/ExternalIdentityImporterTest.java (original)
+++ jackrabbit/oak/trunk/oak-auth-external/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/principal/ExternalIdentityImporterTest.java Thu Aug  4 14:03:31 2016
@@ -50,6 +50,16 @@ import static org.junit.Assert.assertTru
  */
 public class ExternalIdentityImporterTest {
 
+    public static final String XML_EXTERNAL_USER = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" +
+            "<sv:node sv:name=\"t\" xmlns:mix=\"http://www.jcp.org/jcr/mix/1.0\" xmlns:nt=\"http://www.jcp.org/jcr/nt/1.0\" xmlns:fn_old=\"http://www.w3.org/2004/10/xpath-functions\" xmlns:fn=\"http://www.w3.org/2005/xpath-functions\" xmlns:xs=\"http://www.w3.org/2001/XMLSchema\" xmlns:sv=\"http://www.jcp.org/jcr/sv/1.0\" xmlns:rep=\"internal\" xmlns:jcr=\"http://www.jcp.org/jcr/1.0\">" +
+            "   <sv:property sv:name=\"jcr:primaryType\" sv:type=\"Name\"><sv:value>rep:User</sv:value></sv:property>" +
+            "   <sv:property sv:name=\"jcr:uuid\" sv:type=\"String\"><sv:value>e358efa4-89f5-3062-b10d-d7316b65649e</sv:value></sv:property>" +
+            "   <sv:property sv:name=\"rep:authorizableId\" sv:type=\"String\"><sv:value>t</sv:value></sv:property>" +
+            "   <sv:property sv:name=\"rep:principalName\" sv:type=\"String\"><sv:value>tPrinc</sv:value></sv:property>" +
+            "   <sv:property sv:name=\"rep:externalId\" sv:type=\"String\"><sv:value>idp;ext-t</sv:value></sv:property>" +
+            "   <sv:property sv:name=\"rep:lastSynced\" sv:type=\"Date\"><sv:value>2016-05-03T10:03:08.061+02:00</sv:value></sv:property>" +
+            "</sv:node>";
+
     public static final String XML_EXTERNAL_USER_WITH_PRINCIPAL_NAMES = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" +
             "<sv:node sv:name=\"t\" xmlns:mix=\"http://www.jcp.org/jcr/mix/1.0\" xmlns:nt=\"http://www.jcp.org/jcr/nt/1.0\" xmlns:fn_old=\"http://www.w3.org/2004/10/xpath-functions\" xmlns:fn=\"http://www.w3.org/2005/xpath-functions\" xmlns:xs=\"http://www.w3.org/2001/XMLSchema\" xmlns:sv=\"http://www.jcp.org/jcr/sv/1.0\" xmlns:rep=\"internal\" xmlns:jcr=\"http://www.jcp.org/jcr/1.0\">" +
             "   <sv:property sv:name=\"jcr:primaryType\" sv:type=\"Name\"><sv:value>rep:User</sv:value></sv:property>" +
@@ -133,6 +143,20 @@ public class ExternalIdentityImporterTes
     }
 
     @Test
+    public void importExternalUser() throws Exception {
+        Node parent = doImport(createSession(false), UserConstants.DEFAULT_USER_PATH, XML_EXTERNAL_USER);
+        assertHasProperties(parent.getNode("t"), ExternalIdentityConstants.REP_EXTERNAL_ID, ExternalIdentityConstants.REP_LAST_SYNCED);
+        assertNotHasProperties(parent.getNode("t"), ExternalIdentityConstants.REP_EXTERNAL_PRINCIPAL_NAMES);
+    }
+
+    @Test
+    public void importExternalUserAsSystem() throws Exception {
+        Node parent = doImport(createSession(true), UserConstants.DEFAULT_USER_PATH, XML_EXTERNAL_USER);
+        assertHasProperties(parent.getNode("t"), ExternalIdentityConstants.REP_EXTERNAL_ID, ExternalIdentityConstants.REP_LAST_SYNCED);
+        assertNotHasProperties(parent.getNode("t"), ExternalIdentityConstants.REP_EXTERNAL_PRINCIPAL_NAMES);
+    }
+
+    @Test
     public void importExternalUserWithPrincipalNames() throws Exception {
         Node parent = doImport(createSession(false), UserConstants.DEFAULT_USER_PATH, XML_EXTERNAL_USER_WITH_PRINCIPAL_NAMES);
         assertHasProperties(parent.getNode("t"), ExternalIdentityConstants.REP_EXTERNAL_ID);

Modified: jackrabbit/oak/trunk/oak-auth-external/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/principal/ExternalIdentityRepositoryInitializerTest.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-auth-external/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/principal/ExternalIdentityRepositoryInitializerTest.java?rev=1755191&r1=1755190&r2=1755191&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-auth-external/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/principal/ExternalIdentityRepositoryInitializerTest.java (original)
+++ jackrabbit/oak/trunk/oak-auth-external/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/principal/ExternalIdentityRepositoryInitializerTest.java Thu Aug  4 14:03:31 2016
@@ -32,7 +32,16 @@ import static org.junit.Assert.assertTru
 public class ExternalIdentityRepositoryInitializerTest extends AbstractExternalAuthTest {
 
     @Test
-    public void testIndexDefinitions() throws Exception {
+    public void testExternalIdIndexDefinition() throws Exception {
+        Tree oakIndex = root.getTree('/' + IndexConstants.INDEX_DEFINITIONS_NAME);
+        assertTrue(oakIndex.exists());
+
+        Tree externalIdIndex = oakIndex.getChild("externalId");
+        assertIndexDefinition(externalIdIndex, ExternalIdentityConstants.REP_EXTERNAL_ID, true);
+    }
+
+    @Test
+    public void testPrincipalNamesIndexDefinition() throws Exception {
         Tree oakIndex = root.getTree('/' + IndexConstants.INDEX_DEFINITIONS_NAME);
         assertTrue(oakIndex.exists());
 

Modified: jackrabbit/oak/trunk/oak-auth-external/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/principal/ExternalIdentityValidatorTest.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-auth-external/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/principal/ExternalIdentityValidatorTest.java?rev=1755191&r1=1755190&r2=1755191&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-auth-external/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/principal/ExternalIdentityValidatorTest.java (original)
+++ jackrabbit/oak/trunk/oak-auth-external/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/principal/ExternalIdentityValidatorTest.java Thu Aug  4 14:03:31 2016
@@ -22,11 +22,13 @@ import com.google.common.collect.Immutab
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Lists;
 import org.apache.jackrabbit.api.security.user.Authorizable;
+import org.apache.jackrabbit.api.security.user.User;
 import org.apache.jackrabbit.oak.api.CommitFailedException;
 import org.apache.jackrabbit.oak.api.Root;
 import org.apache.jackrabbit.oak.api.Tree;
 import org.apache.jackrabbit.oak.api.Type;
 import org.apache.jackrabbit.oak.spi.security.authentication.external.ExternalLoginModuleTestBase;
+import org.apache.jackrabbit.oak.spi.security.authentication.external.TestIdentityProvider;
 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.util.NodeUtil;
@@ -54,32 +56,19 @@ public class ExternalIdentityValidatorTe
 
         Authorizable a = getUserManager(root).getAuthorizable(USER_ID);
         assertNotNull(a);
-        assertTrue(a.hasProperty(ExternalIdentityConstants.REP_EXTERNAL_PRINCIPAL_NAMES));
+        assertEquals(isDynamic(), a.hasProperty(ExternalIdentityConstants.REP_EXTERNAL_PRINCIPAL_NAMES));
         externalUserPath = a.getPath();
     }
 
     @Override
     protected DefaultSyncConfig createSyncConfig() {
         DefaultSyncConfig config = super.createSyncConfig();
-        config.user().setDynamicMembership(true);
+        config.user().setDynamicMembership(isDynamic());
         return config;
     }
 
-    @Test
-    public void testRemoveRepExternalIdAsSystem() throws Exception {
-        Root systemRoot = getSystemRoot();
-        try {
-            NodeUtil n = new NodeUtil(systemRoot.getTree(externalUserPath));
-
-            n.removeProperty(ExternalIdentityConstants.REP_EXTERNAL_ID);
-            systemRoot.commit();
-            fail("Removing rep:externalId is not allowed if rep:externalPrincipalNames is present.");
-        } catch (CommitFailedException e) {
-            // success
-            assertEquals(73, e.getCode());
-        } finally {
-            systemRoot.refresh();
-        }
+    protected boolean isDynamic() {
+        return true;
     }
 
     @Test
@@ -213,4 +202,174 @@ public class ExternalIdentityValidatorTe
             systemRoot.refresh();
         }
     }
+
+    @Test
+    public void testRepExternalIdMultiple() throws Exception {
+        Root systemRoot = getSystemRoot();
+        try {
+            NodeUtil n = new NodeUtil(systemRoot.getTree(testUserPath));
+            n.setStrings(ExternalIdentityConstants.REP_EXTERNAL_ID, "id", "id2");
+            systemRoot.commit();
+            fail("Creating rep:externalId as multiple STRING property must be detected.");
+        } catch (CommitFailedException e) {
+            // success
+            assertEquals(75, e.getCode());
+        } finally {
+            systemRoot.refresh();
+        }
+    }
+
+    @Test
+    public void testRepExternalIdType() throws Exception {
+        Root systemRoot = getSystemRoot();
+        Tree userTree = systemRoot.getTree(testUserPath);
+
+        java.util.Map<Type, Object> valMap = ImmutableMap.<Type, Object>of(
+                Type.BOOLEAN, Boolean.TRUE,
+                Type.LONG, new Long(1234),
+                Type.NAME, "id"
+        );
+        for (Type t : valMap.keySet()) {
+            Object val = valMap.get(t);
+            try {
+                userTree.setProperty(ExternalIdentityConstants.REP_EXTERNAL_ID, val, t);
+                systemRoot.commit();
+                fail("Creating rep:externalId with type "+t+" must be detected.");
+            } catch (CommitFailedException e) {
+                // success
+                assertEquals(75, e.getCode());
+            } finally {
+                systemRoot.refresh();
+            }
+        }
+    }
+
+    @Test
+    public void testCreateUserWithRepExternalId() throws Exception {
+        User u = getUserManager(root).createUser(TestIdentityProvider.ID_SECOND_USER, null);
+        root.getTree(u.getPath()).setProperty(ExternalIdentityConstants.REP_EXTERNAL_ID, TestIdentityProvider.ID_SECOND_USER);
+        root.commit();
+    }
+
+    @Test
+    public void testCreateUserWithRepExternalIdAsSystem() throws Exception {
+        Root systemRoot = getSystemRoot();
+        User u = getUserManager(systemRoot).createUser(TestIdentityProvider.ID_SECOND_USER, null);
+        systemRoot.getTree(u.getPath()).setProperty(ExternalIdentityConstants.REP_EXTERNAL_ID, TestIdentityProvider.ID_SECOND_USER);
+        systemRoot.commit();
+    }
+
+    @Test
+    public void testAddRepExternalId() throws Exception {
+        try {
+            root.getTree(testUserPath).setProperty(ExternalIdentityConstants.REP_EXTERNAL_ID, "id");
+            root.commit();
+            fail("Adding rep:externalId must be detected in the default setup.");
+        } catch (CommitFailedException e) {
+            // success: verify nature of the exception
+            assertTrue(e.isConstraintViolation());
+            assertEquals(74, e.getCode());
+        }
+
+    }
+
+    @Test
+    public void testAddRepExternalIdAsSystem() throws Exception {
+        Root systemRoot = getSystemRoot();
+        systemRoot.getTree(testUserPath).setProperty(ExternalIdentityConstants.REP_EXTERNAL_ID, "id");
+        systemRoot.commit();
+    }
+
+    @Test
+    public void testModifyRepExternalId() throws Exception {
+        try {
+            root.getTree(externalUserPath).setProperty(ExternalIdentityConstants.REP_EXTERNAL_ID, "anotherValue");
+            root.commit();
+
+            fail("Modification of rep:externalId must be detected in the default setup.");
+        } catch (CommitFailedException e) {
+            // success: verify nature of the exception
+            assertTrue(e.isConstraintViolation());
+            assertEquals(74, e.getCode());
+        }
+    }
+
+    @Test
+    public void testModifyRepExternalIdAsSystem() throws Exception {
+        Root systemRoot = getSystemRoot();
+        systemRoot.getTree(externalUserPath).setProperty(ExternalIdentityConstants.REP_EXTERNAL_ID, "anotherValue");
+        systemRoot.commit();
+    }
+
+    @Test
+    public void testRemoveRepExternalId() throws Exception {
+        try {
+            root.getTree(externalUserPath).removeProperty(ExternalIdentityConstants.REP_EXTERNAL_ID);
+            root.commit();
+
+            fail("Removal of rep:externalId must be detected in the default setup.");
+        } catch (CommitFailedException e) {
+            // success: verify nature of the exception
+            assertTrue(e.isConstraintViolation());
+            assertEquals(73, e.getCode());
+        }
+    }
+
+    @Test
+    public void testRemoveRepExternalIdAsSystem() throws Exception {
+        Root systemRoot = getSystemRoot();
+        try {
+            NodeUtil n = new NodeUtil(systemRoot.getTree(externalUserPath));
+
+            n.removeProperty(ExternalIdentityConstants.REP_EXTERNAL_ID);
+            systemRoot.commit();
+            fail("Removing rep:externalId is not allowed if rep:externalPrincipalNames is present.");
+        } catch (CommitFailedException e) {
+            // success
+            assertEquals(73, e.getCode());
+        } finally {
+            systemRoot.refresh();
+        }
+    }
+
+    @Test
+    public void testRemoveRepExternalIdWithoutPrincipalNames() throws Exception {
+        Root systemRoot = getSystemRoot();
+        systemRoot.getTree(testUserPath).setProperty(ExternalIdentityConstants.REP_EXTERNAL_ID, "id");
+        systemRoot.commit();
+        root.refresh();
+
+        try {
+            root.getTree(testUserPath).removeProperty(ExternalIdentityConstants.REP_EXTERNAL_ID);
+            root.commit();
+
+            fail("Removal of rep:externalId must be detected in the default setup.");
+        } catch (CommitFailedException e) {
+            // success: verify nature of the exception
+            assertTrue(e.isConstraintViolation());
+            assertEquals(74, e.getCode());
+        }
+    }
+
+    @Test
+    public void testRemoveRepExternalIdWithoutPrincipalNamesAsSystem() throws Exception {
+        Root systemRoot = getSystemRoot();
+        systemRoot.getTree(testUserPath).setProperty(ExternalIdentityConstants.REP_EXTERNAL_ID, "id");
+        systemRoot.commit();
+
+        systemRoot.getTree(testUserPath).removeProperty(ExternalIdentityConstants.REP_EXTERNAL_ID);
+        systemRoot.commit();
+    }
+
+    @Test
+    public void testRemoveExternalUser() throws Exception {
+        getUserManager(root).getAuthorizable(USER_ID).remove();
+        root.commit();
+    }
+
+    @Test
+    public void testRemoveExternalUserTree() throws Exception {
+        root.getTree(externalUserPath).remove();
+        root.commit();
+    }
 }
\ No newline at end of file

Modified: jackrabbit/oak/trunk/oak-auth-external/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/principal/ExternalPrincipalConfigurationTest.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-auth-external/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/principal/ExternalPrincipalConfigurationTest.java?rev=1755191&r1=1755190&r2=1755191&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-auth-external/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/principal/ExternalPrincipalConfigurationTest.java (original)
+++ jackrabbit/oak/trunk/oak-auth-external/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/principal/ExternalPrincipalConfigurationTest.java Thu Aug  4 14:03:31 2016
@@ -34,6 +34,7 @@ import org.apache.jackrabbit.oak.namepat
 import org.apache.jackrabbit.oak.spi.commit.MoveTracker;
 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.authentication.external.AbstractExternalAuthTest;
 import org.apache.jackrabbit.oak.spi.security.authentication.external.ExternalIdentityProvider;
@@ -45,6 +46,7 @@ import org.apache.jackrabbit.oak.spi.sec
 import org.apache.jackrabbit.oak.spi.security.authentication.external.basic.DefaultSyncContext;
 import org.apache.jackrabbit.oak.spi.security.authentication.external.impl.DefaultSyncConfigImpl;
 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.principal.PrincipalConfiguration;
 import org.apache.jackrabbit.oak.spi.security.principal.PrincipalProvider;
 import org.apache.jackrabbit.oak.spi.xml.ProtectedItemImporter;
@@ -157,6 +159,24 @@ public class ExternalPrincipalConfigurat
         assertFalse(validatorProviders.isEmpty());
         assertEquals(1, validatorProviders.size());
         assertTrue(validatorProviders.get(0) instanceof ExternalIdentityValidatorProvider);
+
+        enable();
+
+        validatorProviders = principalConfiguration.getValidators(cs.getWorkspaceName(), cs.getAuthInfo().getPrincipals(), new MoveTracker());
+        assertFalse(validatorProviders.isEmpty());
+        assertEquals(1, validatorProviders.size());
+        assertTrue(validatorProviders.get(0) instanceof ExternalIdentityValidatorProvider);
+    }
+
+    @Test
+    public void testGetValidatorsOmitIdProtection() throws Exception {
+        principalConfiguration.setParameters(ConfigurationParameters.of(ExternalIdentityConstants.PARAM_PROTECT_EXTERNAL_IDS, false));
+        ContentSession cs = root.getContentSession();
+
+        List<? extends ValidatorProvider> validatorProviders = principalConfiguration.getValidators(cs.getWorkspaceName(), cs.getAuthInfo().getPrincipals(), new MoveTracker());
+        assertFalse(validatorProviders.isEmpty());
+        assertEquals(1, validatorProviders.size());
+        assertTrue(validatorProviders.get(0) instanceof ExternalIdentityValidatorProvider);
 
         enable();
 

Copied: jackrabbit/oak/trunk/oak-auth-external/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/principal/ValidatorNoProtectionTest.java (from r1754940, jackrabbit/oak/trunk/oak-auth-external/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/principal/ExternalIdentityValidatorTest.java)
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-auth-external/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/principal/ValidatorNoProtectionTest.java?p2=jackrabbit/oak/trunk/oak-auth-external/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/principal/ValidatorNoProtectionTest.java&p1=jackrabbit/oak/trunk/oak-auth-external/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/principal/ExternalIdentityValidatorTest.java&r1=1754940&r2=1755191&rev=1755191&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-auth-external/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/principal/ExternalIdentityValidatorTest.java (original)
+++ jackrabbit/oak/trunk/oak-auth-external/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/principal/ValidatorNoProtectionTest.java Thu Aug  4 14:03:31 2016
@@ -16,201 +16,77 @@
  */
 package org.apache.jackrabbit.oak.spi.security.authentication.external.impl.principal;
 
-import javax.jcr.SimpleCredentials;
+import java.util.Map;
 
 import com.google.common.collect.ImmutableMap;
-import com.google.common.collect.ImmutableSet;
-import com.google.common.collect.Lists;
-import org.apache.jackrabbit.api.security.user.Authorizable;
-import org.apache.jackrabbit.oak.api.CommitFailedException;
 import org.apache.jackrabbit.oak.api.Root;
 import org.apache.jackrabbit.oak.api.Tree;
 import org.apache.jackrabbit.oak.api.Type;
-import org.apache.jackrabbit.oak.spi.security.authentication.external.ExternalLoginModuleTestBase;
-import org.apache.jackrabbit.oak.spi.security.authentication.external.basic.DefaultSyncConfig;
+import org.apache.jackrabbit.oak.spi.security.ConfigurationParameters;
 import org.apache.jackrabbit.oak.spi.security.authentication.external.impl.ExternalIdentityConstants;
 import org.apache.jackrabbit.oak.util.NodeUtil;
 import org.junit.Test;
 
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-
-public class ExternalIdentityValidatorTest extends ExternalLoginModuleTestBase {
-
-    String testUserPath;
-    String externalUserPath;
+public class ValidatorNoProtectionTest extends ExternalIdentityValidatorTest {
 
     @Override
     public void before() throws Exception {
         super.before();
 
-        testUserPath = getTestUser().getPath();
-
-        // force an external user to be synchronized into the repo
-        login(new SimpleCredentials(USER_ID, new char[0])).close();
-        root.refresh();
-
-        Authorizable a = getUserManager(root).getAuthorizable(USER_ID);
-        assertNotNull(a);
-        assertTrue(a.hasProperty(ExternalIdentityConstants.REP_EXTERNAL_PRINCIPAL_NAMES));
-        externalUserPath = a.getPath();
+        ConfigurationParameters params = ConfigurationParameters.of(externalPrincipalConfiguration.getParameters(), ConfigurationParameters.of(ExternalIdentityConstants.PARAM_PROTECT_EXTERNAL_IDS, false));
+        externalPrincipalConfiguration.setParameters(params);
     }
 
     @Override
-    protected DefaultSyncConfig createSyncConfig() {
-        DefaultSyncConfig config = super.createSyncConfig();
-        config.user().setDynamicMembership(true);
-        return config;
-    }
-
-    @Test
-    public void testRemoveRepExternalIdAsSystem() throws Exception {
-        Root systemRoot = getSystemRoot();
-        try {
-            NodeUtil n = new NodeUtil(systemRoot.getTree(externalUserPath));
-
-            n.removeProperty(ExternalIdentityConstants.REP_EXTERNAL_ID);
-            systemRoot.commit();
-            fail("Removing rep:externalId is not allowed if rep:externalPrincipalNames is present.");
-        } catch (CommitFailedException e) {
-            // success
-            assertEquals(73, e.getCode());
-        } finally {
-            systemRoot.refresh();
-        }
-    }
-
-    @Test
-    public void testAddExternalPrincipalNames() throws Exception {
-        Tree userTree = root.getTree(testUserPath);
-        NodeUtil userNode = new NodeUtil(userTree);
-        try {
-            userNode.setStrings(ExternalIdentityConstants.REP_EXTERNAL_PRINCIPAL_NAMES, "principalName");
-            root.commit();
-            fail("Creating rep:externalPrincipalNames must be detected.");
-        } catch (CommitFailedException e) {
-            // success
-            assertEquals(70, e.getCode());
-        } finally {
-            root.refresh();
-        }
-    }
-
     @Test
-    public void testAddExternalPrincipalNamesAsSystemMissingExternalId() throws Exception {
-        Root systemRoot = getSystemRoot();
-        try {
-            NodeUtil n = new NodeUtil(systemRoot.getTree(testUserPath));
-            n.setStrings(ExternalIdentityConstants.REP_EXTERNAL_PRINCIPAL_NAMES, "principalName");
-            systemRoot.commit();
-            fail("Creating rep:externalPrincipalNames without rep:externalId must be detected.");
-        } catch (CommitFailedException e) {
-            // success
-            assertEquals(72, e.getCode());
-        } finally {
-            systemRoot.refresh();
-        }
-    }
-
-    @Test
-    public void testAddExternalPrincipalNamesAsSystem() throws Exception {
+    public void testRepExternalIdMultiple() throws Exception {
         Root systemRoot = getSystemRoot();
         NodeUtil n = new NodeUtil(systemRoot.getTree(testUserPath));
-        n.setString(ExternalIdentityConstants.REP_EXTERNAL_ID, "externalId");
-        n.setStrings(ExternalIdentityConstants.REP_EXTERNAL_PRINCIPAL_NAMES, "principalName");
-        systemRoot.commit();
-    }
-
-    @Test
-    public void testRemoveExternalPrincipalNames() throws Exception {
-        Tree userTree = root.getTree(externalUserPath);
-        try {
-            userTree.removeProperty(ExternalIdentityConstants.REP_EXTERNAL_PRINCIPAL_NAMES);
-            root.commit();
-            fail("Removing rep:externalPrincipalNames must be detected.");
-        } catch (CommitFailedException e) {
-            // success
-            assertEquals(70, e.getCode());
-        } finally {
-            root.refresh();
-        }
-    }
-
-    @Test
-    public void testRemoveExternalPrincipalNamesAsSystem() throws Exception {
-        Root systemRoot = getSystemRoot();
-        NodeUtil n = new NodeUtil(systemRoot.getTree(externalUserPath));
-
-        // removal with system root must succeed
-        n.removeProperty(ExternalIdentityConstants.REP_EXTERNAL_PRINCIPAL_NAMES);
-        systemRoot.commit();
-    }
-
-    @Test
-    public void testModifyExternalPrincipalNames() throws Exception {
-        Tree userTree = root.getTree(externalUserPath);
-        try {
-            userTree.setProperty(ExternalIdentityConstants.REP_EXTERNAL_PRINCIPAL_NAMES, Lists.newArrayList("principalNames"), Type.STRINGS);
-            root.commit();
-            fail("Changing rep:externalPrincipalNames must be detected.");
-        } catch (CommitFailedException e) {
-            // success
-            assertEquals(70, e.getCode());
-        } finally {
-            root.refresh();
-        }
-    }
-
-    @Test
-    public void testModifyExternalPrincipalNamesAsSystem() throws Exception {
-        Root systemRoot = getSystemRoot();
-        NodeUtil n = new NodeUtil(systemRoot.getTree(externalUserPath));
-
-        // changing with system root must succeed
-        n.setStrings(ExternalIdentityConstants.REP_EXTERNAL_PRINCIPAL_NAMES, "principalNames");
+        n.setStrings(ExternalIdentityConstants.REP_EXTERNAL_ID, "id", "id2");
         systemRoot.commit();
     }
 
+    @Override
     @Test
-    public void testExternalPrincipalNamesType() throws Exception {
+    public void testRepExternalIdType() throws Exception {
         Root systemRoot = getSystemRoot();
         Tree userTree = systemRoot.getTree(testUserPath);
 
-        java.util.Map<Type, Object> valMap = ImmutableMap.<Type, Object>of(
-                Type.BOOLEANS, ImmutableSet.of(Boolean.TRUE),
-                Type.LONGS, ImmutableSet.of(new Long(1234)),
-                Type.NAMES, ImmutableSet.of("id", "id2")
+        Map<Type, Object> valMap = ImmutableMap.<Type, Object>of(
+                Type.BOOLEAN, Boolean.TRUE,
+                Type.LONG, new Long(1234),
+                Type.NAME, "id"
         );
         for (Type t : valMap.keySet()) {
             Object val = valMap.get(t);
             try {
-                userTree.setProperty(ExternalIdentityConstants.REP_EXTERNAL_PRINCIPAL_NAMES, val, t);
+                userTree.setProperty(ExternalIdentityConstants.REP_EXTERNAL_ID, val, t);
                 systemRoot.commit();
-                fail("Creating rep:externalPrincipalNames with type "+t+" must be detected.");
-            } catch (CommitFailedException e) {
-                // success
-                assertEquals(71, e.getCode());
             } finally {
                 systemRoot.refresh();
             }
         }
     }
 
+    @Override
+    public void testAddRepExternalId() throws Exception {
+        root.getTree(testUserPath).setProperty(ExternalIdentityConstants.REP_EXTERNAL_ID, "id");
+        root.commit();
+    }
+
+    @Override
     @Test
-    public void testExternalPrincipalNamesSingle() throws Exception {
-        Root systemRoot = getSystemRoot();
-        try {
-            NodeUtil n = new NodeUtil(systemRoot.getTree(testUserPath));
-            n.setString(ExternalIdentityConstants.REP_EXTERNAL_PRINCIPAL_NAMES, "id");
-            systemRoot.commit();
-            fail("Creating rep:externalPrincipalNames as single STRING property must be detected.");
-        } catch (CommitFailedException e) {
-            // success
-            assertEquals(71, e.getCode());
-        } finally {
-            systemRoot.refresh();
-        }
+    public void testModifyRepExternalId() throws Exception {
+        root.getTree(externalUserPath).setProperty(ExternalIdentityConstants.REP_EXTERNAL_ID, "anotherValue");
+        root.commit();
+    }
+
+    @Override
+    public void testRemoveRepExternalIdWithoutPrincipalNames() throws Exception {
+        root.getTree(testUserPath).setProperty(ExternalIdentityConstants.REP_EXTERNAL_ID, "id");
+        root.commit();
+
+        root.getTree(testUserPath).removeProperty(ExternalIdentityConstants.REP_EXTERNAL_ID);
+        root.commit();
     }
 }
\ No newline at end of file

Added: jackrabbit/oak/trunk/oak-auth-external/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/principal/ValidatorNotDynamicTest.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-auth-external/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/principal/ValidatorNotDynamicTest.java?rev=1755191&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-auth-external/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/principal/ValidatorNotDynamicTest.java (added)
+++ jackrabbit/oak/trunk/oak-auth-external/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/principal/ValidatorNotDynamicTest.java Thu Aug  4 14:03:31 2016
@@ -0,0 +1,108 @@
+/*
+ * 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.ImmutableList;
+import org.apache.jackrabbit.oak.api.CommitFailedException;
+import org.apache.jackrabbit.oak.api.Root;
+import org.apache.jackrabbit.oak.api.Type;
+import org.apache.jackrabbit.oak.spi.security.authentication.external.impl.ExternalIdentityConstants;
+import org.apache.jackrabbit.oak.util.NodeUtil;
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+public class ValidatorNotDynamicTest extends ExternalIdentityValidatorTest {
+
+    @Override
+    public void before() throws Exception {
+        super.before();
+    }
+
+    @Override
+    protected boolean isDynamic() {
+        return false;
+    }
+
+    private void setExternalPrincipalNames() throws Exception  {
+        Root systemRoot = getSystemRoot();
+        systemRoot.getTree(externalUserPath).setProperty(ExternalIdentityConstants.REP_EXTERNAL_PRINCIPAL_NAMES, ImmutableList.of("principalName"), Type.STRINGS);
+        systemRoot.commit();
+
+        root.refresh();
+    }
+
+    @Override
+    @Test
+    public void testRemoveExternalPrincipalNames() throws Exception {
+        setExternalPrincipalNames();
+
+        super.testRemoveExternalPrincipalNames();
+    }
+
+    @Override
+    @Test
+    public void testRemoveExternalPrincipalNamesAsSystem() throws Exception {
+        setExternalPrincipalNames();
+
+        super.testRemoveExternalPrincipalNamesAsSystem();
+    }
+
+    @Override
+    @Test
+    public void testModifyExternalPrincipalNames() throws Exception {
+        setExternalPrincipalNames();
+
+        super.testModifyExternalPrincipalNames();
+    }
+
+    @Override
+    @Test
+    public void testModifyExternalPrincipalNamesAsSystem() throws Exception {
+        setExternalPrincipalNames();
+
+        super.testModifyExternalPrincipalNamesAsSystem();
+    }
+
+
+    @Override
+    @Test
+    public void testRemoveRepExternalId() throws Exception {
+        try {
+            root.getTree(externalUserPath).removeProperty(ExternalIdentityConstants.REP_EXTERNAL_ID);
+            root.commit();
+
+            fail("Removal of rep:externalId must be detected in the default setup.");
+        } catch (CommitFailedException e) {
+            // success: verify nature of the exception
+            assertTrue(e.isConstraintViolation());
+            assertEquals(74, e.getCode());
+        }
+    }
+
+    @Override
+    @Test
+    public void testRemoveRepExternalIdAsSystem() throws Exception {
+        Root systemRoot = getSystemRoot();
+        NodeUtil n = new NodeUtil(systemRoot.getTree(externalUserPath));
+
+        n.removeProperty(ExternalIdentityConstants.REP_EXTERNAL_ID);
+        systemRoot.commit();
+    }
+}
\ No newline at end of file

Modified: jackrabbit/oak/trunk/oak-doc/src/site/markdown/security/authentication/external/defaultusersync.md
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-doc/src/site/markdown/security/authentication/external/defaultusersync.md?rev=1755191&r1=1755190&r2=1755191&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-doc/src/site/markdown/security/authentication/external/defaultusersync.md (original)
+++ jackrabbit/oak/trunk/oak-doc/src/site/markdown/security/authentication/external/defaultusersync.md Thu Aug  4 14:03:31 2016
@@ -50,6 +50,12 @@ the authorizables can later on be identi
 - `rep:externalId` : This allows to identify the external users, know the associated IDP and distinguish them from others.
 - `rep:lastSynced` : Sync timestamp to mark the external user/group valid for the configurable time (to reduce expensive syncing). Once expired, they will be validated against the 3rd party system again.
 
+NOTE: Since Oak 1.5.8 the system-maintained property `rep:externalId` is 
+protected and can not be altered using regular JCR and Jackrabbit 
+API, irrespective of the permission setup of the editing session. For 
+backwards compatibility this protection can be turned off. See [OAK-4301] 
+for further details.
+
 The [DefaultSyncContext] is exported as part of the 'basic' package space and
 may be used to provide custom implementations.
 
@@ -106,6 +112,8 @@ will re-create the `rep:externalPrincipa
 <a name="validation"/>
 #### Validation
 
+##### rep:externalPrincipalNames
+
 As of Oak 1.5.3 a dedicated `Validator` implementation asserts that the protected,
 system-maintained property `rep:externalPrincipalNames` is only written by the 
 internal system session. 
@@ -119,10 +127,21 @@ with external user/group accounts.
 
 | Code              | Message                                                  |
 |-------------------|----------------------------------------------------------|
-| 0070              | Attempt to create, modify or remove the system property rep:externalPrincipalNames |
-| 0071              | Attempt to write rep:externalPrincipalNames with a type other than Type.STRINGS |
-| 0072              | Property rep:externalPrincipalNames requires rep:externalId to be present on the Node. |
-| 0073              | Property rep:externalId cannot be removed if rep:externalPrincipalNames is present. |
+| 0070              | Attempt to create, modify or remove the system property 'rep:externalPrincipalNames' |
+| 0071              | Attempt to write 'rep:externalPrincipalNames' with a type other than Type.STRINGS |
+| 0072              | Property 'rep:externalPrincipalNames' requires 'rep:externalId' to be present on the Node. |
+| 0073              | Property 'rep:externalId' cannot be removed if 'rep:externalPrincipalNames' is present. |
+
+##### rep:externalId
+
+If protection of the `rep:externalId` property is enabled (since Oak 1.5.8) the
+validator performs the following checks:
+ 
+| Code              | Message                                                  |
+|-------------------|----------------------------------------------------------|
+| 0074              | Attempt to add, modify or remove the system maintained property 'rep:externalId'. |
+| 0075              | Property 'rep:externalId' may only have a single value of type STRING. |
+ 
 
 <a name="configuration"/>
 ### Configuration
@@ -160,6 +179,13 @@ as additional value to the `requiredServ
 
 See section [Introduction to Oak Security](../introduction.html) for further details on the `SecurityProviderRegistration`.
 
+The `ExternalPrincipalConfiguration` defines the following configuration options:
+     
+| Name                         | Property                      | Description                              |
+|------------------------------|-------------------------------|------------------------------------------|
+| External Identity Protection | `protectExternalId`           | Enables protection of the system maintained `rep:externalId` properties |
+| | | |
+
 <!-- references -->
 [SyncContext]: /oak/docs/apidocs/org/apache/jackrabbit/oak/spi/security/authentication/external/SyncContext.html
 [DefaultSyncContext]: /oak/docs/apidocs/org/apache/jackrabbit/oak/spi/security/authentication/external/basic/DefaultSyncContext.html
@@ -171,4 +197,5 @@ See section [Introduction to Oak Securit
 [ExternalPrincipalConfiguration]: /oak/docs/apidocs/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/principal/ExternalPrincipalConfiguration.html
 [DynamicSyncContext]: /oak/docs/apidocs/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/principal/DynamicSyncContext.html
 [OAK-4101]: https://issues.apache.org/jira/browse/OAK-4101
-[OAK-2687]: https://issues.apache.org/jira/browse/OAK-2687
\ No newline at end of file
+[OAK-2687]: https://issues.apache.org/jira/browse/OAK-2687
+[OAK-4301]: https://issues.apache.org/jira/browse/OAK-4301
\ No newline at end of file