151 changed files with 3969 additions and 907 deletions
@ -0,0 +1,54 @@ |
|||||
|
-- |
||||
|
-- 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. |
||||
|
-- |
||||
|
|
||||
|
ALTER TABLE notification_rule ADD COLUMN IF NOT EXISTS enabled BOOLEAN NOT NULL DEFAULT true; |
||||
|
|
||||
|
-- NOTIFICATION CONFIGS VERSION CONTROL START |
||||
|
|
||||
|
ALTER TABLE notification_template |
||||
|
ADD COLUMN IF NOT EXISTS external_id UUID; |
||||
|
DO |
||||
|
$$ |
||||
|
BEGIN |
||||
|
IF NOT EXISTS(SELECT 1 FROM pg_constraint WHERE conname = 'uq_notification_template_external_id') THEN |
||||
|
ALTER TABLE notification_template ADD CONSTRAINT uq_notification_template_external_id UNIQUE (tenant_id, external_id); |
||||
|
END IF; |
||||
|
END; |
||||
|
$$; |
||||
|
|
||||
|
ALTER TABLE notification_target |
||||
|
ADD COLUMN IF NOT EXISTS external_id UUID; |
||||
|
DO |
||||
|
$$ |
||||
|
BEGIN |
||||
|
IF NOT EXISTS(SELECT 1 FROM pg_constraint WHERE conname = 'uq_notification_target_external_id') THEN |
||||
|
ALTER TABLE notification_target ADD CONSTRAINT uq_notification_target_external_id UNIQUE (tenant_id, external_id); |
||||
|
END IF; |
||||
|
END; |
||||
|
$$; |
||||
|
|
||||
|
ALTER TABLE notification_rule |
||||
|
ADD COLUMN IF NOT EXISTS external_id UUID; |
||||
|
DO |
||||
|
$$ |
||||
|
BEGIN |
||||
|
IF NOT EXISTS(SELECT 1 FROM pg_constraint WHERE conname = 'uq_notification_rule_external_id') THEN |
||||
|
ALTER TABLE notification_rule ADD CONSTRAINT uq_notification_rule_external_id UNIQUE (tenant_id, external_id); |
||||
|
END IF; |
||||
|
END; |
||||
|
$$; |
||||
|
|
||||
|
-- NOTIFICATION CONFIGS VERSION CONTROL END |
||||
@ -1,37 +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.service.apiusage.limits; |
|
||||
|
|
||||
import lombok.RequiredArgsConstructor; |
|
||||
import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration; |
|
||||
|
|
||||
import java.util.function.Function; |
|
||||
|
|
||||
@RequiredArgsConstructor |
|
||||
public enum LimitedApi { |
|
||||
|
|
||||
ENTITY_EXPORT(DefaultTenantProfileConfiguration::getTenantEntityExportRateLimit), |
|
||||
ENTITY_IMPORT(DefaultTenantProfileConfiguration::getTenantEntityImportRateLimit), |
|
||||
NOTIFICATION_REQUESTS(DefaultTenantProfileConfiguration::getTenantNotificationRequestsRateLimit), |
|
||||
NOTIFICATION_REQUESTS_PER_RULE(DefaultTenantProfileConfiguration::getTenantNotificationRequestsPerRuleRateLimit); |
|
||||
|
|
||||
private final Function<DefaultTenantProfileConfiguration, String> configExtractor; |
|
||||
|
|
||||
public String getLimitConfig(DefaultTenantProfileConfiguration profileConfiguration) { |
|
||||
return configExtractor.apply(profileConfiguration); |
|
||||
} |
|
||||
|
|
||||
} |
|
||||
@ -0,0 +1,103 @@ |
|||||
|
/** |
||||
|
* 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.sync.ie.exporting.impl; |
||||
|
|
||||
|
import org.springframework.stereotype.Service; |
||||
|
import org.thingsboard.server.common.data.EntityType; |
||||
|
import org.thingsboard.server.common.data.ExportableEntity; |
||||
|
import org.thingsboard.server.common.data.id.DeviceId; |
||||
|
import org.thingsboard.server.common.data.id.DeviceProfileId; |
||||
|
import org.thingsboard.server.common.data.id.EntityId; |
||||
|
import org.thingsboard.server.common.data.id.NotificationRuleId; |
||||
|
import org.thingsboard.server.common.data.id.NotificationTargetId; |
||||
|
import org.thingsboard.server.common.data.id.RuleChainId; |
||||
|
import org.thingsboard.server.common.data.notification.rule.DefaultNotificationRuleRecipientsConfig; |
||||
|
import org.thingsboard.server.common.data.notification.rule.EscalatedNotificationRuleRecipientsConfig; |
||||
|
import org.thingsboard.server.common.data.notification.rule.NotificationRule; |
||||
|
import org.thingsboard.server.common.data.notification.rule.NotificationRuleRecipientsConfig; |
||||
|
import org.thingsboard.server.common.data.notification.rule.trigger.DeviceActivityNotificationRuleTriggerConfig; |
||||
|
import org.thingsboard.server.common.data.notification.rule.trigger.NotificationRuleTriggerConfig; |
||||
|
import org.thingsboard.server.common.data.notification.rule.trigger.RuleEngineComponentLifecycleEventNotificationRuleTriggerConfig; |
||||
|
import org.thingsboard.server.common.data.sync.ie.EntityExportData; |
||||
|
import org.thingsboard.server.queue.util.TbCoreComponent; |
||||
|
import org.thingsboard.server.service.sync.vc.data.EntitiesExportCtx; |
||||
|
|
||||
|
import java.util.LinkedHashMap; |
||||
|
import java.util.List; |
||||
|
import java.util.Map; |
||||
|
import java.util.Set; |
||||
|
import java.util.UUID; |
||||
|
import java.util.stream.Collectors; |
||||
|
|
||||
|
@Service |
||||
|
@TbCoreComponent |
||||
|
public class NotificationRuleExportService<I extends EntityId, E extends ExportableEntity<I>, D extends EntityExportData<E>> extends BaseEntityExportService<NotificationRuleId, NotificationRule, EntityExportData<NotificationRule>> { |
||||
|
|
||||
|
@Override |
||||
|
protected void setRelatedEntities(EntitiesExportCtx<?> ctx, NotificationRule notificationRule, EntityExportData<NotificationRule> exportData) { |
||||
|
notificationRule.setTemplateId(getExternalIdOrElseInternal(ctx, notificationRule.getTemplateId())); |
||||
|
|
||||
|
NotificationRuleTriggerConfig ruleTriggerConfig = notificationRule.getTriggerConfig(); |
||||
|
switch (ruleTriggerConfig.getTriggerType()) { |
||||
|
case DEVICE_ACTIVITY: { |
||||
|
DeviceActivityNotificationRuleTriggerConfig triggerConfig = (DeviceActivityNotificationRuleTriggerConfig) ruleTriggerConfig; |
||||
|
Set<UUID> devices = triggerConfig.getDevices(); |
||||
|
if (devices != null) { |
||||
|
triggerConfig.setDevices(toExternalIds(devices, DeviceId::new, ctx).collect(Collectors.toSet())); |
||||
|
} |
||||
|
|
||||
|
Set<UUID> deviceProfiles = triggerConfig.getDeviceProfiles(); |
||||
|
if (deviceProfiles != null) { |
||||
|
triggerConfig.setDeviceProfiles(toExternalIds(deviceProfiles, DeviceProfileId::new, ctx).collect(Collectors.toSet())); |
||||
|
} |
||||
|
break; |
||||
|
} |
||||
|
case RULE_ENGINE_COMPONENT_LIFECYCLE_EVENT: |
||||
|
RuleEngineComponentLifecycleEventNotificationRuleTriggerConfig triggerConfig = (RuleEngineComponentLifecycleEventNotificationRuleTriggerConfig) ruleTriggerConfig; |
||||
|
Set<UUID> ruleChains = triggerConfig.getRuleChains(); |
||||
|
if (ruleChains != null) { |
||||
|
triggerConfig.setRuleChains(toExternalIds(ruleChains, RuleChainId::new, ctx).collect(Collectors.toSet())); |
||||
|
} |
||||
|
break; |
||||
|
} |
||||
|
|
||||
|
NotificationRuleRecipientsConfig ruleRecipientsConfig = notificationRule.getRecipientsConfig(); |
||||
|
switch (ruleTriggerConfig.getTriggerType()) { |
||||
|
case ALARM: { |
||||
|
EscalatedNotificationRuleRecipientsConfig recipientsConfig = (EscalatedNotificationRuleRecipientsConfig) ruleRecipientsConfig; |
||||
|
Map<Integer, List<UUID>> escalationTable = new LinkedHashMap<>(recipientsConfig.getEscalationTable()); |
||||
|
escalationTable.replaceAll((delay, targets) -> { |
||||
|
return toExternalIds(targets, NotificationTargetId::new, ctx).collect(Collectors.toList()); |
||||
|
}); |
||||
|
recipientsConfig.setEscalationTable(escalationTable); |
||||
|
break; |
||||
|
} |
||||
|
default: { |
||||
|
DefaultNotificationRuleRecipientsConfig recipientsConfig = (DefaultNotificationRuleRecipientsConfig) ruleRecipientsConfig; |
||||
|
List<UUID> targets = recipientsConfig.getTargets(); |
||||
|
targets = toExternalIds(targets, NotificationTargetId::new, ctx).collect(Collectors.toList()); |
||||
|
recipientsConfig.setTargets(targets); |
||||
|
break; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public Set<EntityType> getSupportedEntityTypes() { |
||||
|
return Set.of(EntityType.NOTIFICATION_RULE); |
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,55 @@ |
|||||
|
/** |
||||
|
* 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.sync.ie.exporting.impl; |
||||
|
|
||||
|
import org.springframework.stereotype.Service; |
||||
|
import org.thingsboard.server.common.data.EntityType; |
||||
|
import org.thingsboard.server.common.data.id.CustomerId; |
||||
|
import org.thingsboard.server.common.data.id.NotificationTargetId; |
||||
|
import org.thingsboard.server.common.data.notification.targets.NotificationTarget; |
||||
|
import org.thingsboard.server.common.data.notification.targets.NotificationTargetType; |
||||
|
import org.thingsboard.server.common.data.notification.targets.platform.CustomerUsersFilter; |
||||
|
import org.thingsboard.server.common.data.notification.targets.platform.PlatformUsersNotificationTargetConfig; |
||||
|
import org.thingsboard.server.common.data.notification.targets.platform.UsersFilter; |
||||
|
import org.thingsboard.server.common.data.sync.ie.EntityExportData; |
||||
|
import org.thingsboard.server.queue.util.TbCoreComponent; |
||||
|
import org.thingsboard.server.service.sync.vc.data.EntitiesExportCtx; |
||||
|
|
||||
|
import java.util.Set; |
||||
|
|
||||
|
@Service |
||||
|
@TbCoreComponent |
||||
|
public class NotificationTargetExportService extends BaseEntityExportService<NotificationTargetId, NotificationTarget, EntityExportData<NotificationTarget>> { |
||||
|
|
||||
|
@Override |
||||
|
protected void setRelatedEntities(EntitiesExportCtx<?> ctx, NotificationTarget notificationTarget, EntityExportData<NotificationTarget> exportData) { |
||||
|
if (notificationTarget.getConfiguration().getType() == NotificationTargetType.PLATFORM_USERS) { |
||||
|
UsersFilter usersFilter = ((PlatformUsersNotificationTargetConfig) notificationTarget.getConfiguration()).getUsersFilter(); |
||||
|
switch (usersFilter.getType()) { |
||||
|
case CUSTOMER_USERS: |
||||
|
CustomerUsersFilter customerUsersFilter = (CustomerUsersFilter) usersFilter; |
||||
|
customerUsersFilter.setCustomerId(getExternalIdOrElseInternal(ctx, new CustomerId(customerUsersFilter.getCustomerId())).getId()); |
||||
|
break; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public Set<EntityType> getSupportedEntityTypes() { |
||||
|
return Set.of(EntityType.NOTIFICATION_TARGET); |
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,42 @@ |
|||||
|
/** |
||||
|
* 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.sync.ie.exporting.impl; |
||||
|
|
||||
|
import org.springframework.stereotype.Service; |
||||
|
import org.thingsboard.server.common.data.EntityType; |
||||
|
import org.thingsboard.server.common.data.id.NotificationTemplateId; |
||||
|
import org.thingsboard.server.common.data.notification.template.NotificationTemplate; |
||||
|
import org.thingsboard.server.common.data.sync.ie.EntityExportData; |
||||
|
import org.thingsboard.server.queue.util.TbCoreComponent; |
||||
|
import org.thingsboard.server.service.sync.vc.data.EntitiesExportCtx; |
||||
|
|
||||
|
import java.util.Set; |
||||
|
|
||||
|
@Service |
||||
|
@TbCoreComponent |
||||
|
public class NotificationTemplateExportService extends BaseEntityExportService<NotificationTemplateId, NotificationTemplate, EntityExportData<NotificationTemplate>> { |
||||
|
|
||||
|
@Override |
||||
|
protected void setRelatedEntities(EntitiesExportCtx<?> ctx, NotificationTemplate notificationTemplate, EntityExportData<NotificationTemplate> exportData) { |
||||
|
|
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public Set<EntityType> getSupportedEntityTypes() { |
||||
|
return Set.of(EntityType.NOTIFICATION_TEMPLATE); |
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,150 @@ |
|||||
|
/** |
||||
|
* 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.sync.ie.importing.impl; |
||||
|
|
||||
|
import lombok.RequiredArgsConstructor; |
||||
|
import org.springframework.stereotype.Service; |
||||
|
import org.thingsboard.server.common.data.EntityType; |
||||
|
import org.thingsboard.server.common.data.User; |
||||
|
import org.thingsboard.server.common.data.audit.ActionType; |
||||
|
import org.thingsboard.server.common.data.exception.ThingsboardException; |
||||
|
import org.thingsboard.server.common.data.id.DeviceId; |
||||
|
import org.thingsboard.server.common.data.id.DeviceProfileId; |
||||
|
import org.thingsboard.server.common.data.id.NotificationRuleId; |
||||
|
import org.thingsboard.server.common.data.id.NotificationTargetId; |
||||
|
import org.thingsboard.server.common.data.id.RuleChainId; |
||||
|
import org.thingsboard.server.common.data.id.TenantId; |
||||
|
import org.thingsboard.server.common.data.id.UUIDBased; |
||||
|
import org.thingsboard.server.common.data.notification.rule.DefaultNotificationRuleRecipientsConfig; |
||||
|
import org.thingsboard.server.common.data.notification.rule.EscalatedNotificationRuleRecipientsConfig; |
||||
|
import org.thingsboard.server.common.data.notification.rule.NotificationRule; |
||||
|
import org.thingsboard.server.common.data.notification.rule.NotificationRuleRecipientsConfig; |
||||
|
import org.thingsboard.server.common.data.notification.rule.trigger.DeviceActivityNotificationRuleTriggerConfig; |
||||
|
import org.thingsboard.server.common.data.notification.rule.trigger.NotificationRuleTriggerConfig; |
||||
|
import org.thingsboard.server.common.data.notification.rule.trigger.NotificationRuleTriggerType; |
||||
|
import org.thingsboard.server.common.data.notification.rule.trigger.RuleEngineComponentLifecycleEventNotificationRuleTriggerConfig; |
||||
|
import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent; |
||||
|
import org.thingsboard.server.common.data.sync.ie.EntityExportData; |
||||
|
import org.thingsboard.server.dao.notification.NotificationRuleService; |
||||
|
import org.thingsboard.server.dao.service.ConstraintValidator; |
||||
|
import org.thingsboard.server.queue.util.TbCoreComponent; |
||||
|
import org.thingsboard.server.service.sync.vc.data.EntitiesImportCtx; |
||||
|
|
||||
|
import java.util.LinkedHashMap; |
||||
|
import java.util.List; |
||||
|
import java.util.Map; |
||||
|
import java.util.Set; |
||||
|
import java.util.UUID; |
||||
|
import java.util.stream.Collectors; |
||||
|
|
||||
|
@Service |
||||
|
@TbCoreComponent |
||||
|
@RequiredArgsConstructor |
||||
|
public class NotificationRuleImportService extends BaseEntityImportService<NotificationRuleId, NotificationRule, EntityExportData<NotificationRule>> { |
||||
|
|
||||
|
private final NotificationRuleService notificationRuleService; |
||||
|
|
||||
|
@Override |
||||
|
protected void setOwner(TenantId tenantId, NotificationRule notificationRule, IdProvider idProvider) { |
||||
|
notificationRule.setTenantId(tenantId); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
protected NotificationRule prepare(EntitiesImportCtx ctx, NotificationRule notificationRule, NotificationRule oldNotificationRule, EntityExportData<NotificationRule> exportData, IdProvider idProvider) { |
||||
|
notificationRule.setTemplateId(idProvider.getInternalId(notificationRule.getTemplateId())); |
||||
|
|
||||
|
NotificationRuleTriggerConfig ruleTriggerConfig = notificationRule.getTriggerConfig(); |
||||
|
NotificationRuleTriggerType triggerType = ruleTriggerConfig.getTriggerType(); |
||||
|
switch (triggerType) { |
||||
|
case DEVICE_ACTIVITY: { |
||||
|
DeviceActivityNotificationRuleTriggerConfig triggerConfig = (DeviceActivityNotificationRuleTriggerConfig) ruleTriggerConfig; |
||||
|
Set<UUID> devices = triggerConfig.getDevices(); |
||||
|
if (devices != null) { |
||||
|
triggerConfig.setDevices(devices.stream().map(DeviceId::new) |
||||
|
.map(idProvider::getInternalId).map(UUIDBased::getId) |
||||
|
.collect(Collectors.toSet())); |
||||
|
} |
||||
|
|
||||
|
Set<UUID> deviceProfiles = triggerConfig.getDeviceProfiles(); |
||||
|
if (deviceProfiles != null) { |
||||
|
triggerConfig.setDeviceProfiles(deviceProfiles.stream().map(DeviceProfileId::new) |
||||
|
.map(idProvider::getInternalId).map(UUIDBased::getId) |
||||
|
.collect(Collectors.toSet())); |
||||
|
} |
||||
|
break; |
||||
|
} |
||||
|
case RULE_ENGINE_COMPONENT_LIFECYCLE_EVENT: |
||||
|
RuleEngineComponentLifecycleEventNotificationRuleTriggerConfig triggerConfig = (RuleEngineComponentLifecycleEventNotificationRuleTriggerConfig) ruleTriggerConfig; |
||||
|
Set<UUID> ruleChains = triggerConfig.getRuleChains(); |
||||
|
if (ruleChains != null) { |
||||
|
triggerConfig.setRuleChains(ruleChains.stream().map(RuleChainId::new) |
||||
|
.map(idProvider::getInternalId).map(UUIDBased::getId) |
||||
|
.collect(Collectors.toSet())); |
||||
|
} |
||||
|
break; |
||||
|
} |
||||
|
if (!triggerType.isTenantLevel()) { |
||||
|
throw new IllegalArgumentException("Trigger type " + triggerType + " is not available for tenants"); |
||||
|
} |
||||
|
|
||||
|
NotificationRuleRecipientsConfig ruleRecipientsConfig = notificationRule.getRecipientsConfig(); |
||||
|
switch (triggerType) { |
||||
|
case ALARM: { |
||||
|
EscalatedNotificationRuleRecipientsConfig recipientsConfig = (EscalatedNotificationRuleRecipientsConfig) ruleRecipientsConfig; |
||||
|
Map<Integer, List<UUID>> escalationTable = new LinkedHashMap<>(recipientsConfig.getEscalationTable()); |
||||
|
escalationTable.replaceAll((delay, targets) -> targets.stream() |
||||
|
.map(NotificationTargetId::new).map(idProvider::getInternalId) |
||||
|
.map(UUIDBased::getId).collect(Collectors.toList())); |
||||
|
recipientsConfig.setEscalationTable(escalationTable); |
||||
|
break; |
||||
|
} |
||||
|
default: { |
||||
|
DefaultNotificationRuleRecipientsConfig recipientsConfig = (DefaultNotificationRuleRecipientsConfig) ruleRecipientsConfig; |
||||
|
List<UUID> targets = recipientsConfig.getTargets().stream() |
||||
|
.map(NotificationTargetId::new).map(idProvider::getInternalId) |
||||
|
.map(UUIDBased::getId).collect(Collectors.toList()); |
||||
|
recipientsConfig.setTargets(targets); |
||||
|
break; |
||||
|
} |
||||
|
} |
||||
|
return notificationRule; |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
protected NotificationRule saveOrUpdate(EntitiesImportCtx ctx, NotificationRule notificationRule, EntityExportData<NotificationRule> exportData, IdProvider idProvider) { |
||||
|
ConstraintValidator.validateFields(notificationRule); |
||||
|
return notificationRuleService.saveNotificationRule(ctx.getTenantId(), notificationRule); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
protected void onEntitySaved(User user, NotificationRule savedEntity, NotificationRule oldEntity) throws ThingsboardException { |
||||
|
entityActionService.logEntityAction(user, savedEntity.getId(), savedEntity, null, |
||||
|
oldEntity == null ? ActionType.ADDED : ActionType.UPDATED, null); |
||||
|
clusterService.broadcastEntityStateChangeEvent(user.getTenantId(), savedEntity.getId(), |
||||
|
oldEntity == null ? ComponentLifecycleEvent.CREATED : ComponentLifecycleEvent.UPDATED); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
protected NotificationRule deepCopy(NotificationRule notificationRule) { |
||||
|
return new NotificationRule(notificationRule); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public EntityType getEntityType() { |
||||
|
return EntityType.NOTIFICATION_RULE; |
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,109 @@ |
|||||
|
/** |
||||
|
* 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.sync.ie.importing.impl; |
||||
|
|
||||
|
import lombok.RequiredArgsConstructor; |
||||
|
import org.apache.commons.collections.CollectionUtils; |
||||
|
import org.springframework.security.access.AccessDeniedException; |
||||
|
import org.springframework.stereotype.Service; |
||||
|
import org.thingsboard.server.common.data.EntityType; |
||||
|
import org.thingsboard.server.common.data.User; |
||||
|
import org.thingsboard.server.common.data.audit.ActionType; |
||||
|
import org.thingsboard.server.common.data.exception.ThingsboardException; |
||||
|
import org.thingsboard.server.common.data.id.CustomerId; |
||||
|
import org.thingsboard.server.common.data.id.NotificationTargetId; |
||||
|
import org.thingsboard.server.common.data.id.TenantId; |
||||
|
import org.thingsboard.server.common.data.id.UUIDBased; |
||||
|
import org.thingsboard.server.common.data.id.UserId; |
||||
|
import org.thingsboard.server.common.data.notification.targets.NotificationTarget; |
||||
|
import org.thingsboard.server.common.data.notification.targets.NotificationTargetType; |
||||
|
import org.thingsboard.server.common.data.notification.targets.platform.CustomerUsersFilter; |
||||
|
import org.thingsboard.server.common.data.notification.targets.platform.PlatformUsersNotificationTargetConfig; |
||||
|
import org.thingsboard.server.common.data.notification.targets.platform.TenantAdministratorsFilter; |
||||
|
import org.thingsboard.server.common.data.notification.targets.platform.UserListFilter; |
||||
|
import org.thingsboard.server.common.data.notification.targets.platform.UsersFilter; |
||||
|
import org.thingsboard.server.common.data.sync.ie.EntityExportData; |
||||
|
import org.thingsboard.server.dao.notification.NotificationTargetService; |
||||
|
import org.thingsboard.server.dao.service.ConstraintValidator; |
||||
|
import org.thingsboard.server.queue.util.TbCoreComponent; |
||||
|
import org.thingsboard.server.service.sync.vc.data.EntitiesImportCtx; |
||||
|
|
||||
|
import java.util.stream.Collectors; |
||||
|
|
||||
|
@Service |
||||
|
@TbCoreComponent |
||||
|
@RequiredArgsConstructor |
||||
|
public class NotificationTargetImportService extends BaseEntityImportService<NotificationTargetId, NotificationTarget, EntityExportData<NotificationTarget>> { |
||||
|
|
||||
|
private final NotificationTargetService notificationTargetService; |
||||
|
|
||||
|
@Override |
||||
|
protected void setOwner(TenantId tenantId, NotificationTarget notificationTarget, IdProvider idProvider) { |
||||
|
notificationTarget.setTenantId(tenantId); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
protected NotificationTarget prepare(EntitiesImportCtx ctx, NotificationTarget notificationTarget, NotificationTarget oldNotificationTarget, EntityExportData<NotificationTarget> exportData, IdProvider idProvider) { |
||||
|
if (notificationTarget.getConfiguration().getType() == NotificationTargetType.PLATFORM_USERS) { |
||||
|
UsersFilter usersFilter = ((PlatformUsersNotificationTargetConfig) notificationTarget.getConfiguration()).getUsersFilter(); |
||||
|
switch (usersFilter.getType()) { |
||||
|
case CUSTOMER_USERS: |
||||
|
CustomerUsersFilter customerUsersFilter = (CustomerUsersFilter) usersFilter; |
||||
|
customerUsersFilter.setCustomerId(idProvider.getInternalId(new CustomerId(customerUsersFilter.getCustomerId())).getId()); |
||||
|
break; |
||||
|
case USER_LIST: |
||||
|
UserListFilter userListFilter = (UserListFilter) usersFilter; |
||||
|
userListFilter.setUsersIds(userListFilter.getUsersIds().stream() |
||||
|
.map(UserId::new).map(idProvider::getInternalId) |
||||
|
.map(UUIDBased::getId).collect(Collectors.toList()) |
||||
|
); |
||||
|
break; |
||||
|
case TENANT_ADMINISTRATORS: |
||||
|
if (CollectionUtils.isNotEmpty(((TenantAdministratorsFilter) usersFilter).getTenantsIds()) || |
||||
|
CollectionUtils.isNotEmpty(((TenantAdministratorsFilter) usersFilter).getTenantProfilesIds())) { |
||||
|
throw new IllegalArgumentException("Permission denied"); |
||||
|
} |
||||
|
break; |
||||
|
case SYSTEM_ADMINISTRATORS: |
||||
|
throw new AccessDeniedException("Permission denied"); |
||||
|
} |
||||
|
} |
||||
|
return notificationTarget; |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
protected NotificationTarget saveOrUpdate(EntitiesImportCtx ctx, NotificationTarget notificationTarget, EntityExportData<NotificationTarget> exportData, IdProvider idProvider) { |
||||
|
ConstraintValidator.validateFields(notificationTarget); |
||||
|
return notificationTargetService.saveNotificationTarget(ctx.getTenantId(), notificationTarget); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
protected void onEntitySaved(User user, NotificationTarget savedEntity, NotificationTarget oldEntity) throws ThingsboardException { |
||||
|
entityActionService.logEntityAction(user, savedEntity.getId(), savedEntity, null, |
||||
|
oldEntity == null ? ActionType.ADDED : ActionType.UPDATED, null); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
protected NotificationTarget deepCopy(NotificationTarget notificationTarget) { |
||||
|
return new NotificationTarget(notificationTarget); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public EntityType getEntityType() { |
||||
|
return EntityType.NOTIFICATION_TARGET; |
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,72 @@ |
|||||
|
/** |
||||
|
* 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.sync.ie.importing.impl; |
||||
|
|
||||
|
import lombok.RequiredArgsConstructor; |
||||
|
import org.springframework.stereotype.Service; |
||||
|
import org.thingsboard.server.common.data.EntityType; |
||||
|
import org.thingsboard.server.common.data.User; |
||||
|
import org.thingsboard.server.common.data.audit.ActionType; |
||||
|
import org.thingsboard.server.common.data.exception.ThingsboardException; |
||||
|
import org.thingsboard.server.common.data.id.NotificationTemplateId; |
||||
|
import org.thingsboard.server.common.data.id.TenantId; |
||||
|
import org.thingsboard.server.common.data.notification.template.NotificationTemplate; |
||||
|
import org.thingsboard.server.common.data.sync.ie.EntityExportData; |
||||
|
import org.thingsboard.server.dao.notification.NotificationTemplateService; |
||||
|
import org.thingsboard.server.dao.service.ConstraintValidator; |
||||
|
import org.thingsboard.server.queue.util.TbCoreComponent; |
||||
|
import org.thingsboard.server.service.sync.vc.data.EntitiesImportCtx; |
||||
|
|
||||
|
@Service |
||||
|
@TbCoreComponent |
||||
|
@RequiredArgsConstructor |
||||
|
public class NotificationTemplateImportService extends BaseEntityImportService<NotificationTemplateId, NotificationTemplate, EntityExportData<NotificationTemplate>> { |
||||
|
|
||||
|
private final NotificationTemplateService notificationTemplateService; |
||||
|
|
||||
|
@Override |
||||
|
protected void setOwner(TenantId tenantId, NotificationTemplate notificationTemplate, IdProvider idProvider) { |
||||
|
notificationTemplate.setTenantId(tenantId); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
protected NotificationTemplate prepare(EntitiesImportCtx ctx, NotificationTemplate notificationTemplate, NotificationTemplate oldEntity, EntityExportData<NotificationTemplate> exportData, IdProvider idProvider) { |
||||
|
return notificationTemplate; |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
protected NotificationTemplate saveOrUpdate(EntitiesImportCtx ctx, NotificationTemplate notificationTemplate, EntityExportData<NotificationTemplate> exportData, IdProvider idProvider) { |
||||
|
ConstraintValidator.validateFields(notificationTemplate); |
||||
|
return notificationTemplateService.saveNotificationTemplate(ctx.getTenantId(), notificationTemplate); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
protected void onEntitySaved(User user, NotificationTemplate savedEntity, NotificationTemplate oldEntity) throws ThingsboardException { |
||||
|
entityActionService.logEntityAction(user, savedEntity.getId(), savedEntity, null, |
||||
|
oldEntity == null ? ActionType.ADDED : ActionType.UPDATED, null); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
protected NotificationTemplate deepCopy(NotificationTemplate notificationTemplate) { |
||||
|
return new NotificationTemplate(notificationTemplate); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public EntityType getEntityType() { |
||||
|
return EntityType.NOTIFICATION_TEMPLATE; |
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,110 @@ |
|||||
|
/** |
||||
|
* 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.limits; |
||||
|
|
||||
|
import org.junit.Before; |
||||
|
import org.junit.Test; |
||||
|
import org.junit.runner.RunWith; |
||||
|
import org.mockito.Mockito; |
||||
|
import org.mockito.junit.MockitoJUnitRunner; |
||||
|
import org.thingsboard.server.common.data.TenantProfile; |
||||
|
import org.thingsboard.server.common.data.id.CustomerId; |
||||
|
import org.thingsboard.server.common.data.id.NotificationRuleId; |
||||
|
import org.thingsboard.server.common.data.id.TenantId; |
||||
|
import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration; |
||||
|
import org.thingsboard.server.common.data.tenant.profile.TenantProfileData; |
||||
|
import org.thingsboard.server.dao.tenant.TbTenantProfileCache; |
||||
|
import org.thingsboard.server.dao.util.limits.DefaultRateLimitService; |
||||
|
import org.thingsboard.server.dao.util.limits.LimitedApi; |
||||
|
import org.thingsboard.server.dao.util.limits.RateLimitService; |
||||
|
|
||||
|
import java.util.List; |
||||
|
import java.util.UUID; |
||||
|
|
||||
|
import static org.junit.Assert.assertFalse; |
||||
|
import static org.junit.Assert.assertTrue; |
||||
|
import static org.mockito.ArgumentMatchers.eq; |
||||
|
import static org.mockito.Mockito.reset; |
||||
|
import static org.mockito.Mockito.when; |
||||
|
|
||||
|
@RunWith(MockitoJUnitRunner.class) |
||||
|
public class RateLimitServiceTest { |
||||
|
|
||||
|
private RateLimitService rateLimitService; |
||||
|
private TbTenantProfileCache tenantProfileCache; |
||||
|
private TenantId tenantId; |
||||
|
|
||||
|
@Before |
||||
|
public void beforeEach() { |
||||
|
tenantProfileCache = Mockito.mock(TbTenantProfileCache.class); |
||||
|
rateLimitService = new DefaultRateLimitService(tenantProfileCache, 60, 100); |
||||
|
tenantId = new TenantId(UUID.randomUUID()); |
||||
|
} |
||||
|
|
||||
|
@Test |
||||
|
public void testRateLimits() { |
||||
|
int max = 2; |
||||
|
String rateLimit = max + ":600"; |
||||
|
DefaultTenantProfileConfiguration profileConfiguration = new DefaultTenantProfileConfiguration(); |
||||
|
profileConfiguration.setTenantEntityExportRateLimit(rateLimit); |
||||
|
profileConfiguration.setTenantEntityImportRateLimit(rateLimit); |
||||
|
profileConfiguration.setTenantNotificationRequestsRateLimit(rateLimit); |
||||
|
profileConfiguration.setTenantNotificationRequestsPerRuleRateLimit(rateLimit); |
||||
|
profileConfiguration.setTenantServerRestLimitsConfiguration(rateLimit); |
||||
|
profileConfiguration.setCustomerServerRestLimitsConfiguration(rateLimit); |
||||
|
profileConfiguration.setWsUpdatesPerSessionRateLimit(rateLimit); |
||||
|
profileConfiguration.setCassandraQueryTenantRateLimitsConfiguration(rateLimit); |
||||
|
updateTenantProfileConfiguration(profileConfiguration); |
||||
|
|
||||
|
for (LimitedApi limitedApi : List.of( |
||||
|
LimitedApi.ENTITY_EXPORT, |
||||
|
LimitedApi.ENTITY_IMPORT, |
||||
|
LimitedApi.NOTIFICATION_REQUESTS, |
||||
|
LimitedApi.REST_REQUESTS, |
||||
|
LimitedApi.CASSANDRA_QUERIES |
||||
|
)) { |
||||
|
testRateLimits(limitedApi, max, tenantId); |
||||
|
} |
||||
|
|
||||
|
CustomerId customerId = new CustomerId(UUID.randomUUID()); |
||||
|
testRateLimits(LimitedApi.REST_REQUESTS, max, customerId); |
||||
|
|
||||
|
NotificationRuleId notificationRuleId = new NotificationRuleId(UUID.randomUUID()); |
||||
|
testRateLimits(LimitedApi.NOTIFICATION_REQUESTS_PER_RULE, max, notificationRuleId); |
||||
|
|
||||
|
String wsSessionId = UUID.randomUUID().toString(); |
||||
|
testRateLimits(LimitedApi.WS_UPDATES_PER_SESSION, max, wsSessionId); |
||||
|
} |
||||
|
|
||||
|
private void testRateLimits(LimitedApi limitedApi, int max, Object level) { |
||||
|
for (int i = 1; i <= max; i++) { |
||||
|
boolean success = rateLimitService.checkRateLimit(limitedApi, tenantId, level); |
||||
|
assertTrue(success); |
||||
|
} |
||||
|
boolean success = rateLimitService.checkRateLimit(limitedApi, tenantId, level); |
||||
|
assertFalse(success); |
||||
|
} |
||||
|
|
||||
|
private void updateTenantProfileConfiguration(DefaultTenantProfileConfiguration profileConfiguration) { |
||||
|
reset(tenantProfileCache); |
||||
|
TenantProfile tenantProfile = new TenantProfile(); |
||||
|
TenantProfileData profileData = new TenantProfileData(); |
||||
|
profileData.setConfiguration(profileConfiguration); |
||||
|
tenantProfile.setProfileData(profileData); |
||||
|
when(tenantProfileCache.get(eq(tenantId))).thenReturn(tenantProfile); |
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,66 @@ |
|||||
|
/** |
||||
|
* 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.security.auth.oauth2; |
||||
|
|
||||
|
import org.junit.Before; |
||||
|
import org.junit.Test; |
||||
|
import org.mockito.Mockito; |
||||
|
|
||||
|
import javax.servlet.http.Cookie; |
||||
|
import javax.servlet.http.HttpServletRequest; |
||||
|
import java.io.IOException; |
||||
|
import java.io.ObjectInputStream; |
||||
|
import java.io.Serializable; |
||||
|
|
||||
|
import static org.junit.Assert.assertEquals; |
||||
|
import static org.thingsboard.server.service.security.auth.oauth2.HttpCookieOAuth2AuthorizationRequestRepository.OAUTH2_AUTHORIZATION_REQUEST_COOKIE_NAME; |
||||
|
|
||||
|
public class HttpCookieOAuth2AuthorizationRequestRepositoryTest { |
||||
|
|
||||
|
private static final String SERIALIZED_ATTACK_STRING = |
||||
|
"rO0ABXNyAHVvcmcudGhpbmdzYm9hcmQuc2VydmVyLnNlcnZpY2Uuc2VjdXJpdHkuYXV0aC5vYXV0aDIuSHR0cENvb2tpZU9BdXRoMkF1dGhvcml6YXRpb25SZXF1ZXN0UmVwb3NpdG9yeVRlc3QkTWFsaWNpb3VzQ2xhc3MAAAAAAAAAAAIAAHhw"; |
||||
|
|
||||
|
private static int maliciousMethodInvocationCounter; |
||||
|
|
||||
|
@Before |
||||
|
public void resetInvocationCounter() { |
||||
|
maliciousMethodInvocationCounter = 0; |
||||
|
} |
||||
|
|
||||
|
@Test |
||||
|
public void whenLoadAuthorizationRequest_thenMaliciousMethodNotInvoked() { |
||||
|
HttpCookieOAuth2AuthorizationRequestRepository cookieRequestRepo = new HttpCookieOAuth2AuthorizationRequestRepository(); |
||||
|
HttpServletRequest request = Mockito.mock(HttpServletRequest.class); |
||||
|
Cookie cookie = new Cookie(OAUTH2_AUTHORIZATION_REQUEST_COOKIE_NAME, SERIALIZED_ATTACK_STRING); |
||||
|
Mockito.when(request.getCookies()).thenReturn(new Cookie[]{cookie}); |
||||
|
|
||||
|
cookieRequestRepo.loadAuthorizationRequest(request); |
||||
|
|
||||
|
assertEquals(0, maliciousMethodInvocationCounter); |
||||
|
} |
||||
|
|
||||
|
private static class MaliciousClass implements Serializable { |
||||
|
private static final long serialVersionUID = 0L; |
||||
|
|
||||
|
public void maliciousMethod() { |
||||
|
maliciousMethodInvocationCounter++; |
||||
|
} |
||||
|
|
||||
|
private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException { |
||||
|
maliciousMethod(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,108 @@ |
|||||
|
/** |
||||
|
* 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; |
||||
|
|
||||
|
import com.fasterxml.jackson.annotation.JsonIgnore; |
||||
|
import com.fasterxml.jackson.core.JsonProcessingException; |
||||
|
import com.fasterxml.jackson.databind.JsonNode; |
||||
|
import com.fasterxml.jackson.databind.ObjectMapper; |
||||
|
import lombok.extern.slf4j.Slf4j; |
||||
|
import org.thingsboard.server.common.data.id.UUIDBased; |
||||
|
import org.thingsboard.server.common.data.validation.NoXss; |
||||
|
|
||||
|
import java.io.ByteArrayInputStream; |
||||
|
import java.io.IOException; |
||||
|
import java.util.Arrays; |
||||
|
import java.util.Objects; |
||||
|
import java.util.function.Consumer; |
||||
|
import java.util.function.Supplier; |
||||
|
|
||||
|
/** |
||||
|
* Created by ashvayka on 19.02.18. |
||||
|
*/ |
||||
|
@Slf4j |
||||
|
public abstract class BaseDataWithAdditionalInfo<I extends UUIDBased> extends BaseData<I> implements HasAdditionalInfo { |
||||
|
|
||||
|
public static final ObjectMapper mapper = new ObjectMapper(); |
||||
|
@NoXss |
||||
|
private transient JsonNode additionalInfo; |
||||
|
@JsonIgnore |
||||
|
private byte[] additionalInfoBytes; |
||||
|
|
||||
|
public BaseDataWithAdditionalInfo() { |
||||
|
super(); |
||||
|
} |
||||
|
|
||||
|
public BaseDataWithAdditionalInfo(I id) { |
||||
|
super(id); |
||||
|
} |
||||
|
|
||||
|
public BaseDataWithAdditionalInfo(BaseDataWithAdditionalInfo<I> baseData) { |
||||
|
super(baseData); |
||||
|
setAdditionalInfo(baseData.getAdditionalInfo()); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public JsonNode getAdditionalInfo() { |
||||
|
return getJson(() -> additionalInfo, () -> additionalInfoBytes); |
||||
|
} |
||||
|
|
||||
|
public void setAdditionalInfo(JsonNode addInfo) { |
||||
|
setJson(addInfo, json -> this.additionalInfo = json, bytes -> this.additionalInfoBytes = bytes); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public boolean equals(Object o) { |
||||
|
if (this == o) return true; |
||||
|
if (o == null || getClass() != o.getClass()) return false; |
||||
|
if (!super.equals(o)) return false; |
||||
|
BaseDataWithAdditionalInfo<?> that = (BaseDataWithAdditionalInfo<?>) o; |
||||
|
return Arrays.equals(additionalInfoBytes, that.additionalInfoBytes); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public int hashCode() { |
||||
|
return Objects.hash(super.hashCode(), additionalInfoBytes); |
||||
|
} |
||||
|
|
||||
|
public static JsonNode getJson(Supplier<JsonNode> jsonData, Supplier<byte[]> binaryData) { |
||||
|
JsonNode json = jsonData.get(); |
||||
|
if (json != null) { |
||||
|
return json; |
||||
|
} else { |
||||
|
byte[] data = binaryData.get(); |
||||
|
if (data != null) { |
||||
|
try { |
||||
|
return mapper.readTree(new ByteArrayInputStream(data)); |
||||
|
} catch (IOException e) { |
||||
|
log.warn("Can't deserialize json data: ", e); |
||||
|
return null; |
||||
|
} |
||||
|
} else { |
||||
|
return null; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public static void setJson(JsonNode json, Consumer<JsonNode> jsonConsumer, Consumer<byte[]> bytesConsumer) { |
||||
|
jsonConsumer.accept(json); |
||||
|
try { |
||||
|
bytesConsumer.accept(mapper.writeValueAsBytes(json)); |
||||
|
} catch (JsonProcessingException e) { |
||||
|
log.warn("Can't serialize json data: ", e); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,31 @@ |
|||||
|
/** |
||||
|
* 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.exception; |
||||
|
|
||||
|
import lombok.Getter; |
||||
|
import org.thingsboard.server.common.data.id.TenantId; |
||||
|
|
||||
|
public class TenantProfileNotFoundException extends RuntimeException { |
||||
|
|
||||
|
@Getter |
||||
|
private final TenantId tenantId; |
||||
|
|
||||
|
public TenantProfileNotFoundException(TenantId tenantId) { |
||||
|
super("Profile for tenant with id " + tenantId + " not found"); |
||||
|
this.tenantId = tenantId; |
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,272 @@ |
|||||
|
/** |
||||
|
* 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.util; |
||||
|
|
||||
|
import lombok.extern.slf4j.Slf4j; |
||||
|
import org.eclipse.leshan.core.LwM2m; |
||||
|
import org.eclipse.leshan.core.model.DDFFileValidator; |
||||
|
import org.eclipse.leshan.core.model.DefaultDDFFileValidator; |
||||
|
import org.eclipse.leshan.core.model.InvalidDDFFileException; |
||||
|
import org.eclipse.leshan.core.model.ObjectModel; |
||||
|
import org.eclipse.leshan.core.model.ResourceModel; |
||||
|
import org.eclipse.leshan.core.util.StringUtils; |
||||
|
import org.w3c.dom.DOMException; |
||||
|
import org.w3c.dom.Document; |
||||
|
import org.w3c.dom.Node; |
||||
|
import org.w3c.dom.NodeList; |
||||
|
import org.xml.sax.SAXException; |
||||
|
|
||||
|
import javax.xml.parsers.DocumentBuilder; |
||||
|
import javax.xml.parsers.DocumentBuilderFactory; |
||||
|
import javax.xml.parsers.ParserConfigurationException; |
||||
|
import java.io.IOException; |
||||
|
import java.io.InputStream; |
||||
|
import java.util.ArrayList; |
||||
|
import java.util.HashMap; |
||||
|
import java.util.List; |
||||
|
import java.util.Map; |
||||
|
|
||||
|
@Slf4j |
||||
|
public class TbDDFFileParser { |
||||
|
private static final DDFFileValidator ddfFileValidator = new DefaultDDFFileValidator(); |
||||
|
|
||||
|
public List<ObjectModel> parse(InputStream inputStream, String streamName) |
||||
|
throws InvalidDDFFileException, IOException { |
||||
|
streamName = streamName == null ? "" : streamName; |
||||
|
|
||||
|
log.debug("Parsing DDF file {}", streamName); |
||||
|
|
||||
|
try { |
||||
|
// Parse XML file
|
||||
|
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); |
||||
|
factory.setNamespaceAware(true); |
||||
|
factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true); |
||||
|
|
||||
|
DocumentBuilder builder = factory.newDocumentBuilder(); |
||||
|
Document document = builder.parse(inputStream); |
||||
|
|
||||
|
// Get DDF file validator
|
||||
|
LwM2m.LwM2mVersion lwm2mVersion = null; |
||||
|
ddfFileValidator.validate(document); |
||||
|
|
||||
|
// Build list of ObjectModel
|
||||
|
ArrayList<ObjectModel> objects = new ArrayList<>(); |
||||
|
NodeList nodeList = document.getDocumentElement().getElementsByTagName("Object"); |
||||
|
for (int i = 0; i < nodeList.getLength(); i++) { |
||||
|
objects.add(parseObject(nodeList.item(i), streamName, lwm2mVersion, true)); |
||||
|
} |
||||
|
return objects; |
||||
|
} catch (InvalidDDFFileException | SAXException e) { |
||||
|
throw new InvalidDDFFileException(e, "Invalid DDF file %s", streamName); |
||||
|
} |
||||
|
catch (ParserConfigurationException e) { |
||||
|
throw new IllegalStateException("Unable to create Document Builder", e); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private ObjectModel parseObject(Node object, String streamName, LwM2m.LwM2mVersion schemaVersion, boolean validate) |
||||
|
throws InvalidDDFFileException { |
||||
|
|
||||
|
Node objectType = object.getAttributes().getNamedItem("ObjectType"); |
||||
|
if (validate && (objectType == null || !"MODefinition".equals(objectType.getTextContent()))) { |
||||
|
throw new InvalidDDFFileException( |
||||
|
"Object element in %s MUST have a ObjectType attribute equals to 'MODefinition'.", streamName); |
||||
|
} |
||||
|
|
||||
|
Integer id = null; |
||||
|
String name = null; |
||||
|
String description = null; |
||||
|
String version = ObjectModel.DEFAULT_VERSION; |
||||
|
Boolean multiple = null; |
||||
|
Boolean mandatory = null; |
||||
|
Map<Integer, ResourceModel> resources = new HashMap<>(); |
||||
|
String urn = null; |
||||
|
String description2 = null; |
||||
|
String lwm2mVersion = ObjectModel.DEFAULT_VERSION; |
||||
|
|
||||
|
for (int i = 0; i < object.getChildNodes().getLength(); i++) { |
||||
|
Node field = object.getChildNodes().item(i); |
||||
|
if (field.getNodeType() != Node.ELEMENT_NODE) |
||||
|
continue; |
||||
|
|
||||
|
switch (field.getNodeName()) { |
||||
|
case "ObjectID": |
||||
|
id = Integer.valueOf(field.getTextContent()); |
||||
|
break; |
||||
|
case "Name": |
||||
|
name = field.getTextContent(); |
||||
|
break; |
||||
|
case "Description1": |
||||
|
description = field.getTextContent(); |
||||
|
break; |
||||
|
case "ObjectVersion": |
||||
|
if (!StringUtils.isEmpty(field.getTextContent())) { |
||||
|
version = field.getTextContent(); |
||||
|
} |
||||
|
break; |
||||
|
case "MultipleInstances": |
||||
|
if ("Multiple".equals(field.getTextContent())) { |
||||
|
multiple = true; |
||||
|
} else if ("Single".equals(field.getTextContent())) { |
||||
|
multiple = false; |
||||
|
} |
||||
|
break; |
||||
|
case "Mandatory": |
||||
|
if ("Mandatory".equals(field.getTextContent())) { |
||||
|
mandatory = true; |
||||
|
} else if ("Optional".equals(field.getTextContent())) { |
||||
|
mandatory = false; |
||||
|
} |
||||
|
break; |
||||
|
case "Resources": |
||||
|
for (int j = 0; j < field.getChildNodes().getLength(); j++) { |
||||
|
Node item = field.getChildNodes().item(j); |
||||
|
if (item.getNodeType() != Node.ELEMENT_NODE) |
||||
|
continue; |
||||
|
|
||||
|
if (item.getNodeName().equals("Item")) { |
||||
|
ResourceModel resource = parseResource(item, streamName); |
||||
|
if (validate && resources.containsKey(resource.id)) { |
||||
|
throw new InvalidDDFFileException( |
||||
|
"Object %s in %s contains at least 2 resources with same id %s.", |
||||
|
id != null ? id : "", streamName, resource.id); |
||||
|
} else { |
||||
|
resources.put(resource.id, resource); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
break; |
||||
|
case "ObjectURN": |
||||
|
urn = field.getTextContent(); |
||||
|
break; |
||||
|
case "LWM2MVersion": |
||||
|
if (!StringUtils.isEmpty(field.getTextContent())) { |
||||
|
lwm2mVersion = field.getTextContent(); |
||||
|
if (schemaVersion != null && !schemaVersion.toString().equals(lwm2mVersion)) { |
||||
|
throw new InvalidDDFFileException( |
||||
|
"LWM2MVersion is not consistent with xml shema(xsi:noNamespaceSchemaLocation) in %s : %s expected but was %s.", |
||||
|
streamName, schemaVersion, lwm2mVersion); |
||||
|
} |
||||
|
} |
||||
|
break; |
||||
|
case "Description2": |
||||
|
description2 = field.getTextContent(); |
||||
|
break; |
||||
|
default: |
||||
|
break; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return new ObjectModel(id, name, description, version, multiple, mandatory, resources.values(), urn, |
||||
|
lwm2mVersion, description2); |
||||
|
|
||||
|
} |
||||
|
|
||||
|
private ResourceModel parseResource(Node item, String streamName) throws DOMException, InvalidDDFFileException { |
||||
|
|
||||
|
Integer id = Integer.valueOf(item.getAttributes().getNamedItem("ID").getTextContent()); |
||||
|
String name = null; |
||||
|
ResourceModel.Operations operations = null; |
||||
|
Boolean multiple = false; |
||||
|
Boolean mandatory = false; |
||||
|
ResourceModel.Type type = null; |
||||
|
String rangeEnumeration = null; |
||||
|
String units = null; |
||||
|
String description = null; |
||||
|
|
||||
|
for (int i = 0; i < item.getChildNodes().getLength(); i++) { |
||||
|
Node field = item.getChildNodes().item(i); |
||||
|
if (field.getNodeType() != Node.ELEMENT_NODE) |
||||
|
continue; |
||||
|
|
||||
|
switch (field.getNodeName()) { |
||||
|
case "Name": |
||||
|
name = field.getTextContent(); |
||||
|
break; |
||||
|
case "Operations": |
||||
|
String strOp = field.getTextContent(); |
||||
|
if (strOp != null && !strOp.isEmpty()) { |
||||
|
operations = ResourceModel.Operations.valueOf(strOp); |
||||
|
} else { |
||||
|
operations = ResourceModel.Operations.NONE; |
||||
|
} |
||||
|
break; |
||||
|
case "MultipleInstances": |
||||
|
if ("Multiple".equals(field.getTextContent())) { |
||||
|
multiple = true; |
||||
|
} else if ("Single".equals(field.getTextContent())) { |
||||
|
multiple = false; |
||||
|
} |
||||
|
break; |
||||
|
case "Mandatory": |
||||
|
if ("Mandatory".equals(field.getTextContent())) { |
||||
|
mandatory = true; |
||||
|
} else if ("Optional".equals(field.getTextContent())) { |
||||
|
mandatory = false; |
||||
|
} |
||||
|
break; |
||||
|
case "Type": |
||||
|
switch (field.getTextContent()) { |
||||
|
case "String": |
||||
|
type = ResourceModel.Type.STRING; |
||||
|
break; |
||||
|
case "Integer": |
||||
|
type = ResourceModel.Type.INTEGER; |
||||
|
break; |
||||
|
case "Float": |
||||
|
type = ResourceModel.Type.FLOAT; |
||||
|
break; |
||||
|
case "Boolean": |
||||
|
type = ResourceModel.Type.BOOLEAN; |
||||
|
break; |
||||
|
case "Opaque": |
||||
|
type = ResourceModel.Type.OPAQUE; |
||||
|
break; |
||||
|
case "Time": |
||||
|
type = ResourceModel.Type.TIME; |
||||
|
break; |
||||
|
case "Objlnk": |
||||
|
type = ResourceModel.Type.OBJLNK; |
||||
|
break; |
||||
|
case "Unsigned Integer": |
||||
|
type = ResourceModel.Type.UNSIGNED_INTEGER; |
||||
|
break; |
||||
|
case "Corelnk": |
||||
|
type = ResourceModel.Type.CORELINK; |
||||
|
break; |
||||
|
case "": |
||||
|
type = ResourceModel.Type.NONE; |
||||
|
break; |
||||
|
default: |
||||
|
break; |
||||
|
} |
||||
|
break; |
||||
|
case "RangeEnumeration": |
||||
|
rangeEnumeration = field.getTextContent(); |
||||
|
break; |
||||
|
case "Units": |
||||
|
units = field.getTextContent(); |
||||
|
break; |
||||
|
case "Description": |
||||
|
description = field.getTextContent(); |
||||
|
break; |
||||
|
default: |
||||
|
break; |
||||
|
} |
||||
|
} |
||||
|
return new ResourceModel(id, name, operations, multiple, mandatory, type, rangeEnumeration, units, description); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,67 @@ |
|||||
|
/** |
||||
|
* 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,42 @@ |
|||||
|
/** |
||||
|
* 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.msa.ui.tabs; |
||||
|
|
||||
|
import org.openqa.selenium.WebDriver; |
||||
|
import org.openqa.selenium.WebElement; |
||||
|
import org.thingsboard.server.msa.ui.base.AbstractBasePage; |
||||
|
|
||||
|
public class AssignDeviceTabElements extends AbstractBasePage { |
||||
|
public AssignDeviceTabElements(WebDriver driver) { |
||||
|
super(driver); |
||||
|
} |
||||
|
|
||||
|
private static final String ASSIGN_ON_CUSTOMER_FIELD = "//input[@formcontrolname='entity']"; |
||||
|
private static final String CUSTOMER_FROM_DROPDOWN = "//div[@role='listbox']/mat-option//span[contains(text(),'%s')]"; |
||||
|
private static final String ASSIGN_BTN = "//button[@type='submit']"; |
||||
|
|
||||
|
public WebElement assignOnCustomerField() { |
||||
|
return waitUntilElementToBeClickable(ASSIGN_ON_CUSTOMER_FIELD); |
||||
|
} |
||||
|
|
||||
|
public WebElement customerFromDropDown(String entityName) { |
||||
|
return waitUntilVisibilityOfElementLocated(String.format(CUSTOMER_FROM_DROPDOWN, entityName)); |
||||
|
} |
||||
|
|
||||
|
public WebElement assignBtn() { |
||||
|
return waitUntilElementToBeClickable(ASSIGN_BTN); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,30 @@ |
|||||
|
/** |
||||
|
* 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.msa.ui.tabs; |
||||
|
|
||||
|
import org.openqa.selenium.WebDriver; |
||||
|
|
||||
|
public class AssignDeviceTabHelper extends AssignDeviceTabElements { |
||||
|
public AssignDeviceTabHelper(WebDriver driver) { |
||||
|
super(driver); |
||||
|
} |
||||
|
|
||||
|
public void assignOnCustomer(String customerTitle) { |
||||
|
assignOnCustomerField().click(); |
||||
|
customerFromDropDown(customerTitle).click(); |
||||
|
assignBtn().click(); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,97 @@ |
|||||
|
/** |
||||
|
* 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.msa.ui.tabs; |
||||
|
|
||||
|
import org.openqa.selenium.WebDriver; |
||||
|
import org.openqa.selenium.WebElement; |
||||
|
import org.thingsboard.server.msa.ui.base.AbstractBasePage; |
||||
|
|
||||
|
public class CreateDeviceTabElements extends AbstractBasePage { |
||||
|
public CreateDeviceTabElements(WebDriver driver) { |
||||
|
super(driver); |
||||
|
} |
||||
|
|
||||
|
private static final String CREATE_DEVICE_NAME_FIELD = "//tb-device-wizard//input[@formcontrolname='name']"; |
||||
|
private static final String CREATE_NEW_DEVICE_PROFILE_RADIO_BTN = "//span[text() = 'Create new device profile']/ancestor::mat-radio-button"; |
||||
|
private static final String SELECT_EXISTING_DEVICE_PROFILE_RADIO_BTN = "//span[text() = 'Select existing device profile']/ancestor::mat-radio-button"; |
||||
|
private static final String DEVICE_PROFILE_TITLE_FIELD = "//input[@formcontrolname='newDeviceProfileTitle']"; |
||||
|
private static final String ADD_BTN = "//span[text() = 'Add']"; |
||||
|
private static final String CLEAR_PROFILE_FIELD_BTN = "//button[@aria-label='Clear']"; |
||||
|
private static final String ENTITY_FROM_DROPDOWN = "//div[@role = 'listbox']//span[text() = '%s']"; |
||||
|
private static final String ASSIGN_ON_CUSTOMER_FIELD = "//input[@formcontrolname='entity']"; |
||||
|
private static final String CUSTOMER_OPTION_BNT = "//div[text() = 'Customer']/ancestor::mat-step-header"; |
||||
|
private static final String CUSTOMER_FROM_DROPDOWN = "//div[@role='listbox']/mat-option//span[contains(text(),'%s')]"; |
||||
|
private static final String DEVICE_LABEL_FIELD = "//tb-device-wizard//input[@formcontrolname='label']"; |
||||
|
private static final String CHECKBOX_GATEWAY = "//tb-device-wizard//mat-checkbox[@formcontrolname='gateway']//label"; |
||||
|
private static final String CHECKBOX_OVERWRITE_ACTIVITY_TIME = "//tb-device-wizard//mat-checkbox[@formcontrolname='overwriteActivityTime']//label"; |
||||
|
private static final String DESCRIPTION_FIELD = "//tb-device-wizard//textarea[@formcontrolname='description']"; |
||||
|
|
||||
|
public WebElement nameField() { |
||||
|
return waitUntilElementToBeClickable(CREATE_DEVICE_NAME_FIELD); |
||||
|
} |
||||
|
|
||||
|
public WebElement createNewDeviceProfileRadioBtn() { |
||||
|
return waitUntilElementToBeClickable(CREATE_NEW_DEVICE_PROFILE_RADIO_BTN); |
||||
|
} |
||||
|
|
||||
|
public WebElement selectExistingDeviceProfileRadioBtn() { |
||||
|
return waitUntilElementToBeClickable(SELECT_EXISTING_DEVICE_PROFILE_RADIO_BTN); |
||||
|
} |
||||
|
|
||||
|
public WebElement deviceProfileTitleField() { |
||||
|
return waitUntilElementToBeClickable(DEVICE_PROFILE_TITLE_FIELD); |
||||
|
} |
||||
|
|
||||
|
public WebElement addBtn() { |
||||
|
return waitUntilElementToBeClickable(ADD_BTN); |
||||
|
} |
||||
|
|
||||
|
public WebElement clearProfileFieldBtn() { |
||||
|
return waitUntilElementToBeClickable(CLEAR_PROFILE_FIELD_BTN); |
||||
|
} |
||||
|
|
||||
|
public WebElement entityFromDropdown(String customerTitle) { |
||||
|
return waitUntilElementToBeClickable(String.format(ENTITY_FROM_DROPDOWN, customerTitle)); |
||||
|
} |
||||
|
|
||||
|
public WebElement assignOnCustomerField() { |
||||
|
return waitUntilElementToBeClickable(ASSIGN_ON_CUSTOMER_FIELD); |
||||
|
} |
||||
|
|
||||
|
public WebElement customerOptionBtn() { |
||||
|
return waitUntilElementToBeClickable(CUSTOMER_OPTION_BNT); |
||||
|
} |
||||
|
|
||||
|
public WebElement customerFromDropDown(String entityName) { |
||||
|
return waitUntilVisibilityOfElementLocated(String.format(CUSTOMER_FROM_DROPDOWN, entityName)); |
||||
|
} |
||||
|
|
||||
|
public WebElement deviceLabelField() { |
||||
|
return waitUntilElementToBeClickable(DEVICE_LABEL_FIELD); |
||||
|
} |
||||
|
|
||||
|
public WebElement checkboxGateway() { |
||||
|
return waitUntilElementToBeClickable(CHECKBOX_GATEWAY); |
||||
|
} |
||||
|
|
||||
|
public WebElement checkboxOverwriteActivityTime() { |
||||
|
return waitUntilElementToBeClickable(CHECKBOX_OVERWRITE_ACTIVITY_TIME); |
||||
|
} |
||||
|
|
||||
|
public WebElement descriptionField() { |
||||
|
return waitUntilElementToBeClickable(DESCRIPTION_FIELD); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,58 @@ |
|||||
|
/** |
||||
|
* 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.msa.ui.tabs; |
||||
|
|
||||
|
import org.openqa.selenium.WebDriver; |
||||
|
|
||||
|
public class CreateDeviceTabHelper extends CreateDeviceTabElements { |
||||
|
public CreateDeviceTabHelper(WebDriver driver) { |
||||
|
super(driver); |
||||
|
} |
||||
|
|
||||
|
public void enterName(String deviceName) { |
||||
|
enterText(nameField(), deviceName); |
||||
|
} |
||||
|
|
||||
|
public void createNewDeviceProfile(String deviceProfileTitle) { |
||||
|
if (!createNewDeviceProfileRadioBtn().getAttribute("class").contains("checked")) { |
||||
|
createNewDeviceProfileRadioBtn().click(); |
||||
|
} |
||||
|
deviceProfileTitleField().sendKeys(deviceProfileTitle); |
||||
|
} |
||||
|
|
||||
|
public void changeDeviceProfile(String deviceProfileName) { |
||||
|
if (!selectExistingDeviceProfileRadioBtn().getAttribute("class").contains("checked")) { |
||||
|
selectExistingDeviceProfileRadioBtn().click(); |
||||
|
} |
||||
|
clearProfileFieldBtn().click(); |
||||
|
entityFromDropdown(deviceProfileName).click(); |
||||
|
} |
||||
|
|
||||
|
public void assignOnCustomer(String customerTitle) { |
||||
|
customerOptionBtn().click(); |
||||
|
assignOnCustomerField().click(); |
||||
|
customerFromDropDown(customerTitle).click(); |
||||
|
sleep(2); //waiting for the action to count
|
||||
|
} |
||||
|
|
||||
|
public void enterLabel(String label) { |
||||
|
enterText(deviceLabelField(), label); |
||||
|
} |
||||
|
|
||||
|
public void enterDescription(String description) { |
||||
|
enterText(descriptionField(), description); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,193 @@ |
|||||
|
/** |
||||
|
* 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.msa.ui.tests.devicessmoke; |
||||
|
|
||||
|
import io.qameta.allure.Description; |
||||
|
import io.qameta.allure.Feature; |
||||
|
import org.openqa.selenium.WebElement; |
||||
|
import org.testng.annotations.AfterClass; |
||||
|
import org.testng.annotations.BeforeClass; |
||||
|
import org.testng.annotations.BeforeMethod; |
||||
|
import org.testng.annotations.Test; |
||||
|
import org.thingsboard.server.common.data.Customer; |
||||
|
import org.thingsboard.server.common.data.Device; |
||||
|
import org.thingsboard.server.common.data.id.CustomerId; |
||||
|
import org.thingsboard.server.msa.ui.pages.CustomerPageHelper; |
||||
|
import org.thingsboard.server.msa.ui.tabs.AssignDeviceTabHelper; |
||||
|
import org.thingsboard.server.msa.ui.utils.EntityPrototypes; |
||||
|
|
||||
|
import java.util.List; |
||||
|
|
||||
|
import static org.assertj.core.api.Assertions.assertThat; |
||||
|
import static org.thingsboard.server.msa.ui.base.AbstractBasePage.random; |
||||
|
import static org.thingsboard.server.msa.ui.utils.Const.ENTITY_NAME; |
||||
|
|
||||
|
@Feature("Assign to customer") |
||||
|
public class AssignToCustomerTest extends AbstractDeviceTest { |
||||
|
|
||||
|
private AssignDeviceTabHelper assignDeviceTab; |
||||
|
private CustomerPageHelper customerPage; |
||||
|
private CustomerId customerId; |
||||
|
private Device device; |
||||
|
private Device device1; |
||||
|
private String customerName; |
||||
|
|
||||
|
@BeforeClass |
||||
|
public void create() { |
||||
|
assignDeviceTab = new AssignDeviceTabHelper(driver); |
||||
|
customerPage = new CustomerPageHelper(driver); |
||||
|
Customer customer = testRestClient.postCustomer(EntityPrototypes.defaultCustomerPrototype(ENTITY_NAME + random())); |
||||
|
customerId = customer.getId(); |
||||
|
customerName = customer.getName(); |
||||
|
device1 = testRestClient.postDevice("", EntityPrototypes.defaultDevicePrototype("Device " + random())); |
||||
|
} |
||||
|
|
||||
|
@AfterClass |
||||
|
public void deleteCustomer() { |
||||
|
deleteCustomerById(customerId); |
||||
|
deleteCustomerByName("Public"); |
||||
|
deleteDeviceByName(device1.getName()); |
||||
|
} |
||||
|
|
||||
|
@BeforeMethod |
||||
|
public void createDevice() { |
||||
|
device = testRestClient.postDevice("", EntityPrototypes.defaultDevicePrototype(ENTITY_NAME)); |
||||
|
deviceName = device.getName(); |
||||
|
} |
||||
|
|
||||
|
@Test(groups = "smoke") |
||||
|
@Description("Assign to customer by right side of device btn") |
||||
|
public void assignToCustomerByRightSideBtn() { |
||||
|
sideBarMenuView.goToDevicesPage(); |
||||
|
devicePage.assignBtn(deviceName).click(); |
||||
|
assignDeviceTab.assignOnCustomer(customerName); |
||||
|
assertIsDisplayed(devicePage.deviceCustomerOnPage(deviceName)); |
||||
|
assertThat(devicePage.deviceCustomerOnPage(deviceName).getText()) |
||||
|
.as("Customer added correctly").isEqualTo(customerName); |
||||
|
|
||||
|
sideBarMenuView.customerBtn().click(); |
||||
|
customerPage.manageCustomersDevicesBtn(customerName).click(); |
||||
|
assertIsDisplayed(devicePage.device(deviceName)); |
||||
|
} |
||||
|
|
||||
|
@Test(groups = "smoke") |
||||
|
@Description("Assign to customer by 'Assign to customer' btn on details tab") |
||||
|
public void assignToCustomerFromDetailsTab() { |
||||
|
sideBarMenuView.goToDevicesPage(); |
||||
|
devicePage.device(deviceName).click(); |
||||
|
devicePage.assignBtnDetailsTab().click(); |
||||
|
assignDeviceTab.assignOnCustomer(customerName); |
||||
|
String customerInAssignedField = devicePage.assignFieldDetailsTab().getAttribute("value"); |
||||
|
devicePage.closeDeviceDetailsViewBtn().click(); |
||||
|
assertIsDisplayed(devicePage.deviceCustomerOnPage(deviceName)); |
||||
|
assertThat(devicePage.deviceCustomerOnPage(deviceName).getText()) |
||||
|
.as("Customer added correctly").isEqualTo(customerName); |
||||
|
assertThat(customerInAssignedField) |
||||
|
.as("Customer in details tab added correctly").isEqualTo(customerName); |
||||
|
|
||||
|
sideBarMenuView.customerBtn().click(); |
||||
|
customerPage.manageCustomersDevicesBtn(customerName).click(); |
||||
|
assertIsDisplayed(devicePage.device(deviceName)); |
||||
|
} |
||||
|
|
||||
|
@Test(groups = "smoke") |
||||
|
@Description("Assign marked device by btn on the top") |
||||
|
public void assignToCustomerMarkedDevice() { |
||||
|
sideBarMenuView.goToDevicesPage(); |
||||
|
devicePage.assignSelectedDevices(deviceName); |
||||
|
assignDeviceTab.assignOnCustomer(customerName); |
||||
|
assertIsDisplayed(devicePage.deviceCustomerOnPage(deviceName)); |
||||
|
assertThat(devicePage.deviceCustomerOnPage(deviceName).getText()) |
||||
|
.as("Customer added correctly").isEqualTo(customerName); |
||||
|
|
||||
|
sideBarMenuView.customerBtn().click(); |
||||
|
customerPage.manageCustomersDevicesBtn(customerName).click(); |
||||
|
assertIsDisplayed(devicePage.device(deviceName)); |
||||
|
} |
||||
|
|
||||
|
@Test(groups = "smoke") |
||||
|
@Description("Unassign from customer by right side of device btn") |
||||
|
public void unassignedFromCustomerByRightSideBtn() { |
||||
|
device.setCustomerId(customerId); |
||||
|
testRestClient.postDevice("", device); |
||||
|
|
||||
|
sideBarMenuView.goToDevicesPage(); |
||||
|
WebElement element = devicePage.deviceCustomerOnPage(deviceName); |
||||
|
devicePage.unassignedDeviceByRightSideBtn(deviceName); |
||||
|
assertInvisibilityOfElement(element); |
||||
|
|
||||
|
sideBarMenuView.customerBtn().click(); |
||||
|
customerPage.manageCustomersDevicesBtn(customerName).click(); |
||||
|
devicePage.assertEntityIsNotPresent(deviceName); |
||||
|
} |
||||
|
|
||||
|
@Test(groups = "smoke") |
||||
|
@Description("Unassign from customer by 'Unassign from customer' btn on details tab") |
||||
|
public void unassignedFromCustomerFromDetailsTab() { |
||||
|
device.setCustomerId(customerId); |
||||
|
testRestClient.postDevice("", device); |
||||
|
|
||||
|
sideBarMenuView.goToDevicesPage(); |
||||
|
WebElement customerInColumn = devicePage.deviceCustomerOnPage(deviceName); |
||||
|
devicePage.device(deviceName).click(); |
||||
|
WebElement assignFieldDetailsTab = devicePage.assignFieldDetailsTab(); |
||||
|
devicePage.unassignedDeviceFromDetailsTab(); |
||||
|
assertInvisibilityOfElement(customerInColumn); |
||||
|
assertInvisibilityOfElement(assignFieldDetailsTab); |
||||
|
|
||||
|
devicePage.closeDeviceDetailsViewBtn().click(); |
||||
|
sideBarMenuView.customerBtn().click(); |
||||
|
customerPage.manageCustomersDevicesBtn(customerName).click(); |
||||
|
devicePage.assertEntityIsNotPresent(deviceName); |
||||
|
} |
||||
|
|
||||
|
@Test(groups = "smoke") |
||||
|
@Description("Can't assign device on several customer") |
||||
|
public void assignToSeveralCustomer() { |
||||
|
device.setCustomerId(customerId); |
||||
|
testRestClient.postDevice("", device); |
||||
|
sideBarMenuView.goToDevicesPage(); |
||||
|
|
||||
|
assertIsDisable(devicePage.assignBtnVisible(deviceName)); |
||||
|
} |
||||
|
|
||||
|
@Test(groups = "smoke") |
||||
|
@Description("Can't assign public device") |
||||
|
public void assignPublicDevice() { |
||||
|
testRestClient.setDevicePublic(device.getId()); |
||||
|
|
||||
|
sideBarMenuView.goToDevicesPage(); |
||||
|
assertIsDisable(devicePage.assignBtnVisible(deviceName)); |
||||
|
} |
||||
|
|
||||
|
@Test(groups = "smoke") |
||||
|
@Description("Assign several devices by btn on the top") |
||||
|
public void assignSeveralDevices() { |
||||
|
sideBarMenuView.goToDevicesPage(); |
||||
|
devicePage.assignSelectedDevices(deviceName, device1.getName()); |
||||
|
assignDeviceTab.assignOnCustomer(customerName); |
||||
|
assertIsDisplayed(devicePage.deviceCustomerOnPage(deviceName)); |
||||
|
assertThat(devicePage.deviceCustomerOnPage(deviceName).getText()) |
||||
|
.as("Customer added correctly").isEqualTo(customerName); |
||||
|
assertThat(devicePage.deviceCustomerOnPage(device1.getName()).getText()) |
||||
|
.as("Customer added correctly").isEqualTo(customerName); |
||||
|
|
||||
|
sideBarMenuView.customerBtn().click(); |
||||
|
customerPage.manageCustomersDevicesBtn(customerName).click(); |
||||
|
List.of(deviceName, device1.getName()). |
||||
|
forEach(d -> assertIsDisplayed(devicePage.device(d))); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,20 @@ |
|||||
|
diff --git a/node_modules/@angular/core/fesm2020/core.mjs b/node_modules/@angular/core/fesm2020/core.mjs
|
||||
|
index 3e93015..9efcb96 100755
|
||||
|
--- a/node_modules/@angular/core/fesm2020/core.mjs
|
||||
|
+++ b/node_modules/@angular/core/fesm2020/core.mjs
|
||||
|
@@ -11053,13 +11053,13 @@ function findDirectiveDefMatches(tView, tNode) {
|
||||
|
if (isNodeMatchingSelectorList(tNode, def.selectors, /* isProjectionMode */ false)) { |
||||
|
matches || (matches = []); |
||||
|
if (isComponentDef(def)) { |
||||
|
- if (ngDevMode) {
|
||||
|
+ // if (ngDevMode) {
|
||||
|
assertTNodeType(tNode, 2 /* TNodeType.Element */, `"${tNode.value}" tags cannot be used as component hosts. ` + |
||||
|
`Please use a different tag to activate the ${stringify(def.type)} component.`); |
||||
|
if (isComponentHost(tNode)) { |
||||
|
throwMultipleComponentError(tNode, matches.find(isComponentDef).type, def.type); |
||||
|
} |
||||
|
- }
|
||||
|
+ // }
|
||||
|
// Components are inserted at the front of the matches array so that their lifecycle |
||||
|
// hooks run before any directive lifecycle hooks. This appears to be for ViewEngine |
||||
|
// compatibility. This logic doesn't make sense with host directives, because it |
||||
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue