diff --git a/.editorconfig b/.editorconfig
index cf79877b0..4a6e05ecb 100644
--- a/.editorconfig
+++ b/.editorconfig
@@ -21,9 +21,9 @@ root = true
charset = utf-8
end_of_line = lf
insert_final_newline = true
-max_line_length = 100
+max_line_length = 120
ij_wrap_on_typing = true
-ij_visual_guides = 100
+ij_visual_guides = 120
[*.{java,xml,py}]
diff --git a/.github/actions/upload-coverage/action.yml b/.github/actions/upload-coverage/action.yml
index 143da955b..bf8ba8cd8 100644
--- a/.github/actions/upload-coverage/action.yml
+++ b/.github/actions/upload-coverage/action.yml
@@ -17,4 +17,5 @@ runs:
uses: codecov/codecov-action@v7
with:
token: ${{ inputs.token }}
+ slug: ${{ github.repository }}
files: ${{ inputs.file }}
diff --git a/.github/workflows/client-ci.yml b/.github/workflows/client-ci.yml
index e076dd256..90d7e8110 100644
--- a/.github/workflows/client-ci.yml
+++ b/.github/workflows/client-ci.yml
@@ -68,4 +68,4 @@ jobs:
uses: ./.github/actions/upload-coverage
with:
token: ${{ secrets.CODECOV_TOKEN }}
- file: target/jacoco.xml
+ file: hugegraph-client/target/jacoco.xml
diff --git a/.github/workflows/hubble-ci.yml b/.github/workflows/hubble-ci.yml
index 5445dbb0b..ae7290aa4 100644
--- a/.github/workflows/hubble-ci.yml
+++ b/.github/workflows/hubble-ci.yml
@@ -23,8 +23,9 @@ on:
env:
TRAVIS_DIR: hugegraph-hubble/hubble-dist/assembly/travis
- HUGEGRAPH_SERVER_COMMIT: 99936be5f41fccd193f120e01206e3cf3c73a050
- HUGEGRAPH_SERVER_FETCH_REF: refs/heads/master
+ # Server PR #3159 declares the GraphSpace default-role contract as API 0.72.
+ HUGEGRAPH_SERVER_COMMIT: 52035dad9ee8d6b666329ca0d03950c773d3e1eb
+ HUGEGRAPH_SERVER_FETCH_REF: refs/pull/3159/head
jobs:
hubble-ci:
@@ -138,6 +139,16 @@ jobs:
cd ../../../
pwd
$TRAVIS_DIR/install-hugegraph.sh $COMMIT_ID $COMMIT_REF
+ API_VERSION="$(curl --fail --silent http://127.0.0.1:8080/versions |
+ python -c 'import json,sys; print(json.load(sys.stdin)["versions"]["api"])')"
+ python - "$API_VERSION" <<'PY'
+ import sys
+ parts = tuple(int(value) for value in sys.argv[1].split(".")[:2])
+ if parts < (0, 72):
+ raise SystemExit(
+ f"Expected HugeGraph REST API >= 0.72, got {sys.argv[1]}"
+ )
+ PY
- name: Release package audit
env:
@@ -197,4 +208,4 @@ jobs:
uses: ./.github/actions/upload-coverage
with:
token: ${{ secrets.CODECOV_TOKEN }}
- file: target/site/jacoco/*.xml
+ file: hugegraph-hubble/hubble-be/target/jacoco.xml
diff --git a/.github/workflows/loader-ci.yml b/.github/workflows/loader-ci.yml
index f8eee02d3..8389ecab6 100644
--- a/.github/workflows/loader-ci.yml
+++ b/.github/workflows/loader-ci.yml
@@ -94,4 +94,4 @@ jobs:
uses: ./.github/actions/upload-coverage
with:
token: ${{ secrets.CODECOV_TOKEN }}
- file: target/jacoco.xml
+ file: hugegraph-loader/target/jacoco.xml
diff --git a/.github/workflows/tools-ci.yml b/.github/workflows/tools-ci.yml
index 340f71aa1..4613bcd0f 100644
--- a/.github/workflows/tools-ci.yml
+++ b/.github/workflows/tools-ci.yml
@@ -59,9 +59,3 @@ jobs:
- name: Run test
run: |
mvn test -Dtest=FuncTestSuite -pl hugegraph-tools -ntp
-
- - name: Upload coverage to Codecov
- uses: ./.github/actions/upload-coverage
- with:
- token: ${{ secrets.CODECOV_TOKEN }}
- file: target/jacoco.xml
diff --git a/.serena/memories/README_INDEX.md b/.serena/memories/README_INDEX.md
index 3f506906f..4f5dd6806 100644
--- a/.serena/memories/README_INDEX.md
+++ b/.serena/memories/README_INDEX.md
@@ -212,7 +212,7 @@ git --no-pager diff HEAD~1
4. ❌ **DON'T** use `System.out.println` (use logger instead)
5. ❌ **DON'T** forget Apache 2.0 license headers
6. ❌ **DON'T** use tabs (use 4 spaces for Java, 2 for frontend)
-7. ❌ **DON'T** exceed 100 character line length
+7. ❌ **DON'T** exceed 120 character line length
8. ❌ **DON'T** commit code that fails CI checks
## Getting Help
diff --git a/.serena/memories/code_style_and_conventions.md b/.serena/memories/code_style_and_conventions.md
index 0c14759c6..594063a5f 100644
--- a/.serena/memories/code_style_and_conventions.md
+++ b/.serena/memories/code_style_and_conventions.md
@@ -12,7 +12,7 @@
### Basic Formatting
- **Indentation**: 4 spaces (NO TABS)
- **Continuation Indent**: 8 spaces
-- **Line Length**: Maximum 100 characters
+- **Line Length**: Maximum 120 characters
- **Line Wrapping**: Enabled for long lines
- **Blank Lines**:
- Keep max 1 blank line in declarations
diff --git a/AGENTS.md b/AGENTS.md
index 4aa6cceaa..9f8ca9eb7 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -79,7 +79,7 @@ mvn test -Dtest=FuncTestSuite -pl hugegraph-tools -ntp
## Code Style
Checkstyle enforced via `tools/checkstyle.xml`:
-- Max line length: 100 characters
+- Max line length: 120 characters
- 4-space indentation (no tabs)
- No star imports
- No `System.out.println`
diff --git a/README.md b/README.md
index 02c1bb719..bdc68a7cd 100644
--- a/README.md
+++ b/README.md
@@ -12,6 +12,13 @@
A comprehensive suite of client SDKs, data tools, and management utilities for [Apache HugeGraph](https://github.com/apache/hugegraph) graph database. Build applications, load data, and manage graphs with production-ready tools.
+Hubble's primary authentication and connection design targets HugeGraph
+`1.8/master`: PD discovery supplies the server address, anonymous mode uses a
+real unauthenticated client, and account/GraphSpace permissions are reduced to
+four readable presets. A thin adapter keeps 1.7 usable and limits 1.5 to its
+standalone core graph workflow; version checks are centralized rather than
+spread across UI pages.
+
**Quick Navigation**: [Architecture](#architecture-overview) | [Quick Start](#quick-start) | [Modules](#module-overview) | [Build](#build--development) | [Docker](#docker) | [Related Projects](#related-projects)
## Related Projects
@@ -57,7 +64,7 @@ graph TB
CLIENT --> HUBBLE
CLIENT --> TOOLS
CLIENT --> SPARK
- HUBBLE -.->|WIP: pd-client| PD
+ HUBBLE -.->|PD discovery UI| PD
LOADER -.->|Sources| SRC["CSV | JSON | HDFS MySQL | Kafka"]
SPARK -.->|I/O| SPK["Spark DataFrames"]
@@ -381,7 +388,7 @@ mvn clean install -DskipTests -Dmaven.javadoc.skip=true -ntp
### Code Style
Checkstyle is enforced via `tools/checkstyle.xml`:
-- Max line length: 100 characters
+- Max line length: 120 characters
- 4-space indentation (no tabs)
- No star imports
- No `System.out.println`
diff --git a/hugegraph-client/src/main/java/org/apache/hugegraph/driver/AuthManager.java b/hugegraph-client/src/main/java/org/apache/hugegraph/driver/AuthManager.java
index af69f518b..f8cc35216 100644
--- a/hugegraph-client/src/main/java/org/apache/hugegraph/driver/AuthManager.java
+++ b/hugegraph-client/src/main/java/org/apache/hugegraph/driver/AuthManager.java
@@ -385,6 +385,11 @@ public boolean isSpaceAdmin(String graphSpace) {
.checkPermission(HugePermission.SPACE, graphSpace);
}
+ public boolean isSpaceMember(String graphSpace) {
+ return this.managerAPI(graphSpace)
+ .checkPermission(HugePermission.SPACE_MEMBER, graphSpace);
+ }
+
public boolean checkDefaultRole(String graphSpace, String role) {
return this.managerAPI(graphSpace)
.checkDefaultRole(graphSpace, role, "");
diff --git a/hugegraph-client/src/main/java/org/apache/hugegraph/driver/HugeClient.java b/hugegraph-client/src/main/java/org/apache/hugegraph/driver/HugeClient.java
index 3f26091f3..c314405f9 100644
--- a/hugegraph-client/src/main/java/org/apache/hugegraph/driver/HugeClient.java
+++ b/hugegraph-client/src/main/java/org/apache/hugegraph/driver/HugeClient.java
@@ -22,8 +22,12 @@
import lombok.Getter;
import org.apache.hugegraph.client.RestClient;
+import org.apache.hugegraph.exception.ServerException;
import org.apache.hugegraph.rest.ClientException;
import org.apache.hugegraph.rest.RestClientConfig;
+import org.apache.hugegraph.structure.auth.TokenPayload;
+import org.apache.hugegraph.structure.auth.User;
+import org.apache.hugegraph.util.E;
import org.apache.hugegraph.util.VersionUtil;
import org.apache.hugegraph.version.ClientVersion;
import org.slf4j.Logger;
@@ -57,6 +61,8 @@ public class HugeClient implements Closeable {
*/
private volatile boolean apiVersionChecked;
private final Object apiVersionLock = new Object();
+ private ServerCompatibility.Profile compatibility =
+ ServerCompatibility.Profile.LEGACY;
private VersionManager version;
private GraphsManager graphs;
private SchemaManager schema;
@@ -209,8 +215,9 @@ private void checkServerApiVersion() {
// 0.81 equals to the {latest_api_version} +10
VersionUtil.check(apiVersion, "0.38", "0.81", "hugegraph-api in server");
this.client.apiVersion(apiVersion);
- boolean supportGs = VersionUtil.gte(this.version.getCoreVersion(), "1.7.0");
- this.client.setSupportGs(supportGs);
+ this.compatibility = ServerCompatibility.profile(
+ this.version.getCoreVersion(), apiVersion.get());
+ this.client.setSupportGs(this.compatibility.supportsGraphSpace());
}
public GraphsManager graphs() {
@@ -257,6 +264,50 @@ public AuthManager auth() {
return this.auth;
}
+ public boolean supportsDefaultRole() {
+ return this.compatibility.supportsDefaultRole();
+ }
+
+ public boolean supportsPersonalProfileUpdate() {
+ return this.compatibility.supportsPersonalProfileUpdate();
+ }
+
+ public User findUserByName(String name) {
+ if (this.supportsDefaultRole()) {
+ return this.auth.getUserByName(name);
+ }
+ return this.auth.listUsers().stream()
+ .filter(user -> name.equals(user.name()))
+ .findFirst()
+ .orElse(null);
+ }
+
+ public User findCurrentUser(String expectedUsername) {
+ TokenPayload payload = this.auth.verifyToken();
+ E.checkState(payload != null &&
+ !Strings.isNullOrEmpty(payload.userId()) &&
+ !Strings.isNullOrEmpty(payload.username()),
+ "Invalid current-user identity");
+ E.checkState(payload.username().equals(expectedUsername),
+ "Authenticated user does not match the expected user");
+
+ User user;
+ try {
+ user = this.auth.getUser(payload.userId());
+ } catch (ServerException e) {
+ if (e.status() != 403 ||
+ this.supportsPersonalProfileUpdate()) {
+ throw e;
+ }
+ user = new User();
+ user.setId(payload.userId());
+ user.name(payload.username());
+ }
+ E.checkState(user != null && expectedUsername.equals(user.name()),
+ "Current-user record does not match the token identity");
+ return user;
+ }
+
public MetricsManager metrics() {
return this.metrics;
}
diff --git a/hugegraph-client/src/main/java/org/apache/hugegraph/driver/ServerCompatibility.java b/hugegraph-client/src/main/java/org/apache/hugegraph/driver/ServerCompatibility.java
new file mode 100644
index 000000000..ac329aacf
--- /dev/null
+++ b/hugegraph-client/src/main/java/org/apache/hugegraph/driver/ServerCompatibility.java
@@ -0,0 +1,111 @@
+/*
+ * 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.hugegraph.driver;
+
+import org.apache.hugegraph.util.VersionUtil;
+
+/**
+ * Small compatibility boundary shared by Hubble and clients.
+ *
+ *
Version checks belong here so callers can express capabilities instead
+ * of branching on server versions in controllers or pages. Unknown versions
+ * deliberately use the conservative legacy profile.
+ */
+public final class ServerCompatibility {
+
+ private static final String GRAPHSPACE_MIN_VERSION = "1.7.0";
+ private static final String DEFAULT_ROLE_MIN_API_VERSION = "0.72";
+
+ private ServerCompatibility() {
+ }
+
+ public static Profile profile(String coreVersion) {
+ return profile(coreVersion, null);
+ }
+
+ public static Profile profile(String coreVersion, String apiVersion) {
+ if (supportsDefaultRoleApi(apiVersion)) {
+ return Profile.MODERN;
+ }
+ if (coreVersion == null || coreVersion.trim().isEmpty()) {
+ return Profile.LEGACY;
+ }
+ try {
+ String normalized = coreVersion.trim();
+ return VersionUtil.gte(normalized, GRAPHSPACE_MIN_VERSION) ? Profile.GRAPHSPACE : Profile.LEGACY;
+ } catch (RuntimeException ignored) {
+ return Profile.LEGACY;
+ }
+ }
+
+ private static boolean supportsDefaultRoleApi(String apiVersion) {
+ if (apiVersion == null || apiVersion.trim().isEmpty()) {
+ return false;
+ }
+ try {
+ return VersionUtil.gte(apiVersion.trim(),
+ DEFAULT_ROLE_MIN_API_VERSION);
+ } catch (RuntimeException ignored) {
+ return false;
+ }
+ }
+
+ public static boolean supportsGraphSpace(String coreVersion) {
+ return profile(coreVersion).supportsGraphSpace();
+ }
+
+ public static boolean supportsDefaultRole(String coreVersion,
+ String apiVersion) {
+ return profile(coreVersion, apiVersion).supportsDefaultRole();
+ }
+
+ public static boolean supportsPersonalProfileUpdate(
+ String coreVersion, String apiVersion) {
+ return profile(coreVersion, apiVersion)
+ .supportsPersonalProfileUpdate();
+ }
+
+ public enum Profile {
+ LEGACY(false, false, false),
+ GRAPHSPACE(true, false, false),
+ MODERN(true, true, true);
+
+ private final boolean graphSpace;
+ private final boolean defaultRole;
+ private final boolean personalProfileUpdate;
+
+ Profile(boolean graphSpace, boolean defaultRole,
+ boolean personalProfileUpdate) {
+ this.graphSpace = graphSpace;
+ this.defaultRole = defaultRole;
+ this.personalProfileUpdate = personalProfileUpdate;
+ }
+
+ public boolean supportsGraphSpace() {
+ return this.graphSpace;
+ }
+
+ public boolean supportsDefaultRole() {
+ return this.defaultRole;
+ }
+
+ public boolean supportsPersonalProfileUpdate() {
+ return this.personalProfileUpdate;
+ }
+ }
+}
diff --git a/hugegraph-client/src/test/java/org/apache/hugegraph/driver/HugeClientCompatibilityTest.java b/hugegraph-client/src/test/java/org/apache/hugegraph/driver/HugeClientCompatibilityTest.java
new file mode 100644
index 000000000..6e14e4ecf
--- /dev/null
+++ b/hugegraph-client/src/test/java/org/apache/hugegraph/driver/HugeClientCompatibilityTest.java
@@ -0,0 +1,174 @@
+/*
+ * 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.hugegraph.driver;
+
+import java.util.Arrays;
+
+import org.apache.hugegraph.exception.ServerException;
+import org.apache.hugegraph.structure.auth.TokenPayload;
+import org.apache.hugegraph.structure.auth.User;
+import org.apache.hugegraph.testutil.Whitebox;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mockito;
+
+public class HugeClientCompatibilityTest {
+
+ private HugeClient client;
+ private AuthManager auth;
+
+ @Before
+ public void setup() {
+ this.client = Mockito.mock(HugeClient.class,
+ Mockito.CALLS_REAL_METHODS);
+ this.auth = Mockito.mock(AuthManager.class);
+ Whitebox.setInternalState(this.client, "auth", this.auth);
+ }
+
+ @Test
+ public void shouldUseDirectLookupForModernServers() {
+ User alice = user("alice");
+ Whitebox.setInternalState(
+ this.client, "compatibility",
+ ServerCompatibility.Profile.MODERN);
+ Mockito.when(this.auth.getUserByName("alice")).thenReturn(alice);
+
+ Assert.assertTrue(this.client.supportsDefaultRole());
+ Assert.assertSame(alice, this.client.findUserByName("alice"));
+ Mockito.verify(this.auth, Mockito.never()).listUsers();
+ }
+
+ @Test
+ public void shouldSearchLegacyUserListsByName() {
+ User alice = user("alice");
+ User bob = user("bob");
+ Whitebox.setInternalState(
+ this.client, "compatibility",
+ ServerCompatibility.Profile.GRAPHSPACE);
+ Mockito.when(this.auth.listUsers())
+ .thenReturn(Arrays.asList(bob, alice));
+
+ Assert.assertFalse(this.client.supportsDefaultRole());
+ Assert.assertSame(alice, this.client.findUserByName("alice"));
+ Assert.assertNull(this.client.findUserByName("missing"));
+ Mockito.verify(this.auth, Mockito.never())
+ .getUserByName(Mockito.anyString());
+ }
+
+ @Test
+ public void shouldFindCurrentUserFromVerifiedTokenIdentity() {
+ TokenPayload payload = Mockito.mock(TokenPayload.class);
+ User alice = user("alice");
+ Mockito.when(payload.userId()).thenReturn("user-id");
+ Mockito.when(payload.username()).thenReturn("alice");
+ Mockito.when(this.auth.verifyToken()).thenReturn(payload);
+ Mockito.when(this.auth.getUser("user-id")).thenReturn(alice);
+
+ Assert.assertSame(alice, this.client.findCurrentUser("alice"));
+ Mockito.verify(this.auth, Mockito.never()).listUsers();
+ Mockito.verify(this.auth, Mockito.never())
+ .getUserByName(Mockito.anyString());
+ }
+
+ @Test
+ public void shouldUseVerifiedIdentityWhenLegacySelfReadIsForbidden() {
+ TokenPayload payload = Mockito.mock(TokenPayload.class);
+ ServerException forbidden = new ServerException("forbidden");
+ forbidden.status(403);
+ Whitebox.setInternalState(
+ this.client, "compatibility",
+ ServerCompatibility.Profile.GRAPHSPACE);
+ Mockito.when(payload.userId()).thenReturn("user-id");
+ Mockito.when(payload.username()).thenReturn("alice");
+ Mockito.when(this.auth.verifyToken()).thenReturn(payload);
+ Mockito.when(this.auth.getUser("user-id")).thenThrow(forbidden);
+
+ User user = this.client.findCurrentUser("alice");
+
+ Assert.assertEquals("user-id", user.id());
+ Assert.assertEquals("alice", user.name());
+ Mockito.verify(this.auth, Mockito.never()).listUsers();
+ }
+
+ @Test
+ public void shouldNotHideForbiddenModernSelfRead() {
+ TokenPayload payload = Mockito.mock(TokenPayload.class);
+ ServerException forbidden = new ServerException("forbidden");
+ forbidden.status(403);
+ Whitebox.setInternalState(
+ this.client, "compatibility",
+ ServerCompatibility.Profile.MODERN);
+ Mockito.when(payload.userId()).thenReturn("user-id");
+ Mockito.when(payload.username()).thenReturn("alice");
+ Mockito.when(this.auth.verifyToken()).thenReturn(payload);
+ Mockito.when(this.auth.getUser("user-id")).thenThrow(forbidden);
+
+ try {
+ this.client.findCurrentUser("alice");
+ Assert.fail("Expected modern self-read failure");
+ } catch (ServerException ignored) {
+ // Expected
+ }
+ }
+
+ @Test
+ public void shouldNotUseLegacyFallbackForInvalidOrMissingUser() {
+ TokenPayload payload = Mockito.mock(TokenPayload.class);
+ Whitebox.setInternalState(
+ this.client, "compatibility",
+ ServerCompatibility.Profile.GRAPHSPACE);
+ Mockito.when(payload.userId()).thenReturn("user-id");
+ Mockito.when(payload.username()).thenReturn("alice");
+ Mockito.when(this.auth.verifyToken()).thenReturn(payload);
+
+ for (int status : Arrays.asList(401, 404)) {
+ ServerException failure = new ServerException("failure");
+ failure.status(status);
+ Mockito.doThrow(failure).when(this.auth).getUser("user-id");
+ try {
+ this.client.findCurrentUser("alice");
+ Assert.fail("Expected legacy self-read failure");
+ } catch (ServerException actual) {
+ Assert.assertSame(failure, actual);
+ }
+ }
+ }
+
+ @Test
+ public void shouldRejectMismatchedCurrentUserIdentity() {
+ TokenPayload payload = Mockito.mock(TokenPayload.class);
+ Mockito.when(payload.userId()).thenReturn("user-id");
+ Mockito.when(payload.username()).thenReturn("bob");
+ Mockito.when(this.auth.verifyToken()).thenReturn(payload);
+
+ try {
+ this.client.findCurrentUser("alice");
+ Assert.fail("Expected a mismatched current-user identity");
+ } catch (IllegalStateException ignored) {
+ // Expected
+ }
+ Mockito.verify(this.auth, Mockito.never()).getUser(Mockito.any());
+ }
+
+ private static User user(String name) {
+ User user = new User();
+ user.name(name);
+ return user;
+ }
+}
diff --git a/hugegraph-client/src/test/java/org/apache/hugegraph/driver/ServerCompatibilityTest.java b/hugegraph-client/src/test/java/org/apache/hugegraph/driver/ServerCompatibilityTest.java
new file mode 100644
index 000000000..577071974
--- /dev/null
+++ b/hugegraph-client/src/test/java/org/apache/hugegraph/driver/ServerCompatibilityTest.java
@@ -0,0 +1,60 @@
+/*
+ * 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.hugegraph.driver;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+public class ServerCompatibilityTest {
+
+ @Test
+ public void shouldKeepLegacyServersConservative() {
+ Assert.assertFalse(ServerCompatibility.supportsGraphSpace("1.5.0"));
+ Assert.assertFalse(ServerCompatibility.supportsGraphSpace("1.6.0"));
+ Assert.assertFalse(ServerCompatibility.supportsGraphSpace(null));
+ Assert.assertFalse(ServerCompatibility.supportsGraphSpace("not-a-version"));
+ }
+
+ @Test
+ public void shouldExposeGraphSpaceForModernServers() {
+ Assert.assertTrue(ServerCompatibility.supportsGraphSpace("1.7.0"));
+ Assert.assertTrue(ServerCompatibility.supportsGraphSpace(" 1.7.0 "));
+ Assert.assertTrue(ServerCompatibility.supportsGraphSpace("1.8.0"));
+ Assert.assertFalse(ServerCompatibility.supportsDefaultRole(
+ "1.7.0", "0.71.0.0"));
+ Assert.assertTrue(ServerCompatibility.supportsDefaultRole(
+ "1.7.0", "0.72.0.0"));
+ Assert.assertFalse(ServerCompatibility.supportsDefaultRole(
+ "1.8.0", "0.71.0.0"));
+ Assert.assertFalse(
+ ServerCompatibility.supportsPersonalProfileUpdate(
+ "1.7.0", "0.71.0.0"));
+ Assert.assertTrue(
+ ServerCompatibility.supportsPersonalProfileUpdate(
+ "1.8.0", "0.72.0.0"));
+ Assert.assertEquals(ServerCompatibility.Profile.GRAPHSPACE,
+ ServerCompatibility.profile("1.7.1",
+ "0.71.0.0"));
+ Assert.assertEquals(ServerCompatibility.Profile.MODERN,
+ ServerCompatibility.profile("1.7.0",
+ "0.72.0.0"));
+ Assert.assertEquals(ServerCompatibility.Profile.GRAPHSPACE,
+ ServerCompatibility.profile("1.7.0",
+ "not-a-version"));
+ }
+}
diff --git a/hugegraph-client/src/test/java/org/apache/hugegraph/unit/ManagerAPITest.java b/hugegraph-client/src/test/java/org/apache/hugegraph/unit/ManagerAPITest.java
index 7fdf1aebd..e98ebcb67 100644
--- a/hugegraph-client/src/test/java/org/apache/hugegraph/unit/ManagerAPITest.java
+++ b/hugegraph-client/src/test/java/org/apache/hugegraph/unit/ManagerAPITest.java
@@ -146,21 +146,28 @@ public void testSpaceChecksUseEachTargetGraphSpacePath() {
AuthManager auth = new AuthManager(client, "DEFAULT", null);
Assert.assertTrue(auth.isSpaceAdmin("space_a"));
- Assert.assertTrue(auth.checkDefaultRole("space_b", "analyst"));
+ Assert.assertTrue(auth.isSpaceMember("space_b"));
+ Assert.assertTrue(auth.checkDefaultRole("space_c", "analyst"));
Assert.assertEquals("graphspaces/space_a/auth/managers/check",
path.getAllValues().get(0));
- Assert.assertEquals("graphspaces/space_b/auth/managers/default",
+ Assert.assertEquals("graphspaces/space_b/auth/managers/check",
path.getAllValues().get(1));
+ Assert.assertEquals("graphspaces/space_c/auth/managers/default",
+ path.getAllValues().get(2));
Assert.assertEquals(HugePermission.SPACE,
params.getAllValues().get(0).get("type"));
+ Assert.assertEquals(HugePermission.SPACE_MEMBER,
+ params.getAllValues().get(1).get("type"));
Assert.assertEquals("space_a",
params.getAllValues().get(0).get("graphspace"));
Assert.assertEquals("space_b",
params.getAllValues().get(1).get("graphspace"));
+ Assert.assertEquals("space_c",
+ params.getAllValues().get(2).get("graphspace"));
Assert.assertEquals("analyst",
- params.getAllValues().get(1).get("role"));
- Assert.assertFalse(params.getAllValues().get(1).containsKey("graph"));
+ params.getAllValues().get(2).get("role"));
+ Assert.assertFalse(params.getAllValues().get(2).containsKey("graph"));
}
@Test
diff --git a/hugegraph-client/src/test/java/org/apache/hugegraph/unit/UnitTestSuite.java b/hugegraph-client/src/test/java/org/apache/hugegraph/unit/UnitTestSuite.java
index ab4c18de3..f48efbcec 100644
--- a/hugegraph-client/src/test/java/org/apache/hugegraph/unit/UnitTestSuite.java
+++ b/hugegraph-client/src/test/java/org/apache/hugegraph/unit/UnitTestSuite.java
@@ -17,6 +17,8 @@
package org.apache.hugegraph.unit;
+import org.apache.hugegraph.driver.HugeClientCompatibilityTest;
+import org.apache.hugegraph.driver.ServerCompatibilityTest;
import org.junit.runner.RunWith;
import org.junit.runners.Suite;
@@ -33,6 +35,8 @@
ManagerAPITest.class,
GraphsAPITest.class,
PDHugeClientFactoryTest.class,
+ HugeClientCompatibilityTest.class,
+ ServerCompatibilityTest.class,
CommonUtilTest.class,
IdUtilTest.class,
SplicingIdGeneratorTest.class
diff --git a/hugegraph-hubble/.prettierrc b/hugegraph-hubble/.prettierrc
index afb777869..b6d7333f3 100644
--- a/hugegraph-hubble/.prettierrc
+++ b/hugegraph-hubble/.prettierrc
@@ -1,6 +1,6 @@
{
"singleQuote": true,
"tabWidth": 2,
- "printWidth": 80,
+ "printWidth": 120,
"trailingComma": "none"
}
diff --git a/hugegraph-hubble/AGENTS.md b/hugegraph-hubble/AGENTS.md
new file mode 100644
index 000000000..9f732a344
--- /dev/null
+++ b/hugegraph-hubble/AGENTS.md
@@ -0,0 +1,39 @@
+# Hubble contributor guide
+
+## Authentication and connection boundary
+
+The `1.8/master` path is the source of truth. Backend configuration exposes
+one `auth.enabled` switch and one connection resolver. The resolver chooses
+either a direct server URL or an address discovered from PD; callers must not
+reimplement `usePD` or infer connection state from page-local flags. In PD mode
+the server address returned by discovery is authoritative, so a manual server
+URL is not required.
+
+Use the unauthenticated HugeGraph client for anonymous mode. Do not manufacture
+an empty token or an administrator session. Anonymous mode has no account
+context and account/permission routes are hidden or rejected at the capability
+boundary.
+
+## Compatibility policy
+
+Compatibility is intentionally one-way:
+
+- `1.8/master`: modern GraphSpace/auth contracts and the complete UI.
+- `1.7`: thin fallback for the legacy response shape; keep the core workflow
+ usable without adding version branches to controllers or React pages.
+- `1.5` standalone: core graph/schema/data operations only. GraphSpace
+ management is unsupported and should degrade with an explicit capability
+ response. Do not add a PD variant for 1.5.
+
+Version checks belong in the client compatibility adapter and connection
+resolver. New code should consume capabilities, not compare literal versions.
+When an old image cannot satisfy a capability, mark the test as `needs input`
+or `skipped` with the exact image tag and reason.
+
+## Verification
+
+For UI changes, use Chrome to exercise login/non-auth mode, connection
+switching, and account/GraphSpace visibility. Static inspection and unit tests
+are not a substitute for this interaction check. Keep screenshots collected
+from the running UI in the documentation assets referenced by
+`README.md`.
diff --git a/hugegraph-hubble/README.md b/hugegraph-hubble/README.md
index cbc13ad04..445b4ee20 100644
--- a/hugegraph-hubble/README.md
+++ b/hugegraph-hubble/README.md
@@ -7,6 +7,33 @@
hugegraph-hubble is a graph management and analysis platform that provides features:
graph data load, schema management, graph relationship analysis, and graphical display.
+## Authentication, connections, and compatibility
+
+Hubble uses one capability-driven connection boundary for `1.8/master`.
+`auth.enabled=true` creates an authenticated session; when it is `false`, Hubble
+uses an unauthenticated client and does not create a fake user. Account and
+permission entry points are hidden in anonymous mode. Connection switching
+always goes through the backend resolver. In PD mode, a valid server address
+returned by discovery is sufficient; a manually configured server URL is not
+required.
+
+Container and orchestrated deployments can set `HUBBLE_AUTH_ENABLED=true` or
+`false`. This explicit runtime value overrides `auth.enabled` from the
+properties file, and invalid values fail startup instead of silently selecting
+an authentication mode.
+
+The UI presents four stable permission meanings: super administrator, GraphSpace
+read-only, GraphSpace read-write, and GraphSpace administrator. The last one
+means member management plus read/write within that GraphSpace; low-level
+`role`, `target`, `access`, and `belong` fields are not exposed.
+
+The compatibility boundary is deliberately small. Server 1.7 uses a thin
+legacy-response fallback. Server 1.5 standalone is limited to core graph,
+schema, data, and Gremlin operations; GraphSpace management is reported as
+unsupported. Version checks stay in the client adapter/resolver rather than
+being scattered through controllers or pages. See
+[`AGENTS.md`](AGENTS.md) for the support matrix and verification rules.
+
## Local development feedback loop
Run the frontend with third-party source-map noise disabled:
diff --git a/hugegraph-hubble/docs/images/pr27/15-standalone-nonauth-visual.png b/hugegraph-hubble/docs/images/pr27/15-standalone-nonauth-visual.png
new file mode 100644
index 000000000..0b472dac9
Binary files /dev/null and b/hugegraph-hubble/docs/images/pr27/15-standalone-nonauth-visual.png differ
diff --git a/hugegraph-hubble/docs/images/pr27/18-standalone-nonauth-visual.png b/hugegraph-hubble/docs/images/pr27/18-standalone-nonauth-visual.png
new file mode 100644
index 000000000..13e8bc6cf
Binary files /dev/null and b/hugegraph-hubble/docs/images/pr27/18-standalone-nonauth-visual.png differ
diff --git a/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/common/Constant.java b/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/common/Constant.java
index 7c29e9518..66f94ed6a 100644
--- a/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/common/Constant.java
+++ b/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/common/Constant.java
@@ -58,6 +58,15 @@ public final class Constant {
public static final String TOKEN_KEY = "auth_token";
public static final String USERNAME_KEY = "username";
+ /**
+ * Server-side-only legacy Gremlin credentials. 1.7's Gremlin HTTP
+ * channel accepts Basic auth while its REST APIs accept the login token.
+ */
+ public static final String PASSWORD_KEY = "auth_password";
+ public static final String PASSWORD_EXPIRE_AT_KEY =
+ "auth_password_expire_at";
+ public static final String GRAPHSPACE_ACCESS_KEY =
+ "validated_graphspace";
public static final int NO_LIMIT = -1;
diff --git a/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/config/HubbleConfig.java b/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/config/HubbleConfig.java
index 8caad2787..ea40f8668 100644
--- a/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/config/HubbleConfig.java
+++ b/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/config/HubbleConfig.java
@@ -28,10 +28,14 @@
import java.io.File;
import java.net.URL;
+import java.util.Locale;
+import java.util.Map;
@Configuration
public class HubbleConfig {
+ static final String AUTH_ENABLED_ENV = "HUBBLE_AUTH_ENABLED";
+
@Autowired
private ApplicationArguments arguments;
@@ -56,6 +60,24 @@ public HugeConfig hugeConfig() {
conf = path;
}
}
- return new HugeConfig(conf);
+ HugeConfig config = new HugeConfig(conf);
+ applyEnvironmentOverrides(config, System.getenv());
+ return config;
+ }
+
+ static void applyEnvironmentOverrides(HugeConfig config,
+ Map environment) {
+ String authEnabled = environment.get(AUTH_ENABLED_ENV);
+ if (authEnabled == null) {
+ return;
+ }
+
+ String normalized = authEnabled.trim().toLowerCase(Locale.ROOT);
+ if (!normalized.equals("true") && !normalized.equals("false")) {
+ throw new ExternalException(
+ AUTH_ENABLED_ENV + " must be true or false");
+ }
+ config.setProperty(HubbleOptions.AUTH_ENABLED.name(),
+ Boolean.valueOf(normalized));
}
}
diff --git a/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/config/WebMvcConfig.java b/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/config/WebMvcConfig.java
index ec0fb77e8..f0a2950b7 100644
--- a/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/config/WebMvcConfig.java
+++ b/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/config/WebMvcConfig.java
@@ -18,6 +18,7 @@
package org.apache.hugegraph.config;
+import org.apache.hugegraph.common.Constant;
import org.apache.hugegraph.handler.LoginInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@@ -66,6 +67,7 @@ public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(this.loginInterceptor())
.addPathPatterns("/api/**")
.excludePathPatterns("/api/**/auth/login")
+ .excludePathPatterns(Constant.API_VERSION + "config")
.excludePathPatterns("/logout")
.excludePathPatterns("/api/**/auth/logout");
}
diff --git a/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/controller/BaseController.java b/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/controller/BaseController.java
index 807da39fc..eb7cffa4f 100644
--- a/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/controller/BaseController.java
+++ b/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/controller/BaseController.java
@@ -21,11 +21,15 @@
import java.util.List;
import java.util.function.Function;
import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpSession;
import org.apache.hugegraph.driver.HugeClient;
import org.apache.hugegraph.driver.factory.PDHugeClientFactory;
import org.apache.hugegraph.options.HubbleOptions;
import org.apache.hugegraph.service.auth.UserService;
+import org.apache.hugegraph.service.auth.AuthModeService;
+import org.apache.hugegraph.service.auth.AuthContextService;
+import org.apache.hugegraph.service.space.GraphSpaceService;
import org.apache.commons.collections.CollectionUtils;
import org.apache.hugegraph.config.HugeConfig;
import org.springframework.beans.factory.annotation.Autowired;
@@ -59,6 +63,12 @@ public abstract class BaseController {
@Autowired
protected UserService userService;
+ @Autowired
+ protected AuthModeService authMode;
+ @Autowired
+ protected AuthContextService authContextService;
+ @Autowired
+ protected GraphSpaceService graphSpaceAccessService;
public static final String ORDER_ASC = "asc";
public static final String ORDER_DESC = "desc";
@@ -134,22 +144,31 @@ protected void delToken() {
protected void clearAuthSession() {
this.delSession(Constant.TOKEN_KEY);
this.delSession(Constant.USERNAME_KEY);
+ this.delSession(Constant.PASSWORD_KEY);
+ this.delSession(Constant.PASSWORD_EXPIRE_AT_KEY);
}
protected HugeClient authClient(String graphSpace, String graph) {
HttpServletRequest request = getRequest();
if (request.getAttribute("hugeClient") != null) {
HugeClient client = (HugeClient) request.getAttribute("hugeClient");
+ this.requireGraphSpaceAccess(client, graphSpace);
client.assignGraph(graphSpace, graph);
return client;
}
- HugeClient client = this.hugeClientPoolService.createAuthClient(
- graphSpace, graph, this.getToken());
+ HugeClient client = this.authMode != null && this.authMode.anonymous() ?
+ this.hugeClientPoolService.createUnauthClient(graphSpace, graph) :
+ this.hugeClientPoolService.createAuthClient(graphSpace, graph, this.getToken());
+ this.requireGraphSpaceAccess(client, graphSpace);
+ if (graphSpace != null || graph != null) {
+ client.assignGraph(graphSpace, graph);
+ }
request.setAttribute("hugeClient", client);
return client;
}
protected HugeClient requireAccountManager() {
+ this.requireAuthenticatedAuthorization();
HugeClient client = this.authClient(null, null);
String level = this.userService.userLevel(client, this.getUser());
if (!"ADMIN".equals(level)) {
@@ -159,6 +178,7 @@ protected HugeClient requireAccountManager() {
}
protected HugeClient requireGraphSpaceManager(String graphSpace) {
+ this.requireAuthenticatedAuthorization();
HugeClient client = this.authClient(null, null);
if (!this.userService.isSuperAdmin(client) &&
!this.userService.isAssignSpaceAdmin(client, graphSpace)) {
@@ -169,7 +189,28 @@ protected HugeClient requireGraphSpaceManager(String graphSpace) {
return client;
}
+ protected HugeClient requireGraphSpaceWrite(String graphSpace) {
+ HugeClient client = this.authClient(null, null);
+ this.requireGraphSpaceAccess(client, graphSpace);
+ this.authContextService.requireGraphSpaceWrite(
+ client, this.getUser(), graphSpace);
+ client.assignGraph(graphSpace, null);
+ return client;
+ }
+
+ protected HugeClient requireGraphSpaceAuthorizationAdmin(
+ String graphSpace) {
+ this.requireAuthenticatedAuthorization();
+ HugeClient client = this.authClient(null, null);
+ if (!this.userService.isSuperAdmin(client)) {
+ throw new ForbiddenException("Permission denied: manage authorization objects");
+ }
+ client.assignGraph(graphSpace, null);
+ return client;
+ }
+
protected HugeClient requireGraphSpaceAdministrator() {
+ this.requireAuthenticatedAuthorization();
HugeClient client = this.authClient(null, null);
if (!this.userService.isSuperAdmin(client)) {
throw new ForbiddenException(
@@ -179,7 +220,52 @@ protected HugeClient requireGraphSpaceAdministrator() {
}
protected HugeClient authGremlinClient(String graphSpace, String graph) {
- return this.authClient(graphSpace, graph);
+ if (this.authMode != null && this.authMode.anonymous()) {
+ return this.authClient(graphSpace, graph);
+ }
+
+ HttpServletRequest request = this.getRequest();
+ HttpSession session = request.getSession(false);
+ if (session == null) {
+ return this.authClient(graphSpace, graph);
+ }
+
+ String username = (String) session.getAttribute(Constant.USERNAME_KEY);
+ String token = (String) session.getAttribute(Constant.TOKEN_KEY);
+ String password = this.validSessionPassword(session);
+ if (!StringUtils.hasText(username) || !StringUtils.hasText(token) ||
+ !StringUtils.hasText(password)) {
+ return this.authClient(graphSpace, graph);
+ }
+
+ Object existing = request.getAttribute("hugeClient");
+ if (existing instanceof HugeClient) {
+ ((HugeClient) existing).close();
+ }
+ HugeClient client = this.createBasicClient(graphSpace, graph,
+ username, password);
+ this.requireGraphSpaceAccess(client, graphSpace);
+ request.setAttribute("hugeClient", client);
+ return client;
+ }
+
+ protected HugeClient createBasicClient(String graphSpace, String graph,
+ String username, String password) {
+ return this.hugeClientPoolService.createBasicClient(
+ graphSpace, graph, username, password);
+ }
+
+ private String validSessionPassword(HttpSession session) {
+ Object password = session.getAttribute(Constant.PASSWORD_KEY);
+ Object expiresAt = session.getAttribute(
+ Constant.PASSWORD_EXPIRE_AT_KEY);
+ if (!(password instanceof String) || !(expiresAt instanceof Number) ||
+ System.currentTimeMillis() >= ((Number) expiresAt).longValue()) {
+ session.removeAttribute(Constant.PASSWORD_KEY);
+ session.removeAttribute(Constant.PASSWORD_EXPIRE_AT_KEY);
+ return null;
+ }
+ return (String) password;
}
protected HugeClient unauthClient() {
@@ -266,10 +352,36 @@ protected HugeClient defaultClient(String graphSpace, String graph) {
HugeClient client = hugeClientPoolService.create(url, graphSpace, graph,
this.getToken());
-
+ this.requireGraphSpaceAccess(client, graphSpace);
return client;
}
+ private void requireAuthenticatedAuthorization() {
+ if (this.authMode != null && this.authMode.anonymous()) {
+ throw new ForbiddenException(
+ "Authentication is required for this operation");
+ }
+ }
+
+ protected void requireGraphSpaceAccess(HugeClient client,
+ String graphSpace) {
+ if (graphSpace == null || !config.get(HubbleOptions.PD_ENABLED)) {
+ return;
+ }
+ HttpServletRequest request = getRequest();
+ if (graphSpace.equals(
+ request.getAttribute(Constant.GRAPHSPACE_ACCESS_KEY))) {
+ return;
+ }
+ if (this.authMode != null && this.authMode.anonymous()) {
+ this.graphSpaceAccessService.requirePublicSpace(client,
+ graphSpace);
+ } else {
+ this.graphSpaceAccessService.requireAccessibleSpace(client,
+ graphSpace);
+ }
+ }
+
public String getUrl() {
boolean pdEnabled = config.get(HubbleOptions.PD_ENABLED);
if (!pdEnabled) {
diff --git a/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/controller/ConfigController.java b/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/controller/ConfigController.java
index 5b3795d8a..8f8f5cb6c 100644
--- a/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/controller/ConfigController.java
+++ b/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/controller/ConfigController.java
@@ -39,11 +39,8 @@ public class ConfigController {
@GetMapping
public Map getConfig() {
Map result = new HashMap<>();
- boolean pdEnabled = config.get(HubbleOptions.PD_ENABLED);
- result.put("pd_enabled", pdEnabled);
- if (!pdEnabled) {
- result.put("server_url", config.get(HubbleOptions.SERVER_URL));
- }
+ result.put("pd_enabled", config.get(HubbleOptions.PD_ENABLED));
+ result.put("auth_enabled", config.get(HubbleOptions.AUTH_ENABLED));
return result;
}
}
diff --git a/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/controller/algorithm/OltpAlgoController.java b/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/controller/algorithm/OltpAlgoController.java
index c50d77ab0..05408442d 100644
--- a/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/controller/algorithm/OltpAlgoController.java
+++ b/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/controller/algorithm/OltpAlgoController.java
@@ -95,7 +95,7 @@ public GremlinResult shortPathAlias(@PathVariable("graphspace") String graphSpac
public GremlinResult rings(@PathVariable("graphspace") String graphSpace,
@PathVariable("graph") String graph,
@RequestBody RingsEntity body) {
- HugeClient client = this.authClient(graphSpace, graph);
+ HugeClient client = this.authGremlinClient(graphSpace, graph);
return this.service.rings(client, body);
}
@@ -103,7 +103,7 @@ public GremlinResult rings(@PathVariable("graphspace") String graphSpace,
public GremlinResult advancedPaths(@PathVariable("graphspace") String graphSpace,
@PathVariable("graph") String graph,
@RequestBody PathsRequest body) {
- HugeClient client = this.authClient(graphSpace, graph);
+ HugeClient client = this.authGremlinClient(graphSpace, graph);
return this.service.advancedpaths(client, body);
}
@@ -111,7 +111,7 @@ public GremlinResult advancedPaths(@PathVariable("graphspace") String graphSpace
public GremlinResult sameNeighbors(@PathVariable("graphspace") String graphSpace,
@PathVariable("graph") String graph,
@RequestBody SameNeighborsEntity body) {
- HugeClient client = this.authClient(graphSpace, graph);
+ HugeClient client = this.authGremlinClient(graphSpace, graph);
return this.service.sameNeighbors(client, body);
}
@@ -119,7 +119,7 @@ public GremlinResult sameNeighbors(@PathVariable("graphspace") String graphSpace
public GremlinResult kout(@PathVariable("graphspace") String graphSpace,
@PathVariable("graph") String graph,
@RequestBody KoutEntity body) {
- HugeClient client = this.authClient(graphSpace, graph);
+ HugeClient client = this.authGremlinClient(graphSpace, graph);
return this.service.kout(client, body);
}
@@ -127,7 +127,7 @@ public GremlinResult kout(@PathVariable("graphspace") String graphSpace,
public GremlinResult koutPost(@PathVariable("graphspace") String graphSpace,
@PathVariable("graph") String graph,
@RequestBody KoutRequest body) {
- HugeClient client = this.authClient(graphSpace, graph);
+ HugeClient client = this.authGremlinClient(graphSpace, graph);
return this.service.koutPost(client, body);
}
@@ -135,7 +135,7 @@ public GremlinResult koutPost(@PathVariable("graphspace") String graphSpace,
public GremlinResult kneighbor(@PathVariable("graphspace") String graphSpace,
@PathVariable("graph") String graph,
@RequestBody KneighborEntity body) {
- HugeClient client = this.authClient(graphSpace, graph);
+ HugeClient client = this.authGremlinClient(graphSpace, graph);
return this.service.kneighbor(client, body);
}
@@ -143,7 +143,7 @@ public GremlinResult kneighbor(@PathVariable("graphspace") String graphSpace,
public GremlinResult kneighborPost(@PathVariable("graphspace") String graphSpace,
@PathVariable("graph") String graph,
@RequestBody KneighborRequest body) {
- HugeClient client = this.authClient(graphSpace, graph);
+ HugeClient client = this.authGremlinClient(graphSpace, graph);
return this.service.kneighborPost(client, body);
}
@@ -151,7 +151,7 @@ public GremlinResult kneighborPost(@PathVariable("graphspace") String graphSpace
public JaccardsimilarityView jaccardSimilarity(@PathVariable("graphspace") String graphSpace,
@PathVariable("graph") String graph,
@RequestBody JaccardSimilarityEntity body) {
- HugeClient client = this.authClient(graphSpace, graph);
+ HugeClient client = this.authGremlinClient(graphSpace, graph);
return this.service.jaccardSimilarity(client, body);
}
@@ -160,7 +160,7 @@ public JaccardsimilarityView jaccardSimilarityPost(
@PathVariable("graphspace") String graphSpace,
@PathVariable("graph") String graph,
@RequestBody SingleSourceJaccardSimilarityRequest body) {
- HugeClient client = this.authClient(graphSpace, graph);
+ HugeClient client = this.authGremlinClient(graphSpace, graph);
return this.service.jaccardSimilarityPost(client, body);
}
@@ -168,7 +168,7 @@ public JaccardsimilarityView jaccardSimilarityPost(
public RanksView personalRank(@PathVariable("graphspace") String graphSpace,
@PathVariable("graph") String graph,
@RequestBody PersonalRankAPI.Request body) {
- HugeClient client = this.authClient(graphSpace, graph);
+ HugeClient client = this.authGremlinClient(graphSpace, graph);
return this.service.personalRank(client, body);
}
@@ -176,7 +176,7 @@ public RanksView personalRank(@PathVariable("graphspace") String graphSpace,
public RanksView neighborRank(@PathVariable("graphspace") String graphSpace,
@PathVariable("graph") String graph,
@RequestBody NeighborRankAPI.Request body) {
- HugeClient client = this.authClient(graphSpace, graph);
+ HugeClient client = this.authGremlinClient(graphSpace, graph);
return this.service.neighborRank(client, body);
}
@@ -184,7 +184,7 @@ public RanksView neighborRank(@PathVariable("graphspace") String graphSpace,
public GremlinResult allShortPaths(@PathVariable("graphspace") String graphSpace,
@PathVariable("graph") String graph,
@RequestBody AllShortestPathsEntity body) {
- HugeClient client = this.authClient(graphSpace, graph);
+ HugeClient client = this.authGremlinClient(graphSpace, graph);
return this.service.allShortestPaths(client, body);
}
@@ -199,7 +199,7 @@ public GremlinResult allShortPathAlias(@PathVariable("graphspace") String graphS
public GremlinResult weightedShortestPath(@PathVariable("graphspace") String graphSpace,
@PathVariable("graph") String graph,
@RequestBody WeightedShortestPathEntity body) {
- HugeClient client = this.authClient(graphSpace, graph);
+ HugeClient client = this.authGremlinClient(graphSpace, graph);
return this.service.weightedShortestPath(client, body);
}
@@ -208,7 +208,7 @@ public GremlinResult singleSourceShortestPath(
@PathVariable("graphspace") String graphSpace,
@PathVariable("graph") String graph,
@RequestBody SingleSourceShortestPathEntity body) {
- HugeClient client = this.authClient(graphSpace, graph);
+ HugeClient client = this.authGremlinClient(graphSpace, graph);
return this.service.singleSourceShortestPath(client, body);
}
@@ -216,7 +216,7 @@ public GremlinResult singleSourceShortestPath(
public GremlinResult multiNodeShortestPath(@PathVariable("graphspace") String graphSpace,
@PathVariable("graph") String graph,
@RequestBody MultiNodeShortestPathRequest body) {
- HugeClient client = this.authClient(graphSpace, graph);
+ HugeClient client = this.authGremlinClient(graphSpace, graph);
return this.service.multiNodeShortestPath(client, body);
}
@@ -224,7 +224,7 @@ public GremlinResult multiNodeShortestPath(@PathVariable("graphspace") String gr
public GremlinResult paths(@PathVariable("graphspace") String graphSpace,
@PathVariable("graph") String graph,
@RequestBody PathsEntity body) {
- HugeClient client = this.authClient(graphSpace, graph);
+ HugeClient client = this.authGremlinClient(graphSpace, graph);
return this.service.paths(client, body);
}
@@ -232,7 +232,7 @@ public GremlinResult paths(@PathVariable("graphspace") String graphSpace,
public GremlinResult customizedPaths(@PathVariable("graphspace") String graphSpace,
@PathVariable("graph") String graph,
@RequestBody CustomizedPathsRequest body) {
- HugeClient client = this.authClient(graphSpace, graph);
+ HugeClient client = this.authGremlinClient(graphSpace, graph);
return this.service.customizedPaths(client, body);
}
@@ -240,7 +240,7 @@ public GremlinResult customizedPaths(@PathVariable("graphspace") String graphSpa
public GremlinResult templatePaths(@PathVariable("graphspace") String graphSpace,
@PathVariable("graph") String graph,
@RequestBody TemplatePathsRequest body) {
- HugeClient client = this.authClient(graphSpace, graph);
+ HugeClient client = this.authGremlinClient(graphSpace, graph);
return this.service.templatePaths(client, body);
}
@@ -248,7 +248,7 @@ public GremlinResult templatePaths(@PathVariable("graphspace") String graphSpace
public GremlinResult crosspoints(@PathVariable("graphspace") String graphSpace,
@PathVariable("graph") String graph,
@RequestBody CrossPointsEntity body) {
- HugeClient client = this.authClient(graphSpace, graph);
+ HugeClient client = this.authGremlinClient(graphSpace, graph);
return this.service.crosspoints(client, body);
}
@@ -256,7 +256,7 @@ public GremlinResult crosspoints(@PathVariable("graphspace") String graphSpace,
public GremlinResult customizedcrosspoints(@PathVariable("graphspace") String graphSpace,
@PathVariable("graph") String graph,
@RequestBody CrosspointsRequest body) {
- HugeClient client = this.authClient(graphSpace, graph);
+ HugeClient client = this.authGremlinClient(graphSpace, graph);
return this.service.customizedcrosspoints(client, body);
}
@@ -264,7 +264,7 @@ public GremlinResult customizedcrosspoints(@PathVariable("graphspace") String gr
public GremlinResult rays(@PathVariable("graphspace") String graphSpace,
@PathVariable("graph") String graph,
@RequestBody RaysEntity body) {
- HugeClient client = this.authClient(graphSpace, graph);
+ HugeClient client = this.authGremlinClient(graphSpace, graph);
return this.service.rays(client, body);
}
@@ -272,7 +272,7 @@ public GremlinResult rays(@PathVariable("graphspace") String graphSpace,
public FusiformsimilarityView fusiformsimilarity(@PathVariable("graphspace") String graphSpace,
@PathVariable("graph") String graph,
@RequestBody FusiformSimilarityRequest body) {
- HugeClient client = this.authClient(graphSpace, graph);
+ HugeClient client = this.authGremlinClient(graphSpace, graph);
return this.service.fusiformsimilarity(client, body);
}
@@ -280,7 +280,7 @@ public FusiformsimilarityView fusiformsimilarity(@PathVariable("graphspace") Str
public Map adamicadar(@PathVariable("graphspace") String graphSpace,
@PathVariable("graph") String graph,
@RequestBody AdamicadarEntity body) {
- HugeClient client = this.authClient(graphSpace, graph);
+ HugeClient client = this.authGremlinClient(graphSpace, graph);
return this.service.adamicadar(client, body);
}
@@ -288,7 +288,7 @@ public Map adamicadar(@PathVariable("graphspace") String graphSp
public Map resourceallocation(@PathVariable("graphspace") String graphSpace,
@PathVariable("graph") String graph,
@RequestBody ResourceallocationEntity body) {
- HugeClient client = this.authClient(graphSpace, graph);
+ HugeClient client = this.authGremlinClient(graphSpace, graph);
return this.service.resourceallocation(client, body);
}
@@ -296,7 +296,7 @@ public Map resourceallocation(@PathVariable("graphspace") String
public GremlinResult sameneighborsbatch(@PathVariable("graphspace") String graphSpace,
@PathVariable("graph") String graph,
@RequestBody SameNeighborsBatchRequest body) {
- HugeClient client = this.authClient(graphSpace, graph);
+ HugeClient client = this.authGremlinClient(graphSpace, graph);
return this.service.sameneighborsbatch(client, body);
}
@@ -304,7 +304,7 @@ public GremlinResult sameneighborsbatch(@PathVariable("graphspace") String graph
public EgonetView egonet(@PathVariable("graphspace") String graphSpace,
@PathVariable("graph") String graph,
@RequestBody EgonetRequest body) {
- HugeClient client = this.authClient(graphSpace, graph);
+ HugeClient client = this.authGremlinClient(graphSpace, graph);
return this.service.egonet(client, body);
}
}
diff --git a/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/controller/auth/AccessController.java b/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/controller/auth/AccessController.java
index 0f97026ed..f2bbc78f5 100644
--- a/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/controller/auth/AccessController.java
+++ b/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/controller/auth/AccessController.java
@@ -47,28 +47,28 @@ public List list(
@PathVariable("graphspace") String graphSpace,
@RequestParam(value = "role_id", required = false) String roleId,
@RequestParam(value = "target_id", required = false) String targetId) {
- HugeClient client = this.requireGraphSpaceManager(graphSpace);
+ HugeClient client = this.requireGraphSpaceAuthorizationAdmin(graphSpace);
return this.accessService.list(client, graphSpace, roleId, targetId);
}
@GetMapping("{id}")
public AccessEntity get(@PathVariable("graphspace") String graphSpace,
@PathVariable("id") String accessId) {
- HugeClient client = this.requireGraphSpaceManager(graphSpace);
+ HugeClient client = this.requireGraphSpaceAuthorizationAdmin(graphSpace);
return this.accessService.get(client, graphSpace, accessId);
}
@PostMapping
public AccessEntity add(@PathVariable("graphspace") String graphSpace,
@RequestBody AccessEntity accessEntity) {
- HugeClient client = this.requireGraphSpaceManager(graphSpace);
+ HugeClient client = this.requireGraphSpaceAuthorizationAdmin(graphSpace);
return this.accessService.addOrUpdate(client, graphSpace, accessEntity);
}
@PutMapping
public AccessEntity update(@PathVariable("graphspace") String graphSpace,
@RequestBody AccessEntity accessEntity) {
- HugeClient client = this.requireGraphSpaceManager(graphSpace);
+ HugeClient client = this.requireGraphSpaceAuthorizationAdmin(graphSpace);
return this.accessService.addOrUpdate(client, graphSpace, accessEntity);
}
@@ -76,7 +76,7 @@ public AccessEntity update(@PathVariable("graphspace") String graphSpace,
public void delete(@PathVariable("graphspace") String graphSpace,
@RequestParam("role_id") String roleId,
@RequestParam("target_id") String targetId) {
- HugeClient client = this.requireGraphSpaceManager(graphSpace);
+ HugeClient client = this.requireGraphSpaceAuthorizationAdmin(graphSpace);
this.accessService.delete(client, graphSpace, roleId, targetId);
}
}
diff --git a/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/controller/auth/BelongController.java b/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/controller/auth/BelongController.java
index 757a4152b..1f7ce5660 100644
--- a/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/controller/auth/BelongController.java
+++ b/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/controller/auth/BelongController.java
@@ -48,7 +48,7 @@ public List list(
@PathVariable("graphspace") String graphSpace,
@RequestParam(value = "role_id", required = false) String roleId,
@RequestParam(value = "user_id", required = false) String userId) {
- HugeClient client = this.requireGraphSpaceManager(graphSpace);
+ HugeClient client = this.requireGraphSpaceAuthorizationAdmin(graphSpace);
return this.belongService.list(client, graphSpace, roleId, userId);
}
@@ -61,7 +61,7 @@ public IPage listPage(
defaultValue = "1") int pageNo,
@RequestParam(name = "page_size", required = false,
defaultValue = "10") int pageSize) {
- HugeClient client = this.requireGraphSpaceManager(graphSpace);
+ HugeClient client = this.requireGraphSpaceAuthorizationAdmin(graphSpace);
return this.belongService.listPage(client, graphSpace, roleId, userId,
pageNo, pageSize);
}
@@ -69,14 +69,14 @@ public IPage listPage(
@GetMapping("{id}")
public BelongEntity get(@PathVariable("graphspace") String graphSpace,
@PathVariable("id") String belongId) {
- HugeClient client = this.requireGraphSpaceManager(graphSpace);
+ HugeClient client = this.requireGraphSpaceAuthorizationAdmin(graphSpace);
return this.belongService.get(client, graphSpace, belongId);
}
@PostMapping
public void create(@PathVariable("graphspace") String graphSpace,
@RequestBody BelongEntity belongEntity) {
- HugeClient client = this.requireGraphSpaceManager(graphSpace);
+ HugeClient client = this.requireGraphSpaceAuthorizationAdmin(graphSpace);
this.belongService.add(client, graphSpace, belongEntity.getRoleId(),
belongEntity.getUserId());
}
@@ -84,7 +84,7 @@ public void create(@PathVariable("graphspace") String graphSpace,
@PostMapping("ids")
public void createMany(@PathVariable("graphspace") String graphSpace,
@RequestBody BelongService.BelongsReq belongsReq) {
- HugeClient client = this.requireGraphSpaceManager(graphSpace);
+ HugeClient client = this.requireGraphSpaceAuthorizationAdmin(graphSpace);
for (String userId : belongsReq.getUserIds()) {
this.belongService.add(client, graphSpace,
belongsReq.getRoleId(), userId);
@@ -94,7 +94,7 @@ public void createMany(@PathVariable("graphspace") String graphSpace,
@DeleteMapping("{id}")
public void delete(@PathVariable("graphspace") String graphSpace,
@PathVariable("id") String belongId) {
- HugeClient client = this.requireGraphSpaceManager(graphSpace);
+ HugeClient client = this.requireGraphSpaceAuthorizationAdmin(graphSpace);
this.belongService.deleteById(client, graphSpace, belongId);
}
@@ -102,7 +102,7 @@ public void delete(@PathVariable("graphspace") String graphSpace,
public void delete(@PathVariable("graphspace") String graphSpace,
@RequestParam("role_id") String roleId,
@RequestParam("user_id") String userId) {
- HugeClient client = this.requireGraphSpaceManager(graphSpace);
+ HugeClient client = this.requireGraphSpaceAuthorizationAdmin(graphSpace);
if (StringUtils.isNotEmpty(roleId) && StringUtils.isNotEmpty(userId)) {
this.belongService.delete(client, graphSpace, roleId, userId);
}
@@ -111,7 +111,7 @@ public void delete(@PathVariable("graphspace") String graphSpace,
@PostMapping("delids")
public void deleteMany(@PathVariable("graphspace") String graphSpace,
@RequestBody DelIdsReq delIdsReq) {
- HugeClient client = this.requireGraphSpaceManager(graphSpace);
+ HugeClient client = this.requireGraphSpaceAuthorizationAdmin(graphSpace);
this.belongService.deleteMany(client, graphSpace,
delIdsReq.ids.toArray(new String[0]));
}
diff --git a/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/controller/auth/GraphSpaceUserController.java b/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/controller/auth/GraphSpaceUserController.java
index 8ec43ae5a..5302227f0 100644
--- a/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/controller/auth/GraphSpaceUserController.java
+++ b/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/controller/auth/GraphSpaceUserController.java
@@ -18,6 +18,8 @@
package org.apache.hugegraph.controller.auth;
+import java.util.Map;
+
import com.baomidou.mybatisplus.core.metadata.IPage;
import org.apache.hugegraph.common.Constant;
import org.apache.hugegraph.driver.HugeClient;
@@ -25,6 +27,7 @@
import org.apache.hugegraph.service.auth.GraphSpaceUserService;
import org.apache.hugegraph.structure.auth.User;
import org.apache.hugegraph.structure.auth.UserManager;
+import org.apache.hugegraph.util.E;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
@@ -82,22 +85,45 @@ public UserView get(@PathVariable("graphspace") String graphSpace,
public UserManager setGraphSpaceAdmin(
@PathVariable("graphspace") String graphSpace,
@PathVariable("id") String userId) {
- HugeClient client = this.requireGraphSpaceManager(graphSpace);
- return client.auth().addSpaceAdmin(userId, graphSpace);
+ HugeClient client =
+ this.requireGraphSpaceAuthorizationAdmin(graphSpace);
+ User account = client.auth().getUser(userId);
+ E.checkNotNull(account, "User");
+ return client.auth().addSpaceAdmin(account.name(), graphSpace);
+ }
+
+ @PutMapping("{id}/preset")
+ public void setPermissionPreset(
+ @PathVariable("graphspace") String graphSpace,
+ @PathVariable("id") String identity,
+ @RequestBody Map body) {
+ String preset = body.get("permission_preset");
+ String userId = body.get("user_id");
+ String username = body.get("username");
+ HugeClient client = this.requirePresetManager(graphSpace, username,
+ preset);
+ E.checkArgument(identity.equals(userId) ||
+ identity.equals(username),
+ "The account identity in the path and body must match");
+ this.userService.applySpacePreset(client, graphSpace, userId, username, preset);
}
@DeleteMapping("spaceadmin/{id}")
public void removeGraphSpaceAdmin(
@PathVariable("graphspace") String graphSpace,
@PathVariable("id") String userId) {
- HugeClient client = this.requireGraphSpaceManager(graphSpace);
- client.auth().delSpaceAdmin(userId, graphSpace);
+ HugeClient client =
+ this.requireGraphSpaceAuthorizationAdmin(graphSpace);
+ User account = client.auth().getUser(userId);
+ E.checkNotNull(account, "User");
+ client.auth().delSpaceAdmin(account.name(), graphSpace);
}
@PostMapping
public UserView create(@PathVariable("graphspace") String graphSpace,
@RequestBody UserView userView) {
- HugeClient client = this.requireGraphSpaceManager(graphSpace);
+ HugeClient client =
+ this.requireGraphSpaceAuthorizationAdmin(graphSpace);
return this.userService.createOrUpdate(client, graphSpace, userView);
}
@@ -105,7 +131,8 @@ public UserView create(@PathVariable("graphspace") String graphSpace,
public UserView createOrUpdate(@PathVariable("graphspace") String graphSpace,
@PathVariable("id") String userId,
@RequestBody UserView userView) {
- HugeClient client = this.requireGraphSpaceManager(graphSpace);
+ HugeClient client =
+ this.requireGraphSpaceAuthorizationAdmin(graphSpace);
userView.setId(userId);
return this.userService.createOrUpdate(client, graphSpace, userView);
}
@@ -113,7 +140,37 @@ public UserView createOrUpdate(@PathVariable("graphspace") String graphSpace,
@DeleteMapping("{id}")
public void delete(@PathVariable("graphspace") String graphSpace,
@PathVariable("id") String userId) {
- HugeClient client = this.requireGraphSpaceManager(graphSpace);
+ HugeClient client = this.requireMemberManager(graphSpace, userId);
this.userService.unauthUser(client, graphSpace, userId);
}
+
+ private HugeClient requirePresetManager(String graphSpace, String username,
+ String preset) {
+ if ("GS_ADMIN".equals(preset)) {
+ return this.requireGraphSpaceAuthorizationAdmin(graphSpace);
+ }
+ return this.requireMemberManagerByUsername(graphSpace, username);
+ }
+
+ private HugeClient requireMemberManager(String graphSpace,
+ String userId) {
+ HugeClient client = this.requireGraphSpaceManager(graphSpace);
+ User account = client.auth().getUser(userId);
+ E.checkNotNull(account, "User");
+ return this.requireMemberManagerByUsername(client, graphSpace, account.name());
+ }
+
+ private HugeClient requireMemberManagerByUsername(String graphSpace, String username) {
+ HugeClient client = this.requireGraphSpaceManager(graphSpace);
+ return this.requireMemberManagerByUsername(client, graphSpace, username);
+ }
+
+ private HugeClient requireMemberManagerByUsername(HugeClient client, String graphSpace, String username) {
+ E.checkArgument(username != null && !username.isEmpty(), "The account name can't be empty");
+ if (client.auth().listSuperAdmin().contains(username) ||
+ client.auth().listSpaceAdmin(graphSpace).contains(username)) {
+ return this.requireGraphSpaceAuthorizationAdmin(graphSpace);
+ }
+ return client;
+ }
}
diff --git a/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/controller/auth/LoginController.java b/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/controller/auth/LoginController.java
index b59b24b74..955909cf4 100644
--- a/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/controller/auth/LoginController.java
+++ b/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/controller/auth/LoginController.java
@@ -75,6 +75,10 @@ public class LoginController extends BaseController {
@PostMapping("/login")
public Object login(@RequestBody Login login) {
+ if (this.authMode != null && this.authMode.anonymous()) {
+ throw new ExternalException(HttpStatus.FORBIDDEN.value(),
+ "Authentication is disabled");
+ }
String address = this.getRequest().getRemoteAddr();
boolean pdEnabled = this.config.get(HubbleOptions.PD_ENABLED);
this.loginAttemptGuard.checkAllowed(login.name(), address);
@@ -100,6 +104,13 @@ public Object login(@RequestBody Login login) {
this.getRequest().changeSessionId();
this.setUser(login.name());
this.setToken(result.token());
+ // HugeGraph 1.7's Gremlin HTTP channel only accepts Basic auth.
+ // Keep the credential server-side for the session lifetime so
+ // graph queries can use the same identity as REST requests.
+ this.setSession(Constant.PASSWORD_KEY, login.password());
+ this.setSession(Constant.PASSWORD_EXPIRE_AT_KEY,
+ System.currentTimeMillis() +
+ TOKEN_EXPIRE_SECONDS * 1000L);
return user;
} catch (Throwable e) {
this.clearAuthSession();
@@ -225,6 +236,9 @@ private static UserEntity currentUser(String username) {
@GetMapping("/status")
public Object status() {
+ if (this.authMode != null && this.authMode.anonymous()) {
+ return ImmutableMap.of("level", "ANONYMOUS");
+ }
HugeClient client = authClient(null, null);
diff --git a/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/controller/auth/RoleController.java b/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/controller/auth/RoleController.java
index 06e0e268f..f7ed393f6 100644
--- a/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/controller/auth/RoleController.java
+++ b/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/controller/auth/RoleController.java
@@ -46,7 +46,7 @@ public class RoleController extends AuthController {
@GetMapping("list")
public List listName(@PathVariable("graphspace") String graphSpace) {
- HugeClient client = this.requireGraphSpaceManager(graphSpace);
+ HugeClient client = this.requireGraphSpaceAuthorizationAdmin(graphSpace);
return this.roleService.list(client, graphSpace,
this.userService.isSuperAdmin(client));
}
@@ -60,7 +60,7 @@ public IPage queryPage(
defaultValue = "1") int pageNo,
@RequestParam(name = "page_size", required = false,
defaultValue = "10") int pageSize) {
- HugeClient client = this.requireGraphSpaceManager(graphSpace);
+ HugeClient client = this.requireGraphSpaceAuthorizationAdmin(graphSpace);
return this.roleService.queryPage(
client, graphSpace, query, pageNo, pageSize,
this.userService.isSuperAdmin(client));
@@ -69,7 +69,7 @@ public IPage queryPage(
@GetMapping("{id}")
public Role get(@PathVariable("graphspace") String graphSpace,
@PathVariable("id") String roleId) {
- HugeClient client = this.requireGraphSpaceManager(graphSpace);
+ HugeClient client = this.requireGraphSpaceAuthorizationAdmin(graphSpace);
return this.roleService.get(client, graphSpace, roleId,
this.userService.isSuperAdmin(client));
}
@@ -77,7 +77,7 @@ public Role get(@PathVariable("graphspace") String graphSpace,
@PostMapping
public Role add(@PathVariable("graphspace") String graphSpace,
@RequestBody Role role) {
- HugeClient client = this.requireGraphSpaceManager(graphSpace);
+ HugeClient client = this.requireGraphSpaceAuthorizationAdmin(graphSpace);
role.graphSpace(graphSpace);
return this.roleService.insert(client, graphSpace, role);
}
@@ -86,7 +86,7 @@ public Role add(@PathVariable("graphspace") String graphSpace,
public Role update(@PathVariable("graphspace") String graphSpace,
@PathVariable("id") String id,
@RequestBody Map body) {
- HugeClient client = this.requireGraphSpaceManager(graphSpace);
+ HugeClient client = this.requireGraphSpaceAuthorizationAdmin(graphSpace);
boolean includeLegacy = this.userService.isSuperAdmin(client);
Role current = this.roleService.get(client, graphSpace, id,
includeLegacy);
@@ -108,7 +108,7 @@ public Role update(@PathVariable("graphspace") String graphSpace,
@DeleteMapping("{id}")
public void delete(@PathVariable("graphspace") String graphSpace,
@PathVariable("id") String id) {
- HugeClient client = this.requireGraphSpaceManager(graphSpace);
+ HugeClient client = this.requireGraphSpaceAuthorizationAdmin(graphSpace);
this.roleService.delete(client, graphSpace, id,
this.userService.isSuperAdmin(client));
}
diff --git a/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/controller/auth/TargetController.java b/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/controller/auth/TargetController.java
index a11368d11..a3d25e3c5 100644
--- a/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/controller/auth/TargetController.java
+++ b/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/controller/auth/TargetController.java
@@ -45,7 +45,7 @@ public class TargetController extends AuthController {
@GetMapping("list")
public List list(@PathVariable("graphspace") String graphSpace) {
- HugeClient client = this.requireGraphSpaceManager(graphSpace);
+ HugeClient client = this.requireGraphSpaceAuthorizationAdmin(graphSpace);
return this.targetService.list(client, graphSpace);
}
@@ -58,7 +58,7 @@ public IPage queryPage(
defaultValue = "1") int pageNo,
@RequestParam(name = "page_size", required = false,
defaultValue = "10") int pageSize) {
- HugeClient client = this.requireGraphSpaceManager(graphSpace);
+ HugeClient client = this.requireGraphSpaceAuthorizationAdmin(graphSpace);
return this.targetService.queryPage(client, graphSpace, query, pageNo,
pageSize);
}
@@ -66,14 +66,14 @@ public IPage queryPage(
@GetMapping("{id}")
public Target get(@PathVariable("graphspace") String graphSpace,
@PathVariable("id") String targetId) {
- HugeClient client = this.requireGraphSpaceManager(graphSpace);
+ HugeClient client = this.requireGraphSpaceAuthorizationAdmin(graphSpace);
return this.targetService.get(client, graphSpace, targetId);
}
@PostMapping
public Target add(@PathVariable("graphspace") String graphSpace,
@RequestBody Target target) {
- HugeClient client = this.requireGraphSpaceManager(graphSpace);
+ HugeClient client = this.requireGraphSpaceAuthorizationAdmin(graphSpace);
return this.targetService.add(client, graphSpace, target);
}
@@ -81,7 +81,7 @@ public Target add(@PathVariable("graphspace") String graphSpace,
public Target update(@PathVariable("graphspace") String graphSpace,
@PathVariable("id") String targetId,
@RequestBody Target target) {
- HugeClient client = this.requireGraphSpaceManager(graphSpace);
+ HugeClient client = this.requireGraphSpaceAuthorizationAdmin(graphSpace);
Target current = this.targetService.get(client, graphSpace, targetId);
current.resources(target.resources());
current.description(target.description());
@@ -91,7 +91,7 @@ public Target update(@PathVariable("graphspace") String graphSpace,
@DeleteMapping("{id}")
public void delete(@PathVariable("graphspace") String graphSpace,
@PathVariable("id") String targetId) {
- HugeClient client = this.requireGraphSpaceManager(graphSpace);
+ HugeClient client = this.requireGraphSpaceAuthorizationAdmin(graphSpace);
this.targetService.delete(client, graphSpace, targetId);
}
}
diff --git a/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/controller/graphs/GraphsController.java b/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/controller/graphs/GraphsController.java
index 8fb8ac697..8e2bef18f 100644
--- a/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/controller/graphs/GraphsController.java
+++ b/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/controller/graphs/GraphsController.java
@@ -333,9 +333,13 @@ public Map graphReadMode(
public Object clone(@PathVariable("graphspace") String graphspace,
@PathVariable("graph") String graph,
@RequestBody GraphCloneEntity graphCloneEntity) {
- return this.graphsService.clone(this.authClient(graphspace, graph),
- graphCloneEntity.convertMap(graphspace,
- graph));
+ HugeClient client = this.authClient(graphspace, graph);
+ String targetGraphSpace = graphCloneEntity.getGraphSpace() == null ?
+ graphspace :
+ graphCloneEntity.getGraphSpace();
+ this.requireGraphSpaceAccess(client, targetGraphSpace);
+ return this.graphsService.clone(
+ client, graphCloneEntity.convertMap(graphspace, graph));
}
//
//@Data
diff --git a/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/controller/ingest/IngestController.java b/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/controller/ingest/IngestController.java
index 7b7f97050..3fadd9af0 100644
--- a/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/controller/ingest/IngestController.java
+++ b/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/controller/ingest/IngestController.java
@@ -283,7 +283,8 @@ public Response createTask(@RequestBody IngestTaskRequest request) {
mapping.setEdgeMappings(edgeMappings);
GraphConnection connection = this.graphConnection(graphSpace, graph);
- HugeClient client = this.authClient(graphSpace, graph);
+ HugeClient client = this.requireGraphSpaceWrite(graphSpace);
+ client.assignGraph(graphSpace, graph);
LoadTask task = this.jobManagerService.createIngestTask(
job, mapping, connection, client);
Map data = new HashMap<>();
@@ -299,9 +300,7 @@ public Response taskList(
@RequestParam(name = "page_no", required = false, defaultValue = "1") int pageNo,
@RequestParam(name = "page_size", required = false, defaultValue = "10") int pageSize) {
- // list all jobs across all graphspaces - use empty strings to get all
- // We need to query without graphspace/graph filter for the ingest view
- IPage page = jobManagerService.listAll(pageNo, pageSize, query);
+ IPage page = this.visibleJobPage(pageNo, pageSize, query);
IPage result = page.convert(job -> {
TaskVO vo = new TaskVO();
@@ -367,11 +366,18 @@ public Response taskDetail(@PathVariable("id") int id) {
return Response.builder().status(Constant.STATUS_NOT_FOUND)
.message("Task not found: " + id).build();
}
+ this.requireJobAccess(job);
return Response.builder().status(Constant.STATUS_OK).data(job).build();
}
@DeleteMapping("/tasks/{id}")
public Response deleteTask(@PathVariable("id") int id) {
+ JobManager job = jobManagerService.get(id);
+ if (job == null) {
+ return Response.builder().status(Constant.STATUS_NOT_FOUND)
+ .message("Task not found: " + id).build();
+ }
+ this.requireJobWrite(job);
jobManagerService.remove(id);
return Response.builder().status(Constant.STATUS_OK).build();
}
@@ -383,6 +389,7 @@ public Response enableTask(@PathVariable("id") int id) {
return Response.builder().status(Constant.STATUS_NOT_FOUND)
.message("Task not found: " + id).build();
}
+ this.requireJobWrite(job);
job.setJobStatus(JobStatus.DEFAULT);
jobManagerService.update(job);
return Response.builder().status(Constant.STATUS_OK).build();
@@ -395,6 +402,7 @@ public Response disableTask(@PathVariable("id") int id) {
return Response.builder().status(Constant.STATUS_NOT_FOUND)
.message("Task not found: " + id).build();
}
+ this.requireJobWrite(job);
job.setJobStatus(JobStatus.FAILED);
jobManagerService.update(job);
return Response.builder().status(Constant.STATUS_OK).build();
@@ -408,6 +416,12 @@ public Response jobList(
@RequestParam(name = "page_no", required = false, defaultValue = "1") int pageNo,
@RequestParam(name = "page_size", required = false, defaultValue = "10") int pageSize) {
+ JobManager job = jobManagerService.get(taskId);
+ if (job == null) {
+ return Response.builder().status(Constant.STATUS_NOT_FOUND)
+ .message("Task not found: " + taskId).build();
+ }
+ this.requireJobAccess(job);
List tasks = loadTaskService.taskListByJob(taskId);
// Manual pagination
@@ -453,11 +467,18 @@ public Response jobDetail(@PathVariable("id") int id) {
return Response.builder().status(Constant.STATUS_NOT_FOUND)
.message("Job not found: " + id).build();
}
+ this.requireLoadTaskAccess(task);
return Response.builder().status(Constant.STATUS_OK).data(task).build();
}
@DeleteMapping("/jobs/{id}")
public Response deleteJob(@PathVariable("id") int id) {
+ LoadTask task = loadTaskService.get(id);
+ if (task == null) {
+ return Response.builder().status(Constant.STATUS_NOT_FOUND)
+ .message("Job not found: " + id).build();
+ }
+ this.requireLoadTaskWrite(task);
loadTaskService.remove(id);
return Response.builder().status(Constant.STATUS_OK).build();
}
@@ -466,7 +487,7 @@ public Response deleteJob(@PathVariable("id") int id) {
@GetMapping("/metrics/task")
public Response metricsTask() {
- List all = jobManagerService.listAll();
+ List all = this.visibleJobs("");
all.forEach(jobManagerService::refreshStatus);
long runningOnce = 0;
@@ -506,6 +527,75 @@ public Response metricsTask() {
// ===== Helpers =====
+ private IPage visibleJobPage(int pageNo, int pageSize,
+ String query) {
+ Set graphSpaces = this.visibleGraphSpaces();
+ IPage page = this.jobManagerService.listByGraphSpaces(
+ graphSpaces, pageNo, pageSize, query);
+ page.getRecords().forEach(jobManagerService::refreshStatus);
+ return page;
+ }
+
+ private List visibleJobs(String query) {
+ Set graphSpaces = this.visibleGraphSpaces();
+ List jobs =
+ this.jobManagerService.listByGraphSpaces(graphSpaces);
+ if (StringUtils.isEmpty(query)) {
+ return jobs;
+ }
+ return jobs.stream()
+ .filter(job -> StringUtils.contains(job.getJobName(), query))
+ .collect(Collectors.toList());
+ }
+
+ private Set visibleGraphSpaces() {
+ if (this.config == null ||
+ !this.config.get(HubbleOptions.PD_ENABLED)) {
+ return null;
+ }
+ HugeClient client = this.authClient(null, null);
+ if (this.authMode != null && this.authMode.anonymous()) {
+ return new LinkedHashSet<>(
+ this.graphSpaceAccessService.listAnonymous(client));
+ }
+ if (this.userService.isSuperAdmin(client)) {
+ return null;
+ }
+ return new LinkedHashSet<>(
+ this.graphSpaceAccessService.listAccessible(client));
+ }
+
+ private void requireJobAccess(JobManager job) {
+ if (job == null) {
+ return;
+ }
+ this.requireGraphSpaceAccess(this.authClient(null, null),
+ job.getGraphSpace());
+ }
+
+ private void requireJobWrite(JobManager job) {
+ if (job == null) {
+ return;
+ }
+ this.requireGraphSpaceWrite(job.getGraphSpace());
+ }
+
+ private void requireLoadTaskAccess(LoadTask task) {
+ JobManager job = task.getJobId() == null ? null :
+ this.jobManagerService.get(task.getJobId());
+ Ex.check(job != null, "job-manager.not-exist.id",
+ task.getJobId());
+ this.requireJobAccess(job);
+ }
+
+ private void requireLoadTaskWrite(LoadTask task) {
+ JobManager job = task.getJobId() == null ? null :
+ this.jobManagerService.get(task.getJobId());
+ Ex.check(job != null, "job-manager.not-exist.id",
+ task.getJobId());
+ this.requireJobWrite(job);
+ }
+
/**
* Same format-whitelist check as
* FileUploadController#checkFileValid
diff --git a/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/controller/langchain/LangChainController.java b/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/controller/langchain/LangChainController.java
index 8c1695fe0..3e39a0eb8 100644
--- a/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/controller/langchain/LangChainController.java
+++ b/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/controller/langchain/LangChainController.java
@@ -184,7 +184,7 @@ public Object gremlin(@PathVariable("graphspace") String graphSpace,
requestLangChainParams.userName, requestLangChainParams.password);
try {
- HugeClient client = this.authClient(graphSpace, graph);
+ HugeClient client = this.authGremlinClient(graphSpace, graph);
JsonView result =
this.queryService.executeSingleGremlinQuery(client, query);
return result.getData();
diff --git a/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/controller/load/FileMappingController.java b/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/controller/load/FileMappingController.java
index 39674b59e..43381c508 100644
--- a/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/controller/load/FileMappingController.java
+++ b/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/controller/load/FileMappingController.java
@@ -112,6 +112,7 @@ public void delete(@PathVariable("graphspace") String graphSpace,
@PathVariable("graph") String graph,
@PathVariable("jobId") int jobId,
@PathVariable("id") int id) {
+ this.requireGraphSpaceWrite(graphSpace);
FileMapping mapping = this.service.get(graphSpace, graph, jobId, id);
if (mapping == null) {
throw new ExternalException("load.file-mapping.not-exist.id", id);
@@ -125,6 +126,7 @@ public void delete(@PathVariable("graphspace") String graphSpace,
public void clear(@PathVariable("graphspace") String graphSpace,
@PathVariable("graph") String graph,
@PathVariable("jobId") int jobId) {
+ this.requireGraphSpaceWrite(graphSpace);
List mappings = this.service.listByJob(graphSpace, graph,
jobId);
Set fileIds = new HashSet<>();
@@ -143,6 +145,7 @@ public FileMapping fileSetting(@PathVariable("graphspace") String graphSpace,
@PathVariable("jobId") int jobId,
@PathVariable("id") int id,
@RequestBody FileSetting newEntity) {
+ this.requireGraphSpaceWrite(graphSpace);
Ex.check(!StringUtils.isEmpty(newEntity.getDelimiter()),
"load.file-mapping.file-setting.delimiter-cannot-be-empty");
Ex.check(!StringUtils.isEmpty(newEntity.getCharset()),
@@ -175,6 +178,7 @@ public FileMapping addVertexMapping(@PathVariable("graphspace") String graphSpac
@PathVariable("jobId") int jobId,
@PathVariable("id") int id,
@RequestBody VertexMapping newEntity) {
+ this.requireGraphSpaceWrite(graphSpace);
FileMapping mapping = this.service.get(graphSpace, graph, jobId, id);
if (mapping == null) {
throw new ExternalException("load.file-mapping.not-exist.id", id);
@@ -195,6 +199,7 @@ public FileMapping updateVertexMapping(@PathVariable("graphspace") String graphS
@PathVariable("id") int id,
@PathVariable("vmid") String vmId,
@RequestBody VertexMapping newEntity) {
+ this.requireGraphSpaceWrite(graphSpace);
FileMapping mapping = this.service.get(graphSpace, graph, jobId, id);
if (mapping == null) {
throw new ExternalException("load.file-mapping.not-exist.id", id);
@@ -221,6 +226,7 @@ public FileMapping deleteVertexMapping(
@PathVariable("jobId") int jobId,
@PathVariable("id") int id,
@PathVariable("vmid") String vmid) {
+ this.requireGraphSpaceWrite(graphSpace);
FileMapping mapping = this.service.get(graphSpace, graph, jobId, id);
if (mapping == null) {
throw new ExternalException("load.file-mapping.not-exist.id", id);
@@ -242,6 +248,7 @@ public FileMapping addEdgeMapping(@PathVariable("graphspace") String graphSpace,
@PathVariable("jobId") int jobId,
@PathVariable("id") int id,
@RequestBody EdgeMapping newEntity) {
+ this.requireGraphSpaceWrite(graphSpace);
FileMapping mapping = this.service.get(graphSpace, graph, jobId, id);
if (mapping == null) {
throw new ExternalException("load.file-mapping.not-exist.id", id);
@@ -262,6 +269,7 @@ public FileMapping updateEdgeMapping(@PathVariable("graphspace") String graphSpa
@PathVariable("id") int id,
@PathVariable("emid") String emId,
@RequestBody EdgeMapping newEntity) {
+ this.requireGraphSpaceWrite(graphSpace);
FileMapping mapping = this.service.get(graphSpace, graph, jobId, id);
if (mapping == null) {
throw new ExternalException("load.file-mapping.not-exist.id", id);
@@ -288,6 +296,7 @@ public FileMapping deleteEdgeMapping(
@PathVariable("jobId") int jobId,
@PathVariable("id") int id,
@PathVariable("emid") String emid) {
+ this.requireGraphSpaceWrite(graphSpace);
FileMapping mapping = this.service.get(graphSpace, graph, jobId, id);
if (mapping == null) {
throw new ExternalException("load.file-mapping.not-exist.id", id);
@@ -312,6 +321,7 @@ public void loadParameter(@PathVariable("graphspace") String graphSpace,
@PathVariable("graph") String graph,
@PathVariable("jobId") int jobId,
@RequestBody LoadParameter newEntity) {
+ this.requireGraphSpaceWrite(graphSpace);
this.checkLoadParameter(newEntity);
List mappings = this.service.listByJob(graphSpace, graph,
jobId);
@@ -327,6 +337,7 @@ public void loadParameter(@PathVariable("graphspace") String graphSpace,
public JobManager nextStep(@PathVariable("graphspace") String graphSpace,
@PathVariable("graph") String graph,
@PathVariable("jobId") int jobId) {
+ this.requireGraphSpaceWrite(graphSpace);
JobManager jobEntity = this.jobService.get(graphSpace, graph, jobId);
Ex.check(jobEntity != null, "job-manager.not-exist.id", jobId);
Ex.check(jobEntity.getJobStatus() == JobStatus.MAPPING,
diff --git a/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/controller/load/FileUploadController.java b/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/controller/load/FileUploadController.java
index 99c36da84..cbbe7cbc6 100644
--- a/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/controller/load/FileUploadController.java
+++ b/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/controller/load/FileUploadController.java
@@ -87,6 +87,7 @@ public Map fileToken(
@PathVariable("jobId") int jobId,
@RequestParam("names")
List fileNames) {
+ this.requireGraphSpaceWrite(graphSpace);
Ex.check(this.jobService.get(graphSpace, graph, jobId) != null,
"job-manager.not-exist.id", jobId);
Ex.check(CollectionUtil.allUnique(fileNames),
@@ -126,6 +127,7 @@ public FileUploadResult upload(@PathVariable("graphspace") String graphSpace,
@RequestParam("token") String token,
@RequestParam("total") int total,
@RequestParam("index") int index) {
+ this.requireGraphSpaceWrite(graphSpace);
this.checkTotalAndIndexValid(total, index);
this.checkFileNameValid(fileName);
this.checkFileNameMatchToken(fileName, token);
@@ -253,6 +255,7 @@ public Boolean delete(@PathVariable("graphspace") String graphSpace,
@PathVariable("jobId") int jobId,
@RequestParam("name") String fileName,
@RequestParam("token") String token) {
+ this.requireGraphSpaceWrite(graphSpace);
this.checkFileNameValid(fileName);
JobManager jobEntity = this.jobService.get(graphSpace, graph, jobId);
Ex.check(jobEntity != null, "job-manager.not-exist.id", jobId);
@@ -286,6 +289,7 @@ public Boolean delete(@PathVariable("graphspace") String graphSpace,
public JobManager nextStep(@PathVariable("graphspace") String graphSpace,
@PathVariable("graph") String graph,
@PathVariable("jobId") int jobId) {
+ this.requireGraphSpaceWrite(graphSpace);
JobManager jobEntity = this.jobService.get(graphSpace, graph, jobId);
Ex.check(jobEntity != null, "job-manager.not-exist.id", jobId);
Ex.check(jobEntity.getJobStatus() == JobStatus.UPLOADING,
diff --git a/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/controller/load/JobManagerController.java b/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/controller/load/JobManagerController.java
index f3c58a3e5..a8fb2e731 100644
--- a/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/controller/load/JobManagerController.java
+++ b/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/controller/load/JobManagerController.java
@@ -23,6 +23,7 @@
import org.apache.commons.lang3.StringUtils;
import org.apache.hugegraph.common.Constant;
import org.apache.hugegraph.common.Response;
+import org.apache.hugegraph.controller.BaseController;
import org.apache.hugegraph.entity.enums.JobStatus;
import org.apache.hugegraph.entity.enums.LoadStatus;
import org.apache.hugegraph.entity.load.FileMapping;
@@ -54,7 +55,7 @@
@RestController
@RequestMapping(Constant.API_VERSION + "graphspaces/{graphspace}/graphs" +
"/{graph}/job-manager")
-public class JobManagerController {
+public class JobManagerController extends BaseController {
private static final int LIMIT = 500;
@@ -73,6 +74,7 @@ public JobManagerController(JobManagerService service) {
public JobManager create(@PathVariable("graphspace") String graphSpace,
@PathVariable("graph") String graph,
@RequestBody JobManager entity) {
+ this.requireGraphSpaceWrite(graphSpace);
synchronized (this.service) {
Ex.check(!StringUtils.isEmpty(entity.getJobName()),
"common.param.cannot-be-null-or-empty", "job_name");
@@ -111,6 +113,7 @@ public JobManager create(@PathVariable("graphspace") String graphSpace,
public void delete(@PathVariable("graphspace") String graphSpace,
@PathVariable("graph") String graph,
@PathVariable("id") int id) {
+ this.requireGraphSpaceWrite(graphSpace);
this.service.deleteJob(graphSpace, graph, id);
}
@@ -155,6 +158,7 @@ public JobManager update(@PathVariable("graphspace") String graphSpace,
@PathVariable("graph") String graph,
@PathVariable("id") int id,
@RequestBody JobManager newEntity) {
+ this.requireGraphSpaceWrite(graphSpace);
Ex.check(!StringUtils.isEmpty(newEntity.getJobName()),
"common.param.cannot-be-null-or-empty", "job_name");
Ex.check(newEntity.getJobName().length() <= 48,
diff --git a/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/controller/load/LoadTaskController.java b/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/controller/load/LoadTaskController.java
index 66f534c18..5b45e7800 100644
--- a/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/controller/load/LoadTaskController.java
+++ b/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/controller/load/LoadTaskController.java
@@ -113,6 +113,7 @@ public LoadTask create(@PathVariable("graphspace") String graphSpace,
@PathVariable("graph") String graph,
@PathVariable("jobId") int jobId,
@RequestBody LoadTask entity) {
+ this.requireGraphSpaceWrite(graphSpace);
JobManager jobEntity = this.jobService.get(graphSpace, graph, jobId);
Ex.check(jobEntity != null, "job-manager.not-exist.id", jobId);
Ex.check(jobEntity.getJobStatus() == JobStatus.SETTING,
@@ -133,6 +134,7 @@ public void delete(@PathVariable("graphspace") String graphSpace,
@PathVariable("graph") String graph,
@PathVariable("jobId") int jobId,
@PathVariable("id") int id) {
+ this.requireGraphSpaceWrite(graphSpace);
LoadTask task = this.service.get(graphSpace, graph, jobId, id);
if (task == null) {
throw new ExternalException("load.task.not-exist.id", id);
@@ -152,6 +154,7 @@ public List start(@PathVariable("graphspace") String graphSpace,
@PathVariable("jobId") int jobId,
@RequestParam("file_mapping_ids")
List fileIds) {
+ this.requireGraphSpaceWrite(graphSpace);
GraphConnection connection = new GraphConnection();
connection.setCluster(config.get(HubbleOptions.PD_CLUSTER));
@@ -205,6 +208,7 @@ public LoadTask pause(@PathVariable("graphspace") String graphSpace,
@PathVariable("graph") String graph,
@PathVariable("jobId") int jobId,
@RequestParam("task_id") int taskId) {
+ this.requireGraphSpaceWrite(graphSpace);
JobManager jobEntity = this.jobService.get(graphSpace, graph, jobId);
Ex.check(jobEntity != null, "job-manager.not-exist.id", jobId);
Ex.check(this.service.get(graphSpace, graph, jobId, taskId) != null,
@@ -225,6 +229,7 @@ public LoadTask resume(@PathVariable("graphspace") String graphSpace,
@PathVariable("graph") String graph,
@PathVariable("jobId") int jobId,
@RequestParam("task_id") int taskId) {
+ this.requireGraphSpaceWrite(graphSpace);
JobManager jobEntity = this.jobService.get(graphSpace, graph, jobId);
Ex.check(jobEntity != null, "job-manager.not-exist.id", jobId);
Ex.check(this.service.get(graphSpace, graph, jobId, taskId) != null,
@@ -245,6 +250,7 @@ public LoadTask stop(@PathVariable("graphspace") String graphSpace,
@PathVariable("graph") String graph,
@PathVariable("jobId") int jobId,
@RequestParam("task_id") int taskId) {
+ this.requireGraphSpaceWrite(graphSpace);
JobManager jobEntity = this.jobService.get(graphSpace, graph, jobId);
Ex.check(jobEntity != null, "job-manager.not-exist.id", jobId);
Ex.check(this.service.get(graphSpace, graph, jobId, taskId) != null,
@@ -265,6 +271,7 @@ public LoadTask retry(@PathVariable("graphspace") String graphSpace,
@PathVariable("graph") String graph,
@PathVariable("jobId") int jobId,
@RequestParam("task_id") int taskId) {
+ this.requireGraphSpaceWrite(graphSpace);
JobManager jobEntity = this.jobService.get(graphSpace, graph, jobId);
Ex.check(jobEntity != null, "job-manager.not-exist.id", jobId);
Ex.check(this.service.get(graphSpace, graph, jobId, taskId) != null,
diff --git a/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/controller/op/OperationsController.java b/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/controller/op/OperationsController.java
index a8eb01420..ab8371bea 100644
--- a/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/controller/op/OperationsController.java
+++ b/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/controller/op/OperationsController.java
@@ -100,6 +100,9 @@ private Set currentCapabilities() {
}
private Set currentCapabilities(HugeClient client) {
+ if (this.authMode != null && this.authMode.anonymous()) {
+ return OperationsCapabilityService.forLevel("ADMIN");
+ }
String level = this.userService.userLevel(client, this.getUser());
return OperationsCapabilityService.forLevel(level);
}
diff --git a/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/controller/query/ExecuteHistoryController.java b/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/controller/query/ExecuteHistoryController.java
index b7f1fe24c..945297a38 100644
--- a/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/controller/query/ExecuteHistoryController.java
+++ b/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/controller/query/ExecuteHistoryController.java
@@ -65,14 +65,19 @@ public ExecuteHistory get(@PathVariable("graphspace") String graphSpace,
@PathVariable("graph") String graph,
@PathVariable("id") int id) {
HugeClient client = this.authClient(graphSpace, graph);
- return this.service.get(client, id);
+ ExecuteHistory history = this.service.get(client, id);
+ if (history == null) {
+ throw new ExternalException("execute-history.not-exist.id", id);
+ }
+ return history;
}
@DeleteMapping("{id}")
public ExecuteHistory delete(@PathVariable("graphspace") String graphSpace,
@PathVariable("graph") String graph,
@PathVariable("id") int id) {
- HugeClient client = this.authClient(graphSpace, graph);
+ HugeClient client = this.requireGraphSpaceWrite(graphSpace);
+ client.assignGraph(graphSpace, graph);
ExecuteHistory oldEntity = this.service.get(client, id);
if (oldEntity == null) {
throw new ExternalException("execute-history.not-exist.id", id);
diff --git a/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/controller/query/GremlinCollectionController.java b/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/controller/query/GremlinCollectionController.java
index 8f66c8590..922b131c1 100644
--- a/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/controller/query/GremlinCollectionController.java
+++ b/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/controller/query/GremlinCollectionController.java
@@ -105,14 +105,18 @@ public IPage list(@PathVariable("graphspace") String graphSpa
}
@GetMapping("{id}")
- public GremlinCollection get(@PathVariable("id") int id) {
- return this.service.get(id);
+ public GremlinCollection get(
+ @PathVariable("graphspace") String graphSpace,
+ @PathVariable("graph") String graph,
+ @PathVariable("id") int id) {
+ return this.service.get(graphSpace, graph, id);
}
@PostMapping
public GremlinCollection create(@PathVariable("graphspace") String graphSpace,
@PathVariable("graph") String graph,
@RequestBody GremlinCollection newEntity) {
+ this.requireGraphSpaceWrite(graphSpace);
this.checkParamsValid(newEntity, true);
newEntity.setGraphSpace(graphSpace);
newEntity.setGraph(graph);
@@ -128,29 +132,39 @@ public GremlinCollection create(@PathVariable("graphspace") String graphSpace,
}
@PutMapping("{id}")
- public GremlinCollection update(@PathVariable("id") int id,
+ public GremlinCollection update(
+ @PathVariable("graphspace") String graphSpace,
+ @PathVariable("graph") String graph,
+ @PathVariable("id") int id,
@RequestBody GremlinCollection newEntity) {
+ this.requireGraphSpaceWrite(graphSpace);
this.checkIdSameAsBody(id, newEntity);
this.checkParamsValid(newEntity, false);
- GremlinCollection oldEntity = this.service.get(id);
+ GremlinCollection oldEntity = this.service.get(graphSpace, graph, id);
if (oldEntity == null) {
throw new ExternalException("gremlin-collection.not-exist.id", id);
}
GremlinCollection entity = this.mergeEntity(oldEntity, newEntity);
+ entity.setGraphSpace(graphSpace);
+ entity.setGraph(graph);
this.checkEntityUnique(entity, false);
- this.service.update(entity);
+ this.service.update(graphSpace, graph, entity);
return entity;
}
@DeleteMapping("{id}")
- public GremlinCollection delete(@PathVariable("id") int id) {
- GremlinCollection oldEntity = this.service.get(id);
+ public GremlinCollection delete(
+ @PathVariable("graphspace") String graphSpace,
+ @PathVariable("graph") String graph,
+ @PathVariable("id") int id) {
+ this.requireGraphSpaceWrite(graphSpace);
+ GremlinCollection oldEntity = this.service.get(graphSpace, graph, id);
if (oldEntity == null) {
throw new ExternalException("gremlin-collection.not-exist.id", id);
}
- this.service.remove(id);
+ this.service.remove(graphSpace, graph, id);
return oldEntity;
}
diff --git a/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/controller/saas/SaasGraphViewController.java b/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/controller/saas/SaasGraphViewController.java
index fd34bef72..b06c6ac9b 100644
--- a/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/controller/saas/SaasGraphViewController.java
+++ b/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/controller/saas/SaasGraphViewController.java
@@ -84,7 +84,7 @@ public GremlinResult execute(@PathVariable("graphspace") String graphSpace,
StopWatch timer = StopWatch.createStarted();
try {
- HugeClient client = this.authClient(graphSpace, graph);
+ HugeClient client = this.authGremlinClient(graphSpace, graph);
GremlinResult result =
this.queryService.executeGremlinQuery(client,
query.convert2GremlinQuery());
@@ -124,7 +124,7 @@ public Map executeAsyncTask(
Map result = new HashMap<>(3);
try {
- HugeClient client = this.authClient(graphSpace, graph);
+ HugeClient client = this.authGremlinClient(graphSpace, graph);
asyncId = this.queryService.executeGremlinAsyncTask(client,
query.convert2GremlinQuery());
status = ExecuteStatus.ASYNC_TASK_SUCCESS;
diff --git a/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/controller/schema/SchemaController.java b/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/controller/schema/SchemaController.java
index 58009368b..d209e8733 100644
--- a/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/controller/schema/SchemaController.java
+++ b/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/controller/schema/SchemaController.java
@@ -91,7 +91,7 @@ public Object schemaGroovy(@PathVariable("graphspace") String graphSpace,
public Object addSchemaGroovy(@PathVariable("graphspace") String graphSpace,
@PathVariable("graph") String graph,
@RequestBody SchemaGroovy schemaGroovy) {
- HugeClient client = this.authClient(graphSpace, graph);
+ HugeClient client = this.authGremlinClient(graphSpace, graph);
String content = schemaGroovy.getSchemaGroovy();
log.info("Add schema groovy: {}", content);
checkSchemaGroovy(content);
diff --git a/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/controller/space/GraphSpaceController.java b/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/controller/space/GraphSpaceController.java
index b8c3bc712..60fb08a8a 100644
--- a/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/controller/space/GraphSpaceController.java
+++ b/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/controller/space/GraphSpaceController.java
@@ -34,7 +34,6 @@
import org.apache.hugegraph.service.graphs.GraphsService;
import org.apache.hugegraph.service.space.GraphSpaceService;
import org.apache.hugegraph.util.E;
-import org.apache.hugegraph.util.PageUtil;
import org.apache.hugegraph.util.UrlUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.DeleteMapping;
@@ -81,8 +80,15 @@ public Object list() {
Collections.singletonList("DEFAULT"));
}
- List graphSpaces =
- this.graphSpaceService.listAll(this.authClient(null, null));
+ HugeClient client = this.authClient(null, null);
+ List graphSpaces;
+ if (this.authMode != null && this.authMode.anonymous()) {
+ graphSpaces = this.graphSpaceService.listAnonymous(client);
+ } else if (this.userService.isSuperAdmin(client)) {
+ graphSpaces = this.graphSpaceService.listAll(client);
+ } else {
+ graphSpaces = this.graphSpaceService.listAccessible(client);
+ }
return ImmutableMap.of("graphspaces", graphSpaces);
}
@@ -101,6 +107,14 @@ public Object queryPage(@RequestParam(name = "query", required = false,
return ImmutableMap.of("records", Collections.emptyList(),
"total", 0);
}
+ if (this.authMode != null && this.authMode.anonymous()) {
+ HugeClient client = this.authClient(null, null);
+ return all ?
+ this.graphSpaceService.queryAnonymousGs(client, query,
+ createTime) :
+ this.graphSpaceService.queryAnonymousGsPage(
+ client, query, createTime, pageNo, pageSize);
+ }
if (all) {
HugeClient client = this.authClient(null, null);
return this.userService.isSuperAdmin(client) ?
@@ -113,8 +127,8 @@ public Object queryPage(@RequestParam(name = "query", required = false,
return this.userService.isSuperAdmin(client) ?
graphSpaceService.queryPage(client, query, createTime,
pageNo, pageSize) :
- PageUtil.page(graphSpaceService.queryAccessibleGs(
- client, query, createTime), pageNo, pageSize);
+ graphSpaceService.queryAccessibleGsPage(
+ client, query, createTime, pageNo, pageSize);
}
@GetMapping("{graphspace}/auth")
@@ -122,8 +136,17 @@ public Object isAuth(@PathVariable("graphspace") String graphSpace) {
if (!isPdEnabled()) {
return ImmutableMap.of("auth", false);
}
- boolean isAuth = graphSpaceService.isAuth(this.authClient(null, null),
- graphSpace);
+ HugeClient client = this.authClient(null, null);
+ boolean isAuth;
+ if (this.authMode != null && this.authMode.anonymous()) {
+ isAuth = this.graphSpaceService.isAuthForAnonymous(client,
+ graphSpace);
+ } else if (this.userService.isSuperAdmin(client)) {
+ isAuth = this.graphSpaceService.isAuth(client, graphSpace);
+ } else {
+ isAuth = this.graphSpaceService.isAuthForAccessible(client,
+ graphSpace);
+ }
return ImmutableMap.of("auth", isAuth);
}
@@ -137,9 +160,15 @@ public Object get(@PathVariable("graphspace") String graphspace) {
return this.graphSpaceService.toView(stub);
}
HugeClient client = this.authClient(null, null);
- // Get GraphSpace Info
- return graphSpaceService.toView(
- graphSpaceService.getWithAdmins(client, graphspace));
+ if (this.authMode != null && this.authMode.anonymous()) {
+ return this.graphSpaceService.getAnonymous(client, graphspace);
+ }
+ GraphSpaceEntity entity = this.userService.isSuperAdmin(client) ?
+ graphSpaceService.getWithAdmins(client,
+ graphspace) :
+ graphSpaceService.getAccessibleWithAdmins(
+ client, graphspace);
+ return graphSpaceService.toView(entity);
}
@PostMapping
diff --git a/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/controller/space/SchemaTemplateController.java b/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/controller/space/SchemaTemplateController.java
index 2e887a342..f24182121 100644
--- a/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/controller/space/SchemaTemplateController.java
+++ b/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/controller/space/SchemaTemplateController.java
@@ -19,6 +19,8 @@
package org.apache.hugegraph.controller.space;
import java.util.List;
+import java.util.Map;
+import java.util.Objects;
import com.google.common.collect.ImmutableMap;
import org.springframework.beans.factory.annotation.Autowired;
@@ -36,6 +38,7 @@
import org.apache.hugegraph.config.HugeConfig;
import org.apache.hugegraph.controller.BaseController;
import org.apache.hugegraph.driver.HugeClient;
+import org.apache.hugegraph.exception.ForbiddenException;
import org.apache.hugegraph.options.HubbleOptions;
import org.apache.hugegraph.service.space.SchemaTemplateService;
import org.apache.hugegraph.structure.space.SchemaTemplate;
@@ -94,7 +97,7 @@ public Object create(@PathVariable("graphspace") String graphSpace,
@RequestBody SchemaTemplate schemaTemplate) {
E.checkArgument(isPdEnabled(),
"Schema template is not supported in standalone mode");
- HugeClient client = this.authClient(graphSpace, null);
+ HugeClient client = this.requireGraphSpaceWrite(graphSpace);
return schemaTemplateService.create(client, schemaTemplate);
}
@@ -102,7 +105,8 @@ public Object create(@PathVariable("graphspace") String graphSpace,
@DeleteMapping("{name}")
public void delete(@PathVariable("graphspace") String graphSpace,
@PathVariable("name") String name) {
- HugeClient client = this.authClient(graphSpace, null);
+ HugeClient client = this.requireTemplateOwnerOrManager(graphSpace,
+ name);
schemaTemplateService.delete(client, name);
}
@@ -110,8 +114,28 @@ public void delete(@PathVariable("graphspace") String graphSpace,
public Object update(@PathVariable("graphspace") String graphSpace,
@PathVariable("name") String name,
@RequestBody SchemaTemplate schemaTemplate) {
- HugeClient client = this.authClient(graphSpace, null);
+ HugeClient client = this.requireTemplateOwnerOrManager(graphSpace,
+ name);
schemaTemplate.name(name);
return schemaTemplateService.update(client, schemaTemplate);
}
+
+ private HugeClient requireTemplateOwnerOrManager(String graphSpace,
+ String name) {
+ HugeClient client = this.requireGraphSpaceWrite(graphSpace);
+ if (this.authMode != null && this.authMode.anonymous()) {
+ return client;
+ }
+ if (this.userService.isSuperAdmin(client) ||
+ this.userService.isAssignSpaceAdmin(client, graphSpace)) {
+ return client;
+ }
+
+ Map template = this.schemaTemplateService.get(client, name);
+ if (!Objects.equals(this.getUser(), template.get("creator"))) {
+ throw new ForbiddenException(
+ "Permission denied: modify schema template");
+ }
+ return client;
+ }
}
diff --git a/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/controller/space/VermeerController.java b/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/controller/space/VermeerController.java
index 27508d836..4206a8fec 100644
--- a/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/controller/space/VermeerController.java
+++ b/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/controller/space/VermeerController.java
@@ -67,7 +67,7 @@ public void load(@RequestBody JsonLoad body) {
String graphspace = body.graphspace;
String graph = body.graph;
String vGraph = vermeerService.convert2VG(graphspace, graph);
- HugeClient client = this.authClient(null, null);
+ HugeClient client = this.authClient(graphspace, graph);
Map graphInfo = HubbleUtil.uncheckedCast(
client.vermeer().getGraphInfoByName(vGraph).get("graph"));
diff --git a/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/entity/auth/RoleEntity.java b/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/entity/auth/RoleEntity.java
index 5da056121..89c6b4e2f 100644
--- a/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/entity/auth/RoleEntity.java
+++ b/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/entity/auth/RoleEntity.java
@@ -19,7 +19,6 @@
package org.apache.hugegraph.entity.auth;
import com.fasterxml.jackson.annotation.JsonProperty;
-import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@@ -28,7 +27,6 @@
@Data
@NoArgsConstructor
-@AllArgsConstructor
@Builder
public class RoleEntity implements Identifiable {
@@ -37,4 +35,17 @@ public class RoleEntity implements Identifiable {
@JsonProperty("role_name")
private String name;
+
+ @JsonProperty("permission_preset")
+ private String permissionPreset;
+
+ public RoleEntity(String id, String name) {
+ this(id, name, null);
+ }
+
+ public RoleEntity(String id, String name, String permissionPreset) {
+ this.id = id;
+ this.name = name;
+ this.permissionPreset = permissionPreset;
+ }
}
diff --git a/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/entity/auth/UserEntity.java b/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/entity/auth/UserEntity.java
index 9e3712819..f7953c5d3 100644
--- a/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/entity/auth/UserEntity.java
+++ b/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/entity/auth/UserEntity.java
@@ -30,6 +30,7 @@
import java.util.Date;
import java.util.List;
+import java.util.Map;
@Data
@NoArgsConstructor
@@ -75,6 +76,12 @@ public class UserEntity implements Identifiable {
@JsonProperty("resSpaces")
protected List resSpaces;
+ @JsonProperty("permission_preset")
+ private String permissionPreset;
+
+ @JsonProperty("graphspace_permissions")
+ private List