|
@@ -8,6 +8,8 @@ import static com.provectus.kafka.ui.model.rbac.permission.TopicAction.VIEW;
|
|
|
import static java.util.stream.Collectors.toList;
|
|
|
|
|
|
import com.provectus.kafka.ui.api.TopicsApi;
|
|
|
+import com.provectus.kafka.ui.config.auth.AuthenticatedUser;
|
|
|
+import com.provectus.kafka.ui.config.auth.RbacUser;
|
|
|
import com.provectus.kafka.ui.mapper.ClusterMapper;
|
|
|
import com.provectus.kafka.ui.model.InternalTopic;
|
|
|
import com.provectus.kafka.ui.model.InternalTopicConfig;
|
|
@@ -27,19 +29,26 @@ import com.provectus.kafka.ui.model.TopicsResponseDTO;
|
|
|
import com.provectus.kafka.ui.model.rbac.AccessContext;
|
|
|
import com.provectus.kafka.ui.service.TopicsService;
|
|
|
import com.provectus.kafka.ui.service.analyze.TopicAnalysisService;
|
|
|
+import com.provectus.kafka.ui.service.audit.AuditService;
|
|
|
import com.provectus.kafka.ui.service.rbac.AccessControlService;
|
|
|
import java.util.Comparator;
|
|
|
import java.util.List;
|
|
|
+import java.util.Map;
|
|
|
+import java.util.Set;
|
|
|
import javax.validation.Valid;
|
|
|
import lombok.RequiredArgsConstructor;
|
|
|
import lombok.extern.slf4j.Slf4j;
|
|
|
import org.apache.commons.lang3.StringUtils;
|
|
|
import org.springframework.http.HttpStatus;
|
|
|
import org.springframework.http.ResponseEntity;
|
|
|
+import org.springframework.security.core.context.ReactiveSecurityContextHolder;
|
|
|
+import org.springframework.security.core.context.SecurityContext;
|
|
|
import org.springframework.web.bind.annotation.RestController;
|
|
|
import org.springframework.web.server.ServerWebExchange;
|
|
|
import reactor.core.publisher.Flux;
|
|
|
import reactor.core.publisher.Mono;
|
|
|
+import reactor.core.scheduler.Scheduler;
|
|
|
+import reactor.core.scheduler.Schedulers;
|
|
|
|
|
|
@RestController
|
|
|
@RequiredArgsConstructor
|
|
@@ -52,69 +61,76 @@ public class TopicsController extends AbstractController implements TopicsApi {
|
|
|
private final TopicAnalysisService topicAnalysisService;
|
|
|
private final ClusterMapper clusterMapper;
|
|
|
private final AccessControlService accessControlService;
|
|
|
+ private final AuditService auditService;
|
|
|
|
|
|
@Override
|
|
|
public Mono<ResponseEntity<TopicDTO>> createTopic(
|
|
|
String clusterName, @Valid Mono<TopicCreationDTO> topicCreation, ServerWebExchange exchange) {
|
|
|
|
|
|
- Mono<Void> validateAccess = accessControlService.validateAccess(AccessContext.builder()
|
|
|
+ var context = AccessContext.builder()
|
|
|
.cluster(clusterName)
|
|
|
.topicActions(CREATE)
|
|
|
- .build());
|
|
|
+ .auditOperation("createTopic")
|
|
|
+ .build();
|
|
|
|
|
|
- return validateAccess.then(
|
|
|
+ return accessControlService.validateAccess(context).then(
|
|
|
topicsService.createTopic(getCluster(clusterName), topicCreation)
|
|
|
.map(clusterMapper::toTopic)
|
|
|
.map(s -> new ResponseEntity<>(s, HttpStatus.OK))
|
|
|
.switchIfEmpty(Mono.just(ResponseEntity.notFound().build()))
|
|
|
- );
|
|
|
+ ).doOnEach(sig -> auditService.audit(context, sig));
|
|
|
}
|
|
|
|
|
|
@Override
|
|
|
public Mono<ResponseEntity<TopicDTO>> recreateTopic(String clusterName,
|
|
|
String topicName, ServerWebExchange exchange) {
|
|
|
- Mono<Void> validateAccess = accessControlService.validateAccess(AccessContext.builder()
|
|
|
+ var context = AccessContext.builder()
|
|
|
.cluster(clusterName)
|
|
|
.topic(topicName)
|
|
|
.topicActions(VIEW, CREATE, DELETE)
|
|
|
- .build());
|
|
|
+ .auditOperation("recreateTopic")
|
|
|
+ .build();
|
|
|
|
|
|
- return validateAccess.then(
|
|
|
+ return accessControlService.validateAccess(context).then(
|
|
|
topicsService.recreateTopic(getCluster(clusterName), topicName)
|
|
|
.map(clusterMapper::toTopic)
|
|
|
.map(s -> new ResponseEntity<>(s, HttpStatus.CREATED))
|
|
|
- );
|
|
|
+ ).doOnEach(sig -> auditService.audit(context, sig));
|
|
|
}
|
|
|
|
|
|
@Override
|
|
|
public Mono<ResponseEntity<TopicDTO>> cloneTopic(
|
|
|
String clusterName, String topicName, String newTopicName, ServerWebExchange exchange) {
|
|
|
|
|
|
- Mono<Void> validateAccess = accessControlService.validateAccess(AccessContext.builder()
|
|
|
+ var context = AccessContext.builder()
|
|
|
.cluster(clusterName)
|
|
|
.topic(topicName)
|
|
|
.topicActions(VIEW, CREATE)
|
|
|
- .build());
|
|
|
+ .auditOperation("cloneTopic")
|
|
|
+ .operationParams(Map.of("newTopicName", newTopicName))
|
|
|
+ .build();
|
|
|
|
|
|
- return validateAccess.then(topicsService.cloneTopic(getCluster(clusterName), topicName, newTopicName)
|
|
|
- .map(clusterMapper::toTopic)
|
|
|
- .map(s -> new ResponseEntity<>(s, HttpStatus.CREATED))
|
|
|
- );
|
|
|
+ return accessControlService.validateAccess(context)
|
|
|
+ .then(topicsService.cloneTopic(getCluster(clusterName), topicName, newTopicName)
|
|
|
+ .map(clusterMapper::toTopic)
|
|
|
+ .map(s -> new ResponseEntity<>(s, HttpStatus.CREATED))
|
|
|
+ ).doOnEach(sig -> auditService.audit(context, sig));
|
|
|
}
|
|
|
|
|
|
@Override
|
|
|
public Mono<ResponseEntity<Void>> deleteTopic(
|
|
|
String clusterName, String topicName, ServerWebExchange exchange) {
|
|
|
|
|
|
- Mono<Void> validateAccess = accessControlService.validateAccess(AccessContext.builder()
|
|
|
+ var context = AccessContext.builder()
|
|
|
.cluster(clusterName)
|
|
|
.topic(topicName)
|
|
|
.topicActions(DELETE)
|
|
|
- .build());
|
|
|
+ .auditOperation("deleteTopic")
|
|
|
+ .build();
|
|
|
|
|
|
- return validateAccess.then(
|
|
|
+ return accessControlService.validateAccess(context).then(
|
|
|
topicsService.deleteTopic(getCluster(clusterName), topicName).map(ResponseEntity::ok)
|
|
|
- );
|
|
|
+ ).doOnEach(sig -> auditService.audit(context, sig));
|
|
|
}
|
|
|
|
|
|
|
|
@@ -122,13 +138,14 @@ public class TopicsController extends AbstractController implements TopicsApi {
|
|
|
public Mono<ResponseEntity<Flux<TopicConfigDTO>>> getTopicConfigs(
|
|
|
String clusterName, String topicName, ServerWebExchange exchange) {
|
|
|
|
|
|
- Mono<Void> validateAccess = accessControlService.validateAccess(AccessContext.builder()
|
|
|
+ var context = AccessContext.builder()
|
|
|
.cluster(clusterName)
|
|
|
.topic(topicName)
|
|
|
.topicActions(VIEW)
|
|
|
- .build());
|
|
|
+ .auditOperation("getTopicConfigs")
|
|
|
+ .build();
|
|
|
|
|
|
- return validateAccess.then(
|
|
|
+ return accessControlService.validateAccess(context).then(
|
|
|
topicsService.getTopicConfigs(getCluster(clusterName), topicName)
|
|
|
.map(lst -> lst.stream()
|
|
|
.map(InternalTopicConfig::from)
|
|
@@ -136,24 +153,25 @@ public class TopicsController extends AbstractController implements TopicsApi {
|
|
|
.collect(toList()))
|
|
|
.map(Flux::fromIterable)
|
|
|
.map(ResponseEntity::ok)
|
|
|
- );
|
|
|
+ ).doOnEach(sig -> auditService.audit(context, sig));
|
|
|
}
|
|
|
|
|
|
@Override
|
|
|
public Mono<ResponseEntity<TopicDetailsDTO>> getTopicDetails(
|
|
|
String clusterName, String topicName, ServerWebExchange exchange) {
|
|
|
|
|
|
- var validatedAccess = accessControlService.withAccess(AccessContext.builder()
|
|
|
+ var context = AccessContext.builder()
|
|
|
.cluster(clusterName)
|
|
|
.topic(topicName)
|
|
|
.topicActions(VIEW)
|
|
|
- .build());
|
|
|
+ .auditOperation("getTopicDetails")
|
|
|
+ .build();
|
|
|
|
|
|
- return validatedAccess.then(
|
|
|
+ return accessControlService.validateAccess(context).then(
|
|
|
topicsService.getTopicDetails(getCluster(clusterName), topicName)
|
|
|
.map(clusterMapper::toTopicDetails)
|
|
|
.map(ResponseEntity::ok)
|
|
|
- );
|
|
|
+ ).doOnEach(sig -> auditService.audit(context, sig));
|
|
|
}
|
|
|
|
|
|
@Override
|
|
@@ -166,6 +184,11 @@ public class TopicsController extends AbstractController implements TopicsApi {
|
|
|
@Valid SortOrderDTO sortOrder,
|
|
|
ServerWebExchange exchange) {
|
|
|
|
|
|
+ AccessContext context = AccessContext.builder()
|
|
|
+ .cluster(clusterName)
|
|
|
+ .auditOperation("getTopics")
|
|
|
+ .build();
|
|
|
+
|
|
|
return topicsService.getTopicsForPagination(getCluster(clusterName))
|
|
|
.flatMap(existingTopics -> {
|
|
|
int pageSize = perPage != null && perPage > 0 ? perPage : DEFAULT_PAGE_SIZE;
|
|
@@ -196,7 +219,8 @@ public class TopicsController extends AbstractController implements TopicsApi {
|
|
|
.topics(topicsToRender.stream().map(clusterMapper::toTopic).collect(toList()))
|
|
|
.pageCount(totalPages));
|
|
|
})
|
|
|
- .map(ResponseEntity::ok);
|
|
|
+ .map(ResponseEntity::ok)
|
|
|
+ .doOnEach(sig -> auditService.audit(context, sig));
|
|
|
}
|
|
|
|
|
|
@Override
|
|
@@ -204,20 +228,19 @@ public class TopicsController extends AbstractController implements TopicsApi {
|
|
|
String clusterName, String topicName, @Valid Mono<TopicUpdateDTO> topicUpdate,
|
|
|
ServerWebExchange exchange) {
|
|
|
|
|
|
- var validatedAccess = accessControlService.withAccess(AccessContext.builder()
|
|
|
+ var context = AccessContext.builder()
|
|
|
.cluster(clusterName)
|
|
|
.topic(topicName)
|
|
|
.topicActions(VIEW, EDIT)
|
|
|
- .operationDescription("Topic update")
|
|
|
- .operationParams(topicUpdate)
|
|
|
- .build());
|
|
|
+ .auditOperation("updateTopic")
|
|
|
+ .build();
|
|
|
|
|
|
- return validatedAccess.then(
|
|
|
+ return accessControlService.validateAccess(context).then(
|
|
|
topicsService
|
|
|
.updateTopic(getCluster(clusterName), topicName, topicUpdate)
|
|
|
.map(clusterMapper::toTopic)
|
|
|
.map(ResponseEntity::ok)
|
|
|
- );
|
|
|
+ ).doOnEach(sig -> auditService.audit(context, sig));
|
|
|
}
|
|
|
|
|
|
@Override
|
|
@@ -226,17 +249,17 @@ public class TopicsController extends AbstractController implements TopicsApi {
|
|
|
Mono<PartitionsIncreaseDTO> partitionsIncrease,
|
|
|
ServerWebExchange exchange) {
|
|
|
|
|
|
- Mono<Void> validateAccess = accessControlService.validateAccess(AccessContext.builder()
|
|
|
+ var context = AccessContext.builder()
|
|
|
.cluster(clusterName)
|
|
|
.topic(topicName)
|
|
|
.topicActions(VIEW, EDIT)
|
|
|
- .build());
|
|
|
+ .build();
|
|
|
|
|
|
- return validateAccess.then(
|
|
|
+ return accessControlService.validateAccess(context).then(
|
|
|
partitionsIncrease.flatMap(partitions ->
|
|
|
topicsService.increaseTopicPartitions(getCluster(clusterName), topicName, partitions)
|
|
|
).map(ResponseEntity::ok)
|
|
|
- );
|
|
|
+ ).doOnEach(sig -> auditService.audit(context, sig));
|
|
|
}
|
|
|
|
|
|
@Override
|
|
@@ -245,31 +268,34 @@ public class TopicsController extends AbstractController implements TopicsApi {
|
|
|
Mono<ReplicationFactorChangeDTO> replicationFactorChange,
|
|
|
ServerWebExchange exchange) {
|
|
|
|
|
|
- Mono<Void> validateAccess = accessControlService.validateAccess(AccessContext.builder()
|
|
|
+ var context = AccessContext.builder()
|
|
|
.cluster(clusterName)
|
|
|
.topic(topicName)
|
|
|
.topicActions(VIEW, EDIT)
|
|
|
- .build());
|
|
|
+ .auditOperation("changeReplicationFactor")
|
|
|
+ .build();
|
|
|
|
|
|
- return validateAccess.then(
|
|
|
+ return accessControlService.validateAccess(context).then(
|
|
|
replicationFactorChange
|
|
|
.flatMap(rfc ->
|
|
|
topicsService.changeReplicationFactor(getCluster(clusterName), topicName, rfc))
|
|
|
.map(ResponseEntity::ok)
|
|
|
- );
|
|
|
+ ).doOnEach(sig -> auditService.audit(context, sig));
|
|
|
}
|
|
|
|
|
|
@Override
|
|
|
public Mono<ResponseEntity<Void>> analyzeTopic(String clusterName, String topicName, ServerWebExchange exchange) {
|
|
|
|
|
|
- Mono<Void> validateAccess = accessControlService.validateAccess(AccessContext.builder()
|
|
|
+ var context = AccessContext.builder()
|
|
|
.cluster(clusterName)
|
|
|
.topic(topicName)
|
|
|
.topicActions(MESSAGES_READ)
|
|
|
- .build());
|
|
|
+ .auditOperation("analyzeTopic")
|
|
|
+ .build();
|
|
|
|
|
|
- return validateAccess.then(
|
|
|
+ return accessControlService.validateAccess(context).then(
|
|
|
topicAnalysisService.analyze(getCluster(clusterName), topicName)
|
|
|
+ .doOnEach(sig -> auditService.audit(context, sig))
|
|
|
.thenReturn(ResponseEntity.ok().build())
|
|
|
);
|
|
|
}
|
|
@@ -277,15 +303,17 @@ public class TopicsController extends AbstractController implements TopicsApi {
|
|
|
@Override
|
|
|
public Mono<ResponseEntity<Void>> cancelTopicAnalysis(String clusterName, String topicName,
|
|
|
ServerWebExchange exchange) {
|
|
|
- Mono<Void> validateAccess = accessControlService.validateAccess(AccessContext.builder()
|
|
|
+ var context = AccessContext.builder()
|
|
|
.cluster(clusterName)
|
|
|
.topic(topicName)
|
|
|
.topicActions(MESSAGES_READ)
|
|
|
- .build());
|
|
|
-
|
|
|
- topicAnalysisService.cancelAnalysis(getCluster(clusterName), topicName);
|
|
|
+ .auditOperation("cancelTopicAnalysis")
|
|
|
+ .build();
|
|
|
|
|
|
- return validateAccess.thenReturn(ResponseEntity.ok().build());
|
|
|
+ return accessControlService.validateAccess(context)
|
|
|
+ .then(Mono.fromRunnable(() -> topicAnalysisService.cancelAnalysis(getCluster(clusterName), topicName)))
|
|
|
+ .doOnEach(sig -> auditService.audit(context, sig))
|
|
|
+ .thenReturn(ResponseEntity.ok().build());
|
|
|
}
|
|
|
|
|
|
|
|
@@ -294,15 +322,18 @@ public class TopicsController extends AbstractController implements TopicsApi {
|
|
|
String topicName,
|
|
|
ServerWebExchange exchange) {
|
|
|
|
|
|
- Mono<Void> validateAccess = accessControlService.validateAccess(AccessContext.builder()
|
|
|
+ var context = AccessContext.builder()
|
|
|
.cluster(clusterName)
|
|
|
.topic(topicName)
|
|
|
.topicActions(MESSAGES_READ)
|
|
|
- .build());
|
|
|
+ .auditOperation("getTopicAnalysis")
|
|
|
+ .build();
|
|
|
|
|
|
- return validateAccess.thenReturn(topicAnalysisService.getTopicAnalysis(getCluster(clusterName), topicName)
|
|
|
+ return accessControlService.validateAccess(context)
|
|
|
+ .thenReturn(topicAnalysisService.getTopicAnalysis(getCluster(clusterName), topicName)
|
|
|
.map(ResponseEntity::ok)
|
|
|
- .orElseGet(() -> ResponseEntity.notFound().build()));
|
|
|
+ .orElseGet(() -> ResponseEntity.notFound().build()))
|
|
|
+ .doOnEach(sig -> auditService.audit(context, sig));
|
|
|
}
|
|
|
|
|
|
private Comparator<InternalTopic> getComparatorForTopic(
|