Browse Source

Permission & AccessContext classes refactoring

iliax 1 year ago
parent
commit
f33232f380

+ 2 - 2
kafka-ui-api/src/main/java/com/provectus/kafka/ui/controller/AccessController.java

@@ -65,9 +65,9 @@ public class AccessController implements AuthorizationApi {
           dto.setClusters(clusters);
           dto.setResource(ResourceTypeDTO.fromValue(permission.getResource().toString().toUpperCase()));
           dto.setValue(permission.getValue());
-          dto.setActions(permission.getActions()
+          dto.setActions(permission.getParsedActions()
               .stream()
-              .map(String::toUpperCase)
+              .map(p -> p.name().toUpperCase())
               .map(this::mapAction)
               .filter(Objects::nonNull)
               .collect(Collectors.toList()));

+ 5 - 9
kafka-ui-api/src/main/java/com/provectus/kafka/ui/controller/ConsumerGroupsController.java

@@ -50,8 +50,7 @@ public class ConsumerGroupsController extends AbstractController implements Cons
                                                         ServerWebExchange exchange) {
     var context = AccessContext.builder()
         .cluster(clusterName)
-        .consumerGroup(id)
-        .consumerGroupActions(DELETE)
+        .consumerGroupActions(id, DELETE)
         .operationName("deleteConsumerGroup")
         .build();
 
@@ -67,8 +66,7 @@ public class ConsumerGroupsController extends AbstractController implements Cons
                                                                         ServerWebExchange exchange) {
     var context = AccessContext.builder()
         .cluster(clusterName)
-        .consumerGroup(consumerGroupId)
-        .consumerGroupActions(VIEW)
+        .consumerGroupActions(consumerGroupId, VIEW)
         .operationName("getConsumerGroup")
         .build();
 
@@ -85,8 +83,7 @@ public class ConsumerGroupsController extends AbstractController implements Cons
                                                                              ServerWebExchange exchange) {
     var context = AccessContext.builder()
         .cluster(clusterName)
-        .topic(topicName)
-        .topicActions(TopicAction.VIEW)
+        .topicActions(topicName, TopicAction.VIEW)
         .operationName("getTopicConsumerGroups")
         .build();
 
@@ -143,9 +140,8 @@ public class ConsumerGroupsController extends AbstractController implements Cons
     return resetDto.flatMap(reset -> {
       var context = AccessContext.builder()
           .cluster(clusterName)
-          .topic(reset.getTopic())
-          .topicActions(TopicAction.VIEW)
-          .consumerGroupActions(RESET_OFFSETS)
+          .topicActions(reset.getTopic(), TopicAction.VIEW)
+          .consumerGroupActions(group, RESET_OFFSETS)
           .operationName("resetConsumerGroupOffsets")
           .build();
 

+ 10 - 23
kafka-ui-api/src/main/java/com/provectus/kafka/ui/controller/KafkaConnectController.java

@@ -55,8 +55,7 @@ public class KafkaConnectController extends AbstractController implements KafkaC
 
     var context = AccessContext.builder()
         .cluster(clusterName)
-        .connect(connectName)
-        .connectActions(ConnectAction.VIEW)
+        .connectActions(connectName, ConnectAction.VIEW)
         .operationName("getConnectors")
         .build();
 
@@ -72,8 +71,7 @@ public class KafkaConnectController extends AbstractController implements KafkaC
 
     var context = AccessContext.builder()
         .cluster(clusterName)
-        .connect(connectName)
-        .connectActions(ConnectAction.VIEW, ConnectAction.CREATE)
+        .connectActions(connectName, ConnectAction.VIEW, ConnectAction.CREATE)
         .operationName("createConnector")
         .build();
 
@@ -90,9 +88,7 @@ public class KafkaConnectController extends AbstractController implements KafkaC
 
     var context = AccessContext.builder()
         .cluster(clusterName)
-        .connect(connectName)
-        .connectActions(ConnectAction.VIEW)
-        .connector(connectorName)
+        .connectActions(connectName, ConnectAction.VIEW)
         .operationName("getConnector")
         .build();
 
@@ -109,8 +105,7 @@ public class KafkaConnectController extends AbstractController implements KafkaC
 
     var context = AccessContext.builder()
         .cluster(clusterName)
-        .connect(connectName)
-        .connectActions(ConnectAction.VIEW, ConnectAction.EDIT)
+        .connectActions(connectName, ConnectAction.VIEW, ConnectAction.EDIT)
         .operationName("deleteConnector")
         .operationParams(Map.of("connectorName", connectName))
         .build();
@@ -132,7 +127,6 @@ public class KafkaConnectController extends AbstractController implements KafkaC
   ) {
     var context = AccessContext.builder()
         .cluster(clusterName)
-        .connectActions(ConnectAction.VIEW, ConnectAction.EDIT)
         .operationName("getAllConnectors")
         .build();
 
@@ -142,7 +136,6 @@ public class KafkaConnectController extends AbstractController implements KafkaC
 
     Flux<FullConnectorInfoDTO> job = kafkaConnectService.getAllConnectors(getCluster(clusterName), search)
         .filterWhen(dto -> accessControlService.isConnectAccessible(dto.getConnect(), clusterName))
-        .filterWhen(dto -> accessControlService.isConnectorAccessible(dto.getConnect(), dto.getName(), clusterName))
         .sort(comparator);
 
     return Mono.just(ResponseEntity.ok(job))
@@ -157,8 +150,7 @@ public class KafkaConnectController extends AbstractController implements KafkaC
 
     var context = AccessContext.builder()
         .cluster(clusterName)
-        .connect(connectName)
-        .connectActions(ConnectAction.VIEW)
+        .connectActions(connectName, ConnectAction.VIEW)
         .operationName("getConnectorConfig")
         .build();
 
@@ -177,8 +169,7 @@ public class KafkaConnectController extends AbstractController implements KafkaC
 
     var context = AccessContext.builder()
         .cluster(clusterName)
-        .connect(connectName)
-        .connectActions(ConnectAction.VIEW, ConnectAction.EDIT)
+        .connectActions(connectName, ConnectAction.VIEW, ConnectAction.EDIT)
         .operationName("setConnectorConfig")
         .operationParams(Map.of("connectorName", connectorName))
         .build();
@@ -204,8 +195,7 @@ public class KafkaConnectController extends AbstractController implements KafkaC
 
     var context = AccessContext.builder()
         .cluster(clusterName)
-        .connect(connectName)
-        .connectActions(connectActions)
+        .connectActions(connectName, connectActions)
         .operationName("updateConnectorState")
         .operationParams(Map.of("connectorName", connectorName))
         .build();
@@ -224,8 +214,7 @@ public class KafkaConnectController extends AbstractController implements KafkaC
                                                                ServerWebExchange exchange) {
     var context = AccessContext.builder()
         .cluster(clusterName)
-        .connect(connectName)
-        .connectActions(ConnectAction.VIEW)
+        .connectActions(connectName, ConnectAction.VIEW)
         .operationName("getConnectorTasks")
         .operationParams(Map.of("connectorName", connectorName))
         .build();
@@ -244,8 +233,7 @@ public class KafkaConnectController extends AbstractController implements KafkaC
 
     var context = AccessContext.builder()
         .cluster(clusterName)
-        .connect(connectName)
-        .connectActions(ConnectAction.VIEW, ConnectAction.RESTART)
+        .connectActions(connectName, ConnectAction.VIEW, ConnectAction.RESTART)
         .operationName("restartConnectorTask")
         .operationParams(Map.of("connectorName", connectorName))
         .build();
@@ -263,8 +251,7 @@ public class KafkaConnectController extends AbstractController implements KafkaC
 
     var context = AccessContext.builder()
         .cluster(clusterName)
-        .connect(connectName)
-        .connectActions(ConnectAction.VIEW)
+        .connectActions(connectName, ConnectAction.VIEW)
         .operationName("getConnectorPlugins")
         .build();
 

+ 4 - 8
kafka-ui-api/src/main/java/com/provectus/kafka/ui/controller/MessagesController.java

@@ -55,8 +55,7 @@ public class MessagesController extends AbstractController implements MessagesAp
 
     var context = AccessContext.builder()
         .cluster(clusterName)
-        .topic(topicName)
-        .topicActions(MESSAGES_DELETE)
+        .topicActions(topicName, MESSAGES_DELETE)
         .build();
 
     return validateAccess(context).<ResponseEntity<Void>>then(
@@ -90,8 +89,7 @@ public class MessagesController extends AbstractController implements MessagesAp
                                                                            ServerWebExchange exchange) {
     var contextBuilder = AccessContext.builder()
         .cluster(clusterName)
-        .topic(topicName)
-        .topicActions(MESSAGES_READ)
+        .topicActions(topicName, MESSAGES_READ)
         .operationName("getTopicMessages");
 
     if (auditService.isAuditTopic(getCluster(clusterName), topicName)) {
@@ -128,8 +126,7 @@ public class MessagesController extends AbstractController implements MessagesAp
 
     var context = AccessContext.builder()
         .cluster(clusterName)
-        .topic(topicName)
-        .topicActions(MESSAGES_PRODUCE)
+        .topicActions(topicName, MESSAGES_PRODUCE)
         .operationName("sendTopicMessages")
         .build();
 
@@ -175,8 +172,7 @@ public class MessagesController extends AbstractController implements MessagesAp
                                                                  ServerWebExchange exchange) {
     var context = AccessContext.builder()
         .cluster(clusterName)
-        .topic(topicName)
-        .topicActions(TopicAction.VIEW)
+        .topicActions(topicName, TopicAction.VIEW)
         .operationName("getSerdes")
         .build();
 

+ 29 - 35
kafka-ui-api/src/main/java/com/provectus/kafka/ui/controller/SchemasController.java

@@ -52,8 +52,7 @@ public class SchemasController extends AbstractController implements SchemasApi
       ServerWebExchange exchange) {
     var context = AccessContext.builder()
         .cluster(clusterName)
-        .schema(subject)
-        .schemaActions(SchemaAction.VIEW)
+        .schemaActions(subject, SchemaAction.VIEW)
         .operationName("checkSchemaCompatibility")
         .build();
 
@@ -73,22 +72,23 @@ public class SchemasController extends AbstractController implements SchemasApi
   public Mono<ResponseEntity<SchemaSubjectDTO>> createNewSchema(
       String clusterName, @Valid Mono<NewSchemaSubjectDTO> newSchemaSubjectMono,
       ServerWebExchange exchange) {
-    var context = AccessContext.builder()
-        .cluster(clusterName)
-        .schemaActions(SchemaAction.CREATE)
-        .operationName("createNewSchema")
-        .build();
-
-    return validateAccess(context).then(
-        newSchemaSubjectMono.flatMap(newSubject ->
-                schemaRegistryService.registerNewSchema(
-                    getCluster(clusterName),
-                    newSubject.getSubject(),
-                    kafkaSrMapper.fromDto(newSubject)
-                )
-            ).map(kafkaSrMapper::toDto)
-            .map(ResponseEntity::ok)
-    ).doOnEach(sig -> audit(context, sig));
+    return newSchemaSubjectMono.flatMap(newSubject -> {
+          var context = AccessContext.builder()
+              .cluster(clusterName)
+              .schemaActions(newSubject.getSubject(), SchemaAction.CREATE)
+              .operationName("createNewSchema")
+              .build();
+          return validateAccess(context).then(
+                  schemaRegistryService.registerNewSchema(
+                      getCluster(clusterName),
+                      newSubject.getSubject(),
+                      kafkaSrMapper.fromDto(newSubject)
+                  ))
+              .map(kafkaSrMapper::toDto)
+              .map(ResponseEntity::ok)
+              .doOnEach(sig -> audit(context, sig));
+        }
+    );
   }
 
   @Override
@@ -96,8 +96,7 @@ public class SchemasController extends AbstractController implements SchemasApi
       String clusterName, String subject, ServerWebExchange exchange) {
     var context = AccessContext.builder()
         .cluster(clusterName)
-        .schema(subject)
-        .schemaActions(SchemaAction.DELETE)
+        .schemaActions(subject, SchemaAction.DELETE)
         .operationName("deleteLatestSchema")
         .build();
 
@@ -113,8 +112,7 @@ public class SchemasController extends AbstractController implements SchemasApi
       String clusterName, String subject, ServerWebExchange exchange) {
     var context = AccessContext.builder()
         .cluster(clusterName)
-        .schema(subject)
-        .schemaActions(SchemaAction.DELETE)
+        .schemaActions(subject, SchemaAction.DELETE)
         .operationName("deleteSchema")
         .build();
 
@@ -130,8 +128,7 @@ public class SchemasController extends AbstractController implements SchemasApi
       String clusterName, String subjectName, Integer version, ServerWebExchange exchange) {
     var context = AccessContext.builder()
         .cluster(clusterName)
-        .schema(subjectName)
-        .schemaActions(SchemaAction.DELETE)
+        .schemaActions(subjectName, SchemaAction.DELETE)
         .operationName("deleteSchemaByVersion")
         .build();
 
@@ -147,8 +144,7 @@ public class SchemasController extends AbstractController implements SchemasApi
       String clusterName, String subjectName, ServerWebExchange exchange) {
     var context = AccessContext.builder()
         .cluster(clusterName)
-        .schema(subjectName)
-        .schemaActions(SchemaAction.VIEW)
+        .schemaActions(subjectName, SchemaAction.VIEW)
         .operationName("getAllVersionsBySubject")
         .build();
 
@@ -176,8 +172,7 @@ public class SchemasController extends AbstractController implements SchemasApi
                                                                 ServerWebExchange exchange) {
     var context = AccessContext.builder()
         .cluster(clusterName)
-        .schema(subject)
-        .schemaActions(SchemaAction.VIEW)
+        .schemaActions(subject, SchemaAction.VIEW)
         .operationName("getLatestSchema")
         .build();
 
@@ -193,8 +188,7 @@ public class SchemasController extends AbstractController implements SchemasApi
       String clusterName, String subject, Integer version, ServerWebExchange exchange) {
     var context = AccessContext.builder()
         .cluster(clusterName)
-        .schema(subject)
-        .schemaActions(SchemaAction.VIEW)
+        .schemaActions(subject, SchemaAction.VIEW)
         .operationName("getSchemaByVersion")
         .operationParams(Map.of("subject", subject, "version", version))
         .build();
@@ -249,7 +243,7 @@ public class SchemasController extends AbstractController implements SchemasApi
       ServerWebExchange exchange) {
     var context = AccessContext.builder()
         .cluster(clusterName)
-        .schemaActions(SchemaAction.MODIFY_GLOBAL_COMPATIBILITY)
+        .schemaGlobalCompatChange()
         .operationName("updateGlobalSchemaCompatibilityLevel")
         .build();
 
@@ -269,16 +263,16 @@ public class SchemasController extends AbstractController implements SchemasApi
   public Mono<ResponseEntity<Void>> updateSchemaCompatibilityLevel(
       String clusterName, String subject, @Valid Mono<CompatibilityLevelDTO> compatibilityLevelMono,
       ServerWebExchange exchange) {
+
     var context = AccessContext.builder()
         .cluster(clusterName)
-        .schemaActions(SchemaAction.EDIT)
+        .schemaActions(subject, SchemaAction.EDIT)
         .operationName("updateSchemaCompatibilityLevel")
         .operationParams(Map.of("subject", subject))
         .build();
 
-    return validateAccess(context).then(
-        compatibilityLevelMono
-            .flatMap(compatibilityLevelDTO ->
+    return compatibilityLevelMono.flatMap(compatibilityLevelDTO ->
+        validateAccess(context).then(
                 schemaRegistryService.updateSchemaCompatibility(
                     getCluster(clusterName),
                     subject,

+ 12 - 23
kafka-ui-api/src/main/java/com/provectus/kafka/ui/controller/TopicsController.java

@@ -58,7 +58,7 @@ public class TopicsController extends AbstractController implements TopicsApi {
     return topicCreationMono.flatMap(topicCreation -> {
       var context = AccessContext.builder()
           .cluster(clusterName)
-          .topicActions(CREATE)
+          .topicActions(topicCreation.getName(), CREATE)
           .operationName("createTopic")
           .operationParams(topicCreation)
           .build();
@@ -77,8 +77,7 @@ public class TopicsController extends AbstractController implements TopicsApi {
                                                       String topicName, ServerWebExchange exchange) {
     var context = AccessContext.builder()
         .cluster(clusterName)
-        .topic(topicName)
-        .topicActions(VIEW, CREATE, DELETE)
+        .topicActions(topicName, VIEW, CREATE, DELETE)
         .operationName("recreateTopic")
         .build();
 
@@ -95,8 +94,7 @@ public class TopicsController extends AbstractController implements TopicsApi {
 
     var context = AccessContext.builder()
         .cluster(clusterName)
-        .topic(topicName)
-        .topicActions(VIEW, CREATE)
+        .topicActions(topicName, VIEW, CREATE)
         .operationName("cloneTopic")
         .operationParams(Map.of("newTopicName", newTopicName))
         .build();
@@ -114,8 +112,7 @@ public class TopicsController extends AbstractController implements TopicsApi {
 
     var context = AccessContext.builder()
         .cluster(clusterName)
-        .topic(topicName)
-        .topicActions(DELETE)
+        .topicActions(topicName, DELETE)
         .operationName("deleteTopic")
         .build();
 
@@ -133,8 +130,7 @@ public class TopicsController extends AbstractController implements TopicsApi {
 
     var context = AccessContext.builder()
         .cluster(clusterName)
-        .topic(topicName)
-        .topicActions(VIEW)
+        .topicActions(topicName, VIEW)
         .operationName("getTopicConfigs")
         .build();
 
@@ -155,8 +151,7 @@ public class TopicsController extends AbstractController implements TopicsApi {
 
     var context = AccessContext.builder()
         .cluster(clusterName)
-        .topic(topicName)
-        .topicActions(VIEW)
+        .topicActions(topicName, VIEW)
         .operationName("getTopicDetails")
         .build();
 
@@ -221,8 +216,7 @@ public class TopicsController extends AbstractController implements TopicsApi {
 
     var context = AccessContext.builder()
         .cluster(clusterName)
-        .topic(topicName)
-        .topicActions(VIEW, EDIT)
+        .topicActions(topicName, VIEW, EDIT)
         .operationName("updateTopic")
         .build();
 
@@ -242,8 +236,7 @@ public class TopicsController extends AbstractController implements TopicsApi {
 
     var context = AccessContext.builder()
         .cluster(clusterName)
-        .topic(topicName)
-        .topicActions(VIEW, EDIT)
+        .topicActions(topicName, VIEW, EDIT)
         .build();
 
     return validateAccess(context).then(
@@ -261,8 +254,7 @@ public class TopicsController extends AbstractController implements TopicsApi {
 
     var context = AccessContext.builder()
         .cluster(clusterName)
-        .topic(topicName)
-        .topicActions(VIEW, EDIT)
+        .topicActions(topicName, VIEW, EDIT)
         .operationName("changeReplicationFactor")
         .build();
 
@@ -279,8 +271,7 @@ public class TopicsController extends AbstractController implements TopicsApi {
 
     var context = AccessContext.builder()
         .cluster(clusterName)
-        .topic(topicName)
-        .topicActions(MESSAGES_READ)
+        .topicActions(topicName, MESSAGES_READ)
         .operationName("analyzeTopic")
         .build();
 
@@ -296,8 +287,7 @@ public class TopicsController extends AbstractController implements TopicsApi {
                                                         ServerWebExchange exchange) {
     var context = AccessContext.builder()
         .cluster(clusterName)
-        .topic(topicName)
-        .topicActions(MESSAGES_READ)
+        .topicActions(topicName, MESSAGES_READ)
         .operationName("cancelTopicAnalysis")
         .build();
 
@@ -315,8 +305,7 @@ public class TopicsController extends AbstractController implements TopicsApi {
 
     var context = AccessContext.builder()
         .cluster(clusterName)
-        .topic(topicName)
-        .topicActions(MESSAGES_READ)
+        .topicActions(topicName, MESSAGES_READ)
         .operationName("getTopicAnalysis")
         .build();
 

+ 72 - 95
kafka-ui-api/src/main/java/com/provectus/kafka/ui/model/rbac/AccessContext.java

@@ -1,5 +1,6 @@
 package com.provectus.kafka.ui.model.rbac;
 
+import com.google.common.base.Preconditions;
 import com.provectus.kafka.ui.model.rbac.permission.AclAction;
 import com.provectus.kafka.ui.model.rbac.permission.ApplicationConfigAction;
 import com.provectus.kafka.ui.model.rbac.permission.AuditAction;
@@ -7,43 +8,66 @@ import com.provectus.kafka.ui.model.rbac.permission.ClusterConfigAction;
 import com.provectus.kafka.ui.model.rbac.permission.ConnectAction;
 import com.provectus.kafka.ui.model.rbac.permission.ConsumerGroupAction;
 import com.provectus.kafka.ui.model.rbac.permission.KsqlAction;
+import com.provectus.kafka.ui.model.rbac.permission.PermissibleAction;
 import com.provectus.kafka.ui.model.rbac.permission.SchemaAction;
 import com.provectus.kafka.ui.model.rbac.permission.TopicAction;
+import jakarta.annotation.Nullable;
+import java.util.ArrayList;
 import java.util.Collection;
-import java.util.Collections;
 import java.util.List;
-import java.util.Map;
+import java.util.stream.Collectors;
 import lombok.Value;
-import org.springframework.util.Assert;
+import org.springframework.security.access.AccessDeniedException;
 
 @Value
 public class AccessContext {
 
-  Collection<ApplicationConfigAction> applicationConfigActions;
+  public interface ResourceAccess {
+    // will be used for audit, should be serializable via json object mapper
+    @Nullable
+    Object resourceId();
 
-  String cluster;
-  Collection<ClusterConfigAction> clusterConfigActions;
-
-  String topic;
-  Collection<TopicAction> topicActions;
+    Resource resourceType();
 
-  String consumerGroup;
-  Collection<ConsumerGroupAction> consumerGroupActions;
+    Collection<PermissibleAction> requestedActions();
 
-  String connect;
-  Collection<ConnectAction> connectActions;
+    // returns false if access not allowed
+    boolean validate(List<Permission> allowedPermissions);
+  }
 
-  String connector;
+  private record SingleResourceAccess(@Nullable String name,
+                                      Resource resourceType,
+                                      Collection<PermissibleAction> requestedActions) implements ResourceAccess {
 
-  String schema;
-  Collection<SchemaAction> schemaActions;
+    SingleResourceAccess(Resource type, List<PermissibleAction> requestedActions) {
+      this(null, type, requestedActions);
+    }
 
-  Collection<KsqlAction> ksqlActions;
+    @Override
+    public Object resourceId() {
+      return name;
+    }
 
-  Collection<AclAction> aclActions;
+    @Override
+    public boolean validate(List<Permission> userPermissions) throws AccessDeniedException {
+      var allowedActions = userPermissions.stream()
+          .filter(permission -> permission.getResource() == resourceType)
+          .filter(permission -> {
+            if (name == null && permission.getCompiledValuePattern() == null) {
+              return true;
+            }
+            Preconditions.checkState(permission.getCompiledValuePattern() != null && name != null);
+            return permission.getCompiledValuePattern().matcher(name).matches();
+          })
+          .flatMap(p -> p.getParsedActions().stream())
+          .collect(Collectors.toSet());
 
-  Collection<AuditAction> auditAction;
+      return allowedActions.containsAll(requestedActions);
+    }
+  }
 
+  String cluster;
+  List<ResourceAccess> accesses;
   String operationName;
   Object operationParams;
 
@@ -52,109 +76,76 @@ public class AccessContext {
   }
 
   public static final class AccessContextBuilder {
-    private Collection<ApplicationConfigAction> applicationConfigActions = Collections.emptySet();
-    private String cluster;
-    private Collection<ClusterConfigAction> clusterConfigActions = Collections.emptySet();
-    private String topic;
-    private Collection<TopicAction> topicActions = Collections.emptySet();
-    private String consumerGroup;
-    private Collection<ConsumerGroupAction> consumerGroupActions = Collections.emptySet();
-    private String connect;
-    private Collection<ConnectAction> connectActions = Collections.emptySet();
-    private String connector;
-    private String schema;
-    private Collection<SchemaAction> schemaActions = Collections.emptySet();
-    private Collection<KsqlAction> ksqlActions = Collections.emptySet();
-    private Collection<AclAction> aclActions = Collections.emptySet();
-    private Collection<AuditAction> auditActions = Collections.emptySet();
 
+    private String cluster;
     private String operationName;
     private Object operationParams;
+    private final List<ResourceAccess> accesses = new ArrayList<>();
 
     private AccessContextBuilder() {
     }
 
-    public AccessContextBuilder applicationConfigActions(ApplicationConfigAction... actions) {
-      Assert.isTrue(actions.length > 0, "actions not present");
-      this.applicationConfigActions = List.of(actions);
-      return this;
-    }
-
     public AccessContextBuilder cluster(String cluster) {
       this.cluster = cluster;
       return this;
     }
 
-    public AccessContextBuilder clusterConfigActions(ClusterConfigAction... actions) {
-      Assert.isTrue(actions.length > 0, "actions not present");
-      this.clusterConfigActions = List.of(actions);
-      return this;
-    }
-
-    public AccessContextBuilder topic(String topic) {
-      this.topic = topic;
-      return this;
-    }
-
-    public AccessContextBuilder topicActions(TopicAction... actions) {
-      Assert.isTrue(actions.length > 0, "actions not present");
-      this.topicActions = List.of(actions);
-      return this;
-    }
-
-    public AccessContextBuilder consumerGroup(String consumerGroup) {
-      this.consumerGroup = consumerGroup;
+    public AccessContextBuilder applicationConfigActions(ApplicationConfigAction... actions) {
+      Preconditions.checkArgument(actions.length > 0, "actions not present");
+      accesses.add(new SingleResourceAccess(Resource.APPLICATIONCONFIG, List.of(actions)));
       return this;
     }
 
-    public AccessContextBuilder consumerGroupActions(ConsumerGroupAction... actions) {
-      Assert.isTrue(actions.length > 0, "actions not present");
-      this.consumerGroupActions = List.of(actions);
+    public AccessContextBuilder clusterConfigActions(ClusterConfigAction... actions) {
+      Preconditions.checkArgument(actions.length > 0, "actions not present");
+      accesses.add(new SingleResourceAccess(Resource.CLUSTERCONFIG, List.of(actions)));
       return this;
     }
 
-    public AccessContextBuilder connect(String connect) {
-      this.connect = connect;
+    public AccessContextBuilder topicActions(String topic, TopicAction... actions) {
+      Preconditions.checkArgument(actions.length > 0, "actions not present");
+      accesses.add(new SingleResourceAccess(topic, Resource.TOPIC, List.of(actions)));
       return this;
     }
 
-    public AccessContextBuilder connectActions(ConnectAction... actions) {
-      Assert.isTrue(actions.length > 0, "actions not present");
-      this.connectActions = List.of(actions);
+    public AccessContextBuilder consumerGroupActions(String consumerGroup, ConsumerGroupAction... actions) {
+      Preconditions.checkArgument(actions.length > 0, "actions not present");
+      accesses.add(new SingleResourceAccess(consumerGroup, Resource.CONSUMER, List.of(actions)));
       return this;
     }
 
-    public AccessContextBuilder connector(String connector) {
-      this.connector = connector;
+    public AccessContextBuilder connectActions(String connect, ConnectAction... actions) {
+      Preconditions.checkArgument(actions.length > 0, "actions not present");
+      accesses.add(new SingleResourceAccess(connect, Resource.CONNECT, List.of(actions)));
       return this;
     }
 
-    public AccessContextBuilder schema(String schema) {
-      this.schema = schema;
+    public AccessContextBuilder schemaActions(String schema, SchemaAction... actions) {
+      Preconditions.checkArgument(actions.length > 0, "actions not present");
+      accesses.add(new SingleResourceAccess(schema, Resource.SCHEMA, List.of(actions)));
       return this;
     }
 
-    public AccessContextBuilder schemaActions(SchemaAction... actions) {
-      Assert.isTrue(actions.length > 0, "actions not present");
-      this.schemaActions = List.of(actions);
+    public AccessContextBuilder schemaGlobalCompatChange() {
+      accesses.add(new SingleResourceAccess(Resource.SCHEMA, List.of(SchemaAction.MODIFY_GLOBAL_COMPATIBILITY)));
       return this;
     }
 
     public AccessContextBuilder ksqlActions(KsqlAction... actions) {
-      Assert.isTrue(actions.length > 0, "actions not present");
-      this.ksqlActions = List.of(actions);
+      Preconditions.checkArgument(actions.length > 0, "actions not present");
+      accesses.add(new SingleResourceAccess(Resource.KSQL, List.of(actions)));
       return this;
     }
 
     public AccessContextBuilder aclActions(AclAction... actions) {
-      Assert.isTrue(actions.length > 0, "actions not present");
-      this.aclActions = List.of(actions);
+      Preconditions.checkArgument(actions.length > 0, "actions not present");
+      accesses.add(new SingleResourceAccess(Resource.ACL, List.of(actions)));
       return this;
     }
 
     public AccessContextBuilder auditActions(AuditAction... actions) {
-      Assert.isTrue(actions.length > 0, "actions not present");
-      this.auditActions = List.of(actions);
+      Preconditions.checkArgument(actions.length > 0, "actions not present");
+      accesses.add(new SingleResourceAccess(Resource.AUDIT, List.of(actions)));
       return this;
     }
 
@@ -168,22 +159,8 @@ public class AccessContext {
       return this;
     }
 
-    public AccessContextBuilder operationParams(Map<String, Object> paramsMap) {
-      this.operationParams = paramsMap;
-      return this;
-    }
-
     public AccessContext build() {
-      return new AccessContext(
-          applicationConfigActions,
-          cluster, clusterConfigActions,
-          topic, topicActions,
-          consumerGroup, consumerGroupActions,
-          connect, connectActions,
-          connector,
-          schema, schemaActions,
-          ksqlActions, aclActions, auditActions,
-          operationName, operationParams);
+      return new AccessContext(cluster, accesses, operationName, operationParams);
     }
   }
 }

+ 0 - 109
kafka-ui-api/src/main/java/com/provectus/kafka/ui/model/rbac/AccessContextV2.java

@@ -1,109 +0,0 @@
-package com.provectus.kafka.ui.model.rbac;
-
-import com.provectus.kafka.ui.model.rbac.permission.AclAction;
-import com.provectus.kafka.ui.model.rbac.permission.ApplicationConfigAction;
-import com.provectus.kafka.ui.model.rbac.permission.AuditAction;
-import com.provectus.kafka.ui.model.rbac.permission.ClusterConfigAction;
-import com.provectus.kafka.ui.model.rbac.permission.ConnectAction;
-import com.provectus.kafka.ui.model.rbac.permission.ConsumerGroupAction;
-import com.provectus.kafka.ui.model.rbac.permission.KsqlAction;
-import com.provectus.kafka.ui.model.rbac.permission.PermissibleAction;
-import com.provectus.kafka.ui.model.rbac.permission.SchemaAction;
-import com.provectus.kafka.ui.model.rbac.permission.TopicAction;
-import jakarta.annotation.Nullable;
-import java.util.ArrayList;
-import java.util.List;
-import lombok.Value;
-import org.springframework.util.Assert;
-
-@Value
-public class AccessContextV2 {
-
-  public record ResourceAccess(@Nullable String name, ResourceV2 type, List<PermissibleAction> actions) {
-  }
-
-  String cluster;
-  List<ResourceAccess> accesses;
-  String operationName;
-  Object operationParams;
-
-  public static AccessContextBuilder builder() {
-    return new AccessContextBuilder();
-  }
-
-  public static final class AccessContextBuilder {
-
-    private String cluster;
-    private String operationName;
-    private Object operationParams;
-    private List<ResourceAccess> accesses = new ArrayList<>();
-
-    private AccessContextBuilder() {
-    }
-
-    public AccessContextBuilder applicationConfigActions(ApplicationConfigAction... actions) {
-      accesses.add(new ResourceAccess(null, ResourceV2.APPLICATIONCONFIG, List.of(actions)));
-      return this;
-    }
-
-    public AccessContextBuilder cluster(String cluster) {
-      this.cluster = cluster;
-      return this;
-    }
-
-    public AccessContextBuilder clusterConfigActions(ClusterConfigAction... actions) {
-      accesses.add(new ResourceAccess(null, ResourceV2.CLUSTERCONFIG, List.of(actions)));
-      return this;
-    }
-
-    public AccessContextBuilder topicActions(String topic, TopicAction... actions) {
-      accesses.add(new ResourceAccess(topic, ResourceV2.APPLICATIONCONFIG, List.of(actions)));
-      return this;
-    }
-
-    public AccessContextBuilder consumerGroupActions(String consumerGroup, ConsumerGroupAction actions) {
-      accesses.add(new ResourceAccess(consumerGroup, ResourceV2.CONSUMER, List.of(actions)));
-      return this;
-    }
-
-    public AccessContextBuilder connectActions(String connect, ConnectAction... actions) {
-      accesses.add(new ResourceAccess(connect, ResourceV2.CONSUMER, List.of(actions)));
-      return this;
-    }
-
-    public AccessContextBuilder schemaActions(String schema, SchemaAction... actions) {
-      accesses.add(new ResourceAccess(schema, ResourceV2.SCHEMA, List.of(actions)));
-      return this;
-    }
-
-    public AccessContextBuilder ksqlActions(KsqlAction... actions) {
-      accesses.add(new ResourceAccess(null, ResourceV2.KSQL, List.of(actions)));
-      return this;
-    }
-
-    public AccessContextBuilder aclActions(AclAction... actions) {
-      accesses.add(new ResourceAccess(null, ResourceV2.ACL, List.of(actions)));
-      return this;
-    }
-
-    public AccessContextBuilder auditActions(AuditAction... actions) {
-      Assert.isTrue(actions.length > 0, "actions not present");
-      accesses.add(new ResourceAccess(null, ResourceV2.AUDIT, List.of(actions)));
-      return this;
-    }
-
-    public AccessContextBuilder operationName(String operationName) {
-      this.operationName = operationName;
-      return this;
-    }
-
-    public AccessContextBuilder operationParams(Object operationParams) {
-      this.operationParams = operationParams;
-      return this;
-    }
-
-    public AccessContextV2 build() {
-      return new AccessContextV2(cluster, accesses, operationName, operationParams);
-    }
-  }
-}

+ 8 - 47
kafka-ui-api/src/main/java/com/provectus/kafka/ui/model/rbac/Permission.java

@@ -1,23 +1,7 @@
 package com.provectus.kafka.ui.model.rbac;
 
-import static com.provectus.kafka.ui.model.rbac.Resource.ACL;
-import static com.provectus.kafka.ui.model.rbac.Resource.APPLICATIONCONFIG;
-import static com.provectus.kafka.ui.model.rbac.Resource.AUDIT;
-import static com.provectus.kafka.ui.model.rbac.Resource.CLUSTERCONFIG;
-import static com.provectus.kafka.ui.model.rbac.Resource.KSQL;
-
-import com.provectus.kafka.ui.model.rbac.permission.AclAction;
-import com.provectus.kafka.ui.model.rbac.permission.ApplicationConfigAction;
-import com.provectus.kafka.ui.model.rbac.permission.AuditAction;
-import com.provectus.kafka.ui.model.rbac.permission.ClusterConfigAction;
-import com.provectus.kafka.ui.model.rbac.permission.ConnectAction;
-import com.provectus.kafka.ui.model.rbac.permission.ConsumerGroupAction;
-import com.provectus.kafka.ui.model.rbac.permission.KsqlAction;
+import com.google.common.base.Preconditions;
 import com.provectus.kafka.ui.model.rbac.permission.PermissibleAction;
-import com.provectus.kafka.ui.model.rbac.permission.SchemaAction;
-import com.provectus.kafka.ui.model.rbac.permission.TopicAction;
-import java.util.Arrays;
-import java.util.Collections;
 import java.util.List;
 import java.util.regex.Pattern;
 import javax.annotation.Nullable;
@@ -32,14 +16,10 @@ import org.springframework.util.Assert;
 @EqualsAndHashCode
 public class Permission {
 
-  private static final List<Resource> RBAC_ACTION_EXEMPT_LIST =
-      List.of(KSQL, CLUSTERCONFIG, APPLICATIONCONFIG, ACL, AUDIT);
-
   Resource resource;
-  ResourceV2 resourceV2;
-  List<String> actions;
-  List<PermissibleAction> actionsv2;
 
+  List<String> actions;
+  transient List<PermissibleAction> parsedActions;
 
   @Nullable
   String value;
@@ -59,41 +39,22 @@ public class Permission {
   @SuppressWarnings("unused")
   public void setActions(List<String> actions) {
     this.actions = actions;
-    this.actionsv2 = resourceV2.parseActions(actions); //TODO: setting order ? set on transform?
   }
 
   public void validate() {
     Assert.notNull(resource, "resource cannot be null");
-    if (!RBAC_ACTION_EXEMPT_LIST.contains(this.resource)) {
-      Assert.notNull(value, "permission value can't be empty for resource " + resource);
-    }
   }
 
   public void transform() {
     if (value != null) {
       this.compiledValuePattern = Pattern.compile(value);
     }
-    if (CollectionUtils.isNotEmpty(actions) && actions.stream().anyMatch("ALL"::equalsIgnoreCase)) {
-      this.actions = getAllActionValues();
+    Preconditions.checkArgument(CollectionUtils.isNotEmpty(actions), "Actions list can't be null or empty list");
+    if (actions.stream().anyMatch("ALL"::equalsIgnoreCase)) {
+      this.parsedActions = resource.allActions();
+    } else {
+      this.parsedActions = resource.parseActions(actions);
     }
   }
 
-  private List<String> getAllActionValues() {
-    if (resource == null) {
-      return Collections.emptyList();
-    }
-
-    return switch (this.resource) {
-      case APPLICATIONCONFIG -> Arrays.stream(ApplicationConfigAction.values()).map(Enum::toString).toList();
-      case CLUSTERCONFIG -> Arrays.stream(ClusterConfigAction.values()).map(Enum::toString).toList();
-      case TOPIC -> Arrays.stream(TopicAction.values()).map(Enum::toString).toList();
-      case CONSUMER -> Arrays.stream(ConsumerGroupAction.values()).map(Enum::toString).toList();
-      case SCHEMA -> Arrays.stream(SchemaAction.values()).map(Enum::toString).toList();
-      case CONNECT -> Arrays.stream(ConnectAction.values()).map(Enum::toString).toList();
-      case KSQL -> Arrays.stream(KsqlAction.values()).map(Enum::toString).toList();
-      case ACL -> Arrays.stream(AclAction.values()).map(Enum::toString).toList();
-      case AUDIT -> Arrays.stream(AuditAction.values()).map(Enum::toString).toList();
-    };
-  }
-
 }

+ 48 - 10
kafka-ui-api/src/main/java/com/provectus/kafka/ui/model/rbac/Resource.java

@@ -1,24 +1,62 @@
 package com.provectus.kafka.ui.model.rbac;
 
+import com.provectus.kafka.ui.model.rbac.permission.AclAction;
+import com.provectus.kafka.ui.model.rbac.permission.ApplicationConfigAction;
+import com.provectus.kafka.ui.model.rbac.permission.ClusterConfigAction;
+import com.provectus.kafka.ui.model.rbac.permission.ConnectAction;
+import com.provectus.kafka.ui.model.rbac.permission.ConsumerGroupAction;
+import com.provectus.kafka.ui.model.rbac.permission.KsqlAction;
+import com.provectus.kafka.ui.model.rbac.permission.PermissibleAction;
+import com.provectus.kafka.ui.model.rbac.permission.SchemaAction;
+import com.provectus.kafka.ui.model.rbac.permission.TopicAction;
+import jakarta.annotation.Nullable;
+import java.util.List;
 import org.apache.commons.lang3.EnumUtils;
-import org.jetbrains.annotations.Nullable;
 
 public enum Resource {
 
-  APPLICATIONCONFIG,
-  CLUSTERCONFIG,
-  TOPIC,
-  CONSUMER,
-  SCHEMA,
-  CONNECT,
-  KSQL,
-  ACL,
-  AUDIT;
+  APPLICATIONCONFIG(ApplicationConfigAction.values()),
+
+  CLUSTERCONFIG(ClusterConfigAction.values()),
+
+  TOPIC(TopicAction.values()),
+
+  CONSUMER(ConsumerGroupAction.values()),
+
+  SCHEMA(SchemaAction.values()),
+
+  CONNECT(ConnectAction.values()),
+
+  KSQL(KsqlAction.values()),
+
+  ACL(AclAction.values()),
+
+  AUDIT(AclAction.values());
+
+  private final List<PermissibleAction> actions;
+
+  Resource(PermissibleAction[] actions) {
+    this.actions = List.of(actions);
+  }
+
+  public List<PermissibleAction> allActions() {
+    return actions;
+  }
 
   @Nullable
   public static Resource fromString(String name) {
     return EnumUtils.getEnum(Resource.class, name);
   }
 
+  public List<PermissibleAction> parseActions(List<String> actionsToParse) {
+    return actionsToParse.stream()
+        .map(String::toUpperCase)
+        .map(toParse -> allActions().stream()
+            .filter(a -> toParse.equals(a.name().toUpperCase()))
+            .findFirst()
+            .orElseThrow(() -> new IllegalArgumentException("no actions found for string '%s'".formatted(toParse)))
+        )
+        .toList();
+  }
 
 }

+ 0 - 55
kafka-ui-api/src/main/java/com/provectus/kafka/ui/model/rbac/ResourceV2.java

@@ -1,55 +0,0 @@
-package com.provectus.kafka.ui.model.rbac;
-
-import com.provectus.kafka.ui.model.rbac.permission.AclAction;
-import com.provectus.kafka.ui.model.rbac.permission.ApplicationConfigAction;
-import com.provectus.kafka.ui.model.rbac.permission.ClusterConfigAction;
-import com.provectus.kafka.ui.model.rbac.permission.ConnectAction;
-import com.provectus.kafka.ui.model.rbac.permission.ConsumerGroupAction;
-import com.provectus.kafka.ui.model.rbac.permission.KsqlAction;
-import com.provectus.kafka.ui.model.rbac.permission.PermissibleAction;
-import com.provectus.kafka.ui.model.rbac.permission.SchemaAction;
-import com.provectus.kafka.ui.model.rbac.permission.TopicAction;
-import java.util.List;
-
-public enum ResourceV2 {
-
-  APPLICATIONCONFIG(ApplicationConfigAction.values()),
-
-  CLUSTERCONFIG(ClusterConfigAction.values()),
-
-  TOPIC(TopicAction.values()),
-
-  CONSUMER(ConsumerGroupAction.values()),
-
-  SCHEMA(SchemaAction.values()),
-
-  CONNECT(ConnectAction.values()),
-
-  KSQL(KsqlAction.values()),
-
-  ACL(AclAction.values()),
-
-  AUDIT(AclAction.values());
-
-  private final List<PermissibleAction> actions;
-
-  ResourceV2(PermissibleAction[] actions) {
-    this.actions = List.of(actions);
-  }
-
-  public List<PermissibleAction> allActions() {
-    return actions;
-  }
-
-  public List<PermissibleAction> parseActions(List<String> actions) {
-    return actions.stream()
-        .map(String::toUpperCase)
-        .map(toParse -> allActions().stream()
-            .filter(a -> toParse.equals(a.name().toUpperCase()))
-            .findFirst().
-            orElseThrow() //TODO err msg
-        )
-        .toList();
-  }
-
-}

+ 8 - 36
kafka-ui-api/src/main/java/com/provectus/kafka/ui/service/audit/AuditRecord.java

@@ -7,10 +7,8 @@ import com.provectus.kafka.ui.exception.ValidationException;
 import com.provectus.kafka.ui.model.rbac.AccessContext;
 import com.provectus.kafka.ui.model.rbac.Resource;
 import com.provectus.kafka.ui.model.rbac.permission.PermissibleAction;
-import java.util.ArrayList;
-import java.util.LinkedHashMap;
+import java.util.Collection;
 import java.util.List;
-import java.util.Map;
 import javax.annotation.Nullable;
 import lombok.SneakyThrows;
 import org.springframework.security.access.AccessDeniedException;
@@ -34,43 +32,17 @@ record AuditRecord(String timestamp,
     return MAPPER.writeValueAsString(this);
   }
 
-  record AuditResource(String accessType, boolean alter, Resource type, @Nullable Object id) {
+  record AuditResource(List<String> accessType, boolean alter, Resource type, @Nullable Object id) {
 
-    private static AuditResource create(PermissibleAction action, Resource type, @Nullable Object id) {
-      return new AuditResource(action.name(), action.isAlter(), type, id);
+    private static AuditResource create(Collection<PermissibleAction> actions, Resource type, @Nullable Object id) {
+      boolean isAlter = actions.stream().anyMatch(PermissibleAction::isAlter);
+      return new AuditResource(actions.stream().map(PermissibleAction::name).toList(), isAlter, type, id);
     }
 
     static List<AuditResource> getAccessedResources(AccessContext ctx) {
-      List<AuditResource> resources = new ArrayList<>();
-      ctx.getClusterConfigActions()
-          .forEach(a -> resources.add(create(a, Resource.CLUSTERCONFIG, null)));
-      ctx.getTopicActions()
-          .forEach(a -> resources.add(create(a, Resource.TOPIC, nameId(ctx.getTopic()))));
-      ctx.getConsumerGroupActions()
-          .forEach(a -> resources.add(create(a, Resource.CONSUMER, nameId(ctx.getConsumerGroup()))));
-      ctx.getConnectActions()
-          .forEach(a -> {
-            Map<String, String> resourceId = new LinkedHashMap<>();
-            resourceId.put("connect", ctx.getConnect());
-            if (ctx.getConnector() != null) {
-              resourceId.put("connector", ctx.getConnector());
-            }
-            resources.add(create(a, Resource.CONNECT, resourceId));
-          });
-      ctx.getSchemaActions()
-          .forEach(a -> resources.add(create(a, Resource.SCHEMA, nameId(ctx.getSchema()))));
-      ctx.getKsqlActions()
-          .forEach(a -> resources.add(create(a, Resource.KSQL, null)));
-      ctx.getAclActions()
-          .forEach(a -> resources.add(create(a, Resource.ACL, null)));
-      ctx.getAuditAction()
-          .forEach(a -> resources.add(create(a, Resource.AUDIT, null)));
-      return resources;
-    }
-
-    @Nullable
-    private static Map<String, Object> nameId(@Nullable String name) {
-      return name != null ? Map.of("name", name) : null;
+      return ctx.getAccesses().stream()
+          .map(r -> create(r.requestedActions(), r.resourceType(), r.resourceId()))
+          .toList();
     }
   }
 

+ 22 - 322
kafka-ui-api/src/main/java/com/provectus/kafka/ui/service/rbac/AccessControlService.java

@@ -9,15 +9,11 @@ import com.provectus.kafka.ui.model.ClusterDTO;
 import com.provectus.kafka.ui.model.ConnectDTO;
 import com.provectus.kafka.ui.model.InternalTopic;
 import com.provectus.kafka.ui.model.rbac.AccessContext;
-import com.provectus.kafka.ui.model.rbac.AccessContextV2;
 import com.provectus.kafka.ui.model.rbac.Permission;
-import com.provectus.kafka.ui.model.rbac.Resource;
-import com.provectus.kafka.ui.model.rbac.ResourceV2;
 import com.provectus.kafka.ui.model.rbac.Role;
 import com.provectus.kafka.ui.model.rbac.Subject;
 import com.provectus.kafka.ui.model.rbac.permission.ConnectAction;
 import com.provectus.kafka.ui.model.rbac.permission.ConsumerGroupAction;
-import com.provectus.kafka.ui.model.rbac.permission.PermissibleAction;
 import com.provectus.kafka.ui.model.rbac.permission.SchemaAction;
 import com.provectus.kafka.ui.model.rbac.permission.TopicAction;
 import com.provectus.kafka.ui.service.rbac.extractor.CognitoAuthorityExtractor;
@@ -31,7 +27,6 @@ import java.util.List;
 import java.util.Objects;
 import java.util.Set;
 import java.util.function.Predicate;
-import java.util.regex.Pattern;
 import java.util.stream.Collectors;
 import javax.annotation.Nullable;
 import lombok.RequiredArgsConstructor;
@@ -97,76 +92,32 @@ public class AccessControlService {
     }
   }
 
-  public Mono<Void> validateAccess(AccessContextV2 context) {
+  public Mono<Void> validateAccess(AccessContext context) {
     if (!rbacEnabled) {
       return Mono.empty();
     }
-
     return getUser()
         .doOnNext(user -> {
-          if (context.getCluster() != null && !isClusterAccessible(context.getCluster(), user)) {
+          if (!validate(user, context)) {
             throw new AccessDeniedException(ACCESS_DENIED);
           }
-          context.getAccesses().forEach(resourceAccess -> {
-            @Nullable String name = resourceAccess.name();
-            ResourceV2 type = resourceAccess.type();
-            List<PermissibleAction> requestedActions = resourceAccess.actions();
-
-            Set<PermissibleAction> allowedActions = properties.getRoles().stream()
-                .filter(filterRole(user))
-                .flatMap(role -> role.getPermissions().stream())
-                .filter(permission -> permission.getResourceV2() == type && Objects.equals(permission.getValue(), name))
-                .flatMap(p -> p.getActionsv2().stream())
-                .collect(Collectors.toSet());
-
-            if (allowedActions.isEmpty()) {
-              //TODO: do smth?
-            }
-
-            if (!allowedActions.containsAll(requestedActions)) {
-              throw new AccessDeniedException(ACCESS_DENIED);
-            }
-          });
         })
         .then();
   }
 
-  public Mono<Void> validateAccess(AccessContext context) {
-    if (!rbacEnabled) {
-      return Mono.empty();
+  // returns false if access not allowed
+  private boolean validate(AuthenticatedUser user, AccessContext context) {
+    if (context.getCluster() != null && !isClusterAccessible(context.getCluster(), user)) {
+      return false;
     }
 
-    if (CollectionUtils.isNotEmpty(context.getApplicationConfigActions())) {
-      return getUser()
-          .doOnNext(user -> {
-            boolean accessGranted = isApplicationConfigAccessible(context, user);
-
-            if (!accessGranted) {
-              throw new AccessDeniedException(ACCESS_DENIED);
-            }
-          }).then();
-    }
+    List<Permission> allUserPermissions = properties.getRoles().stream()
+        .filter(filterRole(user))
+        .flatMap(role -> role.getPermissions().stream())
+        .toList();
 
-    return getUser()
-        .doOnNext(user -> {
-          boolean accessGranted =
-              isApplicationConfigAccessible(context, user)
-                  && isClusterAccessible(context, user)
-                  && isClusterConfigAccessible(context, user)
-                  && isTopicAccessible(context, user)
-                  && isConsumerGroupAccessible(context, user)
-                  && isConnectAccessible(context, user)
-                  && isConnectorAccessible(context, user) // TODO connector selectors
-                  && isSchemaAccessible(context, user)
-                  && isKsqlAccessible(context, user)
-                  && isAclAccessible(context, user)
-                  && isAuditAccessible(context, user);
-
-          if (!accessGranted) {
-            throw new AccessDeniedException(ACCESS_DENIED);
-          }
-        })
-        .then();
+    return context.getAccesses().stream()
+        .allMatch(resourceAccess -> resourceAccess.validate(allUserPermissions));
   }
 
   public Mono<AuthenticatedUser> getUser() {
@@ -177,20 +128,6 @@ public class AccessControlService {
         .map(user -> new AuthenticatedUser(user.name(), user.groups()));
   }
 
-  public boolean isApplicationConfigAccessible(AccessContext context, AuthenticatedUser user) {
-    if (!rbacEnabled) {
-      return true;
-    }
-    if (CollectionUtils.isEmpty(context.getApplicationConfigActions())) {
-      return true;
-    }
-    Set<String> requiredActions = context.getApplicationConfigActions()
-        .stream()
-        .map(a -> a.toString().toUpperCase())
-        .collect(Collectors.toSet());
-    return isAccessible(APPLICATIONCONFIG, null, user, context, requiredActions);
-  }
-
   private boolean isClusterAccessible(String clusterName, AuthenticatedUser user) {
     if (!rbacEnabled) {
       return true;
@@ -201,108 +138,33 @@ public class AccessControlService {
     return properties.getRoles()
         .stream()
         .filter(filterRole(user))
-        .anyMatch(filterCluster(clusterName));
-  }
-
-  private boolean isClusterAccessible(AccessContext context, AuthenticatedUser user) {
-    if (!rbacEnabled) {
-      return true;
-    }
-
-    Assert.isTrue(StringUtils.isNotEmpty(context.getCluster()), "cluster value is empty");
-
-    return properties.getRoles()
-        .stream()
-        .filter(filterRole(user))
-        .anyMatch(filterCluster(context.getCluster()));
+        .anyMatch(role -> role.getClusters().stream().anyMatch(clusterName::equalsIgnoreCase));
   }
 
   public Mono<Boolean> isClusterAccessible(ClusterDTO cluster) {
     if (!rbacEnabled) {
       return Mono.just(true);
     }
-
-    AccessContext accessContext = AccessContext
-        .builder()
-        .cluster(cluster.getName())
-        .build();
-
-    return getUser().map(u -> isClusterAccessible(accessContext, u));
-  }
-
-  public boolean isClusterConfigAccessible(AccessContext context, AuthenticatedUser user) {
-    if (!rbacEnabled) {
-      return true;
-    }
-
-    if (CollectionUtils.isEmpty(context.getClusterConfigActions())) {
-      return true;
-    }
-    Assert.isTrue(StringUtils.isNotEmpty(context.getCluster()), "cluster value is empty");
-
-    Set<String> requiredActions = context.getClusterConfigActions()
-        .stream()
-        .map(a -> a.toString().toUpperCase())
-        .collect(Collectors.toSet());
-
-    return isAccessible(Resource.CLUSTERCONFIG, context.getCluster(), user, context, requiredActions);
-  }
-
-  public boolean isTopicAccessible(AccessContext context, AuthenticatedUser user) {
-    if (!rbacEnabled) {
-      return true;
-    }
-
-    if (context.getTopic() == null && context.getTopicActions().isEmpty()) {
-      return true;
-    }
-    Assert.isTrue(!context.getTopicActions().isEmpty(), "actions are empty");
-
-    Set<String> requiredActions = context.getTopicActions()
-        .stream()
-        .map(a -> a.toString().toUpperCase())
-        .collect(Collectors.toSet());
-
-    return isAccessible(Resource.TOPIC, context.getTopic(), user, context, requiredActions);
+    return getUser().map(u -> isClusterAccessible(cluster.getName(), u));
   }
 
   public Mono<List<InternalTopic>> filterViewableTopics(List<InternalTopic> topics, String clusterName) {
     if (!rbacEnabled) {
       return Mono.just(topics);
     }
-
     return getUser()
         .map(user -> topics.stream()
             .filter(topic -> {
                   var accessContext = AccessContext
                       .builder()
                       .cluster(clusterName)
-                      .topic(topic.getName())
-                      .topicActions(TopicAction.VIEW)
+                      .topicActions(topic.getName(), TopicAction.VIEW)
                       .build();
-                  return isTopicAccessible(accessContext, user);
+                  return validate(user, accessContext);
                 }
             ).toList());
   }
 
-  private boolean isConsumerGroupAccessible(AccessContext context, AuthenticatedUser user) {
-    if (!rbacEnabled) {
-      return true;
-    }
-
-    if (context.getConsumerGroup() == null && context.getConsumerGroupActions().isEmpty()) {
-      return true;
-    }
-    Assert.isTrue(!context.getConsumerGroupActions().isEmpty(), "actions are empty");
-
-    Set<String> requiredActions = context.getConsumerGroupActions()
-        .stream()
-        .map(a -> a.toString().toUpperCase())
-        .collect(Collectors.toSet());
-
-    return isAccessible(Resource.CONSUMER, context.getConsumerGroup(), user, context, requiredActions);
-  }
-
   public Mono<Boolean> isConsumerGroupAccessible(String groupId, String clusterName) {
     if (!rbacEnabled) {
       return Mono.just(true);
@@ -311,29 +173,10 @@ public class AccessControlService {
     AccessContext accessContext = AccessContext
         .builder()
         .cluster(clusterName)
-        .consumerGroup(groupId)
-        .consumerGroupActions(ConsumerGroupAction.VIEW)
+        .consumerGroupActions(groupId, ConsumerGroupAction.VIEW)
         .build();
 
-    return getUser().map(u -> isConsumerGroupAccessible(accessContext, u));
-  }
-
-  public boolean isSchemaAccessible(AccessContext context, AuthenticatedUser user) {
-    if (!rbacEnabled) {
-      return true;
-    }
-
-    if (context.getSchema() == null && context.getSchemaActions().isEmpty()) {
-      return true;
-    }
-    Assert.isTrue(!context.getSchemaActions().isEmpty(), "actions are empty");
-
-    Set<String> requiredActions = context.getSchemaActions()
-        .stream()
-        .map(a -> a.toString().toUpperCase())
-        .collect(Collectors.toSet());
-
-    return isAccessible(Resource.SCHEMA, context.getSchema(), user, context, requiredActions);
+    return getUser().map(u -> validate(u, accessContext));
   }
 
   public Mono<Boolean> isSchemaAccessible(String schema, String clusterName) {
@@ -344,36 +187,16 @@ public class AccessControlService {
     AccessContext accessContext = AccessContext
         .builder()
         .cluster(clusterName)
-        .schema(schema)
-        .schemaActions(SchemaAction.VIEW)
+        .schemaActions(schema, SchemaAction.VIEW)
         .build();
 
-    return getUser().map(u -> isSchemaAccessible(accessContext, u));
-  }
-
-  public boolean isConnectAccessible(AccessContext context, AuthenticatedUser user) {
-    if (!rbacEnabled) {
-      return true;
-    }
-
-    if (context.getConnect() == null && context.getConnectActions().isEmpty()) {
-      return true;
-    }
-    Assert.isTrue(!context.getConnectActions().isEmpty(), "actions are empty");
-
-    Set<String> requiredActions = context.getConnectActions()
-        .stream()
-        .map(a -> a.toString().toUpperCase())
-        .collect(Collectors.toSet());
-
-    return isAccessible(Resource.CONNECT, context.getConnect(), user, context, requiredActions);
+    return getUser().map(u -> validate(u, accessContext));
   }
 
   public Mono<Boolean> isConnectAccessible(ConnectDTO dto, String clusterName) {
     if (!rbacEnabled) {
       return Mono.just(true);
     }
-
     return isConnectAccessible(dto.getName(), clusterName);
   }
 
@@ -385,86 +208,10 @@ public class AccessControlService {
     AccessContext accessContext = AccessContext
         .builder()
         .cluster(clusterName)
-        .connect(connectName)
-        .connectActions(ConnectAction.VIEW)
+        .connectActions(connectName, ConnectAction.VIEW)
         .build();
 
-    return getUser().map(u -> isConnectAccessible(accessContext, u));
-  }
-
-  public boolean isConnectorAccessible(AccessContext context, AuthenticatedUser user) {
-    if (!rbacEnabled) {
-      return true;
-    }
-
-    return isConnectAccessible(context, user);
-  }
-
-  public Mono<Boolean> isConnectorAccessible(String connectName, String connectorName, String clusterName) {
-    if (!rbacEnabled) {
-      return Mono.just(true);
-    }
-
-    AccessContext accessContext = AccessContext
-        .builder()
-        .cluster(clusterName)
-        .connect(connectName)
-        .connectActions(ConnectAction.VIEW)
-        .connector(connectorName)
-        .build();
-
-    return getUser().map(u -> isConnectorAccessible(accessContext, u));
-  }
-
-  private boolean isKsqlAccessible(AccessContext context, AuthenticatedUser user) {
-    if (!rbacEnabled) {
-      return true;
-    }
-
-    if (context.getKsqlActions().isEmpty()) {
-      return true;
-    }
-
-    Set<String> requiredActions = context.getKsqlActions()
-        .stream()
-        .map(a -> a.toString().toUpperCase())
-        .collect(Collectors.toSet());
-
-    return isAccessible(Resource.KSQL, null, user, context, requiredActions);
-  }
-
-  private boolean isAclAccessible(AccessContext context, AuthenticatedUser user) {
-    if (!rbacEnabled) {
-      return true;
-    }
-
-    if (context.getAclActions().isEmpty()) {
-      return true;
-    }
-
-    Set<String> requiredActions = context.getAclActions()
-        .stream()
-        .map(a -> a.toString().toUpperCase())
-        .collect(Collectors.toSet());
-
-    return isAccessible(Resource.ACL, null, user, context, requiredActions);
-  }
-
-  private boolean isAuditAccessible(AccessContext context, AuthenticatedUser user) {
-    if (!rbacEnabled) {
-      return true;
-    }
-
-    if (context.getAuditAction().isEmpty()) {
-      return true;
-    }
-
-    Set<String> requiredActions = context.getAuditAction()
-        .stream()
-        .map(a -> a.toString().toUpperCase())
-        .collect(Collectors.toSet());
-
-    return isAccessible(Resource.AUDIT, null, user, context, requiredActions);
+    return getUser().map(u -> validate(u, accessContext));
   }
 
   public Set<ProviderAuthorityExtractor> getOauthExtractors() {
@@ -478,57 +225,10 @@ public class AccessControlService {
     return Collections.unmodifiableList(properties.getRoles());
   }
 
-  private boolean isAccessible(Resource resource, @Nullable String resourceValue,
-                               AuthenticatedUser user, AccessContext context, Set<String> requiredActions) {
-    Set<String> grantedActions = properties.getRoles()
-        .stream()
-        .filter(filterRole(user))
-        .filter(filterCluster(resource, context.getCluster()))
-        .flatMap(grantedRole -> grantedRole.getPermissions().stream())
-        .filter(filterResource(resource))
-        .filter(filterResourceValue(resourceValue))
-        .flatMap(grantedPermission -> grantedPermission.getActions().stream())
-        .map(String::toUpperCase)
-        .collect(Collectors.toSet());
-
-    return grantedActions.containsAll(requiredActions);
-  }
-
   private Predicate<Role> filterRole(AuthenticatedUser user) {
     return role -> user.groups().contains(role.getName());
   }
 
-  private Predicate<Role> filterCluster(String cluster) {
-    return grantedRole -> grantedRole.getClusters()
-        .stream()
-        .anyMatch(cluster::equalsIgnoreCase);
-  }
-
-  private Predicate<Role> filterCluster(Resource resource, String cluster) {
-    if (resource == APPLICATIONCONFIG) {
-      return role -> true;
-    }
-    return filterCluster(cluster);
-  }
-
-  private Predicate<Permission> filterResource(Resource resource) {
-    return grantedPermission -> resource == grantedPermission.getResource();
-  }
-
-  private Predicate<Permission> filterResourceValue(@Nullable String resourceValue) {
-
-    if (resourceValue == null) {
-      return grantedPermission -> true;
-    }
-    return grantedPermission -> {
-      Pattern valuePattern = grantedPermission.getCompiledValuePattern();
-      if (valuePattern == null) {
-        return true;
-      }
-      return valuePattern.matcher(resourceValue).matches();
-    };
-  }
-
   public boolean isRbacEnabled() {
     return rbacEnabled;
   }

+ 8 - 8
kafka-ui-api/src/test/java/com/provectus/kafka/ui/service/audit/AuditWriterTest.java

@@ -44,17 +44,17 @@ class AuditWriterTest {
 
     static Stream<AccessContext> onlyLogsWhenAlterOperationIsPresentForOneOfResources() {
       Stream<UnaryOperator<AccessContextBuilder>> topicEditActions =
-          TopicAction.ALTER_ACTIONS.stream().map(a -> c -> c.topic("test").topicActions(a));
+          TopicAction.ALTER_ACTIONS.stream().map(a -> c -> c.topicActions("test", a));
       Stream<UnaryOperator<AccessContextBuilder>> clusterConfigEditActions =
           ClusterConfigAction.ALTER_ACTIONS.stream().map(a -> c -> c.clusterConfigActions(a));
       Stream<UnaryOperator<AccessContextBuilder>> aclEditActions =
           AclAction.ALTER_ACTIONS.stream().map(a -> c -> c.aclActions(a));
       Stream<UnaryOperator<AccessContextBuilder>> cgEditActions =
-          ConsumerGroupAction.ALTER_ACTIONS.stream().map(a -> c -> c.consumerGroup("cg").consumerGroupActions(a));
+          ConsumerGroupAction.ALTER_ACTIONS.stream().map(a -> c -> c.consumerGroupActions("cg", a));
       Stream<UnaryOperator<AccessContextBuilder>> schemaEditActions =
-          SchemaAction.ALTER_ACTIONS.stream().map(a -> c -> c.schema("sc").schemaActions(a));
+          SchemaAction.ALTER_ACTIONS.stream().map(a -> c -> c.schemaActions("sc", a));
       Stream<UnaryOperator<AccessContextBuilder>> connEditActions =
-          ConnectAction.ALTER_ACTIONS.stream().map(a -> c -> c.connect("conn").connectActions(a));
+          ConnectAction.ALTER_ACTIONS.stream().map(a -> c -> c.connectActions("conn", a));
       return Stream.of(
               topicEditActions, clusterConfigEditActions, aclEditActions,
               cgEditActions, connEditActions, schemaEditActions
@@ -73,12 +73,12 @@ class AuditWriterTest {
 
     static Stream<AccessContext> doesNothingIfNoResourceHasAlterAction() {
       return Stream.<UnaryOperator<AccessContextBuilder>>of(
-          c -> c.topic("test").topicActions(TopicAction.VIEW),
+          c -> c.topicActions("test", TopicAction.VIEW),
           c -> c.clusterConfigActions(ClusterConfigAction.VIEW),
           c -> c.aclActions(AclAction.VIEW),
-          c -> c.consumerGroup("cg").consumerGroupActions(ConsumerGroupAction.VIEW),
-          c -> c.schema("sc").schemaActions(SchemaAction.VIEW),
-          c -> c.connect("conn").connectActions(ConnectAction.VIEW)
+          c -> c.consumerGroupActions("cg", ConsumerGroupAction.VIEW),
+          c -> c.schemaActions("sc", SchemaAction.VIEW),
+          c -> c.connectActions("conn", ConnectAction.VIEW)
       ).map(setter -> setter.apply(AccessContext.builder().cluster("test").operationName("test")).build());
     }
   }