diff --git a/src/main/java/com/michelin/ns4kafka/controller/RoleBindingController.java b/src/main/java/com/michelin/ns4kafka/controller/RoleBindingController.java index 75866519..38abe5bb 100644 --- a/src/main/java/com/michelin/ns4kafka/controller/RoleBindingController.java +++ b/src/main/java/com/michelin/ns4kafka/controller/RoleBindingController.java @@ -35,14 +35,15 @@ public class RoleBindingController extends NamespacedResourceController { RoleBindingService roleBindingService; /** - * List role bindings by namespace. + * List role bindings by namespace, filtered by name parameter. * * @param namespace The namespace + * @param name The name parameter * @return A list of role bindings */ @Get - public List list(String namespace) { - return roleBindingService.list(namespace); + public List list(String namespace, @QueryValue(defaultValue = "*") String name) { + return roleBindingService.findByWildcardName(namespace, name); } /** @@ -51,8 +52,10 @@ public List list(String namespace) { * @param namespace The namespace * @param name The role binding name * @return A role binding + * @deprecated use list(String, String name) instead. */ @Get("/{name}") + @Deprecated(since = "1.12.0") public Optional get(String namespace, String name) { return roleBindingService.findByName(namespace, name); } diff --git a/src/main/java/com/michelin/ns4kafka/security/auth/AuthenticationService.java b/src/main/java/com/michelin/ns4kafka/security/auth/AuthenticationService.java index 4fb6e512..4417d6e6 100644 --- a/src/main/java/com/michelin/ns4kafka/security/auth/AuthenticationService.java +++ b/src/main/java/com/michelin/ns4kafka/security/auth/AuthenticationService.java @@ -41,7 +41,7 @@ public class AuthenticationService { * @return An authentication response with the user details */ public AuthenticationResponse buildAuthJwtGroups(String username, List groups) { - List roleBindings = roleBindingService.listByGroups(groups); + List roleBindings = roleBindingService.findAllByGroups(groups); if (roleBindings.isEmpty() && !groups.contains(securityProperties.getAdminGroup())) { log.debug("Error during authentication: user groups not found in any namespace"); throw new AuthenticationException(new AuthenticationFailed("No namespace matches your groups")); diff --git a/src/main/java/com/michelin/ns4kafka/service/NamespaceService.java b/src/main/java/com/michelin/ns4kafka/service/NamespaceService.java index eb0cf592..76b3a8bd 100644 --- a/src/main/java/com/michelin/ns4kafka/service/NamespaceService.java +++ b/src/main/java/com/michelin/ns4kafka/service/NamespaceService.java @@ -170,7 +170,7 @@ public List listAllNamespaceResources(Namespace namespace) { .map(ace -> ACCESS_CONTROL_ENTRY + "/" + ace.getMetadata().getName()), resourceQuotaService.findByNamespace(namespace.getMetadata().getName()).stream() .map(resourceQuota -> RESOURCE_QUOTA + "/" + resourceQuota.getMetadata().getName()), - roleBindingService.list(namespace.getMetadata().getName()).stream() + roleBindingService.findAllForNamespace(namespace.getMetadata().getName()).stream() .map(roleBinding -> ROLE_BINDING + "/" + roleBinding.getMetadata().getName()) ) .reduce(Stream::concat) diff --git a/src/main/java/com/michelin/ns4kafka/service/RoleBindingService.java b/src/main/java/com/michelin/ns4kafka/service/RoleBindingService.java index ffec1887..7e3423fe 100644 --- a/src/main/java/com/michelin/ns4kafka/service/RoleBindingService.java +++ b/src/main/java/com/michelin/ns4kafka/service/RoleBindingService.java @@ -2,6 +2,7 @@ import com.michelin.ns4kafka.model.RoleBinding; import com.michelin.ns4kafka.repository.RoleBindingRepository; +import com.michelin.ns4kafka.util.RegexUtils; import jakarta.inject.Inject; import jakarta.inject.Singleton; import java.util.Collection; @@ -17,31 +18,28 @@ public class RoleBindingService { RoleBindingRepository roleBindingRepository; /** - * Delete a role binding. - * - * @param roleBinding The role binding to delete - */ - public void delete(RoleBinding roleBinding) { - roleBindingRepository.delete(roleBinding); - } - - /** - * Create a role binding. + * List role bindings of a given namespace. * - * @param roleBinding The role binding to create + * @param namespace The namespace used to research + * @return The list of associated role bindings */ - public void create(RoleBinding roleBinding) { - roleBindingRepository.create(roleBinding); + public List findAllForNamespace(String namespace) { + return roleBindingRepository.findAllForNamespace(namespace); } /** - * List role bindings by namespace. + * List role bindings of a given namespace, filtered by name parameter. * * @param namespace The namespace used to research + * @param name The name filter * @return The list of associated role bindings */ - public List list(String namespace) { - return roleBindingRepository.findAllForNamespace(namespace); + public List findByWildcardName(String namespace, String name) { + List nameFilterPatterns = RegexUtils.wildcardStringsToRegexPatterns(List.of(name)); + return findAllForNamespace(namespace) + .stream() + .filter(rb -> RegexUtils.filterByPattern(rb.getMetadata().getName(), nameFilterPatterns)) + .toList(); } /** @@ -50,7 +48,7 @@ public List list(String namespace) { * @param groups The groups used to research * @return The list of associated role bindings */ - public List listByGroups(Collection groups) { + public List findAllByGroups(Collection groups) { return roleBindingRepository.findAllForGroups(groups); } @@ -62,9 +60,27 @@ public List listByGroups(Collection groups) { * @return The researched role binding */ public Optional findByName(String namespace, String name) { - return list(namespace) + return findAllForNamespace(namespace) .stream() .filter(t -> t.getMetadata().getName().equals(name)) .findFirst(); } + + /** + * Delete a role binding. + * + * @param roleBinding The role binding to delete + */ + public void delete(RoleBinding roleBinding) { + roleBindingRepository.delete(roleBinding); + } + + /** + * Create a role binding. + * + * @param roleBinding The role binding to create + */ + public void create(RoleBinding roleBinding) { + roleBindingRepository.create(roleBinding); + } } diff --git a/src/test/java/com/michelin/ns4kafka/controller/RoleBindingControllerTest.java b/src/test/java/com/michelin/ns4kafka/controller/RoleBindingControllerTest.java index 110023b3..d74449db 100644 --- a/src/test/java/com/michelin/ns4kafka/controller/RoleBindingControllerTest.java +++ b/src/test/java/com/michelin/ns4kafka/controller/RoleBindingControllerTest.java @@ -17,6 +17,7 @@ import com.michelin.ns4kafka.service.RoleBindingService; import io.micronaut.context.event.ApplicationEventPublisher; import io.micronaut.security.utils.SecurityService; +import java.util.List; import java.util.Map; import java.util.Optional; import org.junit.jupiter.api.Test; @@ -44,22 +45,26 @@ class RoleBindingControllerTest { RoleBindingController roleBindingController; @Test - void applySuccess() { + void shouldCreateRoleBinding() { Namespace ns = Namespace.builder() .metadata(Metadata.builder() .name("test") .cluster("local") .build()) .build(); + RoleBinding rolebinding = RoleBinding.builder() .metadata(Metadata.builder() .name("test.rolebinding") .build()) .build(); - when(namespaceService.findByName(any())).thenReturn(Optional.of(ns)); - when(securityService.username()).thenReturn(Optional.of("test-user")); - when(securityService.hasRole(ResourceBasedSecurityRule.IS_ADMIN)).thenReturn(false); + when(namespaceService.findByName(any())) + .thenReturn(Optional.of(ns)); + when(securityService.username()) + .thenReturn(Optional.of("test-user")); + when(securityService.hasRole(ResourceBasedSecurityRule.IS_ADMIN)) + .thenReturn(false); doNothing().when(applicationEventPublisher).publishEvent(any()); var response = roleBindingController.apply("test", rolebinding, false); @@ -69,20 +74,22 @@ void applySuccess() { } @Test - void applySuccess_AlreadyExists() { + void shouldNotCreateRoleBindingWhenAlreadyExists() { Namespace ns = Namespace.builder() .metadata(Metadata.builder() .name("test") .cluster("local") .build()) .build(); + RoleBinding rolebinding = RoleBinding.builder() .metadata(Metadata.builder() .name("test.rolebinding") .build()) .build(); - when(namespaceService.findByName(any())).thenReturn(Optional.of(ns)); + when(namespaceService.findByName(any())) + .thenReturn(Optional.of(ns)); when(roleBindingService.findByName("test", "test.rolebinding")) .thenReturn(Optional.of(rolebinding)); @@ -94,18 +101,20 @@ void applySuccess_AlreadyExists() { } @Test - void applySuccess_Changed() { + void shouldChangeRoleBinding() { Namespace ns = Namespace.builder() .metadata(Metadata.builder() .name("test") .cluster("local") .build()) .build(); + RoleBinding rolebinding = RoleBinding.builder() .metadata(Metadata.builder() .name("test.rolebinding") .build()) .build(); + RoleBinding rolebindingOld = RoleBinding.builder() .metadata(Metadata.builder() .name("test.rolebinding") @@ -113,11 +122,14 @@ void applySuccess_Changed() { .build()) .build(); - when(namespaceService.findByName(any())).thenReturn(Optional.of(ns)); + when(namespaceService.findByName(any())) + .thenReturn(Optional.of(ns)); when(roleBindingService.findByName("test", "test.rolebinding")) .thenReturn(Optional.of(rolebindingOld)); - when(securityService.username()).thenReturn(Optional.of("test-user")); - when(securityService.hasRole(ResourceBasedSecurityRule.IS_ADMIN)).thenReturn(false); + when(securityService.username()) + .thenReturn(Optional.of("test-user")); + when(securityService.hasRole(ResourceBasedSecurityRule.IS_ADMIN)) + .thenReturn(false); doNothing().when(applicationEventPublisher).publishEvent(any()); var response = roleBindingController.apply("test", rolebinding, false); @@ -127,46 +139,42 @@ void applySuccess_Changed() { } @Test - void createDryRun() { + void shouldCreateRoleBindingInDryRunMode() { Namespace ns = Namespace.builder() .metadata(Metadata.builder() .name("test") .cluster("local") .build()) .build(); + RoleBinding rolebinding = RoleBinding.builder() .metadata(Metadata.builder() .name("test.rolebinding") .build()) .build(); - when(namespaceService.findByName(any())).thenReturn(Optional.of(ns)); + when(namespaceService.findByName(any())) + .thenReturn(Optional.of(ns)); var response = roleBindingController.apply("test", rolebinding, true); - RoleBinding actual = response.body(); assertEquals("created", response.header("X-Ns4kafka-Result")); verify(roleBindingService, never()).create(rolebinding); } @Test - void deleteSucess() { - - Namespace ns = Namespace.builder() - .metadata(Metadata.builder() - .name("test") - .cluster("local") - .build()) - .build(); + void shouldDeleteRoleBinding() { RoleBinding rolebinding = RoleBinding.builder() .metadata(Metadata.builder() .name("test.rolebinding") .build()) .build(); - //when(namespaceService.findByName(any())).thenReturn(Optional.of(ns)); - when(roleBindingService.findByName(any(), any())).thenReturn(Optional.of(rolebinding)); - when(securityService.username()).thenReturn(Optional.of("test-user")); - when(securityService.hasRole(ResourceBasedSecurityRule.IS_ADMIN)).thenReturn(false); + when(roleBindingService.findByName(any(), any())) + .thenReturn(Optional.of(rolebinding)); + when(securityService.username()) + .thenReturn(Optional.of("test-user")); + when(securityService.hasRole(ResourceBasedSecurityRule.IS_ADMIN)) + .thenReturn(false); doNothing().when(applicationEventPublisher).publishEvent(any()); assertDoesNotThrow( @@ -175,16 +183,54 @@ void deleteSucess() { } @Test - void deleteSuccessDryRun() { + void shouldDeleteRoleBindingInDryRunMode() { RoleBinding rolebinding = RoleBinding.builder() .metadata(Metadata.builder() .name("test.rolebinding") .build()) .build(); - when(roleBindingService.findByName(any(), any())).thenReturn(Optional.of(rolebinding)); + when(roleBindingService.findByName(any(), any())) + .thenReturn(Optional.of(rolebinding)); roleBindingController.delete("test", "test.rolebinding", true); verify(roleBindingService, never()).delete(any()); } + + @Test + void shouldListRoleBindingsWithNameParameter() { + RoleBinding rb1 = RoleBinding.builder() + .metadata(Metadata.builder() + .name("namespace-rb1") + .build()) + .build(); + + when(roleBindingService.findByWildcardName("test", "namespace-rb1")) + .thenReturn(List.of(rb1)); + + assertEquals(List.of(rb1), roleBindingController.list("test", "namespace-rb1")); + } + + @Test + void shouldListRoleBindingsWithEmptyNameParameter() { + RoleBinding rb1 = RoleBinding.builder() + .metadata(Metadata.builder() + .name("namespace-rb1") + .build()) + .build(); + + RoleBinding rb2 = RoleBinding.builder() + .metadata(Metadata.builder() + .name("namespace-rb2") + .build()) + .build(); + + when(roleBindingService.findByWildcardName("test", "*")) + .thenReturn(List.of(rb1, rb2)); + when(roleBindingService.findByWildcardName("test", "")) + .thenReturn(List.of(rb1, rb2)); + + assertEquals(List.of(rb1, rb2), roleBindingController.list("test", "*")); + assertEquals(List.of(rb1, rb2), roleBindingController.list("test", "")); + } } diff --git a/src/test/java/com/michelin/ns4kafka/security/auth/AuthenticationServiceTest.java b/src/test/java/com/michelin/ns4kafka/security/auth/AuthenticationServiceTest.java index 1dd24e1f..82cd04ad 100644 --- a/src/test/java/com/michelin/ns4kafka/security/auth/AuthenticationServiceTest.java +++ b/src/test/java/com/michelin/ns4kafka/security/auth/AuthenticationServiceTest.java @@ -41,7 +41,7 @@ class AuthenticationServiceTest { @Test void shouldThrowErrorWhenNoRoleBindingAndNotAdmin() { - when(roleBindingService.listByGroups(any())) + when(roleBindingService.findAllByGroups(any())) .thenReturn(Collections.emptyList()); when(securityProperties.getAdminGroup()) @@ -57,7 +57,7 @@ void shouldThrowErrorWhenNoRoleBindingAndNotAdmin() { @Test void shouldReturnAuthenticationSuccessWhenAdminNoGroup() { - when(roleBindingService.listByGroups(any())) + when(roleBindingService.findAllByGroups(any())) .thenReturn(Collections.emptyList()); when(securityProperties.getAdminGroup()) @@ -96,7 +96,7 @@ void shouldReturnAuthenticationSuccessWhenAdminWithGroups() { .build()) .build(); - when(roleBindingService.listByGroups(any())) + when(roleBindingService.findAllByGroups(any())) .thenReturn(List.of(roleBinding)); when(resourceBasedSecurityRule.computeRolesFromGroups(any())) @@ -144,7 +144,7 @@ void shouldReturnAuthenticationSuccessWhenUserWithGroups() { .build()) .build(); - when(roleBindingService.listByGroups(any())) + when(roleBindingService.findAllByGroups(any())) .thenReturn(List.of(roleBinding)); when(resourceBasedSecurityRule.computeRolesFromGroups(any())) diff --git a/src/test/java/com/michelin/ns4kafka/service/NamespaceServiceTest.java b/src/test/java/com/michelin/ns4kafka/service/NamespaceServiceTest.java index 195a4dfe..f493ed0f 100644 --- a/src/test/java/com/michelin/ns4kafka/service/NamespaceServiceTest.java +++ b/src/test/java/com/michelin/ns4kafka/service/NamespaceServiceTest.java @@ -372,7 +372,7 @@ void listAllNamespaceResourcesEmpty() { .thenReturn(List.of()); when(connectorService.findAllForNamespace(ns)) .thenReturn(List.of()); - when(roleBindingService.list("namespace")) + when(roleBindingService.findAllForNamespace("namespace")) .thenReturn(List.of()); when(aclService.findAllForNamespace(ns)) .thenReturn(List.of()); @@ -409,7 +409,7 @@ void listAllNamespaceResourcesTopic() { .thenReturn(List.of(topic)); when(connectorService.findAllForNamespace(ns)) .thenReturn(List.of()); - when(roleBindingService.list("namespace")) + when(roleBindingService.findAllForNamespace("namespace")) .thenReturn(List.of()); when(aclService.findAllForNamespace(ns)) .thenReturn(List.of()); @@ -447,7 +447,7 @@ void listAllNamespaceResourcesConnect() { .thenReturn(List.of()); when(connectorService.findAllForNamespace(ns)) .thenReturn(List.of(connector)); - when(roleBindingService.list("namespace")) + when(roleBindingService.findAllForNamespace("namespace")) .thenReturn(List.of()); when(aclService.findAllForNamespace(ns)) .thenReturn(List.of()); @@ -485,7 +485,7 @@ void listAllNamespaceResourcesRoleBinding() { .thenReturn(List.of()); when(connectorService.findAllForNamespace(ns)) .thenReturn(List.of()); - when(roleBindingService.list("namespace")) + when(roleBindingService.findAllForNamespace("namespace")) .thenReturn(List.of(rb)); when(aclService.findAllForNamespace(ns)) .thenReturn(List.of()); @@ -523,7 +523,7 @@ void listAllNamespaceResourcesAccessControlEntry() { .thenReturn(List.of()); when(connectorService.findAllForNamespace(ns)) .thenReturn(List.of()); - when(roleBindingService.list("namespace")) + when(roleBindingService.findAllForNamespace("namespace")) .thenReturn(List.of()); when(aclService.findAllForNamespace(ns)) .thenReturn(List.of(ace)); @@ -561,7 +561,7 @@ void listAllNamespaceResourcesConnectCluster() { .thenReturn(List.of()); when(connectorService.findAllForNamespace(ns)) .thenReturn(List.of()); - when(roleBindingService.list("namespace")) + when(roleBindingService.findAllForNamespace("namespace")) .thenReturn(List.of()); when(aclService.findAllForNamespace(ns)) .thenReturn(List.of()); @@ -599,7 +599,7 @@ void listAllNamespaceResourcesQuota() { .thenReturn(List.of()); when(connectorService.findAllForNamespace(ns)) .thenReturn(List.of()); - when(roleBindingService.list("namespace")) + when(roleBindingService.findAllForNamespace("namespace")) .thenReturn(List.of()); when(aclService.findAllForNamespace(ns)) .thenReturn(List.of()); diff --git a/src/test/java/com/michelin/ns4kafka/service/RoleBindingServiceTest.java b/src/test/java/com/michelin/ns4kafka/service/RoleBindingServiceTest.java index d0b9791e..83938c9e 100644 --- a/src/test/java/com/michelin/ns4kafka/service/RoleBindingServiceTest.java +++ b/src/test/java/com/michelin/ns4kafka/service/RoleBindingServiceTest.java @@ -22,19 +22,21 @@ class RoleBindingServiceTest { RoleBindingService roleBindingService; @Test - void findByName() { + void shouldFindByName() { RoleBinding rb1 = RoleBinding.builder() .metadata(Metadata.builder() .name("namespace-rb1") .cluster("local") .build()) .build(); + RoleBinding rb2 = RoleBinding.builder() .metadata(Metadata.builder() .name("namespace-rb2") .cluster("local") .build()) .build(); + RoleBinding rb3 = RoleBinding.builder() .metadata(Metadata.builder() .name("namespace-rb3") @@ -42,9 +44,99 @@ void findByName() { .build()) .build(); - when(roleBindingRepository.findAllForNamespace("namespace")).thenReturn(List.of(rb1, rb2, rb3)); + when(roleBindingRepository.findAllForNamespace("namespace")) + .thenReturn(List.of(rb1, rb2, rb3)); var result = roleBindingService.findByName("namespace", "namespace-rb2"); - assertEquals(rb2, result.get()); + assertEquals(rb2, result.orElse(null)); + } + + @Test + void shouldListRoleBindingsWithoutParameter() { + RoleBinding rb1 = RoleBinding.builder() + .metadata(Metadata.builder() + .name("namespace-rb1") + .build()) + .build(); + + RoleBinding rb2 = RoleBinding.builder() + .metadata(Metadata.builder() + .name("namespace-rb2") + .build()) + .build(); + + RoleBinding rb3 = RoleBinding.builder() + .metadata(Metadata.builder() + .name("namespace-rb3") + .build()) + .build(); + + when(roleBindingRepository.findAllForNamespace("namespace")) + .thenReturn(List.of(rb1, rb2, rb3)); + + assertEquals(List.of(rb1, rb2, rb3), roleBindingService.findAllForNamespace("namespace")); + } + + @Test + void shouldListRoleBindingsWithNameParameter() { + RoleBinding rb1 = RoleBinding.builder() + .metadata(Metadata.builder() + .name("namespace-rb1") + .build()) + .build(); + + RoleBinding rb2 = RoleBinding.builder() + .metadata(Metadata.builder() + .name("namespace-rb2") + .build()) + .build(); + + RoleBinding rb3 = RoleBinding.builder() + .metadata(Metadata.builder() + .name("namespace-rb3") + .build()) + .build(); + + when(roleBindingRepository.findAllForNamespace("namespace")) + .thenReturn(List.of(rb1, rb2, rb3)); + + assertEquals(List.of(rb1), roleBindingService.findByWildcardName("namespace", "namespace-rb1")); + assertEquals(List.of(rb1, rb2, rb3), roleBindingService.findByWildcardName("namespace", "")); + assertEquals(List.of(), roleBindingService.findByWildcardName("namespace", "namespace-rb5")); + } + + @Test + void shouldListRoleBindingsWithWildcardNameParameter() { + RoleBinding rb1 = RoleBinding.builder() + .metadata(Metadata.builder() + .name("namespace-rb1") + .build()) + .build(); + + RoleBinding rb2 = RoleBinding.builder() + .metadata(Metadata.builder() + .name("namespace-rb2") + .build()) + .build(); + + RoleBinding rb3 = RoleBinding.builder() + .metadata(Metadata.builder() + .name("namespace-rb3") + .build()) + .build(); + + RoleBinding rb4 = RoleBinding.builder() + .metadata(Metadata.builder() + .name("rb4") + .build()) + .build(); + + when(roleBindingRepository.findAllForNamespace("namespace")) + .thenReturn(List.of(rb1, rb2, rb3, rb4)); + + assertEquals(List.of(rb1, rb2, rb3), roleBindingService.findByWildcardName("namespace", "namespace-*")); + assertEquals(List.of(rb1, rb2, rb3, rb4), roleBindingService.findByWildcardName("namespace", "*rb?")); + assertEquals(List.of(rb4), roleBindingService.findByWildcardName("namespace", "rb?")); + assertEquals(List.of(), roleBindingService.findByWildcardName("namespace", "role_binding*")); } }