This is an automated email from the ASF dual-hosted git repository.

jackie pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/pinot.git


The following commit(s) were added to refs/heads/master by this push:
     new fed2d5f1b6 update access control check error handling to catch 
throwable and log errors (#13209)
fed2d5f1b6 is described below

commit fed2d5f1b613371237b5a29348f0c043200671ad
Author: dang-stripe <50119799+dang-str...@users.noreply.github.com>
AuthorDate: Fri May 24 14:15:51 2024 -0700

    update access control check error handling to catch throwable and log 
errors (#13209)
---
 .../controller/api/access/AccessControlUtils.java  |  8 +-
 .../api/access/AccessControlUtilsTest.java         | 75 +++++++++++++++++
 .../pinot/core/auth/FineGrainedAuthUtils.java      | 19 ++++-
 .../pinot/core/auth/FineGrainedAuthUtilsTest.java  | 94 ++++++++++++++++++++++
 4 files changed, 192 insertions(+), 4 deletions(-)

diff --git 
a/pinot-controller/src/main/java/org/apache/pinot/controller/api/access/AccessControlUtils.java
 
b/pinot-controller/src/main/java/org/apache/pinot/controller/api/access/AccessControlUtils.java
index 4e56339a4c..825776e38f 100644
--- 
a/pinot-controller/src/main/java/org/apache/pinot/controller/api/access/AccessControlUtils.java
+++ 
b/pinot-controller/src/main/java/org/apache/pinot/controller/api/access/AccessControlUtils.java
@@ -67,9 +67,11 @@ public final class AccessControlUtils {
     } catch (WebApplicationException exception) {
       // throwing the exception if it's WebApplicationException
       throw exception;
-    } catch (Exception e) {
-      throw new ControllerApplicationException(LOGGER, "Caught exception while 
validating permission for "
-          + userMessage, Response.Status.INTERNAL_SERVER_ERROR, e);
+    } catch (Throwable t) {
+      // catch and log Throwable for NoSuchMethodError which can happen when 
there are classpath conflicts
+      // otherwise, grizzly will return a 500 without any logs or indication 
of what failed
+      throw new ControllerApplicationException(LOGGER,
+          "Caught exception while validating permission for " + userMessage, 
Response.Status.INTERNAL_SERVER_ERROR, t);
     }
 
     throw new ControllerApplicationException(LOGGER, "Permission is denied for 
" + userMessage,
diff --git 
a/pinot-controller/src/test/java/org/apache/pinot/controller/api/access/AccessControlUtilsTest.java
 
b/pinot-controller/src/test/java/org/apache/pinot/controller/api/access/AccessControlUtilsTest.java
new file mode 100644
index 0000000000..5270414d34
--- /dev/null
+++ 
b/pinot-controller/src/test/java/org/apache/pinot/controller/api/access/AccessControlUtilsTest.java
@@ -0,0 +1,75 @@
+/**
+ * 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.controller.api.access;
+
+import javax.ws.rs.core.HttpHeaders;
+import javax.ws.rs.core.Response;
+import 
org.apache.pinot.controller.api.exception.ControllerApplicationException;
+import org.mockito.Mockito;
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+
+public class AccessControlUtilsTest {
+
+  private final String _table = "testTable";
+  private final String _endpoint = "/testEndpoint";
+
+  @Test
+  public void testValidatePermissionAllowed() {
+    AccessControl ac = Mockito.mock(AccessControl.class);
+    HttpHeaders mockHttpHeaders = Mockito.mock(HttpHeaders.class);
+
+    Mockito.when(ac.hasAccess(_table, AccessType.READ, mockHttpHeaders, 
_endpoint)).thenReturn(true);
+
+    AccessControlUtils.validatePermission(_table, AccessType.READ, 
mockHttpHeaders, _endpoint, ac);
+  }
+
+  @Test
+  public void testValidatePermissionDenied() {
+    AccessControl ac = Mockito.mock(AccessControl.class);
+    HttpHeaders mockHttpHeaders = Mockito.mock(HttpHeaders.class);
+
+    Mockito.when(ac.hasAccess(_table, AccessType.READ, mockHttpHeaders, 
_endpoint)).thenReturn(false);
+
+    try {
+      AccessControlUtils.validatePermission(_table, AccessType.READ, 
mockHttpHeaders, _endpoint, ac);
+      Assert.fail("Expected ControllerApplicationException");
+    } catch (ControllerApplicationException e) {
+      Assert.assertTrue(e.getMessage().contains("Permission is denied"));
+      Assert.assertEquals(e.getResponse().getStatus(), 
Response.Status.FORBIDDEN.getStatusCode());
+    }
+  }
+
+  @Test
+  public void testValidatePermissionWithNoSuchMethodError() {
+    AccessControl ac = Mockito.mock(AccessControl.class);
+    HttpHeaders mockHttpHeaders = Mockito.mock(HttpHeaders.class);
+
+    Mockito.when(ac.hasAccess(_table, AccessType.READ, mockHttpHeaders, 
_endpoint))
+        .thenThrow(new NoSuchMethodError("Method not found"));
+
+    try {
+      AccessControlUtils.validatePermission(_table, AccessType.READ, 
mockHttpHeaders, _endpoint, ac);
+    } catch (ControllerApplicationException e) {
+      Assert.assertTrue(e.getMessage().contains("Caught exception while 
validating permission"));
+      Assert.assertEquals(e.getResponse().getStatus(), 
Response.Status.INTERNAL_SERVER_ERROR.getStatusCode());
+    }
+  }
+}
diff --git 
a/pinot-core/src/main/java/org/apache/pinot/core/auth/FineGrainedAuthUtils.java 
b/pinot-core/src/main/java/org/apache/pinot/core/auth/FineGrainedAuthUtils.java
index 4a93bc3f10..ba8854818c 100644
--- 
a/pinot-core/src/main/java/org/apache/pinot/core/auth/FineGrainedAuthUtils.java
+++ 
b/pinot-core/src/main/java/org/apache/pinot/core/auth/FineGrainedAuthUtils.java
@@ -28,12 +28,17 @@ import javax.ws.rs.core.UriInfo;
 import org.apache.commons.lang.StringUtils;
 import org.apache.pinot.common.utils.DatabaseUtils;
 import org.apache.pinot.spi.utils.builder.TableNameBuilder;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 
 /**
  * Utility methods to share in Broker and Controller request filters related 
to fine grain authorization.
  */
 public class FineGrainedAuthUtils {
+
+  private static final Logger LOGGER = 
LoggerFactory.getLogger(FineGrainedAuthUtils.class);
+
   private FineGrainedAuthUtils() {
   }
 
@@ -106,8 +111,20 @@ public class FineGrainedAuthUtils {
             Response.Status.INTERNAL_SERVER_ERROR);
       }
 
+      boolean hasAccess;
+      try {
+        hasAccess = accessControl.hasAccess(httpHeaders, auth.targetType(), 
targetId, auth.action());
+      } catch (Throwable t) {
+        // catch and log Throwable for NoSuchMethodError which can happen when 
there are classpath conflicts
+        // otherwise, grizzly will return a 500 without any logs or indication 
of what failed
+        String errorMsg = String.format("Failed to check for access for target 
type %s and target ID %s with action %s",
+            auth.targetType(), targetId, auth.action());
+        LOGGER.error(errorMsg, t);
+        throw new WebApplicationException(errorMsg, t, 
Response.Status.INTERNAL_SERVER_ERROR);
+      }
+
       // Check for access now
-      if (!accessControl.hasAccess(httpHeaders, auth.targetType(), targetId, 
auth.action())) {
+      if (!hasAccess) {
         throw new WebApplicationException(accessDeniedMsg, 
Response.Status.FORBIDDEN);
       }
     } else if (!accessControl.defaultAccess(httpHeaders)) {
diff --git 
a/pinot-core/src/test/java/org/apache/pinot/core/auth/FineGrainedAuthUtilsTest.java
 
b/pinot-core/src/test/java/org/apache/pinot/core/auth/FineGrainedAuthUtilsTest.java
new file mode 100644
index 0000000000..c18f30ecf6
--- /dev/null
+++ 
b/pinot-core/src/test/java/org/apache/pinot/core/auth/FineGrainedAuthUtilsTest.java
@@ -0,0 +1,94 @@
+/**
+ * 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.core.auth;
+
+import java.lang.reflect.Method;
+import javax.ws.rs.WebApplicationException;
+import javax.ws.rs.core.HttpHeaders;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.UriInfo;
+import org.mockito.Mockito;
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+
+public class FineGrainedAuthUtilsTest {
+
+  @Test
+  public void testValidateFineGrainedAuthAllowed() {
+    FineGrainedAccessControl ac = Mockito.mock(FineGrainedAccessControl.class);
+    Mockito.when(ac.hasAccess(Mockito.any(HttpHeaders.class), Mockito.any(), 
Mockito.any(), Mockito.any()))
+        .thenReturn(true);
+
+    UriInfo mockUriInfo = Mockito.mock(UriInfo.class);
+    HttpHeaders mockHttpHeaders = Mockito.mock(HttpHeaders.class);
+
+    FineGrainedAuthUtils.validateFineGrainedAuth(getAnnotatedMethod(), 
mockUriInfo, mockHttpHeaders, ac);
+  }
+
+  @Test
+  public void testValidateFineGrainedAuthDenied() {
+    FineGrainedAccessControl ac = Mockito.mock(FineGrainedAccessControl.class);
+    Mockito.when(ac.hasAccess(Mockito.any(HttpHeaders.class), Mockito.any(), 
Mockito.any(), Mockito.any()))
+        .thenReturn(false);
+
+    UriInfo mockUriInfo = Mockito.mock(UriInfo.class);
+    HttpHeaders mockHttpHeaders = Mockito.mock(HttpHeaders.class);
+
+    try {
+      FineGrainedAuthUtils.validateFineGrainedAuth(getAnnotatedMethod(), 
mockUriInfo, mockHttpHeaders, ac);
+      Assert.fail("Expected WebApplicationException");
+    } catch (WebApplicationException e) {
+      Assert.assertTrue(e.getMessage().contains("Access denied to getCluster 
in the cluster"));
+      Assert.assertEquals(e.getResponse().getStatus(), 
Response.Status.FORBIDDEN.getStatusCode());
+    }
+  }
+
+  @Test
+  public void testValidateFineGrainedAuthWithNoSuchMethodError() {
+    FineGrainedAccessControl ac = Mockito.mock(FineGrainedAccessControl.class);
+    Mockito.when(ac.hasAccess(Mockito.any(HttpHeaders.class), Mockito.any(), 
Mockito.any(), Mockito.any()))
+        .thenThrow(new NoSuchMethodError("Method not found"));
+
+    UriInfo mockUriInfo = Mockito.mock(UriInfo.class);
+    HttpHeaders mockHttpHeaders = Mockito.mock(HttpHeaders.class);
+
+    try {
+      FineGrainedAuthUtils.validateFineGrainedAuth(getAnnotatedMethod(), 
mockUriInfo, mockHttpHeaders, ac);
+      Assert.fail("Expected WebApplicationException");
+    } catch (WebApplicationException e) {
+      Assert.assertTrue(e.getMessage().contains("Failed to check for access"));
+      Assert.assertEquals(e.getResponse().getStatus(), 
Response.Status.INTERNAL_SERVER_ERROR.getStatusCode());
+    }
+  }
+
+  static class TestResource {
+    @Authorize(targetType = TargetType.CLUSTER, action = "getCluster")
+    void getCluster() {
+    }
+  }
+
+  private Method getAnnotatedMethod() {
+    try {
+      return TestResource.class.getDeclaredMethod("getCluster");
+    } catch (NoSuchMethodException e) {
+      throw new RuntimeException(e);
+    }
+  }
+}


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscr...@pinot.apache.org
For additional commands, e-mail: commits-h...@pinot.apache.org

Reply via email to