diff --git a/pinot-controller/src/main/java/org/apache/pinot/controller/api/resources/PinotAccessControlUserRestletResource.java b/pinot-controller/src/main/java/org/apache/pinot/controller/api/resources/PinotAccessControlUserRestletResource.java index 2648862cc7bb..1b74cdf8e8a0 100644 --- a/pinot-controller/src/main/java/org/apache/pinot/controller/api/resources/PinotAccessControlUserRestletResource.java +++ b/pinot-controller/src/main/java/org/apache/pinot/controller/api/resources/PinotAccessControlUserRestletResource.java @@ -90,10 +90,19 @@ public class PinotAccessControlUserRestletResource { * {@inheritDoc} */ public static final Logger LOGGER = LoggerFactory.getLogger(PinotAccessControlUserRestletResource.class); + private static final String COMPONENT_TYPE_REQUIRED_ERROR = "Component type is required as query parameter"; @Inject PinotHelixResourceManager _pinotHelixResourceManager; + private static ComponentType validateRequiredComponentType(String componentTypeStr) { + ComponentType componentType = Constants.validateComponentType(componentTypeStr); + if (componentType == null) { + throw new ControllerApplicationException(LOGGER, COMPONENT_TYPE_REQUIRED_ERROR, Response.Status.BAD_REQUEST); + } + return componentType; + } + @GET @Produces(MediaType.APPLICATION_JSON) @Path("/users") @@ -115,10 +124,11 @@ public String listUsers() { @Authorize(targetType = TargetType.CLUSTER, action = Actions.Cluster.GET_USER) @ApiOperation(value = "Get an user in cluster", notes = "Get an user in cluster") public String getUser(@PathParam("username") String username, - @ApiParam(value = "CONTROLLER|SERVER|BROKER") @QueryParam("component") String componentTypeStr) { + @ApiParam(value = "CONTROLLER|SERVER|BROKER", required = true) @QueryParam("component") + String componentTypeStr) { + ComponentType componentType = validateRequiredComponentType(componentTypeStr); try { ZkHelixPropertyStore propertyStore = _pinotHelixResourceManager.getPropertyStore(); - ComponentType componentType = Constants.validateComponentType(componentTypeStr); String usernameWithType = username + "_" + componentType.name(); UserConfig userConfig = ZKMetadataProvider.getUserConfig(propertyStore, usernameWithType); @@ -168,15 +178,18 @@ public SuccessResponse addUser(String userConfigStr) { @Produces(MediaType.APPLICATION_JSON) @ApiOperation(value = "Delete a user", notes = "Delete a user") public SuccessResponse deleteUser(@PathParam("username") String username, - @ApiParam(value = "CONTROLLER|SERVER|BROKER") @QueryParam("component") String componentTypeStr) { + @ApiParam(value = "CONTROLLER|SERVER|BROKER", required = true) @QueryParam("component") + String componentTypeStr) { List usersDeleted = new LinkedList<>(); - String usernameWithComponentType = username + "_" + componentTypeStr; + ComponentType componentType = validateRequiredComponentType(componentTypeStr); + String componentTypeName = componentType.name(); + String usernameWithComponentType = username + "_" + componentTypeName; try { boolean userExist = false; - userExist = _pinotHelixResourceManager.hasUser(username, componentTypeStr); + userExist = _pinotHelixResourceManager.hasUser(username, componentTypeName); _pinotHelixResourceManager.deleteUser(usernameWithComponentType); if (userExist) { @@ -200,11 +213,14 @@ public SuccessResponse deleteUser(@PathParam("username") String username, @Produces(MediaType.APPLICATION_JSON) @ApiOperation(value = "Update user config for a user", notes = "Update user config for user") public SuccessResponse updateUserConfig(@PathParam("username") String username, - @ApiParam(value = "CONTROLLER|SERVER|BROKER") @QueryParam("component") String componentTypeStr, + @ApiParam(value = "CONTROLLER|SERVER|BROKER", required = true) @QueryParam("component") + String componentTypeStr, @QueryParam("passwordChanged") boolean passwordChanged, String userConfigString) { UserConfig userConfig; - String usernameWithComponentType = username + "_" + componentTypeStr; + ComponentType componentType = validateRequiredComponentType(componentTypeStr); + String componentTypeName = componentType.name(); + String usernameWithComponentType = username + "_" + componentTypeName; try { userConfig = JsonUtils.stringToObject(userConfigString, UserConfig.class); if (passwordChanged) { @@ -216,7 +232,7 @@ public SuccessResponse updateUserConfig(@PathParam("username") String username, "Request user " + usernameWithComponentType + " does not match " + usernameWithComponentTypeFromUserConfig + " in the Request body", Response.Status.BAD_REQUEST); } - if (!_pinotHelixResourceManager.hasUser(username, componentTypeStr)) { + if (!_pinotHelixResourceManager.hasUser(username, componentTypeName)) { throw new ControllerApplicationException(LOGGER, "Request user " + usernameWithComponentType + " does not exist", Response.Status.NOT_FOUND); } diff --git a/pinot-controller/src/test/java/org/apache/pinot/controller/api/PinotAccessControlUserRestletResourceTest.java b/pinot-controller/src/test/java/org/apache/pinot/controller/api/PinotAccessControlUserRestletResourceTest.java index 68155b525ecd..1ab2168c6dc1 100644 --- a/pinot-controller/src/test/java/org/apache/pinot/controller/api/PinotAccessControlUserRestletResourceTest.java +++ b/pinot-controller/src/test/java/org/apache/pinot/controller/api/PinotAccessControlUserRestletResourceTest.java @@ -19,7 +19,13 @@ package org.apache.pinot.controller.api; import com.fasterxml.jackson.databind.JsonNode; +import io.swagger.annotations.ApiParam; +import java.lang.annotation.Annotation; +import java.lang.reflect.Method; +import javax.ws.rs.core.Response; +import org.apache.commons.lang3.tuple.Pair; import org.apache.pinot.common.utils.BcryptUtils; +import org.apache.pinot.controller.api.resources.PinotAccessControlUserRestletResource; import org.apache.pinot.controller.helix.ControllerTest; import org.apache.pinot.spi.config.user.ComponentType; import org.apache.pinot.spi.config.user.RoleType; @@ -30,6 +36,8 @@ import org.testng.annotations.Test; import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertFalse; +import static org.testng.Assert.assertNotNull; import static org.testng.Assert.assertTrue; @@ -55,6 +63,47 @@ private UserConfig getUserConfig(String username, String componentType) UserConfig.class); } + private static ApiParam getApiParam(Method method, int parameterIndex) { + for (Annotation annotation : method.getParameterAnnotations()[parameterIndex]) { + if (annotation instanceof ApiParam) { + return (ApiParam) annotation; + } + } + return null; + } + + @Test + public void testGetUserWithoutComponentReturnsClearBadRequest() + throws Exception { + Pair response = ControllerTest.sendGetRequestWithStatusCode( + TEST_INSTANCE.getControllerBaseApiUrl() + "/users/testUser", null); + assertEquals(response.getLeft().intValue(), Response.Status.BAD_REQUEST.getStatusCode()); + assertTrue(response.getRight().contains("Component type is required")); + assertFalse(response.getRight().contains("Cannot invoke")); + } + + @Test + public void testUserComponentQueryParamIsRequiredInSwagger() + throws Exception { + Method getUser = PinotAccessControlUserRestletResource.class.getDeclaredMethod("getUser", String.class, + String.class); + Method deleteUser = PinotAccessControlUserRestletResource.class.getDeclaredMethod("deleteUser", String.class, + String.class); + Method updateUserConfig = PinotAccessControlUserRestletResource.class.getDeclaredMethod("updateUserConfig", + String.class, String.class, boolean.class, String.class); + + ApiParam getUserComponent = getApiParam(getUser, 1); + ApiParam deleteUserComponent = getApiParam(deleteUser, 1); + ApiParam updateUserComponent = getApiParam(updateUserConfig, 1); + + assertNotNull(getUserComponent); + assertNotNull(deleteUserComponent); + assertNotNull(updateUserComponent); + assertTrue(getUserComponent.required()); + assertTrue(deleteUserComponent.required()); + assertTrue(updateUserComponent.required()); + } + @Test public void testUpdateUserConfig() throws Exception {