Skip to content

Commit

Permalink
Add new functionality, listRoles and dropRole. Both will listen at th…
Browse files Browse the repository at this point in the history
…e same /api/v0/ops/auth/role, but with GET and DELETE instead of POST (which creates)
  • Loading branch information
burmanm committed Nov 11, 2024
1 parent e65215c commit 17547a7
Show file tree
Hide file tree
Showing 5 changed files with 186 additions and 2 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ Changelog for Management API, new PRs should update the `main / unreleased` sect

## unreleased

* [FEATURE] [#566](https://github.com/k8ssandra/management-api-for-apache-cassandra/issues/566) Add listRoles and dropRole functionality to the REST interface

## v0.1.89 (2024-10-29)
* [BUGFIX] [#564](https://github.com/k8ssandra/management-api-for-apache-cassandra/issues/564) Fix LatencyMetrics for DSE

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,11 @@
import javax.management.openmbean.CompositeDataSupport;
import javax.management.openmbean.TabularData;
import org.apache.cassandra.auth.AuthenticatedUser;
import org.apache.cassandra.auth.INetworkAuthorizer;
import org.apache.cassandra.auth.IRoleManager;
import org.apache.cassandra.auth.RoleOptions;
import org.apache.cassandra.auth.RoleResource;
import org.apache.cassandra.config.DatabaseDescriptor;
import org.apache.cassandra.cql3.UntypedResultSet;
import org.apache.cassandra.db.ConsistencyLevel;
import org.apache.cassandra.db.compaction.OperationType;
Expand Down Expand Up @@ -551,6 +553,40 @@ public void createRole(
ShimLoader.instance.get().getRoleManager().createRole(AuthenticatedUser.SYSTEM_USER, rr, ro);
}

@Rpc(name = "listRoles")
public List<Map<String, String>> listRoles() {
logger.debug("Listing roles");
IRoleManager roleManager = ShimLoader.instance.get().getRoleManager();
Set<RoleResource> allRoles = roleManager.getAllRoles();
List<Map<String, String>> roles = new ArrayList<>();
for (RoleResource role : allRoles) {
Map<String, String> roleOutput = new HashMap<>();
roleOutput.put("name", role.getRoleName());
roleOutput.put("super", String.valueOf(roleManager.isSuper(role)));
roleOutput.put("login", String.valueOf(roleManager.canLogin(role)));
ObjectMapper objectMapper = new ObjectMapper();
try {
String s = objectMapper.writeValueAsString(roleManager.getCustomOptions(role));
roleOutput.put("options", s);
} catch (JsonProcessingException e) {
throw new RuntimeException(e);
}
INetworkAuthorizer networkAuthorizer = DatabaseDescriptor.getNetworkAuthorizer();
roleOutput.put("datacenters", networkAuthorizer.authorize(role).toString());
roles.add(roleOutput);
}

return roles;
}

@Rpc(name = "dropRole")
public void dropRole(@RpcParam(name = "username") String username) {
logger.debug("Dropping role {}", username);
RoleResource rr = RoleResource.role(username);

ShimLoader.instance.get().getRoleManager().dropRole(AuthenticatedUser.SYSTEM_USER, rr);
}

@Rpc(name = "checkConsistencyLevel")
public Map<List<Long>, List<String>> checkConsistencyLevel(
@RpcParam(name = "consistency_level") String consistencyLevelName,
Expand Down
63 changes: 63 additions & 0 deletions management-api-server/doc/openapi.json
Original file line number Diff line number Diff line change
Expand Up @@ -281,6 +281,69 @@
}
},
"/api/v0/ops/auth/role" : {
"delete" : {
"operationId" : "dropRole",
"parameters" : [ {
"in" : "query",
"name" : "username",
"schema" : {
"type" : "string"
}
} ],
"responses" : {
"200" : {
"content" : {
"text/plain" : {
"example" : "OK",
"schema" : {
"type" : "string"
}
}
},
"description" : "Role dropped"
},
"400" : {
"content" : {
"text/plain" : {
"example" : "Username is empty",
"schema" : {
"type" : "string"
}
}
},
"description" : "Username is empty"
}
},
"summary" : "Drops a user role"
},
"get" : {
"operationId" : "listRoles",
"responses" : {
"200" : {
"content" : {
"application/json" : {
"example" : "OK",
"schema" : {
"type" : "string"
}
}
},
"description" : "List of roles"
},
"400" : {
"content" : {
"text/plain" : {
"example" : "Username is empty",
"schema" : {
"type" : "string"
}
}
},
"description" : "Username is empty"
}
},
"summary" : "Lists existing user rules"
},
"post" : {
"operationId" : "createRole",
"parameters" : [ {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,30 +5,41 @@
*/
package com.datastax.mgmtapi.resources;

import static com.datastax.mgmtapi.resources.v1.TableOpsResources.LIST_OF_MAP_OF_STRINGS;

import com.datastax.mgmtapi.ManagementApplication;
import com.datastax.mgmtapi.resources.common.BaseResources;
import com.datastax.oss.driver.api.core.cql.ResultSet;
import com.datastax.oss.driver.api.core.cql.Row;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.ExampleObject;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import jakarta.ws.rs.DELETE;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.POST;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.QueryParam;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
import java.util.List;
import java.util.Map;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.HttpStatus;

@Path("/api/v0/ops/auth")
public class AuthResources extends BaseResources {
private static final ObjectMapper jsonMapper = new ObjectMapper();

public AuthResources(ManagementApplication application) {
super(application);
}

@POST
@Path("/role/")
@Path("/role")
@Operation(summary = "Creates a new user role", operationId = "createRole")
@Produces(MediaType.TEXT_PLAIN)
@ApiResponse(
Expand Down Expand Up @@ -73,4 +84,76 @@ public Response createRole(
return Response.ok("OK").build();
});
}

@DELETE
@Path("/role")
@Operation(summary = "Drops a user role", operationId = "dropRole")
@Produces(MediaType.TEXT_PLAIN)
@ApiResponse(
responseCode = "200",
description = "Role dropped",
content =
@Content(
mediaType = MediaType.TEXT_PLAIN,
schema = @Schema(implementation = String.class),
examples = @ExampleObject(value = "OK")))
@ApiResponse(
responseCode = "400",
description = "Username is empty",
content =
@Content(
mediaType = MediaType.TEXT_PLAIN,
schema = @Schema(implementation = String.class),
examples = @ExampleObject(value = "Username is empty")))
public Response dropRole(@QueryParam(value = "username") String name) {
return handle(
() -> {
if (StringUtils.isBlank(name))
return Response.status(Response.Status.BAD_REQUEST.getStatusCode(), "Username is empty")
.build();

app.cqlService.executePreparedStatement(
app.dbUnixSocketFile, "CALL NodeOps.dropRole(?)", name);

return Response.ok("OK").build();
});
}

@GET
@Path("/role")
@Operation(summary = "Lists existing user rules", operationId = "listRoles")
@Produces(MediaType.TEXT_PLAIN)
@ApiResponse(
responseCode = "200",
description = "List of roles",
content =
@Content(
mediaType = MediaType.APPLICATION_JSON,
schema = @Schema(implementation = String.class),
examples = @ExampleObject(value = "OK")))
@ApiResponse(
responseCode = "400",
description = "Username is empty",
content =
@Content(
mediaType = MediaType.TEXT_PLAIN,
schema = @Schema(implementation = String.class),
examples = @ExampleObject(value = "Username is empty")))
public Response listRoles() {
return handle(
() -> {
ResultSet result =
app.cqlService.executePreparedStatement(
app.dbUnixSocketFile, "CALL NodeOps.listRoles()");
Row row = result.one();
if (row != null) {
List<Map<String, String>> roles = row.get(0, LIST_OF_MAP_OF_STRINGS);

return Response.ok(jsonMapper.writeValueAsString(roles), MediaType.APPLICATION_JSON)
.build();
}

return Response.status(HttpStatus.SC_NO_CONTENT).build();
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@
@Path("/api/v1/ops/tables")
public class TableOpsResources extends BaseResources {

private static final GenericType<List<Map<String, String>>> LIST_OF_MAP_OF_STRINGS =
public static final GenericType<List<Map<String, String>>> LIST_OF_MAP_OF_STRINGS =
GenericType.listOf(GenericType.mapOf(String.class, String.class));

public TableOpsResources(ManagementApplication application) {
Expand Down

0 comments on commit 17547a7

Please sign in to comment.