Copilot commented on code in PR #9785:
URL: https://github.com/apache/gravitino/pull/9785#discussion_r2787329883


##########
core/src/main/java/org/apache/gravitino/catalog/CatalogManager.java:
##########
@@ -973,15 +982,16 @@ private CatalogWrapper createCatalogWrapper(
    * properties) of the catalog entity.
    *
    * @param entity The catalog entity.
-   * @return The resolved properties.
+   * @return The resolved BaseCatalog instance.
    */
-  private Map<String, String> getResolvedProperties(CatalogEntity entity) {
+  private BaseCatalog getBaseCatalog(CatalogEntity entity) {
     Map<String, String> conf = entity.getProperties();
     String provider = entity.getProvider();
 
     try (IsolatedClassLoader classLoader = createClassLoader(provider, conf)) {
       BaseCatalog<?> catalog = createBaseCatalog(classLoader, entity);
-      return classLoader.withClassLoader(cl -> catalog.properties(), 
RuntimeException.class);
+      classLoader.withClassLoader(cl -> catalog.properties(), 
RuntimeException.class);
+      return catalog;
     }

Review Comment:
   getBaseCatalog returns a BaseCatalog instance whose IsolatedClassLoader has 
been closed (try-with-resources). Returning objects whose classes come from a 
closed classloader is fragile (and the caller currently invokes methods outside 
a withClassLoader block). Prefer returning the resolved properties / metadata 
extracted inside withClassLoader, or returning a CatalogWrapper that owns the 
classloader and can be closed later.



##########
server/src/main/java/org/apache/gravitino/server/web/rest/CatalogOperations.java:
##########
@@ -73,11 +82,14 @@ public class CatalogOperations {
 
   private final CatalogDispatcher catalogDispatcher;
 
+  private final boolean filterSensitiveProperties;
+
   @Context private HttpServletRequest httpRequest;
 
   @Inject
   public CatalogOperations(CatalogDispatcher catalogDispatcher) {
     this.catalogDispatcher = catalogDispatcher;
+    this.filterSensitiveProperties = 
GravitinoEnv.getInstance().filterSensitiveProperties();
   }

Review Comment:
   CatalogOperations snapshots filterSensitiveProperties in the constructor via 
GravitinoEnv.getInstance().filterSensitiveProperties(). This makes the behavior 
depend on resource construction order (tests now have to inject 
GravitinoEnv.config early) and prevents runtime config changes from taking 
effect. Consider reading the flag from GravitinoEnv/config per request (or at 
least null-guarding when env config is not yet initialized).



##########
server/src/main/java/org/apache/gravitino/server/web/rest/CatalogOperations.java:
##########
@@ -268,7 +296,29 @@ public Response loadCatalog(
     try {
       NameIdentifier ident = NameIdentifierUtil.ofCatalog(metalakeName, 
catalogName);
       Catalog catalog = catalogDispatcher.loadCatalog(ident);
-      Response response = Utils.ok(new 
CatalogResponse(DTOConverters.toDTO(catalog)));
+      CatalogDTO catalogDTO;
+      if (filterSensitiveProperties) {
+        // Only allow users with alter permission to access sensitive data in 
catalog properties.
+        boolean enableAccessSensitiveData =
+            MetadataAuthzHelper.checkAccess(
+                ident,
+                Entity.EntityType.CATALOG,
+                AuthorizationExpressionConstants.CATALOG_OWNER_EXPRESSION);

Review Comment:
   The comment says "alter permission" but the access check uses 
CATALOG_OWNER_EXPRESSION (owner-only). Please either update the comment (and 
docs) to match owner-only semantics, or change the expression used here if 
alter-level access should also see sensitive properties.



##########
server/src/main/java/org/apache/gravitino/server/web/rest/CatalogOperations.java:
##########
@@ -101,16 +113,32 @@ public Response listCatalogs(
             // Lock the root and the metalake with WRITE lock to ensure the 
consistency of the list.
             if (verbose) {
               Catalog[] catalogs = 
catalogDispatcher.listCatalogsInfo(catalogNS);
-              catalogs =
-                  MetadataAuthzHelper.filterByExpression(
-                      metalake,
-                      
AuthorizationExpressionConstants.LOAD_CATALOG_AUTHORIZATION_EXPRESSION,
-                      Entity.EntityType.CATALOG,
-                      catalogs,
-                      (catalogEntity) ->
-                          NameIdentifierUtil.ofCatalog(metalake, 
catalogEntity.name()));
-              Response response = Utils.ok(new 
CatalogListResponse(DTOConverters.toDTOs(catalogs)));
-              LOG.info("List {} catalogs info under metalake: {}", 
catalogs.length, metalake);
+              CatalogDTO[] catalogDTOs;
+              if (filterSensitiveProperties) {
+                MetadataAuthzHelper.FilterResult<Catalog, Catalog> 
filterResult =
+                    MetadataAuthzHelper.partitionByTwoExpressions(
+                        metalake,
+                        
AuthorizationExpressionConstants.CATALOG_OWNER_EXPRESSION,
+                        
AuthorizationExpressionConstants.USE_CATALOG_EXPRESSION,
+                        Entity.EntityType.CATALOG,
+                        catalogs,
+                        (catalogEntity) ->
+                            NameIdentifierUtil.ofCatalog(metalake, 
catalogEntity.name()));
+                // First array: catalogs with full access (can see sensitive 
properties)
+                CatalogDTO[] fullAccessCatalogs = 
DTOConverters.toDTOs(filterResult.getFirst());
+                // Second array: catalogs with use access only (hide sensitive 
properties)
+                CatalogDTO[] limitedAccessCatalogs =
+                    
buildCatalogDTOsWithoutSensitiveProps(filterResult.getSecond());
+                catalogDTOs =
+                    Stream.concat(
+                            Arrays.stream(fullAccessCatalogs), 
Arrays.stream(limitedAccessCatalogs))
+                        .toArray(CatalogDTO[]::new);

Review Comment:
   The current implementation concatenates "full access" catalogs first and 
"limited access" catalogs second, which can reorder results compared to the 
underlying catalog list. If clients rely on stable ordering, consider producing 
DTOs in the original order and deciding per-catalog whether to strip sensitive 
properties based on the user’s privileges.



##########
server/src/test/java/org/apache/gravitino/server/web/rest/TestTableOperations.java:
##########
@@ -119,7 +119,14 @@ public static void setup() throws IllegalAccessException {
     Mockito.doReturn(100000L).when(config).get(TREE_LOCK_MAX_NODE_IN_MEMORY);
     Mockito.doReturn(1000L).when(config).get(TREE_LOCK_MIN_NODE_IN_MEMORY);
     Mockito.doReturn(36000L).when(config).get(TREE_LOCK_CLEAN_INTERVAL);
+    Mockito.doReturn(false)
+        .when(config)
+        .get(org.apache.gravitino.Configs.FILTER_SENSITIVE_PROPERTIES);
+    
Mockito.doReturn(false).when(config).get(org.apache.gravitino.Configs.CACHE_ENABLED);
+    
Mockito.doReturn(false).when(config).get(org.apache.gravitino.Configs.ENABLE_AUTHORIZATION);

Review Comment:
   Avoid using fully-qualified class names in method bodies for Configs; this 
file already follows imported usage elsewhere (e.g., TestCatalogOperations 
imports Configs). Add an import for org.apache.gravitino.Configs and reference 
Configs.FILTER_SENSITIVE_PROPERTIES / CACHE_ENABLED / ENABLE_AUTHORIZATION 
directly to match project conventions.



##########
core/src/main/java/org/apache/gravitino/credential/CredentialProviderFactory.java:
##########
@@ -44,6 +46,24 @@ public static CredentialProvider create(
     }
   }
 
+  /**
+   * Looks up all sensitive property keys from all registered credential 
providers.
+   *
+   * @return A set of all sensitive property keys used by credential providers.
+   */
+  public static Set<String> lookupSensitivePropertyKeys() {
+    ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
+    ServiceLoader<CredentialProvider> serviceLoader =
+        ServiceLoader.load(CredentialProvider.class, classLoader);
+
+    Set<String> allPropertyKeys = new HashSet<>();
+    for (CredentialProvider provider : serviceLoader) {
+      Set<String> providerPropertyKeys = provider.sensitivePropertyKeys();
+      allPropertyKeys.addAll(providerPropertyKeys);
+    }

Review Comment:
   lookupSensitivePropertyKeys() assumes 
CredentialProvider.sensitivePropertyKeys() is non-null and that providers can 
be safely instantiated via ServiceLoader without initialization. To make this 
utility robust, consider null-guarding the returned set and handling/isolating 
ServiceLoader/provider errors so a single bad provider can’t break catalog 
property metadata generation.



##########
docs/gravitino-server-config.md:
##########
@@ -260,6 +260,16 @@ Writer configuration begins with 
`gravitino.audit.writer.${name}`, where `${name
 
 Refer to [security](security/security.md) for HTTPS and authentication 
configurations.
 
+#### Catalog security configuration
+
+| Configuration item                                  | Description            
                                                                                
                                                                                
                              | Default value | Required | Since version |
+|-----------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------------|----------|---------------|
+| `gravitino.authorization.filterSensitiveProperties` | Whether to filter 
sensitive properties (passwords, secret keys, tokens) in catalog responses. 
When enabled, sensitive properties are hidden from users without appropriate 
permissions (owner or alter permission).  | `true`        | No       | 1.1.0    
     |
+
+:::info
+When `gravitino.authorization.filterSensitiveProperties` is set to `true`, 
sensitive catalog properties such as passwords, JDBC credentials, AWS secret 
keys, Azure storage account keys, and authentication tokens will be hidden in 
API responses unless the user has owner or alter permissions on the catalog.

Review Comment:
   The docs state that users with "owner or alter permission" can see sensitive 
catalog properties, but the implementation checks only the owner expression 
(ANY(OWNER, METALAKE, CATALOG)). Please align the documentation with the actual 
behavior (or update the authorization expression in code if alter-level access 
is intended).
   ```suggestion
   | `gravitino.authorization.filterSensitiveProperties` | Whether to filter 
sensitive properties (passwords, secret keys, tokens) in catalog responses. 
When enabled, sensitive properties are hidden from users without appropriate 
owner-level permissions on the catalog. | `true`        | No       | 1.1.0      
   |
   
   :::info
   When `gravitino.authorization.filterSensitiveProperties` is set to `true`, 
sensitive catalog properties such as passwords, JDBC credentials, AWS secret 
keys, Azure storage account keys, and authentication tokens will be hidden in 
API responses unless the user has owner-level permissions on the catalog.
   ```



##########
server/src/main/java/org/apache/gravitino/server/web/rest/CatalogOperations.java:
##########
@@ -101,16 +113,32 @@ public Response listCatalogs(
             // Lock the root and the metalake with WRITE lock to ensure the 
consistency of the list.
             if (verbose) {
               Catalog[] catalogs = 
catalogDispatcher.listCatalogsInfo(catalogNS);
-              catalogs =
-                  MetadataAuthzHelper.filterByExpression(
-                      metalake,
-                      
AuthorizationExpressionConstants.LOAD_CATALOG_AUTHORIZATION_EXPRESSION,
-                      Entity.EntityType.CATALOG,
-                      catalogs,
-                      (catalogEntity) ->
-                          NameIdentifierUtil.ofCatalog(metalake, 
catalogEntity.name()));
-              Response response = Utils.ok(new 
CatalogListResponse(DTOConverters.toDTOs(catalogs)));
-              LOG.info("List {} catalogs info under metalake: {}", 
catalogs.length, metalake);
+              CatalogDTO[] catalogDTOs;
+              if (filterSensitiveProperties) {
+                MetadataAuthzHelper.FilterResult<Catalog, Catalog> 
filterResult =
+                    MetadataAuthzHelper.partitionByTwoExpressions(
+                        metalake,
+                        
AuthorizationExpressionConstants.CATALOG_OWNER_EXPRESSION,
+                        
AuthorizationExpressionConstants.USE_CATALOG_EXPRESSION,
+                        Entity.EntityType.CATALOG,
+                        catalogs,
+                        (catalogEntity) ->
+                            NameIdentifierUtil.ofCatalog(metalake, 
catalogEntity.name()));
+                // First array: catalogs with full access (can see sensitive 
properties)
+                CatalogDTO[] fullAccessCatalogs = 
DTOConverters.toDTOs(filterResult.getFirst());
+                // Second array: catalogs with use access only (hide sensitive 
properties)
+                CatalogDTO[] limitedAccessCatalogs =
+                    
buildCatalogDTOsWithoutSensitiveProps(filterResult.getSecond());
+                catalogDTOs =
+                    Stream.concat(
+                            Arrays.stream(fullAccessCatalogs), 
Arrays.stream(limitedAccessCatalogs))
+                        .toArray(CatalogDTO[]::new);
+              } else {
+                // If filtering is disabled, return all catalogs with full 
properties
+                catalogDTOs = DTOConverters.toDTOs(catalogs);
+              }

Review Comment:
   In the verbose listCatalogs path, authorization filtering is skipped 
entirely when filterSensitiveProperties is false (it returns DTOs for all 
catalogs). Disabling sensitive-property filtering should not bypass access 
control; catalogs should still be filtered by the same authorization expression 
(e.g., LOAD_CATALOG_AUTHORIZATION_EXPRESSION / owner-or-use) and only toggle 
whether sensitive keys are removed.



##########
core/src/main/java/org/apache/gravitino/catalog/CatalogManager.java:
##########
@@ -357,7 +360,13 @@ public Catalog[] listCatalogsInfo(Namespace namespace) 
throws NoSuchMetalakeExce
           // provider is "fileset", still using "hadoop" will lead to catalog 
loading issue. So
           // after reading the catalog entity, we convert it to the new 
fileset catalog entity.
           .map(this::convertFilesetCatalogEntity)
-          .map(e -> e.toCatalogInfoWithResolvedProps(getResolvedProperties(e)))
+          .map(
+              e -> {
+                BaseCatalog catalog = getBaseCatalog(e);
+                return new CatalogInfoWithPropertyMeta(
+                    e.toCatalogInfoWithResolvedProps(catalog.properties()),
+                    catalog.catalogPropertiesMetadata());
+              })

Review Comment:
   listCatalogsInfo now builds CatalogInfoWithPropertyMeta using a BaseCatalog 
created from an IsolatedClassLoader that is closed immediately after 
getBaseCatalog returns. The returned BaseCatalog is then accessed outside the 
isolated classloader context (properties()/catalogPropertiesMetadata()), which 
can break when catalog loading is isolated and provider classes/resources are 
needed. Consider extracting the needed properties + PropertiesMetadata within 
classLoader.withClassLoader and return plain data (or keep the 
wrapper/classloader alive) instead of returning the catalog instance.



-- 
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