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 Topic list API #410

Merged
merged 5 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 @@ -49,14 +49,15 @@ public class TopicController extends NamespacedResourceController {
ResourceQuotaService resourceQuotaService;

/**
* List topics by namespace.
* List topics by namespace, filtered by name parameter.
*
* @param namespace The namespace
* @param name The name parameter
* @return A list of topics
*/
@Get
public List<Topic> list(String namespace) {
return topicService.findAllForNamespace(getNamespace(namespace));
public List<Topic> list(String namespace, @QueryValue(defaultValue = "*") String name) {
return topicService.findByWildcardName(getNamespace(namespace), name);
}

/**
Expand All @@ -65,9 +66,11 @@ public List<Topic> list(String namespace) {
* @param namespace The name
* @param topic The topic name
* @return The topic
* @deprecated use list(String, String name) instead.
*/
@Get("/{topic}")
public Optional<Topic> getTopic(String namespace, String topic) {
@Deprecated(since = "1.12.0")
public Optional<Topic> get(String namespace, String topic) {
return topicService.findByName(getNamespace(namespace), topic);
}

Expand Down Expand Up @@ -156,8 +159,8 @@ public HttpResponse<Topic> apply(String namespace, @Valid @Body Topic topic,
*/
@Status(HttpStatus.NO_CONTENT)
@Delete("/{topic}{?dryrun}")
public HttpResponse<Void> deleteTopic(String namespace, String topic,
@QueryValue(defaultValue = "false") boolean dryrun)
public HttpResponse<Void> delete(String namespace, String topic,
@QueryValue(defaultValue = "false") boolean dryrun)
throws InterruptedException, ExecutionException, TimeoutException {
Namespace ns = getNamespace(namespace);
if (!topicService.isNamespaceOwnerOfTopic(namespace, topic)) {
Expand Down
36 changes: 35 additions & 1 deletion src/main/java/com/michelin/ns4kafka/service/AclService.java
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,24 @@ public List<AccessControlEntry> findAllGrantedToNamespace(Namespace namespace) {
.toList();
}

/**
* Find all owner-ACLs on a resource for a given namespace.
*
* @param namespace The namespace
* @param resourceType The resource
* @return A list of ACLs
*/
public List<AccessControlEntry> findResourceOwnerGrantedToNamespace(Namespace namespace,
AccessControlEntry.ResourceType resourceType) {
return accessControlEntryRepository.findAll()
.stream()
.filter(accessControlEntry ->
accessControlEntry.getSpec().getGrantedTo().equals(namespace.getMetadata().getName())
&& accessControlEntry.getSpec().getPermission() == AccessControlEntry.Permission.OWNER
&& accessControlEntry.getSpec().getResourceType() == resourceType)
.toList();
}

/**
* Find all public granted ACLs.
*
Expand Down Expand Up @@ -340,4 +358,20 @@ public boolean isNamespaceOwnerOfResource(String namespace, AccessControlEntry.R
public Optional<AccessControlEntry> findByName(String namespace, String name) {
return accessControlEntryRepository.findByName(namespace, name);
}
}

/**
* Check if there is any ACL concerning the given resource.
*
* @param acls The OWNER ACL list on resource
* @param resourceName The resource name to check ACL against
* @return true if there is any OWNER ACL concerning the given resource, false otherwise
*/
public boolean isAnyAclOfResource(List<AccessControlEntry> acls, String resourceName) {
return acls
.stream()
.anyMatch(acl -> switch (acl.getSpec().getResourcePatternType()) {
case PREFIXED -> resourceName.startsWith(acl.getSpec().getResource());
case LITERAL -> resourceName.equals(acl.getSpec().getResource());
});
}
}
35 changes: 19 additions & 16 deletions src/main/java/com/michelin/ns4kafka/service/TopicService.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import com.michelin.ns4kafka.property.ManagedClusterProperties;
import com.michelin.ns4kafka.repository.TopicRepository;
import com.michelin.ns4kafka.service.executor.TopicAsyncExecutor;
import com.michelin.ns4kafka.util.RegexUtils;
import io.micronaut.context.ApplicationContext;
import io.micronaut.inject.qualifiers.Qualifiers;
import jakarta.inject.Inject;
Expand Down Expand Up @@ -63,24 +64,26 @@ public List<Topic> findAll() {
* @return A list of topics
*/
public List<Topic> findAllForNamespace(Namespace namespace) {
List<AccessControlEntry> acls = aclService.findAllGrantedToNamespace(namespace);
List<AccessControlEntry> acls = aclService
.findResourceOwnerGrantedToNamespace(namespace, AccessControlEntry.ResourceType.TOPIC);
return topicRepository.findAllForCluster(namespace.getMetadata().getCluster())
.stream()
.filter(topic -> acls.stream().anyMatch(accessControlEntry -> {
//need to check accessControlEntry.Permission, we want OWNER
if (accessControlEntry.getSpec().getPermission() != AccessControlEntry.Permission.OWNER) {
return false;
}
if (accessControlEntry.getSpec().getResourceType() == AccessControlEntry.ResourceType.TOPIC) {
return switch (accessControlEntry.getSpec().getResourcePatternType()) {
case PREFIXED ->
topic.getMetadata().getName().startsWith(accessControlEntry.getSpec().getResource());
case LITERAL ->
topic.getMetadata().getName().equals(accessControlEntry.getSpec().getResource());
};
}
return false;
}))
.filter(topic -> aclService.isAnyAclOfResource(acls, topic.getMetadata().getName()))
.toList();
}

/**
* Find all topics of a given namespace, filtered by name parameter.
*
* @param namespace The namespace
* @param name The name filter
* @return A list of topics
*/
public List<Topic> findByWildcardName(Namespace namespace, String name) {
List<String> nameFilterPatterns = RegexUtils.wildcardStringsToRegexPatterns(List.of(name));
return findAllForNamespace(namespace)
.stream()
.filter(topic -> RegexUtils.filterByPattern(topic.getMetadata().getName(), nameFilterPatterns))
.toList();
}

Expand Down
Loading