This is an automated email from the ASF dual-hosted git repository. imbajin pushed a commit to branch cx/bump-server-api-version in repository https://gitbox.apache.org/repos/asf/hugegraph.git
commit 0752ec20af2230b884b50a7570b9eec96279654d Author: dark <[email protected]> AuthorDate: Mon Aug 17 23:39:13 2026 +0800 fix(server): enforce Gremlin mutations - classify mutation steps from Gremlin bytecode - require write access for add and property steps - require delete access for drop steps - cover read write delete and nested traversals --- .../apache/hugegraph/auth/HugeGraphAuthProxy.java | 34 ++++++++++++++++++ .../unit/auth/HugeGraphAuthProxyTest.java | 40 ++++++++++++++++++++++ 2 files changed, 74 insertions(+) diff --git a/hugegraph-server/hugegraph-api/src/main/java/org/apache/hugegraph/auth/HugeGraphAuthProxy.java b/hugegraph-server/hugegraph-api/src/main/java/org/apache/hugegraph/auth/HugeGraphAuthProxy.java index 4b0aed578..5b4e70460 100644 --- a/hugegraph-server/hugegraph-api/src/main/java/org/apache/hugegraph/auth/HugeGraphAuthProxy.java +++ b/hugegraph-server/hugegraph-api/src/main/java/org/apache/hugegraph/auth/HugeGraphAuthProxy.java @@ -22,6 +22,7 @@ import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Date; +import java.util.EnumSet; import java.util.Iterator; import java.util.List; import java.util.Map; @@ -95,6 +96,7 @@ import org.apache.tinkerpop.gremlin.process.traversal.Script; import org.apache.tinkerpop.gremlin.process.traversal.Traversal; import org.apache.tinkerpop.gremlin.process.traversal.TraversalStrategies; import org.apache.tinkerpop.gremlin.process.traversal.TraversalStrategy; +import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversal.Symbols; import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversalSource; import org.apache.tinkerpop.gremlin.process.traversal.translator.GroovyTranslator; import org.apache.tinkerpop.gremlin.structure.Edge; @@ -2414,6 +2416,11 @@ public final class HugeGraphAuthProxy implements HugeGraph { */ String caller = Thread.currentThread().getName(); if (!caller.contains(TraversalStrategiesProxy.REST_WORKER)) { + for (HugePermission permission : + traversalPermissions(traversal.getBytecode())) { + verifyNamePermission(permission, ResourceType.GREMLIN, + script); + } verifyNamePermission(HugePermission.EXECUTE, ResourceType.GREMLIN, script); } @@ -2461,4 +2468,31 @@ public final class HugeGraphAuthProxy implements HugeGraph { return this.origin.toString(); } } + + private static Set<HugePermission> traversalPermissions(Bytecode bytecode) { + Set<HugePermission> permissions = EnumSet.noneOf(HugePermission.class); + collectTraversalPermissions(bytecode, permissions); + return permissions; + } + + private static void collectTraversalPermissions( + Bytecode bytecode, + Set<HugePermission> permissions) { + for (Instruction instruction : bytecode.getStepInstructions()) { + String operator = instruction.getOperator(); + if (Symbols.addV.equals(operator) || + Symbols.addE.equals(operator) || + Symbols.property.equals(operator)) { + permissions.add(HugePermission.WRITE); + } else if (Symbols.drop.equals(operator)) { + permissions.add(HugePermission.DELETE); + } + for (Object argument : instruction.getArguments()) { + if (argument instanceof Bytecode) { + collectTraversalPermissions((Bytecode) argument, + permissions); + } + } + } + } } diff --git a/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/unit/auth/HugeGraphAuthProxyTest.java b/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/unit/auth/HugeGraphAuthProxyTest.java index 1b209c913..7b1ae32e2 100644 --- a/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/unit/auth/HugeGraphAuthProxyTest.java +++ b/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/unit/auth/HugeGraphAuthProxyTest.java @@ -19,13 +19,16 @@ package org.apache.hugegraph.unit.auth; import java.lang.reflect.Method; import java.util.ArrayList; +import java.util.Collections; import java.util.List; +import java.util.Set; import org.apache.hugegraph.HugeGraph; import org.apache.hugegraph.auth.AuthManager; import org.apache.hugegraph.auth.HugeAuthenticator; import org.apache.hugegraph.auth.HugeDefaultRole; import org.apache.hugegraph.auth.HugeGraphAuthProxy; +import org.apache.hugegraph.auth.HugePermission; import org.apache.hugegraph.auth.RolePermission; import org.apache.hugegraph.auth.UserWithRole; import org.apache.hugegraph.backend.id.IdGenerator; @@ -44,6 +47,8 @@ import org.apache.logging.log4j.core.LoggerContext; import org.apache.logging.log4j.core.appender.AbstractAppender; import org.apache.logging.log4j.core.config.LoggerConfig; import org.apache.logging.log4j.core.config.Property; +import org.apache.tinkerpop.gremlin.process.traversal.Bytecode; +import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversal.Symbols; import org.junit.After; import org.junit.Test; import org.mockito.Mockito; @@ -366,6 +371,41 @@ public class HugeGraphAuthProxyTest extends BaseUnitTest { } } + @Test + public void testTraversalPermissions() throws Exception { + Bytecode read = new Bytecode(); + read.addStep(Symbols.V); + Assert.assertTrue(traversalPermissions(read).isEmpty()); + + Bytecode write = new Bytecode(); + write.addStep(Symbols.addV, "person"); + write.addStep(Symbols.property, "name", "marko"); + Assert.assertEquals(Collections.singleton(HugePermission.WRITE), + traversalPermissions(write)); + + Bytecode delete = new Bytecode(); + delete.addStep(Symbols.V); + delete.addStep(Symbols.drop); + Assert.assertEquals(Collections.singleton(HugePermission.DELETE), + traversalPermissions(delete)); + + Bytecode nested = new Bytecode(); + nested.addStep(Symbols.addE, "knows"); + Bytecode parent = new Bytecode(); + parent.addStep(Symbols.sideEffect, nested); + Assert.assertEquals(Collections.singleton(HugePermission.WRITE), + traversalPermissions(parent)); + } + + @SuppressWarnings("unchecked") + private static Set<HugePermission> traversalPermissions(Bytecode bytecode) + throws Exception { + Method method = HugeGraphAuthProxy.class.getDeclaredMethod( + "traversalPermissions", Bytecode.class); + method.setAccessible(true); + return (Set<HugePermission>) method.invoke(null, bytecode); + } + private static class TestAppender extends AbstractAppender { private final List<LogEvent> events;
