iliax пре 1 година
родитељ
комит
97273698a3

+ 8 - 3
kafka-ui-api/src/main/java/com/provectus/kafka/ui/model/rbac/AccessContext.java

@@ -34,9 +34,9 @@ public class AccessContext {
     boolean isAccessible(List<Permission> userPermissions);
   }
 
-  private record SingleResourceAccess(@Nullable String name,
-                                      Resource resourceType,
-                                      Collection<PermissibleAction> requestedActions) implements ResourceAccess {
+  record SingleResourceAccess(@Nullable String name,
+                              Resource resourceType,
+                              Collection<PermissibleAction> requestedActions) implements ResourceAccess {
 
     SingleResourceAccess(Resource type, List<PermissibleAction> requestedActions) {
       this(null, type, requestedActions);
@@ -74,6 +74,11 @@ public class AccessContext {
     return new AccessContextBuilder();
   }
 
+  public boolean isAccessible(List<Permission> allUserPermissions) {
+    return getAccesses().stream()
+        .allMatch(resourceAccess -> resourceAccess.isAccessible(allUserPermissions));
+  }
+
   public static final class AccessContextBuilder {
 
     private String cluster;

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

@@ -44,13 +44,13 @@ public class Permission {
 
   public void validate() {
     Preconditions.checkNotNull(resource, "resource cannot be null");
+    Preconditions.checkArgument(isNotEmpty(actions), "Actions list for %s can't be null or empty", resource);
   }
 
   public void transform() {
     if (value != null) {
       this.compiledValuePattern = Pattern.compile(value);
     }
-    Preconditions.checkArgument(isNotEmpty(actions), "Actions list for %s can't be null or empty", resource);
     if (actions.stream().anyMatch("ALL"::equalsIgnoreCase)) {
       this.parsedActions = resource.allActions();
     } else {

+ 1 - 2
kafka-ui-api/src/main/java/com/provectus/kafka/ui/service/rbac/AccessControlService.java

@@ -114,8 +114,7 @@ public class AccessControlService {
         .flatMap(role -> role.getPermissions().stream())
         .toList();
 
-    return context.getAccesses().stream()
-        .allMatch(resourceAccess -> resourceAccess.isAccessible(allUserPermissions));
+    return context.isAccessible(allUserPermissions);
   }
 
   public Mono<AuthenticatedUser> getUser() {

+ 112 - 0
kafka-ui-api/src/test/java/com/provectus/kafka/ui/model/rbac/AccessContextTest.java

@@ -0,0 +1,112 @@
+package com.provectus.kafka.ui.model.rbac;
+
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import com.provectus.kafka.ui.model.rbac.AccessContext.ResourceAccess;
+import com.provectus.kafka.ui.model.rbac.AccessContext.SingleResourceAccess;
+import com.provectus.kafka.ui.model.rbac.permission.ClusterConfigAction;
+import com.provectus.kafka.ui.model.rbac.permission.PermissibleAction;
+import com.provectus.kafka.ui.model.rbac.permission.TopicAction;
+import jakarta.annotation.Nullable;
+import java.util.List;
+import java.util.stream.Stream;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+
+class AccessContextTest {
+
+  @Test
+  void validateReturnsTrueIfAllResourcesAreAccessible() {
+    ResourceAccess okResourceAccess1 = mock(ResourceAccess.class);
+    when(okResourceAccess1.isAccessible(any())).thenReturn(true);
+
+    ResourceAccess okResourceAccess2 = mock(ResourceAccess.class);
+    when(okResourceAccess2.isAccessible(any())).thenReturn(true);
+
+    var cxt = new AccessContext("cluster", List.of(okResourceAccess1, okResourceAccess2), "op", "params");
+    assertThat(cxt.isAccessible(List.of())).isTrue();
+  }
+
+  @Test
+  void validateReturnsFalseIfAnyResourcesCantBeAccessible() {
+    ResourceAccess okResourceAccess = mock(ResourceAccess.class);
+    when(okResourceAccess.isAccessible(any())).thenReturn(true);
+
+    ResourceAccess failureResourceAccess = mock(ResourceAccess.class);
+    when(failureResourceAccess.isAccessible(any())).thenReturn(false);
+
+    var cxt = new AccessContext("cluster", List.of(okResourceAccess, failureResourceAccess), "op", "params");
+    assertThat(cxt.isAccessible(List.of())).isFalse();
+  }
+
+
+  @Nested
+  class SingleResourceAccessTest {
+
+    @Test
+    void allowsAccessForResourceWithNameIfUserHasAllNeededPermissions() {
+      SingleResourceAccess sra =
+          new SingleResourceAccess("test_topic123", Resource.TOPIC, List.of(TopicAction.VIEW, TopicAction.EDIT));
+
+      var allowed = sra.isAccessible(
+          List.of(
+              permission(Resource.TOPIC, "test_topic.*", TopicAction.EDIT),
+              permission(Resource.TOPIC, "test.*", TopicAction.VIEW)));
+
+      assertThat(allowed).isTrue();
+    }
+
+    @Test
+    void deniesAccessForResourceWithNameIfUserHasSomePermissionsMissing() {
+      SingleResourceAccess sra =
+          new SingleResourceAccess("test_topic123", Resource.TOPIC,
+              List.of(TopicAction.VIEW, TopicAction.MESSAGES_DELETE));
+
+      var allowed = sra.isAccessible(
+          List.of(
+              permission(Resource.TOPIC, "test_topic.*", TopicAction.EDIT),
+              permission(Resource.TOPIC, "test.*", TopicAction.VIEW)));
+
+      assertThat(allowed).isFalse();
+    }
+
+    @Test
+    void allowsAccessForResourceWithoutNameIfUserHasAllNeededPermissions() {
+      SingleResourceAccess sra =
+          new SingleResourceAccess(Resource.CLUSTERCONFIG, List.of(ClusterConfigAction.VIEW));
+
+      var allowed = sra.isAccessible(
+          List.of(
+              permission(Resource.CLUSTERCONFIG, null, ClusterConfigAction.VIEW, ClusterConfigAction.EDIT)));
+
+      assertThat(allowed).isTrue();
+    }
+
+    @Test
+    void deniesAccessForResourceWithoutNameIfUserHasAllNeededPermissions() {
+      SingleResourceAccess sra =
+          new SingleResourceAccess(Resource.CLUSTERCONFIG, List.of(ClusterConfigAction.EDIT));
+
+      var allowed = sra.isAccessible(
+          List.of(
+              permission(Resource.CLUSTERCONFIG, null, ClusterConfigAction.VIEW)));
+
+      assertThat(allowed).isFalse();
+    }
+
+    private Permission permission(Resource res, @Nullable String namePattern, PermissibleAction... actions) {
+      Permission p = new Permission();
+      p.setResource(res.name());
+      p.setActions(Stream.of(actions).map(PermissibleAction::name).toList());
+      p.setValue(namePattern);
+      p.validate();
+      p.transform();
+      return p;
+    }
+  }
+
+}

+ 40 - 0
kafka-ui-api/src/test/java/com/provectus/kafka/ui/model/rbac/PermissionTest.java

@@ -0,0 +1,40 @@
+package com.provectus.kafka.ui.model.rbac;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import com.provectus.kafka.ui.model.rbac.permission.TopicAction;
+import java.util.List;
+import org.junit.jupiter.api.Test;
+
+class PermissionTest {
+
+  @Test
+  void transformSetsParseableFields() {
+    var p = new Permission();
+    p.setResource("toPic");
+    p.setActions(List.of("vIEW", "EdiT"));
+    p.setValue("patt|ern");
+
+    p.transform();
+
+    assertThat(p.getParsedActions())
+        .isEqualTo(List.of(TopicAction.VIEW, TopicAction.EDIT));
+
+    assertThat(p.getCompiledValuePattern())
+        .isNotNull()
+        .matches(pattern -> pattern.pattern().equals("patt|ern"));
+  }
+
+  @Test
+  void transformSetsFullActionsListIfAllActionPassed() {
+    var p = new Permission();
+    p.setResource("toPic");
+    p.setActions(List.of("All"));
+
+    p.transform();
+
+    assertThat(p.getParsedActions())
+        .isEqualTo(List.of(TopicAction.values()));
+  }
+
+}