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

apucher pushed a commit to branch manual-authorization-annotation
in repository https://gitbox.apache.org/repos/asf/pinot.git

commit ee391e918b188e6e7e6dade5f11157b4c04cf1d4
Author: Alexander Pucher <[email protected]>
AuthorDate: Fri Aug 19 13:21:45 2022 -0700

    add @ManualAuthorization annotation for non-standard endpoints
---
 .../api/access/AuthenticationFilter.java           |  5 +++
 .../controller/api/access/ManualAuthorization.java | 37 ++++++++++++++++++++++
 .../api/resources/PinotQueryResource.java          |  2 ++
 .../api/resources/PinotSchemaRestletResource.java  | 15 +++++++--
 .../api/resources/PinotTableRestletResource.java   | 23 ++++++++++++--
 .../api/resources/TableConfigsRestletResource.java | 28 +++++++++++++++-
 6 files changed, 105 insertions(+), 5 deletions(-)

diff --git 
a/pinot-controller/src/main/java/org/apache/pinot/controller/api/access/AuthenticationFilter.java
 
b/pinot-controller/src/main/java/org/apache/pinot/controller/api/access/AuthenticationFilter.java
index 8ebd1a2883..2ff482acc9 100644
--- 
a/pinot-controller/src/main/java/org/apache/pinot/controller/api/access/AuthenticationFilter.java
+++ 
b/pinot-controller/src/main/java/org/apache/pinot/controller/api/access/AuthenticationFilter.java
@@ -82,6 +82,11 @@ public class AuthenticationFilter implements 
ContainerRequestFilter {
       return;
     }
 
+    // check if the method's authorization is disabled (i.e. performed 
manually within method)
+    if (endpointMethod.isAnnotationPresent(ManualAuthorization.class)) {
+      return;
+    }
+
     // Note that table name is extracted from "path parameters" or "query 
parameters" if it's defined as one of the
     // followings:
     //     - "tableName",
diff --git 
a/pinot-controller/src/main/java/org/apache/pinot/controller/api/access/ManualAuthorization.java
 
b/pinot-controller/src/main/java/org/apache/pinot/controller/api/access/ManualAuthorization.java
new file mode 100644
index 0000000000..b0624418c8
--- /dev/null
+++ 
b/pinot-controller/src/main/java/org/apache/pinot/controller/api/access/ManualAuthorization.java
@@ -0,0 +1,37 @@
+/**
+ * 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 java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+
+/**
+ * Annotation to be used on top of REST endpoints. Methods annotated with this 
annotation don't perform default
+ * authorization via AuthenticationFilter. This is useful when performing 
authorization manually via calls to
+ * {@code AuthenticationFiler.validatePermissions()}
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.METHOD)
+public @interface ManualAuthorization {
+
+}
diff --git 
a/pinot-controller/src/main/java/org/apache/pinot/controller/api/resources/PinotQueryResource.java
 
b/pinot-controller/src/main/java/org/apache/pinot/controller/api/resources/PinotQueryResource.java
index a382122c33..ebee631174 100644
--- 
a/pinot-controller/src/main/java/org/apache/pinot/controller/api/resources/PinotQueryResource.java
+++ 
b/pinot-controller/src/main/java/org/apache/pinot/controller/api/resources/PinotQueryResource.java
@@ -54,6 +54,7 @@ import org.apache.pinot.common.utils.request.RequestUtils;
 import org.apache.pinot.controller.ControllerConf;
 import org.apache.pinot.controller.api.access.AccessControl;
 import org.apache.pinot.controller.api.access.AccessControlFactory;
+import org.apache.pinot.controller.api.access.ManualAuthorization;
 import org.apache.pinot.controller.helix.core.PinotHelixResourceManager;
 import org.apache.pinot.core.query.executor.sql.SqlQueryExecutor;
 import org.apache.pinot.spi.utils.CommonConstants;
@@ -87,6 +88,7 @@ public class PinotQueryResource {
 
   @POST
   @Path("sql")
+  @ManualAuthorization // performed by broker
   public String handlePostSql(String requestJsonStr, @Context HttpHeaders 
httpHeaders) {
     try {
       JsonNode requestJson = JsonUtils.stringToJsonNode(requestJsonStr);
diff --git 
a/pinot-controller/src/main/java/org/apache/pinot/controller/api/resources/PinotSchemaRestletResource.java
 
b/pinot-controller/src/main/java/org/apache/pinot/controller/api/resources/PinotSchemaRestletResource.java
index 2bb05d0553..c5a39a38c2 100644
--- 
a/pinot-controller/src/main/java/org/apache/pinot/controller/api/resources/PinotSchemaRestletResource.java
+++ 
b/pinot-controller/src/main/java/org/apache/pinot/controller/api/resources/PinotSchemaRestletResource.java
@@ -61,6 +61,7 @@ import 
org.apache.pinot.controller.api.access.AccessControlFactory;
 import org.apache.pinot.controller.api.access.AccessControlUtils;
 import org.apache.pinot.controller.api.access.AccessType;
 import org.apache.pinot.controller.api.access.Authenticate;
+import org.apache.pinot.controller.api.access.ManualAuthorization;
 import org.apache.pinot.controller.api.events.MetadataEventNotifierFactory;
 import org.apache.pinot.controller.api.events.SchemaEventType;
 import 
org.apache.pinot.controller.api.exception.ControllerApplicationException;
@@ -209,6 +210,7 @@ public class PinotSchemaRestletResource {
       @ApiResponse(code = 400, message = "Missing or invalid request body"),
       @ApiResponse(code = 500, message = "Internal error")
   })
+  @ManualAuthorization // performed after parsing schema
   public ConfigSuccessResponse addSchema(
       @ApiParam(value = "Whether to override the schema if the schema exists") 
@DefaultValue("true")
       @QueryParam("override") boolean override, FormDataMultiPart multiPart, 
@Context HttpHeaders httpHeaders,
@@ -235,6 +237,7 @@ public class PinotSchemaRestletResource {
       @ApiResponse(code = 400, message = "Missing or invalid request body"),
       @ApiResponse(code = 500, message = "Internal error")
   })
+  @ManualAuthorization // performed after parsing schema
   public ConfigSuccessResponse addSchema(
       @ApiParam(value = "Whether to override the schema if the schema exists") 
@DefaultValue("true")
       @QueryParam("override") boolean override, String schemaJsonString, 
@Context HttpHeaders httpHeaders,
@@ -266,11 +269,15 @@ public class PinotSchemaRestletResource {
       @ApiResponse(code = 400, message = "Missing or invalid request body"),
       @ApiResponse(code = 500, message = "Internal error")
   })
-  public String validateSchema(FormDataMultiPart multiPart) {
+  @ManualAuthorization // performed after parsing schema
+  public String validateSchema(FormDataMultiPart multiPart, @Context 
HttpHeaders httpHeaders, @Context Request request) {
     Pair<Schema, Map<String, Object>> schemaAndUnrecognizedProps =
         getSchemaAndUnrecognizedPropertiesFromMultiPart(multiPart);
     Schema schema = schemaAndUnrecognizedProps.getLeft();
+    String endpointUrl = request.getRequestURL().toString();
     validateSchemaInternal(schema);
+    AccessControlUtils.validatePermission(schema.getSchemaName(), 
AccessType.CREATE, httpHeaders, endpointUrl,
+        _accessControlFactory.create());
     ObjectNode response = schema.toJsonObject();
     response.set("unrecognizedProperties", 
JsonUtils.objectToJsonNode(schemaAndUnrecognizedProps.getRight()));
     try {
@@ -291,7 +298,8 @@ public class PinotSchemaRestletResource {
       @ApiResponse(code = 400, message = "Missing or invalid request body"),
       @ApiResponse(code = 500, message = "Internal error")
   })
-  public String validateSchema(String schemaJsonString) {
+  @ManualAuthorization // performed after parsing schema
+  public String validateSchema(String schemaJsonString, @Context HttpHeaders 
httpHeaders, @Context Request request) {
     Pair<Schema, Map<String, Object>> schemaAndUnrecognizedProps = null;
     try {
       schemaAndUnrecognizedProps = 
JsonUtils.stringToObjectAndUnrecognizedProperties(schemaJsonString, 
Schema.class);
@@ -300,7 +308,10 @@ public class PinotSchemaRestletResource {
       throw new ControllerApplicationException(LOGGER, msg, 
Response.Status.BAD_REQUEST, e);
     }
     Schema schema = schemaAndUnrecognizedProps.getLeft();
+    String endpointUrl = request.getRequestURL().toString();
     validateSchemaInternal(schema);
+    AccessControlUtils.validatePermission(schema.getSchemaName(), 
AccessType.CREATE, httpHeaders, endpointUrl,
+        _accessControlFactory.create());
     ObjectNode response = schema.toJsonObject();
     response.set("unrecognizedProperties", 
JsonUtils.objectToJsonNode(schemaAndUnrecognizedProps.getRight()));
     try {
diff --git 
a/pinot-controller/src/main/java/org/apache/pinot/controller/api/resources/PinotTableRestletResource.java
 
b/pinot-controller/src/main/java/org/apache/pinot/controller/api/resources/PinotTableRestletResource.java
index 5d5030a4e6..2965e80ca2 100644
--- 
a/pinot-controller/src/main/java/org/apache/pinot/controller/api/resources/PinotTableRestletResource.java
+++ 
b/pinot-controller/src/main/java/org/apache/pinot/controller/api/resources/PinotTableRestletResource.java
@@ -80,6 +80,7 @@ import 
org.apache.pinot.controller.api.access.AccessControlFactory;
 import org.apache.pinot.controller.api.access.AccessControlUtils;
 import org.apache.pinot.controller.api.access.AccessType;
 import org.apache.pinot.controller.api.access.Authenticate;
+import org.apache.pinot.controller.api.access.ManualAuthorization;
 import 
org.apache.pinot.controller.api.exception.ControllerApplicationException;
 import org.apache.pinot.controller.api.exception.InvalidTableConfigException;
 import org.apache.pinot.controller.api.exception.TableAlreadyExistsException;
@@ -168,6 +169,7 @@ public class PinotTableRestletResource {
   @Produces(MediaType.APPLICATION_JSON)
   @Path("/tables")
   @ApiOperation(value = "Adds a table", notes = "Adds a table")
+  @ManualAuthorization // performed after parsing table configs
   public ConfigSuccessResponse addTable(
       String tableConfigStr,
       @ApiParam(value = "comma separated list of validation type(s) to skip. 
supported types: (ALL|TASK|UPSERT)")
@@ -534,7 +536,8 @@ public class PinotTableRestletResource {
   public ObjectNode checkTableConfig(
       String tableConfigStr,
       @ApiParam(value = "comma separated list of validation type(s) to skip. 
supported types: (ALL|TASK|UPSERT)")
-      @QueryParam("validationTypesToSkip") @Nullable String typesToSkip) {
+      @QueryParam("validationTypesToSkip") @Nullable String typesToSkip, 
@Context HttpHeaders httpHeaders,
+      @Context Request request) {
     Pair<TableConfig, Map<String, Object>> tableConfig;
     try {
       tableConfig = 
JsonUtils.stringToObjectAndUnrecognizedProperties(tableConfigStr, 
TableConfig.class);
@@ -542,6 +545,13 @@ public class PinotTableRestletResource {
       String msg = String.format("Invalid table config json string: %s", 
tableConfigStr);
       throw new ControllerApplicationException(LOGGER, msg, 
Response.Status.BAD_REQUEST, e);
     }
+
+    // validate permission
+    String tableName = tableConfig.getLeft().getTableName();
+    String endpointUrl = request.getRequestURL().toString();
+    AccessControlUtils.validatePermission(tableName, AccessType.CREATE, 
httpHeaders, endpointUrl,
+        _accessControlFactory.create());
+
     ObjectNode validationResponse =
         validateConfig(tableConfig.getLeft(), 
_pinotHelixResourceManager.getSchemaForTableConfig(tableConfig.getLeft()),
             typesToSkip);
@@ -562,12 +572,21 @@ public class PinotTableRestletResource {
   public String validateTableAndSchema(
       TableAndSchemaConfig tableSchemaConfig,
       @ApiParam(value = "comma separated list of validation type(s) to skip. 
supported types: (ALL|TASK|UPSERT)")
-      @QueryParam("validationTypesToSkip") @Nullable String typesToSkip) {
+      @QueryParam("validationTypesToSkip") @Nullable String typesToSkip, 
@Context HttpHeaders httpHeaders,
+      @Context Request request) {
     TableConfig tableConfig = tableSchemaConfig.getTableConfig();
     Schema schema = tableSchemaConfig.getSchema();
+
     if (schema == null) {
       schema = _pinotHelixResourceManager.getSchemaForTableConfig(tableConfig);
     }
+
+    // validate permission
+    String schemaName = schema != null ? schema.getSchemaName() : null;
+    String endpointUrl = request.getRequestURL().toString();
+    AccessControlUtils.validatePermission(schemaName, AccessType.CREATE, 
httpHeaders, endpointUrl,
+        _accessControlFactory.create());
+
     return validateConfig(tableSchemaConfig.getTableConfig(), schema, 
typesToSkip).toString();
   }
 
diff --git 
a/pinot-controller/src/main/java/org/apache/pinot/controller/api/resources/TableConfigsRestletResource.java
 
b/pinot-controller/src/main/java/org/apache/pinot/controller/api/resources/TableConfigsRestletResource.java
index d238756783..d309fd2dba 100644
--- 
a/pinot-controller/src/main/java/org/apache/pinot/controller/api/resources/TableConfigsRestletResource.java
+++ 
b/pinot-controller/src/main/java/org/apache/pinot/controller/api/resources/TableConfigsRestletResource.java
@@ -56,6 +56,7 @@ import 
org.apache.pinot.controller.api.access.AccessControlFactory;
 import org.apache.pinot.controller.api.access.AccessControlUtils;
 import org.apache.pinot.controller.api.access.AccessType;
 import org.apache.pinot.controller.api.access.Authenticate;
+import org.apache.pinot.controller.api.access.ManualAuthorization;
 import 
org.apache.pinot.controller.api.exception.ControllerApplicationException;
 import org.apache.pinot.controller.api.exception.InvalidTableConfigException;
 import org.apache.pinot.controller.api.exception.TableAlreadyExistsException;
@@ -160,6 +161,7 @@ public class TableConfigsRestletResource {
   @Path("/tableConfigs")
   @ApiOperation(value = "Add the TableConfigs using the tableConfigsStr json",
       notes = "Add the TableConfigs using the tableConfigsStr json")
+  @ManualAuthorization // performed after parsing table configs
   public ConfigSuccessResponse addConfig(
       String tableConfigsStr,
       @ApiParam(value = "comma separated list of validation type(s) to skip. 
supported types: (ALL|TASK|UPSERT)")
@@ -378,7 +380,8 @@ public class TableConfigsRestletResource {
   @ApiOperation(value = "Validate the TableConfigs", notes = "Validate the 
TableConfigs")
   public String validateConfig(String tableConfigsStr,
       @ApiParam(value = "comma separated list of validation type(s) to skip. 
supported types: (ALL|TASK|UPSERT)")
-      @QueryParam("validationTypesToSkip") @Nullable String typesToSkip) {
+      @QueryParam("validationTypesToSkip") @Nullable String typesToSkip, 
@Context HttpHeaders httpHeaders,
+      @Context Request request) {
     Pair<TableConfigs, Map<String, Object>> tableConfigsAndUnrecognizedProps;
     TableConfigs tableConfigs;
     try {
@@ -389,6 +392,29 @@ public class TableConfigsRestletResource {
       throw new ControllerApplicationException(LOGGER,
           String.format("Invalid TableConfigs json string: %s", 
tableConfigsStr), Response.Status.BAD_REQUEST, e);
     }
+
+    String endpointUrl = request.getRequestURL().toString();
+    AccessControl accessControl = _accessControlFactory.create();
+    Schema schema = tableConfigs.getSchema();
+    TableConfig offlineTableConfig = tableConfigs.getOffline();
+    TableConfig realtimeTableConfig = tableConfigs.getRealtime();
+
+    AccessControlUtils
+        .validatePermission(schema.getSchemaName(), AccessType.CREATE, 
httpHeaders, endpointUrl, accessControl);
+
+    if (offlineTableConfig != null) {
+      tuneConfig(offlineTableConfig, schema);
+      AccessControlUtils
+          .validatePermission(offlineTableConfig.getTableName(), 
AccessType.CREATE, httpHeaders, endpointUrl,
+              accessControl);
+    }
+    if (realtimeTableConfig != null) {
+      tuneConfig(realtimeTableConfig, schema);
+      AccessControlUtils
+          .validatePermission(realtimeTableConfig.getTableName(), 
AccessType.CREATE, httpHeaders, endpointUrl,
+              accessControl);
+    }
+
     TableConfigs validatedTableConfigs = validateConfig(tableConfigs, 
typesToSkip);
     ObjectNode response = 
JsonUtils.objectToJsonNode(validatedTableConfigs).deepCopy();
     response.set("unrecognizedProperties", 
JsonUtils.objectToJsonNode(tableConfigsAndUnrecognizedProps.getRight()));


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

Reply via email to