This is an automated email from the ASF dual-hosted git repository.

kezhuw pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/curator.git


The following commit(s) were added to refs/heads/master by this push:
     new 9fe79d81 CURATOR-715: Check node existence bottom up in 
ZkPaths::mkdirs (#506)
9fe79d81 is described below

commit 9fe79d81a8c5f2484bc29087024b5d65c8fba37f
Author: Pablo Francisco PĂ©rez Hidalgo <[email protected]>
AuthorDate: Thu Sep 12 17:12:06 2024 +0200

    CURATOR-715: Check node existence bottom up in ZkPaths::mkdirs (#506)
    
    ZOOKEEPER-2590 enforces read ACL permission for check().
    
    When creating parents if needed, Apache Curator client checks the existence
    of all nodes in the path from the root node to the created one. However,
    this is not necessary, it is enough to check the existence of the nodes
    between the new node and the first existing ancestor.
    
    There are use cases where the first levels of a sub-tree are protected 
against
    read through ACLs. The current implementation makes it impossible to use
    `creatingParentsIfNeeded`.
    
    This pr also bump ZooKeeper to 3.9.2 which ZOOKEEPER-2590 is shipped.
---
 .../java/org/apache/curator/utils/ZKPaths.java     | 43 ++++++++++++++--------
 .../apache/curator/framework/imps/TestCreate.java  | 35 ++++++++++++++++--
 pom.xml                                            |  2 +-
 3 files changed, 61 insertions(+), 19 deletions(-)

diff --git a/curator-client/src/main/java/org/apache/curator/utils/ZKPaths.java 
b/curator-client/src/main/java/org/apache/curator/utils/ZKPaths.java
index 5364ab5c..2bc47544 100644
--- a/curator-client/src/main/java/org/apache/curator/utils/ZKPaths.java
+++ b/curator-client/src/main/java/org/apache/curator/utils/ZKPaths.java
@@ -289,7 +289,20 @@ public class ZKPaths {
             throws InterruptedException, KeeperException {
         PathUtils.validatePath(path);
 
-        int pos = 1; // skip first slash, root is guaranteed to exist
+        int pos = path.length();
+        String subPath;
+
+        // This first loop locates the first parent that doesn't exist from 
leaf nodes towards root
+        // this way, it is not required to have read access on all parents.
+        // This is relevant after 
https://issues.apache.org/jira/browse/ZOOKEEPER-2590.
+
+        do {
+            pos = path.lastIndexOf(PATH_SEPARATOR_CHAR, pos - 1);
+            subPath = path.substring(0, pos);
+        } while (pos > 0 && zookeeper.exists(subPath, false) == null);
+
+        // Start creating the subtree after the longest path that exists, 
assuming root always exists.
+
         do {
             pos = path.indexOf(PATH_SEPARATOR_CHAR, pos + 1);
 
@@ -301,23 +314,23 @@ public class ZKPaths {
                 }
             }
 
-            String subPath = path.substring(0, pos);
-            if (zookeeper.exists(subPath, false) == null) {
-                try {
-                    List<ACL> acl = null;
-                    if (aclProvider != null) {
-                        acl = aclProvider.getAclForPath(subPath);
-                        if (acl == null) {
-                            acl = aclProvider.getDefaultAcl();
-                        }
-                    }
+            subPath = path.substring(0, pos);
+
+            // All the paths from the initial `pos` do not exist.
+            try {
+                List<ACL> acl = null;
+                if (aclProvider != null) {
+                    acl = aclProvider.getAclForPath(subPath);
                     if (acl == null) {
-                        acl = ZooDefs.Ids.OPEN_ACL_UNSAFE;
+                        acl = aclProvider.getDefaultAcl();
                     }
-                    zookeeper.create(subPath, new byte[0], acl, 
getCreateMode(asContainers));
-                } catch (KeeperException.NodeExistsException ignore) {
-                    // ignore... someone else has created it since we checked
                 }
+                if (acl == null) {
+                    acl = ZooDefs.Ids.OPEN_ACL_UNSAFE;
+                }
+                zookeeper.create(subPath, new byte[0], acl, 
getCreateMode(asContainers));
+            } catch (KeeperException.NodeExistsException ignore) {
+                // ignore... someone else has created it since we checked
             }
 
         } while (pos < path.length());
diff --git 
a/curator-framework/src/test/java/org/apache/curator/framework/imps/TestCreate.java
 
b/curator-framework/src/test/java/org/apache/curator/framework/imps/TestCreate.java
index fc866a89..56be2a7b 100644
--- 
a/curator-framework/src/test/java/org/apache/curator/framework/imps/TestCreate.java
+++ 
b/curator-framework/src/test/java/org/apache/curator/framework/imps/TestCreate.java
@@ -20,9 +20,7 @@
 package org.apache.curator.framework.imps;
 
 import static org.apache.zookeeper.ZooDefs.Ids.ANYONE_ID_UNSAFE;
-import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertFalse;
-import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.junit.jupiter.api.Assertions.*;
 import java.util.Collections;
 import java.util.List;
 import java.util.Optional;
@@ -597,4 +595,35 @@ public class TestCreate extends BaseClassForTests {
         path = ZKPaths.makePath("hola", name);
         assertEquals(ProtectedUtils.normalizePath(path), "/hola/yo");
     }
+
+    /**
+     * Tests that parents existence checks don't need READ access to the whole 
path (from / to the new node)
+     * but just to the first ancestor parent. (See 
https://issues.apache.org/jira/browse/ZOOKEEPER-2590)
+     */
+    @Test
+    public void testForbiddenAncestors() throws Exception {
+        CuratorFramework client = createClient(testACLProvider);
+        try {
+            client.start();
+
+            client.create().creatingParentsIfNeeded().forPath("/bat/bi/hiru");
+            client.setACL()
+                    .withACL(Collections.singletonList(new ACL(0, 
ANYONE_ID_UNSAFE)))
+                    .forPath("/bat");
+
+            // In creation attempts where the parent ("/bat") has ACL that 
restricts read, creation request fails.
+            try {
+                client.create().creatingParentsIfNeeded().forPath("/bat/bost");
+                fail("Expected NoAuthException when not authorized to read new 
node grandparent");
+            } catch (KeeperException.NoAuthException noAuthException) {
+            }
+
+            // But creating a node in the same subtree where its grandparent 
has read access is allowed and
+            // Curator will not check for higher nodes' existence.
+            
client.create().creatingParentsIfNeeded().forPath("/bat/bi/hiru/bost");
+            assertNotNull(client.checkExists().forPath("/bat/bi/hiru/bost"));
+        } finally {
+            CloseableUtils.closeQuietly(client);
+        }
+    }
 }
diff --git a/pom.xml b/pom.xml
index e9c99290..a2ef3c2c 100644
--- a/pom.xml
+++ b/pom.xml
@@ -72,7 +72,7 @@
         <redirectTestOutputToFile>true</redirectTestOutputToFile>
 
         <!-- versions -->
-        <zookeeper-version>3.9.1</zookeeper-version>
+        <zookeeper-version>3.9.2</zookeeper-version>
         <maven-bundle-plugin-version>5.1.4</maven-bundle-plugin-version>
         <maven-compiler-plugin-version>3.10.0</maven-compiler-plugin-version>
         
<maven-dependency-plugin-version>3.2.0</maven-dependency-plugin-version>

Reply via email to