This is an automated email from the ASF dual-hosted git repository.
yuqi4733 pushed a commit to branch branch-1.0
in repository https://gitbox.apache.org/repos/asf/gravitino.git
The following commit(s) were added to refs/heads/branch-1.0 by this push:
new 73e6071fac [#8089][#8099] Improvement(authz): Support concurrent
metadata authorization (#8570)
73e6071fac is described below
commit 73e6071fac3f42a8df6fbf812714aa1c30c8ba60
Author: github-actions[bot]
<41898282+github-actions[bot]@users.noreply.github.com>
AuthorDate: Wed Sep 17 09:23:27 2025 +0800
[#8089][#8099] Improvement(authz): Support concurrent metadata
authorization (#8570)
### What changes were proposed in this pull request?
Avoid duplicate authorization checks in list operations
The time complexity of Jcasbin's enforce method is O(n). Further caching
of authorization results can reduce the time spent on authorization.
### Why are the changes needed?
Fix: #8089 #8099
### Does this PR introduce _any_ user-facing change?
None
### How was this patch tested?
Existing IT test cases in
org.apache.gravitino.client.integration.test.authorization
Co-authored-by: yangyang zhong <[email protected]>
---
.../main/java/org/apache/gravitino/Configs.java | 9 +
.../authorization/AuthorizationRequestContext.java | 153 +++++++++++
.../authorization/GravitinoAuthorizer.java | 15 +-
docs/gravitino-server-config.md | 1 +
.../server/authorization/MetadataFilterHelper.java | 119 +++++++--
.../authorization/PassThroughAuthorizer.java | 13 +-
.../AuthorizationExpressionConverter.java | 8 +-
.../AuthorizationExpressionEvaluator.java | 17 +-
.../authorization/jcasbin/JcasbinAuthorizer.java | 279 ++++++++++++++-------
.../authorization/MockGravitinoAuthorizer.java | 13 +-
.../authorization/TestMetadataFilterHelper.java | 21 ++
.../authorization/TestPassThroughAuthorizer.java | 5 +-
.../TestAuthorizationExpressionConverter.java | 16 +-
.../TestAuthorizationExpressionEvaluator.java | 17 +-
.../jcasbin/TestJcasbinAuthorizer.java | 47 +++-
.../web/filter/GravitinoInterceptionService.java | 4 +-
.../filter/TestGravitinoInterceptionService.java | 13 +-
17 files changed, 598 insertions(+), 152 deletions(-)
diff --git a/core/src/main/java/org/apache/gravitino/Configs.java
b/core/src/main/java/org/apache/gravitino/Configs.java
index 56392fadf3..9f04aa9318 100644
--- a/core/src/main/java/org/apache/gravitino/Configs.java
+++ b/core/src/main/java/org/apache/gravitino/Configs.java
@@ -93,6 +93,8 @@ public class Configs {
public static final int DEFAULT_RELATIONAL_JDBC_BACKEND_MAX_CONNECTIONS =
100;
+ public static final int DEFAULT_GRAVITINO_AUTHORIZATION_THREAD_POOL_SIZE =
100;
+
public static final long
DEFAULT_RELATIONAL_JDBC_BACKEND_MAX_WAIT_MILLISECONDS = 1000L;
public static final int GARBAGE_COLLECTOR_SINGLE_DELETION_LIMIT = 100;
@@ -293,6 +295,13 @@ public class Configs {
.stringConf()
.createWithDefault("org.apache.gravitino.server.authorization.jcasbin.JcasbinAuthorizer");
+ public static final ConfigEntry<Integer>
GRAVITINO_AUTHORIZATION_THREAD_POOL_SIZE =
+ new ConfigBuilder("gravitino.authorization.threadPoolSize")
+ .doc("The thread pool size of metadata authorization requests")
+ .version(ConfigConstants.VERSION_1_0_0)
+ .intConf()
+ .createWithDefault(DEFAULT_GRAVITINO_AUTHORIZATION_THREAD_POOL_SIZE);
+
public static final ConfigEntry<List<String>> SERVICE_ADMINS =
new ConfigBuilder("gravitino.authorization.serviceAdmins")
.doc("The admins of Gravitino service")
diff --git
a/core/src/main/java/org/apache/gravitino/authorization/AuthorizationRequestContext.java
b/core/src/main/java/org/apache/gravitino/authorization/AuthorizationRequestContext.java
new file mode 100644
index 0000000000..804e6ae50b
--- /dev/null
+++
b/core/src/main/java/org/apache/gravitino/authorization/AuthorizationRequestContext.java
@@ -0,0 +1,153 @@
+/*
+ * 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.gravitino.authorization;
+
+import java.security.Principal;
+import java.util.Map;
+import java.util.Objects;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.function.Function;
+import org.apache.gravitino.MetadataObject;
+
+public class AuthorizationRequestContext {
+
+ /** Used to cache the results of metadata authorization. */
+ private final Map<AuthorizationKey, Boolean> allowAuthorizerCache = new
ConcurrentHashMap<>();
+
+ /** Used to cache the results of metadata authorization. */
+ private final Map<AuthorizationKey, Boolean> denyAuthorizerCache = new
ConcurrentHashMap<>();
+
+ /** Used to determine whether the role has already been loaded. */
+ private final AtomicBoolean hasLoadRole = new AtomicBoolean();
+
+ /**
+ * check allow
+ *
+ * @param principal principal
+ * @param metalake metalake
+ * @param metadataObject metadata object
+ * @param privilege privilege
+ * @param authorizer authorizer
+ * @return authorization result
+ */
+ public boolean authorizeAllow(
+ Principal principal,
+ String metalake,
+ MetadataObject metadataObject,
+ Privilege.Name privilege,
+ Function<AuthorizationKey, Boolean> authorizer) {
+ AuthorizationKey context = new AuthorizationKey(principal, metalake,
metadataObject, privilege);
+ return allowAuthorizerCache.computeIfAbsent(context, authorizer);
+ }
+
+ /**
+ * check deny
+ *
+ * @param principal principal
+ * @param metalake metalake
+ * @param metadataObject metadata object
+ * @param privilege privilege
+ * @param authorizer authorizer
+ * @return authorization result
+ */
+ public boolean authorizeDeny(
+ Principal principal,
+ String metalake,
+ MetadataObject metadataObject,
+ Privilege.Name privilege,
+ Function<AuthorizationKey, Boolean> authorizer) {
+ AuthorizationKey context = new AuthorizationKey(principal, metalake,
metadataObject, privilege);
+ return denyAuthorizerCache.computeIfAbsent(context, authorizer);
+ }
+
+ public void loadRole(Runnable runnable) {
+ if (hasLoadRole.get()) {
+ return;
+ }
+ runnable.run();
+ hasLoadRole.set(true);
+ }
+
+ public static class AuthorizationKey {
+ private Principal principal;
+ private String metalake;
+ private MetadataObject metadataObject;
+ private Privilege.Name privilege;
+
+ public AuthorizationKey(
+ Principal principal,
+ String metalake,
+ MetadataObject metadataObject,
+ Privilege.Name privilege) {
+ this.principal = principal;
+ this.metalake = metalake;
+ this.metadataObject = metadataObject;
+ this.privilege = privilege;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (!(o instanceof AuthorizationKey)) {
+ return false;
+ }
+ AuthorizationKey that = (AuthorizationKey) o;
+ return Objects.equals(principal, that.principal)
+ && Objects.equals(metalake, that.metalake)
+ && Objects.equals(metadataObject, that.metadataObject)
+ && Objects.equals(privilege, that.privilege);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(principal, metalake, metadataObject, privilege);
+ }
+
+ public Principal getPrincipal() {
+ return principal;
+ }
+
+ public void setPrincipal(Principal principal) {
+ this.principal = principal;
+ }
+
+ public String getMetalake() {
+ return metalake;
+ }
+
+ public void setMetalake(String metalake) {
+ this.metalake = metalake;
+ }
+
+ public MetadataObject getMetadataObject() {
+ return metadataObject;
+ }
+
+ public void setMetadataObject(MetadataObject metadataObject) {
+ this.metadataObject = metadataObject;
+ }
+
+ public Privilege.Name getPrivilege() {
+ return privilege;
+ }
+
+ public void setPrivilege(Privilege.Name privilege) {
+ this.privilege = privilege;
+ }
+ }
+}
diff --git
a/core/src/main/java/org/apache/gravitino/authorization/GravitinoAuthorizer.java
b/core/src/main/java/org/apache/gravitino/authorization/GravitinoAuthorizer.java
index 08bcaa251d..7dcef770bd 100644
---
a/core/src/main/java/org/apache/gravitino/authorization/GravitinoAuthorizer.java
+++
b/core/src/main/java/org/apache/gravitino/authorization/GravitinoAuthorizer.java
@@ -42,19 +42,22 @@ public interface GravitinoAuthorizer extends Closeable {
* @param metalake the metalake
* @param metadataObject the metadataObject.
* @param privilege for example, CREATE_CATALOG, CREATE_TABLE, etc.
+ * @param requestContext authorization request context
* @return authorization result.
*/
boolean authorize(
Principal principal,
String metalake,
MetadataObject metadataObject,
- Privilege.Name privilege);
+ Privilege.Name privilege,
+ AuthorizationRequestContext requestContext);
boolean deny(
Principal principal,
String metalake,
MetadataObject metadataObject,
- Privilege.Name privilege);
+ Privilege.Name privilege,
+ AuthorizationRequestContext requestContext);
/**
* Determine whether the user is the Owner of a certain metadata object.
@@ -97,9 +100,11 @@ public interface GravitinoAuthorizer extends Closeable {
* @param metalake metalake
* @param type metadata type
* @param fullName metadata full name
+ * @param requestContext authorization request context
* @return authorization result
*/
- boolean hasSetOwnerPermission(String metalake, String type, String fullName);
+ boolean hasSetOwnerPermission(
+ String metalake, String type, String fullName,
AuthorizationRequestContext requestContext);
/**
* Determine whether the user can grant or revoke privilege for metadata
@@ -107,9 +112,11 @@ public interface GravitinoAuthorizer extends Closeable {
* @param metalake metalake
* @param type metadata type
* @param fullName metadata full name
+ * @param requestContext authorization request context
* @return authorization result
*/
- boolean hasMetadataPrivilegePermission(String metalake, String type, String
fullName);
+ boolean hasMetadataPrivilegePermission(
+ String metalake, String type, String fullName,
AuthorizationRequestContext requestContext);
/**
* When the permissions of a role change, it is necessary to notify the
GravitinoAuthorizer in
diff --git a/docs/gravitino-server-config.md b/docs/gravitino-server-config.md
index 211cb0faa2..749d0e66f3 100644
--- a/docs/gravitino-server-config.md
+++ b/docs/gravitino-server-config.md
@@ -348,6 +348,7 @@ These variables override the corresponding entries in
`gravitino.conf` at startu
| `GRAVITINO_ENTITY_STORE_RELATIONAL_JDBC_PASSWORD` |
`gravitino.entity.store.relational.jdbcPassword` | `gravitino`
| 1.0.0 |
| `GRAVITINO_CATALOG_CACHE_EVICTION_INTERVAL_MS` |
`gravitino.catalog.cache.evictionIntervalMs` | `3600000`
| 1.0.0 |
| `GRAVITINO_AUTHORIZATION_ENABLE` |
`gravitino.authorization.enable` | `false`
| 1.0.0 |
+| `GRAVITINO_AUTHORIZATION_THREAD_POOL_SIZE` |
`gravitino.authorization.threadPoolSize` | `100`
| 1.0.0 |
| `GRAVITINO_AUTHORIZATION_SERVICE_ADMINS` |
`gravitino.authorization.serviceAdmins` | `anonymous`
| 1.0.0 |
| `GRAVITINO_AUX_SERVICE_NAMES` |
`gravitino.auxService.names` | `iceberg-rest`
| 1.0.0 |
| `GRAVITINO_ICEBERG_REST_CLASSPATH` |
`gravitino.iceberg-rest.classpath` |
`iceberg-rest-server/libs, iceberg-rest-server/conf` | 1.0.0 |
diff --git
a/server-common/src/main/java/org/apache/gravitino/server/authorization/MetadataFilterHelper.java
b/server-common/src/main/java/org/apache/gravitino/server/authorization/MetadataFilterHelper.java
index 1a002daafc..e435bac4a1 100644
---
a/server-common/src/main/java/org/apache/gravitino/server/authorization/MetadataFilterHelper.java
+++
b/server-common/src/main/java/org/apache/gravitino/server/authorization/MetadataFilterHelper.java
@@ -19,20 +19,29 @@ package org.apache.gravitino.server.authorization;
import java.lang.reflect.Array;
import java.security.Principal;
+import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
+import java.util.List;
import java.util.Map;
+import java.util.Objects;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.Executor;
+import java.util.concurrent.Executors;
import java.util.function.Function;
import org.apache.gravitino.Config;
import org.apache.gravitino.Configs;
import org.apache.gravitino.Entity;
import org.apache.gravitino.GravitinoEnv;
import org.apache.gravitino.NameIdentifier;
+import org.apache.gravitino.authorization.AuthorizationRequestContext;
import org.apache.gravitino.authorization.GravitinoAuthorizer;
import org.apache.gravitino.authorization.Privilege;
import
org.apache.gravitino.server.authorization.expression.AuthorizationExpressionEvaluator;
import org.apache.gravitino.utils.NameIdentifierUtil;
import org.apache.gravitino.utils.PrincipalUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
/**
* MetadataFilterHelper performs permission checks on the list data returned
by the REST API based
@@ -41,6 +50,9 @@ import org.apache.gravitino.utils.PrincipalUtils;
*/
public class MetadataFilterHelper {
+ private static final Logger LOG =
LoggerFactory.getLogger(MetadataFilterHelper.class);
+ private static volatile Executor executor = null;
+
private MetadataFilterHelper() {}
/**
@@ -59,9 +71,11 @@ public class MetadataFilterHelper {
if (!enableAuthorization()) {
return metadataList;
}
+ checkExecutor();
GravitinoAuthorizer gravitinoAuthorizer =
GravitinoAuthorizerProvider.getInstance().getGravitinoAuthorizer();
Principal currentPrincipal = PrincipalUtils.getCurrentPrincipal();
+ AuthorizationRequestContext authorizationRequestContext = new
AuthorizationRequestContext();
return Arrays.stream(metadataList)
.filter(
metaDataName ->
@@ -69,7 +83,8 @@ public class MetadataFilterHelper {
currentPrincipal,
metalake,
NameIdentifierUtil.toMetadataObject(metaDataName,
entityType),
- Privilege.Name.valueOf(privilege)))
+ Privilege.Name.valueOf(privilege),
+ authorizationRequestContext))
.toArray(NameIdentifier[]::new);
}
@@ -87,18 +102,40 @@ public class MetadataFilterHelper {
String expression,
Entity.EntityType entityType,
NameIdentifier[] nameIdentifiers) {
- AuthorizationExpressionEvaluator authorizationExpressionEvaluator =
- new AuthorizationExpressionEvaluator(expression);
if (!enableAuthorization()) {
return nameIdentifiers;
}
- return Arrays.stream(nameIdentifiers)
- .filter(
- metaDataName -> {
- Map<Entity.EntityType, NameIdentifier> nameIdentifierMap =
- spiltMetadataNames(metalake, entityType, metaDataName);
- return
authorizationExpressionEvaluator.evaluate(nameIdentifierMap);
- })
+ checkExecutor();
+ AuthorizationRequestContext authorizationRequestContext = new
AuthorizationRequestContext();
+ List<CompletableFuture<NameIdentifier>> futures = new ArrayList<>();
+ for (NameIdentifier nameIdentifier : nameIdentifiers) {
+ Principal currentPrincipal = PrincipalUtils.getCurrentPrincipal();
+ futures.add(
+ CompletableFuture.supplyAsync(
+ () -> {
+ try {
+ return PrincipalUtils.doAs(
+ currentPrincipal,
+ () -> {
+ Map<Entity.EntityType, NameIdentifier>
nameIdentifierMap =
+ spiltMetadataNames(metalake, entityType,
nameIdentifier);
+ AuthorizationExpressionEvaluator
authorizationExpressionEvaluator =
+ new AuthorizationExpressionEvaluator(expression);
+ return authorizationExpressionEvaluator.evaluate(
+ nameIdentifierMap, authorizationRequestContext)
+ ? nameIdentifier
+ : null;
+ });
+ } catch (Exception e) {
+ LOG.error("GravitinoAuthorize error:{}", e.getMessage(), e);
+ return null;
+ }
+ },
+ executor));
+ }
+ return futures.stream()
+ .map(CompletableFuture::join)
+ .filter(Objects::nonNull)
.toArray(NameIdentifier[]::new);
}
@@ -122,16 +159,38 @@ public class MetadataFilterHelper {
if (!enableAuthorization()) {
return entities;
}
- AuthorizationExpressionEvaluator authorizationExpressionEvaluator =
- new AuthorizationExpressionEvaluator(expression);
- return Arrays.stream(entities)
- .filter(
- entity -> {
- NameIdentifier nameIdentifier = toNameIdentifier.apply(entity);
- Map<Entity.EntityType, NameIdentifier> nameIdentifierMap =
- spiltMetadataNames(metalake, entityType, nameIdentifier);
- return
authorizationExpressionEvaluator.evaluate(nameIdentifierMap);
- })
+ checkExecutor();
+ AuthorizationRequestContext authorizationRequestContext = new
AuthorizationRequestContext();
+ List<CompletableFuture<E>> futures = new ArrayList<>();
+ for (E entity : entities) {
+ Principal currentPrincipal = PrincipalUtils.getCurrentPrincipal();
+ futures.add(
+ CompletableFuture.supplyAsync(
+ () -> {
+ try {
+ return PrincipalUtils.doAs(
+ currentPrincipal,
+ () -> {
+ AuthorizationExpressionEvaluator
authorizationExpressionEvaluator =
+ new AuthorizationExpressionEvaluator(expression);
+ NameIdentifier nameIdentifier =
toNameIdentifier.apply(entity);
+ Map<Entity.EntityType, NameIdentifier>
nameIdentifierMap =
+ spiltMetadataNames(metalake, entityType,
nameIdentifier);
+ return authorizationExpressionEvaluator.evaluate(
+ nameIdentifierMap, authorizationRequestContext)
+ ? entity
+ : null;
+ });
+ } catch (Exception e) {
+ LOG.error("GravitinoAuthorize error:{}", e.getMessage(), e);
+ return null;
+ }
+ },
+ executor));
+ }
+ return futures.stream()
+ .map(CompletableFuture::join)
+ .filter(Objects::nonNull)
.toArray(size -> (E[])
Array.newInstance(entities.getClass().getComponentType(), size));
}
@@ -214,4 +273,24 @@ public class MetadataFilterHelper {
Config config = GravitinoEnv.getInstance().config();
return config != null && config.get(Configs.ENABLE_AUTHORIZATION);
}
+
+ private static void checkExecutor() {
+ if (executor == null) {
+ synchronized (MetadataFilterHelper.class) {
+ if (executor == null) {
+ executor =
+ Executors.newFixedThreadPool(
+ GravitinoEnv.getInstance()
+ .config()
+ .get(Configs.GRAVITINO_AUTHORIZATION_THREAD_POOL_SIZE),
+ runnable -> {
+ Thread thread = new Thread(runnable);
+ thread.setDaemon(true);
+ thread.setName("MetadataFilterHelper-ThreadPool-" +
thread.getId());
+ return thread;
+ });
+ }
+ }
+ }
+ }
}
diff --git
a/server-common/src/main/java/org/apache/gravitino/server/authorization/PassThroughAuthorizer.java
b/server-common/src/main/java/org/apache/gravitino/server/authorization/PassThroughAuthorizer.java
index 3288f92b3b..3d4452c65e 100644
---
a/server-common/src/main/java/org/apache/gravitino/server/authorization/PassThroughAuthorizer.java
+++
b/server-common/src/main/java/org/apache/gravitino/server/authorization/PassThroughAuthorizer.java
@@ -22,6 +22,7 @@ import java.security.Principal;
import org.apache.gravitino.Entity;
import org.apache.gravitino.MetadataObject;
import org.apache.gravitino.NameIdentifier;
+import org.apache.gravitino.authorization.AuthorizationRequestContext;
import org.apache.gravitino.authorization.GravitinoAuthorizer;
import org.apache.gravitino.authorization.Privilege;
@@ -39,7 +40,8 @@ public class PassThroughAuthorizer implements
GravitinoAuthorizer {
Principal principal,
String metalake,
MetadataObject metadataObject,
- Privilege.Name privilege) {
+ Privilege.Name privilege,
+ AuthorizationRequestContext requestContext) {
return true;
}
@@ -48,7 +50,8 @@ public class PassThroughAuthorizer implements
GravitinoAuthorizer {
Principal principal,
String metalake,
MetadataObject metadataObject,
- Privilege.Name privilege) {
+ Privilege.Name privilege,
+ AuthorizationRequestContext requestContext) {
return false;
}
@@ -73,12 +76,14 @@ public class PassThroughAuthorizer implements
GravitinoAuthorizer {
}
@Override
- public boolean hasSetOwnerPermission(String metalake, String type, String
fullName) {
+ public boolean hasSetOwnerPermission(
+ String metalake, String type, String fullName,
AuthorizationRequestContext requestContext) {
return true;
}
@Override
- public boolean hasMetadataPrivilegePermission(String metalake, String type,
String fullName) {
+ public boolean hasMetadataPrivilegePermission(
+ String metalake, String type, String fullName,
AuthorizationRequestContext requestContext) {
return true;
}
diff --git
a/server-common/src/main/java/org/apache/gravitino/server/authorization/expression/AuthorizationExpressionConverter.java
b/server-common/src/main/java/org/apache/gravitino/server/authorization/expression/AuthorizationExpressionConverter.java
index 71bbacbcc0..40fecc482e 100644
---
a/server-common/src/main/java/org/apache/gravitino/server/authorization/expression/AuthorizationExpressionConverter.java
+++
b/server-common/src/main/java/org/apache/gravitino/server/authorization/expression/AuthorizationExpressionConverter.java
@@ -89,7 +89,7 @@ public class AuthorizationExpressionConverter {
replacement =
String.format(
"authorizer.deny(principal,METALAKE_NAME,%s,"
- +
"@org.apache.gravitino.authorization.Privilege\\$Name@%s)",
+ +
"@org.apache.gravitino.authorization.Privilege\\$Name@%s,authorizationContext)",
type, privilege);
} else if (AuthConstants.SELF.equals(privilegeOrExpression)) {
replacement =
@@ -100,7 +100,7 @@ public class AuthorizationExpressionConverter {
replacement =
String.format(
"authorizer.authorize(principal,METALAKE_NAME,%s,"
- +
"@org.apache.gravitino.authorization.Privilege\\$Name@%s)",
+ +
"@org.apache.gravitino.authorization.Privilege\\$Name@%s,authorizationContext)",
type, privilegeOrExpression);
}
@@ -241,11 +241,11 @@ public class AuthorizationExpressionConverter {
expression =
expression.replaceAll(
CAN_SET_OWNER,
-
"authorizer.hasSetOwnerPermission(p_metalake,p_metadataObjectType,p_fullName)");
+
"authorizer.hasSetOwnerPermission(p_metalake,p_metadataObjectType,p_fullName,authorizationContext)");
expression =
expression.replaceAll(
CAN_OPERATE_METADATA_PRIVILEGE,
-
"authorizer.hasMetadataPrivilegePermission(p_metalake,p_type,p_fullName)");
+
"authorizer.hasMetadataPrivilegePermission(p_metalake,p_type,p_fullName,authorizationContext)");
return expression;
}
}
diff --git
a/server-common/src/main/java/org/apache/gravitino/server/authorization/expression/AuthorizationExpressionEvaluator.java
b/server-common/src/main/java/org/apache/gravitino/server/authorization/expression/AuthorizationExpressionEvaluator.java
index c83b1a0590..78ddf58d44 100644
---
a/server-common/src/main/java/org/apache/gravitino/server/authorization/expression/AuthorizationExpressionEvaluator.java
+++
b/server-common/src/main/java/org/apache/gravitino/server/authorization/expression/AuthorizationExpressionEvaluator.java
@@ -29,6 +29,7 @@ import ognl.OgnlException;
import org.apache.gravitino.Entity;
import org.apache.gravitino.MetadataObject;
import org.apache.gravitino.NameIdentifier;
+import org.apache.gravitino.authorization.AuthorizationRequestContext;
import org.apache.gravitino.authorization.GravitinoAuthorizer;
import org.apache.gravitino.server.authorization.GravitinoAuthorizerProvider;
import org.apache.gravitino.utils.NameIdentifierUtil;
@@ -57,8 +58,10 @@ public class AuthorizationExpressionEvaluator {
* @param metadataNames key-metadata type, value-metadata NameIdentifier
* @return authorization result
*/
- public boolean evaluate(Map<Entity.EntityType, NameIdentifier>
metadataNames) {
- return evaluate(metadataNames, new HashMap<>());
+ public boolean evaluate(
+ Map<Entity.EntityType, NameIdentifier> metadataNames,
+ AuthorizationRequestContext requestContext) {
+ return evaluate(metadataNames, new HashMap<>(), requestContext);
}
/**
@@ -70,13 +73,16 @@ public class AuthorizationExpressionEvaluator {
* @return authorization result
*/
public boolean evaluate(
- Map<Entity.EntityType, NameIdentifier> metadataNames, Map<String,
Object> pathParams) {
+ Map<Entity.EntityType, NameIdentifier> metadataNames,
+ Map<String, Object> pathParams,
+ AuthorizationRequestContext requestContext) {
Principal currentPrincipal = PrincipalUtils.getCurrentPrincipal();
GravitinoAuthorizer gravitinoAuthorizer =
GravitinoAuthorizerProvider.getInstance().getGravitinoAuthorizer();
OgnlContext ognlContext = Ognl.createDefaultContext(null);
ognlContext.put("principal", currentPrincipal);
ognlContext.put("authorizer", gravitinoAuthorizer);
+ ognlContext.put("authorizationContext", requestContext);
ognlContext.putAll(pathParams);
metadataNames.forEach(
(type, entityNameIdent) -> {
@@ -91,10 +97,9 @@ public class AuthorizationExpressionEvaluator {
ognlContext.put(
"METALAKE_NAME",
Optional.ofNullable(nameIdentifier).map(NameIdentifier::name).orElse(""));
try {
- Object value = Ognl.getValue(ognlAuthorizationExpression, ognlContext);
- return (boolean) value;
+ return (boolean) Ognl.getValue(ognlAuthorizationExpression, ognlContext);
} catch (OgnlException e) {
- throw new RuntimeException("ognl evaluate error", e);
+ throw new RuntimeException(e);
}
}
diff --git
a/server-common/src/main/java/org/apache/gravitino/server/authorization/jcasbin/JcasbinAuthorizer.java
b/server-common/src/main/java/org/apache/gravitino/server/authorization/jcasbin/JcasbinAuthorizer.java
index 558573c936..beb1ac2675 100644
---
a/server-common/src/main/java/org/apache/gravitino/server/authorization/jcasbin/JcasbinAuthorizer.java
+++
b/server-common/src/main/java/org/apache/gravitino/server/authorization/jcasbin/JcasbinAuthorizer.java
@@ -23,13 +23,20 @@ import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.security.Principal;
+import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
+import java.util.Map;
import java.util.Objects;
import java.util.Set;
+import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.Executor;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ThreadPoolExecutor;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
+import org.apache.gravitino.Configs;
import org.apache.gravitino.Entity;
import org.apache.gravitino.EntityStore;
import org.apache.gravitino.GravitinoEnv;
@@ -38,6 +45,7 @@ import org.apache.gravitino.MetadataObjects;
import org.apache.gravitino.NameIdentifier;
import org.apache.gravitino.SupportsRelationOperations;
import org.apache.gravitino.auth.AuthConstants;
+import org.apache.gravitino.authorization.AuthorizationRequestContext;
import org.apache.gravitino.authorization.GravitinoAuthorizer;
import org.apache.gravitino.authorization.Privilege;
import org.apache.gravitino.authorization.SecurableObject;
@@ -76,8 +84,28 @@ public class JcasbinAuthorizer implements
GravitinoAuthorizer {
*/
private Set<Long> loadedRoles = ConcurrentHashMap.newKeySet();
+ /**
+ * loadedOwners is used to cache owners that have loaded permissions. When
the permissions of a
+ * role are updated, they should be removed from it.
+ */
+ private Set<Long> loadedOwners = ConcurrentHashMap.newKeySet();
+
+ private Map<Long, Long> ownerRel = new ConcurrentHashMap<>();
+
+ private Executor executor = null;
+
@Override
public void initialize() {
+ executor =
+ Executors.newFixedThreadPool(
+ GravitinoEnv.getInstance()
+ .config()
+ .get(Configs.GRAVITINO_AUTHORIZATION_THREAD_POOL_SIZE),
+ runnable -> {
+ Thread thread = new Thread(runnable);
+ thread.setName("GravitinoAuthorizer-ThreadPool-" +
thread.getId());
+ return thread;
+ });
allowEnforcer = new SyncedEnforcer(getModel("/jcasbin_model.conf"), new
GravitinoAdapter());
allowInternalAuthorizer = new InternalAuthorizer(allowEnforcer);
denyEnforcer = new SyncedEnforcer(getModel("/jcasbin_model.conf"), new
GravitinoAdapter());
@@ -101,10 +129,21 @@ public class JcasbinAuthorizer implements
GravitinoAuthorizer {
Principal principal,
String metalake,
MetadataObject metadataObject,
- Privilege.Name privilege) {
+ Privilege.Name privilege,
+ AuthorizationRequestContext requestContext) {
boolean result =
- allowInternalAuthorizer.authorizeInternal(
- principal, metalake, metadataObject, privilege.name());
+ requestContext.authorizeAllow(
+ principal,
+ metalake,
+ metadataObject,
+ privilege,
+ (authorizationKey) ->
+ allowInternalAuthorizer.authorizeInternal(
+ authorizationKey.getPrincipal().getName(),
+ authorizationKey.getMetalake(),
+ authorizationKey.getMetadataObject(),
+ authorizationKey.getPrivilege().name(),
+ requestContext));
LOG.debug(
"principal {},metalake {},metadata object {},privilege {}, result {}",
principal,
@@ -120,10 +159,21 @@ public class JcasbinAuthorizer implements
GravitinoAuthorizer {
Principal principal,
String metalake,
MetadataObject metadataObject,
- Privilege.Name privilege) {
+ Privilege.Name privilege,
+ AuthorizationRequestContext requestContext) {
boolean result =
- denyInternalAuthorizer.authorizeInternal(
- principal, metalake, metadataObject, privilege.name());
+ requestContext.authorizeDeny(
+ principal,
+ metalake,
+ metadataObject,
+ privilege,
+ (authorizationKey) ->
+ denyInternalAuthorizer.authorizeInternal(
+ authorizationKey.getPrincipal().getName(),
+ authorizationKey.getMetalake(),
+ authorizationKey.getMetadataObject(),
+ authorizationKey.getPrivilege().name(),
+ requestContext));
LOG.debug(
"principal {},metalake {},metadata object {},privilege {},deny result
{}",
principal,
@@ -136,9 +186,19 @@ public class JcasbinAuthorizer implements
GravitinoAuthorizer {
@Override
public boolean isOwner(Principal principal, String metalake, MetadataObject
metadataObject) {
- boolean result =
- allowInternalAuthorizer.authorizeInternal(
- principal, metalake, metadataObject, AuthConstants.OWNER);
+ Long userId;
+ boolean result;
+ try {
+ Long metadataId = MetadataIdConverter.getID(metadataObject, metalake);
+ loadOwnerPolicy(metalake, metadataObject, metadataId);
+ UserEntity userEntity = getUserEntity(principal.getName(), metalake);
+ userId = userEntity.id();
+ metadataId = MetadataIdConverter.getID(metadataObject, metalake);
+ result = Objects.equals(userId, ownerRel.get(metadataId));
+ } catch (Exception e) {
+ LOG.debug("Can not get entity id", e);
+ result = false;
+ }
LOG.debug(
"principal {},metalake {},metadata object {},privilege {},deny result
{}",
principal,
@@ -187,10 +247,18 @@ public class JcasbinAuthorizer implements
GravitinoAuthorizer {
Long roleId =
MetadataIdConverter.getID(
NameIdentifierUtil.toMetadataObject(nameIdentifier, type),
metalake);
- UserEntity userEntity = getUserEntity(currentUserName, metalake);
- Long userId = userEntity.id();
- loadRolePrivilege(metalake, currentUserName, userId);
- return allowEnforcer.hasRoleForUser(String.valueOf(userId),
String.valueOf(roleId));
+ EntityStore entityStore = GravitinoEnv.getInstance().entityStore();
+ NameIdentifier userNameIdentifier =
+ NameIdentifierUtil.ofUser(metalake,
PrincipalUtils.getCurrentUserName());
+ List<RoleEntity> entities =
+ entityStore
+ .relationOperations()
+ .listEntitiesByRelation(
+ SupportsRelationOperations.Type.ROLE_USER_REL,
+ userNameIdentifier,
+ Entity.EntityType.USER);
+ return entities.stream().anyMatch(roleEntity ->
Objects.equals(roleEntity.id(), roleId));
+
} catch (Exception e) {
LOG.warn("can not get user id or role id.", e);
return false;
@@ -200,7 +268,8 @@ public class JcasbinAuthorizer implements
GravitinoAuthorizer {
}
@Override
- public boolean hasSetOwnerPermission(String metalake, String type, String
fullName) {
+ public boolean hasSetOwnerPermission(
+ String metalake, String type, String fullName,
AuthorizationRequestContext requestContext) {
Principal currentPrincipal = PrincipalUtils.getCurrentPrincipal();
MetadataObject metalakeObject =
MetadataObjects.of(ImmutableList.of(metalake),
MetadataObject.Type.METALAKE);
@@ -221,9 +290,15 @@ public class JcasbinAuthorizer implements
GravitinoAuthorizer {
currentPrincipal,
metalake,
MetadataObjects.parent(metadataObject),
- Privilege.Name.USE_CATALOG);
+ Privilege.Name.USE_CATALOG,
+ requestContext);
boolean hasMetalakeUseCatalog =
- authorize(currentPrincipal, metalake, metalakeObject,
Privilege.Name.USE_CATALOG);
+ authorize(
+ currentPrincipal,
+ metalake,
+ metalakeObject,
+ Privilege.Name.USE_CATALOG,
+ requestContext);
return hasCatalogUseCatalog || hasMetalakeUseCatalog;
}
if (tempType == MetadataObject.Type.TABLE
@@ -232,16 +307,27 @@ public class JcasbinAuthorizer implements
GravitinoAuthorizer {
|| tempType == MetadataObject.Type.MODEL) {
// table owner need use_catalog and use_schema privileges
boolean hasMetalakeUseSchema =
- authorize(currentPrincipal, metalake, metalakeObject,
Privilege.Name.USE_SCHEMA);
+ authorize(
+ currentPrincipal,
+ metalake,
+ metalakeObject,
+ Privilege.Name.USE_SCHEMA,
+ requestContext);
MetadataObject schemaObject = MetadataObjects.parent(metadataObject);
boolean hasCatalogUseSchema =
authorize(
currentPrincipal,
metalake,
MetadataObjects.parent(schemaObject),
- Privilege.Name.USE_SCHEMA);
+ Privilege.Name.USE_SCHEMA,
+ requestContext);
boolean hasSchemaUseSchema =
- authorize(currentPrincipal, metalake, schemaObject,
Privilege.Name.USE_SCHEMA);
+ authorize(
+ currentPrincipal,
+ metalake,
+ schemaObject,
+ Privilege.Name.USE_SCHEMA,
+ requestContext);
return hasMetalakeUseSchema || hasCatalogUseSchema ||
hasSchemaUseSchema;
}
return true;
@@ -252,13 +338,18 @@ public class JcasbinAuthorizer implements
GravitinoAuthorizer {
}
@Override
- public boolean hasMetadataPrivilegePermission(String metalake, String type,
String fullName) {
+ public boolean hasMetadataPrivilegePermission(
+ String metalake, String type, String fullName,
AuthorizationRequestContext requestContext) {
Principal currentPrincipal = PrincipalUtils.getCurrentPrincipal();
MetadataObject metalakeMetadataObject =
MetadataObjects.of(ImmutableList.of(metalake),
MetadataObject.Type.METALAKE);
return authorize(
- currentPrincipal, metalake, metalakeMetadataObject,
Privilege.Name.MANAGE_GRANTS)
- || hasSetOwnerPermission(metalake, type, fullName);
+ currentPrincipal,
+ metalake,
+ metalakeMetadataObject,
+ Privilege.Name.MANAGE_GRANTS,
+ requestContext)
+ || hasSetOwnerPermission(metalake, type, fullName, requestContext);
}
@Override
@@ -273,18 +364,19 @@ public class JcasbinAuthorizer implements
GravitinoAuthorizer {
String metalake, Long oldOwnerId, NameIdentifier nameIdentifier,
Entity.EntityType type) {
MetadataObject metadataObject =
NameIdentifierUtil.toMetadataObject(nameIdentifier, type);
Long metadataId = MetadataIdConverter.getID(metadataObject, metalake);
- ImmutableList<String> policy =
- ImmutableList.of(
- String.valueOf(oldOwnerId),
- String.valueOf(metadataObject.type()),
- String.valueOf(metadataId),
- AuthConstants.OWNER,
- AuthConstants.ALLOW);
- allowEnforcer.removePolicy(policy);
+ ownerRel.remove(metadataId);
+ loadedOwners.remove(metadataId);
}
@Override
- public void close() throws IOException {}
+ public void close() throws IOException {
+ if (executor != null) {
+ if (executor instanceof ThreadPoolExecutor) {
+ ThreadPoolExecutor threadPoolExecutor = (ThreadPoolExecutor) executor;
+ threadPoolExecutor.shutdown();
+ }
+ }
+ }
private class InternalAuthorizer {
@@ -295,13 +387,21 @@ public class JcasbinAuthorizer implements
GravitinoAuthorizer {
}
private boolean authorizeInternal(
- Principal principal, String metalake, MetadataObject metadataObject,
String privilege) {
- String username = principal.getName();
- return loadPrivilegeAndAuthorize(username, metalake, metadataObject,
privilege);
+ String username,
+ String metalake,
+ MetadataObject metadataObject,
+ String privilege,
+ AuthorizationRequestContext requestContext) {
+ return loadPrivilegeAndAuthorize(
+ username, metalake, metadataObject, privilege, requestContext);
}
private boolean loadPrivilegeAndAuthorize(
- String username, String metalake, MetadataObject metadataObject,
String privilege) {
+ String username,
+ String metalake,
+ MetadataObject metadataObject,
+ String privilege,
+ AuthorizationRequestContext requestContext) {
Long metadataId;
Long userId;
try {
@@ -312,12 +412,15 @@ public class JcasbinAuthorizer implements
GravitinoAuthorizer {
LOG.debug("Can not get entity id", e);
return false;
}
- loadPrivilege(metalake, username, userId, metadataObject, metadataId);
+ loadRolePrivilege(metalake, username, userId, requestContext);
return authorizeByJcasbin(userId, metadataObject, metadataId, privilege);
}
private boolean authorizeByJcasbin(
Long userId, MetadataObject metadataObject, Long metadataId, String
privilege) {
+ if (AuthConstants.OWNER.equals(privilege)) {
+ return Objects.equals(userId, ownerRel.get(metadataId));
+ }
return enforcer.enforce(
String.valueOf(userId),
String.valueOf(metadataObject.type()),
@@ -336,50 +439,62 @@ public class JcasbinAuthorizer implements
GravitinoAuthorizer {
return userEntity;
}
- private void loadPrivilege(
- String metalake,
- String username,
- Long userId,
- MetadataObject metadataObject,
- Long metadataObjectId) {
- try {
- loadRolePrivilege(metalake, username, userId);
- loadOwnerPolicy(metalake, metadataObject, metadataObjectId);
- } catch (Exception e) {
- LOG.error(e.getMessage(), e);
- }
- }
-
- private void loadRolePrivilege(String metalake, String username, Long
userId) throws IOException {
- EntityStore entityStore = GravitinoEnv.getInstance().entityStore();
- NameIdentifier userNameIdentifier = NameIdentifierUtil.ofUser(metalake,
username);
- List<RoleEntity> entities =
- entityStore
- .relationOperations()
- .listEntitiesByRelation(
- SupportsRelationOperations.Type.ROLE_USER_REL,
- userNameIdentifier,
- Entity.EntityType.USER);
-
- for (RoleEntity role : entities) {
- Long roleId = role.id();
- allowEnforcer.addRoleForUser(String.valueOf(userId),
String.valueOf(roleId));
- denyEnforcer.addRoleForUser(String.valueOf(userId),
String.valueOf(roleId));
- if (loadedRoles.contains(roleId)) {
- continue;
- }
- role =
- entityStore.get(
- NameIdentifierUtil.ofRole(metalake, role.name()),
- Entity.EntityType.ROLE,
- RoleEntity.class);
-
- loadPolicyByRoleEntity(role);
- loadedRoles.add(roleId);
- }
+ private void loadRolePrivilege(
+ String metalake, String username, Long userId,
AuthorizationRequestContext requestContext) {
+ requestContext.loadRole(
+ () -> {
+ EntityStore entityStore = GravitinoEnv.getInstance().entityStore();
+ NameIdentifier userNameIdentifier =
NameIdentifierUtil.ofUser(metalake, username);
+ List<RoleEntity> entities;
+ try {
+ entities =
+ entityStore
+ .relationOperations()
+ .listEntitiesByRelation(
+ SupportsRelationOperations.Type.ROLE_USER_REL,
+ userNameIdentifier,
+ Entity.EntityType.USER);
+ List<CompletableFuture<Void>> loadRoleFutures = new ArrayList<>();
+ for (RoleEntity role : entities) {
+ Long roleId = role.id();
+ allowEnforcer.addRoleForUser(String.valueOf(userId),
String.valueOf(roleId));
+ denyEnforcer.addRoleForUser(String.valueOf(userId),
String.valueOf(roleId));
+ if (loadedRoles.contains(roleId)) {
+ continue;
+ }
+ CompletableFuture<Void> loadRoleFuture =
+ CompletableFuture.supplyAsync(
+ () -> {
+ try {
+ return entityStore.get(
+ NameIdentifierUtil.ofRole(metalake,
role.name()),
+ Entity.EntityType.ROLE,
+ RoleEntity.class);
+ } catch (Exception e) {
+ throw new RuntimeException("Failed to load role:
" + role.name(), e);
+ }
+ },
+ executor)
+ .thenAcceptAsync(
+ roleEntity -> {
+ loadPolicyByRoleEntity(roleEntity);
+ loadedRoles.add(roleId);
+ },
+ executor);
+ loadRoleFutures.add(loadRoleFuture);
+ }
+ CompletableFuture.allOf(loadRoleFutures.toArray(new
CompletableFuture[0])).join();
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ });
}
private void loadOwnerPolicy(String metalake, MetadataObject metadataObject,
Long metadataId) {
+ if (loadedOwners.contains(metadataId)) {
+ LOG.debug("Metadata {} OWNER has bean loaded.", metadataId);
+ return;
+ }
try {
NameIdentifier entityIdent = MetadataObjectUtil.toEntityIdent(metalake,
metadataObject);
EntityStore entityStore = GravitinoEnv.getInstance().entityStore();
@@ -393,14 +508,8 @@ public class JcasbinAuthorizer implements
GravitinoAuthorizer {
for (Entity ownerEntity : owners) {
if (ownerEntity instanceof UserEntity) {
UserEntity user = (UserEntity) ownerEntity;
- ImmutableList<String> policy =
- ImmutableList.of(
- String.valueOf(user.id()),
- String.valueOf(metadataObject.type()),
- String.valueOf(metadataId),
- AuthConstants.OWNER,
- AuthConstants.ALLOW);
- allowEnforcer.addPolicy(policy);
+ ownerRel.put(metadataId, user.id());
+ loadedOwners.add(metadataId);
}
}
} catch (IOException e) {
diff --git
a/server-common/src/test/java/org/apache/gravitino/server/authorization/MockGravitinoAuthorizer.java
b/server-common/src/test/java/org/apache/gravitino/server/authorization/MockGravitinoAuthorizer.java
index 82ca4e3651..34b6210b5c 100644
---
a/server-common/src/test/java/org/apache/gravitino/server/authorization/MockGravitinoAuthorizer.java
+++
b/server-common/src/test/java/org/apache/gravitino/server/authorization/MockGravitinoAuthorizer.java
@@ -22,6 +22,7 @@ import java.util.Objects;
import org.apache.gravitino.Entity;
import org.apache.gravitino.MetadataObject;
import org.apache.gravitino.NameIdentifier;
+import org.apache.gravitino.authorization.AuthorizationRequestContext;
import org.apache.gravitino.authorization.GravitinoAuthorizer;
import org.apache.gravitino.authorization.Privilege;
@@ -36,7 +37,8 @@ public class MockGravitinoAuthorizer implements
GravitinoAuthorizer {
Principal principal,
String metalake,
MetadataObject metadataObject,
- Privilege.Name privilege) {
+ Privilege.Name privilege,
+ AuthorizationRequestContext requestContext) {
if (!("tester".equals(principal.getName()) &&
"testMetalake".equals(metalake))) {
return false;
}
@@ -62,7 +64,8 @@ public class MockGravitinoAuthorizer implements
GravitinoAuthorizer {
Principal principal,
String metalake,
MetadataObject metadataObject,
- Privilege.Name privilege) {
+ Privilege.Name privilege,
+ AuthorizationRequestContext requestContext) {
return false;
}
@@ -91,12 +94,14 @@ public class MockGravitinoAuthorizer implements
GravitinoAuthorizer {
}
@Override
- public boolean hasSetOwnerPermission(String metalake, String type, String
fullName) {
+ public boolean hasSetOwnerPermission(
+ String metalake, String type, String fullName,
AuthorizationRequestContext requestContext) {
return true;
}
@Override
- public boolean hasMetadataPrivilegePermission(String metalake, String type,
String fullName) {
+ public boolean hasMetadataPrivilegePermission(
+ String metalake, String type, String fullName,
AuthorizationRequestContext requestContext) {
return true;
}
diff --git
a/server-common/src/test/java/org/apache/gravitino/server/authorization/TestMetadataFilterHelper.java
b/server-common/src/test/java/org/apache/gravitino/server/authorization/TestMetadataFilterHelper.java
index 2714845b3d..7a55f30e2f 100644
---
a/server-common/src/test/java/org/apache/gravitino/server/authorization/TestMetadataFilterHelper.java
+++
b/server-common/src/test/java/org/apache/gravitino/server/authorization/TestMetadataFilterHelper.java
@@ -17,11 +17,14 @@
package org.apache.gravitino.server.authorization;
+import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.mockStatic;
import static org.mockito.Mockito.when;
+import java.lang.reflect.Field;
+import java.util.concurrent.Executor;
import org.apache.gravitino.Config;
import org.apache.gravitino.Configs;
import org.apache.gravitino.Entity;
@@ -61,12 +64,15 @@ public class TestMetadataFilterHelper {
@Test
public void testFilter() {
+ makeCompletableFutureUseCurrentThread();
try (MockedStatic<PrincipalUtils> principalUtilsMocked =
mockStatic(PrincipalUtils.class);
MockedStatic<GravitinoAuthorizerProvider> mockStatic =
mockStatic(GravitinoAuthorizerProvider.class)) {
principalUtilsMocked
.when(PrincipalUtils::getCurrentPrincipal)
.thenReturn(new UserPrincipal("tester"));
+ principalUtilsMocked.when(() -> PrincipalUtils.doAs(any(),
any())).thenCallRealMethod();
+
GravitinoAuthorizerProvider mockedProvider =
mock(GravitinoAuthorizerProvider.class);
mockStatic.when(GravitinoAuthorizerProvider::getInstance).thenReturn(mockedProvider);
when(mockedProvider.getGravitinoAuthorizer()).thenReturn(new
MockGravitinoAuthorizer());
@@ -90,12 +96,15 @@ public class TestMetadataFilterHelper {
@Test
public void testFilterByExpression() {
+ makeCompletableFutureUseCurrentThread();
try (MockedStatic<PrincipalUtils> principalUtilsMocked =
mockStatic(PrincipalUtils.class);
MockedStatic<GravitinoAuthorizerProvider> mockStatic =
mockStatic(GravitinoAuthorizerProvider.class)) {
principalUtilsMocked
.when(PrincipalUtils::getCurrentPrincipal)
.thenReturn(new UserPrincipal("tester"));
+ principalUtilsMocked.when(() -> PrincipalUtils.doAs(any(),
any())).thenCallRealMethod();
+
GravitinoAuthorizerProvider mockedProvider =
mock(GravitinoAuthorizerProvider.class);
mockStatic.when(GravitinoAuthorizerProvider::getInstance).thenReturn(mockedProvider);
when(mockedProvider.getGravitinoAuthorizer()).thenReturn(new
MockGravitinoAuthorizer());
@@ -121,4 +130,16 @@ public class TestMetadataFilterHelper {
Assertions.assertEquals("testMetalake.testCatalog.testSchema2",
filtered2[1].toString());
}
}
+
+ private static void makeCompletableFutureUseCurrentThread() {
+ try {
+ Executor currentThread = Runnable::run;
+ Class<MetadataFilterHelper> jcasbinAuthorizerClass =
MetadataFilterHelper.class;
+ Field field = jcasbinAuthorizerClass.getDeclaredField("executor");
+ field.setAccessible(true);
+ field.set(null, currentThread);
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
}
diff --git
a/server-common/src/test/java/org/apache/gravitino/server/authorization/TestPassThroughAuthorizer.java
b/server-common/src/test/java/org/apache/gravitino/server/authorization/TestPassThroughAuthorizer.java
index 32e8510cce..85540daff5 100644
---
a/server-common/src/test/java/org/apache/gravitino/server/authorization/TestPassThroughAuthorizer.java
+++
b/server-common/src/test/java/org/apache/gravitino/server/authorization/TestPassThroughAuthorizer.java
@@ -18,6 +18,7 @@
package org.apache.gravitino.server.authorization;
import java.io.IOException;
+import org.apache.gravitino.authorization.AuthorizationRequestContext;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
@@ -27,7 +28,9 @@ public class TestPassThroughAuthorizer {
@Test
public void testAuthorize() throws IOException {
try (PassThroughAuthorizer passThroughAuthorizer = new
PassThroughAuthorizer()) {
- boolean result = passThroughAuthorizer.authorize(null, null, null, null);
+ boolean result =
+ passThroughAuthorizer.authorize(
+ null, null, null, null, new AuthorizationRequestContext());
Assertions.assertTrue(result, "Logic error in PassThroughAuthorizer");
}
}
diff --git
a/server-common/src/test/java/org/apache/gravitino/server/authorization/expression/TestAuthorizationExpressionConverter.java
b/server-common/src/test/java/org/apache/gravitino/server/authorization/expression/TestAuthorizationExpressionConverter.java
index d512cdeaf0..29a8cfca77 100644
---
a/server-common/src/test/java/org/apache/gravitino/server/authorization/expression/TestAuthorizationExpressionConverter.java
+++
b/server-common/src/test/java/org/apache/gravitino/server/authorization/expression/TestAuthorizationExpressionConverter.java
@@ -54,9 +54,9 @@ public class TestAuthorizationExpressionConverter {
createTableAuthorizationExpression);
Assertions.assertEquals(
"authorizer.authorize(principal,METALAKE_NAME,CATALOG,"
- +
"@org.apache.gravitino.authorization.Privilege$Name@CREATE_TABLE) "
+ +
"@org.apache.gravitino.authorization.Privilege$Name@CREATE_TABLE,authorizationContext)
"
+ "|| authorizer.authorize(principal,METALAKE_NAME,SCHEMA,"
- +
"@org.apache.gravitino.authorization.Privilege$Name@CREATE_SCHEMA)",
+ +
"@org.apache.gravitino.authorization.Privilege$Name@CREATE_SCHEMA,authorizationContext)",
createTableOgnlExpression);
String selectTableAuthorizationExpression =
"CATALOG::USE_CATALOG && SCHEMA::USE_SCHEMA &&"
@@ -66,13 +66,13 @@ public class TestAuthorizationExpressionConverter {
selectTableAuthorizationExpression);
Assertions.assertEquals(
"authorizer.authorize(principal,METALAKE_NAME,CATALOG,"
- + "@org.apache.gravitino.authorization.Privilege$Name@USE_CATALOG)
"
+ +
"@org.apache.gravitino.authorization.Privilege$Name@USE_CATALOG,authorizationContext)
"
+ "&& authorizer.authorize(principal,METALAKE_NAME,SCHEMA,"
- + "@org.apache.gravitino.authorization.Privilege$Name@USE_SCHEMA) "
+ +
"@org.apache.gravitino.authorization.Privilege$Name@USE_SCHEMA,authorizationContext)
"
+ "&& (authorizer.authorize(principal,METALAKE_NAME,TABLE,"
- +
"@org.apache.gravitino.authorization.Privilege$Name@SELECT_TABLE) "
+ +
"@org.apache.gravitino.authorization.Privilege$Name@SELECT_TABLE,authorizationContext)
"
+ "|| authorizer.authorize(principal,METALAKE_NAME,TABLE,"
- +
"@org.apache.gravitino.authorization.Privilege$Name@MODIFY_TABLE))",
+ +
"@org.apache.gravitino.authorization.Privilege$Name@MODIFY_TABLE,authorizationContext))",
selectTableOgnlExpression);
}
@@ -83,7 +83,7 @@ public class TestAuthorizationExpressionConverter {
AuthorizationExpressionConverter.convertToOgnlExpression(expressionWithOwner);
Assertions.assertEquals(
"authorizer.authorize(principal,METALAKE_NAME,CATALOG,"
- +
"@org.apache.gravitino.authorization.Privilege$Name@CREATE_SCHEMA) "
+ +
"@org.apache.gravitino.authorization.Privilege$Name@CREATE_SCHEMA,authorizationContext)
"
+ "|| authorizer.isOwner(principal,METALAKE_NAME,SCHEMA)",
createTableOgnlExpression);
@@ -94,7 +94,7 @@ public class TestAuthorizationExpressionConverter {
"(authorizer.isOwner(principal,METALAKE_NAME,METALAKE) "
+ "|| authorizer.isOwner(principal,METALAKE_NAME,CATALOG))"
+ " && authorizer.authorize(principal,METALAKE_NAME,CATALOG"
- +
",@org.apache.gravitino.authorization.Privilege$Name@USE_CATALOG))",
+ +
",@org.apache.gravitino.authorization.Privilege$Name@USE_CATALOG,authorizationContext))",
useCatalogOgnExpression);
}
diff --git
a/server-common/src/test/java/org/apache/gravitino/server/authorization/expression/TestAuthorizationExpressionEvaluator.java
b/server-common/src/test/java/org/apache/gravitino/server/authorization/expression/TestAuthorizationExpressionEvaluator.java
index 21b04532bd..59def1e536 100644
---
a/server-common/src/test/java/org/apache/gravitino/server/authorization/expression/TestAuthorizationExpressionEvaluator.java
+++
b/server-common/src/test/java/org/apache/gravitino/server/authorization/expression/TestAuthorizationExpressionEvaluator.java
@@ -26,6 +26,7 @@ import java.util.Map;
import org.apache.gravitino.Entity;
import org.apache.gravitino.NameIdentifier;
import org.apache.gravitino.UserPrincipal;
+import org.apache.gravitino.authorization.AuthorizationRequestContext;
import org.apache.gravitino.server.authorization.GravitinoAuthorizerProvider;
import org.apache.gravitino.server.authorization.MockGravitinoAuthorizer;
import org.apache.gravitino.utils.NameIdentifierUtil;
@@ -63,11 +64,15 @@ public class TestAuthorizationExpressionEvaluator {
Entity.EntityType.TABLE,
NameIdentifierUtil.ofTable(
"testMetalake", "testCatalog", "testSchema",
"testTableHasNotPermission"));
-
Assertions.assertFalse(authorizationExpressionEvaluator.evaluate(metadataNames));
+ Assertions.assertFalse(
+ authorizationExpressionEvaluator.evaluate(
+ metadataNames, new AuthorizationRequestContext()));
metadataNames.put(
Entity.EntityType.TABLE,
NameIdentifierUtil.ofTable("testMetalake", "testCatalog",
"testSchema", "testTable"));
-
Assertions.assertTrue(authorizationExpressionEvaluator.evaluate(metadataNames));
+ Assertions.assertTrue(
+ authorizationExpressionEvaluator.evaluate(
+ metadataNames, new AuthorizationRequestContext()));
}
}
@@ -91,10 +96,14 @@ public class TestAuthorizationExpressionEvaluator {
metadataNames.put(
Entity.EntityType.CATALOG,
NameIdentifierUtil.ofCatalog("metalakeWithOwner", "testCatalog"));
-
Assertions.assertFalse(authorizationExpressionEvaluator.evaluate(metadataNames));
+ Assertions.assertFalse(
+ authorizationExpressionEvaluator.evaluate(
+ metadataNames, new AuthorizationRequestContext()));
metadataNames.put(
Entity.EntityType.METALAKE,
NameIdentifierUtil.ofMetalake("metalakeWithOwner"));
-
Assertions.assertTrue(authorizationExpressionEvaluator.evaluate(metadataNames));
+ Assertions.assertTrue(
+ authorizationExpressionEvaluator.evaluate(
+ metadataNames, new AuthorizationRequestContext()));
}
}
}
diff --git
a/server-common/src/test/java/org/apache/gravitino/server/authorization/jcasbin/TestJcasbinAuthorizer.java
b/server-common/src/test/java/org/apache/gravitino/server/authorization/jcasbin/TestJcasbinAuthorizer.java
index b1fc7bdb98..2fd4cca724 100644
---
a/server-common/src/test/java/org/apache/gravitino/server/authorization/jcasbin/TestJcasbinAuthorizer.java
+++
b/server-common/src/test/java/org/apache/gravitino/server/authorization/jcasbin/TestJcasbinAuthorizer.java
@@ -30,10 +30,13 @@ import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.collect.ImmutableList;
import java.io.IOException;
+import java.lang.reflect.Field;
import java.security.Principal;
import java.util.ArrayList;
import java.util.List;
+import java.util.concurrent.Executor;
import java.util.stream.Collectors;
+import org.apache.commons.lang3.reflect.FieldUtils;
import org.apache.gravitino.Entity;
import org.apache.gravitino.EntityStore;
import org.apache.gravitino.GravitinoEnv;
@@ -43,6 +46,7 @@ import org.apache.gravitino.NameIdentifier;
import org.apache.gravitino.Namespace;
import org.apache.gravitino.SupportsRelationOperations;
import org.apache.gravitino.UserPrincipal;
+import org.apache.gravitino.authorization.AuthorizationRequestContext;
import org.apache.gravitino.authorization.Privilege;
import org.apache.gravitino.authorization.SecurableObject;
import org.apache.gravitino.meta.AuditInfo;
@@ -50,8 +54,10 @@ import org.apache.gravitino.meta.BaseMetalake;
import org.apache.gravitino.meta.RoleEntity;
import org.apache.gravitino.meta.SchemaVersion;
import org.apache.gravitino.meta.UserEntity;
+import org.apache.gravitino.server.ServerConfig;
import org.apache.gravitino.server.authorization.MetadataIdConverter;
import org.apache.gravitino.storage.relational.po.SecurableObjectPO;
+import org.apache.gravitino.storage.relational.service.OwnerMetaService;
import org.apache.gravitino.storage.relational.utils.POConverters;
import org.apache.gravitino.utils.NameIdentifierUtil;
import org.apache.gravitino.utils.NamespaceUtil;
@@ -91,21 +97,26 @@ public class TestJcasbinAuthorizer {
private static MockedStatic<MetadataIdConverter>
metadataIdConverterMockedStatic;
+ private static MockedStatic<OwnerMetaService> ownerMetaServiceMockedStatic;
+
private static JcasbinAuthorizer jcasbinAuthorizer;
private static ObjectMapper objectMapper = new ObjectMapper();
@BeforeAll
public static void setup() throws IOException {
- jcasbinAuthorizer = new JcasbinAuthorizer();
- jcasbinAuthorizer.initialize();
- principalUtilsMockedStatic = mockStatic(PrincipalUtils.class);
- metadataIdConverterMockedStatic = mockStatic(MetadataIdConverter.class);
+ OwnerMetaService ownerMetaService = mock(OwnerMetaService.class);
+ ownerMetaServiceMockedStatic = mockStatic(OwnerMetaService.class);
+
ownerMetaServiceMockedStatic.when(OwnerMetaService::getInstance).thenReturn(ownerMetaService);
gravitinoEnvMockedStatic = mockStatic(GravitinoEnv.class);
gravitinoEnvMockedStatic.when(GravitinoEnv::getInstance).thenReturn(gravitinoEnv);
+ when(gravitinoEnv.config()).thenReturn(new ServerConfig());
+ principalUtilsMockedStatic = mockStatic(PrincipalUtils.class);
+ metadataIdConverterMockedStatic = mockStatic(MetadataIdConverter.class);
principalUtilsMockedStatic
.when(PrincipalUtils::getCurrentPrincipal)
.thenReturn(new UserPrincipal(USERNAME));
+ principalUtilsMockedStatic.when(() -> PrincipalUtils.doAs(any(),
any())).thenCallRealMethod();
metadataIdConverterMockedStatic
.when(() -> MetadataIdConverter.getID(any(), eq(METALAKE)))
.thenReturn(CATALOG_ID);
@@ -116,6 +127,8 @@ public class TestJcasbinAuthorizer {
eq(Entity.EntityType.USER),
eq(UserEntity.class)))
.thenReturn(getUserEntity());
+ jcasbinAuthorizer = new JcasbinAuthorizer();
+ jcasbinAuthorizer.initialize();
BaseMetalake baseMetalake =
BaseMetalake.builder()
.withId(USER_METALAKE_ID)
@@ -138,10 +151,17 @@ public class TestJcasbinAuthorizer {
if (metadataIdConverterMockedStatic != null) {
metadataIdConverterMockedStatic.close();
}
+ if (ownerMetaServiceMockedStatic != null) {
+ ownerMetaServiceMockedStatic.close();
+ }
+ if (gravitinoEnvMockedStatic != null) {
+ gravitinoEnvMockedStatic.close();
+ }
}
@Test
public void testAuthorize() throws IOException {
+ makeCompletableFutureUseCurrentThread(jcasbinAuthorizer);
Principal currentPrincipal = PrincipalUtils.getCurrentPrincipal();
assertFalse(doAuthorize(currentPrincipal));
RoleEntity allowRole =
@@ -228,15 +248,16 @@ public class TestJcasbinAuthorizer {
assertFalse(doAuthorizeOwner(currentPrincipal));
}
- private boolean doAuthorize(Principal currentPrincipal) {
+ private Boolean doAuthorize(Principal currentPrincipal) {
return jcasbinAuthorizer.authorize(
currentPrincipal,
"testMetalake",
MetadataObjects.of(null, "testCatalog", MetadataObject.Type.CATALOG),
- USE_CATALOG);
+ USE_CATALOG,
+ new AuthorizationRequestContext());
}
- private boolean doAuthorizeOwner(Principal currentPrincipal) {
+ private Boolean doAuthorizeOwner(Principal currentPrincipal) {
return jcasbinAuthorizer.isOwner(
currentPrincipal,
"testMetalake",
@@ -312,4 +333,16 @@ public class TestJcasbinAuthorizer {
return POConverters.fromSecurableObjectPO(
"testCatalog2", getDenySecurableObjectPO(),
MetadataObject.Type.CATALOG);
}
+
+ private static void makeCompletableFutureUseCurrentThread(JcasbinAuthorizer
jcasbinAuthorizer) {
+ try {
+ Executor currentThread = Runnable::run;
+ Class<JcasbinAuthorizer> jcasbinAuthorizerClass =
JcasbinAuthorizer.class;
+ Field field = jcasbinAuthorizerClass.getDeclaredField("executor");
+ field.setAccessible(true);
+ FieldUtils.writeField(field, jcasbinAuthorizer, currentThread);
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
}
diff --git
a/server/src/main/java/org/apache/gravitino/server/web/filter/GravitinoInterceptionService.java
b/server/src/main/java/org/apache/gravitino/server/web/filter/GravitinoInterceptionService.java
index 791f7ec7f9..58ca5b89cf 100644
---
a/server/src/main/java/org/apache/gravitino/server/web/filter/GravitinoInterceptionService.java
+++
b/server/src/main/java/org/apache/gravitino/server/web/filter/GravitinoInterceptionService.java
@@ -37,6 +37,7 @@ import org.apache.commons.lang3.StringUtils;
import org.apache.gravitino.Entity;
import org.apache.gravitino.MetadataObject;
import org.apache.gravitino.NameIdentifier;
+import org.apache.gravitino.authorization.AuthorizationRequestContext;
import
org.apache.gravitino.server.authorization.annotations.AuthorizationExpression;
import
org.apache.gravitino.server.authorization.annotations.AuthorizationMetadata;
import
org.apache.gravitino.server.authorization.expression.AuthorizationExpressionEvaluator;
@@ -128,7 +129,8 @@ public class GravitinoInterceptionService implements
InterceptionService {
AuthorizationExpressionEvaluator authorizationExpressionEvaluator =
new AuthorizationExpressionEvaluator(expression);
boolean authorizeResult =
- authorizationExpressionEvaluator.evaluate(metadataContext,
pathParams);
+ authorizationExpressionEvaluator.evaluate(
+ metadataContext, pathParams, new
AuthorizationRequestContext());
if (!authorizeResult) {
MetadataObject.Type type =
expressionAnnotation.accessMetadataType();
NameIdentifier accessMetadataName =
diff --git
a/server/src/test/java/org/apache/gravitino/server/web/filter/TestGravitinoInterceptionService.java
b/server/src/test/java/org/apache/gravitino/server/web/filter/TestGravitinoInterceptionService.java
index a50b8b3a94..f508d082e4 100644
---
a/server/src/test/java/org/apache/gravitino/server/web/filter/TestGravitinoInterceptionService.java
+++
b/server/src/test/java/org/apache/gravitino/server/web/filter/TestGravitinoInterceptionService.java
@@ -33,6 +33,7 @@ import org.apache.gravitino.Entity;
import org.apache.gravitino.MetadataObject;
import org.apache.gravitino.NameIdentifier;
import org.apache.gravitino.UserPrincipal;
+import org.apache.gravitino.authorization.AuthorizationRequestContext;
import org.apache.gravitino.authorization.GravitinoAuthorizer;
import org.apache.gravitino.authorization.Privilege;
import org.apache.gravitino.dto.responses.ErrorResponse;
@@ -147,7 +148,8 @@ public class TestGravitinoInterceptionService {
Principal principal,
String metalake,
MetadataObject metadataObject,
- Privilege.Name privilege) {
+ Privilege.Name privilege,
+ AuthorizationRequestContext requestContext) {
return "tester".equals(principal.getName())
&& "testMetalake".equals(metalake)
&& metadataObject.type() == MetadataObject.Type.METALAKE
@@ -159,7 +161,8 @@ public class TestGravitinoInterceptionService {
Principal principal,
String metalake,
MetadataObject metadataObject,
- Privilege.Name privilege) {
+ Privilege.Name privilege,
+ AuthorizationRequestContext requestContext) {
return false;
}
@@ -184,12 +187,14 @@ public class TestGravitinoInterceptionService {
}
@Override
- public boolean hasSetOwnerPermission(String metalake, String type, String
fullName) {
+ public boolean hasSetOwnerPermission(
+ String metalake, String type, String fullName,
AuthorizationRequestContext requestContext) {
return true;
}
@Override
- public boolean hasMetadataPrivilegePermission(String metalake, String
type, String fullName) {
+ public boolean hasMetadataPrivilegePermission(
+ String metalake, String type, String fullName,
AuthorizationRequestContext requestContext) {
return true;
}