diff --git a/api/src/main/java/io/kafbat/ui/controller/KafkaConnectController.java b/api/src/main/java/io/kafbat/ui/controller/KafkaConnectController.java index 4c5dc4d84..945d77f92 100644 --- a/api/src/main/java/io/kafbat/ui/controller/KafkaConnectController.java +++ b/api/src/main/java/io/kafbat/ui/controller/KafkaConnectController.java @@ -191,11 +191,7 @@ public Mono> updateConnectorState(String clusterName, Strin ConnectorActionDTO action, ServerWebExchange exchange) { ConnectAction[] connectActions; - if (RESTART_ACTIONS.contains(action)) { - connectActions = new ConnectAction[] {ConnectAction.VIEW, ConnectAction.RESTART}; - } else { - connectActions = new ConnectAction[] {ConnectAction.VIEW, ConnectAction.EDIT}; - } + connectActions = new ConnectAction[] {ConnectAction.VIEW, ConnectAction.OPERATE}; var context = AccessContext.builder() .cluster(clusterName) @@ -237,7 +233,7 @@ public Mono> restartConnectorTask(String clusterName, Strin var context = AccessContext.builder() .cluster(clusterName) - .connectActions(connectName, ConnectAction.VIEW, ConnectAction.RESTART) + .connectActions(connectName, ConnectAction.VIEW, ConnectAction.OPERATE) .operationName("restartConnectorTask") .operationParams(Map.of(CONNECTOR_NAME, connectorName)) .build(); diff --git a/api/src/main/java/io/kafbat/ui/model/rbac/Resource.java b/api/src/main/java/io/kafbat/ui/model/rbac/Resource.java index 600c9c4e6..691bc9e94 100644 --- a/api/src/main/java/io/kafbat/ui/model/rbac/Resource.java +++ b/api/src/main/java/io/kafbat/ui/model/rbac/Resource.java @@ -12,7 +12,13 @@ import io.kafbat.ui.model.rbac.permission.SchemaAction; import io.kafbat.ui.model.rbac.permission.TopicAction; import jakarta.annotation.Nullable; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.stream.Collectors; import java.util.stream.Stream; import org.apache.commons.lang3.EnumUtils; @@ -28,7 +34,7 @@ public enum Resource { SCHEMA(SchemaAction.values()), - CONNECT(ConnectAction.values()), + CONNECT(ConnectAction.values(), ConnectAction.ALIASES), KSQL(KsqlAction.values()), @@ -38,14 +44,27 @@ public enum Resource { CLIENT_QUOTAS(ClientQuotaAction.values()); - private final List actions; + + private final Map actions; + private final Map aliases; + + Resource(PermissibleAction[] actions, Map aliases) { + this.actions = Arrays.stream(actions) + .collect( + Collectors.toMap( + a -> a.name().toLowerCase(), + a -> a + ) + ); + this.aliases = aliases; + } Resource(PermissibleAction[] actions) { - this.actions = List.of(actions); + this(actions, Map.of()); } public List allActions() { - return actions; + return new ArrayList<>(actions.values()); } @Nullable @@ -54,17 +73,14 @@ public static Resource fromString(String name) { } public List parseActionsWithDependantsUnnest(List actionsToParse) { - return actionsToParse.stream() - .map(toParse -> actions.stream() - .filter(a -> toParse.equalsIgnoreCase(a.name())) - .findFirst() + return actionsToParse.stream().map(toParse -> + Optional.ofNullable(actions.get(toParse.toLowerCase())) + .or(() -> Optional.ofNullable(aliases.get(toParse.toLowerCase()))) .orElseThrow(() -> new IllegalArgumentException( "'%s' actions not applicable for resource %s".formatted(toParse, name()))) - ) - // unnesting all dependant actions - .flatMap(a -> Stream.concat(Stream.of(a), a.unnestAllDependants())) - .distinct() - .toList(); + ).flatMap(a -> + Stream.concat(Stream.of(a), a.unnestAllDependants()) + ).distinct().toList(); } } diff --git a/api/src/main/java/io/kafbat/ui/model/rbac/permission/ConnectAction.java b/api/src/main/java/io/kafbat/ui/model/rbac/permission/ConnectAction.java index a357245bc..81fc8d382 100644 --- a/api/src/main/java/io/kafbat/ui/model/rbac/permission/ConnectAction.java +++ b/api/src/main/java/io/kafbat/ui/model/rbac/permission/ConnectAction.java @@ -1,5 +1,6 @@ package io.kafbat.ui.model.rbac.permission; +import java.util.Map; import java.util.Set; import org.apache.commons.lang3.EnumUtils; import org.jetbrains.annotations.Nullable; @@ -9,10 +10,9 @@ public enum ConnectAction implements PermissibleAction { VIEW, EDIT(VIEW), CREATE(VIEW), - RESTART(VIEW), + OPERATE(VIEW), DELETE(VIEW), - RESET_OFFSETS(VIEW) - + RESET_OFFSETS(VIEW), ; private final ConnectAction[] dependantActions; @@ -21,7 +21,9 @@ public enum ConnectAction implements PermissibleAction { this.dependantActions = dependantActions; } - public static final Set ALTER_ACTIONS = Set.of(CREATE, EDIT, DELETE, RESTART, RESET_OFFSETS); + public static final Set ALTER_ACTIONS = Set.of(CREATE, EDIT, DELETE, OPERATE, RESET_OFFSETS); + + public static final Map ALIASES = Map.of("restart", OPERATE); @Nullable public static ConnectAction fromString(String name) { diff --git a/api/src/test/java/io/kafbat/ui/model/rbac/AccessContextTest.java b/api/src/test/java/io/kafbat/ui/model/rbac/AccessContextTest.java index 161a34075..b1417f2c7 100644 --- a/api/src/test/java/io/kafbat/ui/model/rbac/AccessContextTest.java +++ b/api/src/test/java/io/kafbat/ui/model/rbac/AccessContextTest.java @@ -9,6 +9,7 @@ import io.kafbat.ui.model.rbac.AccessContext.ResourceAccess; import io.kafbat.ui.model.rbac.AccessContext.SingleResourceAccess; import io.kafbat.ui.model.rbac.permission.ClusterConfigAction; +import io.kafbat.ui.model.rbac.permission.ConnectAction; import io.kafbat.ui.model.rbac.permission.PermissibleAction; import io.kafbat.ui.model.rbac.permission.TopicAction; import jakarta.annotation.Nullable; @@ -98,10 +99,30 @@ void deniesAccessForResourceWithoutNameIfUserHasAllNeededPermissions() { assertThat(allowed).isFalse(); } + @Test + void shouldMapActionAliases() { + SingleResourceAccess sra = + new SingleResourceAccess(Resource.CONNECT, List.of(ConnectAction.OPERATE)); + + var allowed = sra.isAccessible( + List.of( + permission(Resource.CONNECT, null, List.of("restart")) + ) + ); + + assertThat(allowed).isTrue(); + } + private Permission permission(Resource res, @Nullable String namePattern, PermissibleAction... actions) { + return permission( + res, namePattern, Stream.of(actions).map(PermissibleAction::name).toList() + ); + } + + private Permission permission(Resource res, @Nullable String namePattern, List actions) { Permission p = new Permission(); p.setResource(res.name()); - p.setActions(Stream.of(actions).map(PermissibleAction::name).toList()); + p.setActions(actions); p.setValue(namePattern); p.validate(); p.transform(); diff --git a/api/src/test/java/io/kafbat/ui/model/rbac/PermissionTest.java b/api/src/test/java/io/kafbat/ui/model/rbac/PermissionTest.java index 3d2fd72be..3aed48d18 100644 --- a/api/src/test/java/io/kafbat/ui/model/rbac/PermissionTest.java +++ b/api/src/test/java/io/kafbat/ui/model/rbac/PermissionTest.java @@ -34,7 +34,7 @@ void transformSetsFullActionsListIfAllActionPassed() { p.transform(); assertThat(p.getParsedActions()) - .isEqualTo(List.of(TopicAction.values())); + .containsExactlyInAnyOrder(TopicAction.values()); } @Test diff --git a/contract/src/main/resources/swagger/kafbat-ui-api.yaml b/contract/src/main/resources/swagger/kafbat-ui-api.yaml index 158330062..7b4c65744 100644 --- a/contract/src/main/resources/swagger/kafbat-ui-api.yaml +++ b/contract/src/main/resources/swagger/kafbat-ui-api.yaml @@ -3947,6 +3947,7 @@ components: - MESSAGES_READ - MESSAGES_PRODUCE - MESSAGES_DELETE + - OPERATE - RESTART ResourceType: diff --git a/frontend/src/components/Connect/Details/Actions/Actions.tsx b/frontend/src/components/Connect/Details/Actions/Actions.tsx index 6909b4617..f1f2ff13d 100644 --- a/frontend/src/components/Connect/Details/Actions/Actions.tsx +++ b/frontend/src/components/Connect/Details/Actions/Actions.tsx @@ -91,7 +91,7 @@ const Actions: React.FC = () => { onClick={pauseConnectorHandler} permission={{ resource: ResourceType.CONNECT, - action: Action.EDIT, + action: Action.OPERATE, value: routerProps.connectName, }} > @@ -103,7 +103,7 @@ const Actions: React.FC = () => { onClick={stopConnectorHandler} permission={{ resource: ResourceType.CONNECT, - action: Action.EDIT, + action: Action.OPERATE, value: routerProps.connectName, }} > @@ -116,7 +116,7 @@ const Actions: React.FC = () => { onClick={resumeConnectorHandler} permission={{ resource: ResourceType.CONNECT, - action: Action.EDIT, + action: Action.OPERATE, value: routerProps.connectName, }} > @@ -127,7 +127,7 @@ const Actions: React.FC = () => { onClick={restartConnectorHandler} permission={{ resource: ResourceType.CONNECT, - action: Action.RESTART, + action: Action.OPERATE, value: routerProps.connectName, }} > @@ -137,7 +137,7 @@ const Actions: React.FC = () => { onClick={restartAllTasksHandler} permission={{ resource: ResourceType.CONNECT, - action: Action.RESTART, + action: Action.OPERATE, value: routerProps.connectName, }} > @@ -147,7 +147,7 @@ const Actions: React.FC = () => { onClick={restartFailedTasksHandler} permission={{ resource: ResourceType.CONNECT, - action: Action.RESTART, + action: Action.OPERATE, value: routerProps.connectName, }} > diff --git a/frontend/src/components/Connect/Details/Tasks/ActionsCellTasks.tsx b/frontend/src/components/Connect/Details/Tasks/ActionsCellTasks.tsx index 053c9c086..8055456a4 100644 --- a/frontend/src/components/Connect/Details/Tasks/ActionsCellTasks.tsx +++ b/frontend/src/components/Connect/Details/Tasks/ActionsCellTasks.tsx @@ -25,7 +25,7 @@ const ActionsCellTasks: React.FC> = ({ row }) => { confirm="Are you sure you want to restart the task?" permission={{ resource: ResourceType.CONNECT, - action: Action.RESTART, + action: Action.OPERATE, value: routerProps.connectName, }} > diff --git a/frontend/src/components/Connect/List/ActionsCell.tsx b/frontend/src/components/Connect/List/ActionsCell.tsx index 4ad50c00e..e41479fd2 100644 --- a/frontend/src/components/Connect/List/ActionsCell.tsx +++ b/frontend/src/components/Connect/List/ActionsCell.tsx @@ -126,7 +126,7 @@ const ActionsCell: React.FC> = ({ disabled={isMutating} permission={{ resource: ResourceType.CONNECT, - action: Action.RESTART, + action: Action.OPERATE, value: connect, }} > @@ -137,7 +137,7 @@ const ActionsCell: React.FC> = ({ disabled={isMutating} permission={{ resource: ResourceType.CONNECT, - action: Action.RESTART, + action: Action.OPERATE, value: connect, }} > @@ -148,7 +148,7 @@ const ActionsCell: React.FC> = ({ disabled={isMutating} permission={{ resource: ResourceType.CONNECT, - action: Action.RESTART, + action: Action.OPERATE, value: connect, }} >