Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
77cee94
feat(api): add Hubble-compatible graph profile and management endpoints
Yeaury Apr 24, 2026
42f3dfd
feat(auth): implement default role management for GraphSpace
Yeaury Apr 24, 2026
e5e5028
feat(api): add SchemaTemplate CRUD API
Yeaury Apr 24, 2026
19af891
fix(auth): delegate default graph/role methods in AuthManagerProxy
Yeaury Apr 24, 2026
1a810c0
fix(api):Add createByText for text/plain graph creation compatibility
Yeaury Apr 24, 2026
01e64e8
fix(api):keep consistent with the License header
Yeaury Apr 25, 2026
79ea319
fix: address code review issues for Hubble 2.0 API adaptation (#3008)
Yeaury Apr 25, 2026
f722cd5
fix: ensure HugeGraph Server is usable in non-PD (standalone RocksDB)…
Yeaury Apr 26, 2026
9ac0f2b
fix(api): Fix bugs related to updating user information and passwords.
Yeaury Apr 26, 2026
34222e5
fix(api): fix user lookup to use getUserByName() instead of getUser()…
Yeaury Apr 29, 2026
70d88fb
fix(gremlin): register ContextGremlinServer listener before loading g…
Yeaury May 1, 2026
35002f7
fix(auth): initialize creator field in StandardAuthManagerV2 create m…
Yeaury May 1, 2026
34673b7
fix(api): add missing isPrefix helper method in GraphsAPI
Yeaury May 5, 2026
f868dbc
fix(auth): make unsetDefaultGraph idempotent in StandardAuthManagerV2
Yeaury May 5, 2026
6ddfb17
fix(api): address Copilot review issues in Hubble 2.0 API adaptation
Yeaury May 5, 2026
b67df7b
fix(api): fix compilation error and address imbajin review issues
Yeaury May 5, 2026
2a17b8d
fix(api): route default graph ops through AuthManager in all modes
Yeaury May 5, 2026
dc011c0
fix(auth): address Copilot review issues in auth schema and belong bi…
Yeaury May 5, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

package org.apache.hugegraph.api;

import java.time.format.DateTimeFormatter;
import java.util.Collection;
import java.util.Map;
import java.util.concurrent.Callable;
Expand Down Expand Up @@ -89,6 +90,13 @@ public class API {
private static final String STANDALONE_ERROR =
"GraphSpace management is not supported in standalone mode";

/**
* Shared date formatter for API response timestamps (thread-safe, reusable).
* Example output: {@code "2024-05-01 12:30:00"}
*/
protected static final DateTimeFormatter DATE_FORMATTER =
DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");

public static HugeGraph graph(GraphManager manager, String graphSpace,
String graph) {
HugeGraph g = manager.graph(graphSpace, graph);
Expand Down Expand Up @@ -282,6 +290,28 @@ public static void validPermission(boolean hasPermission, String user,
}
}

/**
* Returns {@code true} if the profile's {@code "name"} or {@code "nickname"}
* field starts with the given prefix. Returns {@code true} when prefix is
* null or empty (i.e. no filtering).
*
* <p>Used by {@code GraphsAPI.listProfile} and {@code GraphSpaceAPI.listProfile}
* to filter results by a user-supplied prefix string.
*
* @param profile a map containing at least a {@code "name"} key
* @param prefix the prefix to match against; ignored when blank
* @return whether the entry matches the prefix filter
*/
protected static boolean isPrefix(Map<String, Object> profile, String prefix) {
if (prefix == null || prefix.isEmpty()) {
return true;
}
String name = profile.get("name").toString();
Object nicknameObj = profile.get("nickname");
String nickname = nicknameObj != null ? nicknameObj.toString() : "";
return name.startsWith(prefix) || nickname.startsWith(prefix);
}

public static class ApiMeasurer {

public static final String EDGE_ITER = "edge_iterations";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,12 @@
import java.util.ArrayList;
import java.util.List;

import org.apache.commons.lang3.StringUtils;

import org.apache.hugegraph.api.API;
import org.apache.hugegraph.api.filter.StatusFilter;
import org.apache.hugegraph.auth.AuthManager;
import org.apache.hugegraph.auth.HugeDefaultRole;
import org.apache.hugegraph.auth.HugeGraphAuthProxy;
import org.apache.hugegraph.auth.HugePermission;
import org.apache.hugegraph.core.GraphManager;
Expand Down Expand Up @@ -259,6 +262,46 @@ public String getRolesInGs(@Context GraphManager manager,
result));
}

@GET
@Timed
@Path("default")
@Consumes(APPLICATION_JSON)
public String checkDefaultRole(@Context GraphManager manager,
@PathParam("graphspace") String graphSpace,
@QueryParam("role") String role,
@QueryParam("graph") String graph) {
LOG.debug("check if current user is default role: {} {} {}",
role, graphSpace, graph);
Comment thread
Yeaury marked this conversation as resolved.
ensurePdModeEnabled(manager);
AuthManager authManager = manager.authManager();
String user = HugeGraphAuthProxy.username();

E.checkArgument(StringUtils.isNotEmpty(role) &&
StringUtils.isNotEmpty(graphSpace),
"Must pass graphspace and role params");

HugeDefaultRole defaultRole;
try {
defaultRole = HugeDefaultRole.valueOf(role.toUpperCase());
} catch (IllegalArgumentException e) {
E.checkArgument(false, "Invalid role value '%s'", role);
defaultRole = null; // unreachable, satisfies compiler
}
boolean hasGraph = defaultRole.equals(HugeDefaultRole.OBSERVER);
E.checkArgument(!hasGraph || StringUtils.isNotEmpty(graph),
"Must set a graph for observer");

boolean result;
if (hasGraph) {
result = authManager.isDefaultRole(graphSpace, graph, user,
defaultRole);
} else {
result = authManager.isDefaultRole(graphSpace, user,
defaultRole);
}
return manager.serializer().writeMap(ImmutableMap.of("check", result));
}

private void validUser(AuthManager authManager, String user) {
E.checkArgument(authManager.findUser(user) != null ||
authManager.findGroup(user) != null,
Expand All @@ -274,7 +317,7 @@ private void validType(HugePermission type) {

private void validGraphSpace(GraphManager manager, String graphSpace) {
E.checkArgument(manager.graphSpace(graphSpace) != null,
"The graph space is not exist");
"The graph space '%s' does not exist", graphSpace);
}

private static class JsonManager implements Checkable {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -108,9 +108,20 @@ public String update(@Context GraphManager manager,
public String list(@Context GraphManager manager,
@Parameter(description = "The graph space name")
@PathParam("graphspace") String graphSpace,
@Parameter(description = "Filter by user name")
@QueryParam("name") String name,
@Parameter(description = "The limit of results to return")
@QueryParam("limit") @DefaultValue("100") long limit) {
LOG.debug("GraphSpace [{}] list users", graphSpace);
LOG.debug("GraphSpace [{}] list users, name={}", graphSpace, name);

if (StringUtils.isNotEmpty(name)) {
HugeUser user = manager.authManager().findUser(name);
if (user == null) {
throw new NotFoundException("Can't find user with name '%s'",
name);
}
return manager.serializer().writeAuthElement(user);
}

List<HugeUser> users = manager.authManager().listAllUsers(limit);
return manager.serializer().writeAuthElements("users", users);
Expand Down Expand Up @@ -178,6 +189,9 @@ private static class JsonUser implements Checkable {
@JsonProperty("user_password")
@Schema(description = "The user password", required = true)
private String password;
@JsonProperty("user_nickname")
@Schema(description = "The user nickname")
private String nickname;
@JsonProperty("user_phone")
@Schema(description = "The user phone number")
private String phone;
Expand All @@ -197,6 +211,9 @@ public HugeUser build(HugeUser user) {
if (this.password != null) {
user.password(StringEncoding.hashPassword(this.password));
}
if (this.nickname != null) {
user.nickname(this.nickname);
}
if (this.phone != null) {
user.phone(this.phone);
}
Expand All @@ -215,6 +232,7 @@ public HugeUser build(HugeUser user) {
public HugeUser build() {
HugeUser user = new HugeUser(this.name);
user.password(StringEncoding.hashPassword(this.password));
user.nickname(this.nickname);
user.phone(this.phone);
user.email(this.email);
user.avatar(this.avatar);
Expand All @@ -233,10 +251,12 @@ public void checkCreate(boolean isBatch) {
@Override
public void checkUpdate() {
E.checkArgument(!StringUtils.isEmpty(this.password) ||
this.nickname != null ||
this.phone != null ||
this.email != null ||
this.avatar != null,
"Expect one of user password/phone/email/avatar]");
this.avatar != null ||
this.description != null,
"Expect one of user password/nickname/phone/email/avatar/description");
}
}
}
Loading
Loading