This is an automated email from the ASF dual-hosted git repository.
roryqi pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/gravitino.git
The following commit(s) were added to refs/heads/main by this push:
new c79996e7a8 [#12269] fix(authz): Allow catalog owners to grant schema
privileges (#12673)
c79996e7a8 is described below
commit c79996e7a882147bbe93660edc2bc771a2fb2292
Author: Stefan Wang <[email protected]>
AuthorDate: Thu Aug 27 22:23:38 2026 -0400
[#12269] fix(authz): Allow catalog owners to grant schema privileges
(#12673)
### What changes were proposed in this pull request?
A user who owns a catalog and one of its schemas can currently get `403
Forbidden` when granting schema privileges unless they also have an
explicit `USE_CATALOG` grant. This change allows the catalog owner to
perform the operation and adds a regression for that case.
The authorizer now continues checking ancestors when ownership of the
current object does not satisfy its parent usage requirement.
### Why are the changes needed?
The schema ownership check returned its failed `USE_CATALOG` result
immediately, so the authorizer never reached the catalog ownership
check. A user who owned both objects received `403 Forbidden: "Current
user can not grant privilege to role."`, while owning only the catalog
allowed the same operation.
Fix: #12269
### Does this PR introduce _any_ user-facing change?
Yes. A catalog owner who also owns a child schema can grant privileges
on that schema without a redundant `USE_CATALOG` grant.
### How was this patch tested?
```shell
./gradlew :server-common:test --tests
'org.apache.gravitino.server.authorization.jcasbin.TestJcasbinAuthorizer'
```
<details>
<summary>Raw logs</summary>
```text
Before, on upstream main:
TestJcasbinAuthorizer.testHasSetOwnerPermissionAllowsSchemaAndCatalogOwner
FAILED
expected: <true> but was: <false>
Tests: 1, failures: 1
After:
Tests: 59, skipped: 0, failures: 0, errors: 0
BUILD SUCCESSFUL
```
</details>
Signed-off-by: 1fanwang <[email protected]>
---
.../authorization/jcasbin/JcasbinAuthorizer.java | 7 ++++---
.../jcasbin/TestJcasbinAuthorizer.java | 24 ++++++++++++++++++++++
2 files changed, 28 insertions(+), 3 deletions(-)
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 eb3c2ab766..4e82289318 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
@@ -585,9 +585,10 @@ public class JcasbinAuthorizer implements
GravitinoAuthorizer {
MetadataObject metadataObject = MetadataObjects.parse(fullName,
metadataType);
do {
- if (isOwner(currentPrincipal, metalake, metadataObject, requestContext))
{
- return hasParentUsagePermission(
- currentPrincipal, metalake, metadataObject, metalakeObject,
requestContext);
+ if (isOwner(currentPrincipal, metalake, metadataObject, requestContext)
+ && hasParentUsagePermission(
+ currentPrincipal, metalake, metadataObject, metalakeObject,
requestContext)) {
+ return true;
}
} while ((metadataObject = MetadataObjects.parent(metadataObject)) !=
null);
return false;
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 80297eda17..c5339e1a18 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
@@ -2428,6 +2428,30 @@ public class TestJcasbinAuthorizer {
"Owner should be able to manage privileges without checking DENY
MANAGE_GRANTS");
}
+ @Test
+ public void testHasSetOwnerPermissionAllowsSchemaAndCatalogOwner() throws
Exception {
+ MetadataObject metalakeObject =
+ MetadataObjects.of(ImmutableList.of(METALAKE),
MetadataObject.Type.METALAKE);
+ metadataIdConverterMockedStatic
+ .when(() -> MetadataIdConverter.getID(eq(metalakeObject),
eq(METALAKE)))
+ .thenReturn(Optional.of(USER_METALAKE_ID));
+ when(ownerMetaMapper.selectOwnerByMetadataObjectIdAndType(eq(CATALOG_ID),
eq("SCHEMA")))
+ .thenReturn(new OwnerInfo(USER_ID, "USER"));
+ when(ownerMetaMapper.selectOwnerByMetadataObjectIdAndType(eq(CATALOG_ID),
eq("CATALOG")))
+ .thenReturn(new OwnerInfo(USER_ID, "USER"));
+ getOwnerRelCache(jcasbinAuthorizer).invalidateAll();
+
+ try {
+ assertTrue(
+ jcasbinAuthorizer.hasSetOwnerPermission(
+ METALAKE, "SCHEMA", "testCatalog.testSchema", new
AuthorizationRequestContext()));
+ } finally {
+ metadataIdConverterMockedStatic
+ .when(() -> MetadataIdConverter.getID(eq(metalakeObject),
eq(METALAKE)))
+ .thenReturn(Optional.of(CATALOG_ID));
+ }
+ }
+
@Test
public void testHasSetOwnerPermissionRejectsDenyUseCatalogForTableOwner()
throws Exception {
makeCompletableFutureUseCurrentThread(jcasbinAuthorizer);