Skip to content

Commit

Permalink
refactor premission and access in controller rest api. (#13696)
Browse files Browse the repository at this point in the history
  • Loading branch information
abhioncbr authored Aug 6, 2024
1 parent 2faf868 commit 0f2dcb7
Show file tree
Hide file tree
Showing 3 changed files with 70 additions and 60 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -57,9 +57,7 @@
import org.apache.pinot.common.metrics.ControllerMeter;
import org.apache.pinot.common.metrics.ControllerMetrics;
import org.apache.pinot.common.utils.DatabaseUtils;
import org.apache.pinot.controller.api.access.AccessControl;
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.events.MetadataEventNotifierFactory;
Expand Down Expand Up @@ -240,7 +238,8 @@ public ConfigSuccessResponse addSchema(
validateSchemaName(schema);
String schemaName = DatabaseUtils.translateTableName(schema.getSchemaName(), httpHeaders);
schema.setSchemaName(schemaName);
checkPermissionAndAccess(schemaName, request, httpHeaders, AccessType.CREATE, Actions.Table.CREATE_SCHEMA);
ResourceUtils.checkPermissionAndAccess(schemaName, request, httpHeaders,
AccessType.CREATE, Actions.Table.CREATE_SCHEMA, _accessControlFactory, LOGGER);
SuccessResponse successResponse = addSchema(schema, override, force);
return new ConfigSuccessResponse(successResponse.getStatus(), schemaAndUnrecognizedProps.getRight());
}
Expand Down Expand Up @@ -271,7 +270,8 @@ public ConfigSuccessResponse addSchema(
validateSchemaName(schema);
String schemaName = DatabaseUtils.translateTableName(schema.getSchemaName(), httpHeaders);
schema.setSchemaName(schemaName);
checkPermissionAndAccess(schemaName, request, httpHeaders, AccessType.CREATE, Actions.Table.CREATE_SCHEMA);
ResourceUtils.checkPermissionAndAccess(schemaName, request, httpHeaders,
AccessType.CREATE, Actions.Table.CREATE_SCHEMA, _accessControlFactory, LOGGER);
SuccessResponse successResponse = addSchema(schema, override, force);
return new ConfigSuccessResponse(successResponse.getStatus(), schemaAndUnrecognizedProperties.getRight());
}
Expand All @@ -296,7 +296,8 @@ public String validateSchema(FormDataMultiPart multiPart, @Context HttpHeaders h
String schemaName = DatabaseUtils.translateTableName(schema.getSchemaName(), httpHeaders);
schema.setSchemaName(schemaName);
validateSchemaInternal(schema);
checkPermissionAndAccess(schemaName, request, httpHeaders, AccessType.READ, Actions.Table.VALIDATE_SCHEMA);
ResourceUtils.checkPermissionAndAccess(schemaName, request, httpHeaders,
AccessType.READ, Actions.Table.VALIDATE_SCHEMA, _accessControlFactory, LOGGER);
ObjectNode response = schema.toJsonObject();
response.set("unrecognizedProperties", JsonUtils.objectToJsonNode(schemaAndUnrecognizedProps.getRight()));
try {
Expand Down Expand Up @@ -326,7 +327,8 @@ public String validateSchema(String schemaJsonString, @Context HttpHeaders httpH
String schemaName = DatabaseUtils.translateTableName(schema.getSchemaName(), httpHeaders);
schema.setSchemaName(schemaName);
validateSchemaInternal(schema);
checkPermissionAndAccess(schemaName, request, httpHeaders, AccessType.READ, Actions.Table.VALIDATE_SCHEMA);
ResourceUtils.checkPermissionAndAccess(schemaName, request, httpHeaders,
AccessType.READ, Actions.Table.VALIDATE_SCHEMA, _accessControlFactory, LOGGER);
ObjectNode response = schema.toJsonObject();
response.set("unrecognizedProperties", JsonUtils.objectToJsonNode(schemaAndUnrecognizedProps.getRight()));
try {
Expand Down Expand Up @@ -528,30 +530,4 @@ private void deleteSchemaInternal(String schemaName) {
Response.Status.INTERNAL_SERVER_ERROR);
}
}

/**
* Validates the permission and access for a given schema based on the request and HTTP headers.
* This method checks if the current user has the necessary permissions to perform an action on the specified schema.
* It utilizes the {@link AccessControl} mechanism to determine access rights
* and throws a {@link ControllerApplicationException} with a {@link Response.Status#FORBIDDEN} status
* if the access is denied.
*
* @param schemaName The name of the schema for which the permission and access are being checked.
* @param request The {@link Request} object containing information about the current request,
* used to extract the endpoint URL.
* @param httpHeaders The {@link HttpHeaders} associated with the request,
* used for authorization and other header-based access control checks.
* @param accessType The type of access being requested (e.g., CREATE, READ, UPDATE, DELETE).
* @param action The specific action being checked against the access control policies.
* @throws ControllerApplicationException if the user does not have the required permissions or access.
*/
private void checkPermissionAndAccess(String schemaName, Request request, HttpHeaders httpHeaders,
AccessType accessType, String action) {
String endpointUrl = request.getRequestURL().toString();
AccessControl accessControl = _accessControlFactory.create();
AccessControlUtils.validatePermission(schemaName, accessType, httpHeaders, endpointUrl, accessControl);
if (!accessControl.hasAccess(httpHeaders, TargetType.TABLE, schemaName, action)) {
throw new ControllerApplicationException(LOGGER, "Permission denied", Response.Status.FORBIDDEN);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -85,9 +85,7 @@
import org.apache.pinot.common.utils.DatabaseUtils;
import org.apache.pinot.common.utils.helix.HelixHelper;
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.AccessControlUtils;
import org.apache.pinot.controller.api.access.AccessType;
import org.apache.pinot.controller.api.access.Authenticate;
import org.apache.pinot.controller.api.exception.ControllerApplicationException;
Expand Down Expand Up @@ -212,19 +210,11 @@ public ConfigSuccessResponse addTable(String tableConfigStr,
tableNameWithType = DatabaseUtils.translateTableName(tableConfig.getTableName(), httpHeaders);
tableConfig.setTableName(tableNameWithType);
// Handle legacy config
SegmentsValidationAndRetentionConfig validationConfig = tableConfig.getValidationConfig();
if (validationConfig.getSchemaName() != null) {
validationConfig.setSchemaName(DatabaseUtils.translateTableName(validationConfig.getSchemaName(), httpHeaders));
}
handleLegacySchemaConfig(tableConfig, httpHeaders);

// validate permission
String endpointUrl = request.getRequestURL().toString();
AccessControl accessControl = _accessControlFactory.create();
AccessControlUtils.validatePermission(tableNameWithType, AccessType.CREATE, httpHeaders, endpointUrl,
accessControl);
if (!accessControl.hasAccess(httpHeaders, TargetType.TABLE, tableNameWithType, Actions.Table.CREATE_TABLE)) {
throw new ControllerApplicationException(LOGGER, "Permission denied", Response.Status.FORBIDDEN);
}
ResourceUtils.checkPermissionAndAccess(tableNameWithType, request, httpHeaders,
AccessType.CREATE, Actions.Table.CREATE_TABLE, _accessControlFactory, LOGGER);

Schema schema = _pinotHelixResourceManager.getSchemaForTableConfig(tableConfig);

Expand Down Expand Up @@ -487,10 +477,7 @@ public ConfigSuccessResponse updateTableConfig(
tableNameWithType = DatabaseUtils.translateTableName(tableConfig.getTableName(), headers);
tableConfig.setTableName(tableNameWithType);
// Handle legacy config
SegmentsValidationAndRetentionConfig validationConfig = tableConfig.getValidationConfig();
if (validationConfig.getSchemaName() != null) {
validationConfig.setSchemaName(DatabaseUtils.translateTableName(validationConfig.getSchemaName(), headers));
}
handleLegacySchemaConfig(tableConfig, headers);
String tableNameFromPath = DatabaseUtils.translateTableName(
TableNameBuilder.forType(tableConfig.getTableType()).tableNameWithType(tableName), headers);
if (!tableNameFromPath.equals(tableNameWithType)) {
Expand Down Expand Up @@ -554,20 +541,13 @@ public ObjectNode checkTableConfig(String tableConfigStr,
TableConfig tableConfig = tableConfigAndUnrecognizedProperties.getLeft();
String tableNameWithType = DatabaseUtils.translateTableName(tableConfig.getTableName(), httpHeaders);
tableConfig.setTableName(tableNameWithType);

// Handle legacy config
SegmentsValidationAndRetentionConfig validationConfig = tableConfig.getValidationConfig();
if (validationConfig.getSchemaName() != null) {
validationConfig.setSchemaName(DatabaseUtils.translateTableName(validationConfig.getSchemaName(), httpHeaders));
}
handleLegacySchemaConfig(tableConfig, httpHeaders);

// validate permission
String endpointUrl = request.getRequestURL().toString();
AccessControl accessControl = _accessControlFactory.create();
AccessControlUtils.validatePermission(tableNameWithType, AccessType.READ, httpHeaders, endpointUrl, accessControl);
if (!accessControl.hasAccess(httpHeaders, TargetType.TABLE, tableNameWithType,
Actions.Table.VALIDATE_TABLE_CONFIGS)) {
throw new ControllerApplicationException(LOGGER, "Permission denied", Response.Status.FORBIDDEN);
}
ResourceUtils.checkPermissionAndAccess(tableNameWithType, request, httpHeaders,
AccessType.READ, Actions.Table.VALIDATE_TABLE_CONFIGS, _accessControlFactory, LOGGER);

ObjectNode validationResponse =
validateConfig(tableConfig, _pinotHelixResourceManager.getSchemaForTableConfig(tableConfig), typesToSkip);
Expand Down Expand Up @@ -1270,4 +1250,22 @@ private long validateSegmentStateForTable(String offlineTableName)

return timeBoundaryMs;
}

/**
* Handles the legacy schema configuration for a given table configuration.
* This method updates the schema name in the validation configuration of the table config
* to ensure it is correctly translated based on the provided HTTP headers.
* This is necessary to maintain compatibility with older configurations that may not
* have the schema name properly set or formatted.
*
* @param tableConfig The {@link TableConfig} object containing the table configuration.
* @param httpHeaders The {@link HttpHeaders} object containing the HTTP headers, used to
* translate the schema name if necessary.
*/
private void handleLegacySchemaConfig(TableConfig tableConfig, HttpHeaders httpHeaders) {
SegmentsValidationAndRetentionConfig validationConfig = tableConfig.getValidationConfig();
if (validationConfig.getSchemaName() != null) {
validationConfig.setSchemaName(DatabaseUtils.translateTableName(validationConfig.getSchemaName(), httpHeaders));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,18 @@

import java.util.List;
import javax.annotation.Nullable;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.Response;
import org.apache.pinot.common.exception.TableNotFoundException;
import org.apache.pinot.controller.api.access.AccessControl;
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.exception.ControllerApplicationException;
import org.apache.pinot.controller.helix.core.PinotHelixResourceManager;
import org.apache.pinot.core.auth.TargetType;
import org.apache.pinot.spi.config.table.TableType;
import org.glassfish.grizzly.http.server.Request;
import org.slf4j.Logger;


Expand All @@ -42,4 +49,33 @@ public static List<String> getExistingTableNamesWithType(PinotHelixResourceManag
throw new ControllerApplicationException(logger, e.getMessage(), Response.Status.FORBIDDEN);
}
}

/**
* Validates the permission and access for a specified type based on the incoming request and HTTP headers.
* This method ensures that the current user has the necessary permissions to perform the specified action
* on the given type. It leverages the {@link AccessControl} mechanism to assess access rights and
* throws a {@link ControllerApplicationException} with a {@link Response.Status#FORBIDDEN} status code
* if access is denied. This is crucial for enforcing security and access control within the application.
*
* @param typeName The name of the type for which permission and access are being verified.
* @param request The {@link Request} object containing details about the current request, utilized
* to extract the endpoint URL for access validation.
* @param httpHeaders The {@link HttpHeaders} associated with the request, used for authorization
* and other header-based access control checks.
* @param accessType The type of access being requested (e.g., CREATE, READ, UPDATE, DELETE).
* @param action The specific action being checked against the access control policies.
* @param accessControlFactory The {@link AccessControlFactory} used to create an {@link AccessControl} instance
* for validating permissions.
* @param logger The {@link Logger} used for logging any access control related messages.
* @throws ControllerApplicationException if the user lacks the required permissions or access.
*/
public static void checkPermissionAndAccess(String typeName, Request request, HttpHeaders httpHeaders,
AccessType accessType, String action, AccessControlFactory accessControlFactory, Logger logger) {
String endpointUrl = request.getRequestURL().toString();
AccessControl accessControl = accessControlFactory.create();
AccessControlUtils.validatePermission(typeName, accessType, httpHeaders, endpointUrl, accessControl);
if (!accessControl.hasAccess(httpHeaders, TargetType.TABLE, typeName, action)) {
throw new ControllerApplicationException(logger, "Permission denied", Response.Status.FORBIDDEN);
}
}
}

0 comments on commit 0f2dcb7

Please sign in to comment.