Index: oak-core/src/main/java/org/apache/jackrabbit/oak/core/LazyValue.java =================================================================== --- oak-core/src/main/java/org/apache/jackrabbit/oak/core/LazyValue.java (revision 1851744) +++ oak-commons/src/main/java/org/apache/jackrabbit/oak/commons/LazyValue.java (date 1548156633000) @@ -14,7 +14,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.apache.jackrabbit.oak.core; +package org.apache.jackrabbit.oak.commons; /** * An instances of this class represents a lazy value of type {@code T}. @@ -24,7 +24,7 @@ *

* {@code LazyValue} instances are thread safe. */ -abstract class LazyValue { +public abstract class LazyValue { private volatile T value; /** Index: oak-commons/src/main/java/org/apache/jackrabbit/oak/commons/package-info.java IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== --- oak-commons/src/main/java/org/apache/jackrabbit/oak/commons/package-info.java (revision 1851744) +++ oak-commons/src/main/java/org/apache/jackrabbit/oak/commons/package-info.java (date 1548157576000) @@ -14,7 +14,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -@Version("1.2.1") +@Version("1.3.0") package org.apache.jackrabbit.oak.commons; import org.osgi.annotation.versioning.Version; Index: oak-core/src/main/java/org/apache/jackrabbit/oak/core/MutableRoot.java IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== --- oak-core/src/main/java/org/apache/jackrabbit/oak/core/MutableRoot.java (revision 1851744) +++ oak-core/src/main/java/org/apache/jackrabbit/oak/core/MutableRoot.java (date 1548156850000) @@ -39,6 +39,7 @@ import org.apache.jackrabbit.oak.api.ContentSession; import org.apache.jackrabbit.oak.api.QueryEngine; import org.apache.jackrabbit.oak.api.Root; +import org.apache.jackrabbit.oak.commons.LazyValue; import org.apache.jackrabbit.oak.commons.PathUtils; import org.apache.jackrabbit.oak.plugins.index.diffindex.UUIDDiffIndexProviderWrapper; import org.apache.jackrabbit.oak.query.ExecutionContext; Index: oak-core/src/main/java/org/apache/jackrabbit/oak/core/SecureNodeBuilder.java IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== --- oak-core/src/main/java/org/apache/jackrabbit/oak/core/SecureNodeBuilder.java (revision 1851744) +++ oak-core/src/main/java/org/apache/jackrabbit/oak/core/SecureNodeBuilder.java (date 1548156796000) @@ -23,6 +23,7 @@ import org.apache.jackrabbit.oak.api.PropertyState; import org.apache.jackrabbit.oak.api.Tree; import org.apache.jackrabbit.oak.api.Type; +import org.apache.jackrabbit.oak.commons.LazyValue; import org.apache.jackrabbit.oak.plugins.tree.factories.TreeFactory; import org.apache.jackrabbit.oak.spi.security.authorization.permission.PermissionProvider; import org.apache.jackrabbit.oak.spi.security.authorization.permission.TreePermission; Index: oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/SelectorImpl.java IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== --- oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/SelectorImpl.java (revision 1851744) +++ oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/SelectorImpl.java (date 1548181373000) @@ -20,11 +20,7 @@ import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.collect.Lists.newArrayList; -import static org.apache.jackrabbit.JcrConstants.JCR_MIXINTYPES; -import static org.apache.jackrabbit.JcrConstants.JCR_PRIMARYTYPE; import static org.apache.jackrabbit.JcrConstants.NT_BASE; -import static org.apache.jackrabbit.oak.api.Type.NAME; -import static org.apache.jackrabbit.oak.api.Type.NAMES; import java.util.ArrayList; import java.util.List; @@ -35,8 +31,11 @@ import org.apache.jackrabbit.oak.api.Result.SizePrecision; import org.apache.jackrabbit.oak.api.Tree; import org.apache.jackrabbit.oak.api.Type; +import org.apache.jackrabbit.oak.commons.LazyValue; import org.apache.jackrabbit.oak.commons.PathUtils; +import org.apache.jackrabbit.oak.core.ImmutableRoot; import org.apache.jackrabbit.oak.plugins.memory.PropertyBuilder; +import org.apache.jackrabbit.oak.plugins.tree.TreeUtil; import org.apache.jackrabbit.oak.query.QueryImpl; import org.apache.jackrabbit.oak.query.QueryOptions; import org.apache.jackrabbit.oak.spi.query.fulltext.FullTextExpression; @@ -158,6 +157,8 @@ private Tree lastTree; private String lastPath; + private LazyValue lastReadOnlyTree; + public SelectorImpl(NodeTypeInfo nodeTypeInfo, String selectorName) { this.nodeTypeInfo = checkNotNull(nodeTypeInfo); this.selectorName = checkNotNull(selectorName); @@ -496,24 +497,19 @@ } private boolean evaluateTypeMatch() { - Tree tree = getTree(currentRow.getPath()); + String path = currentRow.getPath(); + Tree tree = getTree(path); if (tree == null || !tree.exists()) { return false; } - PropertyState primary = tree.getProperty(JCR_PRIMARYTYPE); - if (primary != null && primary.getType() == NAME) { - String name = primary.getValue(NAME); - if (primaryTypes.contains(name)) { - return true; - } + String primaryTypeName = TreeUtil.getPrimaryTypeName(tree, getReadOnlyTree(path)); + if (primaryTypeName != null && primaryTypes.contains(primaryTypeName)) { + return true; } - PropertyState mixins = tree.getProperty(JCR_MIXINTYPES); - if (mixins != null && mixins.getType() == NAMES) { - for (String name : mixins.getValue(NAMES)) { - if (mixinTypes.contains(name)) { - return true; - } + for (String mixinName : TreeUtil.getMixinTypeNames(tree, getReadOnlyTree(path))) { + if (mixinTypes.contains(mixinName)) { + return true; } } // no matches found @@ -552,10 +548,23 @@ if (lastPath == null || !path.equals(lastPath)) { lastTree = query.getTree(path); lastPath = path; + lastReadOnlyTree = null; } return lastTree; } + private LazyValue getReadOnlyTree(@NotNull String path) { + if (lastReadOnlyTree == null) { + lastReadOnlyTree = new LazyValue() { + @Override + protected Tree createValue() { + return new ImmutableRoot(query.getExecutionContext().getBaseState()).getTree(path); + } + }; + } + return lastReadOnlyTree; + } + /** * The value for the given selector for the current node. * Index: oak-core/src/test/java/org/apache/jackrabbit/oak/core/SecureNodeBuilderTest.java IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== --- oak-core/src/test/java/org/apache/jackrabbit/oak/core/SecureNodeBuilderTest.java (revision 1851744) +++ oak-core/src/test/java/org/apache/jackrabbit/oak/core/SecureNodeBuilderTest.java (date 1548158005000) @@ -19,6 +19,7 @@ import com.google.common.collect.ImmutableList; import com.google.common.collect.Iterables; import org.apache.jackrabbit.oak.api.Type; +import org.apache.jackrabbit.oak.commons.LazyValue; import org.apache.jackrabbit.oak.plugins.memory.MemoryNodeStore; import org.apache.jackrabbit.oak.plugins.memory.PropertyStates; import org.apache.jackrabbit.oak.spi.commit.CommitInfo; Index: oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/tree/impl/TreeUtilTest.java IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== --- oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/tree/impl/TreeUtilTest.java (revision 1851744) +++ oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/tree/impl/TreeUtilTest.java (date 1548179661000) @@ -16,17 +16,28 @@ */ package org.apache.jackrabbit.oak.plugins.tree.impl; +import com.google.common.collect.Iterables; import org.apache.jackrabbit.JcrConstants; +import org.apache.jackrabbit.oak.AbstractSecurityTest; +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.AbstractSecurityTest; import org.apache.jackrabbit.oak.api.Type; +import org.apache.jackrabbit.oak.commons.LazyValue; import org.apache.jackrabbit.oak.plugins.tree.TreeUtil; import org.apache.jackrabbit.oak.spi.nodetype.NodeTypeConstants; import org.junit.Test; +import org.mockito.Mock; +import org.mockito.Mockito; + +import javax.jcr.GuestCredentials; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.when; public class TreeUtilTest extends AbstractSecurityTest { @@ -65,4 +76,133 @@ assertNotNull(ps); assertEquals("", ps.getValue(Type.STRING)); } + + @Test + public void testGetPrimaryTypeNameAccessible() throws Exception { + Tree tree = root.getTree("/"); + String expected = TreeUtil.getPrimaryTypeName(tree); + assertEquals(expected, TreeUtil.getPrimaryTypeName(tree, new LazyValue() { + @Override + protected Tree createValue() { + return tree; + } + })); + } + + @Test + public void testGetPrimaryTypeNameNotAccessible() throws Exception { + Tree tree = root.getTree("/"); + String expected = TreeUtil.getPrimaryTypeName(tree); + try (ContentSession cs = login(new GuestCredentials())) { + Root r = cs.getLatestRoot(); + assertNull(TreeUtil.getPrimaryTypeName(r.getTree("/"))); + assertEquals(expected, TreeUtil.getPrimaryTypeName(r.getTree("/"), new LazyValue() { + @Override + protected Tree createValue() { + return tree; + } + })); + } + } + + @Test + public void testGetPrimaryTypeNameNotAccessibleNew() throws Exception { + Tree testTree = TreeUtil.addChild(root.getTree("/"), "test", NodeTypeConstants.NT_OAK_UNSTRUCTURED); + + Tree t = Mockito.mock(Tree.class); + when(t.hasProperty(JcrConstants.JCR_PRIMARYTYPE)).thenReturn(false); + when(t.getStatus()).thenReturn(Tree.Status.NEW); + + assertNull(TreeUtil.getPrimaryTypeName(t, new LazyValue() { + @Override + protected Tree createValue() { + return testTree; + } + })); + } + + @Test + public void testGetMixinTypeNamesMissingAccessible() throws Exception { + assertTrue(Iterables.isEmpty(TreeUtil.getMixinTypeNames(root.getTree("/")))); + assertTrue(Iterables.isEmpty(TreeUtil.getMixinTypeNames(root.getTree("/"), new LazyValue() { + @Override + protected Tree createValue() { + return root.getTree("/"); + } + }))); + } + + @Test + public void testGetMixinTypeNamesMissingNotAccessible() throws Exception { + Tree tree = root.getTree("/"); + assertTrue(Iterables.isEmpty(TreeUtil.getMixinTypeNames(tree))); + + try (ContentSession cs = login(new GuestCredentials())) { + Root guestRoot = cs.getLatestRoot(); + assertTrue(Iterables.isEmpty(TreeUtil.getMixinTypeNames(guestRoot.getTree("/")))); + assertTrue(Iterables.isEmpty(TreeUtil.getMixinTypeNames(guestRoot.getTree("/"), new LazyValue() { + @Override + protected Tree createValue() { + return tree; + } + }))); + } + } + + @Test + public void testGetMixinTypeNamesPresentAccessible() throws Exception { + Tree testTree = TreeUtil.addChild(root.getTree("/"), "test", NodeTypeConstants.NT_OAK_UNSTRUCTURED); + TreeUtil.addMixin(testTree, "mix:title", root.getTree(NodeTypeConstants.NODE_TYPES_PATH), "uid"); + + String path = testTree.getPath(); + Iterable expected = TreeUtil.getMixinTypeNames(root.getTree(path)); + assertTrue(Iterables.contains(expected, "mix:title")); + + assertTrue(Iterables.elementsEqual(expected, TreeUtil.getMixinTypeNames(testTree, new LazyValue() { + @Override + protected Tree createValue() { + return testTree; + } + }))); + } + + @Test + public void testGetMixinTypeNamesPresentNotAccessible() throws Exception { + Tree testTree = TreeUtil.addChild(root.getTree("/"), "test", NodeTypeConstants.NT_OAK_UNSTRUCTURED); + TreeUtil.addMixin(testTree, "mix:title", root.getTree(NodeTypeConstants.NODE_TYPES_PATH), "uid"); + root.commit(); + + String path = testTree.getPath(); + Iterable expected = TreeUtil.getMixinTypeNames(root.getTree(path)); + assertTrue(Iterables.contains(expected, "mix:title")); + + try (ContentSession cs = login(new GuestCredentials())) { + Root guestRoot = cs.getLatestRoot(); + assertTrue(Iterables.isEmpty(TreeUtil.getMixinTypeNames(guestRoot.getTree(path)))); + assertTrue(Iterables.elementsEqual(expected, TreeUtil.getMixinTypeNames(guestRoot.getTree(path), new LazyValue() { + @Override + protected Tree createValue() { + return testTree; + } + }))); + } + } + + @Test + public void testGetMixinTypeNamesPresentNotAccessibleNew() throws Exception { + Tree testTree = TreeUtil.addChild(root.getTree("/"), "test", NodeTypeConstants.NT_OAK_UNSTRUCTURED); + TreeUtil.addMixin(testTree, "mix:title", root.getTree(NodeTypeConstants.NODE_TYPES_PATH), "uid"); + root.commit(); + + Tree t = Mockito.mock(Tree.class); + when(t.hasProperty(JcrConstants.JCR_MIXINTYPES)).thenReturn(false); + when(t.getStatus()).thenReturn(Tree.Status.NEW); + + assertTrue(Iterables.isEmpty(TreeUtil.getMixinTypeNames(t, new LazyValue() { + @Override + protected Tree createValue() { + return testTree; + } + }))); + } } \ No newline at end of file Index: oak-core/src/test/java/org/apache/jackrabbit/oak/security/authorization/evaluation/AbstractOakCoreTest.java IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== --- oak-core/src/test/java/org/apache/jackrabbit/oak/security/authorization/evaluation/AbstractOakCoreTest.java (revision 1851744) +++ oak-core/src/test/java/org/apache/jackrabbit/oak/security/authorization/evaluation/AbstractOakCoreTest.java (date 1548164656000) @@ -25,6 +25,8 @@ import org.apache.jackrabbit.oak.AbstractSecurityTest; import org.apache.jackrabbit.oak.api.ContentSession; import org.apache.jackrabbit.oak.api.Root; +import org.apache.jackrabbit.oak.api.Tree; +import org.apache.jackrabbit.oak.plugins.tree.TreeUtil; import org.apache.jackrabbit.oak.util.NodeUtil; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -50,18 +52,17 @@ testPrincipal = getTestUser().getPrincipal(); - NodeUtil rootNode = new NodeUtil(root.getTree("/")); - NodeUtil a = rootNode.addChild("a", NT_UNSTRUCTURED); - a.setString("aProp", "aValue"); + Tree a = TreeUtil.addChild(root.getTree("/"), "a", NT_UNSTRUCTURED); + a.setProperty("aProp", "aValue"); - NodeUtil b = a.addChild("b", NT_UNSTRUCTURED); - b.setString("bProp", "bValue"); + Tree b = TreeUtil.addChild(a, "b", NT_UNSTRUCTURED); + b.setProperty("bProp", "bValue"); // sibling - NodeUtil bb = a.addChild("bb", NT_UNSTRUCTURED); - bb.setString("bbProp", "bbValue"); + Tree bb = TreeUtil.addChild(a, "bb", NT_UNSTRUCTURED); + bb.setProperty("bbProp", "bbValue"); - NodeUtil c = b.addChild("c", NT_UNSTRUCTURED); - c.setString("cProp", "cValue"); + Tree c = TreeUtil.addChild(b, "c", NT_UNSTRUCTURED); + c.setProperty("cProp", "cValue"); root.commit(); } Index: oak-core/src/test/java/org/apache/jackrabbit/oak/security/authorization/evaluation/AbstractQueryTest.java IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== --- oak-core/src/test/java/org/apache/jackrabbit/oak/security/authorization/evaluation/AbstractQueryTest.java (date 1548175352000) +++ oak-core/src/test/java/org/apache/jackrabbit/oak/security/authorization/evaluation/AbstractQueryTest.java (date 1548175352000) @@ -0,0 +1,164 @@ +/* + * 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.security.authorization.evaluation; + +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Iterables; +import org.apache.jackrabbit.JcrConstants; +import org.apache.jackrabbit.api.security.JackrabbitAccessControlList; +import org.apache.jackrabbit.commons.jackrabbit.authorization.AccessControlUtils; +import org.apache.jackrabbit.oak.api.Result; +import org.apache.jackrabbit.oak.api.Tree; +import org.apache.jackrabbit.oak.plugins.tree.TreeUtil; +import org.apache.jackrabbit.oak.spi.nodetype.NodeTypeConstants; +import org.apache.jackrabbit.oak.spi.security.authorization.AuthorizationConfiguration; +import org.apache.jackrabbit.oak.spi.security.authorization.accesscontrol.AccessControlConstants; +import org.apache.jackrabbit.oak.spi.security.authorization.permission.PermissionProvider; +import org.apache.jackrabbit.oak.spi.security.authorization.permission.Permissions; +import org.apache.jackrabbit.oak.spi.security.privilege.PrivilegeConstants; +import org.jetbrains.annotations.NotNull; +import org.junit.Before; +import org.junit.Test; + +import javax.jcr.PropertyType; +import javax.jcr.RepositoryException; +import javax.jcr.Session; +import javax.jcr.Value; +import javax.jcr.query.Query; +import javax.jcr.security.AccessControlManager; +import java.util.Collections; +import java.util.Map; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +public abstract class AbstractQueryTest extends AbstractOakCoreTest { + + Tree node; + Tree subnode; + + @Before + public void before() throws Exception { + super.before(); + + createIndexDefinition(); + + node = TreeUtil.addChild(root.getTree("/"), "node", JcrConstants.NT_UNSTRUCTURED); + subnode = TreeUtil.addChild(node, "subnode", JcrConstants.NT_UNSTRUCTURED); + root.commit(); + } + + void grantPropertyReadAccess(@NotNull String propertyName) throws Exception { + AccessControlManager acMgr = getAccessControlManager(root); + JackrabbitAccessControlList acl = AccessControlUtils.getAccessControlList(acMgr, "/"); + if (acl != null) { + Map restrictions = ImmutableMap.of(AccessControlConstants.REP_ITEM_NAMES, new Value[] {getValueFactory(root).createValue(propertyName, PropertyType.NAME)}); + acl.addEntry(testPrincipal, AccessControlUtils.privilegesFromNames(acMgr, PrivilegeConstants.REP_READ_PROPERTIES), true, null, restrictions); + acMgr.setPolicy(acl.getPath(), acl); + } + } + + void createIndexDefinition() throws RepositoryException {}; + abstract String getStatement(); + + @Test + public void testQueryWithEmptyGlobRestriction() throws Exception { + // setup permissions for testuser using rep:glob restriction such that + // - access to /node is granted + // - access to /node/subnode is denied + AccessControlManager acm = getAccessControlManager(root); + JackrabbitAccessControlList acl = AccessControlUtils.getAccessControlList(acm, node.getPath()); + if (acl != null) { + Map restrictions = ImmutableMap.of(AccessControlConstants.REP_GLOB, getValueFactory(root).createValue("")); + acl.addEntry(testPrincipal, AccessControlUtils.privilegesFromNames(acm, PrivilegeConstants.JCR_ALL), true, restrictions); + acm.setPolicy(acl.getPath(), acl); + root.commit(); + } + + assertAccess(node.getPath(), subnode.getPath(), false); + + // test that query result corresponds to the direct access (node readable, subnode not readable) + Result result = getTestRoot().getQueryEngine().executeQuery(getStatement(), Query.JCR_SQL2, Collections.emptyMap(), Collections.emptyMap()); + + Iterable expected = ImmutableSet.of(node.getPath()); + assertTrue(Iterables.elementsEqual(expected, Iterables.transform(result.getRows(), row -> row.getPath()))); + } + + @Test + public void testQueryWithEmptyGlobRestrictionAndPropertyRead() throws Exception { + // setup permissions for testuser using rep:glob restriction such that + // - access to /node is granted + // - access to /node/subnode is denied + AccessControlManager acm = getAccessControlManager(root); + JackrabbitAccessControlList acl = AccessControlUtils.getAccessControlList(acm, node.getPath()); + if (acl != null) { + Map restrictions = ImmutableMap.of(AccessControlConstants.REP_GLOB, getValueFactory(root).createValue("")); + acl.addEntry(testPrincipal, privilegesFromNames(PrivilegeConstants.JCR_ALL), true, restrictions); + + restrictions = ImmutableMap.of(AccessControlConstants.REP_GLOB, getValueFactory(root).createValue("/"+NodeTypeConstants.JCR_PRIMARYTYPE)); + acl.addEntry(testPrincipal, privilegesFromNames(PrivilegeConstants.REP_READ_PROPERTIES), true, restrictions); + + acm.setPolicy(acl.getPath(), acl); + root.commit(); + } + + assertAccess(node.getPath(), subnode.getPath(), true); + + // test that query result corresponds to the direct access (node readable, subnode not readable) + Result result = getTestRoot().getQueryEngine().executeQuery(getStatement(), Query.JCR_SQL2, Collections.emptyMap(), Collections.emptyMap()); + + Iterable expected = ImmutableSet.of(node.getPath()); + assertTrue(Iterables.elementsEqual(expected, Iterables.transform(result.getRows(), row -> row.getPath()))); + } + + @Test + public void testQueryWithAllowNodeAndDenySubNode() throws Exception { + // setup permissions for testuser using 2 aces such that + // - access to /node is granted + // - access to /node/subnode is denied + setupPermission(node.getPath(), testPrincipal, true, PrivilegeConstants.JCR_ALL); + setupPermission(subnode.getPath(), testPrincipal, false, PrivilegeConstants.JCR_ALL); + + assertAccess(node.getPath(), subnode.getPath(), true); + + // test that query result corresponds to the direct access (node readable, subnode not readable) + Result result = getTestRoot().getQueryEngine().executeQuery(getStatement(), Query.JCR_SQL2, Collections.emptyMap(), Collections.emptyMap()); + + Iterable expected = ImmutableSet.of(node.getPath()); + assertTrue(Iterables.elementsEqual(expected, Iterables.transform(result.getRows(), row -> row.getPath()))); + } + + private void assertAccess(@NotNull String nodePath, @NotNull String subnodePath, boolean canReadPrimaryType) throws Exception { + // verify access control setup + assertTrue(getTestRoot().getTree(nodePath).exists()); + assertFalse(getTestRoot().getTree(subnodePath).exists()); + + // verify PermissionProvider.isGranted(String, String) as it is used inside + // the query code base (FilterImpl.isAccessible) + PermissionProvider pp = getConfig(AuthorizationConfiguration.class).getPermissionProvider(getTestRoot(), getTestSession().getWorkspaceName(), getTestSession().getAuthInfo().getPrincipals()); + assertTrue(pp.isGranted(nodePath, Session.ACTION_READ)); + + assertEquals(canReadPrimaryType, pp.isGranted(nodePath + '/' + JcrConstants.JCR_PRIMARYTYPE, Session.ACTION_READ)); + assertEquals(canReadPrimaryType, pp.isGranted(nodePath + '/' + JcrConstants.JCR_PRIMARYTYPE, Permissions.getString(Permissions.READ_PROPERTY))); + + assertFalse(pp.isGranted(subnodePath, Session.ACTION_READ)); + assertFalse(pp.isGranted(subnodePath + '/' + JcrConstants.JCR_PRIMARYTYPE, Session.ACTION_READ)); + assertFalse(pp.isGranted(subnodePath + '/' + JcrConstants.JCR_PRIMARYTYPE, Permissions.getString(Permissions.READ_PROPERTY))); + } +} \ No newline at end of file Index: oak-core/src/test/java/org/apache/jackrabbit/oak/security/authorization/evaluation/IndexedQueryMixinTest.java IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== --- oak-core/src/test/java/org/apache/jackrabbit/oak/security/authorization/evaluation/IndexedQueryMixinTest.java (date 1548175366000) +++ oak-core/src/test/java/org/apache/jackrabbit/oak/security/authorization/evaluation/IndexedQueryMixinTest.java (date 1548175366000) @@ -0,0 +1,55 @@ +/* + * 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.security.authorization.evaluation; + +import org.apache.jackrabbit.oak.api.Tree; +import org.apache.jackrabbit.oak.plugins.index.IndexConstants; +import org.apache.jackrabbit.oak.plugins.index.IndexUtils; +import org.apache.jackrabbit.oak.plugins.tree.TreeUtil; +import org.apache.jackrabbit.oak.spi.nodetype.NodeTypeConstants; + +import javax.jcr.RepositoryException; + +import static org.junit.Assert.assertTrue; + +public class IndexedQueryMixinTest extends AbstractQueryTest { + + @Override + public void before() throws Exception { + super.before(); + + TreeUtil.addMixin(node, "mix:title", root.getTree(NodeTypeConstants.NODE_TYPES_PATH), "userId"); + node.setProperty("jcr:title", "title"); + TreeUtil.addMixin(subnode, "mix:title", root.getTree(NodeTypeConstants.NODE_TYPES_PATH), "userId"); + subnode.setProperty("jcr:title", "title"); + + grantPropertyReadAccess("jcr:title"); + + root.commit(); + } + + @Override + void createIndexDefinition() throws RepositoryException { + Tree oakIndex = root.getTree("/"+IndexConstants.INDEX_DEFINITIONS_NAME); + assertTrue(oakIndex.exists()); + IndexUtils.createIndexDefinition(oakIndex, "test-index", false, new String[] {"jcr:title"}, "mix:title"); + } + + String getStatement() { + return "SELECT * FROM [mix:title] WHERE [jcr:title] is not null"; + } +} \ No newline at end of file Index: oak-core/src/test/java/org/apache/jackrabbit/oak/security/authorization/evaluation/IndexedQueryTest.java IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== --- oak-core/src/test/java/org/apache/jackrabbit/oak/security/authorization/evaluation/IndexedQueryTest.java (date 1548175348000) +++ oak-core/src/test/java/org/apache/jackrabbit/oak/security/authorization/evaluation/IndexedQueryTest.java (date 1548175348000) @@ -0,0 +1,51 @@ +/* + * 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.security.authorization.evaluation; + +import org.apache.jackrabbit.oak.api.Tree; +import org.apache.jackrabbit.oak.plugins.index.IndexConstants; +import org.apache.jackrabbit.oak.plugins.index.IndexUtils; + +import javax.jcr.RepositoryException; + +import static org.junit.Assert.assertTrue; + +public class IndexedQueryTest extends AbstractQueryTest { + + @Override + public void before() throws Exception { + super.before(); + + node.setProperty("title", "a"); + subnode.setProperty("title", "b"); + + grantPropertyReadAccess("title"); + + root.commit(); + } + + @Override + void createIndexDefinition() throws RepositoryException { + Tree oakIndex = root.getTree("/"+IndexConstants.INDEX_DEFINITIONS_NAME); + assertTrue(oakIndex.exists()); + IndexUtils.createIndexDefinition(oakIndex, "test-index", false, new String[] {"title"}, "nt:unstructured"); + } + + String getStatement() { + return "SELECT * FROM [nt:unstructured] WHERE [title] is not null"; + } +} \ No newline at end of file Index: oak-core/src/test/java/org/apache/jackrabbit/oak/security/authorization/evaluation/TraversingQueryMixinTest.java IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== --- oak-core/src/test/java/org/apache/jackrabbit/oak/security/authorization/evaluation/TraversingQueryMixinTest.java (date 1548168288000) +++ oak-core/src/test/java/org/apache/jackrabbit/oak/security/authorization/evaluation/TraversingQueryMixinTest.java (date 1548168288000) @@ -0,0 +1,38 @@ +/* + * 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.security.authorization.evaluation; + +import org.apache.jackrabbit.oak.plugins.tree.TreeUtil; +import org.apache.jackrabbit.oak.spi.nodetype.NodeTypeConstants; + +import javax.jcr.RepositoryException; + +public class TraversingQueryMixinTest extends AbstractQueryTest { + + @Override + public void before() throws Exception { + super.before(); + + TreeUtil.addMixin(node, "mix:title", root.getTree(NodeTypeConstants.NODE_TYPES_PATH), "userId"); + TreeUtil.addMixin(subnode, "mix:title", root.getTree(NodeTypeConstants.NODE_TYPES_PATH), "userId"); + root.commit(); + } + + String getStatement() { + return "SELECT * FROM [mix:title] option (traversal ok)"; + } +} \ No newline at end of file Index: oak-core/src/test/java/org/apache/jackrabbit/oak/security/authorization/evaluation/TraversingQueryTest.java IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== --- oak-core/src/test/java/org/apache/jackrabbit/oak/security/authorization/evaluation/TraversingQueryTest.java (date 1548164807000) +++ oak-core/src/test/java/org/apache/jackrabbit/oak/security/authorization/evaluation/TraversingQueryTest.java (date 1548164807000) @@ -0,0 +1,55 @@ +/* + * 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.security.authorization.evaluation; + +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Iterables; +import org.apache.jackrabbit.JcrConstants; +import org.apache.jackrabbit.api.security.JackrabbitAccessControlList; +import org.apache.jackrabbit.commons.jackrabbit.authorization.AccessControlUtils; +import org.apache.jackrabbit.oak.api.QueryEngine; +import org.apache.jackrabbit.oak.api.Result; +import org.apache.jackrabbit.oak.api.Root; +import org.apache.jackrabbit.oak.api.Tree; +import org.apache.jackrabbit.oak.plugins.tree.TreeUtil; +import org.apache.jackrabbit.oak.spi.nodetype.NodeTypeConstants; +import org.apache.jackrabbit.oak.spi.security.authorization.AuthorizationConfiguration; +import org.apache.jackrabbit.oak.spi.security.authorization.accesscontrol.AccessControlConstants; +import org.apache.jackrabbit.oak.spi.security.authorization.permission.PermissionProvider; +import org.apache.jackrabbit.oak.spi.security.authorization.permission.Permissions; +import org.apache.jackrabbit.oak.spi.security.privilege.PrivilegeConstants; +import org.jetbrains.annotations.NotNull; +import org.junit.Before; +import org.junit.Test; + +import javax.jcr.Session; +import javax.jcr.Value; +import javax.jcr.query.Query; +import javax.jcr.security.AccessControlManager; +import java.util.Collections; +import java.util.Map; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +public class TraversingQueryTest extends AbstractQueryTest { + + String getStatement() { + return "SELECT * FROM [nt:unstructured] option (traversal ok)"; + } +} \ No newline at end of file Index: oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/session/NodeImpl.java IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== --- oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/session/NodeImpl.java (revision 1851744) +++ oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/session/NodeImpl.java (date 1548157422000) @@ -79,6 +79,7 @@ import org.apache.jackrabbit.oak.api.Tree; import org.apache.jackrabbit.oak.api.Tree.Status; import org.apache.jackrabbit.oak.api.Type; +import org.apache.jackrabbit.oak.commons.LazyValue; import org.apache.jackrabbit.oak.commons.PathUtils; import org.apache.jackrabbit.oak.jcr.delegate.NodeDelegate; import org.apache.jackrabbit.oak.jcr.delegate.PropertyDelegate; @@ -1281,31 +1282,27 @@ //------------------------------------------------------------< internal >--- @Nullable private String getPrimaryTypeName(@NotNull Tree tree) { - String primaryTypeName = null; - if (tree.hasProperty(JcrConstants.JCR_PRIMARYTYPE)) { - primaryTypeName = TreeUtil.getPrimaryTypeName(tree); - } else if (tree.getStatus() != Status.NEW) { - // OAK-2441: for backwards compatibility with Jackrabbit 2.x try to - // read the primary type from the underlying node state. - primaryTypeName = TreeUtil.getPrimaryTypeName(RootFactory.createReadOnlyRoot(sessionDelegate.getRoot()).getTree(tree.getPath())); - } - return primaryTypeName; + return TreeUtil.getPrimaryTypeName(tree, getReadOnlyTree(tree)); } @NotNull private Iterator getMixinTypeNames(@NotNull Tree tree) throws RepositoryException { - Iterator mixinNames = Collections.emptyIterator(); if (tree.hasProperty(JcrConstants.JCR_MIXINTYPES) || canReadMixinTypes(tree)) { - mixinNames = TreeUtil.getNames(tree, JcrConstants.JCR_MIXINTYPES).iterator(); - } else if (tree.getStatus() != Status.NEW) { - // OAK-2441: for backwards compatibility with Jackrabbit 2.x try to - // read the primary type from the underlying node state. - mixinNames = TreeUtil.getNames( - RootFactory.createReadOnlyRoot(sessionDelegate.getRoot()).getTree(tree.getPath()), - JcrConstants.JCR_MIXINTYPES).iterator(); + return TreeUtil.getMixinTypeNames(tree).iterator(); + } else { + return TreeUtil.getMixinTypeNames(tree, getReadOnlyTree(tree)).iterator(); } - return mixinNames; } + + @NotNull + private LazyValue getReadOnlyTree(@NotNull Tree tree) { + return new LazyValue() { + @Override + protected Tree createValue() { + return RootFactory.createReadOnlyRoot(sessionDelegate.getRoot()).getTree(tree.getPath()); + } + }; + } private boolean canReadMixinTypes(@NotNull Tree tree) throws RepositoryException { // OAK-7652: use an zero length MVP to check read permission on jcr:mixinTypes Index: oak-security-spi/src/main/java/org/apache/jackrabbit/oak/plugins/tree/package-info.java IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== --- oak-security-spi/src/main/java/org/apache/jackrabbit/oak/plugins/tree/package-info.java (revision 1851744) +++ oak-security-spi/src/main/java/org/apache/jackrabbit/oak/plugins/tree/package-info.java (date 1548157809000) @@ -14,7 +14,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -@Version("3.1.0") +@Version("3.2.0") package org.apache.jackrabbit.oak.plugins.tree; import org.osgi.annotation.versioning.Version; Index: oak-security-spi/src/main/java/org/apache/jackrabbit/oak/plugins/tree/TreeUtil.java IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== --- oak-security-spi/src/main/java/org/apache/jackrabbit/oak/plugins/tree/TreeUtil.java (revision 1851744) +++ oak-security-spi/src/main/java/org/apache/jackrabbit/oak/plugins/tree/TreeUtil.java (date 1548157445000) @@ -34,6 +34,7 @@ import org.apache.jackrabbit.oak.api.PropertyState; import org.apache.jackrabbit.oak.api.Tree; import org.apache.jackrabbit.oak.api.Type; +import org.apache.jackrabbit.oak.commons.LazyValue; import org.apache.jackrabbit.oak.commons.PathUtils; import org.apache.jackrabbit.oak.commons.UUIDUtils; import org.apache.jackrabbit.oak.plugins.memory.PropertyStates; @@ -87,6 +88,37 @@ return getStringInternal(tree, JcrConstants.JCR_PRIMARYTYPE, Type.NAME); } + @Nullable + public static String getPrimaryTypeName(@NotNull Tree tree, @NotNull LazyValue readOnlyTree) { + String primaryTypeName = null; + if (tree.hasProperty(JcrConstants.JCR_PRIMARYTYPE)) { + primaryTypeName = TreeUtil.getPrimaryTypeName(tree); + } else if (tree.getStatus() != Tree.Status.NEW) { + // OAK-2441: for backwards compatibility with Jackrabbit 2.x try to + // read the primary type from the underlying node state. + primaryTypeName = TreeUtil.getPrimaryTypeName(readOnlyTree.get()); + } + return primaryTypeName; + } + + @NotNull + public static Iterable getMixinTypeNames(@NotNull Tree tree) { + return TreeUtil.getNames(tree, JcrConstants.JCR_MIXINTYPES); + } + + @NotNull + public static Iterable getMixinTypeNames(@NotNull Tree tree, @NotNull LazyValue readOnlyTree) { + Iterable mixinNames = emptyList(); + if (tree.hasProperty(JcrConstants.JCR_MIXINTYPES)) { + mixinNames = getMixinTypeNames(tree); + } else if (tree.getStatus() != Tree.Status.NEW) { + // OAK-2441: for backwards compatibility with Jackrabbit 2.x try to + // read the primary type from the underlying node state. + mixinNames = TreeUtil.getNames(readOnlyTree.get(), JcrConstants.JCR_MIXINTYPES); + } + return mixinNames; + } + @Nullable public static Iterable getStrings(@NotNull Tree tree, @NotNull String propertyName) { PropertyState property = tree.getProperty(propertyName);