Skip to content

Commit

Permalink
OAK-6563 - Session.hasCapability(...) should reflect read-only status…
Browse files Browse the repository at this point in the history
… of mounts

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

git-svn-id: https://svn.apache.org/repos/asf/jackrabbit/oak/trunk@1806602 13f79535-47bb-0310-9956-ffa450edef68
  • Loading branch information
rombert committed Aug 29, 2017
1 parent 4948825 commit 05cff17
Show file tree
Hide file tree
Showing 4 changed files with 171 additions and 7 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
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;
Expand Down Expand Up @@ -61,8 +62,10 @@
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;
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -152,6 +156,17 @@ public RepositoryImpl(@Nonnull ContentRepository contentRepository,
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 >---
Expand Down Expand Up @@ -343,7 +358,7 @@ protected SessionContext createSessionContext(
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);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@

import javax.annotation.CheckForNull;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.jcr.PathNotFoundException;
import javax.jcr.Repository;
import javax.jcr.RepositoryException;
Expand Down Expand Up @@ -56,6 +57,7 @@
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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -118,15 +121,15 @@ public SessionContext(
int observationQueueLength, CommitRateLimiter commitRateLimiter) {

this(repository, statisticManager, securityProvider, whiteboard, attributes, delegate,
observationQueueLength, commitRateLimiter, false);
observationQueueLength, commitRateLimiter, null, false);
}

public SessionContext(
@Nonnull Repository repository, @Nonnull StatisticManager statisticManager,
@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);
Expand All @@ -135,6 +138,7 @@ public SessionContext(
this.delegate = checkNotNull(delegate);
this.observationQueueLength = observationQueueLength;
this.commitRateLimiter = commitRateLimiter;
this.mountInfoProvider = mountInfoProvider;
SessionStats sessionStats = delegate.getSessionStats();
sessionStats.setAttributes(attributes);

Expand Down Expand Up @@ -317,6 +321,11 @@ public boolean getFastQueryResultSize() {
return fastQueryResultSize;
}

@Nullable
public MountInfoProvider getMountInfoProvider() {
return mountInfoProvider;
}

//-----------------------------------------------------< NamePathMapper >---

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@
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;
Expand Down Expand Up @@ -669,7 +670,7 @@ public boolean hasCapability(String methodName, Object target, Object[] argument
// 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;
Expand All @@ -685,7 +686,7 @@ public boolean hasCapability(String methodName, Object target, Object[] argument
} 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;
Expand All @@ -694,16 +695,23 @@ public boolean hasCapability(String methodName, Object target, Object[] argument
}
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());
}
}
}
// TODO: add more best-effort checks
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 {
Expand Down
Original file line number Diff line number Diff line change
@@ -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));
}
}
}

0 comments on commit 05cff17

Please sign in to comment.