committed by
GitHub
61 changed files with 1065 additions and 167 deletions
@ -0,0 +1,71 @@ |
|||
/** |
|||
* Copyright © 2016-2023 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.notification.rule.trigger; |
|||
|
|||
import lombok.RequiredArgsConstructor; |
|||
import org.springframework.stereotype.Service; |
|||
import org.thingsboard.server.common.data.id.EntityId; |
|||
import org.thingsboard.server.common.data.id.TenantId; |
|||
import org.thingsboard.server.common.data.notification.info.RateLimitsNotificationInfo; |
|||
import org.thingsboard.server.common.data.notification.info.RuleOriginatedNotificationInfo; |
|||
import org.thingsboard.server.common.data.notification.rule.trigger.config.NotificationRuleTriggerType; |
|||
import org.thingsboard.server.common.data.notification.rule.trigger.config.RateLimitsNotificationRuleTriggerConfig; |
|||
import org.thingsboard.server.common.data.util.CollectionsUtil; |
|||
import org.thingsboard.server.common.data.notification.rule.trigger.RateLimitsTrigger; |
|||
import org.thingsboard.server.dao.entity.EntityService; |
|||
import org.thingsboard.server.dao.tenant.TenantService; |
|||
|
|||
import java.util.Optional; |
|||
|
|||
@Service |
|||
@RequiredArgsConstructor |
|||
public class RateLimitsTriggerProcessor implements NotificationRuleTriggerProcessor<RateLimitsTrigger, RateLimitsNotificationRuleTriggerConfig> { |
|||
|
|||
private final TenantService tenantService; |
|||
private final EntityService entityService; |
|||
|
|||
@Override |
|||
public boolean matchesFilter(RateLimitsTrigger trigger, RateLimitsNotificationRuleTriggerConfig triggerConfig) { |
|||
return trigger.getLimitLevel() != null && trigger.getApi().getLabel() != null && |
|||
CollectionsUtil.emptyOrContains(triggerConfig.getApis(), trigger.getApi()); |
|||
} |
|||
|
|||
@Override |
|||
public RuleOriginatedNotificationInfo constructNotificationInfo(RateLimitsTrigger trigger) { |
|||
EntityId limitLevel = trigger.getLimitLevel(); |
|||
String tenantName = tenantService.findTenantById(trigger.getTenantId()).getName(); |
|||
String limitLevelEntityName = null; |
|||
if (limitLevel instanceof TenantId) { |
|||
limitLevelEntityName = tenantName; |
|||
} else if (limitLevel != null) { |
|||
limitLevelEntityName = Optional.ofNullable(trigger.getLimitLevelEntityName()) |
|||
.orElseGet(() -> entityService.fetchEntityName(trigger.getTenantId(), limitLevel).orElse(null)); |
|||
} |
|||
return RateLimitsNotificationInfo.builder() |
|||
.tenantId(trigger.getTenantId()) |
|||
.tenantName(tenantName) |
|||
.api(trigger.getApi()) |
|||
.limitLevel(limitLevel) |
|||
.limitLevelEntityName(limitLevelEntityName) |
|||
.build(); |
|||
} |
|||
|
|||
@Override |
|||
public NotificationRuleTriggerType getTriggerType() { |
|||
return NotificationRuleTriggerType.RATE_LIMITS; |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,70 @@ |
|||
/** |
|||
* Copyright © 2016-2023 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.common.data.limit; |
|||
|
|||
import lombok.Getter; |
|||
import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration; |
|||
|
|||
import java.util.Optional; |
|||
import java.util.function.Function; |
|||
|
|||
public enum LimitedApi { |
|||
|
|||
ENTITY_EXPORT(DefaultTenantProfileConfiguration::getTenantEntityExportRateLimit, "entity version creation", true), |
|||
ENTITY_IMPORT(DefaultTenantProfileConfiguration::getTenantEntityImportRateLimit, "entity version load", true), |
|||
NOTIFICATION_REQUESTS(DefaultTenantProfileConfiguration::getTenantNotificationRequestsRateLimit, "notification requests", true), |
|||
NOTIFICATION_REQUESTS_PER_RULE(DefaultTenantProfileConfiguration::getTenantNotificationRequestsPerRuleRateLimit, "notification requests per rule", false), |
|||
REST_REQUESTS_PER_TENANT(DefaultTenantProfileConfiguration::getTenantServerRestLimitsConfiguration, "REST API requests", true), |
|||
REST_REQUESTS_PER_CUSTOMER(DefaultTenantProfileConfiguration::getCustomerServerRestLimitsConfiguration, "REST API requests per customer", false), |
|||
WS_UPDATES_PER_SESSION(DefaultTenantProfileConfiguration::getWsUpdatesPerSessionRateLimit, "WS updates per session", true), |
|||
CASSANDRA_QUERIES(DefaultTenantProfileConfiguration::getCassandraQueryTenantRateLimitsConfiguration, "Cassandra queries", true), |
|||
PASSWORD_RESET(false, true), |
|||
TWO_FA_VERIFICATION_CODE_SEND(false, true), |
|||
TWO_FA_VERIFICATION_CODE_CHECK(false, true), |
|||
TRANSPORT_MESSAGES_PER_TENANT("transport messages", true), |
|||
TRANSPORT_MESSAGES_PER_DEVICE("transport messages per device", false); |
|||
|
|||
private Function<DefaultTenantProfileConfiguration, String> configExtractor; |
|||
@Getter |
|||
private final boolean perTenant; |
|||
@Getter |
|||
private boolean refillRateLimitIntervally; |
|||
@Getter |
|||
private String label; |
|||
|
|||
LimitedApi(Function<DefaultTenantProfileConfiguration, String> configExtractor, String label, boolean perTenant) { |
|||
this.configExtractor = configExtractor; |
|||
this.label = label; |
|||
this.perTenant = perTenant; |
|||
} |
|||
|
|||
LimitedApi(boolean perTenant, boolean refillRateLimitIntervally) { |
|||
this.perTenant = perTenant; |
|||
this.refillRateLimitIntervally = refillRateLimitIntervally; |
|||
} |
|||
|
|||
LimitedApi(String label, boolean perTenant) { |
|||
this.label = label; |
|||
this.perTenant = perTenant; |
|||
} |
|||
|
|||
public String getLimitConfig(DefaultTenantProfileConfiguration profileConfiguration) { |
|||
return Optional.ofNullable(configExtractor) |
|||
.map(extractor -> extractor.apply(profileConfiguration)) |
|||
.orElse(null); |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,59 @@ |
|||
/** |
|||
* Copyright © 2016-2023 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.common.data.notification.info; |
|||
|
|||
import lombok.AllArgsConstructor; |
|||
import lombok.Builder; |
|||
import lombok.Data; |
|||
import lombok.NoArgsConstructor; |
|||
import org.thingsboard.server.common.data.id.EntityId; |
|||
import org.thingsboard.server.common.data.id.TenantId; |
|||
import org.thingsboard.server.common.data.limit.LimitedApi; |
|||
|
|||
import java.util.Map; |
|||
|
|||
import static org.thingsboard.server.common.data.util.CollectionsUtil.mapOf; |
|||
|
|||
@Data |
|||
@NoArgsConstructor |
|||
@AllArgsConstructor |
|||
@Builder |
|||
public class RateLimitsNotificationInfo implements RuleOriginatedNotificationInfo { |
|||
|
|||
private TenantId tenantId; |
|||
private String tenantName; |
|||
private LimitedApi api; |
|||
private EntityId limitLevel; |
|||
private String limitLevelEntityName; |
|||
|
|||
@Override |
|||
public Map<String, String> getTemplateData() { |
|||
return mapOf( |
|||
"api", api.getLabel(), |
|||
"limitLevelEntityType", limitLevel != null ? limitLevel.getEntityType().getNormalName() : null, |
|||
"limitLevelEntityId", limitLevel != null ? limitLevel.getId().toString() : null, |
|||
"limitLevelEntityName", limitLevelEntityName, |
|||
"tenantName", tenantName, |
|||
"tenantId", tenantId.toString() |
|||
); |
|||
} |
|||
|
|||
@Override |
|||
public TenantId getAffectedTenantId() { |
|||
return tenantId; |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,62 @@ |
|||
/** |
|||
* Copyright © 2016-2023 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.common.data.notification.rule.trigger; |
|||
|
|||
import lombok.Builder; |
|||
import lombok.Data; |
|||
import org.thingsboard.server.common.data.id.EntityId; |
|||
import org.thingsboard.server.common.data.id.TenantId; |
|||
import org.thingsboard.server.common.data.limit.LimitedApi; |
|||
import org.thingsboard.server.common.data.notification.rule.trigger.config.NotificationRuleTriggerType; |
|||
|
|||
import java.util.concurrent.TimeUnit; |
|||
|
|||
@Data |
|||
@Builder |
|||
public class RateLimitsTrigger implements NotificationRuleTrigger { |
|||
|
|||
private final TenantId tenantId; |
|||
private final LimitedApi api; |
|||
private final EntityId limitLevel; |
|||
private final String limitLevelEntityName; |
|||
|
|||
@Override |
|||
public NotificationRuleTriggerType getType() { |
|||
return NotificationRuleTriggerType.RATE_LIMITS; |
|||
} |
|||
|
|||
@Override |
|||
public EntityId getOriginatorEntityId() { |
|||
return limitLevel != null ? limitLevel : tenantId; |
|||
} |
|||
|
|||
|
|||
@Override |
|||
public boolean deduplicate() { |
|||
return true; |
|||
} |
|||
|
|||
@Override |
|||
public String getDeduplicationKey() { |
|||
return String.join(":", NotificationRuleTrigger.super.getDeduplicationKey(), api.toString()); |
|||
} |
|||
|
|||
@Override |
|||
public long getDefaultDeduplicationDuration() { |
|||
return TimeUnit.HOURS.toMillis(4); |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,45 @@ |
|||
/** |
|||
* Copyright © 2016-2023 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.common.data.notification.rule.trigger.config; |
|||
|
|||
import lombok.AllArgsConstructor; |
|||
import lombok.Builder; |
|||
import lombok.Data; |
|||
import lombok.NoArgsConstructor; |
|||
import org.thingsboard.server.common.data.limit.LimitedApi; |
|||
|
|||
import java.util.Set; |
|||
import java.util.stream.Collectors; |
|||
|
|||
@Data |
|||
@AllArgsConstructor |
|||
@NoArgsConstructor |
|||
@Builder |
|||
public class RateLimitsNotificationRuleTriggerConfig implements NotificationRuleTriggerConfig { |
|||
|
|||
private Set<LimitedApi> apis; |
|||
|
|||
@Override |
|||
public NotificationRuleTriggerType getTriggerType() { |
|||
return NotificationRuleTriggerType.RATE_LIMITS; |
|||
} |
|||
|
|||
@Override |
|||
public String getDeduplicationKey() { |
|||
return apis == null ? "#" : apis.stream().sorted().map(Enum::name).collect(Collectors.joining(",")); |
|||
} |
|||
|
|||
} |
|||
@ -1,67 +0,0 @@ |
|||
/** |
|||
* Copyright © 2016-2023 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.dao.util.limits; |
|||
|
|||
import lombok.Getter; |
|||
import org.thingsboard.server.common.data.EntityType; |
|||
import org.thingsboard.server.common.data.id.EntityId; |
|||
import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration; |
|||
|
|||
import java.util.function.BiFunction; |
|||
import java.util.function.Function; |
|||
|
|||
public enum LimitedApi { |
|||
|
|||
ENTITY_EXPORT(DefaultTenantProfileConfiguration::getTenantEntityExportRateLimit), |
|||
ENTITY_IMPORT(DefaultTenantProfileConfiguration::getTenantEntityImportRateLimit), |
|||
NOTIFICATION_REQUESTS(DefaultTenantProfileConfiguration::getTenantNotificationRequestsRateLimit), |
|||
NOTIFICATION_REQUESTS_PER_RULE(DefaultTenantProfileConfiguration::getTenantNotificationRequestsPerRuleRateLimit), |
|||
REST_REQUESTS((profileConfiguration, level) -> ((EntityId) level).getEntityType() == EntityType.TENANT ? |
|||
profileConfiguration.getTenantServerRestLimitsConfiguration() : |
|||
profileConfiguration.getCustomerServerRestLimitsConfiguration()), |
|||
WS_UPDATES_PER_SESSION(DefaultTenantProfileConfiguration::getWsUpdatesPerSessionRateLimit), |
|||
CASSANDRA_QUERIES(DefaultTenantProfileConfiguration::getCassandraQueryTenantRateLimitsConfiguration), |
|||
PASSWORD_RESET(true), |
|||
TWO_FA_VERIFICATION_CODE_SEND(true), |
|||
TWO_FA_VERIFICATION_CODE_CHECK(true); |
|||
|
|||
private final BiFunction<DefaultTenantProfileConfiguration, Object, String> configExtractor; |
|||
@Getter |
|||
private final boolean refillRateLimitIntervally; |
|||
|
|||
LimitedApi(Function<DefaultTenantProfileConfiguration, String> configExtractor) { |
|||
this((profileConfiguration, level) -> configExtractor.apply(profileConfiguration)); |
|||
} |
|||
|
|||
LimitedApi(BiFunction<DefaultTenantProfileConfiguration, Object, String> configExtractor) { |
|||
this.configExtractor = configExtractor; |
|||
this.refillRateLimitIntervally = false; |
|||
} |
|||
|
|||
LimitedApi(boolean refillRateLimitIntervally) { |
|||
this.configExtractor = null; |
|||
this.refillRateLimitIntervally = refillRateLimitIntervally; |
|||
} |
|||
|
|||
public String getLimitConfig(DefaultTenantProfileConfiguration profileConfiguration, Object level) { |
|||
if (configExtractor != null) { |
|||
return configExtractor.apply(profileConfiguration, level); |
|||
} else { |
|||
throw new IllegalArgumentException("No tenant profile config for " + name() + " rate limits"); |
|||
} |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,43 @@ |
|||
///
|
|||
/// Copyright © 2016-2023 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.
|
|||
///
|
|||
|
|||
export enum LimitedApi { |
|||
ENTITY_EXPORT = 'ENTITY_EXPORT', |
|||
ENTITY_IMPORT = 'ENTITY_IMPORT', |
|||
NOTIFICATION_REQUESTS = 'NOTIFICATION_REQUESTS', |
|||
NOTIFICATION_REQUESTS_PER_RULE = 'NOTIFICATION_REQUESTS_PER_RULE', |
|||
REST_REQUESTS_PER_TENANT = 'REST_REQUESTS_PER_TENANT', |
|||
REST_REQUESTS_PER_CUSTOMER = 'REST_REQUESTS_PER_CUSTOMER', |
|||
WS_UPDATES_PER_SESSION = 'WS_UPDATES_PER_SESSION', |
|||
CASSANDRA_QUERIES = 'CASSANDRA_QUERIES', |
|||
TRANSPORT_MESSAGES_PER_TENANT = 'TRANSPORT_MESSAGES_PER_TENANT', |
|||
TRANSPORT_MESSAGES_PER_DEVICE = 'TRANSPORT_MESSAGES_PER_DEVICE' |
|||
} |
|||
|
|||
export const LimitedApiTranslationMap = new Map<LimitedApi, string>( |
|||
[ |
|||
[LimitedApi.ENTITY_EXPORT, 'api-limit.entity-version-creation'], |
|||
[LimitedApi.ENTITY_IMPORT, 'api-limit.entity-version-load'], |
|||
[LimitedApi.NOTIFICATION_REQUESTS, 'api-limit.notification-requests'], |
|||
[LimitedApi.NOTIFICATION_REQUESTS_PER_RULE, 'api-limit.notification-requests-per-rule'], |
|||
[LimitedApi.REST_REQUESTS_PER_TENANT, 'api-limit.rest-api-requests'], |
|||
[LimitedApi.REST_REQUESTS_PER_CUSTOMER, 'api-limit.rest-api-requests-per-customer'], |
|||
[LimitedApi.WS_UPDATES_PER_SESSION, 'api-limit.ws-updates-per-session'], |
|||
[LimitedApi.CASSANDRA_QUERIES, 'api-limit.cassandra-queries'], |
|||
[LimitedApi.TRANSPORT_MESSAGES_PER_TENANT, 'api-limit.transport-messages'], |
|||
[LimitedApi.TRANSPORT_MESSAGES_PER_DEVICE, 'api-limit.transport-messages-per-device'] |
|||
] |
|||
); |
|||
@ -0,0 +1,50 @@ |
|||
#### Exceeded rate limits notification templatization |
|||
|
|||
<div class="divider"></div> |
|||
<br/> |
|||
|
|||
Notification subject and message fields support templatization. |
|||
The list of available templatization parameters depends on the template type. |
|||
See the available types and parameters below: |
|||
|
|||
Available template parameters: |
|||
|
|||
* `api` - rate-limited API label; one of: 'REST API requests', 'REST API requests per customer', 'transport messages', |
|||
'transport messages per device', 'Cassandra queries', 'WS updates per session', 'notification requests', 'notification requests per rule', |
|||
'entity version creation', 'entity version load'; |
|||
* `limitLevelEntityType` - entity type of the limit level entity, e.g. 'Tenant', 'Device', 'Notification rule', 'Customer', etc.; |
|||
* `limitLevelEntityId` - id of the limit level entity; |
|||
* `limitLevelEntityName` - name of the limit level entity; |
|||
* `tenantId` - id of the tenant; |
|||
* `tenantName` - name of the tenant; |
|||
* `recipientTitle` - title of the recipient (first and last name if specified, email otherwise); |
|||
* `recipientEmail` - email of the recipient; |
|||
* `recipientFirstName` - first name of the recipient; |
|||
* `recipientLastName` - last name of the recipient; |
|||
|
|||
Parameter names must be wrapped using `${...}`. For example: `${recipientFirstName}`. |
|||
You may also modify the value of the parameter with one of the suffixes: |
|||
|
|||
* `upperCase`, for example - `${recipientFirstName:upperCase}` |
|||
* `lowerCase`, for example - `${recipientFirstName:lowerCase}` |
|||
* `capitalize`, for example - `${recipientFirstName:capitalize}` |
|||
|
|||
<div class="divider"></div> |
|||
|
|||
##### Examples |
|||
|
|||
Let's assume customer 'Customer A' exceeded rate limit for per-customer REST API requests. The following template: |
|||
|
|||
```text |
|||
Rate limits for ${api} exceeded for ${limitLevelEntityType:lowerCase} '${limitLevelEntityName}' |
|||
{:copy-code} |
|||
``` |
|||
|
|||
will be transformed to: |
|||
|
|||
```text |
|||
Rate limits for REST API requests per customer exceeded for customer 'Customer A' |
|||
``` |
|||
|
|||
<br> |
|||
<br> |
|||
Loading…
Reference in new issue