wip
This commit is contained in:
parent
13fcdced8d
commit
bf06c34f78
11 changed files with 142 additions and 47 deletions
|
@ -4,10 +4,10 @@ import static java.util.stream.Collectors.toMap;
|
||||||
|
|
||||||
import com.provectus.kafka.ui.api.ClientQuotasApi;
|
import com.provectus.kafka.ui.api.ClientQuotasApi;
|
||||||
import com.provectus.kafka.ui.model.ClientQuotasDTO;
|
import com.provectus.kafka.ui.model.ClientQuotasDTO;
|
||||||
import com.provectus.kafka.ui.service.audit.AuditService;
|
import com.provectus.kafka.ui.model.rbac.AccessContext;
|
||||||
|
import com.provectus.kafka.ui.model.rbac.permission.ClientQuotaAction;
|
||||||
import com.provectus.kafka.ui.service.quota.ClientQuotaRecord;
|
import com.provectus.kafka.ui.service.quota.ClientQuotaRecord;
|
||||||
import com.provectus.kafka.ui.service.quota.QuotaService;
|
import com.provectus.kafka.ui.service.quota.ClientQuotaService;
|
||||||
import com.provectus.kafka.ui.service.rbac.AccessControlService;
|
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
import java.util.LinkedHashMap;
|
import java.util.LinkedHashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
@ -23,37 +23,56 @@ import reactor.core.publisher.Mono;
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
public class ClientQuotasController extends AbstractController implements ClientQuotasApi {
|
public class ClientQuotasController extends AbstractController implements ClientQuotasApi {
|
||||||
|
|
||||||
private final QuotaService quotaService;
|
private final ClientQuotaService clientQuotaService;
|
||||||
private final AccessControlService accessControlService;
|
|
||||||
private final AuditService auditService;
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Mono<ResponseEntity<Flux<ClientQuotasDTO>>> listQuotas(String clusterName,
|
public Mono<ResponseEntity<Flux<ClientQuotasDTO>>> listQuotas(String clusterName,
|
||||||
ServerWebExchange exchange) {
|
ServerWebExchange exchange) {
|
||||||
return Mono.just(quotaService.list(getCluster(clusterName)).map(this::map))
|
var context = AccessContext.builder()
|
||||||
.map(ResponseEntity::ok);
|
.cluster(clusterName)
|
||||||
|
.operationName("listClientQuotas")
|
||||||
|
.clientQuotaActions(ClientQuotaAction.VIEW)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
Mono<ResponseEntity<Flux<ClientQuotasDTO>>> operation =
|
||||||
|
Mono.just(clientQuotaService.list(getCluster(clusterName)).map(this::mapToDto))
|
||||||
|
.map(ResponseEntity::ok);
|
||||||
|
|
||||||
|
return validateAccess(context)
|
||||||
|
.then(operation)
|
||||||
|
.doOnEach(sig -> audit(context, sig));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Mono<ResponseEntity<Void>> upsertClientQuotas(String clusterName,
|
public Mono<ResponseEntity<Void>> upsertClientQuotas(String clusterName,
|
||||||
Mono<ClientQuotasDTO> clientQuotasDto,
|
Mono<ClientQuotasDTO> quotasDto,
|
||||||
ServerWebExchange exchange) {
|
ServerWebExchange exchange) {
|
||||||
return clientQuotasDto.flatMap(
|
var context = AccessContext.builder()
|
||||||
quotas ->
|
.cluster(clusterName)
|
||||||
quotaService.upsert(
|
.operationName("upsertClientQuotas")
|
||||||
|
.clientQuotaActions(ClientQuotaAction.EDIT)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
Mono<ResponseEntity<Void>> operation = quotasDto.flatMap(
|
||||||
|
newQuotas ->
|
||||||
|
clientQuotaService.upsert(
|
||||||
getCluster(clusterName),
|
getCluster(clusterName),
|
||||||
quotas.getUser(),
|
newQuotas.getUser(),
|
||||||
quotas.getClientId(),
|
newQuotas.getClientId(),
|
||||||
quotas.getIp(),
|
newQuotas.getIp(),
|
||||||
Optional.ofNullable(quotas.getQuotas()).orElse(Map.of())
|
Optional.ofNullable(newQuotas.getQuotas()).orElse(Map.of())
|
||||||
.entrySet()
|
.entrySet()
|
||||||
.stream()
|
.stream()
|
||||||
.collect(toMap(Map.Entry::getKey, e -> e.getValue().doubleValue()))
|
.collect(toMap(Map.Entry::getKey, e -> e.getValue().doubleValue()))
|
||||||
)
|
)
|
||||||
).map(statusCode -> ResponseEntity.status(statusCode).build());
|
).map(statusCode -> ResponseEntity.status(statusCode).build());
|
||||||
|
|
||||||
|
return validateAccess(context)
|
||||||
|
.then(operation)
|
||||||
|
.doOnEach(sig -> audit(context, sig));
|
||||||
}
|
}
|
||||||
|
|
||||||
private ClientQuotasDTO map(ClientQuotaRecord quotaRecord) {
|
private ClientQuotasDTO mapToDto(ClientQuotaRecord quotaRecord) {
|
||||||
return new ClientQuotasDTO()
|
return new ClientQuotasDTO()
|
||||||
.user(quotaRecord.user())
|
.user(quotaRecord.user())
|
||||||
.clientId(quotaRecord.clientId())
|
.clientId(quotaRecord.clientId())
|
||||||
|
|
|
@ -3,6 +3,7 @@ package com.provectus.kafka.ui.model.rbac;
|
||||||
import com.provectus.kafka.ui.model.rbac.permission.AclAction;
|
import com.provectus.kafka.ui.model.rbac.permission.AclAction;
|
||||||
import com.provectus.kafka.ui.model.rbac.permission.ApplicationConfigAction;
|
import com.provectus.kafka.ui.model.rbac.permission.ApplicationConfigAction;
|
||||||
import com.provectus.kafka.ui.model.rbac.permission.AuditAction;
|
import com.provectus.kafka.ui.model.rbac.permission.AuditAction;
|
||||||
|
import com.provectus.kafka.ui.model.rbac.permission.ClientQuotaAction;
|
||||||
import com.provectus.kafka.ui.model.rbac.permission.ClusterConfigAction;
|
import com.provectus.kafka.ui.model.rbac.permission.ClusterConfigAction;
|
||||||
import com.provectus.kafka.ui.model.rbac.permission.ConnectAction;
|
import com.provectus.kafka.ui.model.rbac.permission.ConnectAction;
|
||||||
import com.provectus.kafka.ui.model.rbac.permission.ConsumerGroupAction;
|
import com.provectus.kafka.ui.model.rbac.permission.ConsumerGroupAction;
|
||||||
|
@ -44,6 +45,8 @@ public class AccessContext {
|
||||||
|
|
||||||
Collection<AuditAction> auditAction;
|
Collection<AuditAction> auditAction;
|
||||||
|
|
||||||
|
Collection<ClientQuotaAction> clientQuotaActions;
|
||||||
|
|
||||||
String operationName;
|
String operationName;
|
||||||
Object operationParams;
|
Object operationParams;
|
||||||
|
|
||||||
|
@ -67,6 +70,7 @@ public class AccessContext {
|
||||||
private Collection<KsqlAction> ksqlActions = Collections.emptySet();
|
private Collection<KsqlAction> ksqlActions = Collections.emptySet();
|
||||||
private Collection<AclAction> aclActions = Collections.emptySet();
|
private Collection<AclAction> aclActions = Collections.emptySet();
|
||||||
private Collection<AuditAction> auditActions = Collections.emptySet();
|
private Collection<AuditAction> auditActions = Collections.emptySet();
|
||||||
|
private Collection<ClientQuotaAction> clientQuotaActions = Collections.emptySet();
|
||||||
|
|
||||||
private String operationName;
|
private String operationName;
|
||||||
private Object operationParams;
|
private Object operationParams;
|
||||||
|
@ -158,6 +162,12 @@ public class AccessContext {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public AccessContextBuilder clientQuotaActions(ClientQuotaAction... actions) {
|
||||||
|
Assert.isTrue(actions.length > 0, "actions not present");
|
||||||
|
this.clientQuotaActions = List.of(actions);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
public AccessContextBuilder operationName(String operationName) {
|
public AccessContextBuilder operationName(String operationName) {
|
||||||
this.operationName = operationName;
|
this.operationName = operationName;
|
||||||
return this;
|
return this;
|
||||||
|
@ -182,7 +192,7 @@ public class AccessContext {
|
||||||
connect, connectActions,
|
connect, connectActions,
|
||||||
connector,
|
connector,
|
||||||
schema, schemaActions,
|
schema, schemaActions,
|
||||||
ksqlActions, aclActions, auditActions,
|
ksqlActions, aclActions, auditActions, clientQuotaActions,
|
||||||
operationName, operationParams);
|
operationName, operationParams);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,12 +3,14 @@ package com.provectus.kafka.ui.model.rbac;
|
||||||
import static com.provectus.kafka.ui.model.rbac.Resource.ACL;
|
import static com.provectus.kafka.ui.model.rbac.Resource.ACL;
|
||||||
import static com.provectus.kafka.ui.model.rbac.Resource.APPLICATIONCONFIG;
|
import static com.provectus.kafka.ui.model.rbac.Resource.APPLICATIONCONFIG;
|
||||||
import static com.provectus.kafka.ui.model.rbac.Resource.AUDIT;
|
import static com.provectus.kafka.ui.model.rbac.Resource.AUDIT;
|
||||||
|
import static com.provectus.kafka.ui.model.rbac.Resource.CLIENT_QUOTAS;
|
||||||
import static com.provectus.kafka.ui.model.rbac.Resource.CLUSTERCONFIG;
|
import static com.provectus.kafka.ui.model.rbac.Resource.CLUSTERCONFIG;
|
||||||
import static com.provectus.kafka.ui.model.rbac.Resource.KSQL;
|
import static com.provectus.kafka.ui.model.rbac.Resource.KSQL;
|
||||||
|
|
||||||
import com.provectus.kafka.ui.model.rbac.permission.AclAction;
|
import com.provectus.kafka.ui.model.rbac.permission.AclAction;
|
||||||
import com.provectus.kafka.ui.model.rbac.permission.ApplicationConfigAction;
|
import com.provectus.kafka.ui.model.rbac.permission.ApplicationConfigAction;
|
||||||
import com.provectus.kafka.ui.model.rbac.permission.AuditAction;
|
import com.provectus.kafka.ui.model.rbac.permission.AuditAction;
|
||||||
|
import com.provectus.kafka.ui.model.rbac.permission.ClientQuotaAction;
|
||||||
import com.provectus.kafka.ui.model.rbac.permission.ClusterConfigAction;
|
import com.provectus.kafka.ui.model.rbac.permission.ClusterConfigAction;
|
||||||
import com.provectus.kafka.ui.model.rbac.permission.ConnectAction;
|
import com.provectus.kafka.ui.model.rbac.permission.ConnectAction;
|
||||||
import com.provectus.kafka.ui.model.rbac.permission.ConsumerGroupAction;
|
import com.provectus.kafka.ui.model.rbac.permission.ConsumerGroupAction;
|
||||||
|
@ -32,7 +34,7 @@ import org.springframework.util.Assert;
|
||||||
public class Permission {
|
public class Permission {
|
||||||
|
|
||||||
private static final List<Resource> RBAC_ACTION_EXEMPT_LIST =
|
private static final List<Resource> RBAC_ACTION_EXEMPT_LIST =
|
||||||
List.of(KSQL, CLUSTERCONFIG, APPLICATIONCONFIG, ACL, AUDIT);
|
List.of(KSQL, CLUSTERCONFIG, APPLICATIONCONFIG, ACL, AUDIT, CLIENT_QUOTAS);
|
||||||
|
|
||||||
Resource resource;
|
Resource resource;
|
||||||
List<String> actions;
|
List<String> actions;
|
||||||
|
@ -88,6 +90,7 @@ public class Permission {
|
||||||
case KSQL -> Arrays.stream(KsqlAction.values()).map(Enum::toString).toList();
|
case KSQL -> Arrays.stream(KsqlAction.values()).map(Enum::toString).toList();
|
||||||
case ACL -> Arrays.stream(AclAction.values()).map(Enum::toString).toList();
|
case ACL -> Arrays.stream(AclAction.values()).map(Enum::toString).toList();
|
||||||
case AUDIT -> Arrays.stream(AuditAction.values()).map(Enum::toString).toList();
|
case AUDIT -> Arrays.stream(AuditAction.values()).map(Enum::toString).toList();
|
||||||
|
case CLIENT_QUOTAS -> Arrays.stream(ClientQuotaAction.values()).map(Enum::toString).toList();
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -13,7 +13,8 @@ public enum Resource {
|
||||||
CONNECT,
|
CONNECT,
|
||||||
KSQL,
|
KSQL,
|
||||||
ACL,
|
ACL,
|
||||||
AUDIT;
|
AUDIT,
|
||||||
|
CLIENT_QUOTAS;
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
public static Resource fromString(String name) {
|
public static Resource fromString(String name) {
|
||||||
|
|
|
@ -0,0 +1,18 @@
|
||||||
|
package com.provectus.kafka.ui.model.rbac.permission;
|
||||||
|
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
public enum ClientQuotaAction implements PermissibleAction {
|
||||||
|
|
||||||
|
VIEW,
|
||||||
|
EDIT
|
||||||
|
|
||||||
|
;
|
||||||
|
|
||||||
|
public static final Set<ClientQuotaAction> ALTER_ACTIONS = Set.of(EDIT);
|
||||||
|
|
||||||
|
public boolean isAlter() {
|
||||||
|
return ALTER_ACTIONS.contains(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -4,5 +4,5 @@ public sealed interface PermissibleAction permits
|
||||||
AclAction, ApplicationConfigAction,
|
AclAction, ApplicationConfigAction,
|
||||||
ConsumerGroupAction, SchemaAction,
|
ConsumerGroupAction, SchemaAction,
|
||||||
ConnectAction, ClusterConfigAction,
|
ConnectAction, ClusterConfigAction,
|
||||||
KsqlAction, TopicAction, AuditAction {
|
KsqlAction, TopicAction, AuditAction, ClientQuotaAction {
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package com.provectus.kafka.ui.service.quota;
|
package com.provectus.kafka.ui.service.quota;
|
||||||
|
|
||||||
import jakarta.annotation.Nullable;
|
import jakarta.annotation.Nullable;
|
||||||
|
import java.util.Comparator;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import org.apache.kafka.common.quota.ClientQuotaEntity;
|
import org.apache.kafka.common.quota.ClientQuotaEntity;
|
||||||
|
|
||||||
|
@ -9,12 +10,17 @@ public record ClientQuotaRecord(@Nullable String user,
|
||||||
@Nullable String ip,
|
@Nullable String ip,
|
||||||
Map<String, Double> quotas) {
|
Map<String, Double> quotas) {
|
||||||
|
|
||||||
static ClientQuotaRecord create(ClientQuotaEntity entity, Map<String, Double> qoutas) {
|
static final Comparator<ClientQuotaRecord> COMPARATOR =
|
||||||
|
Comparator.<ClientQuotaRecord, String>comparing(r -> r.user)
|
||||||
|
.thenComparing(r -> r.clientId)
|
||||||
|
.thenComparing(r -> r.ip);
|
||||||
|
|
||||||
|
static ClientQuotaRecord create(ClientQuotaEntity entity, Map<String, Double> quotas) {
|
||||||
return new ClientQuotaRecord(
|
return new ClientQuotaRecord(
|
||||||
entity.entries().get(ClientQuotaEntity.USER),
|
entity.entries().get(ClientQuotaEntity.USER),
|
||||||
entity.entries().get(ClientQuotaEntity.CLIENT_ID),
|
entity.entries().get(ClientQuotaEntity.CLIENT_ID),
|
||||||
entity.entries().get(ClientQuotaEntity.IP),
|
entity.entries().get(ClientQuotaEntity.IP),
|
||||||
qoutas
|
quotas
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,14 +24,13 @@ import org.apache.kafka.common.quota.ClientQuotaEntity;
|
||||||
import org.apache.kafka.common.quota.ClientQuotaFilter;
|
import org.apache.kafka.common.quota.ClientQuotaFilter;
|
||||||
import org.apache.kafka.common.quota.ClientQuotaFilterComponent;
|
import org.apache.kafka.common.quota.ClientQuotaFilterComponent;
|
||||||
import org.springframework.http.HttpStatus;
|
import org.springframework.http.HttpStatus;
|
||||||
import org.springframework.http.HttpStatusCode;
|
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import reactor.core.publisher.Flux;
|
import reactor.core.publisher.Flux;
|
||||||
import reactor.core.publisher.Mono;
|
import reactor.core.publisher.Mono;
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
public class QuotaService {
|
public class ClientQuotaService {
|
||||||
|
|
||||||
private final AdminClientService adminClientService;
|
private final AdminClientService adminClientService;
|
||||||
|
|
||||||
|
@ -39,36 +38,29 @@ public class QuotaService {
|
||||||
return adminClientService.get(cluster)
|
return adminClientService.get(cluster)
|
||||||
.flatMap(ac -> ac.getClientQuotas(ClientQuotaFilter.all()))
|
.flatMap(ac -> ac.getClientQuotas(ClientQuotaFilter.all()))
|
||||||
.flatMapIterable(map ->
|
.flatMapIterable(map ->
|
||||||
map.entrySet().stream().map(e -> ClientQuotaRecord.create(e.getKey(), e.getValue())).toList());
|
map.entrySet().stream().map(e -> ClientQuotaRecord.create(e.getKey(), e.getValue())).toList())
|
||||||
|
.sort(ClientQuotaRecord.COMPARATOR);
|
||||||
}
|
}
|
||||||
|
|
||||||
//returns 201 is new entity was created, 200 if existing was updated, 204 if it was deleted
|
//returns 201 if new entity was created, 200 if existing was updated, 204 if existing was deleted
|
||||||
public Mono<HttpStatus> upsert(KafkaCluster cluster,
|
public Mono<HttpStatus> upsert(KafkaCluster cluster,
|
||||||
@Nullable String user,
|
@Nullable String user,
|
||||||
@Nullable String clientId,
|
@Nullable String clientId,
|
||||||
@Nullable String ip,
|
@Nullable String ip,
|
||||||
Map<String, Double> newQuotas) {
|
Map<String, Double> newQuotas) {
|
||||||
ClientQuotaEntity quotaEntity = quotaEntity(user, clientId, ip);
|
ClientQuotaEntity quotaEntity = quotaEntity(user, clientId, ip);
|
||||||
return adminClientService.get(cluster)
|
return adminClientService.get(cluster)
|
||||||
.flatMap(ac ->
|
.flatMap(ac ->
|
||||||
findQuotas(ac, quotaEntity)
|
findQuotas(ac, quotaEntity)
|
||||||
.flatMap(currentQuotas -> {
|
.flatMap(currentQuotas -> {
|
||||||
Set<String> quotasToClear = Sets.difference(currentQuotas.keySet(), newQuotas.keySet());
|
|
||||||
List<ClientQuotaAlteration.Op> ops = Stream.concat(
|
|
||||||
quotasToClear.stream()
|
|
||||||
//setting null value to clear current state
|
|
||||||
.map(name -> new ClientQuotaAlteration.Op(name, null)),
|
|
||||||
newQuotas.entrySet().stream()
|
|
||||||
.map(e -> new ClientQuotaAlteration.Op(e.getKey(), e.getValue()))
|
|
||||||
).toList();
|
|
||||||
|
|
||||||
HttpStatus result = HttpStatus.OK; //updated
|
HttpStatus result = HttpStatus.OK; //updated
|
||||||
if (newQuotas.isEmpty()) {
|
if (newQuotas.isEmpty()) {
|
||||||
result = HttpStatus.NO_CONTENT; //deleted
|
result = HttpStatus.NO_CONTENT; //deleted
|
||||||
} else if (currentQuotas.isEmpty()) {
|
} else if (currentQuotas.isEmpty()) {
|
||||||
result = HttpStatus.CREATED;
|
result = HttpStatus.CREATED;
|
||||||
}
|
}
|
||||||
return ac.alterClientQuota(new ClientQuotaAlteration(quotaEntity, ops))
|
var alteration = createAlteration(quotaEntity, currentQuotas, newQuotas);
|
||||||
|
return ac.alterClientQuota(alteration)
|
||||||
.thenReturn(result);
|
.thenReturn(result);
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
@ -85,12 +77,26 @@ public class QuotaService {
|
||||||
return new ClientQuotaEntity(id);
|
return new ClientQuotaEntity(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Mono<Map<String, Double>> findQuotas(ReactiveAdminClient ac, ClientQuotaEntity quotaEntity) {
|
private ClientQuotaAlteration createAlteration(ClientQuotaEntity quotaEntity,
|
||||||
return ac.getClientQuotas(searchFilter(quotaEntity))
|
Map<String, Double> currentQuotas,
|
||||||
.map(foundRecords -> Optional.ofNullable(foundRecords.get(quotaEntity)).orElse(Map.of()));
|
Map<String, Double> newQuotas) {
|
||||||
|
Set<String> quotasToClear = Sets.difference(currentQuotas.keySet(), newQuotas.keySet());
|
||||||
|
List<ClientQuotaAlteration.Op> ops = Stream.concat(
|
||||||
|
quotasToClear.stream()
|
||||||
|
.map(name -> new ClientQuotaAlteration.Op(name, null)), //setting null value to clear current state
|
||||||
|
newQuotas.entrySet().stream()
|
||||||
|
.map(e -> new ClientQuotaAlteration.Op(e.getKey(), e.getValue()))
|
||||||
|
).toList();
|
||||||
|
return new ClientQuotaAlteration(quotaEntity, ops);
|
||||||
}
|
}
|
||||||
|
|
||||||
private ClientQuotaFilter searchFilter(ClientQuotaEntity quotaEntity) {
|
// returns empty map if no quotas found for an entity
|
||||||
|
private Mono<Map<String, Double>> findQuotas(ReactiveAdminClient ac, ClientQuotaEntity quotaEntity) {
|
||||||
|
return ac.getClientQuotas(crateSearchFilter(quotaEntity))
|
||||||
|
.map(found -> Optional.ofNullable(found.get(quotaEntity)).orElse(Map.of()));
|
||||||
|
}
|
||||||
|
|
||||||
|
private ClientQuotaFilter crateSearchFilter(ClientQuotaEntity quotaEntity) {
|
||||||
List<ClientQuotaFilterComponent> filters = new ArrayList<>();
|
List<ClientQuotaFilterComponent> filters = new ArrayList<>();
|
||||||
quotaEntity.entries().forEach((type, name) -> filters.add(ClientQuotaFilterComponent.ofEntity(type, name)));
|
quotaEntity.entries().forEach((type, name) -> filters.add(ClientQuotaFilterComponent.ofEntity(type, name)));
|
||||||
return ClientQuotaFilter.contains(filters);
|
return ClientQuotaFilter.contains(filters);
|
|
@ -123,7 +123,8 @@ public class AccessControlService {
|
||||||
&& isSchemaAccessible(context, user)
|
&& isSchemaAccessible(context, user)
|
||||||
&& isKsqlAccessible(context, user)
|
&& isKsqlAccessible(context, user)
|
||||||
&& isAclAccessible(context, user)
|
&& isAclAccessible(context, user)
|
||||||
&& isAuditAccessible(context, user);
|
&& isAuditAccessible(context, user)
|
||||||
|
&& isClientQuotaAccessible(context, user);
|
||||||
|
|
||||||
if (!accessGranted) {
|
if (!accessGranted) {
|
||||||
throw new AccessDeniedException(ACCESS_DENIED);
|
throw new AccessDeniedException(ACCESS_DENIED);
|
||||||
|
@ -417,6 +418,23 @@ public class AccessControlService {
|
||||||
return isAccessible(Resource.AUDIT, null, user, context, requiredActions);
|
return isAccessible(Resource.AUDIT, null, user, context, requiredActions);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean isClientQuotaAccessible(AccessContext context, AuthenticatedUser user) {
|
||||||
|
if (!rbacEnabled) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (context.getClientQuotaActions().isEmpty()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
Set<String> requiredActions = context.getClientQuotaActions()
|
||||||
|
.stream()
|
||||||
|
.map(a -> a.toString().toUpperCase())
|
||||||
|
.collect(Collectors.toSet());
|
||||||
|
|
||||||
|
return isAccessible(Resource.CLIENT_QUOTAS, null, user, context, requiredActions);
|
||||||
|
}
|
||||||
|
|
||||||
public Set<ProviderAuthorityExtractor> getOauthExtractors() {
|
public Set<ProviderAuthorityExtractor> getOauthExtractors() {
|
||||||
return oauthExtractors;
|
return oauthExtractors;
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,11 @@
|
||||||
|
package com.provectus.kafka.ui.service.quota;
|
||||||
|
|
||||||
|
import com.provectus.kafka.ui.AbstractIntegrationTest;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
|
||||||
|
class ClientQuotaServiceTest extends AbstractIntegrationTest {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
ClientQuotaService quotaService;
|
||||||
|
|
||||||
|
}
|
|
@ -1936,6 +1936,9 @@ paths:
|
||||||
- ClientQuotas
|
- ClientQuotas
|
||||||
summary: upsertClientQuotas
|
summary: upsertClientQuotas
|
||||||
operationId: upsertClientQuotas
|
operationId: upsertClientQuotas
|
||||||
|
description: |
|
||||||
|
- updates/creates client quota record if `quotas` field is non-empty
|
||||||
|
- deletes client quota record if `quotas` field is null or empty
|
||||||
parameters:
|
parameters:
|
||||||
- name: clusterName
|
- name: clusterName
|
||||||
in: path
|
in: path
|
||||||
|
@ -1951,7 +1954,7 @@ paths:
|
||||||
200:
|
200:
|
||||||
description: Existing quota updated
|
description: Existing quota updated
|
||||||
201:
|
201:
|
||||||
description: Quota created
|
description: New quota created
|
||||||
204:
|
204:
|
||||||
description: Existing quota deleted
|
description: Existing quota deleted
|
||||||
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue