3030import javax .xml .bind .DatatypeConverter ;
3131
3232import org .apache .hugegraph .auth .HugeAuthenticator ;
33- import org .apache .hugegraph .auth .HugeAuthenticator .RequiredPerm ;
34- import org .apache .hugegraph .auth .HugeAuthenticator .RolePerm ;
3533import org .apache .hugegraph .auth .HugeAuthenticator .User ;
34+ import org .apache .hugegraph .auth .HugeGraphAuthProxy ;
3635import org .apache .hugegraph .auth .RolePermission ;
3736import org .apache .hugegraph .config .HugeConfig ;
3837import org .apache .hugegraph .core .GraphManager ;
5453import jakarta .ws .rs .Priorities ;
5554import jakarta .ws .rs .container .ContainerRequestContext ;
5655import jakarta .ws .rs .container .ContainerRequestFilter ;
56+ import jakarta .ws .rs .container .ContainerResponseContext ;
57+ import jakarta .ws .rs .container .ContainerResponseFilter ;
5758import jakarta .ws .rs .container .PreMatching ;
5859import jakarta .ws .rs .core .Context ;
5960import jakarta .ws .rs .core .HttpHeaders ;
6465@ Provider
6566@ PreMatching
6667@ Priority (Priorities .AUTHENTICATION )
67- public class AuthenticationFilter implements ContainerRequestFilter {
68+ public class AuthenticationFilter implements ContainerRequestFilter , ContainerResponseFilter {
6869
6970 public static final String BASIC_AUTH_PREFIX = "Basic " ;
7071 public static final String BEARER_TOKEN_PREFIX = "Bearer " ;
7172
73+ public static final String ALL_GRAPH_SPACES = "*" ;
74+
7275 private static final Logger LOG = Log .logger (AuthenticationFilter .class );
7376
7477 private static final AntPathMatcher MATCHER = new AntPathMatcher ();
@@ -97,11 +100,38 @@ public void filter(ContainerRequestContext context) throws IOException {
97100 if (isWhiteAPI (context )) {
98101 return ;
99102 }
103+ GraphManager manager = this .managerProvider .get ();
100104 User user = this .authenticate (context );
101- Authorizer authorizer = new Authorizer (user , context .getUriInfo ());
105+
106+ // Inject request graph space into AuthContext for permission check
107+ // Extract graphspace from path like: /graphspaces/{graphspace}/...
108+ String path = context .getUriInfo ().getPath ();
109+ LOG .debug ("AuthenticationFilter: path={}" , path );
110+ if (path != null && path .contains ("graphspaces/" )) {
111+ String [] parts = path .split ("/" );
112+ for (int i = 0 ; i < parts .length - 1 ; i ++) {
113+ if ("graphspaces" .equals (parts [i ]) && i + 1 < parts .length ) {
114+ String requestGraphSpace = parts [i + 1 ];
115+ HugeGraphAuthProxy .setRequestGraphSpace (requestGraphSpace );
116+ LOG .debug ("AuthenticationFilter: set RequestGraphSpace={}" , requestGraphSpace );
117+ break ;
118+ }
119+ }
120+ }
121+
122+ Authorizer authorizer = new Authorizer (manager , user , context .getUriInfo ());
102123 context .setSecurityContext (authorizer );
103124 }
104125
126+ @ Override
127+ public void filter (ContainerRequestContext requestContext ,
128+ ContainerResponseContext responseContext ) throws IOException {
129+ // Clean up ThreadLocal variables after request is processed
130+ // This prevents memory leaks in thread pool
131+ HugeGraphAuthProxy .resetSpaceContext ();
132+ LOG .debug ("HugeGraphAuthProxy ThreadLocal cleaned up after request" );
133+ }
134+
105135 protected User authenticate (ContainerRequestContext context ) {
106136 GraphManager manager = this .managerProvider .get ();
107137 E .checkState (manager != null , "Context GraphManager is absent" );
@@ -188,10 +218,12 @@ public static class Authorizer implements SecurityContext {
188218 private final UriInfo uri ;
189219 private final User user ;
190220 private final Principal principal ;
221+ private final GraphManager manager ;
191222
192- public Authorizer (final User user , final UriInfo uri ) {
223+ public Authorizer (GraphManager manager , final User user , final UriInfo uri ) {
193224 E .checkNotNull (user , "user" );
194225 E .checkNotNull (uri , "uri" );
226+ this .manager = manager ;
195227 this .uri = uri ;
196228 this .user = user ;
197229 this .principal = new UserPrincipal ();
@@ -232,19 +264,56 @@ public String getAuthenticationScheme() {
232264
233265 private boolean matchPermission (String required ) {
234266 boolean valid ;
235- RequiredPerm requiredPerm ;
267+ HugeAuthenticator .RequiredPerm requiredPerm ;
268+
269+ /*
270+ * if request url contains graph space and the corresponding space
271+ * does not enable permission check, return true
272+ * */
273+ if (!isAuth ()) {
274+ return true ;
275+ }
236276
237- if (!required .startsWith (HugeAuthenticator .KEY_OWNER )) {
238- // Permission format like: "admin"
239- requiredPerm = new RequiredPerm ();
277+ if (!required .startsWith (HugeAuthenticator .KEY_GRAPHSPACE )) {
278+ // Permission format like: "admin", "space", "analyst", "space_member"
279+ requiredPerm = new HugeAuthenticator . RequiredPerm ();
240280 requiredPerm .owner (required );
281+
282+ // For space-level roles, set graphSpace from path parameter
283+ if ("space" .equals (required ) || "space_member" .equals (required )) {
284+ // If graphspace parameter is not in path, use DEFAULT
285+ List <String > graphSpaceParams = this .uri .getPathParameters ().get ("graphspace" );
286+ String graphSpace = "DEFAULT" ;
287+ if (graphSpaceParams != null && !graphSpaceParams .isEmpty ()) {
288+ graphSpace = graphSpaceParams .get (0 );
289+ }
290+ requiredPerm .graphSpace (graphSpace );
291+ }
292+
293+ // Role inheritance is handled in HugeAuthenticator.matchSpace()
294+ valid = HugeAuthenticator .RolePerm .matchApiRequiredPerm (this .role (), requiredPerm );
241295 } else {
242- // The required like: $owner=graph1 $action=vertex_write
243- requiredPerm = RequiredPerm .fromPermission (required );
296+ // The required like:
297+ // $graphspace=graphspace $owner=graph1 $action=vertex_write
298+ requiredPerm = HugeAuthenticator .RequiredPerm .fromPermission (required );
299+
300+ /*
301+ * Replace graphspace value (it may be a variable) if the
302+ * permission format like:
303+ * "$graphspace=$graphspace $owner=$graph $action=vertex_write"
304+ */
305+ String graphSpace = requiredPerm .graphSpace ();
306+ if (graphSpace .startsWith (HugeAuthenticator .VAR_PREFIX )) {
307+ int prefixLen = HugeAuthenticator .VAR_PREFIX .length ();
308+ assert graphSpace .length () > prefixLen ;
309+ graphSpace = graphSpace .substring (prefixLen );
310+ graphSpace = this .getPathParameter (graphSpace );
311+ requiredPerm .graphSpace (graphSpace );
312+ }
244313
245314 /*
246- * Replace owner value (it may be a variable) if the permission
247- * format like: "$owner=$graph $action=vertex_write"
315+ * Replace owner value(it may be a variable) if the permission
316+ * format like: "$graphspace=$graphspace $ owner=$graph $action=vertex_write"
248317 */
249318 String owner = requiredPerm .owner ();
250319 if (owner .startsWith (HugeAuthenticator .VAR_PREFIX )) {
@@ -255,32 +324,47 @@ private boolean matchPermission(String required) {
255324 owner = this .getPathParameter (owner );
256325 requiredPerm .owner (owner );
257326 }
327+ valid = HugeAuthenticator .RolePerm .matchApiRequiredPerm (this .role (), requiredPerm );
258328 }
259329
260- if (LOG .isDebugEnabled ()) {
261- LOG .debug ("Verify permission {} {} for user '{}' with role {}" ,
262- requiredPerm .action ().string (), requiredPerm .resourceObject (),
263- this .user .username (), this .user .role ());
264- }
265-
266- // verify role permission
267- valid = RolePerm .match (this .role (), requiredPerm );
268-
269- if (!valid && LOG .isInfoEnabled () &&
330+ if (!valid &&
270331 !required .equals (HugeAuthenticator .USER_ADMIN )) {
271- LOG .info ("User '{}' is denied to {} {}" , this .user .username (),
272- requiredPerm .action ().string (), requiredPerm .resourceObject ());
332+ LOG .info (
333+ user .userId ().asString (),
334+ requiredPerm .action ().string (),
335+ requiredPerm .resourceObject ());
273336 }
274337 return valid ;
275338 }
276339
277340 private String getPathParameter (String key ) {
278341 List <String > params = this .uri .getPathParameters ().get (key );
342+ // For graphspace parameter, use "DEFAULT" if not present in path
343+ if ("graphspace" .equals (key ) && (params == null || params .isEmpty ())) {
344+ return "DEFAULT" ;
345+ }
279346 E .checkState (params != null && params .size () == 1 ,
280347 "There is no matched path parameter: '%s'" , key );
281348 return params .get (0 );
282349 }
283350
351+ private boolean isAuth () {
352+ List <String > params = this .uri .getPathParameters ().get (
353+ "graphspace" );
354+ if (params != null && params .size () == 1 ) {
355+ String graphSpace = params .get (0 );
356+ if (ALL_GRAPH_SPACES .equals (graphSpace )) {
357+ return true ;
358+ }
359+ E .checkArgumentNotNull (this .manager .graphSpace (graphSpace ),
360+ "The graph space '%s' does not exist" ,
361+ graphSpace );
362+ return this .manager .graphSpace (graphSpace ).auth ();
363+ } else {
364+ return true ;
365+ }
366+ }
367+
284368 private final class UserPrincipal implements Principal {
285369
286370 @ Override
0 commit comments