From 8d28a28b08bf9b16794345ccaa908f051d66716a Mon Sep 17 00:00:00 2001 From: Viacheslav Klimov Date: Thu, 28 Oct 2021 18:08:17 +0300 Subject: [PATCH 01/10] Implement entities search within all tenants --- .../controller/EntityQueryController.java | 83 ++++++- .../data/search/EntitiesSearchRequest.java | 25 +++ .../data/search/EntitySearchResult.java | 50 +++++ .../service/query/EntitiesSearchService.java | 26 +++ .../query/EntitiesSearchServiceImpl.java | 208 ++++++++++++++++++ .../permission/PermissionChecker.java | 1 + .../permission/SysAdminPermissions.java | 31 +-- .../common/data/query/EntityFilterType.java | 3 +- .../data/query/EntityNameOrIdFilter.java | 32 +++ .../query/DefaultEntityQueryRepository.java | 52 ++++- .../dao/sql/query/EntityKeyMapping.java | 60 ++++- 11 files changed, 532 insertions(+), 39 deletions(-) create mode 100644 application/src/main/java/org/thingsboard/server/data/search/EntitiesSearchRequest.java create mode 100644 application/src/main/java/org/thingsboard/server/data/search/EntitySearchResult.java create mode 100644 application/src/main/java/org/thingsboard/server/service/query/EntitiesSearchService.java create mode 100644 application/src/main/java/org/thingsboard/server/service/query/EntitiesSearchServiceImpl.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/query/EntityNameOrIdFilter.java diff --git a/application/src/main/java/org/thingsboard/server/controller/EntityQueryController.java b/application/src/main/java/org/thingsboard/server/controller/EntityQueryController.java index 019066c636..e5467c8ae9 100644 --- a/application/src/main/java/org/thingsboard/server/controller/EntityQueryController.java +++ b/application/src/main/java/org/thingsboard/server/controller/EntityQueryController.java @@ -17,9 +17,10 @@ package org.thingsboard.server.controller; import io.swagger.annotations.ApiOperation; import io.swagger.annotations.ApiParam; -import org.springframework.beans.factory.annotation.Autowired; +import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; @@ -36,20 +37,30 @@ import org.thingsboard.server.common.data.query.EntityCountQuery; import org.thingsboard.server.common.data.query.EntityData; import org.thingsboard.server.common.data.query.EntityDataPageLink; import org.thingsboard.server.common.data.query.EntityDataQuery; +import org.thingsboard.server.data.search.EntitiesSearchRequest; +import org.thingsboard.server.data.search.EntitySearchResult; import org.thingsboard.server.queue.util.TbCoreComponent; +import org.thingsboard.server.service.query.EntitiesSearchService; import org.thingsboard.server.service.query.EntityQueryService; import static org.thingsboard.server.controller.ControllerConstants.ALARM_DATA_QUERY_DESCRIPTION; import static org.thingsboard.server.controller.ControllerConstants.ENTITY_COUNT_QUERY_DESCRIPTION; import static org.thingsboard.server.controller.ControllerConstants.ENTITY_DATA_QUERY_DESCRIPTION; +import static org.thingsboard.server.controller.ControllerConstants.NEW_LINE; +import static org.thingsboard.server.controller.ControllerConstants.PAGE_NUMBER_DESCRIPTION; +import static org.thingsboard.server.controller.ControllerConstants.PAGE_SIZE_DESCRIPTION; +import static org.thingsboard.server.controller.ControllerConstants.SORT_ORDER_ALLOWABLE_VALUES; +import static org.thingsboard.server.controller.ControllerConstants.SORT_ORDER_DESCRIPTION; +import static org.thingsboard.server.controller.ControllerConstants.SORT_PROPERTY_DESCRIPTION; @RestController @TbCoreComponent @RequestMapping("/api") +@RequiredArgsConstructor public class EntityQueryController extends BaseController { - @Autowired - private EntityQueryService entityQueryService; + private final EntityQueryService entityQueryService; + private final EntitiesSearchService entitiesSearchService; private static final int MAX_PAGE_SIZE = 100; @@ -123,4 +134,70 @@ public class EntityQueryController extends BaseController { } } + @ApiOperation(value = "Search entities (searchEntities)", notes = "Search entities with specified entity type by id or name within the whole platform. " + + "Searchable entity types are: CUSTOMER, USER, DEVICE, DEVICE_PROFILE, ASSET, ENTITY_VIEW, DASHBOARD, " + + "RULE_CHAIN, EDGE, OTA_PACKAGE, TB_RESOURCE, WIDGETS_BUNDLE, TENANT, TENANT_PROFILE." + NEW_LINE + + "The platform will search for entities, where a name contains the search text (case-insensitively), " + + "or if the search query is a valid UUID (e.g. 128e4d40-26b3-11ec-aaeb-c7661c54701e) then " + + "it will also search for an entity where id fully matches the query. If search query is empty " + + "then all entities will be returned (according to page number, page size and sorting)." + NEW_LINE + + "The returned result is a page of EntitySearchResult, which contains: " + + "entity id, entity fields represented as strings, tenant info and owner info. " + + "Returned entity fields are: name, type (will be present for USER, DEVICE, ASSET, ENTITY_VIEW, RULE_CHAIN, " + + "EDGE, OTA_PACKAGE, TB_RESOURCE entity types; in case of USER - the type is its authority), " + + "createdTime and lastActivityTime (will only be present for DEVICE and USER; for USER it is its last login time). " + + "Tenant info contains tenant's id and title; owner info contains the same info for an entity's owner " + + "(its customer, or if it is not a customer's entity - tenant)." + NEW_LINE + + "Example response value:\n" + + "{\n" + + " \"data\": [\n" + + " {\n" + + " \"entityId\": {\n" + + " \"entityType\": \"DEVICE\",\n" + + " \"id\": \"48be0670-25c9-11ec-a618-8165eb6b112a\"\n" + + " },\n" + + " \"fields\": {\n" + + " \"name\": \"Thermostat T1\",\n" + + " \"createdTime\": \"1633430698071\",\n" + + " \"lastActivityTime\": \"1635761085285\",\n" + + " \"type\": \"thermostat\"\n" + + " },\n" + + " \"tenantInfo\": {\n" + + " \"id\": {\n" + + " \"entityType\": \"TENANT\",\n" + + " \"id\": \"2ddd6120-25c9-11ec-a618-8165eb6b112a\"\n" + + " },\n" + + " \"name\": \"Tenant\"\n" + + " },\n" + + " \"ownerInfo\": {\n" + + " \"id\": {\n" + + " \"entityType\": \"CUSTOMER\",\n" + + " \"id\": \"26cba800-eee3-11eb-9e2c-fb031bd4619c\"\n" + + " },\n" + + " \"name\": \"Customer A\"\n" + + " }\n" + + " }\n" + + " ],\n" + + " \"totalPages\": 1,\n" + + " \"totalElements\": 1,\n" + + " \"hasNext\": false\n" + + "}") + @PostMapping("/entities/search") + @PreAuthorize("hasAnyAuthority('SYS_ADMIN')") + public PageData searchEntities(@RequestBody EntitiesSearchRequest request, + @ApiParam(value = PAGE_NUMBER_DESCRIPTION, required = true) + @RequestParam int page, + @ApiParam(value = PAGE_SIZE_DESCRIPTION, required = true) + @RequestParam int pageSize, + @ApiParam(value = SORT_PROPERTY_DESCRIPTION, allowableValues = "name, type, createdTime, lastActivityTime, createdTime, tenantId, customerId", required = false) + @RequestParam(required = false) String sortProperty, + @ApiParam(value = SORT_ORDER_DESCRIPTION, allowableValues = SORT_ORDER_ALLOWABLE_VALUES, required = false) + @RequestParam(required = false) String sortOrder) throws ThingsboardException { + try { + return entitiesSearchService.searchEntities(getCurrentUser(), request, createPageLink(pageSize, page, null, sortProperty, sortOrder)); + } catch (Exception e) { + throw handleException(e); + } + } + } diff --git a/application/src/main/java/org/thingsboard/server/data/search/EntitiesSearchRequest.java b/application/src/main/java/org/thingsboard/server/data/search/EntitiesSearchRequest.java new file mode 100644 index 0000000000..48f8966107 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/data/search/EntitiesSearchRequest.java @@ -0,0 +1,25 @@ +/** + * Copyright © 2016-2021 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.data.search; + +import lombok.Data; +import org.thingsboard.server.common.data.EntityType; + +@Data +public class EntitiesSearchRequest { + private EntityType entityType; + private String searchQuery; +} diff --git a/application/src/main/java/org/thingsboard/server/data/search/EntitySearchResult.java b/application/src/main/java/org/thingsboard/server/data/search/EntitySearchResult.java new file mode 100644 index 0000000000..a26d580b7d --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/data/search/EntitySearchResult.java @@ -0,0 +1,50 @@ +/** + * Copyright © 2016-2021 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.data.search; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import org.thingsboard.server.common.data.id.EntityId; +import org.thingsboard.server.common.data.id.TenantId; + +import java.util.Map; + +@Data +public class EntitySearchResult { + private EntityId entityId; + private Map fields; + + private EntityTenantInfo tenantInfo; + private EntityOwnerInfo ownerInfo; + + @Data + @AllArgsConstructor + @NoArgsConstructor + public static final class EntityTenantInfo { + private TenantId id; + private String name; + } + + @Data + @AllArgsConstructor + @NoArgsConstructor + public static final class EntityOwnerInfo { + private EntityId id; + private String name; + } + +} diff --git a/application/src/main/java/org/thingsboard/server/service/query/EntitiesSearchService.java b/application/src/main/java/org/thingsboard/server/service/query/EntitiesSearchService.java new file mode 100644 index 0000000000..36a34f89e7 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/query/EntitiesSearchService.java @@ -0,0 +1,26 @@ +/** + * Copyright © 2016-2021 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.query; + +import org.thingsboard.server.common.data.page.PageData; +import org.thingsboard.server.common.data.page.PageLink; +import org.thingsboard.server.data.search.EntitiesSearchRequest; +import org.thingsboard.server.data.search.EntitySearchResult; +import org.thingsboard.server.service.security.model.SecurityUser; + +public interface EntitiesSearchService { + PageData searchEntities(SecurityUser user, EntitiesSearchRequest request, PageLink pageLink); +} diff --git a/application/src/main/java/org/thingsboard/server/service/query/EntitiesSearchServiceImpl.java b/application/src/main/java/org/thingsboard/server/service/query/EntitiesSearchServiceImpl.java new file mode 100644 index 0000000000..1753a49c0d --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/query/EntitiesSearchServiceImpl.java @@ -0,0 +1,208 @@ +/** + * Copyright © 2016-2021 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.query; + +import com.google.common.base.Strings; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; +import org.springframework.stereotype.Service; +import org.thingsboard.server.common.data.ContactBased; +import org.thingsboard.server.common.data.Customer; +import org.thingsboard.server.common.data.EntityType; +import org.thingsboard.server.common.data.Tenant; +import org.thingsboard.server.common.data.id.CustomerId; +import org.thingsboard.server.common.data.id.EntityId; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.page.PageData; +import org.thingsboard.server.common.data.page.PageLink; +import org.thingsboard.server.common.data.page.SortOrder; +import org.thingsboard.server.common.data.query.EntityData; +import org.thingsboard.server.common.data.query.EntityDataPageLink; +import org.thingsboard.server.common.data.query.EntityDataQuery; +import org.thingsboard.server.common.data.query.EntityDataSortOrder; +import org.thingsboard.server.common.data.query.EntityKey; +import org.thingsboard.server.common.data.query.EntityKeyType; +import org.thingsboard.server.common.data.query.EntityNameOrIdFilter; +import org.thingsboard.server.dao.customer.CustomerService; +import org.thingsboard.server.dao.tenant.TenantService; +import org.thingsboard.server.data.search.EntitiesSearchRequest; +import org.thingsboard.server.data.search.EntitySearchResult; +import org.thingsboard.server.queue.util.TbCoreComponent; +import org.thingsboard.server.service.security.model.SecurityUser; +import org.thingsboard.server.service.state.DefaultDeviceStateService; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.EnumSet; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.UUID; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import static org.thingsboard.server.common.data.EntityType.ASSET; +import static org.thingsboard.server.common.data.EntityType.CUSTOMER; +import static org.thingsboard.server.common.data.EntityType.DASHBOARD; +import static org.thingsboard.server.common.data.EntityType.DEVICE; +import static org.thingsboard.server.common.data.EntityType.DEVICE_PROFILE; +import static org.thingsboard.server.common.data.EntityType.EDGE; +import static org.thingsboard.server.common.data.EntityType.ENTITY_VIEW; +import static org.thingsboard.server.common.data.EntityType.OTA_PACKAGE; +import static org.thingsboard.server.common.data.EntityType.RULE_CHAIN; +import static org.thingsboard.server.common.data.EntityType.TB_RESOURCE; +import static org.thingsboard.server.common.data.EntityType.TENANT; +import static org.thingsboard.server.common.data.EntityType.TENANT_PROFILE; +import static org.thingsboard.server.common.data.EntityType.USER; +import static org.thingsboard.server.common.data.EntityType.WIDGETS_BUNDLE; +import static org.thingsboard.server.dao.sql.query.EntityKeyMapping.CREATED_TIME; +import static org.thingsboard.server.dao.sql.query.EntityKeyMapping.CUSTOMER_ID; +import static org.thingsboard.server.dao.sql.query.EntityKeyMapping.LAST_ACTIVITY_TIME; +import static org.thingsboard.server.dao.sql.query.EntityKeyMapping.NAME; +import static org.thingsboard.server.dao.sql.query.EntityKeyMapping.TENANT_ID; +import static org.thingsboard.server.dao.sql.query.EntityKeyMapping.TYPE; + +@Service +@TbCoreComponent +@RequiredArgsConstructor +@Slf4j +public class EntitiesSearchServiceImpl implements EntitiesSearchService { + private final EntityQueryService entityQueryService; + + private final TenantService tenantService; + private final CustomerService customerService; + private final DefaultDeviceStateService deviceStateService; + + private static final List entityResponseFields = Stream.of(CREATED_TIME, NAME, TYPE, TENANT_ID, CUSTOMER_ID) + .map(field -> new EntityKey(EntityKeyType.ENTITY_FIELD, field)) + .collect(Collectors.toList()); + + private static final Set searchableEntityTypes = EnumSet.of( + TENANT, CUSTOMER, USER, DASHBOARD, ASSET, DEVICE, RULE_CHAIN, ENTITY_VIEW, + WIDGETS_BUNDLE, TENANT_PROFILE, DEVICE_PROFILE, TB_RESOURCE, OTA_PACKAGE, EDGE + ); + + @Override + public PageData searchEntities(SecurityUser user, EntitiesSearchRequest request, PageLink pageLink) { + EntityType entityType = request.getEntityType(); + if (!searchableEntityTypes.contains(entityType)) { + return new PageData<>(); + } + + EntityDataQuery query = createSearchQuery(request.getSearchQuery(), entityType, pageLink); + PageData resultPage = entityQueryService.findEntityDataByQuery(user, query); + + Map> localOwnersCache = new HashMap<>(); + return resultPage.mapData(entityData -> { + Map fields = new HashMap<>(); + entityData.getLatest().values().stream() + .flatMap(values -> values.entrySet().stream()) + .forEach(entry -> fields.put(entry.getKey(), Strings.emptyToNull(entry.getValue().getValue()))); + + EntitySearchResult entitySearchResult = new EntitySearchResult(); + + entitySearchResult.setEntityId(entityData.getEntityId()); + entitySearchResult.setFields(fields); + setOwnerInfo(entitySearchResult, localOwnersCache); + + return entitySearchResult; + }); + } + + private EntityDataQuery createSearchQuery(String searchQuery, EntityType entityType, PageLink pageLink) { + EntityDataPageLink entityDataPageLink = new EntityDataPageLink(); + entityDataPageLink.setPageSize(pageLink.getPageSize()); + entityDataPageLink.setPage(pageLink.getPage()); + if (pageLink.getSortOrder() != null && StringUtils.isNotEmpty(pageLink.getSortOrder().getProperty())) { + entityDataPageLink.setSortOrder(new EntityDataSortOrder(new EntityKey(EntityKeyType.ENTITY_FIELD, pageLink.getSortOrder().getProperty()), + EntityDataSortOrder.Direction.valueOf(Optional.ofNullable(pageLink.getSortOrder().getDirection()).orElse(SortOrder.Direction.ASC).name()))); + } + + EntityNameOrIdFilter filter = new EntityNameOrIdFilter(); + filter.setEntityType(entityType); + filter.setNameOrId(searchQuery); + + List entityFields = entityResponseFields; + List latestValues = Collections.emptyList(); + + if (entityType == USER) { + entityFields = new ArrayList<>(entityFields); + entityFields.add(new EntityKey(EntityKeyType.ENTITY_FIELD, LAST_ACTIVITY_TIME)); + } else if (entityType == DEVICE) { + EntityKey lastActivityTimeKey; + if (deviceStateService.isPersistToTelemetry()) { + lastActivityTimeKey = new EntityKey(EntityKeyType.TIME_SERIES, LAST_ACTIVITY_TIME); + } else { + lastActivityTimeKey = new EntityKey(EntityKeyType.SERVER_ATTRIBUTE, LAST_ACTIVITY_TIME); + } + latestValues = List.of(lastActivityTimeKey); + if (entityDataPageLink.getSortOrder() != null && entityDataPageLink.getSortOrder().getKey().getKey().equals(LAST_ACTIVITY_TIME)) { + entityDataPageLink.getSortOrder().setKey(lastActivityTimeKey); + } + } + + return new EntityDataQuery(filter, entityDataPageLink, entityFields, latestValues, Collections.emptyList()); + } + + private void setOwnerInfo(EntitySearchResult entitySearchResult, Map> localOwnersCache) { + Map fields = entitySearchResult.getFields(); + + UUID tenantUuid = toUuid(fields.remove(TENANT_ID)); + UUID customerUuid = toUuid(fields.remove(CUSTOMER_ID)); + + Tenant tenant = null; + if (tenantUuid != null) { + tenant = getTenant(new TenantId(tenantUuid), localOwnersCache); + } + + ContactBased owner; + if (customerUuid != null) { + owner = getCustomer(new CustomerId(customerUuid), localOwnersCache); + } else { + owner = tenant; + } + + if (tenant != null) { + entitySearchResult.setTenantInfo(new EntitySearchResult.EntityTenantInfo(tenant.getId(), tenant.getName())); + } + if (owner != null) { + entitySearchResult.setOwnerInfo(new EntitySearchResult.EntityOwnerInfo(owner.getId(), owner.getName())); + } + } + + private Tenant getTenant(TenantId tenantId, Map> localOwnersCache) { + return (Tenant) localOwnersCache.computeIfAbsent(tenantId, id -> tenantService.findTenantById(tenantId)); + } + + private Customer getCustomer(CustomerId customerId, Map> localOwnersCache) { + return (Customer) localOwnersCache.computeIfAbsent(customerId, id -> customerService.findCustomerById(TenantId.SYS_TENANT_ID, customerId)); + } + + private UUID toUuid(String uuid) { + try { + UUID id = UUID.fromString(uuid); + if (!id.equals(EntityId.NULL_UUID)) { + return id; + } + } catch (Exception ignored) {} + + return null; + } + +} diff --git a/application/src/main/java/org/thingsboard/server/service/security/permission/PermissionChecker.java b/application/src/main/java/org/thingsboard/server/service/security/permission/PermissionChecker.java index 3f6e16d753..45a43876e6 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/permission/PermissionChecker.java +++ b/application/src/main/java/org/thingsboard/server/service/security/permission/PermissionChecker.java @@ -69,5 +69,6 @@ public interface PermissionChecker { } }; + PermissionChecker allowReadPermissionChecker = new GenericPermissionChecker(Operation.READ, Operation.READ_TELEMETRY, Operation.READ_ATTRIBUTES); } diff --git a/application/src/main/java/org/thingsboard/server/service/security/permission/SysAdminPermissions.java b/application/src/main/java/org/thingsboard/server/service/security/permission/SysAdminPermissions.java index f703102e18..8f9648dc93 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/permission/SysAdminPermissions.java +++ b/application/src/main/java/org/thingsboard/server/service/security/permission/SysAdminPermissions.java @@ -17,10 +17,7 @@ package org.thingsboard.server.service.security.permission; import org.springframework.stereotype.Component; import org.thingsboard.server.common.data.HasTenantId; -import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.id.EntityId; -import org.thingsboard.server.common.data.id.UserId; -import org.thingsboard.server.common.data.security.Authority; import org.thingsboard.server.service.security.model.SecurityUser; @Component(value="sysAdminPermissions") @@ -29,23 +26,29 @@ public class SysAdminPermissions extends AbstractPermissions { public SysAdminPermissions() { super(); put(Resource.ADMIN_SETTINGS, PermissionChecker.allowAllPermissionChecker); - put(Resource.DASHBOARD, new PermissionChecker.GenericPermissionChecker(Operation.READ)); + put(Resource.DASHBOARD, PermissionChecker.allowReadPermissionChecker); put(Resource.TENANT, PermissionChecker.allowAllPermissionChecker); - put(Resource.RULE_CHAIN, systemEntityPermissionChecker); - put(Resource.USER, userPermissionChecker); + put(Resource.RULE_CHAIN, PermissionChecker.allowReadPermissionChecker); + put(Resource.USER, PermissionChecker.allowAllPermissionChecker); put(Resource.WIDGETS_BUNDLE, systemEntityPermissionChecker); put(Resource.WIDGET_TYPE, systemEntityPermissionChecker); put(Resource.OAUTH2_CONFIGURATION_INFO, PermissionChecker.allowAllPermissionChecker); put(Resource.OAUTH2_CONFIGURATION_TEMPLATE, PermissionChecker.allowAllPermissionChecker); put(Resource.TENANT_PROFILE, PermissionChecker.allowAllPermissionChecker); - put(Resource.TB_RESOURCE, systemEntityPermissionChecker); + put(Resource.TB_RESOURCE, PermissionChecker.allowAllPermissionChecker); + put(Resource.CUSTOMER, PermissionChecker.allowReadPermissionChecker); + put(Resource.ASSET, PermissionChecker.allowReadPermissionChecker); + put(Resource.DEVICE, PermissionChecker.allowReadPermissionChecker); + put(Resource.ENTITY_VIEW, PermissionChecker.allowReadPermissionChecker); + put(Resource.DEVICE_PROFILE, PermissionChecker.allowReadPermissionChecker); + put(Resource.OTA_PACKAGE, PermissionChecker.allowReadPermissionChecker); + put(Resource.EDGE, PermissionChecker.allowReadPermissionChecker); } private static final PermissionChecker systemEntityPermissionChecker = new PermissionChecker() { @Override public boolean hasPermission(SecurityUser user, Operation operation, EntityId entityId, HasTenantId entity) { - if (entity.getTenantId() != null && !entity.getTenantId().isNullUid()) { return false; } @@ -53,16 +56,4 @@ public class SysAdminPermissions extends AbstractPermissions { } }; - private static final PermissionChecker userPermissionChecker = new PermissionChecker() { - - @Override - public boolean hasPermission(SecurityUser user, Operation operation, UserId userId, User userEntity) { - if (Authority.CUSTOMER_USER.equals(userEntity.getAuthority())) { - return false; - } - return true; - } - - }; - } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/query/EntityFilterType.java b/common/data/src/main/java/org/thingsboard/server/common/data/query/EntityFilterType.java index 1a843c7b0f..c75d6bf4df 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/query/EntityFilterType.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/query/EntityFilterType.java @@ -29,7 +29,8 @@ public enum EntityFilterType { DEVICE_SEARCH_QUERY("deviceSearchQuery"), ENTITY_VIEW_SEARCH_QUERY("entityViewSearchQuery"), EDGE_SEARCH_QUERY("edgeSearchQuery"), - API_USAGE_STATE("apiUsageState"); + API_USAGE_STATE("apiUsageState"), + ENTITY_NAME_OR_ID("entityNameOrId"); private final String label; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/query/EntityNameOrIdFilter.java b/common/data/src/main/java/org/thingsboard/server/common/data/query/EntityNameOrIdFilter.java new file mode 100644 index 0000000000..1fcb9e9350 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/query/EntityNameOrIdFilter.java @@ -0,0 +1,32 @@ +/** + * Copyright © 2016-2021 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.query; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.thingsboard.server.common.data.EntityType; + +@EqualsAndHashCode(callSuper = true) +@Data +public class EntityNameOrIdFilter extends EntitySearchQueryFilter { + private String nameOrId; + private EntityType entityType; + + @Override + public EntityFilterType getType() { + return EntityFilterType.ENTITY_NAME_OR_ID; + } +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/query/DefaultEntityQueryRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/query/DefaultEntityQueryRepository.java index 4b331f78f2..cbecc96f59 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/query/DefaultEntityQueryRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/query/DefaultEntityQueryRepository.java @@ -44,6 +44,7 @@ import org.thingsboard.server.common.data.query.EntityFilterType; import org.thingsboard.server.common.data.query.EntityKeyType; import org.thingsboard.server.common.data.query.EntityListFilter; import org.thingsboard.server.common.data.query.EntityNameFilter; +import org.thingsboard.server.common.data.query.EntityNameOrIdFilter; import org.thingsboard.server.common.data.query.EntitySearchQueryFilter; import org.thingsboard.server.common.data.query.EntityTypeFilter; import org.thingsboard.server.common.data.query.EntityViewSearchQueryFilter; @@ -236,6 +237,12 @@ public class DefaultEntityQueryRepository implements EntityQueryRepository { entityTableMap.put(EntityType.TENANT, "tenant"); entityTableMap.put(EntityType.API_USAGE_STATE, SELECT_API_USAGE_STATE); entityTableMap.put(EntityType.EDGE, "edge"); + entityTableMap.put(EntityType.RULE_CHAIN, "rule_chain"); + entityTableMap.put(EntityType.WIDGETS_BUNDLE, "widgets_bundle"); + entityTableMap.put(EntityType.TENANT_PROFILE, "tenant_profile"); + entityTableMap.put(EntityType.DEVICE_PROFILE, "device_profile"); + entityTableMap.put(EntityType.TB_RESOURCE, "resource"); + entityTableMap.put(EntityType.OTA_PACKAGE, "ota_package"); } public static EntityType[] RELATION_QUERY_ENTITY_TYPES = new EntityType[]{ @@ -441,7 +448,7 @@ public class DefaultEntityQueryRepository implements EntityQueryRepository { Optional sortOrderMappingOpt = mappings.stream().filter(EntityKeyMapping::isSortOrder).findFirst(); if (sortOrderMappingOpt.isPresent()) { EntityKeyMapping sortOrderMapping = sortOrderMappingOpt.get(); - String direction = sortOrder.getDirection() == EntityDataSortOrder.Direction.ASC ? "asc" : "desc"; + String direction = sortOrder.getDirection() == EntityDataSortOrder.Direction.ASC ? "asc" : "desc nulls last"; if (sortOrderMapping.getEntityKey().getType() == EntityKeyType.ENTITY_FIELD) { dataQuery = String.format("%s order by %s %s, result.id %s", dataQuery, sortOrderMapping.getValueAlias(), direction, direction); } else { @@ -471,15 +478,26 @@ public class DefaultEntityQueryRepository implements EntityQueryRepository { String entityFieldsQuery = EntityKeyMapping.buildQuery(ctx, entityFieldsFilters, entityFilter.getType()); String result = permissionQuery; if (!entityFilterQuery.isEmpty()) { - result += " and (" + entityFilterQuery + ")"; + if (!result.isEmpty()) { + result += " and (" + entityFilterQuery + ")"; + } else { + result = "(" + entityFilterQuery + ")"; + } } if (!entityFieldsQuery.isEmpty()) { - result += " and (" + entityFieldsQuery + ")"; + if (!result.isEmpty()) { + result += " and (" + entityFieldsQuery + ")"; + } else { + result = "(" + entityFieldsQuery + ")"; + } } return result; } private String buildPermissionQuery(QueryContext ctx, EntityFilter entityFilter) { + if (ctx.getTenantId().equals(TenantId.SYS_TENANT_ID)) { + return ""; + } switch (entityFilter.getType()) { case RELATIONS_QUERY: case DEVICE_SEARCH_QUERY: @@ -548,6 +566,8 @@ public class DefaultEntityQueryRepository implements EntityQueryRepository { case API_USAGE_STATE: case ENTITY_TYPE: return ""; + case ENTITY_NAME_OR_ID: + return entityNameOrIdQuery(ctx, (EntityNameOrIdFilter) entityFilter); default: throw new RuntimeException("Not implemented!"); } @@ -759,7 +779,27 @@ public class DefaultEntityQueryRepository implements EntityQueryRepository { private String entityNameQuery(QueryContext ctx, EntityNameFilter filter) { ctx.addStringParameter("entity_filter_name_filter", filter.getEntityNameFilter()); - return "lower(e.search_text) like lower(concat(:entity_filter_name_filter, '%%'))"; + return "lower(e.search_text) like lower(concat('%', :entity_filter_name_filter, '%'))"; + } + + private String entityNameOrIdQuery(QueryContext ctx, EntityNameOrIdFilter filter) { + String nameOrId = filter.getNameOrId(); + if (StringUtils.isNotEmpty(nameOrId)) { + nameOrId = nameOrId.replaceAll("%", "\\\\%").replaceAll("_", "\\\\_"); + ctx.addStringParameter("entity_id_or_search_text_filter", nameOrId); + String query = ""; + + String searchTextField = EntityKeyMapping.searchTextFields.get(filter.getEntityType()); + query += "lower(e." + searchTextField + ") like lower(concat('%', :entity_id_or_search_text_filter, '%'))"; + + try { + UUID.fromString(nameOrId); + query += " or e.id = :entity_id_or_search_text_filter::uuid"; + } catch (Exception ignored) {} + + return query; + } + return "true"; } private String typeQuery(QueryContext ctx, EntityFilter filter) { @@ -787,7 +827,7 @@ public class DefaultEntityQueryRepository implements EntityQueryRepository { } ctx.addStringParameter("entity_filter_type_query_type", type); ctx.addStringParameter("entity_filter_type_query_name", name); - return "e.type = :entity_filter_type_query_type and lower(e.search_text) like lower(concat(:entity_filter_type_query_name, '%%'))"; + return "e.type = :entity_filter_type_query_type and lower(e.search_text) like lower(concat('%', :entity_filter_type_query_name, '%'))"; } private EntityType resolveEntityType(EntityFilter entityFilter) { @@ -816,6 +856,8 @@ public class DefaultEntityQueryRepository implements EntityQueryRepository { return ((RelationsQueryFilter) entityFilter).getRootEntity().getEntityType(); case API_USAGE_STATE: return EntityType.API_USAGE_STATE; + case ENTITY_NAME_OR_ID: + return ((EntityNameOrIdFilter) entityFilter).getEntityType(); default: throw new RuntimeException("Not implemented!"); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/query/EntityKeyMapping.java b/dao/src/main/java/org/thingsboard/server/dao/sql/query/EntityKeyMapping.java index caed48758a..3d40156329 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/query/EntityKeyMapping.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/query/EntityKeyMapping.java @@ -38,6 +38,7 @@ import org.thingsboard.server.dao.model.ModelConstants; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; +import java.util.EnumMap; import java.util.HashMap; import java.util.HashSet; import java.util.List; @@ -53,6 +54,8 @@ public class EntityKeyMapping { private static final Map> allowedEntityFieldMap = new HashMap<>(); private static final Map entityFieldColumnMap = new HashMap<>(); private static final Map> aliases = new HashMap<>(); + private static final Map> propertiesFunctions = new EnumMap<>(EntityType.class); + public static final Map searchTextFields = new EnumMap<>(EntityType.class); public static final String CREATED_TIME = "createdTime"; public static final String ENTITY_TYPE = "entityType"; @@ -72,35 +75,51 @@ public class EntityKeyMapping { public static final String ZIP = "zip"; public static final String PHONE = "phone"; public static final String ADDITIONAL_INFO = "additionalInfo"; + public static final String TENANT_ID = "tenantId"; + public static final String CUSTOMER_ID = "customerId"; + public static final String AUTHORITY = "authority"; + public static final String RESOURCE_TYPE = "resourceType"; + public static final String LAST_ACTIVITY_TIME = "lastActivityTime"; - public static final List typedEntityFields = Arrays.asList(CREATED_TIME, ENTITY_TYPE, NAME, TYPE, ADDITIONAL_INFO); - public static final List widgetEntityFields = Arrays.asList(CREATED_TIME, ENTITY_TYPE, NAME); + public static final List typedEntityFields = Arrays.asList(CREATED_TIME, ENTITY_TYPE, NAME, TYPE, ADDITIONAL_INFO, TENANT_ID); public static final List commonEntityFields = Arrays.asList(CREATED_TIME, ENTITY_TYPE, NAME, ADDITIONAL_INFO); - public static final List dashboardEntityFields = Arrays.asList(CREATED_TIME, ENTITY_TYPE, TITLE); - public static final List labeledEntityFields = Arrays.asList(CREATED_TIME, ENTITY_TYPE, NAME, TYPE, LABEL, ADDITIONAL_INFO); + + public static final List dashboardEntityFields = Arrays.asList(CREATED_TIME, ENTITY_TYPE, TITLE, TENANT_ID); + public static final List labeledEntityFields = Arrays.asList(CREATED_TIME, ENTITY_TYPE, NAME, TYPE, LABEL, ADDITIONAL_INFO, TENANT_ID, CUSTOMER_ID); public static final List contactBasedEntityFields = Arrays.asList(CREATED_TIME, ENTITY_TYPE, EMAIL, TITLE, COUNTRY, STATE, CITY, ADDRESS, ADDRESS_2, ZIP, PHONE, ADDITIONAL_INFO); - public static final Set apiUsageStateEntityFields = new HashSet<>(Arrays.asList(CREATED_TIME, ENTITY_TYPE, NAME)); + public static final Set apiUsageStateEntityFields = new HashSet<>(Arrays.asList(CREATED_TIME, ENTITY_TYPE, NAME)); public static final Set commonEntityFieldsSet = new HashSet<>(commonEntityFields); public static final Set relationQueryEntityFieldsSet = new HashSet<>(Arrays.asList(CREATED_TIME, ENTITY_TYPE, NAME, TYPE, LABEL, FIRST_NAME, LAST_NAME, EMAIL, REGION, TITLE, COUNTRY, STATE, CITY, ADDRESS, ADDRESS_2, ZIP, PHONE, ADDITIONAL_INFO)); static { allowedEntityFieldMap.put(EntityType.DEVICE, new HashSet<>(labeledEntityFields)); allowedEntityFieldMap.put(EntityType.ASSET, new HashSet<>(labeledEntityFields)); + allowedEntityFieldMap.put(EntityType.EDGE, new HashSet<>(labeledEntityFields)); allowedEntityFieldMap.put(EntityType.ENTITY_VIEW, new HashSet<>(typedEntityFields)); + allowedEntityFieldMap.get(EntityType.ENTITY_VIEW).add(CUSTOMER_ID); allowedEntityFieldMap.put(EntityType.TENANT, new HashSet<>(contactBasedEntityFields)); allowedEntityFieldMap.get(EntityType.TENANT).add(REGION); allowedEntityFieldMap.put(EntityType.CUSTOMER, new HashSet<>(contactBasedEntityFields)); + allowedEntityFieldMap.get(EntityType.CUSTOMER).add(TENANT_ID); + + allowedEntityFieldMap.put(EntityType.USER, new HashSet<>(Arrays.asList(CREATED_TIME, FIRST_NAME, LAST_NAME, EMAIL, + ADDITIONAL_INFO, AUTHORITY, TENANT_ID, CUSTOMER_ID))); - allowedEntityFieldMap.put(EntityType.USER, new HashSet<>(Arrays.asList(CREATED_TIME, FIRST_NAME, LAST_NAME, EMAIL, ADDITIONAL_INFO))); + allowedEntityFieldMap.put(EntityType.DEVICE_PROFILE, new HashSet<>(commonEntityFields)); + allowedEntityFieldMap.get(EntityType.DEVICE_PROFILE).add(TENANT_ID); allowedEntityFieldMap.put(EntityType.DASHBOARD, new HashSet<>(dashboardEntityFields)); allowedEntityFieldMap.put(EntityType.RULE_CHAIN, new HashSet<>(commonEntityFields)); + allowedEntityFieldMap.get(EntityType.RULE_CHAIN).add(TENANT_ID); + allowedEntityFieldMap.get(EntityType.RULE_CHAIN).add(TYPE); allowedEntityFieldMap.put(EntityType.RULE_NODE, new HashSet<>(commonEntityFields)); - allowedEntityFieldMap.put(EntityType.WIDGET_TYPE, new HashSet<>(widgetEntityFields)); - allowedEntityFieldMap.put(EntityType.WIDGETS_BUNDLE, new HashSet<>(widgetEntityFields)); + allowedEntityFieldMap.put(EntityType.WIDGET_TYPE, new HashSet<>(Arrays.asList(CREATED_TIME, ENTITY_TYPE, NAME, TENANT_ID))); + allowedEntityFieldMap.put(EntityType.WIDGETS_BUNDLE, new HashSet<>(Arrays.asList(CREATED_TIME, ENTITY_TYPE, TITLE, TENANT_ID))); allowedEntityFieldMap.put(EntityType.API_USAGE_STATE, apiUsageStateEntityFields); + allowedEntityFieldMap.put(EntityType.TB_RESOURCE, Set.of(CREATED_TIME, ENTITY_TYPE, RESOURCE_TYPE, TITLE, TENANT_ID)); + allowedEntityFieldMap.put(EntityType.OTA_PACKAGE, Set.of(CREATED_TIME, ENTITY_TYPE, TYPE, TITLE, TENANT_ID)); entityFieldColumnMap.put(CREATED_TIME, ModelConstants.CREATED_TIME_PROPERTY); entityFieldColumnMap.put(ENTITY_TYPE, ModelConstants.ENTITY_TYPE_PROPERTY); @@ -120,25 +139,42 @@ public class EntityKeyMapping { entityFieldColumnMap.put(ZIP, ModelConstants.ZIP_PROPERTY); entityFieldColumnMap.put(PHONE, ModelConstants.PHONE_PROPERTY); entityFieldColumnMap.put(ADDITIONAL_INFO, ModelConstants.ADDITIONAL_INFO_PROPERTY); + entityFieldColumnMap.put(TENANT_ID, ModelConstants.TENANT_ID_PROPERTY); + entityFieldColumnMap.put(CUSTOMER_ID, ModelConstants.CUSTOMER_ID_PROPERTY); + entityFieldColumnMap.put(AUTHORITY, ModelConstants.USER_AUTHORITY_PROPERTY); + entityFieldColumnMap.put(RESOURCE_TYPE, ModelConstants.RESOURCE_TYPE_COLUMN); Map contactBasedAliases = new HashMap<>(); contactBasedAliases.put(NAME, TITLE); contactBasedAliases.put(LABEL, TITLE); aliases.put(EntityType.TENANT, contactBasedAliases); - aliases.put(EntityType.CUSTOMER, contactBasedAliases); + aliases.put(EntityType.CUSTOMER, new HashMap<>(contactBasedAliases)); aliases.put(EntityType.DASHBOARD, contactBasedAliases); Map commonEntityAliases = new HashMap<>(); commonEntityAliases.put(TITLE, NAME); aliases.put(EntityType.DEVICE, commonEntityAliases); aliases.put(EntityType.ASSET, commonEntityAliases); aliases.put(EntityType.ENTITY_VIEW, commonEntityAliases); - aliases.put(EntityType.WIDGETS_BUNDLE, commonEntityAliases); + aliases.put(EntityType.EDGE, commonEntityAliases); + aliases.put(EntityType.WIDGETS_BUNDLE, new HashMap<>(commonEntityAliases)); + aliases.get(EntityType.WIDGETS_BUNDLE).put(NAME, TITLE); Map userEntityAliases = new HashMap<>(); userEntityAliases.put(TITLE, EMAIL); userEntityAliases.put(LABEL, EMAIL); userEntityAliases.put(NAME, EMAIL); + userEntityAliases.put(TYPE, AUTHORITY); aliases.put(EntityType.USER, userEntityAliases); + aliases.put(EntityType.TB_RESOURCE, Map.of(NAME, TITLE, TYPE, RESOURCE_TYPE)); + aliases.put(EntityType.OTA_PACKAGE, Map.of(NAME, TITLE)); + + propertiesFunctions.put(EntityType.USER, Map.of( + LAST_ACTIVITY_TIME, "cast(e.additional_info::json ->> 'lastLoginTs' as bigint)" + )); + + Arrays.stream(EntityType.values()).forEach(entityType -> { + searchTextFields.put(entityType, ModelConstants.SEARCH_TEXT_PROPERTY); + }); } private int index; @@ -179,6 +215,10 @@ public class EntityKeyMapping { String column = entityFieldColumnMap.get(alias); return String.format("cast(e.%s as varchar) as %s", column, getValueAlias()); } else { + Map entityPropertiesFunctions = propertiesFunctions.get(entityType); + if (entityPropertiesFunctions != null && entityPropertiesFunctions.containsKey(alias)) { + return String.format("%s as %s", entityPropertiesFunctions.get(alias), getValueAlias()); + } return String.format("'' as %s", getValueAlias()); } } From 25d8f4d3c0ff65e7279b6624e1e23602c7918fb9 Mon Sep 17 00:00:00 2001 From: Vladyslav_Prykhodko Date: Fri, 5 Nov 2021 17:51:10 +0200 Subject: [PATCH 02/10] Added fields length validation --- .../server/common/data/AdminSettings.java | 2 + .../server/common/data/OtaPackageInfo.java | 3 ++ .../server/common/data/TbResourceInfo.java | 2 + .../server/common/data/Tenant.java | 1 + .../server/common/data/edge/Edge.java | 12 ++++++ .../data/oauth2/OAuth2BasicMapperConfig.java | 7 +++ .../OAuth2ClientRegistrationTemplate.java | 14 ++++++ .../data/oauth2/OAuth2CustomMapperConfig.java | 9 +++- .../data/oauth2/OAuth2MapperConfig.java | 4 ++ .../data/plugin/ComponentDescriptor.java | 3 ++ .../common/data/widget/BaseWidgetType.java | 4 ++ .../common/data/widget/WidgetTypeDetails.java | 3 ++ .../admin/oauth2-settings.component.html | 43 +++++++++++++++++++ .../pages/admin/oauth2-settings.component.ts | 30 +++++++------ .../home/pages/edge/edge.component.html | 7 ++- .../modules/home/pages/edge/edge.component.ts | 4 +- .../pages/widget/widget-editor.component.html | 2 +- .../assets/locale/locale.constant-en_US.json | 14 ++++++ 18 files changed, 146 insertions(+), 18 deletions(-) diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/AdminSettings.java b/common/data/src/main/java/org/thingsboard/server/common/data/AdminSettings.java index 5467db00e3..041318d43c 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/AdminSettings.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/AdminSettings.java @@ -21,6 +21,7 @@ import org.thingsboard.server.common.data.id.AdminSettingsId; import com.fasterxml.jackson.databind.JsonNode; import org.thingsboard.server.common.data.id.DeviceId; +import org.thingsboard.server.common.data.validation.Length; import org.thingsboard.server.common.data.validation.NoXss; @ApiModel @@ -29,6 +30,7 @@ public class AdminSettings extends BaseData { private static final long serialVersionUID = -7670322981725511892L; @NoXss + @Length(fieldName = "key") private String key; private transient JsonNode jsonValue; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/OtaPackageInfo.java b/common/data/src/main/java/org/thingsboard/server/common/data/OtaPackageInfo.java index 429c78e560..7b63c0dba0 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/OtaPackageInfo.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/OtaPackageInfo.java @@ -68,10 +68,13 @@ public class OtaPackageInfo extends SearchTextBasedWithAdditionalInfo implements Has private String title; @ApiModelProperty(position = 5, value = "Resource type.", example = "LWM2M_MODEL", readOnly = true) private ResourceType resourceType; + @NoXss + @Length(fieldName = "resourceKey") @ApiModelProperty(position = 6, value = "Resource key.", example = "19_1.0", readOnly = true) private String resourceKey; @ApiModelProperty(position = 7, value = "Resource search text.", example = "19_1.0:binaryappdatacontainer", readOnly = true) diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/Tenant.java b/common/data/src/main/java/org/thingsboard/server/common/data/Tenant.java index 982f18e05c..3db0dea9d7 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/Tenant.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/Tenant.java @@ -37,6 +37,7 @@ public class Tenant extends ContactBased implements HasTenantId { @ApiModelProperty(position = 3, value = "Title of the tenant", example = "Company A") private String title; @NoXss + @Length(fieldName = "region") @ApiModelProperty(position = 5, value = "Geo region of the tenant", example = "North America") private String region; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/edge/Edge.java b/common/data/src/main/java/org/thingsboard/server/common/data/edge/Edge.java index ddb30ffe64..b7bab9baf7 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/edge/Edge.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/edge/Edge.java @@ -29,6 +29,7 @@ import org.thingsboard.server.common.data.id.EdgeId; import org.thingsboard.server.common.data.id.RuleChainId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.validation.Length; +import org.thingsboard.server.common.data.validation.NoXss; @ApiModel @EqualsAndHashCode(callSuper = true) @@ -41,15 +42,26 @@ public class Edge extends SearchTextBasedWithAdditionalInfo implements H private TenantId tenantId; private CustomerId customerId; private RuleChainId rootRuleChainId; + @NoXss @Length(fieldName = "name") private String name; + @NoXss @Length(fieldName = "type") private String type; + @NoXss @Length(fieldName = "label") private String label; + @NoXss + @Length(fieldName = "routingKey") private String routingKey; + @NoXss + @Length(fieldName = "secret") private String secret; + @NoXss + @Length(fieldName = "edgeLicenseKey", max = 30) private String edgeLicenseKey; + @NoXss + @Length(fieldName = "cloudEndpoint") private String cloudEndpoint; public Edge() { diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2BasicMapperConfig.java b/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2BasicMapperConfig.java index 652929bc96..937d88b1c1 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2BasicMapperConfig.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2BasicMapperConfig.java @@ -21,6 +21,7 @@ import lombok.Builder; import lombok.Data; import lombok.EqualsAndHashCode; import lombok.ToString; +import org.thingsboard.server.common.data.validation.Length; @Builder(toBuilder = true) @EqualsAndHashCode @@ -28,21 +29,27 @@ import lombok.ToString; @ToString @ApiModel public class OAuth2BasicMapperConfig { + @Length(fieldName = "emailAttributeKey", max = 31) @ApiModelProperty(value = "Email attribute key of OAuth2 principal attributes. " + "Must be specified for BASIC mapper type and cannot be specified for GITHUB type") private final String emailAttributeKey; + @Length(fieldName = "firstNameAttributeKey", max = 31) @ApiModelProperty(value = "First name attribute key") private final String firstNameAttributeKey; + @Length(fieldName = "lastNameAttributeKey", max = 31) @ApiModelProperty(value = "Last name attribute key") private final String lastNameAttributeKey; @ApiModelProperty(value = "Tenant naming strategy. For DOMAIN type, domain for tenant name will be taken from the email (substring before '@')", required = true) private final TenantNameStrategyType tenantNameStrategy; + @Length(fieldName = "tenantNamePattern") @ApiModelProperty(value = "Tenant name pattern for CUSTOM naming strategy. " + "OAuth2 attributes in the pattern can be used by enclosing attribute key in '%{' and '}'", example = "%{email}") private final String tenantNamePattern; + @Length(fieldName = "customerNamePattern") @ApiModelProperty(value = "Customer name pattern. When creating a user on the first OAuth2 log in, if specified, " + "customer name will be used to create or find existing customer in the platform and assign customerId to the user") private final String customerNamePattern; + @Length(fieldName = "defaultDashboardName") @ApiModelProperty(value = "Name of the tenant's dashboard to set as default dashboard for newly created user") private final String defaultDashboardName; @ApiModelProperty(value = "Whether default dashboard should be open in full screen") diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2ClientRegistrationTemplate.java b/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2ClientRegistrationTemplate.java index 56f866eaab..f4baef2abb 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2ClientRegistrationTemplate.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2ClientRegistrationTemplate.java @@ -24,7 +24,9 @@ import lombok.ToString; import org.thingsboard.server.common.data.HasName; import org.thingsboard.server.common.data.SearchTextBasedWithAdditionalInfo; import org.thingsboard.server.common.data.id.OAuth2ClientRegistrationTemplateId; +import org.thingsboard.server.common.data.validation.Length; +import javax.validation.Valid; import java.util.List; @EqualsAndHashCode(callSuper = true) @@ -34,30 +36,42 @@ import java.util.List; @ApiModel public class OAuth2ClientRegistrationTemplate extends SearchTextBasedWithAdditionalInfo implements HasName { + @Length(fieldName = "providerId") @ApiModelProperty(value = "OAuth2 provider identifier (e.g. its name)", required = true) private String providerId; + @Valid @ApiModelProperty(value = "Default config for mapping OAuth2 log in response to platform entities") private OAuth2MapperConfig mapperConfig; + @Length(fieldName = "authorizationUri") @ApiModelProperty(value = "Default authorization URI of the OAuth2 provider") private String authorizationUri; + @Length(fieldName = "accessTokenUri") @ApiModelProperty(value = "Default access token URI of the OAuth2 provider") private String accessTokenUri; @ApiModelProperty(value = "Default OAuth scopes that will be requested from OAuth2 platform") private List scope; + @Length(fieldName = "userInfoUri") @ApiModelProperty(value = "Default user info URI of the OAuth2 provider") private String userInfoUri; + @Length(fieldName = "userNameAttributeName") @ApiModelProperty(value = "Default name of the username attribute in OAuth2 provider log in response") private String userNameAttributeName; + @Length(fieldName = "jwkSetUri") @ApiModelProperty(value = "Default JSON Web Key URI of the OAuth2 provider") private String jwkSetUri; + @Length(fieldName = "clientAuthenticationMethod") @ApiModelProperty(value = "Default client authentication method to use: 'BASIC' or 'POST'") private String clientAuthenticationMethod; + @Length(fieldName = "comment") @ApiModelProperty(value = "Comment for OAuth2 provider") private String comment; + @Length(fieldName = "loginButtonIcon") @ApiModelProperty(value = "Default log in button icon for OAuth2 provider") private String loginButtonIcon; + @Length(fieldName = "loginButtonLabel") @ApiModelProperty(value = "Default OAuth2 provider label") private String loginButtonLabel; + @Length(fieldName = "helpLink") @ApiModelProperty(value = "Help link for OAuth2 provider") private String helpLink; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2CustomMapperConfig.java b/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2CustomMapperConfig.java index 2dac9b4100..e1bab24619 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2CustomMapperConfig.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2CustomMapperConfig.java @@ -15,15 +15,22 @@ */ package org.thingsboard.server.common.data.oauth2; -import lombok.*; +import lombok.Builder; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; +import org.thingsboard.server.common.data.validation.Length; @Builder(toBuilder = true) @EqualsAndHashCode @Data @ToString(exclude = {"password"}) public class OAuth2CustomMapperConfig { + @Length(fieldName = "url") private final String url; + @Length(fieldName = "username") private final String username; + @Length(fieldName = "password") private final String password; private final boolean sendToken; } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2MapperConfig.java b/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2MapperConfig.java index df0cc45d7b..9790334efb 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2MapperConfig.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2MapperConfig.java @@ -21,6 +21,8 @@ import lombok.Data; import lombok.EqualsAndHashCode; import lombok.ToString; +import javax.validation.Valid; + @Builder(toBuilder = true) @EqualsAndHashCode @Data @@ -32,8 +34,10 @@ public class OAuth2MapperConfig { private boolean activateUser; @ApiModelProperty(value = "Type of OAuth2 mapper. Depending on this param, different mapper config fields must be specified", required = true) private MapperType type; + @Valid @ApiModelProperty(value = "Mapper config for BASIC and GITHUB mapper types") private OAuth2BasicMapperConfig basic; + @Valid @ApiModelProperty(value = "Mapper config for CUSTOM mapper type") private OAuth2CustomMapperConfig custom; } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/plugin/ComponentDescriptor.java b/common/data/src/main/java/org/thingsboard/server/common/data/plugin/ComponentDescriptor.java index 5c856cf38d..9ee3320069 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/plugin/ComponentDescriptor.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/plugin/ComponentDescriptor.java @@ -21,6 +21,7 @@ import io.swagger.annotations.ApiModelProperty; import lombok.*; import org.thingsboard.server.common.data.SearchTextBased; import org.thingsboard.server.common.data.id.ComponentDescriptorId; +import org.thingsboard.server.common.data.validation.Length; /** * @author Andrew Shvayka @@ -35,12 +36,14 @@ public class ComponentDescriptor extends SearchTextBased @Getter @Setter private ComponentType type; @ApiModelProperty(position = 4, value = "Scope of the Rule Node. Always set to 'TENANT', since no rule chains on the 'SYSTEM' level yet.", readOnly = true, allowableValues = "TENANT", example = "TENANT") @Getter @Setter private ComponentScope scope; + @Length(fieldName = "name") @ApiModelProperty(position = 5, value = "Name of the Rule Node. Taken from the @RuleNode annotation.", readOnly = true, example = "Custom Rule Node") @Getter @Setter private String name; @ApiModelProperty(position = 6, value = "Full name of the Java class that implements the Rule Engine Node interface.", readOnly = true, example = "com.mycompany.CustomRuleNode") @Getter @Setter private String clazz; @ApiModelProperty(position = 7, value = "Complex JSON object that represents the Rule Node configuration.", readOnly = true) @Getter @Setter private transient JsonNode configurationDescriptor; + @Length(fieldName = "actions") @ApiModelProperty(position = 8, value = "Rule Node Actions. Deprecated. Always null.", readOnly = true) @Getter @Setter private String actions; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/widget/BaseWidgetType.java b/common/data/src/main/java/org/thingsboard/server/common/data/widget/BaseWidgetType.java index 0cc3e63b05..89452ffdbc 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/widget/BaseWidgetType.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/widget/BaseWidgetType.java @@ -21,6 +21,7 @@ import org.thingsboard.server.common.data.BaseData; import org.thingsboard.server.common.data.HasTenantId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.WidgetTypeId; +import org.thingsboard.server.common.data.validation.Length; import org.thingsboard.server.common.data.validation.NoXss; @Data @@ -31,12 +32,15 @@ public class BaseWidgetType extends BaseData implements HasTenantI @ApiModelProperty(position = 3, value = "JSON object with Tenant Id.", readOnly = true) private TenantId tenantId; @NoXss + @Length(fieldName = "bundleAlias") @ApiModelProperty(position = 4, value = "Reference to widget bundle", readOnly = true) private String bundleAlias; @NoXss + @Length(fieldName = "alias") @ApiModelProperty(position = 5, value = "Unique alias that is used in dashboards as a reference widget type", readOnly = true) private String alias; @NoXss + @Length(fieldName = "name") @ApiModelProperty(position = 6, value = "Widget name used in search and UI", readOnly = true) private String name; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/widget/WidgetTypeDetails.java b/common/data/src/main/java/org/thingsboard/server/common/data/widget/WidgetTypeDetails.java index fe7f87c7af..1af83de90d 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/widget/WidgetTypeDetails.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/widget/WidgetTypeDetails.java @@ -19,15 +19,18 @@ import com.fasterxml.jackson.annotation.JsonPropertyOrder; import io.swagger.annotations.ApiModelProperty; import lombok.Data; import org.thingsboard.server.common.data.id.WidgetTypeId; +import org.thingsboard.server.common.data.validation.Length; import org.thingsboard.server.common.data.validation.NoXss; @Data @JsonPropertyOrder({ "alias", "name", "image", "description", "descriptor" }) public class WidgetTypeDetails extends WidgetType { + @NoXss @ApiModelProperty(position = 8, value = "Base64 encoded thumbnail", readOnly = true) private String image; @NoXss + @Length(fieldName = "description") @ApiModelProperty(position = 9, value = "Description of the widget", readOnly = true) private String description; diff --git a/ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.html b/ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.html index 30fc1a66d6..7a90f69a8d 100644 --- a/ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.html +++ b/ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.html @@ -79,6 +79,9 @@ {{ 'admin.error-verification-url' | translate }} + + {{ 'admin.domain-name-max-length' | translate }} + @@ -246,6 +249,9 @@ {{ 'admin.oauth2.client-id-required' | translate }} + + {{ 'admin.oauth2.client-id-max-length' | translate }} + @@ -254,6 +260,9 @@ {{ 'admin.oauth2.client-secret-required' | translate }} + + {{ 'admin.oauth2.client-secret-max-length' | translate }} + @@ -426,17 +435,29 @@ *ngIf="registration.get('mapperConfig.basic.emailAttributeKey').hasError('required')"> {{ 'admin.oauth2.email-attribute-key-required' | translate }} + + {{ 'admin.oauth2.email-attribute-key-max-length' | translate }} +
admin.oauth2.first-name-attribute-key + + {{ 'admin.oauth2.first-name-attribute-key-max-length' | translate }} + admin.oauth2.last-name-attribute-key + + {{ 'admin.oauth2.last-name-attribute-key-max-length' | translate }} +
@@ -460,18 +481,30 @@ *ngIf="registration.get('mapperConfig.basic.tenantNamePattern').hasError('required')"> {{ 'admin.oauth2.tenant-name-pattern-required' | translate }} + + {{ 'admin.oauth2.tenant-name-pattern-max-length' | translate }} + admin.oauth2.customer-name-pattern + + {{ 'admin.oauth2.customer-name-pattern-max-length' | translate }} +
admin.oauth2.default-dashboard-name + + {{ 'admin.oauth2.default-dashboard-name-max-length' | translate }} + @@ -493,18 +526,28 @@ *ngIf="registration.get('mapperConfig.custom.url').hasError('pattern')"> {{ 'admin.oauth2.url-pattern' | translate }} + + {{ 'admin.oauth2.url-max-length' | translate }} +
common.username + + {{ 'admin.oauth2.username-max-length' | translate }} + common.password + + {{ 'admin.oauth2.password-max-length' | translate }} +
diff --git a/ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.ts b/ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.ts index 61e001e75b..2ab97d1b44 100644 --- a/ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.ts +++ b/ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.ts @@ -155,13 +155,18 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha tenantNamePattern = {value: null, disabled: true}; } const basicGroup = this.fb.group({ - emailAttributeKey: [mapperConfigBasic?.emailAttributeKey ? mapperConfigBasic.emailAttributeKey : 'email', Validators.required], - firstNameAttributeKey: [mapperConfigBasic?.firstNameAttributeKey ? mapperConfigBasic.firstNameAttributeKey : ''], - lastNameAttributeKey: [mapperConfigBasic?.lastNameAttributeKey ? mapperConfigBasic.lastNameAttributeKey : ''], + emailAttributeKey: [mapperConfigBasic?.emailAttributeKey ? mapperConfigBasic.emailAttributeKey : 'email', + [Validators.required, Validators.maxLength(31)]], + firstNameAttributeKey: [mapperConfigBasic?.firstNameAttributeKey ? mapperConfigBasic.firstNameAttributeKey : '', + Validators.maxLength(31)], + lastNameAttributeKey: [mapperConfigBasic?.lastNameAttributeKey ? mapperConfigBasic.lastNameAttributeKey : '', + Validators.maxLength(31)], tenantNameStrategy: [mapperConfigBasic?.tenantNameStrategy ? mapperConfigBasic.tenantNameStrategy : TenantNameStrategy.DOMAIN], - tenantNamePattern: [tenantNamePattern, Validators.required], - customerNamePattern: [mapperConfigBasic?.customerNamePattern ? mapperConfigBasic.customerNamePattern : null], - defaultDashboardName: [mapperConfigBasic?.defaultDashboardName ? mapperConfigBasic.defaultDashboardName : null], + tenantNamePattern: [tenantNamePattern, [Validators.required, Validators.maxLength(255)]], + customerNamePattern: [mapperConfigBasic?.customerNamePattern ? mapperConfigBasic.customerNamePattern : null, + Validators.maxLength(255)], + defaultDashboardName: [mapperConfigBasic?.defaultDashboardName ? mapperConfigBasic.defaultDashboardName : null, + Validators.maxLength(255)], alwaysFullScreen: [isDefinedAndNotNull(mapperConfigBasic?.alwaysFullScreen) ? mapperConfigBasic.alwaysFullScreen : false] }); @@ -178,9 +183,10 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha private formCustomGroup(mapperConfigCustom?: MapperConfigCustom): FormGroup { return this.fb.group({ - url: [mapperConfigCustom?.url ? mapperConfigCustom.url : null, [Validators.required, Validators.pattern(this.URL_REGEXP)]], - username: [mapperConfigCustom?.username ? mapperConfigCustom.username : null], - password: [mapperConfigCustom?.password ? mapperConfigCustom.password : null] + url: [mapperConfigCustom?.url ? mapperConfigCustom.url : null, + [Validators.required, Validators.pattern(this.URL_REGEXP), Validators.maxLength(255)]], + username: [mapperConfigCustom?.username ? mapperConfigCustom.username : null, Validators.maxLength(255)], + password: [mapperConfigCustom?.password ? mapperConfigCustom.password : null, Validators.maxLength(255)] }); } @@ -266,7 +272,7 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha private buildDomainInfoForm(domainInfo?: OAuth2DomainInfo): FormGroup { return this.fb.group({ name: [domainInfo ? domainInfo.name : this.window.location.hostname, [ - Validators.required, + Validators.required, Validators.maxLength(255), Validators.pattern(this.DOMAIN_AND_PORT_REGEXP)]], scheme: [domainInfo?.scheme ? domainInfo.scheme : DomainSchema.HTTPS, Validators.required] }, {validators: this.uniqueDomainValidator}); @@ -300,8 +306,8 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha platforms: [registration?.platforms ? registration.platforms : []], loginButtonLabel: [registration?.loginButtonLabel ? registration.loginButtonLabel : null, Validators.required], loginButtonIcon: [registration?.loginButtonIcon ? registration.loginButtonIcon : null], - clientId: [registration?.clientId ? registration.clientId : '', Validators.required], - clientSecret: [registration?.clientSecret ? registration.clientSecret : '', Validators.required], + clientId: [registration?.clientId ? registration.clientId : '', [Validators.required, Validators.maxLength(255)]], + clientSecret: [registration?.clientSecret ? registration.clientSecret : '', [Validators.required, Validators.maxLength(2048)]], accessTokenUri: [registration?.accessTokenUri ? registration.accessTokenUri : '', [Validators.required, Validators.pattern(this.URL_REGEXP)]], diff --git a/ui-ngx/src/app/modules/home/pages/edge/edge.component.html b/ui-ngx/src/app/modules/home/pages/edge/edge.component.html index 6d357ffee8..00f32ed578 100644 --- a/ui-ngx/src/app/modules/home/pages/edge/edge.component.html +++ b/ui-ngx/src/app/modules/home/pages/edge/edge.component.html @@ -143,8 +143,8 @@ {{ 'edge.edge-license-key-required' | translate }} - - {{ 'edge.type-max-length' | translate }} + + {{ 'edge.edge-license-key-max-length' | translate }}
@@ -156,6 +156,9 @@ {{ 'edge.cloud-endpoint-required' | translate }} + + {{ 'edge.cloud-endpoint-max-length' | translate }} + diff --git a/ui-ngx/src/app/modules/home/pages/edge/edge.component.ts b/ui-ngx/src/app/modules/home/pages/edge/edge.component.ts index 397423b863..162bfeb463 100644 --- a/ui-ngx/src/app/modules/home/pages/edge/edge.component.ts +++ b/ui-ngx/src/app/modules/home/pages/edge/edge.component.ts @@ -73,8 +73,8 @@ export class EdgeComponent extends EntityComponent { name: [entity ? entity.name : '', [Validators.required, Validators.maxLength(255)]], type: [entity?.type ? entity.type : 'default', [Validators.required, Validators.maxLength(255)]], label: [entity ? entity.label : '', Validators.maxLength(255)], - cloudEndpoint: [null, [Validators.required]], - edgeLicenseKey: ['', [Validators.required]], + cloudEndpoint: [null, [Validators.required, Validators.maxLength(255)]], + edgeLicenseKey: ['', [Validators.required, Validators.maxLength(30)]], routingKey: this.fb.control({value: entity ? entity.routingKey : null, disabled: true}), secret: this.fb.control({value: entity ? entity.secret : null, disabled: true}), additionalInfo: this.fb.group( diff --git a/ui-ngx/src/app/modules/home/pages/widget/widget-editor.component.html b/ui-ngx/src/app/modules/home/pages/widget/widget-editor.component.html index 399c85b401..f935087bdf 100644 --- a/ui-ngx/src/app/modules/home/pages/widget/widget-editor.component.html +++ b/ui-ngx/src/app/modules/home/pages/widget/widget-editor.component.html @@ -21,7 +21,7 @@ - diff --git a/ui-ngx/src/assets/locale/locale.constant-en_US.json b/ui-ngx/src/assets/locale/locale.constant-en_US.json index ae80aeb230..8b7ff34a54 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -158,6 +158,7 @@ "user-lockout-notification-email": "In case user account lockout, send notification to email", "domain-name": "Domain name", "domain-name-unique": "Domain name and protocol need to unique.", + "domain-name-max-length": "Domain name should be less than 256", "error-verification-url": "A domain name shouldn't contain symbols '/' and ':'. Example: thingsboard.io", "oauth2": { "access-token-uri": "Access token URI", @@ -174,21 +175,28 @@ "client-authentication-method": "Client authentication method", "client-id": "Client ID", "client-id-required": "Client ID is required.", + "client-id-max-length": "Client ID should be less than 256", "client-secret": "Client secret", "client-secret-required": "Client secret is required.", + "client-secret-max-length": "Client secret should be less than 2049", "custom-setting": "Custom settings", "customer-name-pattern": "Customer name pattern", + "customer-name-pattern-max-length": "Customer name pattern should be less than 256", "default-dashboard-name": "Default dashboard name", + "default-dashboard-name-max-length": "Default dashboard name should be less than 256", "delete-domain-text": "Be careful, after the confirmation a domain and all provider data will be unavailable.", "delete-domain-title": "Are you sure you want to delete settings the domain '{{domainName}}'?", "delete-registration-text": "Be careful, after the confirmation a provider data will be unavailable.", "delete-registration-title": "Are you sure you want to delete the provider '{{name}}'?", "email-attribute-key": "Email attribute key", "email-attribute-key-required": "Email attribute key is required.", + "email-attribute-key-max-length": "Email attribute key should be less than 32", "first-name-attribute-key": "First name attribute key", + "first-name-attribute-key-max-length": "First name attribute key should be less than 32", "general": "General", "jwk-set-uri": "JSON Web Key URI", "last-name-attribute-key": "Last name attribute key", + "last-name-attribute-key-max-length": "Last name attribute key should be less than 32", "login-button-icon": "Login button icon", "login-button-label": "Provider label", "login-button-label-placeholder": "Login with $(Provider label)", @@ -197,6 +205,7 @@ "mapper": "Mapper", "new-domain": "New domain", "oauth2": "OAuth2", + "password-max-length": "Password should be less than 256", "redirect-uri-template": "Redirect URI template", "copy-redirect-uri": "Copy redirect URI", "registration-id": "Registration ID", @@ -206,14 +215,17 @@ "scope-required": "Scope is required.", "tenant-name-pattern": "Tenant name pattern", "tenant-name-pattern-required": "Tenant name pattern is required.", + "tenant-name-pattern-max-length": "Tenant name pattern ishould be less than 256", "tenant-name-strategy": "Tenant name strategy", "type": "Mapper type", "uri-pattern-error": "Invalid URI format.", "url": "URL", "url-pattern": "Invalid URL format.", "url-required": "URL is required.", + "url-max-length": "URL should be less than 256", "user-info-uri": "User info URI", "user-info-uri-required": "User info URI is required.", + "username-max-length": "User name should be less than 256", "user-name-attribute-name": "User name attribute key", "user-name-attribute-name-required": "User name attribute key is required", "protocol": "Protocol", @@ -1448,9 +1460,11 @@ "name-required": "Name is required.", "edge-license-key": "Edge License Key", "edge-license-key-required": "Edge License Key is required.", + "edge-license-key-max-length": "Edge License Key should be less than 31", "edge-license-key-hint": "To obtain your license please navigate to the pricing page and select the best license option for your case.", "cloud-endpoint": "Cloud Endpoint", "cloud-endpoint-required": "Cloud Endpoint is required.", + "cloud-endpoint-max-length": "Cloud Endpoint should be less than 256", "cloud-endpoint-hint": "Edge requires HTTP(s) access to Cloud (ThingsBoard CE/PE) to verify the license key. Please specify Cloud URL that Edge is able to connect to.", "description": "Description", "details": "Details", From 676a60e8042ce105427d7475f1c09fa3f55aede8 Mon Sep 17 00:00:00 2001 From: Andrew Shvayka Date: Fri, 5 Nov 2021 18:26:53 +0200 Subject: [PATCH 03/10] Revert "[3.3.3] Entities search within all tenants" --- .../controller/EntityQueryController.java | 83 +------ .../data/search/EntitiesSearchRequest.java | 25 --- .../data/search/EntitySearchResult.java | 50 ----- .../service/query/EntitiesSearchService.java | 26 --- .../query/EntitiesSearchServiceImpl.java | 208 ------------------ .../permission/PermissionChecker.java | 1 - .../permission/SysAdminPermissions.java | 31 ++- .../common/data/query/EntityFilterType.java | 3 +- .../data/query/EntityNameOrIdFilter.java | 32 --- .../query/DefaultEntityQueryRepository.java | 52 +---- .../dao/sql/query/EntityKeyMapping.java | 60 +---- 11 files changed, 39 insertions(+), 532 deletions(-) delete mode 100644 application/src/main/java/org/thingsboard/server/data/search/EntitiesSearchRequest.java delete mode 100644 application/src/main/java/org/thingsboard/server/data/search/EntitySearchResult.java delete mode 100644 application/src/main/java/org/thingsboard/server/service/query/EntitiesSearchService.java delete mode 100644 application/src/main/java/org/thingsboard/server/service/query/EntitiesSearchServiceImpl.java delete mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/query/EntityNameOrIdFilter.java diff --git a/application/src/main/java/org/thingsboard/server/controller/EntityQueryController.java b/application/src/main/java/org/thingsboard/server/controller/EntityQueryController.java index e5467c8ae9..019066c636 100644 --- a/application/src/main/java/org/thingsboard/server/controller/EntityQueryController.java +++ b/application/src/main/java/org/thingsboard/server/controller/EntityQueryController.java @@ -17,10 +17,9 @@ package org.thingsboard.server.controller; import io.swagger.annotations.ApiOperation; import io.swagger.annotations.ApiParam; -import lombok.RequiredArgsConstructor; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.ResponseEntity; import org.springframework.security.access.prepost.PreAuthorize; -import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; @@ -37,30 +36,20 @@ import org.thingsboard.server.common.data.query.EntityCountQuery; import org.thingsboard.server.common.data.query.EntityData; import org.thingsboard.server.common.data.query.EntityDataPageLink; import org.thingsboard.server.common.data.query.EntityDataQuery; -import org.thingsboard.server.data.search.EntitiesSearchRequest; -import org.thingsboard.server.data.search.EntitySearchResult; import org.thingsboard.server.queue.util.TbCoreComponent; -import org.thingsboard.server.service.query.EntitiesSearchService; import org.thingsboard.server.service.query.EntityQueryService; import static org.thingsboard.server.controller.ControllerConstants.ALARM_DATA_QUERY_DESCRIPTION; import static org.thingsboard.server.controller.ControllerConstants.ENTITY_COUNT_QUERY_DESCRIPTION; import static org.thingsboard.server.controller.ControllerConstants.ENTITY_DATA_QUERY_DESCRIPTION; -import static org.thingsboard.server.controller.ControllerConstants.NEW_LINE; -import static org.thingsboard.server.controller.ControllerConstants.PAGE_NUMBER_DESCRIPTION; -import static org.thingsboard.server.controller.ControllerConstants.PAGE_SIZE_DESCRIPTION; -import static org.thingsboard.server.controller.ControllerConstants.SORT_ORDER_ALLOWABLE_VALUES; -import static org.thingsboard.server.controller.ControllerConstants.SORT_ORDER_DESCRIPTION; -import static org.thingsboard.server.controller.ControllerConstants.SORT_PROPERTY_DESCRIPTION; @RestController @TbCoreComponent @RequestMapping("/api") -@RequiredArgsConstructor public class EntityQueryController extends BaseController { - private final EntityQueryService entityQueryService; - private final EntitiesSearchService entitiesSearchService; + @Autowired + private EntityQueryService entityQueryService; private static final int MAX_PAGE_SIZE = 100; @@ -134,70 +123,4 @@ public class EntityQueryController extends BaseController { } } - @ApiOperation(value = "Search entities (searchEntities)", notes = "Search entities with specified entity type by id or name within the whole platform. " + - "Searchable entity types are: CUSTOMER, USER, DEVICE, DEVICE_PROFILE, ASSET, ENTITY_VIEW, DASHBOARD, " + - "RULE_CHAIN, EDGE, OTA_PACKAGE, TB_RESOURCE, WIDGETS_BUNDLE, TENANT, TENANT_PROFILE." + NEW_LINE + - "The platform will search for entities, where a name contains the search text (case-insensitively), " + - "or if the search query is a valid UUID (e.g. 128e4d40-26b3-11ec-aaeb-c7661c54701e) then " + - "it will also search for an entity where id fully matches the query. If search query is empty " + - "then all entities will be returned (according to page number, page size and sorting)." + NEW_LINE + - "The returned result is a page of EntitySearchResult, which contains: " + - "entity id, entity fields represented as strings, tenant info and owner info. " + - "Returned entity fields are: name, type (will be present for USER, DEVICE, ASSET, ENTITY_VIEW, RULE_CHAIN, " + - "EDGE, OTA_PACKAGE, TB_RESOURCE entity types; in case of USER - the type is its authority), " + - "createdTime and lastActivityTime (will only be present for DEVICE and USER; for USER it is its last login time). " + - "Tenant info contains tenant's id and title; owner info contains the same info for an entity's owner " + - "(its customer, or if it is not a customer's entity - tenant)." + NEW_LINE + - "Example response value:\n" + - "{\n" + - " \"data\": [\n" + - " {\n" + - " \"entityId\": {\n" + - " \"entityType\": \"DEVICE\",\n" + - " \"id\": \"48be0670-25c9-11ec-a618-8165eb6b112a\"\n" + - " },\n" + - " \"fields\": {\n" + - " \"name\": \"Thermostat T1\",\n" + - " \"createdTime\": \"1633430698071\",\n" + - " \"lastActivityTime\": \"1635761085285\",\n" + - " \"type\": \"thermostat\"\n" + - " },\n" + - " \"tenantInfo\": {\n" + - " \"id\": {\n" + - " \"entityType\": \"TENANT\",\n" + - " \"id\": \"2ddd6120-25c9-11ec-a618-8165eb6b112a\"\n" + - " },\n" + - " \"name\": \"Tenant\"\n" + - " },\n" + - " \"ownerInfo\": {\n" + - " \"id\": {\n" + - " \"entityType\": \"CUSTOMER\",\n" + - " \"id\": \"26cba800-eee3-11eb-9e2c-fb031bd4619c\"\n" + - " },\n" + - " \"name\": \"Customer A\"\n" + - " }\n" + - " }\n" + - " ],\n" + - " \"totalPages\": 1,\n" + - " \"totalElements\": 1,\n" + - " \"hasNext\": false\n" + - "}") - @PostMapping("/entities/search") - @PreAuthorize("hasAnyAuthority('SYS_ADMIN')") - public PageData searchEntities(@RequestBody EntitiesSearchRequest request, - @ApiParam(value = PAGE_NUMBER_DESCRIPTION, required = true) - @RequestParam int page, - @ApiParam(value = PAGE_SIZE_DESCRIPTION, required = true) - @RequestParam int pageSize, - @ApiParam(value = SORT_PROPERTY_DESCRIPTION, allowableValues = "name, type, createdTime, lastActivityTime, createdTime, tenantId, customerId", required = false) - @RequestParam(required = false) String sortProperty, - @ApiParam(value = SORT_ORDER_DESCRIPTION, allowableValues = SORT_ORDER_ALLOWABLE_VALUES, required = false) - @RequestParam(required = false) String sortOrder) throws ThingsboardException { - try { - return entitiesSearchService.searchEntities(getCurrentUser(), request, createPageLink(pageSize, page, null, sortProperty, sortOrder)); - } catch (Exception e) { - throw handleException(e); - } - } - } diff --git a/application/src/main/java/org/thingsboard/server/data/search/EntitiesSearchRequest.java b/application/src/main/java/org/thingsboard/server/data/search/EntitiesSearchRequest.java deleted file mode 100644 index 48f8966107..0000000000 --- a/application/src/main/java/org/thingsboard/server/data/search/EntitiesSearchRequest.java +++ /dev/null @@ -1,25 +0,0 @@ -/** - * Copyright © 2016-2021 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.data.search; - -import lombok.Data; -import org.thingsboard.server.common.data.EntityType; - -@Data -public class EntitiesSearchRequest { - private EntityType entityType; - private String searchQuery; -} diff --git a/application/src/main/java/org/thingsboard/server/data/search/EntitySearchResult.java b/application/src/main/java/org/thingsboard/server/data/search/EntitySearchResult.java deleted file mode 100644 index a26d580b7d..0000000000 --- a/application/src/main/java/org/thingsboard/server/data/search/EntitySearchResult.java +++ /dev/null @@ -1,50 +0,0 @@ -/** - * Copyright © 2016-2021 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.data.search; - -import lombok.AllArgsConstructor; -import lombok.Data; -import lombok.NoArgsConstructor; -import org.thingsboard.server.common.data.id.EntityId; -import org.thingsboard.server.common.data.id.TenantId; - -import java.util.Map; - -@Data -public class EntitySearchResult { - private EntityId entityId; - private Map fields; - - private EntityTenantInfo tenantInfo; - private EntityOwnerInfo ownerInfo; - - @Data - @AllArgsConstructor - @NoArgsConstructor - public static final class EntityTenantInfo { - private TenantId id; - private String name; - } - - @Data - @AllArgsConstructor - @NoArgsConstructor - public static final class EntityOwnerInfo { - private EntityId id; - private String name; - } - -} diff --git a/application/src/main/java/org/thingsboard/server/service/query/EntitiesSearchService.java b/application/src/main/java/org/thingsboard/server/service/query/EntitiesSearchService.java deleted file mode 100644 index 36a34f89e7..0000000000 --- a/application/src/main/java/org/thingsboard/server/service/query/EntitiesSearchService.java +++ /dev/null @@ -1,26 +0,0 @@ -/** - * Copyright © 2016-2021 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.query; - -import org.thingsboard.server.common.data.page.PageData; -import org.thingsboard.server.common.data.page.PageLink; -import org.thingsboard.server.data.search.EntitiesSearchRequest; -import org.thingsboard.server.data.search.EntitySearchResult; -import org.thingsboard.server.service.security.model.SecurityUser; - -public interface EntitiesSearchService { - PageData searchEntities(SecurityUser user, EntitiesSearchRequest request, PageLink pageLink); -} diff --git a/application/src/main/java/org/thingsboard/server/service/query/EntitiesSearchServiceImpl.java b/application/src/main/java/org/thingsboard/server/service/query/EntitiesSearchServiceImpl.java deleted file mode 100644 index 1753a49c0d..0000000000 --- a/application/src/main/java/org/thingsboard/server/service/query/EntitiesSearchServiceImpl.java +++ /dev/null @@ -1,208 +0,0 @@ -/** - * Copyright © 2016-2021 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.query; - -import com.google.common.base.Strings; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.apache.commons.lang3.StringUtils; -import org.springframework.stereotype.Service; -import org.thingsboard.server.common.data.ContactBased; -import org.thingsboard.server.common.data.Customer; -import org.thingsboard.server.common.data.EntityType; -import org.thingsboard.server.common.data.Tenant; -import org.thingsboard.server.common.data.id.CustomerId; -import org.thingsboard.server.common.data.id.EntityId; -import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.common.data.page.PageData; -import org.thingsboard.server.common.data.page.PageLink; -import org.thingsboard.server.common.data.page.SortOrder; -import org.thingsboard.server.common.data.query.EntityData; -import org.thingsboard.server.common.data.query.EntityDataPageLink; -import org.thingsboard.server.common.data.query.EntityDataQuery; -import org.thingsboard.server.common.data.query.EntityDataSortOrder; -import org.thingsboard.server.common.data.query.EntityKey; -import org.thingsboard.server.common.data.query.EntityKeyType; -import org.thingsboard.server.common.data.query.EntityNameOrIdFilter; -import org.thingsboard.server.dao.customer.CustomerService; -import org.thingsboard.server.dao.tenant.TenantService; -import org.thingsboard.server.data.search.EntitiesSearchRequest; -import org.thingsboard.server.data.search.EntitySearchResult; -import org.thingsboard.server.queue.util.TbCoreComponent; -import org.thingsboard.server.service.security.model.SecurityUser; -import org.thingsboard.server.service.state.DefaultDeviceStateService; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.EnumSet; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.Set; -import java.util.UUID; -import java.util.stream.Collectors; -import java.util.stream.Stream; - -import static org.thingsboard.server.common.data.EntityType.ASSET; -import static org.thingsboard.server.common.data.EntityType.CUSTOMER; -import static org.thingsboard.server.common.data.EntityType.DASHBOARD; -import static org.thingsboard.server.common.data.EntityType.DEVICE; -import static org.thingsboard.server.common.data.EntityType.DEVICE_PROFILE; -import static org.thingsboard.server.common.data.EntityType.EDGE; -import static org.thingsboard.server.common.data.EntityType.ENTITY_VIEW; -import static org.thingsboard.server.common.data.EntityType.OTA_PACKAGE; -import static org.thingsboard.server.common.data.EntityType.RULE_CHAIN; -import static org.thingsboard.server.common.data.EntityType.TB_RESOURCE; -import static org.thingsboard.server.common.data.EntityType.TENANT; -import static org.thingsboard.server.common.data.EntityType.TENANT_PROFILE; -import static org.thingsboard.server.common.data.EntityType.USER; -import static org.thingsboard.server.common.data.EntityType.WIDGETS_BUNDLE; -import static org.thingsboard.server.dao.sql.query.EntityKeyMapping.CREATED_TIME; -import static org.thingsboard.server.dao.sql.query.EntityKeyMapping.CUSTOMER_ID; -import static org.thingsboard.server.dao.sql.query.EntityKeyMapping.LAST_ACTIVITY_TIME; -import static org.thingsboard.server.dao.sql.query.EntityKeyMapping.NAME; -import static org.thingsboard.server.dao.sql.query.EntityKeyMapping.TENANT_ID; -import static org.thingsboard.server.dao.sql.query.EntityKeyMapping.TYPE; - -@Service -@TbCoreComponent -@RequiredArgsConstructor -@Slf4j -public class EntitiesSearchServiceImpl implements EntitiesSearchService { - private final EntityQueryService entityQueryService; - - private final TenantService tenantService; - private final CustomerService customerService; - private final DefaultDeviceStateService deviceStateService; - - private static final List entityResponseFields = Stream.of(CREATED_TIME, NAME, TYPE, TENANT_ID, CUSTOMER_ID) - .map(field -> new EntityKey(EntityKeyType.ENTITY_FIELD, field)) - .collect(Collectors.toList()); - - private static final Set searchableEntityTypes = EnumSet.of( - TENANT, CUSTOMER, USER, DASHBOARD, ASSET, DEVICE, RULE_CHAIN, ENTITY_VIEW, - WIDGETS_BUNDLE, TENANT_PROFILE, DEVICE_PROFILE, TB_RESOURCE, OTA_PACKAGE, EDGE - ); - - @Override - public PageData searchEntities(SecurityUser user, EntitiesSearchRequest request, PageLink pageLink) { - EntityType entityType = request.getEntityType(); - if (!searchableEntityTypes.contains(entityType)) { - return new PageData<>(); - } - - EntityDataQuery query = createSearchQuery(request.getSearchQuery(), entityType, pageLink); - PageData resultPage = entityQueryService.findEntityDataByQuery(user, query); - - Map> localOwnersCache = new HashMap<>(); - return resultPage.mapData(entityData -> { - Map fields = new HashMap<>(); - entityData.getLatest().values().stream() - .flatMap(values -> values.entrySet().stream()) - .forEach(entry -> fields.put(entry.getKey(), Strings.emptyToNull(entry.getValue().getValue()))); - - EntitySearchResult entitySearchResult = new EntitySearchResult(); - - entitySearchResult.setEntityId(entityData.getEntityId()); - entitySearchResult.setFields(fields); - setOwnerInfo(entitySearchResult, localOwnersCache); - - return entitySearchResult; - }); - } - - private EntityDataQuery createSearchQuery(String searchQuery, EntityType entityType, PageLink pageLink) { - EntityDataPageLink entityDataPageLink = new EntityDataPageLink(); - entityDataPageLink.setPageSize(pageLink.getPageSize()); - entityDataPageLink.setPage(pageLink.getPage()); - if (pageLink.getSortOrder() != null && StringUtils.isNotEmpty(pageLink.getSortOrder().getProperty())) { - entityDataPageLink.setSortOrder(new EntityDataSortOrder(new EntityKey(EntityKeyType.ENTITY_FIELD, pageLink.getSortOrder().getProperty()), - EntityDataSortOrder.Direction.valueOf(Optional.ofNullable(pageLink.getSortOrder().getDirection()).orElse(SortOrder.Direction.ASC).name()))); - } - - EntityNameOrIdFilter filter = new EntityNameOrIdFilter(); - filter.setEntityType(entityType); - filter.setNameOrId(searchQuery); - - List entityFields = entityResponseFields; - List latestValues = Collections.emptyList(); - - if (entityType == USER) { - entityFields = new ArrayList<>(entityFields); - entityFields.add(new EntityKey(EntityKeyType.ENTITY_FIELD, LAST_ACTIVITY_TIME)); - } else if (entityType == DEVICE) { - EntityKey lastActivityTimeKey; - if (deviceStateService.isPersistToTelemetry()) { - lastActivityTimeKey = new EntityKey(EntityKeyType.TIME_SERIES, LAST_ACTIVITY_TIME); - } else { - lastActivityTimeKey = new EntityKey(EntityKeyType.SERVER_ATTRIBUTE, LAST_ACTIVITY_TIME); - } - latestValues = List.of(lastActivityTimeKey); - if (entityDataPageLink.getSortOrder() != null && entityDataPageLink.getSortOrder().getKey().getKey().equals(LAST_ACTIVITY_TIME)) { - entityDataPageLink.getSortOrder().setKey(lastActivityTimeKey); - } - } - - return new EntityDataQuery(filter, entityDataPageLink, entityFields, latestValues, Collections.emptyList()); - } - - private void setOwnerInfo(EntitySearchResult entitySearchResult, Map> localOwnersCache) { - Map fields = entitySearchResult.getFields(); - - UUID tenantUuid = toUuid(fields.remove(TENANT_ID)); - UUID customerUuid = toUuid(fields.remove(CUSTOMER_ID)); - - Tenant tenant = null; - if (tenantUuid != null) { - tenant = getTenant(new TenantId(tenantUuid), localOwnersCache); - } - - ContactBased owner; - if (customerUuid != null) { - owner = getCustomer(new CustomerId(customerUuid), localOwnersCache); - } else { - owner = tenant; - } - - if (tenant != null) { - entitySearchResult.setTenantInfo(new EntitySearchResult.EntityTenantInfo(tenant.getId(), tenant.getName())); - } - if (owner != null) { - entitySearchResult.setOwnerInfo(new EntitySearchResult.EntityOwnerInfo(owner.getId(), owner.getName())); - } - } - - private Tenant getTenant(TenantId tenantId, Map> localOwnersCache) { - return (Tenant) localOwnersCache.computeIfAbsent(tenantId, id -> tenantService.findTenantById(tenantId)); - } - - private Customer getCustomer(CustomerId customerId, Map> localOwnersCache) { - return (Customer) localOwnersCache.computeIfAbsent(customerId, id -> customerService.findCustomerById(TenantId.SYS_TENANT_ID, customerId)); - } - - private UUID toUuid(String uuid) { - try { - UUID id = UUID.fromString(uuid); - if (!id.equals(EntityId.NULL_UUID)) { - return id; - } - } catch (Exception ignored) {} - - return null; - } - -} diff --git a/application/src/main/java/org/thingsboard/server/service/security/permission/PermissionChecker.java b/application/src/main/java/org/thingsboard/server/service/security/permission/PermissionChecker.java index 45a43876e6..3f6e16d753 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/permission/PermissionChecker.java +++ b/application/src/main/java/org/thingsboard/server/service/security/permission/PermissionChecker.java @@ -69,6 +69,5 @@ public interface PermissionChecker { } }; - PermissionChecker allowReadPermissionChecker = new GenericPermissionChecker(Operation.READ, Operation.READ_TELEMETRY, Operation.READ_ATTRIBUTES); } diff --git a/application/src/main/java/org/thingsboard/server/service/security/permission/SysAdminPermissions.java b/application/src/main/java/org/thingsboard/server/service/security/permission/SysAdminPermissions.java index 8f9648dc93..f703102e18 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/permission/SysAdminPermissions.java +++ b/application/src/main/java/org/thingsboard/server/service/security/permission/SysAdminPermissions.java @@ -17,7 +17,10 @@ package org.thingsboard.server.service.security.permission; import org.springframework.stereotype.Component; import org.thingsboard.server.common.data.HasTenantId; +import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.id.EntityId; +import org.thingsboard.server.common.data.id.UserId; +import org.thingsboard.server.common.data.security.Authority; import org.thingsboard.server.service.security.model.SecurityUser; @Component(value="sysAdminPermissions") @@ -26,29 +29,23 @@ public class SysAdminPermissions extends AbstractPermissions { public SysAdminPermissions() { super(); put(Resource.ADMIN_SETTINGS, PermissionChecker.allowAllPermissionChecker); - put(Resource.DASHBOARD, PermissionChecker.allowReadPermissionChecker); + put(Resource.DASHBOARD, new PermissionChecker.GenericPermissionChecker(Operation.READ)); put(Resource.TENANT, PermissionChecker.allowAllPermissionChecker); - put(Resource.RULE_CHAIN, PermissionChecker.allowReadPermissionChecker); - put(Resource.USER, PermissionChecker.allowAllPermissionChecker); + put(Resource.RULE_CHAIN, systemEntityPermissionChecker); + put(Resource.USER, userPermissionChecker); put(Resource.WIDGETS_BUNDLE, systemEntityPermissionChecker); put(Resource.WIDGET_TYPE, systemEntityPermissionChecker); put(Resource.OAUTH2_CONFIGURATION_INFO, PermissionChecker.allowAllPermissionChecker); put(Resource.OAUTH2_CONFIGURATION_TEMPLATE, PermissionChecker.allowAllPermissionChecker); put(Resource.TENANT_PROFILE, PermissionChecker.allowAllPermissionChecker); - put(Resource.TB_RESOURCE, PermissionChecker.allowAllPermissionChecker); - put(Resource.CUSTOMER, PermissionChecker.allowReadPermissionChecker); - put(Resource.ASSET, PermissionChecker.allowReadPermissionChecker); - put(Resource.DEVICE, PermissionChecker.allowReadPermissionChecker); - put(Resource.ENTITY_VIEW, PermissionChecker.allowReadPermissionChecker); - put(Resource.DEVICE_PROFILE, PermissionChecker.allowReadPermissionChecker); - put(Resource.OTA_PACKAGE, PermissionChecker.allowReadPermissionChecker); - put(Resource.EDGE, PermissionChecker.allowReadPermissionChecker); + put(Resource.TB_RESOURCE, systemEntityPermissionChecker); } private static final PermissionChecker systemEntityPermissionChecker = new PermissionChecker() { @Override public boolean hasPermission(SecurityUser user, Operation operation, EntityId entityId, HasTenantId entity) { + if (entity.getTenantId() != null && !entity.getTenantId().isNullUid()) { return false; } @@ -56,4 +53,16 @@ public class SysAdminPermissions extends AbstractPermissions { } }; + private static final PermissionChecker userPermissionChecker = new PermissionChecker() { + + @Override + public boolean hasPermission(SecurityUser user, Operation operation, UserId userId, User userEntity) { + if (Authority.CUSTOMER_USER.equals(userEntity.getAuthority())) { + return false; + } + return true; + } + + }; + } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/query/EntityFilterType.java b/common/data/src/main/java/org/thingsboard/server/common/data/query/EntityFilterType.java index c75d6bf4df..1a843c7b0f 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/query/EntityFilterType.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/query/EntityFilterType.java @@ -29,8 +29,7 @@ public enum EntityFilterType { DEVICE_SEARCH_QUERY("deviceSearchQuery"), ENTITY_VIEW_SEARCH_QUERY("entityViewSearchQuery"), EDGE_SEARCH_QUERY("edgeSearchQuery"), - API_USAGE_STATE("apiUsageState"), - ENTITY_NAME_OR_ID("entityNameOrId"); + API_USAGE_STATE("apiUsageState"); private final String label; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/query/EntityNameOrIdFilter.java b/common/data/src/main/java/org/thingsboard/server/common/data/query/EntityNameOrIdFilter.java deleted file mode 100644 index 1fcb9e9350..0000000000 --- a/common/data/src/main/java/org/thingsboard/server/common/data/query/EntityNameOrIdFilter.java +++ /dev/null @@ -1,32 +0,0 @@ -/** - * Copyright © 2016-2021 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.query; - -import lombok.Data; -import lombok.EqualsAndHashCode; -import org.thingsboard.server.common.data.EntityType; - -@EqualsAndHashCode(callSuper = true) -@Data -public class EntityNameOrIdFilter extends EntitySearchQueryFilter { - private String nameOrId; - private EntityType entityType; - - @Override - public EntityFilterType getType() { - return EntityFilterType.ENTITY_NAME_OR_ID; - } -} diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/query/DefaultEntityQueryRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/query/DefaultEntityQueryRepository.java index cbecc96f59..4b331f78f2 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/query/DefaultEntityQueryRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/query/DefaultEntityQueryRepository.java @@ -44,7 +44,6 @@ import org.thingsboard.server.common.data.query.EntityFilterType; import org.thingsboard.server.common.data.query.EntityKeyType; import org.thingsboard.server.common.data.query.EntityListFilter; import org.thingsboard.server.common.data.query.EntityNameFilter; -import org.thingsboard.server.common.data.query.EntityNameOrIdFilter; import org.thingsboard.server.common.data.query.EntitySearchQueryFilter; import org.thingsboard.server.common.data.query.EntityTypeFilter; import org.thingsboard.server.common.data.query.EntityViewSearchQueryFilter; @@ -237,12 +236,6 @@ public class DefaultEntityQueryRepository implements EntityQueryRepository { entityTableMap.put(EntityType.TENANT, "tenant"); entityTableMap.put(EntityType.API_USAGE_STATE, SELECT_API_USAGE_STATE); entityTableMap.put(EntityType.EDGE, "edge"); - entityTableMap.put(EntityType.RULE_CHAIN, "rule_chain"); - entityTableMap.put(EntityType.WIDGETS_BUNDLE, "widgets_bundle"); - entityTableMap.put(EntityType.TENANT_PROFILE, "tenant_profile"); - entityTableMap.put(EntityType.DEVICE_PROFILE, "device_profile"); - entityTableMap.put(EntityType.TB_RESOURCE, "resource"); - entityTableMap.put(EntityType.OTA_PACKAGE, "ota_package"); } public static EntityType[] RELATION_QUERY_ENTITY_TYPES = new EntityType[]{ @@ -448,7 +441,7 @@ public class DefaultEntityQueryRepository implements EntityQueryRepository { Optional sortOrderMappingOpt = mappings.stream().filter(EntityKeyMapping::isSortOrder).findFirst(); if (sortOrderMappingOpt.isPresent()) { EntityKeyMapping sortOrderMapping = sortOrderMappingOpt.get(); - String direction = sortOrder.getDirection() == EntityDataSortOrder.Direction.ASC ? "asc" : "desc nulls last"; + String direction = sortOrder.getDirection() == EntityDataSortOrder.Direction.ASC ? "asc" : "desc"; if (sortOrderMapping.getEntityKey().getType() == EntityKeyType.ENTITY_FIELD) { dataQuery = String.format("%s order by %s %s, result.id %s", dataQuery, sortOrderMapping.getValueAlias(), direction, direction); } else { @@ -478,26 +471,15 @@ public class DefaultEntityQueryRepository implements EntityQueryRepository { String entityFieldsQuery = EntityKeyMapping.buildQuery(ctx, entityFieldsFilters, entityFilter.getType()); String result = permissionQuery; if (!entityFilterQuery.isEmpty()) { - if (!result.isEmpty()) { - result += " and (" + entityFilterQuery + ")"; - } else { - result = "(" + entityFilterQuery + ")"; - } + result += " and (" + entityFilterQuery + ")"; } if (!entityFieldsQuery.isEmpty()) { - if (!result.isEmpty()) { - result += " and (" + entityFieldsQuery + ")"; - } else { - result = "(" + entityFieldsQuery + ")"; - } + result += " and (" + entityFieldsQuery + ")"; } return result; } private String buildPermissionQuery(QueryContext ctx, EntityFilter entityFilter) { - if (ctx.getTenantId().equals(TenantId.SYS_TENANT_ID)) { - return ""; - } switch (entityFilter.getType()) { case RELATIONS_QUERY: case DEVICE_SEARCH_QUERY: @@ -566,8 +548,6 @@ public class DefaultEntityQueryRepository implements EntityQueryRepository { case API_USAGE_STATE: case ENTITY_TYPE: return ""; - case ENTITY_NAME_OR_ID: - return entityNameOrIdQuery(ctx, (EntityNameOrIdFilter) entityFilter); default: throw new RuntimeException("Not implemented!"); } @@ -779,27 +759,7 @@ public class DefaultEntityQueryRepository implements EntityQueryRepository { private String entityNameQuery(QueryContext ctx, EntityNameFilter filter) { ctx.addStringParameter("entity_filter_name_filter", filter.getEntityNameFilter()); - return "lower(e.search_text) like lower(concat('%', :entity_filter_name_filter, '%'))"; - } - - private String entityNameOrIdQuery(QueryContext ctx, EntityNameOrIdFilter filter) { - String nameOrId = filter.getNameOrId(); - if (StringUtils.isNotEmpty(nameOrId)) { - nameOrId = nameOrId.replaceAll("%", "\\\\%").replaceAll("_", "\\\\_"); - ctx.addStringParameter("entity_id_or_search_text_filter", nameOrId); - String query = ""; - - String searchTextField = EntityKeyMapping.searchTextFields.get(filter.getEntityType()); - query += "lower(e." + searchTextField + ") like lower(concat('%', :entity_id_or_search_text_filter, '%'))"; - - try { - UUID.fromString(nameOrId); - query += " or e.id = :entity_id_or_search_text_filter::uuid"; - } catch (Exception ignored) {} - - return query; - } - return "true"; + return "lower(e.search_text) like lower(concat(:entity_filter_name_filter, '%%'))"; } private String typeQuery(QueryContext ctx, EntityFilter filter) { @@ -827,7 +787,7 @@ public class DefaultEntityQueryRepository implements EntityQueryRepository { } ctx.addStringParameter("entity_filter_type_query_type", type); ctx.addStringParameter("entity_filter_type_query_name", name); - return "e.type = :entity_filter_type_query_type and lower(e.search_text) like lower(concat('%', :entity_filter_type_query_name, '%'))"; + return "e.type = :entity_filter_type_query_type and lower(e.search_text) like lower(concat(:entity_filter_type_query_name, '%%'))"; } private EntityType resolveEntityType(EntityFilter entityFilter) { @@ -856,8 +816,6 @@ public class DefaultEntityQueryRepository implements EntityQueryRepository { return ((RelationsQueryFilter) entityFilter).getRootEntity().getEntityType(); case API_USAGE_STATE: return EntityType.API_USAGE_STATE; - case ENTITY_NAME_OR_ID: - return ((EntityNameOrIdFilter) entityFilter).getEntityType(); default: throw new RuntimeException("Not implemented!"); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/query/EntityKeyMapping.java b/dao/src/main/java/org/thingsboard/server/dao/sql/query/EntityKeyMapping.java index 3d40156329..caed48758a 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/query/EntityKeyMapping.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/query/EntityKeyMapping.java @@ -38,7 +38,6 @@ import org.thingsboard.server.dao.model.ModelConstants; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; -import java.util.EnumMap; import java.util.HashMap; import java.util.HashSet; import java.util.List; @@ -54,8 +53,6 @@ public class EntityKeyMapping { private static final Map> allowedEntityFieldMap = new HashMap<>(); private static final Map entityFieldColumnMap = new HashMap<>(); private static final Map> aliases = new HashMap<>(); - private static final Map> propertiesFunctions = new EnumMap<>(EntityType.class); - public static final Map searchTextFields = new EnumMap<>(EntityType.class); public static final String CREATED_TIME = "createdTime"; public static final String ENTITY_TYPE = "entityType"; @@ -75,51 +72,35 @@ public class EntityKeyMapping { public static final String ZIP = "zip"; public static final String PHONE = "phone"; public static final String ADDITIONAL_INFO = "additionalInfo"; - public static final String TENANT_ID = "tenantId"; - public static final String CUSTOMER_ID = "customerId"; - public static final String AUTHORITY = "authority"; - public static final String RESOURCE_TYPE = "resourceType"; - public static final String LAST_ACTIVITY_TIME = "lastActivityTime"; - public static final List typedEntityFields = Arrays.asList(CREATED_TIME, ENTITY_TYPE, NAME, TYPE, ADDITIONAL_INFO, TENANT_ID); + public static final List typedEntityFields = Arrays.asList(CREATED_TIME, ENTITY_TYPE, NAME, TYPE, ADDITIONAL_INFO); + public static final List widgetEntityFields = Arrays.asList(CREATED_TIME, ENTITY_TYPE, NAME); public static final List commonEntityFields = Arrays.asList(CREATED_TIME, ENTITY_TYPE, NAME, ADDITIONAL_INFO); - - public static final List dashboardEntityFields = Arrays.asList(CREATED_TIME, ENTITY_TYPE, TITLE, TENANT_ID); - public static final List labeledEntityFields = Arrays.asList(CREATED_TIME, ENTITY_TYPE, NAME, TYPE, LABEL, ADDITIONAL_INFO, TENANT_ID, CUSTOMER_ID); + public static final List dashboardEntityFields = Arrays.asList(CREATED_TIME, ENTITY_TYPE, TITLE); + public static final List labeledEntityFields = Arrays.asList(CREATED_TIME, ENTITY_TYPE, NAME, TYPE, LABEL, ADDITIONAL_INFO); public static final List contactBasedEntityFields = Arrays.asList(CREATED_TIME, ENTITY_TYPE, EMAIL, TITLE, COUNTRY, STATE, CITY, ADDRESS, ADDRESS_2, ZIP, PHONE, ADDITIONAL_INFO); - public static final Set apiUsageStateEntityFields = new HashSet<>(Arrays.asList(CREATED_TIME, ENTITY_TYPE, NAME)); + public static final Set apiUsageStateEntityFields = new HashSet<>(Arrays.asList(CREATED_TIME, ENTITY_TYPE, NAME)); public static final Set commonEntityFieldsSet = new HashSet<>(commonEntityFields); public static final Set relationQueryEntityFieldsSet = new HashSet<>(Arrays.asList(CREATED_TIME, ENTITY_TYPE, NAME, TYPE, LABEL, FIRST_NAME, LAST_NAME, EMAIL, REGION, TITLE, COUNTRY, STATE, CITY, ADDRESS, ADDRESS_2, ZIP, PHONE, ADDITIONAL_INFO)); static { allowedEntityFieldMap.put(EntityType.DEVICE, new HashSet<>(labeledEntityFields)); allowedEntityFieldMap.put(EntityType.ASSET, new HashSet<>(labeledEntityFields)); - allowedEntityFieldMap.put(EntityType.EDGE, new HashSet<>(labeledEntityFields)); allowedEntityFieldMap.put(EntityType.ENTITY_VIEW, new HashSet<>(typedEntityFields)); - allowedEntityFieldMap.get(EntityType.ENTITY_VIEW).add(CUSTOMER_ID); allowedEntityFieldMap.put(EntityType.TENANT, new HashSet<>(contactBasedEntityFields)); allowedEntityFieldMap.get(EntityType.TENANT).add(REGION); allowedEntityFieldMap.put(EntityType.CUSTOMER, new HashSet<>(contactBasedEntityFields)); - allowedEntityFieldMap.get(EntityType.CUSTOMER).add(TENANT_ID); - - allowedEntityFieldMap.put(EntityType.USER, new HashSet<>(Arrays.asList(CREATED_TIME, FIRST_NAME, LAST_NAME, EMAIL, - ADDITIONAL_INFO, AUTHORITY, TENANT_ID, CUSTOMER_ID))); - allowedEntityFieldMap.put(EntityType.DEVICE_PROFILE, new HashSet<>(commonEntityFields)); - allowedEntityFieldMap.get(EntityType.DEVICE_PROFILE).add(TENANT_ID); + allowedEntityFieldMap.put(EntityType.USER, new HashSet<>(Arrays.asList(CREATED_TIME, FIRST_NAME, LAST_NAME, EMAIL, ADDITIONAL_INFO))); allowedEntityFieldMap.put(EntityType.DASHBOARD, new HashSet<>(dashboardEntityFields)); allowedEntityFieldMap.put(EntityType.RULE_CHAIN, new HashSet<>(commonEntityFields)); - allowedEntityFieldMap.get(EntityType.RULE_CHAIN).add(TENANT_ID); - allowedEntityFieldMap.get(EntityType.RULE_CHAIN).add(TYPE); allowedEntityFieldMap.put(EntityType.RULE_NODE, new HashSet<>(commonEntityFields)); - allowedEntityFieldMap.put(EntityType.WIDGET_TYPE, new HashSet<>(Arrays.asList(CREATED_TIME, ENTITY_TYPE, NAME, TENANT_ID))); - allowedEntityFieldMap.put(EntityType.WIDGETS_BUNDLE, new HashSet<>(Arrays.asList(CREATED_TIME, ENTITY_TYPE, TITLE, TENANT_ID))); + allowedEntityFieldMap.put(EntityType.WIDGET_TYPE, new HashSet<>(widgetEntityFields)); + allowedEntityFieldMap.put(EntityType.WIDGETS_BUNDLE, new HashSet<>(widgetEntityFields)); allowedEntityFieldMap.put(EntityType.API_USAGE_STATE, apiUsageStateEntityFields); - allowedEntityFieldMap.put(EntityType.TB_RESOURCE, Set.of(CREATED_TIME, ENTITY_TYPE, RESOURCE_TYPE, TITLE, TENANT_ID)); - allowedEntityFieldMap.put(EntityType.OTA_PACKAGE, Set.of(CREATED_TIME, ENTITY_TYPE, TYPE, TITLE, TENANT_ID)); entityFieldColumnMap.put(CREATED_TIME, ModelConstants.CREATED_TIME_PROPERTY); entityFieldColumnMap.put(ENTITY_TYPE, ModelConstants.ENTITY_TYPE_PROPERTY); @@ -139,42 +120,25 @@ public class EntityKeyMapping { entityFieldColumnMap.put(ZIP, ModelConstants.ZIP_PROPERTY); entityFieldColumnMap.put(PHONE, ModelConstants.PHONE_PROPERTY); entityFieldColumnMap.put(ADDITIONAL_INFO, ModelConstants.ADDITIONAL_INFO_PROPERTY); - entityFieldColumnMap.put(TENANT_ID, ModelConstants.TENANT_ID_PROPERTY); - entityFieldColumnMap.put(CUSTOMER_ID, ModelConstants.CUSTOMER_ID_PROPERTY); - entityFieldColumnMap.put(AUTHORITY, ModelConstants.USER_AUTHORITY_PROPERTY); - entityFieldColumnMap.put(RESOURCE_TYPE, ModelConstants.RESOURCE_TYPE_COLUMN); Map contactBasedAliases = new HashMap<>(); contactBasedAliases.put(NAME, TITLE); contactBasedAliases.put(LABEL, TITLE); aliases.put(EntityType.TENANT, contactBasedAliases); - aliases.put(EntityType.CUSTOMER, new HashMap<>(contactBasedAliases)); + aliases.put(EntityType.CUSTOMER, contactBasedAliases); aliases.put(EntityType.DASHBOARD, contactBasedAliases); Map commonEntityAliases = new HashMap<>(); commonEntityAliases.put(TITLE, NAME); aliases.put(EntityType.DEVICE, commonEntityAliases); aliases.put(EntityType.ASSET, commonEntityAliases); aliases.put(EntityType.ENTITY_VIEW, commonEntityAliases); - aliases.put(EntityType.EDGE, commonEntityAliases); - aliases.put(EntityType.WIDGETS_BUNDLE, new HashMap<>(commonEntityAliases)); - aliases.get(EntityType.WIDGETS_BUNDLE).put(NAME, TITLE); + aliases.put(EntityType.WIDGETS_BUNDLE, commonEntityAliases); Map userEntityAliases = new HashMap<>(); userEntityAliases.put(TITLE, EMAIL); userEntityAliases.put(LABEL, EMAIL); userEntityAliases.put(NAME, EMAIL); - userEntityAliases.put(TYPE, AUTHORITY); aliases.put(EntityType.USER, userEntityAliases); - aliases.put(EntityType.TB_RESOURCE, Map.of(NAME, TITLE, TYPE, RESOURCE_TYPE)); - aliases.put(EntityType.OTA_PACKAGE, Map.of(NAME, TITLE)); - - propertiesFunctions.put(EntityType.USER, Map.of( - LAST_ACTIVITY_TIME, "cast(e.additional_info::json ->> 'lastLoginTs' as bigint)" - )); - - Arrays.stream(EntityType.values()).forEach(entityType -> { - searchTextFields.put(entityType, ModelConstants.SEARCH_TEXT_PROPERTY); - }); } private int index; @@ -215,10 +179,6 @@ public class EntityKeyMapping { String column = entityFieldColumnMap.get(alias); return String.format("cast(e.%s as varchar) as %s", column, getValueAlias()); } else { - Map entityPropertiesFunctions = propertiesFunctions.get(entityType); - if (entityPropertiesFunctions != null && entityPropertiesFunctions.containsKey(alias)) { - return String.format("%s as %s", entityPropertiesFunctions.get(alias), getValueAlias()); - } return String.format("'' as %s", getValueAlias()); } } From 3abf323a1ea4d0d0cabfd47b8bf568a45ae4c244 Mon Sep 17 00:00:00 2001 From: Vladyslav_Prykhodko Date: Fri, 5 Nov 2021 18:51:04 +0200 Subject: [PATCH 04/10] UI: Remove invalid JSON form fields in the time series table --- application/src/main/data/json/system/widget_bundles/cards.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/application/src/main/data/json/system/widget_bundles/cards.json b/application/src/main/data/json/system/widget_bundles/cards.json index 41aed542dd..9b70d91a08 100644 --- a/application/src/main/data/json/system/widget_bundles/cards.json +++ b/application/src/main/data/json/system/widget_bundles/cards.json @@ -55,7 +55,7 @@ "templateHtml": "\n", "templateCss": "", "controllerScript": "self.onInit = function() {\n}\n\nself.onDataUpdated = function() {\n self.ctx.$scope.timeseriesTableWidget.onDataUpdated();\n}\n\nself.typeParameters = function() {\n return {\n ignoreDataUpdateOnIntervalTick: true\n };\n}\n\nself.actionSources = function() {\n return {\n 'actionCellButton': {\n name: 'widget-action.action-cell-button',\n multiple: true,\n hasShowCondition: true\n },\n 'rowClick': {\n name: 'widget-action.row-click',\n multiple: false\n }\n };\n}\n\nself.onDestroy = function() {\n}", - "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"TimeseriesTableSettings\",\n \"properties\": {\n \"enableSearch\": {\n \"title\": \"Enable search\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"enableStickyHeader\": {\n \"title\": \"Always display header\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"enableStickyAction\": {\n \"title\": \"Always display actions column\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"reserveSpaceForHiddenAction\": {\n \"title\": \"Hidden cell button actions display mode\",\n \"type\": \"string\",\n \"default\": \"true\"\n },\n \"showTimestamp\": {\n \"title\": \"Display timestamp column\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"showMilliseconds\": {\n \"title\": \"Display timestamp milliseconds\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"displayPagination\": {\n \"title\": \"Display pagination\",\n \"type\": \"boolean\",\n \"default\": true\n }, \n \"useEntityLabel\": {\n \"title\": \"Use entity label in tab name\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"defaultPageSize\": {\n \"title\": \"Default page size\",\n \"type\": \"number\",\n \"default\": 10\n },\n \"hideEmptyLines\": {\n \"title\": \"Hide empty lines\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"disableStickyHeader\": {\n \"title\": \"Disable sticky header\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"useRowStyleFunction\": {\n \"title\": \"Use row style function\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"rowStyleFunction\": {\n \"title\": \"Row style function: f(rowData, ctx)\",\n \"type\": \"string\",\n \"default\": \"\"\n }\n },\n \"required\": []\n },\n \"form\": [\n \"enableSearch\",\n \"enableStickyHeader\",\n \"enableStickyAction\",\n {\n \"key\": \"reserveSpaceForHiddenAction\",\n \"type\": \"rc-select\",\n \"multiple\": false,\n \"items\": [\n {\n \"value\": \"true\",\n \"label\": \"Show empty space instead of hidden cell button action\"\n },\n {\n \"value\": \"false\",\n \"label\": \"Don't reserve space for hidden action buttons\"\n }\n ]\n },\n \"showTimestamp\",\n \"showMilliseconds\",\n \"displayPagination\",\n \"useEntityLabel\",\n \"defaultPageSize\",\n \"identifyDeviceSelector\",\n \"hideEmptyLines\",\n \"useRowStyleFunction\",\n {\n \"key\": \"rowStyleFunction\",\n \"type\": \"javascript\",\n \"helpId\": \"widget/lib/timeseries/row_style_fn\",\n \"condition\": \"model.useRowStyleFunction === true\"\n }\n ]\n}", + "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"TimeseriesTableSettings\",\n \"properties\": {\n \"enableSearch\": {\n \"title\": \"Enable search\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"enableStickyHeader\": {\n \"title\": \"Always display header\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"enableStickyAction\": {\n \"title\": \"Always display actions column\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"reserveSpaceForHiddenAction\": {\n \"title\": \"Hidden cell button actions display mode\",\n \"type\": \"string\",\n \"default\": \"true\"\n },\n \"showTimestamp\": {\n \"title\": \"Display timestamp column\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"showMilliseconds\": {\n \"title\": \"Display timestamp milliseconds\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"displayPagination\": {\n \"title\": \"Display pagination\",\n \"type\": \"boolean\",\n \"default\": true\n }, \n \"useEntityLabel\": {\n \"title\": \"Use entity label in tab name\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"defaultPageSize\": {\n \"title\": \"Default page size\",\n \"type\": \"number\",\n \"default\": 10\n },\n \"hideEmptyLines\": {\n \"title\": \"Hide empty lines\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"disableStickyHeader\": {\n \"title\": \"Disable sticky header\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"useRowStyleFunction\": {\n \"title\": \"Use row style function\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"rowStyleFunction\": {\n \"title\": \"Row style function: f(rowData, ctx)\",\n \"type\": \"string\",\n \"default\": \"\"\n }\n },\n \"required\": []\n },\n \"form\": [\n \"enableSearch\",\n \"enableStickyHeader\",\n \"enableStickyAction\",\n {\n \"key\": \"reserveSpaceForHiddenAction\",\n \"type\": \"rc-select\",\n \"multiple\": false,\n \"items\": [\n {\n \"value\": \"true\",\n \"label\": \"Show empty space instead of hidden cell button action\"\n },\n {\n \"value\": \"false\",\n \"label\": \"Don't reserve space for hidden action buttons\"\n }\n ]\n },\n \"showTimestamp\",\n \"showMilliseconds\",\n \"displayPagination\",\n \"useEntityLabel\",\n \"defaultPageSize\",\n \"hideEmptyLines\",\n \"useRowStyleFunction\",\n {\n \"key\": \"rowStyleFunction\",\n \"type\": \"javascript\",\n \"helpId\": \"widget/lib/timeseries/row_style_fn\",\n \"condition\": \"model.useRowStyleFunction === true\"\n }\n ]\n}", "dataKeySettingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"DataKeySettings\",\n \"properties\": {\n \"useCellStyleFunction\": {\n \"title\": \"Use cell style function\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"cellStyleFunction\": {\n \"title\": \"Cell style function: f(value, rowData, ctx)\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"useCellContentFunction\": {\n \"title\": \"Use cell content function\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"cellContentFunction\": {\n \"title\": \"Cell content function: f(value, rowData, ctx)\",\n \"type\": \"string\",\n \"default\": \"\"\n }\n },\n \"required\": []\n },\n \"form\": [\n \"useCellStyleFunction\",\n {\n \"key\": \"cellStyleFunction\",\n \"type\": \"javascript\",\n \"helpId\": \"widget/lib/timeseries/cell_style_fn\",\n \"condition\": \"model.useCellStyleFunction === true\"\n },\n \"useCellContentFunction\",\n {\n \"key\": \"cellContentFunction\",\n \"type\": \"javascript\",\n \"helpId\": \"widget/lib/timeseries/cell_content_fn\",\n \"condition\": \"model.useCellContentFunction === true\"\n }\n ]\n}", "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Temperature °C\",\"color\":\"#2196f3\",\"settings\":{\"useCellStyleFunction\":true,\"cellStyleFunction\":\"if (value) {\\n var percent = (value + 60)/120 * 100;\\n var color = tinycolor.mix('blue', 'red', amount = percent);\\n color.setAlpha(.5);\\n return {\\n paddingLeft: '20px',\\n color: '#ffffff',\\n background: color.toRgbString(),\\n fontSize: '18px'\\n };\\n} else {\\n return {};\\n}\"},\"_hash\":0.8587686344902596,\"funcBody\":\"var value = prevValue + Math.random() * 40 - 20;\\nvar multiplier = Math.pow(10, 1 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -60) {\\n\\tvalue = -60;\\n} else if (value > 60) {\\n\\tvalue = 60;\\n}\\nreturn value;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Humidity, %\",\"color\":\"#ffc107\",\"settings\":{\"useCellStyleFunction\":true,\"cellStyleFunction\":\"if (value) {\\n var percent = value;\\n var backgroundColor = tinycolor('blue');\\n backgroundColor.setAlpha(value/100);\\n var color = 'blue';\\n if (value > 50) {\\n color = 'white';\\n }\\n \\n return {\\n paddingLeft: '20px',\\n color: color,\\n background: backgroundColor.toRgbString(),\\n fontSize: '18px'\\n };\\n} else {\\n return {};\\n}\",\"useCellContentFunction\":false},\"_hash\":0.12775350966079668,\"funcBody\":\"var value = prevValue + Math.random() * 20 - 10;\\nvar multiplier = Math.pow(10, 1 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < 5) {\\n\\tvalue = 5;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"interval\":1000,\"timewindowMs\":60000},\"aggregation\":{\"type\":\"NONE\",\"limit\":200}},\"showTitle\":true,\"backgroundColor\":\"rgb(255, 255, 255)\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{\"showTimestamp\":true,\"displayPagination\":true,\"defaultPageSize\":10},\"title\":\"Timeseries table\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400,\"padding\":\"5px 10px 5px 10px\"},\"useDashboardTimewindow\":false,\"showLegend\":false,\"widgetStyle\":{},\"actions\":{},\"showTitleIcon\":false,\"iconColor\":\"rgba(0, 0, 0, 0.87)\",\"iconSize\":\"24px\"}" } From d511bd22a4da5a9416c8bb62b438f8f9fefcfaaa Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Sat, 6 Nov 2021 16:42:25 +0200 Subject: [PATCH 05/10] Remove redundant length constraint for comment field --- .../common/data/oauth2/OAuth2ClientRegistrationTemplate.java | 1 - 1 file changed, 1 deletion(-) diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2ClientRegistrationTemplate.java b/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2ClientRegistrationTemplate.java index f4baef2abb..b4070313e8 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2ClientRegistrationTemplate.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2ClientRegistrationTemplate.java @@ -62,7 +62,6 @@ public class OAuth2ClientRegistrationTemplate extends SearchTextBasedWithAdditio @Length(fieldName = "clientAuthenticationMethod") @ApiModelProperty(value = "Default client authentication method to use: 'BASIC' or 'POST'") private String clientAuthenticationMethod; - @Length(fieldName = "comment") @ApiModelProperty(value = "Comment for OAuth2 provider") private String comment; @Length(fieldName = "loginButtonIcon") From 541110b3445d109185641c089c086e9aa6fc6d67 Mon Sep 17 00:00:00 2001 From: Dima Landiak Date: Fri, 5 Nov 2021 18:29:38 +0200 Subject: [PATCH 06/10] fixed concurrency exception when deleting relation --- .../dao/sql/relation/JpaRelationDao.java | 6 +++++- .../dao/service/BaseRelationServiceTest.java | 20 +++++++++++++++++++ 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/relation/JpaRelationDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/relation/JpaRelationDao.java index 91cc1a0c9f..2eaa5c28a2 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/relation/JpaRelationDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/relation/JpaRelationDao.java @@ -157,7 +157,11 @@ public class JpaRelationDao extends JpaAbstractDaoListeningExecutorService imple private boolean deleteRelationIfExists(RelationCompositeKey key) { boolean relationExistsBeforeDelete = relationRepository.existsById(key); if (relationExistsBeforeDelete) { - relationRepository.deleteById(key); + try { + relationRepository.deleteById(key); + } catch (ConcurrencyFailureException e) { + log.debug("[{}] Concurrency exception while deleting relation", key, e); + } } return relationExistsBeforeDelete; } diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/BaseRelationServiceTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/BaseRelationServiceTest.java index 6f5ea9acb9..27dc7f812a 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/service/BaseRelationServiceTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/service/BaseRelationServiceTest.java @@ -16,6 +16,8 @@ package org.thingsboard.server.dao.service; import com.datastax.oss.driver.api.core.uuid.Uuids; +import com.google.common.util.concurrent.Futures; +import com.google.common.util.concurrent.ListenableFuture; import org.junit.After; import org.junit.Assert; import org.junit.Before; @@ -31,6 +33,7 @@ import org.thingsboard.server.common.data.relation.RelationTypeGroup; import org.thingsboard.server.common.data.relation.RelationsSearchParameters; import org.thingsboard.server.dao.exception.DataValidationException; +import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.concurrent.ExecutionException; @@ -84,6 +87,23 @@ public abstract class BaseRelationServiceTest extends AbstractServiceTest { Assert.assertTrue(relationService.deleteRelationAsync(SYSTEM_TENANT_ID, childId, subChildId, EntityRelation.CONTAINS_TYPE, RelationTypeGroup.COMMON).get()); } + @Test + public void testDeleteRelationConcurrently() throws ExecutionException, InterruptedException { + AssetId parentId = new AssetId(Uuids.timeBased()); + AssetId childId = new AssetId(Uuids.timeBased()); + + EntityRelation relationA = new EntityRelation(parentId, childId, EntityRelation.CONTAINS_TYPE); + + saveRelation(relationA); + + List> futures = new ArrayList<>(); + for (int i = 0; i < 2; i++) { + futures.add(relationService.deleteRelationAsync(SYSTEM_TENANT_ID, relationA)); + } + List results = Futures.allAsList(futures).get(); + Assert.assertTrue(results.contains(true)); + } + @Test public void testDeleteEntityRelations() throws ExecutionException, InterruptedException { AssetId parentId = new AssetId(Uuids.timeBased()); From 0db0bb8607bd13b00384d3a04d9e675fd317e6c7 Mon Sep 17 00:00:00 2001 From: ShvaykaD Date: Mon, 8 Nov 2021 11:44:38 +0200 Subject: [PATCH 07/10] fix coap efento callbacks --- .../server/transport/coap/callback/CoapEfentoCallback.java | 5 ++--- .../transport/coap/efento/CoapEfentoTransportResource.java | 5 ++--- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/callback/CoapEfentoCallback.java b/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/callback/CoapEfentoCallback.java index f2387bf0d1..efda6f3efe 100644 --- a/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/callback/CoapEfentoCallback.java +++ b/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/callback/CoapEfentoCallback.java @@ -34,10 +34,9 @@ public class CoapEfentoCallback implements TransportServiceCallback { @Override public void onSuccess(Void msg) { + //We respond only to confirmed requests in order to reduce battery consumption for Efento devices. if (isConRequest()) { - Response response = new Response(onSuccessResponse); - response.setAcknowledged(true); - exchange.respond(response); + exchange.respond(new Response(onSuccessResponse)); } } diff --git a/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/efento/CoapEfentoTransportResource.java b/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/efento/CoapEfentoTransportResource.java index 06395109d1..6fea53f48a 100644 --- a/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/efento/CoapEfentoTransportResource.java +++ b/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/efento/CoapEfentoTransportResource.java @@ -99,10 +99,9 @@ public class CoapEfentoTransportResource extends AbstractCoapTransportResource { break; case DEVICE_INFO: case CONFIGURATION: - Response response = new Response(CoAP.ResponseCode.CREATED); + //We respond only to confirmed requests in order to reduce battery consumption for Efento devices. if (exchange.advanced().getRequest().isConfirmable()) { - response.setAcknowledged(true); - exchange.respond(response); + exchange.respond(new Response(CoAP.ResponseCode.CREATED)); } break; default: From 037d1797accb623539b23494fc31146bcd169d78 Mon Sep 17 00:00:00 2001 From: zbeacon Date: Mon, 8 Nov 2021 10:43:22 +0200 Subject: [PATCH 08/10] Fix for mqtt duplication sending on sent failed --- .../src/main/java/org/thingsboard/mqtt/MqttClientImpl.java | 1 + 1 file changed, 1 insertion(+) diff --git a/netty-mqtt/src/main/java/org/thingsboard/mqtt/MqttClientImpl.java b/netty-mqtt/src/main/java/org/thingsboard/mqtt/MqttClientImpl.java index fdc44dbb42..827d9fa4cf 100644 --- a/netty-mqtt/src/main/java/org/thingsboard/mqtt/MqttClientImpl.java +++ b/netty-mqtt/src/main/java/org/thingsboard/mqtt/MqttClientImpl.java @@ -368,6 +368,7 @@ final class MqttClientImpl implements MqttClient { if (channelFuture != null) { pendingPublish.setSent(true); if (channelFuture.cause() != null) { + this.pendingPublishes.remove(pendingPublish.getMessageId()); future.setFailure(channelFuture.cause()); return future; } From e7c4e7685196d805faebb0e28260a163c7c224d9 Mon Sep 17 00:00:00 2001 From: zbeacon Date: Mon, 8 Nov 2021 12:59:42 +0200 Subject: [PATCH 09/10] Added removing for timers in pendingPublishes on channel is closed --- .../org/thingsboard/mqtt/MqttClientImpl.java | 32 +++++++++++-------- .../thingsboard/mqtt/MqttPendingPublish.java | 5 +++ 2 files changed, 24 insertions(+), 13 deletions(-) diff --git a/netty-mqtt/src/main/java/org/thingsboard/mqtt/MqttClientImpl.java b/netty-mqtt/src/main/java/org/thingsboard/mqtt/MqttClientImpl.java index 827d9fa4cf..5fbdb883af 100644 --- a/netty-mqtt/src/main/java/org/thingsboard/mqtt/MqttClientImpl.java +++ b/netty-mqtt/src/main/java/org/thingsboard/mqtt/MqttClientImpl.java @@ -55,6 +55,7 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.BiConsumer; /** * Represents an MqttClientImpl connected to a single MQTT server. Will try to keep the connection going at all times @@ -160,6 +161,7 @@ final class MqttClientImpl implements MqttClient { subscriptions.clear(); pendingServerUnsubscribes.clear(); qos2PendingIncomingPublishes.clear(); + pendingPublishes.forEach((id, mqttPendingPublish) -> mqttPendingPublish.onChannelClosed()); pendingPublishes.clear(); pendingSubscribeTopics.clear(); handlerToSubscribtion.clear(); @@ -366,20 +368,24 @@ final class MqttClientImpl implements MqttClient { ChannelFuture channelFuture = this.sendAndFlushPacket(message); if (channelFuture != null) { - pendingPublish.setSent(true); - if (channelFuture.cause() != null) { - this.pendingPublishes.remove(pendingPublish.getMessageId()); - future.setFailure(channelFuture.cause()); - return future; - } - } - if (pendingPublish.isSent() && pendingPublish.getQos() == MqttQoS.AT_MOST_ONCE) { - this.pendingPublishes.remove(pendingPublish.getMessageId()); - pendingPublish.getFuture().setSuccess(null); //We don't get an ACK for QOS 0 - } else if (pendingPublish.isSent()) { - pendingPublish.startPublishRetransmissionTimer(this.eventLoop.next(), this::sendAndFlushPacket); + channelFuture.addListener(result -> { + pendingPublish.setSent(true); + if (result.cause() != null) { + pendingPublishes.remove(pendingPublish.getMessageId()); + future.setFailure(result.cause()); + } else { + if (pendingPublish.isSent() && pendingPublish.getQos() == MqttQoS.AT_MOST_ONCE) { + pendingPublishes.remove(pendingPublish.getMessageId()); + pendingPublish.getFuture().setSuccess(null); //We don't get an ACK for QOS 0 + } else if (pendingPublish.isSent()) { + pendingPublish.startPublishRetransmissionTimer(eventLoop.next(), MqttClientImpl.this::sendAndFlushPacket); + } else { + pendingPublishes.remove(pendingPublish.getMessageId()); + } + } + }); } else { - this.pendingPublishes.remove(pendingPublish.getMessageId()); + pendingPublishes.remove(pendingPublish.getMessageId()); } return future; } diff --git a/netty-mqtt/src/main/java/org/thingsboard/mqtt/MqttPendingPublish.java b/netty-mqtt/src/main/java/org/thingsboard/mqtt/MqttPendingPublish.java index cedbd4556d..f647569553 100644 --- a/netty-mqtt/src/main/java/org/thingsboard/mqtt/MqttPendingPublish.java +++ b/netty-mqtt/src/main/java/org/thingsboard/mqtt/MqttPendingPublish.java @@ -98,4 +98,9 @@ final class MqttPendingPublish { void onPubcompReceived() { this.pubrelRetransmissionHandler.stop(); } + + void onChannelClosed() { + this.publishRetransmissionHandler.stop(); + this.pubrelRetransmissionHandler.stop(); + } } From 06f199b9362df2300ef95b5e55449f4d1fbb6ba3 Mon Sep 17 00:00:00 2001 From: zbeacon Date: Mon, 8 Nov 2021 13:25:03 +0200 Subject: [PATCH 10/10] Added stopping for subscriptions handlers and for unsubscription handlers --- .../src/main/java/org/thingsboard/mqtt/MqttClientImpl.java | 2 ++ .../main/java/org/thingsboard/mqtt/MqttPendingPublish.java | 4 ++-- .../java/org/thingsboard/mqtt/MqttPendingSubscription.java | 6 +++++- .../org/thingsboard/mqtt/MqttPendingUnsubscription.java | 6 +++++- 4 files changed, 14 insertions(+), 4 deletions(-) diff --git a/netty-mqtt/src/main/java/org/thingsboard/mqtt/MqttClientImpl.java b/netty-mqtt/src/main/java/org/thingsboard/mqtt/MqttClientImpl.java index 5fbdb883af..f30fc6e6ad 100644 --- a/netty-mqtt/src/main/java/org/thingsboard/mqtt/MqttClientImpl.java +++ b/netty-mqtt/src/main/java/org/thingsboard/mqtt/MqttClientImpl.java @@ -156,9 +156,11 @@ final class MqttClientImpl implements MqttClient { if (callback != null) { callback.connectionLost(e); } + pendingSubscriptions.forEach((id, mqttPendingSubscription) -> mqttPendingSubscription.onChannelClosed()); pendingSubscriptions.clear(); serverSubscriptions.clear(); subscriptions.clear(); + pendingServerUnsubscribes.forEach((id, mqttPendingServerUnsubscribes) -> mqttPendingServerUnsubscribes.onChannelClosed()); pendingServerUnsubscribes.clear(); qos2PendingIncomingPublishes.clear(); pendingPublishes.forEach((id, mqttPendingPublish) -> mqttPendingPublish.onChannelClosed()); diff --git a/netty-mqtt/src/main/java/org/thingsboard/mqtt/MqttPendingPublish.java b/netty-mqtt/src/main/java/org/thingsboard/mqtt/MqttPendingPublish.java index f647569553..e19a60226a 100644 --- a/netty-mqtt/src/main/java/org/thingsboard/mqtt/MqttPendingPublish.java +++ b/netty-mqtt/src/main/java/org/thingsboard/mqtt/MqttPendingPublish.java @@ -24,7 +24,7 @@ import io.netty.util.concurrent.Promise; import java.util.function.Consumer; -final class MqttPendingPublish { +final class MqttPendingPublish{ private final int messageId; private final Promise future; @@ -99,7 +99,7 @@ final class MqttPendingPublish { this.pubrelRetransmissionHandler.stop(); } - void onChannelClosed() { + void onChannelClosed(){ this.publishRetransmissionHandler.stop(); this.pubrelRetransmissionHandler.stop(); } diff --git a/netty-mqtt/src/main/java/org/thingsboard/mqtt/MqttPendingSubscription.java b/netty-mqtt/src/main/java/org/thingsboard/mqtt/MqttPendingSubscription.java index d0d396d784..975a399691 100644 --- a/netty-mqtt/src/main/java/org/thingsboard/mqtt/MqttPendingSubscription.java +++ b/netty-mqtt/src/main/java/org/thingsboard/mqtt/MqttPendingSubscription.java @@ -23,7 +23,7 @@ import java.util.HashSet; import java.util.Set; import java.util.function.Consumer; -final class MqttPendingSubscription { +final class MqttPendingSubscription{ private final Promise future; private final String topic; @@ -99,4 +99,8 @@ final class MqttPendingSubscription { return once; } } + + void onChannelClosed(){ + this.retransmissionHandler.stop(); + } } diff --git a/netty-mqtt/src/main/java/org/thingsboard/mqtt/MqttPendingUnsubscription.java b/netty-mqtt/src/main/java/org/thingsboard/mqtt/MqttPendingUnsubscription.java index ca9d0b6e77..9042aa268a 100644 --- a/netty-mqtt/src/main/java/org/thingsboard/mqtt/MqttPendingUnsubscription.java +++ b/netty-mqtt/src/main/java/org/thingsboard/mqtt/MqttPendingUnsubscription.java @@ -21,7 +21,7 @@ import io.netty.util.concurrent.Promise; import java.util.function.Consumer; -final class MqttPendingUnsubscription { +final class MqttPendingUnsubscription{ private final Promise future; private final String topic; @@ -52,4 +52,8 @@ final class MqttPendingUnsubscription { void onUnsubackReceived(){ this.retransmissionHandler.stop(); } + + void onChannelClosed(){ + this.retransmissionHandler.stop(); + } }