Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Handle wildcard parameter in RoleBinding list API #413

Merged
merged 10 commits into from
Jul 31, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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<RoleBinding> list(String namespace) {
return roleBindingService.list(namespace);
public List<RoleBinding> list(String namespace, @QueryValue(defaultValue = "*") String name) {
return roleBindingService.findByWildcardName(namespace, name);
}

/**
Expand All @@ -51,8 +52,10 @@ public List<RoleBinding> 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<RoleBinding> get(String namespace, String name) {
return roleBindingService.findByName(namespace, name);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ public class AuthenticationService {
* @return An authentication response with the user details
*/
public AuthenticationResponse buildAuthJwtGroups(String username, List<String> groups) {
List<RoleBinding> roleBindings = roleBindingService.listByGroups(groups);
List<RoleBinding> 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"));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,7 @@ public List<String> 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)
Expand Down
52 changes: 34 additions & 18 deletions src/main/java/com/michelin/ns4kafka/service/RoleBindingService.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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<RoleBinding> 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<RoleBinding> list(String namespace) {
return roleBindingRepository.findAllForNamespace(namespace);
public List<RoleBinding> findByWildcardName(String namespace, String name) {
List<String> nameFilterPatterns = RegexUtils.wildcardStringsToRegexPatterns(List.of(name));
return findAllForNamespace(namespace)
.stream()
.filter(rb -> RegexUtils.filterByPattern(rb.getMetadata().getName(), nameFilterPatterns))
.toList();
}

/**
Expand All @@ -50,7 +48,7 @@ public List<RoleBinding> list(String namespace) {
* @param groups The groups used to research
* @return The list of associated role bindings
*/
public List<RoleBinding> listByGroups(Collection<String> groups) {
public List<RoleBinding> findAllByGroups(Collection<String> groups) {
return roleBindingRepository.findAllForGroups(groups);
}

Expand All @@ -62,9 +60,27 @@ public List<RoleBinding> listByGroups(Collection<String> groups) {
* @return The researched role binding
*/
public Optional<RoleBinding> 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);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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);
Expand All @@ -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));

Expand All @@ -94,30 +101,35 @@ 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")
.labels(Map.of("old", "label"))
.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);
Expand All @@ -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(
Expand All @@ -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", ""));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ class AuthenticationServiceTest {

@Test
void shouldThrowErrorWhenNoRoleBindingAndNotAdmin() {
when(roleBindingService.listByGroups(any()))
when(roleBindingService.findAllByGroups(any()))
.thenReturn(Collections.emptyList());

when(securityProperties.getAdminGroup())
Expand All @@ -57,7 +57,7 @@ void shouldThrowErrorWhenNoRoleBindingAndNotAdmin() {

@Test
void shouldReturnAuthenticationSuccessWhenAdminNoGroup() {
when(roleBindingService.listByGroups(any()))
when(roleBindingService.findAllByGroups(any()))
.thenReturn(Collections.emptyList());

when(securityProperties.getAdminGroup())
Expand Down Expand Up @@ -96,7 +96,7 @@ void shouldReturnAuthenticationSuccessWhenAdminWithGroups() {
.build())
.build();

when(roleBindingService.listByGroups(any()))
when(roleBindingService.findAllByGroups(any()))
.thenReturn(List.of(roleBinding));

when(resourceBasedSecurityRule.computeRolesFromGroups(any()))
Expand Down Expand Up @@ -144,7 +144,7 @@ void shouldReturnAuthenticationSuccessWhenUserWithGroups() {
.build())
.build();

when(roleBindingService.listByGroups(any()))
when(roleBindingService.findAllByGroups(any()))
.thenReturn(List.of(roleBinding));

when(resourceBasedSecurityRule.computeRolesFromGroups(any()))
Expand Down
Loading