Copilot commented on code in PR #17295:
URL: https://github.com/apache/pinot/pull/17295#discussion_r2613239574


##########
pinot-core/src/main/java/org/apache/pinot/core/auth/BasicAuthUtils.java:
##########
@@ -122,10 +122,14 @@ public static List<ZkBasicAuthPrincipal> 
extractBasicAuthPrincipals(List<UserCon
               .orElseGet(() -> Collections.emptyList())
               .stream().map(x -> x.toString())
               .collect(Collectors.toSet());
-          //todo: Handle RLS filters properly
+
+          // Extract RLS filters from UserConfig

Review Comment:
   Consider adding a brief inline comment explaining that RLS filters are now 
being extracted from UserConfig, since this is a key change from the previous 
implementation where an empty map was passed. This would help future readers 
understand the significance of this change.
   ```suggestion
             // Extract RLS filters from UserConfig.
             // Note: This is a key change from the previous implementation 
where an empty map was always passed.
   ```



##########
pinot-broker/src/main/java/org/apache/pinot/broker/broker/ZkBasicAuthAccessControlFactory.java:
##########
@@ -120,6 +124,26 @@ public TableAuthorizationResult 
authorize(RequesterIdentity requesterIdentity, S
       return new TableAuthorizationResult(failedTables);
     }
 
+    @Override
+    public TableRowColAccessResult getRowColFilters(RequesterIdentity 
requesterIdentity, String table) {
+      Optional<ZkBasicAuthPrincipal> principalOpt = 
getPrincipalAuth(requesterIdentity);
+
+      Preconditions.checkState(principalOpt.isPresent(), "Principal is not 
authorized");
+      Preconditions.checkState(table != null, "Table cannot be null");
+
+      TableRowColAccessResult tableRowColAccessResult = new 
TableRowColAccessResultImpl();
+      ZkBasicAuthPrincipal principal = principalOpt.get();
+
+      //precondition: The principal should have the table.

Review Comment:
   The comment has a typo: 'precondition' should be capitalized and formatted 
consistently with the Preconditions.checkArgument below it. Consider changing 
this to a standard Javadoc comment or removing it since the 
Preconditions.checkArgument already expresses this requirement clearly.
   ```suggestion
   
   ```



##########
pinot-common/src/main/java/org/apache/pinot/common/utils/config/AccessControlUserConfigUtils.java:
##########
@@ -48,13 +56,32 @@ public static UserConfig fromZNRecord(ZNRecord znRecord) {
         List<String> tableList = znRecord.getListField(UserConfig.TABLES_KEY);
         List<String> excludeTableList = 
znRecord.getListField(UserConfig.EXCLUDE_TABLES_KEY);
 
-        List<String> permissionListFromZNRecord = 
znRecord.getListField(UserConfig.PERMISSIONS_KEY);
+        List<String> permissionListFromZNRecord = znRecord.getListField(
+            UserConfig.PERMISSIONS_KEY);
         List<AccessType> permissionList = null;
         if (permissionListFromZNRecord != null) {
             permissionList = permissionListFromZNRecord.stream()
-                .map(x -> AccessType.valueOf(x)).collect(Collectors.toList());
+                .map(x -> AccessType.valueOf(x))
+                .collect(Collectors.toList());
+        }
+
+        // Extract RLS filters from simple fields (stored as JSON)
+        Map<String, List<String>> rlsFilters = null;
+        String rlsFiltersJson = simpleFields.get(UserConfig.RLS_FILTERS_KEY);
+        if (rlsFiltersJson != null && !rlsFiltersJson.isEmpty()) {
+            try {
+                rlsFilters = JsonUtils.stringToObject(
+                    rlsFiltersJson, new TypeReference<Map<String, 
List<String>>>() {
+                    }
+                );
+            } catch (IOException e) {
+                // Log error but continue - RLS filters are optional
+                LOGGER.error("Failed to deserialize RLS filters for user: {}", 
username, e);
+            }
         }

Review Comment:
   Consider adding a comment explaining why RLS filters are stored as JSON in 
simple fields rather than using list fields like other list-based data. This 
design decision would be helpful for future maintainers to understand the 
serialization approach.



##########
pinot-integration-tests/src/test/java/org/apache/pinot/integration/tests/RowLevelSecurityZkAuthIntegrationTest.java:
##########
@@ -0,0 +1,209 @@
+/**
+ * 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.pinot.integration.tests;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import java.util.Map;
+import org.apache.pinot.spi.env.PinotConfiguration;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+
+/**
+ * Row Level Security integration test using ZkAuth (ZooKeeper-based 
authentication).
+ * Users and permissions are created dynamically via REST API calls to the 
controller.
+ * This approach allows for dynamic user management without requiring cluster 
restarts.
+ */
+public class RowLevelSecurityZkAuthIntegrationTest extends 
RowLevelSecurityIntegrationTestBase {
+  private static final Logger LOGGER = 
LoggerFactory.getLogger(RowLevelSecurityZkAuthIntegrationTest.class);
+
+  @Override
+  protected void overrideControllerConf(Map<String, Object> properties) {
+    properties.put("controller.segment.fetcher.auth.token", AUTH_TOKEN);
+    properties.put("controller.admin.access.control.factory.class",
+        
"org.apache.pinot.controller.api.access.ZkBasicAuthAccessControlFactory");
+    properties.put("access.control.init.username", ADMIN_USER);
+    properties.put("access.control.init.password", ADMIN_PASSWORD);
+  }
+
+  @Override
+  protected void overrideBrokerConf(PinotConfiguration brokerConf) {
+    brokerConf.setProperty("pinot.broker.enable.row.column.level.auth", 
"true");
+    brokerConf.setProperty("pinot.broker.access.control.class",
+        "org.apache.pinot.broker.broker.ZkBasicAuthAccessControlFactory");
+  }
+
+  @Override
+  protected void overrideServerConf(PinotConfiguration serverConf) {
+    serverConf.setProperty("pinot.server.segment.fetcher.auth.token", 
AUTH_TOKEN);
+    serverConf.setProperty("pinot.server.segment.uploader.auth.token", 
AUTH_TOKEN);
+    serverConf.setProperty("pinot.server.instance.auth.token", AUTH_TOKEN);
+  }
+
+  /**
+   * Initialize users via REST API after the cluster has started.
+   * This creates the same users with the same permissions and RLS filters as 
BasicAuth test.
+   */
+  @Override
+  protected void initializeUsers()
+      throws Exception {
+    LOGGER.info("Initializing users via REST API for ZkAuth test");
+
+    // Wait for controller to be ready
+    waitForControllerReady();
+
+    // Create users for BROKER component with RLS filters
+    createBrokerUsers();
+
+    // Create users for CONTROLLER component
+    createControllerUsers();
+
+    // Create users for SERVER component
+    createServerUsers();
+
+    LOGGER.info("Successfully initialized all users for ZkAuth test");
+  }
+
+  private void waitForControllerReady()
+      throws Exception {
+    int maxRetries = 30;
+    int retryCount = 0;
+    String healthUrl = "http://localhost:"; + getControllerPort() + "/health";
+    while (retryCount < maxRetries) {
+      try {
+        String response = sendGetRequest(healthUrl);
+        if (response != null && !response.isEmpty()) {
+          LOGGER.info("Controller is ready");
+          return;
+        }
+      } catch (Exception e) {
+        // Controller not ready yet

Review Comment:
   Consider logging the exception message to help with debugging when the 
controller fails to become ready. This would provide more context about why the 
health check failed.
   ```suggestion
           // Controller not ready yet
           LOGGER.debug("Controller health check failed on attempt {}: {}", 
retryCount + 1, e.getMessage(), e);
   ```



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


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to