Index: oak-core/src/test/java/org/apache/jackrabbit/oak/security/authorization/evaluation/QueryTest.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/QueryTest.java (date 1548085291000) +++ oak-core/src/test/java/org/apache/jackrabbit/oak/security/authorization/evaluation/QueryTest.java (date 1548085291000) @@ -0,0 +1,147 @@ +/* + * 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.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 QueryTest extends AbstractOakCoreTest { + + private static final String STMT = "SELECT * FROM [nt:unstructured] option (traversal ok)"; + + @Test + public void testQueryWithEmptyGlobRestriction() throws Exception { + Tree node = TreeUtil.addChild(root.getTree("/"), "node", NodeTypeConstants.NT_UNSTRUCTURED); + Tree subnode = TreeUtil.addChild(node, "subnode", NodeTypeConstants.NT_UNSTRUCTURED); + + // 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()); + + // test that query result corresponds to the direct access (node readable, subnode not readable) + Result result = getTestRoot().getQueryEngine().executeQuery(STMT, 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 { + Tree node = TreeUtil.addChild(root.getTree("/"), "node", NodeTypeConstants.NT_UNSTRUCTURED); + Tree subnode = TreeUtil.addChild(node, "subnode", NodeTypeConstants.NT_UNSTRUCTURED); + + // 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()); + + // test that query result corresponds to the direct access (node readable, subnode not readable) + Result result = getTestRoot().getQueryEngine().executeQuery(STMT, 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 { + Tree node = TreeUtil.addChild(root.getTree("/"), "node", NodeTypeConstants.NT_UNSTRUCTURED); + Tree subnode = TreeUtil.addChild(node, "subnode", NodeTypeConstants.NT_UNSTRUCTURED); + + // 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()); + + // test that query result corresponds to the direct access (node readable, subnode not readable) + Result result = getTestRoot().getQueryEngine().executeQuery(STMT, 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) 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)); + + // TODO: uncomment to illustrate missing access on jcr:primaryType +// assertTrue(pp.isGranted(nodePath + '/' + JcrConstants.JCR_PRIMARYTYPE, Session.ACTION_READ)); +// assertTrue(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