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 dj...@apache.org on 2016/09/28 14:22:53 UTC

svn commit: r1762669 - in /jackrabbit/oak/branches/1.4: ./ 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/extern...

Author: dj
Date: Wed Sep 28 14:22:53 2016
New Revision: 1762669

URL: http://svn.apache.org/viewvc?rev=1762669&view=rev
Log:
OAK-4860 : Backport OAK-4301 and OAK-4845
- backport of  OAK-4301 (depends on OAK-4048 and OAK-4257)

Added:
    jackrabbit/oak/branches/1.4/oak-auth-external/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/principal/ValidatorNoProtectionTest.java
      - copied unchanged from r1755191, jackrabbit/oak/trunk/oak-auth-external/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/principal/ValidatorNoProtectionTest.java
    jackrabbit/oak/branches/1.4/oak-auth-external/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/principal/ValidatorNotDynamicTest.java
      - copied unchanged from r1755191, jackrabbit/oak/trunk/oak-auth-external/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/principal/ValidatorNotDynamicTest.java
Modified:
    jackrabbit/oak/branches/1.4/   (props changed)
    jackrabbit/oak/branches/1.4/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/DefaultSyncHandler.java
    jackrabbit/oak/branches/1.4/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/ExternalIdentityConstants.java
    jackrabbit/oak/branches/1.4/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/jmx/SynchronizationMBean.java
    jackrabbit/oak/branches/1.4/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/principal/ExternalIdentityRepositoryInitializer.java
    jackrabbit/oak/branches/1.4/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/principal/ExternalIdentityValidatorProvider.java
    jackrabbit/oak/branches/1.4/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/principal/ExternalPrincipalConfiguration.java
    jackrabbit/oak/branches/1.4/oak-auth-external/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/basic/RepExternalIdTest.java
    jackrabbit/oak/branches/1.4/oak-auth-external/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/DefaultSyncHandlerTest.java
    jackrabbit/oak/branches/1.4/oak-auth-external/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/principal/ExternalIdentityRepositoryInitializerTest.java
    jackrabbit/oak/branches/1.4/oak-auth-external/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/principal/ExternalIdentityValidatorTest.java
    jackrabbit/oak/branches/1.4/oak-auth-external/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/principal/ExternalPrincipalConfigurationTest.java
    jackrabbit/oak/branches/1.4/oak-core/src/main/java/org/apache/jackrabbit/oak/security/authorization/AuthorizationContext.java
    jackrabbit/oak/branches/1.4/oak-core/src/main/java/org/apache/jackrabbit/oak/security/authorization/composite/CompositeAuthorizationConfiguration.java
    jackrabbit/oak/branches/1.4/oak-core/src/test/java/org/apache/jackrabbit/oak/security/authorization/composite/CompositeAuthorizationConfigurationTest.java
    jackrabbit/oak/branches/1.4/oak-doc/src/site/markdown/security/authentication/external/defaultusersync.md

Propchange: jackrabbit/oak/branches/1.4/
------------------------------------------------------------------------------
--- svn:mergeinfo (original)
+++ svn:mergeinfo Wed Sep 28 14:22:53 2016
@@ -1,3 +1,3 @@
 /jackrabbit/oak/branches/1.0:1665962
-/jackrabbit/oak/trunk:1733615,1733875,1733913,1733929,1734230,1734254,1734279,1734941,1735052,1735081,1735141,1735267,1735405,1735484,1735549,1735564,1735588,1735622,1735638,1735919,1735983,1736176,1737309-1737310,1737334,1737349,1737998,1738004,1738136,1738138,1738207,1738234,1738252,1738775,1738795,1738833,1738950,1738957,1738963,1739712,1739760,1739867,1739894,1739959-1739960,1740114,1740116,1740250,1740333,1740360,1740625-1740626,1740774,1740837,1740971,1741016,1741032,1741339,1741343,1742077,1742117,1742363,1742520,1742888,1742916,1743097,1743172,1743343,1743674,1744265,1744292,1744589,1744670,1744672,1744959,1745038,1745127,1745197,1745336,1745368,1746086,1746117,1746342,1746345,1746408,1746696,1746981,1747198,1747200,1747341-1747342,1747380,1747387,1747406,1747492,1747512,1747654,1748505,1748553,1748722,1748870,1749275,1749350,1749424,1749443,1749464,1749475,1749645,1749662,1749815,1749872,1749875,1749899,1750052,1750076-1750077,1750287,1750457,1750462,1750465,1750495,1750626
 ,1750809,1750886,1751410,1751445-1751446,1751478,1751755,1751871,1752198,1752202,1752259,1752273-1752274,1752283,1752292,1752438,1752447-1752448,1752508,1752596,1752616,1752659,1752672,1753262,1753331-1753332,1753335-1753336,1753355,1753444,1754117,1754239,1755157,1756520,1756580,1757119,1757166,1759433,1760340,1760373,1760387,1760661-1760662,1761412,1761444,1761571,1761762,1761787,1761876,1762635
+/jackrabbit/oak/trunk:1733615,1733875,1733913,1733929,1734230,1734254,1734279,1734941,1735052,1735081,1735141,1735267,1735405,1735484,1735549,1735564,1735588,1735622,1735638,1735919,1735983,1736176,1737309-1737310,1737334,1737349,1737998,1738004,1738136,1738138,1738207,1738234,1738252,1738775,1738795,1738833,1738950,1738957,1738963,1739712,1739760,1739867,1739894,1739959-1739960,1740114,1740116,1740250,1740333,1740349,1740360,1740625-1740626,1740774,1740837,1740879,1740971,1741016,1741032,1741339,1741343,1742077,1742117,1742125,1742363,1742520,1742888,1742916,1743097,1743172,1743343,1743674,1744265,1744292,1744589,1744670,1744672,1744959,1745038,1745127,1745197,1745336,1745368,1746086,1746117,1746342,1746345,1746408,1746696,1746981,1747198,1747200,1747341-1747342,1747380,1747387,1747406,1747492,1747512,1747654,1748505,1748553,1748722,1748870,1749275,1749350,1749424,1749443,1749464,1749475,1749645,1749662,1749815,1749872,1749875,1749899,1750052,1750076-1750077,1750287,1750457,1750462
 ,1750465,1750495,1750626,1750809,1750886,1751410,1751445-1751446,1751478,1751755,1751871,1752198,1752202,1752259,1752273-1752274,1752283,1752292,1752438,1752447-1752448,1752508,1752596,1752616,1752659,1752672,1753262,1753331-1753332,1753335-1753336,1753355,1753444,1754117,1754239,1755157,1755191,1756520,1756580,1757119,1757166,1759433,1760340,1760373,1760387,1760661-1760662,1761412,1761444,1761571,1761762,1761787,1761876,1762635
 /jackrabbit/trunk:1345480

Modified: jackrabbit/oak/branches/1.4/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/DefaultSyncHandler.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/branches/1.4/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/DefaultSyncHandler.java?rev=1762669&r1=1762668&r2=1762669&view=diff
==============================================================================
--- jackrabbit/oak/branches/1.4/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/DefaultSyncHandler.java (original)
+++ jackrabbit/oak/branches/1.4/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/DefaultSyncHandler.java Wed Sep 28 14:22:53 2016
@@ -148,7 +148,7 @@ public class DefaultSyncHandler implemen
                 while (iter.hasNext()) {
                     try {
                         SyncedIdentity id = DefaultSyncContext.createSyncedIdentity(iter.next());
-                        if (id != null) {
+                        if (id != null && id.getExternalIdRef() != null) {
                             return id;
                         }
                     } catch (RepositoryException e) {

Modified: jackrabbit/oak/branches/1.4/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/branches/1.4/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/ExternalIdentityConstants.java?rev=1762669&r1=1762668&r2=1762669&view=diff
==============================================================================
--- jackrabbit/oak/branches/1.4/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/ExternalIdentityConstants.java (original)
+++ jackrabbit/oak/branches/1.4/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/ExternalIdentityConstants.java Wed Sep 28 14:22:53 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/branches/1.4/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/jmx/SynchronizationMBean.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/branches/1.4/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/jmx/SynchronizationMBean.java?rev=1762669&r1=1762668&r2=1762669&view=diff
==============================================================================
--- jackrabbit/oak/branches/1.4/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/jmx/SynchronizationMBean.java (original)
+++ jackrabbit/oak/branches/1.4/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/jmx/SynchronizationMBean.java Wed Sep 28 14:22:53 2016
@@ -110,7 +110,7 @@ public interface SynchronizationMBean {
 
     /**
      * Purges all orphaned users. this is similar to invoke {@link #syncUsers(String[], boolean)} with the list of
-     * orphaned users. Note tha this can eb an expensive operation since all potential users need to be examined.
+     * orphaned users. Note tha this can be an expensive operation since all potential users need to be examined.
      *
      * @return result messages.
      */

Modified: jackrabbit/oak/branches/1.4/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/branches/1.4/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/principal/ExternalIdentityRepositoryInitializer.java?rev=1762669&r1=1762668&r2=1762669&view=diff
==============================================================================
--- jackrabbit/oak/branches/1.4/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/principal/ExternalIdentityRepositoryInitializer.java (original)
+++ jackrabbit/oak/branches/1.4/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/principal/ExternalIdentityRepositoryInitializer.java Wed Sep 28 14:22:53 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/branches/1.4/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/branches/1.4/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/principal/ExternalIdentityValidatorProvider.java?rev=1762669&r1=1762668&r2=1762669&view=diff
==============================================================================
--- jackrabbit/oak/branches/1.4/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/principal/ExternalIdentityValidatorProvider.java (original)
+++ jackrabbit/oak/branches/1.4/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/principal/ExternalIdentityValidatorProvider.java Wed Sep 28 14:22:53 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/branches/1.4/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/branches/1.4/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/principal/ExternalPrincipalConfiguration.java?rev=1762669&r1=1762668&r2=1762669&view=diff
==============================================================================
--- jackrabbit/oak/branches/1.4/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/principal/ExternalPrincipalConfiguration.java (original)
+++ jackrabbit/oak/branches/1.4/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/principal/ExternalPrincipalConfiguration.java Wed Sep 28 14:22:53 2016
@@ -37,6 +37,8 @@ import com.google.common.collect.Iterato
 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;
@@ -51,6 +53,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.PrincipalConfiguration;
@@ -80,6 +83,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);
@@ -124,13 +133,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
@@ -170,6 +179,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);
+    }
+
     /**
      * Implementation of the {@code PrincipalProvider} interface that never
      * returns any principals.

Modified: jackrabbit/oak/branches/1.4/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/branches/1.4/oak-auth-external/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/basic/RepExternalIdTest.java?rev=1762669&r1=1762668&r2=1762669&view=diff
==============================================================================
--- jackrabbit/oak/branches/1.4/oak-auth-external/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/basic/RepExternalIdTest.java (original)
+++ jackrabbit/oak/branches/1.4/oak-auth-external/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/basic/RepExternalIdTest.java Wed Sep 28 14:22:53 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/branches/1.4/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/branches/1.4/oak-auth-external/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/DefaultSyncHandlerTest.java?rev=1762669&r1=1762668&r2=1762669&view=diff
==============================================================================
--- jackrabbit/oak/branches/1.4/oak-auth-external/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/DefaultSyncHandlerTest.java (original)
+++ jackrabbit/oak/branches/1.4/oak-auth-external/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/DefaultSyncHandlerTest.java Wed Sep 28 14:22:53 2016
@@ -26,8 +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.namepath.NamePathMapper;
-import org.apache.jackrabbit.oak.plugins.value.ValueFactoryImpl;
+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.ExternalIdentityProvider;
 import org.apache.jackrabbit.oak.spi.security.authentication.external.ExternalIdentityRef;
@@ -50,6 +49,7 @@ import static org.junit.Assert.assertNot
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertSame;
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
 
 /**
  * DefaultSyncHandlerTest
@@ -87,6 +87,8 @@ public class DefaultSyncHandlerTest exte
     private void sync(@Nonnull String id, boolean isGroup) throws Exception {
         SyncContext ctx = syncHandler.createContext(idp, userManager, getValueFactory());
         ExternalIdentity exIdentity = (isGroup) ? idp.getGroup(id) : idp.getUser(id);
+        assertNotNull(exIdentity);
+
         SyncResult res = ctx.sync(exIdentity);
         assertSame(SyncResult.Status.ADD, res.getStatus());
         root.commit();
@@ -99,7 +101,7 @@ public class DefaultSyncHandlerTest exte
 
     @Test
     public void testCreateContext() throws Exception {
-        SyncContext ctx = syncHandler.createContext(idp, userManager, new ValueFactoryImpl(root, NamePathMapper.DEFAULT));
+        SyncContext ctx = syncHandler.createContext(idp, userManager, getValueFactory());
         assertTrue(ctx instanceof DefaultSyncContext);
     }
 
@@ -123,7 +125,9 @@ public class DefaultSyncHandlerTest exte
 
         SyncedIdentity id = syncHandler.findIdentity(userManager, USER_ID);
         assertNotNull("known authorizable should exist", id);
-        assertEquals("external user should have correct external ref.idp", idp.getName(), id.getExternalIdRef().getProviderName());
+        ExternalIdentityRef ref = id.getExternalIdRef();
+        assertNotNull(ref);
+        assertEquals("external user should have correct external ref.idp", idp.getName(), ref.getProviderName());
         assertEquals("external user should have correct external ref.id", USER_ID, id.getExternalIdRef().getId());
     }
 
@@ -144,12 +148,15 @@ 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);
         assertNull(si.getExternalIdRef());
     }
 
@@ -219,9 +226,9 @@ public class DefaultSyncHandlerTest exte
     @Test
     public void testListIdentitiesBeforeSync() throws Exception {
         Iterator<SyncedIdentity> identities = syncHandler.listIdentities(userManager);
-        while (identities.hasNext()) {
+        if (identities.hasNext()) {
             SyncedIdentity si = identities.next();
-            assertNull(si.getExternalIdRef());
+            fail("Sync handler returned unexpected identity: " + si);
         }
     }
 
@@ -242,7 +249,7 @@ public class DefaultSyncHandlerTest exte
                 expected.remove(si.getId());
                 assertNotNull(si.getExternalIdRef());
             } else {
-                assertNull(si.getExternalIdRef());
+                fail("Sync handler returned unexpected identity: " + si);
             }
         }
         assertTrue(expected.isEmpty());

Modified: jackrabbit/oak/branches/1.4/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/branches/1.4/oak-auth-external/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/principal/ExternalIdentityRepositoryInitializerTest.java?rev=1762669&r1=1762668&r2=1762669&view=diff
==============================================================================
--- jackrabbit/oak/branches/1.4/oak-auth-external/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/principal/ExternalIdentityRepositoryInitializerTest.java (original)
+++ jackrabbit/oak/branches/1.4/oak-auth-external/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/principal/ExternalIdentityRepositoryInitializerTest.java Wed Sep 28 14:22:53 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/branches/1.4/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/branches/1.4/oak-auth-external/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/principal/ExternalIdentityValidatorTest.java?rev=1762669&r1=1762668&r2=1762669&view=diff
==============================================================================
--- jackrabbit/oak/branches/1.4/oak-auth-external/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/principal/ExternalIdentityValidatorTest.java (original)
+++ jackrabbit/oak/branches/1.4/oak-auth-external/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/principal/ExternalIdentityValidatorTest.java Wed Sep 28 14:22:53 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/branches/1.4/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/branches/1.4/oak-auth-external/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/principal/ExternalPrincipalConfigurationTest.java?rev=1762669&r1=1762668&r2=1762669&view=diff
==============================================================================
--- jackrabbit/oak/branches/1.4/oak-auth-external/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/principal/ExternalPrincipalConfigurationTest.java (original)
+++ jackrabbit/oak/branches/1.4/oak-auth-external/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/principal/ExternalPrincipalConfigurationTest.java Wed Sep 28 14:22:53 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();
 

Modified: jackrabbit/oak/branches/1.4/oak-core/src/main/java/org/apache/jackrabbit/oak/security/authorization/AuthorizationContext.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/branches/1.4/oak-core/src/main/java/org/apache/jackrabbit/oak/security/authorization/AuthorizationContext.java?rev=1762669&r1=1762668&r2=1762669&view=diff
==============================================================================
--- jackrabbit/oak/branches/1.4/oak-core/src/main/java/org/apache/jackrabbit/oak/security/authorization/AuthorizationContext.java (original)
+++ jackrabbit/oak/branches/1.4/oak-core/src/main/java/org/apache/jackrabbit/oak/security/authorization/AuthorizationContext.java Wed Sep 28 14:22:53 2016
@@ -61,7 +61,7 @@ final class AuthorizationContext impleme
     @Override
     public boolean definesTree(@Nonnull Tree tree) {
         String ntName = TreeUtil.getPrimaryTypeName(tree);
-        return isNtName(ntName);
+        return ntName != null && isNtName(ntName);
     }
 
     @Override

Modified: jackrabbit/oak/branches/1.4/oak-core/src/main/java/org/apache/jackrabbit/oak/security/authorization/composite/CompositeAuthorizationConfiguration.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/branches/1.4/oak-core/src/main/java/org/apache/jackrabbit/oak/security/authorization/composite/CompositeAuthorizationConfiguration.java?rev=1762669&r1=1762668&r2=1762669&view=diff
==============================================================================
--- jackrabbit/oak/branches/1.4/oak-core/src/main/java/org/apache/jackrabbit/oak/security/authorization/composite/CompositeAuthorizationConfiguration.java (original)
+++ jackrabbit/oak/branches/1.4/oak-core/src/main/java/org/apache/jackrabbit/oak/security/authorization/composite/CompositeAuthorizationConfiguration.java Wed Sep 28 14:22:53 2016
@@ -115,8 +115,9 @@ public class CompositeAuthorizationConfi
             default:
                 List<RestrictionProvider> rps = new ArrayList<RestrictionProvider>(configurations.size());
                 for (AuthorizationConfiguration c : configurations) {
-                    if (RestrictionProvider.EMPTY != c) {
-                        rps.add(c.getRestrictionProvider());
+                    RestrictionProvider rp = c.getRestrictionProvider();
+                    if (RestrictionProvider.EMPTY != rp) {
+                        rps.add(rp);
                     }
                 }
                 return CompositeRestrictionProvider.newInstance(rps);

Modified: jackrabbit/oak/branches/1.4/oak-core/src/test/java/org/apache/jackrabbit/oak/security/authorization/composite/CompositeAuthorizationConfigurationTest.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/branches/1.4/oak-core/src/test/java/org/apache/jackrabbit/oak/security/authorization/composite/CompositeAuthorizationConfigurationTest.java?rev=1762669&r1=1762668&r2=1762669&view=diff
==============================================================================
--- jackrabbit/oak/branches/1.4/oak-core/src/test/java/org/apache/jackrabbit/oak/security/authorization/composite/CompositeAuthorizationConfigurationTest.java (original)
+++ jackrabbit/oak/branches/1.4/oak-core/src/test/java/org/apache/jackrabbit/oak/security/authorization/composite/CompositeAuthorizationConfigurationTest.java Wed Sep 28 14:22:53 2016
@@ -19,6 +19,7 @@ package org.apache.jackrabbit.oak.securi
 import java.security.Principal;
 import java.util.Collections;
 
+import javax.annotation.Nonnull;
 import javax.jcr.RepositoryException;
 import javax.jcr.security.AccessControlManager;
 
@@ -34,6 +35,7 @@ import org.apache.jackrabbit.oak.spi.sec
 import org.junit.Test;
 
 import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotSame;
 import static org.junit.Assert.assertSame;
 import static org.junit.Assert.assertTrue;
 
@@ -79,7 +81,7 @@ public class CompositeAuthorizationConfi
     }
 
     @Test
-    public void testSlingRestrictionProvider() {
+    public void testSingleRestrictionProvider() {
         CompositeAuthorizationConfiguration cc = getCompositeConfiguration(new AuthorizationConfigurationImpl(getSecurityProvider()));
 
         RestrictionProvider rp = cc.getRestrictionProvider();
@@ -136,4 +138,37 @@ public class CompositeAuthorizationConfi
         RestrictionProvider rp = cc.getRestrictionProvider();
         assertTrue(rp instanceof CompositeRestrictionProvider);
     }
+
+    @Test
+    public void testMultipleWithEmptyRestrictionProvider() {
+        CompositeAuthorizationConfiguration cc = getCompositeConfiguration(
+                new AuthorizationConfigurationImpl(getSecurityProvider()),
+                new OpenAuthorizationConfiguration() {
+                    @Nonnull
+                    @Override
+                    public RestrictionProvider getRestrictionProvider() {
+                        return RestrictionProvider.EMPTY;
+                    }
+                });
+
+        RestrictionProvider rp = cc.getRestrictionProvider();
+        assertFalse(rp instanceof CompositeRestrictionProvider);
+        assertNotSame(RestrictionProvider.EMPTY, rp);
+    }
+
+    @Test
+    public void testOnlyEmptyRestrictionProvider() {
+        AuthorizationConfiguration ac = new OpenAuthorizationConfiguration() {
+            @Nonnull
+            @Override
+            public RestrictionProvider getRestrictionProvider() {
+                return RestrictionProvider.EMPTY;
+            }
+        };
+        CompositeAuthorizationConfiguration cc = getCompositeConfiguration(ac, ac);
+
+        RestrictionProvider rp = cc.getRestrictionProvider();
+        assertFalse(rp instanceof CompositeRestrictionProvider);
+        assertSame(RestrictionProvider.EMPTY, rp);
+    }
 }
\ No newline at end of file

Modified: jackrabbit/oak/branches/1.4/oak-doc/src/site/markdown/security/authentication/external/defaultusersync.md
URL: http://svn.apache.org/viewvc/jackrabbit/oak/branches/1.4/oak-doc/src/site/markdown/security/authentication/external/defaultusersync.md?rev=1762669&r1=1762668&r2=1762669&view=diff
==============================================================================
--- jackrabbit/oak/branches/1.4/oak-doc/src/site/markdown/security/authentication/external/defaultusersync.md (original)
+++ jackrabbit/oak/branches/1.4/oak-doc/src/site/markdown/security/authentication/external/defaultusersync.md Wed Sep 28 14:22:53 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