From dd186c567851dccd3d9ed1e4d157ddb4c159136a Mon Sep 17 00:00:00 2001
From: Robert Munteanu <rombert@apache.org>
Date: Fri, 18 Aug 2017 15:49:01 +0300
Subject: [PATCH] OAK-6563 - Session.hasCapability(...) should reflect
 read-only status of mounts

Strawman ( probably incorrect ) patch which shows how the changes could be made.
---
 .../jackrabbit/oak/jcr/repository/RepositoryImpl.java   | 17 ++++++++++++++++-
 .../jackrabbit/oak/jcr/session/SessionContext.java      | 13 +++++++++++--
 .../apache/jackrabbit/oak/jcr/session/SessionImpl.java  | 16 ++++++++++++----
 3 files changed, 39 insertions(+), 7 deletions(-)

diff --git a/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/repository/RepositoryImpl.java b/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/repository/RepositoryImpl.java
index 3c70171064..bbc274f5b1 100644
--- a/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/repository/RepositoryImpl.java
+++ b/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/repository/RepositoryImpl.java
@@ -22,6 +22,7 @@ import static java.util.Collections.singletonMap;
 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.session.SessionStats;
 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 JackrabbitRepository {
     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 JackrabbitRepository {
         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 JackrabbitRepository {
             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);
     }
 
     /**
diff --git a/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/session/SessionContext.java b/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/session/SessionContext.java
index 565933cc9c..b66962a071 100644
--- a/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/session/SessionContext.java
+++ b/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/session/SessionContext.java
@@ -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.namepath.NamePathMapperImpl;
 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 NamePathMapper {
     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 NamePathMapper {
              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 NamePathMapper {
             @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 NamePathMapper {
         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 NamePathMapper {
         return fastQueryResultSize;
     }
 
+    @Nullable
+    public MountInfoProvider getMountInfoProvider() {
+        return mountInfoProvider;
+    }
+
     //-----------------------------------------------------< NamePathMapper >---
 
     @Override
diff --git a/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/session/SessionImpl.java b/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/session/SessionImpl.java
index 492e096fee..c19fc6a365 100644
--- a/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/session/SessionImpl.java
+++ b/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/session/SessionImpl.java
@@ -66,6 +66,7 @@ import org.apache.jackrabbit.oak.jcr.delegate.SessionDelegate;
 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 JackrabbitSession {
                         // 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 JackrabbitSession {
                 } 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 JackrabbitSession {
                 }
                 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 JackrabbitSession {
         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 {
-- 
2.14.0

