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 ed1310c55fb4d46fe90cdb764863e05dbbf903ff Author: dark <[email protected]> AuthorDate: Mon Aug 17 10:01:40 2026 +0800 feat(server): support GraphSpace-wide observer - persist observer access against the all-graphs target - apply read-only access to existing and future graphs - migrate and remove legacy graph-scoped observer grants - document the GraphSpace-wide contract in API version 0.72 --- .../org/apache/hugegraph/api/auth/ManagerAPI.java | 14 +++- .../apache/hugegraph/api/space/GraphSpaceAPI.java | 37 +++++++--- .../org/apache/hugegraph/version/ApiVersion.java | 2 +- .../hugegraph/auth/StandardAuthManagerV2.java | 10 ++- .../unit/api/space/GraphSpaceAPITest.java | 81 +++++++++++++++++++++- 5 files changed, 127 insertions(+), 17 deletions(-) diff --git a/hugegraph-server/hugegraph-api/src/main/java/org/apache/hugegraph/api/auth/ManagerAPI.java b/hugegraph-server/hugegraph-api/src/main/java/org/apache/hugegraph/api/auth/ManagerAPI.java index 37aee8c65..5989d4889 100644 --- a/hugegraph-server/hugegraph-api/src/main/java/org/apache/hugegraph/api/auth/ManagerAPI.java +++ b/hugegraph-server/hugegraph-api/src/main/java/org/apache/hugegraph/api/auth/ManagerAPI.java @@ -287,9 +287,8 @@ public class ManagerAPI extends API { defaultRole = null; // unreachable, satisfies compiler } validGraphSpace(manager, graphSpace); - boolean hasGraph = defaultRole.equals(HugeDefaultRole.OBSERVER); - E.checkArgument(!hasGraph || StringUtils.isNotEmpty(graph), - "Must set a graph for observer"); + boolean hasGraph = defaultRole.equals(HugeDefaultRole.OBSERVER) && + StringUtils.isNotEmpty(graph); if (hasGraph) { validGraph(manager, graphSpace, graph); } @@ -301,6 +300,15 @@ public class ManagerAPI extends API { } else { result = authManager.isDefaultRole(graphSpace, user, defaultRole); + if (!result && defaultRole.equals(HugeDefaultRole.OBSERVER)) { + for (String currentGraph : manager.graphs(graphSpace)) { + if (authManager.isDefaultRole( + graphSpace, currentGraph, user, defaultRole)) { + result = true; + break; + } + } + } } return manager.serializer().writeMap(ImmutableMap.of("check", result)); } diff --git a/hugegraph-server/hugegraph-api/src/main/java/org/apache/hugegraph/api/space/GraphSpaceAPI.java b/hugegraph-server/hugegraph-api/src/main/java/org/apache/hugegraph/api/space/GraphSpaceAPI.java index 81f13cf3f..934508ed3 100644 --- a/hugegraph-server/hugegraph-api/src/main/java/org/apache/hugegraph/api/space/GraphSpaceAPI.java +++ b/hugegraph-server/hugegraph-api/src/main/java/org/apache/hugegraph/api/space/GraphSpaceAPI.java @@ -146,10 +146,8 @@ public class GraphSpaceAPI extends API { throw new ForbiddenException("Forbidden to set role " + role.toString()); } - boolean hasGraph = role.equals(HugeDefaultRole.OBSERVER); - - E.checkArgument(!hasGraph || StringUtils.isNotEmpty(graph), - "Must set a graph for observer"); + boolean hasGraph = role.equals(HugeDefaultRole.OBSERVER) && + StringUtils.isNotEmpty(graph); if (hasGraph) { validGraph(manager, name, graph); } @@ -164,6 +162,12 @@ public class GraphSpaceAPI extends API { result.put("graph", graph); } else { authManager.createSpaceDefaultRole(name, user, role); + if (role.equals(HugeDefaultRole.OBSERVER)) { + for (String currentGraph : manager.graphs(name)) { + authManager.deleteDefaultRole( + name, user, role, currentGraph); + } + } } return manager.serializer().writeMap(result); @@ -203,9 +207,8 @@ public class GraphSpaceAPI extends API { defaultRole.equals(HugeDefaultRole.SPACE)) { throw new ForbiddenException("Forbidden to check role " + role); } - boolean hasGraph = defaultRole.equals(HugeDefaultRole.OBSERVER); - E.checkArgument(!hasGraph || StringUtils.isNotEmpty(graph), - "Must set a graph for observer"); + boolean hasGraph = defaultRole.equals(HugeDefaultRole.OBSERVER) && + StringUtils.isNotEmpty(graph); if (hasGraph) { validGraph(manager, name, graph); } @@ -217,6 +220,15 @@ public class GraphSpaceAPI extends API { } else { result = authManager.isDefaultRole(name, user, defaultRole); + if (!result && defaultRole.equals(HugeDefaultRole.OBSERVER)) { + for (String currentGraph : manager.graphs(name)) { + if (authManager.isDefaultRole( + name, currentGraph, user, defaultRole)) { + result = true; + break; + } + } + } } return manager.serializer().writeMap(ImmutableMap.of("check", result)); } @@ -259,9 +271,8 @@ public class GraphSpaceAPI extends API { E.checkArgument(false, "Invalid role value '%s'", role); defaultRole = null; // unreachable, satisfies compiler } - boolean hasGraph = defaultRole.equals(HugeDefaultRole.OBSERVER); - E.checkArgument(!hasGraph || StringUtils.isNotEmpty(graph), - "Must set a graph for observer"); + boolean hasGraph = defaultRole.equals(HugeDefaultRole.OBSERVER) && + StringUtils.isNotEmpty(graph); if (hasGraph) { validGraph(manager, name, graph); } @@ -269,6 +280,12 @@ public class GraphSpaceAPI extends API { authManager.deleteDefaultRole(name, user, defaultRole, graph); } else { authManager.deleteDefaultRole(name, user, defaultRole); + if (defaultRole.equals(HugeDefaultRole.OBSERVER)) { + for (String currentGraph : manager.graphs(name)) { + authManager.deleteDefaultRole( + name, user, defaultRole, currentGraph); + } + } } } diff --git a/hugegraph-server/hugegraph-api/src/main/java/org/apache/hugegraph/version/ApiVersion.java b/hugegraph-server/hugegraph-api/src/main/java/org/apache/hugegraph/version/ApiVersion.java index faadd1f5a..00e8dad03 100644 --- a/hugegraph-server/hugegraph-api/src/main/java/org/apache/hugegraph/version/ApiVersion.java +++ b/hugegraph-server/hugegraph-api/src/main/java/org/apache/hugegraph/version/ApiVersion.java @@ -121,7 +121,7 @@ public final class ApiVersion { * [0.69] Issue-1748: Support Cypher query RESTful API * [0.70] PR-2242: Add edge-existence RESTful API * [0.71] PR-2286: Support Arthas API & Metric API prometheus format - * [0.72] Support GraphSpace default-role management APIs + * [0.72] Support GraphSpace-wide default-role management APIs */ /** diff --git a/hugegraph-server/hugegraph-core/src/main/java/org/apache/hugegraph/auth/StandardAuthManagerV2.java b/hugegraph-server/hugegraph-core/src/main/java/org/apache/hugegraph/auth/StandardAuthManagerV2.java index 1f34aa459..aaf2a9df1 100644 --- a/hugegraph-server/hugegraph-core/src/main/java/org/apache/hugegraph/auth/StandardAuthManagerV2.java +++ b/hugegraph-server/hugegraph-core/src/main/java/org/apache/hugegraph/auth/StandardAuthManagerV2.java @@ -1815,7 +1815,10 @@ public class StandardAuthManagerV2 implements AuthManager { @Override public boolean isDefaultRole(String graphSpace, String owner, HugeDefaultRole role) { - return isDefaultRole(graphSpace, owner, role.toString()); + String roleName = role.isGraphRole() ? + getGraphDefaultRole(ALL_GRAPHS, role.toString()) : + role.toString(); + return isDefaultRole(graphSpace, owner, roleName); } @Override @@ -1828,7 +1831,10 @@ public class StandardAuthManagerV2 implements AuthManager { @Override public void deleteDefaultRole(String graphSpace, String owner, HugeDefaultRole role) { - deleteDefaultRoleByName(graphSpace, owner, role.toString()); + String roleName = role.isGraphRole() ? + getGraphDefaultRole(ALL_GRAPHS, role.toString()) : + role.toString(); + deleteDefaultRoleByName(graphSpace, owner, roleName); } @Override diff --git a/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/unit/api/space/GraphSpaceAPITest.java b/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/unit/api/space/GraphSpaceAPITest.java index caa659a4d..6315f3ae7 100644 --- a/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/unit/api/space/GraphSpaceAPITest.java +++ b/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/unit/api/space/GraphSpaceAPITest.java @@ -87,6 +87,81 @@ public class GraphSpaceAPITest extends BaseUnitTest { Assert.assertContains("\"check\":true", result); } + @Test + public void testAdminCanCheckSpaceWideObserverRole() { + GraphSpaceAPI api = new GraphSpaceAPI(); + GraphManager manager = managerWithDefaultRoleContext(ADMIN, true); + setContext(ADMIN); + + String result = api.checkDefaultRole(manager, GRAPHSPACE, TARGET, + "OBSERVER", null); + + Assert.assertContains("\"check\":true", result); + } + + @Test + public void testCurrentUserCanCheckSpaceWideObserverRole() { + ManagerAPI api = new ManagerAPI(); + GraphManager manager = managerWithDefaultRoleContext(TARGET, false); + setContext(TARGET); + + String result = api.checkDefaultRole(manager, GRAPHSPACE, + "OBSERVER", null); + + Assert.assertContains("\"check\":true", result); + } + + @Test + public void testCurrentUserObserverCheckFallsBackToLegacyGraphRole() { + ManagerAPI api = new ManagerAPI(); + GraphManager manager = managerWithDefaultRoleContext(TARGET, false); + AuthManager auth = manager.authManager(); + Mockito.when(auth.isDefaultRole( + GRAPHSPACE, TARGET, HugeDefaultRole.OBSERVER)) + .thenReturn(false); + setContext(TARGET); + + String result = api.checkDefaultRole(manager, GRAPHSPACE, + "OBSERVER", null); + + Assert.assertContains("\"check\":true", result); + Mockito.verify(auth).isDefaultRole( + GRAPHSPACE, GRAPH, TARGET, HugeDefaultRole.OBSERVER); + } + + @Test + public void testObserverCheckFallsBackToLegacyGraphRole() { + GraphSpaceAPI api = new GraphSpaceAPI(); + GraphManager manager = managerWithDefaultRoleContext(ADMIN, true); + AuthManager auth = manager.authManager(); + Mockito.when(auth.isDefaultRole( + GRAPHSPACE, TARGET, HugeDefaultRole.OBSERVER)) + .thenReturn(false); + setContext(ADMIN); + + String result = api.checkDefaultRole(manager, GRAPHSPACE, TARGET, + "OBSERVER", null); + + Assert.assertContains("\"check\":true", result); + Mockito.verify(auth).isDefaultRole( + GRAPHSPACE, GRAPH, TARGET, HugeDefaultRole.OBSERVER); + } + + @Test + public void testObserverDeleteCleansSpaceAndLegacyGraphRoles() { + GraphSpaceAPI api = new GraphSpaceAPI(); + GraphManager manager = managerWithDefaultRoleContext(ADMIN, true); + AuthManager auth = manager.authManager(); + setContext(ADMIN); + + api.deleteDefaultRole(manager, GRAPHSPACE, TARGET, "OBSERVER", null); + + Mockito.verify(auth).deleteDefaultRole( + GRAPHSPACE, TARGET, HugeDefaultRole.OBSERVER); + Mockito.verify(auth).deleteDefaultRole( + GRAPHSPACE, TARGET, HugeDefaultRole.OBSERVER, GRAPH); + } + @Test public void testManagerDefaultRoleRejectsMissingGraphSpace() { ManagerAPI api = new ManagerAPI(); @@ -191,6 +266,9 @@ public class GraphSpaceAPITest extends BaseUnitTest { Mockito.when(authManager.isDefaultRole(GRAPHSPACE, TARGET, HugeDefaultRole.SPACE)) .thenReturn(true); + Mockito.when(authManager.isDefaultRole(GRAPHSPACE, TARGET, + HugeDefaultRole.OBSERVER)) + .thenReturn(true); Mockito.when(authManager.findUser(TARGET)) .thenReturn(new HugeUser(TARGET)); @@ -206,7 +284,8 @@ public class GraphSpaceAPITest extends BaseUnitTest { MetaManager metaManager = Mockito.mock(MetaManager.class); Mockito.when(metaManager.graphConfigs(GRAPHSPACE)) - .thenReturn(Collections.emptyMap()); + .thenReturn(Collections.singletonMap( + GRAPHSPACE + "-" + GRAPH, Collections.emptyMap())); Whitebox.setInternalState(manager, "metaManager", metaManager); Map<String, Graph> graphs = new ConcurrentHashMap<>();
