blob: 3486dbfdf04b210ba065d6cc88abe19419a35bf8 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.jackrabbit.oak.spi.security.authorization.cug.impl;
import java.util.ArrayList;
import java.util.List;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import org.apache.jackrabbit.JcrConstants;
import org.apache.jackrabbit.oak.api.ContentSession;
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.commons.PathUtils;
import org.apache.jackrabbit.oak.namepath.NamePathMapper;
import org.apache.jackrabbit.oak.plugins.identifier.IdentifierManager;
import org.apache.jackrabbit.oak.spi.nodetype.NodeTypeConstants;
import org.apache.jackrabbit.oak.plugins.tree.TreeType;
import org.apache.jackrabbit.oak.plugins.version.ReadOnlyVersionManager;
import org.apache.jackrabbit.oak.spi.version.VersionConstants;
import org.apache.jackrabbit.oak.spi.security.authorization.permission.Permissions;
import org.apache.jackrabbit.oak.spi.security.authorization.permission.TreePermission;
import org.apache.jackrabbit.oak.spi.security.principal.EveryonePrincipal;
import org.apache.jackrabbit.oak.plugins.tree.TreeUtil;
import org.jetbrains.annotations.NotNull;
import org.junit.Before;
import org.junit.Test;
import static com.google.common.base.Preconditions.checkNotNull;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
/**
* Test read access to version related information both in the regular
* content and in the version storage.
*/
public class VersionTest extends AbstractCugTest implements NodeTypeConstants, VersionConstants {
private ContentSession testSession;
private Root testRoot;
private ReadOnlyVersionManager versionManager;
private List<String> readAccess = new ArrayList<>();
private List<String> noReadAccess = new ArrayList<>();
@Before
@Override
public void before() throws Exception {
super.before();
// create cugs
// - /content/a : allow testGroup, deny everyone
// - /content/aa/bb : allow testGroup, deny everyone
// - /content/a/b/c : allow everyone, deny testGroup (isolated)
// - /content2 : allow everyone, deny testGroup (isolated)
setupCugsAndAcls();
readAccess = ImmutableList.of(
SUPPORTED_PATH,
"/content/subtree",
"/content/aa");
noReadAccess = ImmutableList.of(
UNSUPPORTED_PATH, /* no access */
"/content2", /* granted by cug only */
"/content/a", /* granted by ace, denied by cug */
"/content/aa/bb" /* granted by ace, denied by cug */
);
for (String path : Iterables.concat(readAccess, noReadAccess)) {
addVersionContent(path);
}
testSession = createTestSession();
testRoot = testSession.getLatestRoot();
versionManager = ReadOnlyVersionManager.getInstance(root, NamePathMapper.DEFAULT);
}
@Override
public void after() throws Exception {
try {
if (testSession != null) {
testSession.close();
}
} finally {
super.after();
}
}
private Tree addVersionContent(@NotNull String path) throws Exception {
Tree t = root.getTree(path);
Tree typesRoot = root.getTree(NodeTypeConstants.NODE_TYPES_PATH);
TreeUtil.addMixin(t, JcrConstants.MIX_VERSIONABLE, typesRoot, null);
root.commit();
// force the creation of a version with frozen node
t.setProperty(JCR_ISCHECKEDOUT, false);
root.commit();
t.setProperty(JCR_ISCHECKEDOUT, true);
root.commit();
if (testRoot != null) {
testRoot.refresh();
}
return t;
}
@Test
public void testReadVersionContent() throws Exception {
IdentifierManager idMgr = new IdentifierManager(testRoot);
ReadOnlyVersionManager vMgr = ReadOnlyVersionManager.getInstance(testRoot, NamePathMapper.DEFAULT);
for (String path : readAccess) {
Tree t = testRoot.getTree(path);
assertTrue(path, t.exists());
PropertyState ps = t.getProperty(JCR_VERSIONHISTORY);
assertNotNull(ps);
String vhUUID = ps.getValue(Type.STRING);
assertEquals(vhUUID, ps.getValue(Type.STRING));
Tree versionHistory = vMgr.getVersionHistory(t);
assertNotNull(versionHistory);
assertTrue(versionHistory.exists());
assertTrue(versionHistory.getChild(JCR_ROOTVERSION).exists());
assertFalse(versionHistory.getParent().exists());
Tree vhTree = testRoot.getTree(versionHistory.getPath());
assertTrue(vhTree.exists());
String vhPath = idMgr.resolveUUID(vhUUID);
assertNotNull(vhPath);
assertEquals(versionHistory.getPath(), vhPath);
assertTrue(testRoot.getTree(vhPath).exists());
assertTrue(testRoot.getTree(vhPath + '/' + JCR_ROOTVERSION).exists());
}
}
@Test
public void testReadVersionContentNoAccess() {
IdentifierManager idMgr = new IdentifierManager(testRoot);
for (String path : noReadAccess) {
String vhUUID = checkNotNull(TreeUtil.getString(root.getTree(path), JCR_VERSIONHISTORY));
String vhPath = PathUtils.concat(VERSION_STORE_PATH, versionManager.getVersionHistoryPath(vhUUID));
Tree vHistory = testRoot.getTree(vhPath);
assertFalse(vHistory.exists());
assertFalse(vHistory.getParent().exists());
assertFalse(vHistory.getChild(JCR_ROOTVERSION).exists());
assertFalse(testRoot.getTree(vhPath + '/' + JCR_ROOTVERSION).exists());
String vh = idMgr.resolveUUID(vhUUID);
assertNull(path, vh);
}
}
@Test
public void testReadVersionStorage() {
assertFalse(testRoot.getTree(VersionConstants.VERSION_STORE_PATH).exists());
}
@Test
public void testSupportedPermissions() throws Exception {
Tree versionable = addVersionContent("/content/a/b/c");
CugPermissionProvider pp = createCugPermissionProvider(ImmutableSet.of(SUPPORTED_PATH, SUPPORTED_PATH2));
Tree versionStorage = root.getTree(VersionConstants.VERSION_STORE_PATH);
assertEquals(Permissions.NO_PERMISSION, pp.supportedPermissions(versionStorage, null, Permissions.READ));
assertEquals(Permissions.NO_PERMISSION, pp.supportedPermissions(versionStorage.getParent(), null, Permissions.READ));
// tree with cug (access is granted)
Tree vh = versionManager.getVersionHistory(versionable);
assertEquals(Permissions.READ, pp.supportedPermissions(vh, null, Permissions.READ));
// tree with cug (but no access granted)
vh = versionManager.getVersionHistory(root.getTree("/content2"));
assertEquals(Permissions.READ, pp.supportedPermissions(vh, null, Permissions.READ));
// tree without cug
vh = versionManager.getVersionHistory(root.getTree(UNSUPPORTED_PATH));
assertEquals(Permissions.NO_PERMISSION, pp.supportedPermissions(vh, null, Permissions.READ));
// tree without cug
vh = versionManager.getVersionHistory(root.getTree(SUPPORTED_PATH));
assertEquals(Permissions.NO_PERMISSION, pp.supportedPermissions(vh, null, Permissions.READ));
}
@Test
public void testVersionableRemoved() throws Exception {
// cug at /content/a/b/c grants access
Tree versionable = addVersionContent("/content/a/b/c");
Tree vh = checkNotNull(versionManager.getVersionHistory(versionable));
assertTrue(testRoot.getTree(vh.getPath()).exists());
versionable.remove();
root.commit();
// the cug-permission provider still supports the path as there exists
// a cug higher up in the hierarchy
// -> the parent cug takes effect now
CugPermissionProvider pp = createCugPermissionProvider(ImmutableSet.of(SUPPORTED_PATH, SUPPORTED_PATH2));
assertEquals(Permissions.READ, pp.supportedPermissions(vh, null, Permissions.READ));
assertFalse(pp.isGranted(vh, null, Permissions.READ));
// the vh associated with /content/a/b/c is no longer accessible
testRoot.refresh();
assertFalse(testRoot.getTree(vh.getPath()).exists());
}
@Test
public void testVersionableRemoved2() throws Exception {
// cug at /content/a/b/c denies access
Tree versionable = root.getTree("/content/a");
Tree vh = checkNotNull(versionManager.getVersionHistory(versionable));
assertFalse(testRoot.getTree(vh.getPath()).exists());
versionable.remove();
root.commit();
// removing this versionable node removes the CUG in this tree
// -> the permission provider is no longer responsible
CugPermissionProvider pp = createCugPermissionProvider(ImmutableSet.of(SUPPORTED_PATH, SUPPORTED_PATH2));
assertEquals(Permissions.NO_PERMISSION, pp.supportedPermissions(vh, null, Permissions.READ));
assertFalse(pp.isGranted(vh, null, Permissions.READ));
// subsequently the deny of the former CUG is gone as well
testRoot.refresh();
assertTrue(testRoot.getTree(vh.getPath()).exists());
}
@Test
public void testTreePermissionVersionable() throws Exception {
Tree versionable = root.getTree("/content/a");
Tree vh = checkNotNull(versionManager.getVersionHistory(versionable));
CugPermissionProvider pp = createCugPermissionProvider(ImmutableSet.of(SUPPORTED_PATH, SUPPORTED_PATH2), EveryonePrincipal.getInstance());
Tree t = root.getTree("/");
TreePermission tp = pp.getTreePermission(t, TreePermission.EMPTY);
String path = PathUtils.concat(vh.getPath(), "1.0", JCR_FROZENNODE, "b/c");
for (String segm : PathUtils.elements(path)) {
t = t.getChild(segm);
tp = pp.getTreePermission(t, tp);
if (JCR_SYSTEM.equals(segm) || ReadOnlyVersionManager.isVersionStoreTree(t)) {
assertTrue(t.getPath(), tp instanceof EmptyCugTreePermission);
} else {
assertTrue(t.getPath(), tp instanceof CugTreePermission);
assertEquals(t.getPath(), "c".equals(segm), tp.canRead());
}
}
}
@Test
public void testTreePermissionVersionable2() throws Exception {
Tree versionable = root.getTree("/content");
Tree vh = checkNotNull(versionManager.getVersionHistory(versionable));
CugPermissionProvider pp = createCugPermissionProvider(ImmutableSet.of(SUPPORTED_PATH, SUPPORTED_PATH2));
Tree t = root.getTree("/");
TreePermission tp = pp.getTreePermission(t, TreePermission.EMPTY);
String path = PathUtils.concat(vh.getPath(), "1.0", JCR_FROZENNODE, "aa");
for (String segm : PathUtils.elements(path)) {
t = t.getChild(segm);
tp = pp.getTreePermission(t, tp);
if (JCR_SYSTEM.equals(segm) || ReadOnlyVersionManager.isVersionStoreTree(t)) {
assertTrue(t.getPath(), tp instanceof EmptyCugTreePermission);
} else {
assertTrue(t.getPath(), tp instanceof CugTreePermission);
}
}
}
@Test
public void testTreePermissionVersionableUnsupportedPath() throws Exception {
Tree versionable = root.getTree(UNSUPPORTED_PATH);
Tree vh = checkNotNull(versionManager.getVersionHistory(versionable));
CugPermissionProvider pp = createCugPermissionProvider(ImmutableSet.of(SUPPORTED_PATH, SUPPORTED_PATH2));
Tree t = root.getTree("/");
TreePermission tp = pp.getTreePermission(t, TreePermission.EMPTY);
for (String segm : PathUtils.elements(vh.getPath())) {
t = t.getChild(segm);
tp = pp.getTreePermission(t, tp);
if (JCR_SYSTEM.equals(segm) || ReadOnlyVersionManager.isVersionStoreTree(t)) {
assertTrue(t.getPath(), tp instanceof EmptyCugTreePermission);
} else {
assertSame(t.getPath(), TreePermission.NO_RECOURSE, tp);
}
}
}
@Test
public void testTreePermissionAtVersionableAboveSupported() throws Exception {
Tree vh = checkNotNull(versionManager.getVersionHistory(root.getTree(SUPPORTED_PATH)));
CugPermissionProvider pp = createCugPermissionProvider(ImmutableSet.of(SUPPORTED_PATH + "/a"));
TreePermission tp = getTreePermission(root, vh.getPath(), pp);
assertTrue(tp instanceof EmptyCugTreePermission);
}
@Test
public void testCugAtRoot() throws Exception {
Tree versionable = root.getTree(UNSUPPORTED_PATH);
String vhPath = checkNotNull(versionManager.getVersionHistory(versionable)).getPath();
try {
createCug(root, PathUtils.ROOT_PATH, EveryonePrincipal.NAME);
root.commit();
CugPermissionProvider pp = createCugPermissionProvider(ImmutableSet.of("/"));
Tree t = root.getTree("/");
TreePermission tp = pp.getTreePermission(t, TreePermission.EMPTY);
assertTrue(tp instanceof CugTreePermission);
for (String segm : PathUtils.elements(vhPath)) {
t = t.getChild(segm);
tp = pp.getTreePermission(t, tp);
assertTrue(tp instanceof CugTreePermission);
}
} finally {
root.getTree("/").removeProperty(JCR_MIXINTYPES);
Tree cug = root.getTree("/rep:cugPolicy");
if (cug.exists()) {
cug.remove();
}
root.commit();
}
}
@Test
public void testVersionableWithUnsupportedType() throws Exception {
Tree versionable = root.getTree("/content");
Tree vh = checkNotNull(versionManager.getVersionHistory(versionable));
Tree frozen = vh.getChild("1.0").getChild(JCR_FROZENNODE).getChild("a").getChild("b").getChild("c");
Tree invalidFrozen = frozen.addChild(REP_CUG_POLICY);
invalidFrozen.setProperty(JCR_PRIMARYTYPE, NT_REP_CUG_POLICY);
CugPermissionProvider pp = createCugPermissionProvider(ImmutableSet.of(SUPPORTED_PATH, SUPPORTED_PATH2));
TreePermission tp = getTreePermission(root, PathUtils.concat(vh.getPath(), "1.0", JCR_FROZENNODE, "a/b/c"), pp);
TreePermission tpForUnsupportedType = pp.getTreePermission(invalidFrozen, TreeType.VERSION, tp);
assertEquals(TreePermission.NO_RECOURSE, tpForUnsupportedType);
}
@Test
public void testVersionableWithCugParent() throws Exception {
addVersionContent("/content/aa/bb/cc");
Tree cc = root.getTree("/content/aa/bb/cc");
assertFalse(CugUtil.hasCug(cc));
Tree vh = checkNotNull(versionManager.getVersionHistory(cc));
Tree t = root.getTree("/");
CugPermissionProvider pp = createCugPermissionProvider(
ImmutableSet.of(SUPPORTED_PATH, SUPPORTED_PATH2), getTestGroupPrincipal());
TreePermission tp = getTreePermission(root, vh.getPath(), pp);
assertTrue(tp instanceof CugTreePermission);
assertTrue(((CugTreePermission) tp).isInCug());
assertTrue(((CugTreePermission) tp).isAllow());
}
}