Export/import api rate limiting
This commit is contained in:
parent
18083f1094
commit
1610222b72
@ -0,0 +1,76 @@
|
||||
/**
|
||||
* Copyright © 2016-2022 The Thingsboard Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.thingsboard.server.service.apiusage;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.thingsboard.server.common.data.id.TenantId;
|
||||
import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration;
|
||||
import org.thingsboard.server.common.msg.tools.TbRateLimits;
|
||||
import org.thingsboard.server.dao.tenant.TbTenantProfileCache;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.function.Function;
|
||||
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class DefaultRateLimitService implements RateLimitService {
|
||||
|
||||
private final TbTenantProfileCache tenantProfileCache;
|
||||
|
||||
private final Map<String, Map<TenantId, TbRateLimits>> rateLimits = new ConcurrentHashMap<>();
|
||||
|
||||
@Override
|
||||
public boolean checkEntityExportLimit(TenantId tenantId) {
|
||||
return checkLimit(tenantId, "entityExport", DefaultTenantProfileConfiguration::getTenantEntityExportRateLimit);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean checkEntityImportLimit(TenantId tenantId) {
|
||||
return checkLimit(tenantId, "entityImport", DefaultTenantProfileConfiguration::getTenantEntityImportRateLimit);
|
||||
}
|
||||
|
||||
private boolean checkLimit(TenantId tenantId, String rateLimitsKey, Function<DefaultTenantProfileConfiguration, String> rateLimitConfigExtractor) {
|
||||
String rateLimitConfig = tenantProfileCache.get(tenantId).getProfileConfiguration()
|
||||
.map(rateLimitConfigExtractor).orElse(null);
|
||||
|
||||
Map<TenantId, TbRateLimits> rateLimits = this.rateLimits.get(rateLimitsKey);
|
||||
if (StringUtils.isEmpty(rateLimitConfig)) {
|
||||
if (rateLimits != null) {
|
||||
rateLimits.remove(tenantId);
|
||||
if (rateLimits.isEmpty()) {
|
||||
this.rateLimits.remove(rateLimitsKey);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
if (rateLimits == null) {
|
||||
rateLimits = new ConcurrentHashMap<>();
|
||||
this.rateLimits.put(rateLimitsKey, rateLimits);
|
||||
}
|
||||
TbRateLimits rateLimit = rateLimits.get(tenantId);
|
||||
if (rateLimit == null || !rateLimit.getConfiguration().equals(rateLimitConfig)) {
|
||||
rateLimit = new TbRateLimits(rateLimitConfig);
|
||||
rateLimits.put(tenantId, rateLimit);
|
||||
}
|
||||
|
||||
return rateLimit.tryConsume();
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,26 @@
|
||||
/**
|
||||
* Copyright © 2016-2022 The Thingsboard Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.thingsboard.server.service.apiusage;
|
||||
|
||||
import org.thingsboard.server.common.data.id.TenantId;
|
||||
|
||||
public interface RateLimitService {
|
||||
|
||||
boolean checkEntityExportLimit(TenantId tenantId);
|
||||
|
||||
boolean checkEntityImportLimit(TenantId tenantId);
|
||||
|
||||
}
|
||||
@ -22,6 +22,7 @@ import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.thingsboard.server.common.data.EntityType;
|
||||
import org.thingsboard.server.common.data.ExportableEntity;
|
||||
import org.thingsboard.server.common.data.exception.ThingsboardErrorCode;
|
||||
import org.thingsboard.server.common.data.exception.ThingsboardException;
|
||||
import org.thingsboard.server.common.data.id.EntityId;
|
||||
import org.thingsboard.server.common.data.sync.ThrowingRunnable;
|
||||
@ -31,6 +32,7 @@ import org.thingsboard.server.common.data.sync.ie.EntityImportResult;
|
||||
import org.thingsboard.server.common.data.sync.ie.EntityImportSettings;
|
||||
import org.thingsboard.server.dao.exception.DataValidationException;
|
||||
import org.thingsboard.server.queue.util.TbCoreComponent;
|
||||
import org.thingsboard.server.service.apiusage.RateLimitService;
|
||||
import org.thingsboard.server.service.security.model.SecurityUser;
|
||||
import org.thingsboard.server.service.sync.ie.exporting.EntityExportService;
|
||||
import org.thingsboard.server.service.sync.ie.exporting.impl.BaseEntityExportService;
|
||||
@ -55,6 +57,8 @@ public class DefaultEntitiesExportImportService implements EntitiesExportImportS
|
||||
private final Map<EntityType, EntityExportService<?, ?, ?>> exportServices = new HashMap<>();
|
||||
private final Map<EntityType, EntityImportService<?, ?, ?>> importServices = new HashMap<>();
|
||||
|
||||
private final RateLimitService rateLimitService;
|
||||
|
||||
protected static final List<EntityType> SUPPORTED_ENTITY_TYPES = List.of(
|
||||
EntityType.CUSTOMER, EntityType.ASSET, EntityType.RULE_CHAIN,
|
||||
EntityType.DASHBOARD, EntityType.DEVICE_PROFILE, EntityType.DEVICE
|
||||
@ -63,6 +67,10 @@ public class DefaultEntitiesExportImportService implements EntitiesExportImportS
|
||||
|
||||
@Override
|
||||
public <E extends ExportableEntity<I>, I extends EntityId> EntityExportData<E> exportEntity(SecurityUser user, I entityId, EntityExportSettings exportSettings) throws ThingsboardException {
|
||||
if (!rateLimitService.checkEntityExportLimit(user.getTenantId())) {
|
||||
throw new ThingsboardException("Rate limit for entities export is exceeded", ThingsboardErrorCode.TOO_MANY_REQUESTS);
|
||||
}
|
||||
|
||||
EntityType entityType = entityId.getEntityType();
|
||||
EntityExportService<I, E, EntityExportData<E>> exportService = getExportService(entityType);
|
||||
|
||||
@ -73,6 +81,9 @@ public class DefaultEntitiesExportImportService implements EntitiesExportImportS
|
||||
@Override
|
||||
public <E extends ExportableEntity<I>, I extends EntityId> EntityImportResult<E> importEntity(SecurityUser user, EntityExportData<E> exportData, EntityImportSettings importSettings,
|
||||
boolean saveReferences, boolean sendEvents) throws ThingsboardException {
|
||||
if (!rateLimitService.checkEntityImportLimit(user.getTenantId())) {
|
||||
throw new ThingsboardException("Rate limit for entities import is exceeded", ThingsboardErrorCode.TOO_MANY_REQUESTS);
|
||||
}
|
||||
if (exportData.getEntity() == null || exportData.getEntity().getId() == null) {
|
||||
throw new DataValidationException("Invalid entity data");
|
||||
}
|
||||
|
||||
@ -15,8 +15,6 @@
|
||||
*/
|
||||
package org.thingsboard.server.common.data.tenant.profile;
|
||||
|
||||
import io.swagger.annotations.ApiModel;
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
@ -46,6 +44,9 @@ public class DefaultTenantProfileConfiguration implements TenantProfileConfigura
|
||||
private String transportDeviceTelemetryMsgRateLimit;
|
||||
private String transportDeviceTelemetryDataPointsRateLimit;
|
||||
|
||||
private String tenantEntityExportRateLimit;
|
||||
private String tenantEntityImportRateLimit;
|
||||
|
||||
private long maxTransportMessages;
|
||||
private long maxTransportDataPoints;
|
||||
private long maxREExecutions;
|
||||
|
||||
@ -27,6 +27,7 @@ import java.time.Duration;
|
||||
*/
|
||||
public class TbRateLimits {
|
||||
private final LocalBucket bucket;
|
||||
private final String configuration;
|
||||
|
||||
public TbRateLimits(String limitsConfiguration) {
|
||||
LocalBucketBuilder builder = Bucket4j.builder();
|
||||
@ -42,8 +43,7 @@ public class TbRateLimits {
|
||||
} else {
|
||||
throw new IllegalArgumentException("Failed to parse rate limits configuration: " + limitsConfiguration);
|
||||
}
|
||||
|
||||
|
||||
this.configuration = limitsConfiguration;
|
||||
}
|
||||
|
||||
public boolean tryConsume() {
|
||||
@ -54,4 +54,8 @@ public class TbRateLimits {
|
||||
return bucket.tryConsume(number);
|
||||
}
|
||||
|
||||
public String getConfiguration() {
|
||||
return configuration;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user