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 ro...@apache.org on 2017/08/29 14:48:56 UTC

svn commit: r1806602 - in /jackrabbit/oak/trunk/oak-jcr/src: main/java/org/apache/jackrabbit/oak/jcr/repository/ main/java/org/apache/jackrabbit/oak/jcr/session/ test/java/org/apache/jackrabbit/oak/jcr/session/

Author: rombert
Date: Tue Aug 29 14:48:56 2017
New Revision: 1806602

URL: http://svn.apache.org/viewvc?rev=1806602&view=rev
Log:
OAK-6563 - Session.hasCapability(...) should reflect read-only status of mounts

Add optional support for checking the Mount status in SessionImpl.hasCapability.

Added:
    jackrabbit/oak/trunk/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/session/SessionImplCapabilityWithMountInfoProviderTest.java
Modified:
    jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/repository/RepositoryImpl.java
    jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/session/SessionContext.java
    jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/session/SessionImpl.java

Modified: jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/repository/RepositoryImpl.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/repository/RepositoryImpl.java?rev=1806602&r1=1806601&r2=1806602&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/repository/RepositoryImpl.java (original)
+++ jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/repository/RepositoryImpl.java Tue Aug 29 14:48:56 2017
@@ -22,6 +22,7 @@ import static java.util.Collections.sing
 import static org.apache.jackrabbit.oak.spi.whiteboard.WhiteboardUtils.registerMBean;
 
 import java.io.Closeable;
+import java.util.List;
 import java.util.Map;
 import java.util.concurrent.Callable;
 import java.util.concurrent.ScheduledExecutorService;
@@ -61,8 +62,10 @@ import org.apache.jackrabbit.oak.jcr.ses
 import org.apache.jackrabbit.oak.plugins.observation.CommitRateLimiter;
 import org.apache.jackrabbit.oak.spi.gc.DelegatingGCMonitor;
 import org.apache.jackrabbit.oak.spi.gc.GCMonitor;
+import org.apache.jackrabbit.oak.spi.mount.MountInfoProvider;
 import org.apache.jackrabbit.oak.spi.security.SecurityProvider;
 import org.apache.jackrabbit.oak.spi.whiteboard.Registration;
+import org.apache.jackrabbit.oak.spi.whiteboard.Tracker;
 import org.apache.jackrabbit.oak.spi.whiteboard.Whiteboard;
 import org.apache.jackrabbit.oak.stats.Clock;
 import org.apache.jackrabbit.oak.stats.StatisticManager;
@@ -105,6 +108,7 @@ public class RepositoryImpl implements J
     private final Clock.Fast clock;
     private final DelegatingGCMonitor gcMonitor = new DelegatingGCMonitor();
     private final Registration gcMonitorRegistration;
+    private final MountInfoProvider mountInfoProvider;
 
     /**
      * {@link ThreadLocal} counter that keeps track of the save operations
@@ -152,6 +156,17 @@ public class RepositoryImpl implements J
         this.clock = new Clock.Fast(scheduledExecutor);
         this.gcMonitorRegistration = whiteboard.register(GCMonitor.class, gcMonitor, emptyMap());
         this.fastQueryResultSize = fastQueryResultSize;
+
+        Tracker<MountInfoProvider> tracker = whiteboard.track(MountInfoProvider.class);
+        List<MountInfoProvider> services = tracker.getServices();
+        tracker.stop();
+
+        if ( services.isEmpty() )
+            this.mountInfoProvider = null;
+        else if ( services.size() == 1 )
+            this.mountInfoProvider = services.get(0);
+        else
+            throw new IllegalArgumentException("Found " + services.size() + " MountInfoProvider references, expected at most 1.");
     }
 
     //---------------------------------------------------------< Repository >---
@@ -343,7 +358,7 @@ public class RepositoryImpl implements J
             Map<String, Object> attributes, SessionDelegate delegate, int observationQueueLength,
             CommitRateLimiter commitRateLimiter) {
         return new SessionContext(this, statisticManager, securityProvider, whiteboard, attributes,
-                delegate, observationQueueLength, commitRateLimiter, fastQueryResultSize);
+                delegate, observationQueueLength, commitRateLimiter, mountInfoProvider, fastQueryResultSize);
     }
 
     /**

Modified: jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/session/SessionContext.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/session/SessionContext.java?rev=1806602&r1=1806601&r2=1806602&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/session/SessionContext.java (original)
+++ jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/session/SessionContext.java Tue Aug 29 14:48:56 2017
@@ -28,6 +28,7 @@ import java.util.Set;
 
 import javax.annotation.CheckForNull;
 import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
 import javax.jcr.PathNotFoundException;
 import javax.jcr.Repository;
 import javax.jcr.RepositoryException;
@@ -56,6 +57,7 @@ import org.apache.jackrabbit.oak.namepat
 import org.apache.jackrabbit.oak.plugins.nodetype.ReadOnlyNodeTypeManager;
 import org.apache.jackrabbit.oak.plugins.observation.CommitRateLimiter;
 import org.apache.jackrabbit.oak.plugins.value.jcr.ValueFactoryImpl;
+import org.apache.jackrabbit.oak.spi.mount.MountInfoProvider;
 import org.apache.jackrabbit.oak.spi.security.SecurityConfiguration;
 import org.apache.jackrabbit.oak.spi.security.SecurityProvider;
 import org.apache.jackrabbit.oak.spi.security.authorization.AuthorizationConfiguration;
@@ -89,6 +91,7 @@ public class SessionContext implements N
     private final SessionDelegate delegate;
     private final int observationQueueLength;
     private final CommitRateLimiter commitRateLimiter;
+    private MountInfoProvider mountInfoProvider;
 
     private final NamePathMapper namePathMapper;
     private final ValueFactory valueFactory;
@@ -118,7 +121,7 @@ public class SessionContext implements N
              int observationQueueLength, CommitRateLimiter commitRateLimiter) {
         
         this(repository, statisticManager, securityProvider, whiteboard, attributes, delegate,
-            observationQueueLength, commitRateLimiter, false);
+            observationQueueLength, commitRateLimiter, null, false);
     }
 
     public SessionContext(
@@ -126,7 +129,7 @@ public class SessionContext implements N
             @Nonnull SecurityProvider securityProvider, @Nonnull Whiteboard whiteboard,
             @Nonnull Map<String, Object> attributes, @Nonnull final SessionDelegate delegate,
             int observationQueueLength, CommitRateLimiter commitRateLimiter,
-            boolean fastQueryResultSize) {
+            MountInfoProvider mountInfoProvider, boolean fastQueryResultSize) {
         this.repository = checkNotNull(repository);
         this.statisticManager = statisticManager;
         this.securityProvider = checkNotNull(securityProvider);
@@ -135,6 +138,7 @@ public class SessionContext implements N
         this.delegate = checkNotNull(delegate);
         this.observationQueueLength = observationQueueLength;
         this.commitRateLimiter = commitRateLimiter;
+        this.mountInfoProvider = mountInfoProvider;
         SessionStats sessionStats = delegate.getSessionStats();
         sessionStats.setAttributes(attributes);
 
@@ -317,6 +321,11 @@ public class SessionContext implements N
         return fastQueryResultSize;
     }
 
+    @Nullable
+    public MountInfoProvider getMountInfoProvider() {
+        return mountInfoProvider;
+    }
+
     //-----------------------------------------------------< NamePathMapper >---
 
     @Override

Modified: jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/session/SessionImpl.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/session/SessionImpl.java?rev=1806602&r1=1806601&r2=1806602&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/session/SessionImpl.java (original)
+++ jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/session/SessionImpl.java Tue Aug 29 14:48:56 2017
@@ -66,6 +66,7 @@ import org.apache.jackrabbit.oak.jcr.del
 import org.apache.jackrabbit.oak.jcr.security.AccessManager;
 import org.apache.jackrabbit.oak.jcr.session.operation.SessionOperation;
 import org.apache.jackrabbit.oak.jcr.xml.ImportHandler;
+import org.apache.jackrabbit.oak.spi.mount.MountInfoProvider;
 import org.apache.jackrabbit.oak.spi.security.authentication.ImpersonationCredentials;
 import org.apache.jackrabbit.oak.spi.security.authorization.permission.Permissions;
 import org.apache.jackrabbit.oak.stats.CounterStats;
@@ -669,7 +670,7 @@ public class SessionImpl implements Jack
                         // add-node needs to be checked on the (path of) the
                         // new node that has/will be added
                         String path = PathUtils.concat(tree.getPath(), sessionContext.getOakName(arguments[0].toString()));
-                        return accessMgr.hasPermissions(path, Session.ACTION_ADD_NODE);
+                        return accessMgr.hasPermissions(path, Session.ACTION_ADD_NODE)  && !isMountedReadOnly(path);
                     }
                 } else if ("setPrimaryType".equals(methodName) || "addMixin".equals(methodName) || "removeMixin".equals(methodName)) {
                     permission = Permissions.NODE_TYPE_MANAGEMENT;
@@ -685,7 +686,7 @@ public class SessionImpl implements Jack
                 } else if ("remove".equals(methodName)) {
                     permission = Permissions.REMOVE_NODE;
                 }
-                return accessMgr.hasPermissions(tree, null, permission);
+                return accessMgr.hasPermissions(tree, null, permission) && !isMountedReadOnly(tree.getPath());
             } else {
                 if ("setValue".equals(methodName)) {
                     permission = Permissions.MODIFY_PROPERTY;
@@ -694,9 +695,11 @@ public class SessionImpl implements Jack
                 }
                 NodeDelegate parentDelegate = dlg.getParent();
                 if (parentDelegate != null) {
-                    return accessMgr.hasPermissions(parentDelegate.getTree(), ((PropertyDelegate) dlg).getPropertyState(), permission);
+                    return accessMgr.hasPermissions(parentDelegate.getTree(), ((PropertyDelegate) dlg).getPropertyState(), permission) 
+                            && !isMountedReadOnly(parentDelegate.getPath());
                 } else {
-                    return accessMgr.hasPermissions(dlg.getPath(), (permission == Permissions.MODIFY_PROPERTY) ? Session.ACTION_SET_PROPERTY : Session.ACTION_REMOVE);
+                    return accessMgr.hasPermissions(dlg.getPath(), (permission == Permissions.MODIFY_PROPERTY) ? Session.ACTION_SET_PROPERTY : Session.ACTION_REMOVE)
+                            && !isMountedReadOnly(dlg.getPath());
                 }
             }
         }
@@ -704,6 +707,11 @@ public class SessionImpl implements Jack
         return true;
     }
 
+    private boolean isMountedReadOnly(String path) {
+        MountInfoProvider mip = sessionContext.getMountInfoProvider();
+        return mip != null && mip.getMountByPath(path).isReadOnly();
+    }
+
     @Override
     @Nonnull
     public AccessControlManager getAccessControlManager() throws RepositoryException {

Added: jackrabbit/oak/trunk/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/session/SessionImplCapabilityWithMountInfoProviderTest.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/session/SessionImplCapabilityWithMountInfoProviderTest.java?rev=1806602&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/session/SessionImplCapabilityWithMountInfoProviderTest.java (added)
+++ jackrabbit/oak/trunk/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/session/SessionImplCapabilityWithMountInfoProviderTest.java Tue Aug 29 14:48:56 2017
@@ -0,0 +1,132 @@
+/*
+ * 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.jcr.session;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import java.util.Collections;
+
+import javax.jcr.Repository;
+import javax.jcr.Session;
+import javax.jcr.SimpleCredentials;
+
+import org.apache.jackrabbit.JcrConstants;
+import org.apache.jackrabbit.oak.composite.CompositeNodeStore;
+import org.apache.jackrabbit.oak.jcr.Jcr;
+import org.apache.jackrabbit.oak.plugins.memory.MemoryNodeStore;
+import org.apache.jackrabbit.oak.spi.commit.CommitInfo;
+import org.apache.jackrabbit.oak.spi.commit.EmptyHook;
+import org.apache.jackrabbit.oak.spi.mount.MountInfoProvider;
+import org.apache.jackrabbit.oak.spi.mount.Mounts;
+import org.apache.jackrabbit.oak.spi.state.NodeBuilder;
+import org.apache.jackrabbit.oak.spi.whiteboard.DefaultWhiteboard;
+import org.apache.jackrabbit.oak.spi.whiteboard.Whiteboard;
+import org.junit.Before;
+import org.junit.Test;
+
+public class SessionImplCapabilityWithMountInfoProviderTest {
+    
+    private Session adminSession;
+
+    @Before
+    public void prepare() throws Exception {
+        MountInfoProvider mip = Mounts.newBuilder().readOnlyMount("ro", "/private").build();
+        
+        MemoryNodeStore roStore = new MemoryNodeStore();
+        {
+            NodeBuilder builder = roStore.getRoot().builder();
+            builder
+                .child("private").setProperty(JcrConstants.JCR_PRIMARYTYPE, JcrConstants.NT_UNSTRUCTURED)
+                    .setProperty("prop", "value")
+                .child("foo").setProperty(JcrConstants.JCR_PRIMARYTYPE, JcrConstants.NT_UNSTRUCTURED);
+            roStore.merge(builder, EmptyHook.INSTANCE, CommitInfo.EMPTY);
+        }
+        
+        MemoryNodeStore globalStore = new MemoryNodeStore();
+        {
+            NodeBuilder builder = globalStore.getRoot().builder();
+            builder
+                .child("foo").setProperty(JcrConstants.JCR_PRIMARYTYPE, JcrConstants.NT_UNSTRUCTURED)
+                    .setProperty("prop", "value")
+                .child("bar").setProperty(JcrConstants.JCR_PRIMARYTYPE, JcrConstants.NT_UNSTRUCTURED);
+            globalStore.merge(builder, EmptyHook.INSTANCE, CommitInfo.EMPTY);
+        }
+        
+        CompositeNodeStore store = new CompositeNodeStore.Builder(mip, globalStore)
+            .addMount("ro", roStore)
+            .build();
+        
+        Whiteboard whiteboard = new  DefaultWhiteboard();
+        whiteboard.register(MountInfoProvider.class, mip, Collections.emptyMap());
+        
+        Jcr jcr = new Jcr(store).with(whiteboard);
+        jcr.createContentRepository();
+        Repository repository = jcr.createRepository();
+        
+        adminSession = repository.login(new SimpleCredentials("admin", "admin".toCharArray()));        
+    }
+
+    @Test
+    public void addNode() throws Exception {
+        
+        // unable to add nodes in the read-only mount
+        assertFalse("Must not be able to add a child not under the private mount root",
+                adminSession.hasCapability("addNode", adminSession.getNode("/private"), new String[] {"foo"}));
+        assertFalse("Must not be able to add a child not under the private mount", 
+                adminSession.hasCapability("addNode", adminSession.getNode("/private/foo"), new String[] {"bar"}));
+        // able to add nodes outside the read-only mount
+        assertTrue("Must be able to add a child node under the root",
+                adminSession.hasCapability("addNode", adminSession.getNode("/"), new String[] {"not-private"}));
+        // unable to add node at the root of the read-only mount ( even though it already exists )
+        assertFalse("Must not be able to add a child node in place of the private mount",
+                adminSession.hasCapability("addNode", adminSession.getNode("/"), new String[] {"private"}));
+    }
+    
+    @Test
+    public void orderBefore() throws Exception {
+        // able to order the root of the mount since the operation is performed on the parent
+        assertTrue(adminSession.hasCapability("orderBefore", adminSession.getNode("/private"), null));
+        assertFalse(adminSession.hasCapability("orderBefore", adminSession.getNode("/private/foo"), null));
+    }
+    
+    @Test
+    public void simpleNodeOperations() throws Exception {
+        for ( String operation : new String[] { "setPrimaryType", "addMixin", "removeMixin" , "setProperty", "remove"} )  {
+            for ( String privateMountNode : new String[] { "/private", "/private/foo" } ) {
+                assertFalse("Unexpected return value for hasCapability(" + operation+ ") on node '" + privateMountNode +"' from the private mount",
+                        adminSession.hasCapability(operation, adminSession.getNode(privateMountNode), null));
+            }
+            String globalMountNode = "/foo";
+            assertTrue("Unexpected return value for hasCapability(" + operation+ ") on node '" + globalMountNode +"' from the global mount",
+                    adminSession.hasCapability(operation, adminSession.getNode(globalMountNode), null));
+        }
+    }    
+
+    @Test
+    public void itemOperations() throws Exception {
+        for ( String operation : new String[] { "setValue", "remove"} )  {
+            String privateMountProp = "/private/prop";
+            String globalMountProp = "/foo/prop";
+            
+            assertFalse("Unexpected return value for hasCapability(" + operation+ ") on item '" + privateMountProp +"' from the private mount",
+                    adminSession.hasCapability(operation, adminSession.getItem(privateMountProp), null));
+            assertTrue("Unexpected return value for hasCapability(" + operation+ ") on item '" + globalMountProp +"' from the global mount",
+                    adminSession.hasCapability(operation, adminSession.getItem(globalMountProp), null));
+        }        
+    }
+}



Re: svn commit: r1806602 - in /jackrabbit/oak/trunk/oak-jcr/src: main/java/org/apache/jackrabbit/oak/jcr/repository/ main/java/org/apache/jackrabbit/oak/jcr/session/ test/java/org/apache/jackrabbit/oak/jcr/session/

Posted by Robert Munteanu <ro...@apache.org>.
On Tue, 2017-08-29 at 21:03 -0700, Chetan Mehrotra wrote:
> +
> +        Tracker<MountInfoProvider> tracker =
> whiteboard.track(MountInfoProvider.class);
> +        List<MountInfoProvider> services = tracker.getServices();
> +        tracker.stop();
> +
> +        if ( services.isEmpty() )
> +            this.mountInfoProvider = null;
> +        else if ( services.size() == 1 )
> +            this.mountInfoProvider = services.get(0);
> +        else
> +            throw new IllegalArgumentException("Found " +
> services.size() + " MountInfoProvider references, expected at most
> 1.");
> 
> You can also use WhiteboardUtils#getService here which hides this
> stuff
> Chetan Mehrotra

Good catch, thanks Chetan. Applied in r1806682.

Robert

Re: svn commit: r1806602 - in /jackrabbit/oak/trunk/oak-jcr/src: main/java/org/apache/jackrabbit/oak/jcr/repository/ main/java/org/apache/jackrabbit/oak/jcr/session/ test/java/org/apache/jackrabbit/oak/jcr/session/

Posted by Chetan Mehrotra <ch...@gmail.com>.
+
+        Tracker<MountInfoProvider> tracker =
whiteboard.track(MountInfoProvider.class);
+        List<MountInfoProvider> services = tracker.getServices();
+        tracker.stop();
+
+        if ( services.isEmpty() )
+            this.mountInfoProvider = null;
+        else if ( services.size() == 1 )
+            this.mountInfoProvider = services.get(0);
+        else
+            throw new IllegalArgumentException("Found " +
services.size() + " MountInfoProvider references, expected at most
1.");

You can also use WhiteboardUtils#getService here which hides this stuff
Chetan Mehrotra


On Tue, Aug 29, 2017 at 7:48 AM,  <ro...@apache.org> wrote:
> Author: rombert
> Date: Tue Aug 29 14:48:56 2017
> New Revision: 1806602
>
> URL: http://svn.apache.org/viewvc?rev=1806602&view=rev
> Log:
> OAK-6563 - Session.hasCapability(...) should reflect read-only status of mounts
>
> Add optional support for checking the Mount status in SessionImpl.hasCapability.
>
> Added:
>     jackrabbit/oak/trunk/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/session/SessionImplCapabilityWithMountInfoProviderTest.java
> Modified:
>     jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/repository/RepositoryImpl.java
>     jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/session/SessionContext.java
>     jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/session/SessionImpl.java
>
> Modified: jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/repository/RepositoryImpl.java
> URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/repository/RepositoryImpl.java?rev=1806602&r1=1806601&r2=1806602&view=diff
> ==============================================================================
> --- jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/repository/RepositoryImpl.java (original)
> +++ jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/repository/RepositoryImpl.java Tue Aug 29 14:48:56 2017
> @@ -22,6 +22,7 @@ import static java.util.Collections.sing
>  import static org.apache.jackrabbit.oak.spi.whiteboard.WhiteboardUtils.registerMBean;
>
>  import java.io.Closeable;
> +import java.util.List;
>  import java.util.Map;
>  import java.util.concurrent.Callable;
>  import java.util.concurrent.ScheduledExecutorService;
> @@ -61,8 +62,10 @@ import org.apache.jackrabbit.oak.jcr.ses
>  import org.apache.jackrabbit.oak.plugins.observation.CommitRateLimiter;
>  import org.apache.jackrabbit.oak.spi.gc.DelegatingGCMonitor;
>  import org.apache.jackrabbit.oak.spi.gc.GCMonitor;
> +import org.apache.jackrabbit.oak.spi.mount.MountInfoProvider;
>  import org.apache.jackrabbit.oak.spi.security.SecurityProvider;
>  import org.apache.jackrabbit.oak.spi.whiteboard.Registration;
> +import org.apache.jackrabbit.oak.spi.whiteboard.Tracker;
>  import org.apache.jackrabbit.oak.spi.whiteboard.Whiteboard;
>  import org.apache.jackrabbit.oak.stats.Clock;
>  import org.apache.jackrabbit.oak.stats.StatisticManager;
> @@ -105,6 +108,7 @@ public class RepositoryImpl implements J
>      private final Clock.Fast clock;
>      private final DelegatingGCMonitor gcMonitor = new DelegatingGCMonitor();
>      private final Registration gcMonitorRegistration;
> +    private final MountInfoProvider mountInfoProvider;
>
>      /**
>       * {@link ThreadLocal} counter that keeps track of the save operations
> @@ -152,6 +156,17 @@ public class RepositoryImpl implements J
>          this.clock = new Clock.Fast(scheduledExecutor);
>          this.gcMonitorRegistration = whiteboard.register(GCMonitor.class, gcMonitor, emptyMap());
>          this.fastQueryResultSize = fastQueryResultSize;
> +
> +        Tracker<MountInfoProvider> tracker = whiteboard.track(MountInfoProvider.class);
> +        List<MountInfoProvider> services = tracker.getServices();
> +        tracker.stop();
> +
> +        if ( services.isEmpty() )
> +            this.mountInfoProvider = null;
> +        else if ( services.size() == 1 )
> +            this.mountInfoProvider = services.get(0);
> +        else
> +            throw new IllegalArgumentException("Found " + services.size() + " MountInfoProvider references, expected at most 1.");
>      }
>
>      //---------------------------------------------------------< Repository >---
> @@ -343,7 +358,7 @@ public class RepositoryImpl implements J
>              Map<String, Object> attributes, SessionDelegate delegate, int observationQueueLength,
>              CommitRateLimiter commitRateLimiter) {
>          return new SessionContext(this, statisticManager, securityProvider, whiteboard, attributes,
> -                delegate, observationQueueLength, commitRateLimiter, fastQueryResultSize);
> +                delegate, observationQueueLength, commitRateLimiter, mountInfoProvider, fastQueryResultSize);
>      }
>
>      /**
>
> Modified: jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/session/SessionContext.java
> URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/session/SessionContext.java?rev=1806602&r1=1806601&r2=1806602&view=diff
> ==============================================================================
> --- jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/session/SessionContext.java (original)
> +++ jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/session/SessionContext.java Tue Aug 29 14:48:56 2017
> @@ -28,6 +28,7 @@ import java.util.Set;
>
>  import javax.annotation.CheckForNull;
>  import javax.annotation.Nonnull;
> +import javax.annotation.Nullable;
>  import javax.jcr.PathNotFoundException;
>  import javax.jcr.Repository;
>  import javax.jcr.RepositoryException;
> @@ -56,6 +57,7 @@ import org.apache.jackrabbit.oak.namepat
>  import org.apache.jackrabbit.oak.plugins.nodetype.ReadOnlyNodeTypeManager;
>  import org.apache.jackrabbit.oak.plugins.observation.CommitRateLimiter;
>  import org.apache.jackrabbit.oak.plugins.value.jcr.ValueFactoryImpl;
> +import org.apache.jackrabbit.oak.spi.mount.MountInfoProvider;
>  import org.apache.jackrabbit.oak.spi.security.SecurityConfiguration;
>  import org.apache.jackrabbit.oak.spi.security.SecurityProvider;
>  import org.apache.jackrabbit.oak.spi.security.authorization.AuthorizationConfiguration;
> @@ -89,6 +91,7 @@ public class SessionContext implements N
>      private final SessionDelegate delegate;
>      private final int observationQueueLength;
>      private final CommitRateLimiter commitRateLimiter;
> +    private MountInfoProvider mountInfoProvider;
>
>      private final NamePathMapper namePathMapper;
>      private final ValueFactory valueFactory;
> @@ -118,7 +121,7 @@ public class SessionContext implements N
>               int observationQueueLength, CommitRateLimiter commitRateLimiter) {
>
>          this(repository, statisticManager, securityProvider, whiteboard, attributes, delegate,
> -            observationQueueLength, commitRateLimiter, false);
> +            observationQueueLength, commitRateLimiter, null, false);
>      }
>
>      public SessionContext(
> @@ -126,7 +129,7 @@ public class SessionContext implements N
>              @Nonnull SecurityProvider securityProvider, @Nonnull Whiteboard whiteboard,
>              @Nonnull Map<String, Object> attributes, @Nonnull final SessionDelegate delegate,
>              int observationQueueLength, CommitRateLimiter commitRateLimiter,
> -            boolean fastQueryResultSize) {
> +            MountInfoProvider mountInfoProvider, boolean fastQueryResultSize) {
>          this.repository = checkNotNull(repository);
>          this.statisticManager = statisticManager;
>          this.securityProvider = checkNotNull(securityProvider);
> @@ -135,6 +138,7 @@ public class SessionContext implements N
>          this.delegate = checkNotNull(delegate);
>          this.observationQueueLength = observationQueueLength;
>          this.commitRateLimiter = commitRateLimiter;
> +        this.mountInfoProvider = mountInfoProvider;
>          SessionStats sessionStats = delegate.getSessionStats();
>          sessionStats.setAttributes(attributes);
>
> @@ -317,6 +321,11 @@ public class SessionContext implements N
>          return fastQueryResultSize;
>      }
>
> +    @Nullable
> +    public MountInfoProvider getMountInfoProvider() {
> +        return mountInfoProvider;
> +    }
> +
>      //-----------------------------------------------------< NamePathMapper >---
>
>      @Override
>
> Modified: jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/session/SessionImpl.java
> URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/session/SessionImpl.java?rev=1806602&r1=1806601&r2=1806602&view=diff
> ==============================================================================
> --- jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/session/SessionImpl.java (original)
> +++ jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/session/SessionImpl.java Tue Aug 29 14:48:56 2017
> @@ -66,6 +66,7 @@ import org.apache.jackrabbit.oak.jcr.del
>  import org.apache.jackrabbit.oak.jcr.security.AccessManager;
>  import org.apache.jackrabbit.oak.jcr.session.operation.SessionOperation;
>  import org.apache.jackrabbit.oak.jcr.xml.ImportHandler;
> +import org.apache.jackrabbit.oak.spi.mount.MountInfoProvider;
>  import org.apache.jackrabbit.oak.spi.security.authentication.ImpersonationCredentials;
>  import org.apache.jackrabbit.oak.spi.security.authorization.permission.Permissions;
>  import org.apache.jackrabbit.oak.stats.CounterStats;
> @@ -669,7 +670,7 @@ public class SessionImpl implements Jack
>                          // add-node needs to be checked on the (path of) the
>                          // new node that has/will be added
>                          String path = PathUtils.concat(tree.getPath(), sessionContext.getOakName(arguments[0].toString()));
> -                        return accessMgr.hasPermissions(path, Session.ACTION_ADD_NODE);
> +                        return accessMgr.hasPermissions(path, Session.ACTION_ADD_NODE)  && !isMountedReadOnly(path);
>                      }
>                  } else if ("setPrimaryType".equals(methodName) || "addMixin".equals(methodName) || "removeMixin".equals(methodName)) {
>                      permission = Permissions.NODE_TYPE_MANAGEMENT;
> @@ -685,7 +686,7 @@ public class SessionImpl implements Jack
>                  } else if ("remove".equals(methodName)) {
>                      permission = Permissions.REMOVE_NODE;
>                  }
> -                return accessMgr.hasPermissions(tree, null, permission);
> +                return accessMgr.hasPermissions(tree, null, permission) && !isMountedReadOnly(tree.getPath());
>              } else {
>                  if ("setValue".equals(methodName)) {
>                      permission = Permissions.MODIFY_PROPERTY;
> @@ -694,9 +695,11 @@ public class SessionImpl implements Jack
>                  }
>                  NodeDelegate parentDelegate = dlg.getParent();
>                  if (parentDelegate != null) {
> -                    return accessMgr.hasPermissions(parentDelegate.getTree(), ((PropertyDelegate) dlg).getPropertyState(), permission);
> +                    return accessMgr.hasPermissions(parentDelegate.getTree(), ((PropertyDelegate) dlg).getPropertyState(), permission)
> +                            && !isMountedReadOnly(parentDelegate.getPath());
>                  } else {
> -                    return accessMgr.hasPermissions(dlg.getPath(), (permission == Permissions.MODIFY_PROPERTY) ? Session.ACTION_SET_PROPERTY : Session.ACTION_REMOVE);
> +                    return accessMgr.hasPermissions(dlg.getPath(), (permission == Permissions.MODIFY_PROPERTY) ? Session.ACTION_SET_PROPERTY : Session.ACTION_REMOVE)
> +                            && !isMountedReadOnly(dlg.getPath());
>                  }
>              }
>          }
> @@ -704,6 +707,11 @@ public class SessionImpl implements Jack
>          return true;
>      }
>
> +    private boolean isMountedReadOnly(String path) {
> +        MountInfoProvider mip = sessionContext.getMountInfoProvider();
> +        return mip != null && mip.getMountByPath(path).isReadOnly();
> +    }
> +
>      @Override
>      @Nonnull
>      public AccessControlManager getAccessControlManager() throws RepositoryException {
>
> Added: jackrabbit/oak/trunk/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/session/SessionImplCapabilityWithMountInfoProviderTest.java
> URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/session/SessionImplCapabilityWithMountInfoProviderTest.java?rev=1806602&view=auto
> ==============================================================================
> --- jackrabbit/oak/trunk/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/session/SessionImplCapabilityWithMountInfoProviderTest.java (added)
> +++ jackrabbit/oak/trunk/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/session/SessionImplCapabilityWithMountInfoProviderTest.java Tue Aug 29 14:48:56 2017
> @@ -0,0 +1,132 @@
> +/*
> + * 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.jcr.session;
> +
> +import static org.junit.Assert.assertFalse;
> +import static org.junit.Assert.assertTrue;
> +
> +import java.util.Collections;
> +
> +import javax.jcr.Repository;
> +import javax.jcr.Session;
> +import javax.jcr.SimpleCredentials;
> +
> +import org.apache.jackrabbit.JcrConstants;
> +import org.apache.jackrabbit.oak.composite.CompositeNodeStore;
> +import org.apache.jackrabbit.oak.jcr.Jcr;
> +import org.apache.jackrabbit.oak.plugins.memory.MemoryNodeStore;
> +import org.apache.jackrabbit.oak.spi.commit.CommitInfo;
> +import org.apache.jackrabbit.oak.spi.commit.EmptyHook;
> +import org.apache.jackrabbit.oak.spi.mount.MountInfoProvider;
> +import org.apache.jackrabbit.oak.spi.mount.Mounts;
> +import org.apache.jackrabbit.oak.spi.state.NodeBuilder;
> +import org.apache.jackrabbit.oak.spi.whiteboard.DefaultWhiteboard;
> +import org.apache.jackrabbit.oak.spi.whiteboard.Whiteboard;
> +import org.junit.Before;
> +import org.junit.Test;
> +
> +public class SessionImplCapabilityWithMountInfoProviderTest {
> +
> +    private Session adminSession;
> +
> +    @Before
> +    public void prepare() throws Exception {
> +        MountInfoProvider mip = Mounts.newBuilder().readOnlyMount("ro", "/private").build();
> +
> +        MemoryNodeStore roStore = new MemoryNodeStore();
> +        {
> +            NodeBuilder builder = roStore.getRoot().builder();
> +            builder
> +                .child("private").setProperty(JcrConstants.JCR_PRIMARYTYPE, JcrConstants.NT_UNSTRUCTURED)
> +                    .setProperty("prop", "value")
> +                .child("foo").setProperty(JcrConstants.JCR_PRIMARYTYPE, JcrConstants.NT_UNSTRUCTURED);
> +            roStore.merge(builder, EmptyHook.INSTANCE, CommitInfo.EMPTY);
> +        }
> +
> +        MemoryNodeStore globalStore = new MemoryNodeStore();
> +        {
> +            NodeBuilder builder = globalStore.getRoot().builder();
> +            builder
> +                .child("foo").setProperty(JcrConstants.JCR_PRIMARYTYPE, JcrConstants.NT_UNSTRUCTURED)
> +                    .setProperty("prop", "value")
> +                .child("bar").setProperty(JcrConstants.JCR_PRIMARYTYPE, JcrConstants.NT_UNSTRUCTURED);
> +            globalStore.merge(builder, EmptyHook.INSTANCE, CommitInfo.EMPTY);
> +        }
> +
> +        CompositeNodeStore store = new CompositeNodeStore.Builder(mip, globalStore)
> +            .addMount("ro", roStore)
> +            .build();
> +
> +        Whiteboard whiteboard = new  DefaultWhiteboard();
> +        whiteboard.register(MountInfoProvider.class, mip, Collections.emptyMap());
> +
> +        Jcr jcr = new Jcr(store).with(whiteboard);
> +        jcr.createContentRepository();
> +        Repository repository = jcr.createRepository();
> +
> +        adminSession = repository.login(new SimpleCredentials("admin", "admin".toCharArray()));
> +    }
> +
> +    @Test
> +    public void addNode() throws Exception {
> +
> +        // unable to add nodes in the read-only mount
> +        assertFalse("Must not be able to add a child not under the private mount root",
> +                adminSession.hasCapability("addNode", adminSession.getNode("/private"), new String[] {"foo"}));
> +        assertFalse("Must not be able to add a child not under the private mount",
> +                adminSession.hasCapability("addNode", adminSession.getNode("/private/foo"), new String[] {"bar"}));
> +        // able to add nodes outside the read-only mount
> +        assertTrue("Must be able to add a child node under the root",
> +                adminSession.hasCapability("addNode", adminSession.getNode("/"), new String[] {"not-private"}));
> +        // unable to add node at the root of the read-only mount ( even though it already exists )
> +        assertFalse("Must not be able to add a child node in place of the private mount",
> +                adminSession.hasCapability("addNode", adminSession.getNode("/"), new String[] {"private"}));
> +    }
> +
> +    @Test
> +    public void orderBefore() throws Exception {
> +        // able to order the root of the mount since the operation is performed on the parent
> +        assertTrue(adminSession.hasCapability("orderBefore", adminSession.getNode("/private"), null));
> +        assertFalse(adminSession.hasCapability("orderBefore", adminSession.getNode("/private/foo"), null));
> +    }
> +
> +    @Test
> +    public void simpleNodeOperations() throws Exception {
> +        for ( String operation : new String[] { "setPrimaryType", "addMixin", "removeMixin" , "setProperty", "remove"} )  {
> +            for ( String privateMountNode : new String[] { "/private", "/private/foo" } ) {
> +                assertFalse("Unexpected return value for hasCapability(" + operation+ ") on node '" + privateMountNode +"' from the private mount",
> +                        adminSession.hasCapability(operation, adminSession.getNode(privateMountNode), null));
> +            }
> +            String globalMountNode = "/foo";
> +            assertTrue("Unexpected return value for hasCapability(" + operation+ ") on node '" + globalMountNode +"' from the global mount",
> +                    adminSession.hasCapability(operation, adminSession.getNode(globalMountNode), null));
> +        }
> +    }
> +
> +    @Test
> +    public void itemOperations() throws Exception {
> +        for ( String operation : new String[] { "setValue", "remove"} )  {
> +            String privateMountProp = "/private/prop";
> +            String globalMountProp = "/foo/prop";
> +
> +            assertFalse("Unexpected return value for hasCapability(" + operation+ ") on item '" + privateMountProp +"' from the private mount",
> +                    adminSession.hasCapability(operation, adminSession.getItem(privateMountProp), null));
> +            assertTrue("Unexpected return value for hasCapability(" + operation+ ") on item '" + globalMountProp +"' from the global mount",
> +                    adminSession.hasCapability(operation, adminSession.getItem(globalMountProp), null));
> +        }
> +    }
> +}
>
>