LiJie20190102 commented on code in PR #13022:
URL: https://github.com/apache/gravitino/pull/13022#discussion_r4429842351


##########
lance/lance-rest-server/src/main/java/org/apache/gravitino/lance/service/authorization/LanceMetadataAuthorizationMethodInterceptor.java:
##########
@@ -196,59 +202,82 @@ protected Object toErrorResponse(Method method, Object[] 
args, Throwable throwab
   }
 
   /**
-   * Authorizes a create request whose mode overwrites an object that already 
exists.
+   * Authorizes a create request whose mode affects an existing object.
    *
    * <p>An overwrite replaces an existing namespace or table, so it is a 
modification rather than a
    * creation and is authorized against the modification expression for the 
invoked resource instead
    * of the create expression on the method. Without this, CREATE_CATALOG, 
CREATE_SCHEMA, or
    * CREATE_TABLE would escalate into permission to replace an object the 
caller does not own.
    *
+   * <p>The {@code exist_ok} mode returns the existing object's metadata 
(location, properties,
+   * schema) when the object already exists. That is a read of the object, so 
the caller must hold
+   * the same read privilege that a describe/probe would require. Without this 
check, a caller with
+   * only CREATE_TABLE could obtain metadata that DescribeTable would deny.
+   *
    * <p>The mode alone decides this, without probing whether the object 
exists: an existence probe
    * at authorization time would race with the create that follows it, and the 
required privilege
    * would then depend on that race.
    */
-  private static final class OverwriteAuthzHandler implements 
AuthorizationHandler {
+  private static final class CreateModeAuthzHandler implements 
AuthorizationHandler {
 
     private static final String OVERWRITE_MODE = "OVERWRITE";
+    private static final String EXIST_OK_MODE = "EXIST_OK";
 
     private final boolean overwrite;
-    private final String authorizationExpression;
+    private final boolean existOk;
+    private final String overwriteExpression;
+    private final String existOkExpression;
 
-    private OverwriteAuthzHandler(String mode, String authorizationExpression) 
{
+    private CreateModeAuthzHandler(
+        String mode, String overwriteExpression, String existOkExpression) {
       // Read the mode through the same normalization the create operation 
applies, so a token the
-      // operation will act on as an overwrite cannot be authorized as a plain 
create. Comparing the
-      // raw string here would leave a gap: " overwrite " reaches the 
operation as OVERWRITE but
-      // would not match, and a caller holding only a create privilege could 
replace an object owned
-      // by somebody else.
-      this.overwrite = OVERWRITE_MODE.equals(CommonUtil.normalizeToken(mode));
-      this.authorizationExpression = authorizationExpression;
+      // operation will act on as an overwrite or exist_ok cannot be 
authorized as a plain create.
+      String normalized = CommonUtil.normalizeToken(mode);
+      this.overwrite = OVERWRITE_MODE.equals(normalized);
+      this.existOk = EXIST_OK_MODE.equals(normalized);
+      this.overwriteExpression = overwriteExpression;
+      this.existOkExpression = existOkExpression;
     }
 
     @Override
     public void process(Map<Entity.EntityType, NameIdentifier> 
nameIdentifierMap) {
-      if (!overwrite) {
-        return;
-      }
-
       Entity.EntityType entityType = deepestEntityType(nameIdentifierMap);
-      boolean authorized =
-          new AuthorizationExpressionEvaluator(authorizationExpression)
-              .evaluate(
-                  nameIdentifierMap,
-                  new HashMap<>(),
-                  new AuthorizationRequestContext(),
-                  Optional.of(entityType.name()));
-      if (!authorized) {
-        throw new ForbiddenException(
-            "User '%s' is not authorized to overwrite '%s'",
-            PrincipalUtils.getCurrentUserName(), 
nameIdentifierMap.get(entityType));
+      if (overwrite) {
+        boolean authorized =
+            new AuthorizationExpressionEvaluator(overwriteExpression)
+                .evaluate(
+                    nameIdentifierMap,
+                    new HashMap<>(),
+                    new AuthorizationRequestContext(),
+                    Optional.of(entityType.name()));
+        if (!authorized) {
+          throw new ForbiddenException(
+              "User '%s' is not authorized to overwrite '%s'",
+              PrincipalUtils.getCurrentUserName(), 
nameIdentifierMap.get(entityType));
+        }
+      }
+      if (existOk) {
+        boolean authorized =
+            new AuthorizationExpressionEvaluator(existOkExpression)
+                .evaluate(
+                    nameIdentifierMap,
+                    new HashMap<>(),
+                    new AuthorizationRequestContext(),
+                    Optional.of(entityType.name()));
+        if (!authorized) {

Review Comment:
   Fixed in 963f7dd6c. The handler now resolves the addressed object first, 
mirroring the core server's load-as-probe authorization: an exist_ok request 
that resolves to an existing object is authorized against the read expression 
(and the create expression is skipped, so read-only callers are not denied), 
while a request that resolves to an absent object falls through to the create 
expression on the method, so create-only callers keep their normal creation 
path. Added the missing-object HTTP regressions: 
LanceNamespaceAuthorizationIT.testExistOkPreservesCreationOfAnAbsentNamespace 
and 
LanceTableAuthorizationIT.testExistOkCreatesAnAbsentTableButDoesNotReadAnExistingOne,
 plus unit coverage in TestLanceMetadataAuthorizationMethodInterceptor.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to