This commit is contained in:
Andrii Shvaika 2022-06-22 11:03:32 +03:00
commit 02f61cea79
3 changed files with 481 additions and 4 deletions

View File

@ -28,6 +28,10 @@ import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.audit.ActionType;
import org.thingsboard.server.common.data.edge.EdgeEventActionType;
import org.thingsboard.server.common.data.exception.ThingsboardErrorCode;
import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.EntityIdFactory;
@ -37,9 +41,11 @@ import org.thingsboard.server.common.data.relation.EntityRelationsQuery;
import org.thingsboard.server.common.data.relation.RelationTypeGroup;
import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.entitiy.entityRelation.TbEntityRelationService;
import org.thingsboard.server.service.security.model.SecurityUser;
import org.thingsboard.server.service.security.permission.Operation;
import java.util.List;
import java.util.UUID;
import java.util.stream.Collectors;
import static org.thingsboard.server.controller.ControllerConstants.ENTITY_ID_PARAM_DESCRIPTION;
@ -80,8 +86,8 @@ public class EntityRelationController extends BaseController {
public void saveRelation(@ApiParam(value = "A JSON value representing the relation.", required = true)
@RequestBody EntityRelation relation) throws ThingsboardException {
checkNotNull(relation);
checkEntityId(relation.getFrom(), Operation.WRITE);
checkEntityId(relation.getTo(), Operation.WRITE);
checkCanCreateRelation(relation.getFrom());
checkCanCreateRelation(relation.getTo());
if (relation.getTypeGroup() == null) {
relation.setTypeGroup(RelationTypeGroup.COMMON);
}
@ -107,8 +113,9 @@ public class EntityRelationController extends BaseController {
checkParameter(TO_TYPE, strToType);
EntityId fromId = EntityIdFactory.getByTypeAndId(strFromType, strFromId);
EntityId toId = EntityIdFactory.getByTypeAndId(strToType, strToId);
checkEntityId(fromId, Operation.WRITE);
checkEntityId(toId, Operation.WRITE);
checkCanCreateRelation(fromId);
checkCanCreateRelation(toId);
RelationTypeGroup relationTypeGroup = parseRelationTypeGroup(strRelationTypeGroup, RelationTypeGroup.COMMON);
EntityRelation relation = new EntityRelation(fromId, toId, strRelationType, relationTypeGroup);
@ -341,6 +348,14 @@ public class EntityRelationController extends BaseController {
}
}
private void checkCanCreateRelation(EntityId entityId) throws ThingsboardException {
SecurityUser currentUser = getCurrentUser();
var isTenantAdminAndRelateToSelf = currentUser.isTenantAdmin() && currentUser.getTenantId().equals(entityId);
if (!isTenantAdminAndRelateToSelf) {
checkEntityId(entityId, Operation.WRITE);
}
}
private <T extends EntityRelation> List<T> filterRelationsByReadPermission(List<T> relationsByQuery) {
return relationsByQuery.stream().filter(relationByQuery -> {
try {

View File

@ -0,0 +1,439 @@
/**
* Copyright © 2016-2022 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.controller;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.JsonNode;
import lombok.extern.slf4j.Slf4j;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.EntityView;
import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.relation.EntityRelation;
import org.thingsboard.server.common.data.relation.EntityRelationInfo;
import org.thingsboard.server.common.data.relation.EntityRelationsQuery;
import org.thingsboard.server.common.data.relation.EntitySearchDirection;
import org.thingsboard.server.common.data.relation.RelationEntityTypeFilter;
import org.thingsboard.server.common.data.relation.RelationTypeGroup;
import org.thingsboard.server.common.data.relation.RelationsSearchParameters;
import org.thingsboard.server.common.data.security.Authority;
import org.thingsboard.server.dao.relation.RelationService;
import java.util.Collections;
import java.util.List;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@Slf4j
public class BaseEntityRelationControllerTest extends AbstractControllerTest {
public static final String BASE_DEVICE_NAME = "Test dummy device";
@Autowired
RelationService relationService;
private IdComparator<EntityView> idComparator;
private Tenant savedTenant;
private User tenantAdmin;
private Device mainDevice;
@Before
public void beforeTest() throws Exception {
loginSysAdmin();
idComparator = new IdComparator<>();
Tenant tenant = new Tenant();
tenant.setTitle("Test tenant");
savedTenant = doPost("/api/tenant", tenant, Tenant.class);
Assert.assertNotNull(savedTenant);
tenantAdmin = new User();
tenantAdmin.setAuthority(Authority.TENANT_ADMIN);
tenantAdmin.setTenantId(savedTenant.getId());
tenantAdmin.setEmail("tenant2@thingsboard.org");
tenantAdmin.setFirstName("Joe");
tenantAdmin.setLastName("Downs");
tenantAdmin = createUserAndLogin(tenantAdmin, "testPassword1");
Device device = new Device();
device.setName("Main test device");
device.setType("default");
mainDevice = doPost("/api/device", device, Device.class);
}
@After
public void afterTest() throws Exception {
loginSysAdmin();
doDelete("/api/tenant/" + savedTenant.getId().getId().toString())
.andExpect(status().isOk());
}
@Test
public void testSaveAndFindRelation() throws Exception {
Device device = buildSimpleDevice("Test device 1");
EntityRelation relation = createFromRelation(mainDevice, device, "CONTAINS");
doPost("/api/relation", relation).andExpect(status().isOk());
String url = String.format("/api/relation?fromId=%s&fromType=%s&relationType=%s&toId=%s&toType=%s",
mainDevice.getUuidId(), EntityType.DEVICE,
"CONTAINS", device.getUuidId(), EntityType.DEVICE
);
EntityRelation foundRelation = doGet(url, EntityRelation.class);
Assert.assertNotNull("Relation is not found!", foundRelation);
Assert.assertEquals("Found relation is not equals origin!", relation, foundRelation);
}
@Test
public void testSaveAndFindRelationsByFrom() throws Exception {
final int numOfDevices = 30;
createDevicesByFrom(numOfDevices, BASE_DEVICE_NAME);
String url = String.format("/api/relations?fromId=%s&fromType=%s",
mainDevice.getUuidId(), EntityType.DEVICE
);
assertFoundList(url, numOfDevices);
}
@Test
public void testSaveAndFindRelationsByTo() throws Exception {
final int numOfDevices = 30;
createDevicesByTo(numOfDevices, BASE_DEVICE_NAME);
String url = String.format("/api/relations?toId=%s&toType=%s",
mainDevice.getUuidId(), EntityType.DEVICE
);
assertFoundList(url, numOfDevices);
}
@Test
public void testSaveAndFindRelationsByFromWithRelationType() throws Exception {
final int numOfDevices = 30;
createDevicesByFrom(numOfDevices, BASE_DEVICE_NAME);
Device device = buildSimpleDevice("Unique dummy test device ");
EntityRelation relation = createFromRelation(mainDevice, device, "TEST");
doPost("/api/relation", relation).andExpect(status().isOk());
String url = String.format("/api/relations?fromId=%s&fromType=%s&relationType=%s",
mainDevice.getUuidId(), EntityType.DEVICE, "TEST"
);
assertFoundList(url, 1);
}
@Test
public void testSaveAndFindRelationsByToWithRelationType() throws Exception {
final int numOfDevices = 30;
createDevicesByFrom(numOfDevices, BASE_DEVICE_NAME);
Device device = buildSimpleDevice("Unique dummy test device ");
EntityRelation relation = createFromRelation(device, mainDevice, "TEST");
doPost("/api/relation", relation).andExpect(status().isOk());
String url = String.format("/api/relations?toId=%s&toType=%s&relationType=%s",
mainDevice.getUuidId(), EntityType.DEVICE, "TEST"
);
assertFoundList(url, 1);
}
@Test
public void testFindRelationsInfoByFrom() throws Exception {
final int numOfDevices = 30;
createDevicesByFrom(numOfDevices, BASE_DEVICE_NAME);
String url = String.format("/api/relations/info?fromId=%s&fromType=%s",
mainDevice.getUuidId(), EntityType.DEVICE
);
List<EntityRelationInfo> relationsInfos =
JacksonUtil.convertValue(doGet(url, JsonNode.class), new TypeReference<List<EntityRelationInfo>>() {
});
Assert.assertNotNull("Relations is not found!", relationsInfos);
Assert.assertEquals("List of found relationsInfos is not equal to number of created relations!",
numOfDevices, relationsInfos.size());
assertRelationsInfosByFrom(relationsInfos);
}
@Test
public void testFindRelationsInfoByTo() throws Exception {
final int numOfDevices = 30;
createDevicesByTo(numOfDevices, BASE_DEVICE_NAME);
String url = String.format("/api/relations/info?toId=%s&toType=%s",
mainDevice.getUuidId(), EntityType.DEVICE
);
List<EntityRelationInfo> relationsInfos =
JacksonUtil.convertValue(doGet(url, JsonNode.class), new TypeReference<List<EntityRelationInfo>>() {
});
Assert.assertNotNull("Relations is not found!", relationsInfos);
Assert.assertEquals("List of found relationsInfos is not equal to number of created relations!",
numOfDevices, relationsInfos.size());
assertRelationsInfosByTo(relationsInfos);
}
@Test
public void testDeleteRelation() throws Exception {
Device device = buildSimpleDevice("Test device 1");
EntityRelation relation = createFromRelation(mainDevice, device, "CONTAINS");
doPost("/api/relation", relation).andExpect(status().isOk());
String url = String.format("/api/relation?fromId=%s&fromType=%s&relationType=%s&toId=%s&toType=%s",
mainDevice.getUuidId(), EntityType.DEVICE,
"CONTAINS", device.getUuidId(), EntityType.DEVICE
);
EntityRelation foundRelation = doGet(url, EntityRelation.class);
Assert.assertNotNull("Relation is not found!", foundRelation);
Assert.assertEquals("Found relation is not equals origin!", relation, foundRelation);
doDelete(url).andExpect(status().isOk());
doGet(url).andExpect(status().is4xxClientError());
}
@Test
public void testDeleteRelations() throws Exception {
final int numOfDevices = 30;
createDevicesByFrom(numOfDevices, BASE_DEVICE_NAME + " from");
createDevicesByTo(numOfDevices, BASE_DEVICE_NAME + " to");
String urlTo = String.format("/api/relations?toId=%s&toType=%s",
mainDevice.getUuidId(), EntityType.DEVICE
);
String urlFrom = String.format("/api/relations?fromId=%s&fromType=%s",
mainDevice.getUuidId(), EntityType.DEVICE
);
assertFoundList(urlTo, numOfDevices);
assertFoundList(urlFrom, numOfDevices);
String url = String.format("/api/relations?entityId=%s&entityType=%s",
mainDevice.getUuidId(), EntityType.DEVICE
);
doDelete(url).andExpect(status().isOk());
Assert.assertTrue(
"Performed deletion of all relations but some relations were found!",
doGet(urlTo, List.class).isEmpty()
);
Assert.assertTrue(
"Performed deletion of all relations but some relations were found!",
doGet(urlFrom, List.class).isEmpty()
);
}
@Test
public void testFindRelationsByFromQuery() throws Exception {
final int numOfDevices = 30;
createDevicesByFrom(numOfDevices, BASE_DEVICE_NAME);
EntityRelationsQuery query = new EntityRelationsQuery();
query.setParameters(new RelationsSearchParameters(
mainDevice.getUuidId(), EntityType.DEVICE,
EntitySearchDirection.FROM,
RelationTypeGroup.COMMON,
1, true
));
query.setFilters(Collections.singletonList(
new RelationEntityTypeFilter("CONTAINS", List.of(EntityType.DEVICE))
));
List<EntityRelation> relations = readResponse(
doPost("/api/relations", query).andExpect(status().isOk()),
new TypeReference<List<EntityRelation>>() {}
);
assertFoundRelations(relations, numOfDevices);
}
@Test
public void testFindRelationsByToQuery() throws Exception {
final int numOfDevices = 30;
createDevicesByTo(numOfDevices, BASE_DEVICE_NAME);
EntityRelationsQuery query = new EntityRelationsQuery();
query.setParameters(new RelationsSearchParameters(
mainDevice.getUuidId(), EntityType.DEVICE,
EntitySearchDirection.TO,
RelationTypeGroup.COMMON,
1, true
));
query.setFilters(Collections.singletonList(
new RelationEntityTypeFilter("CONTAINS", List.of(EntityType.DEVICE))
));
List<EntityRelation> relations = readResponse(
doPost("/api/relations", query).andExpect(status().isOk()),
new TypeReference<List<EntityRelation>>() {}
);
assertFoundRelations(relations, numOfDevices);
}
@Test
public void testFindRelationsInfoByFromQuery() throws Exception{
final int numOfDevices = 30;
createDevicesByFrom(numOfDevices, BASE_DEVICE_NAME);
EntityRelationsQuery query = new EntityRelationsQuery();
query.setParameters(new RelationsSearchParameters(
mainDevice.getUuidId(), EntityType.DEVICE,
EntitySearchDirection.FROM,
RelationTypeGroup.COMMON,
1, true
));
query.setFilters(Collections.singletonList(
new RelationEntityTypeFilter("CONTAINS", List.of(EntityType.DEVICE))
));
List<EntityRelationInfo> relationsInfo = readResponse(
doPost("/api/relations/info", query).andExpect(status().isOk()),
new TypeReference<List<EntityRelationInfo>>() {}
);
assertRelationsInfosByFrom(relationsInfo);
}
@Test
public void testFindRelationsInfoByToQuery() throws Exception{
final int numOfDevices = 30;
createDevicesByTo(numOfDevices, BASE_DEVICE_NAME);
EntityRelationsQuery query = new EntityRelationsQuery();
query.setParameters(new RelationsSearchParameters(
mainDevice.getUuidId(), EntityType.DEVICE,
EntitySearchDirection.TO,
RelationTypeGroup.COMMON,
1, true
));
query.setFilters(Collections.singletonList(
new RelationEntityTypeFilter("CONTAINS", List.of(EntityType.DEVICE))
));
List<EntityRelationInfo> relationsInfo = readResponse(
doPost("/api/relations/info", query).andExpect(status().isOk()),
new TypeReference<List<EntityRelationInfo>>() {}
);
assertRelationsInfosByTo(relationsInfo);
}
@Test
public void testCreateRelationFromTenantToDevice() throws Exception{
EntityRelation relation = new EntityRelation(tenantAdmin.getTenantId(), mainDevice.getId(), "CONTAINS");
doPost("/api/relation", relation).andExpect(status().isOk());
String url = String.format("/api/relation?fromId=%s&fromType=%s&relationType=%s&toId=%s&toType=%s",
tenantAdmin.getTenantId(), EntityType.TENANT,
"CONTAINS", mainDevice.getUuidId(), EntityType.DEVICE
);
EntityRelation foundRelation = doGet(url, EntityRelation.class);
Assert.assertNotNull("Relation is not found!", foundRelation);
Assert.assertEquals("Found relation is not equals origin!", relation, foundRelation);
}
@Test
public void testCreateRelationFromDeviceToTenant() throws Exception{
EntityRelation relation = new EntityRelation(mainDevice.getId(), tenantAdmin.getTenantId(), "CONTAINS");
doPost("/api/relation", relation).andExpect(status().isOk());
String url = String.format("/api/relation?fromId=%s&fromType=%s&relationType=%s&toId=%s&toType=%s",
mainDevice.getUuidId(), EntityType.DEVICE,
"CONTAINS", tenantAdmin.getTenantId(), EntityType.TENANT
);
EntityRelation foundRelation = doGet(url, EntityRelation.class);
Assert.assertNotNull("Relation is not found!", foundRelation);
Assert.assertEquals("Found relation is not equals origin!", relation, foundRelation);
}
private Device buildSimpleDevice(String name) throws Exception {
Device device = new Device();
device.setName(name);
device.setType("default");
device = doPost("/api/device", device, Device.class);
return device;
}
private EntityRelation createFromRelation(Device mainDevice, Device device, String relationType) {
return new EntityRelation(mainDevice.getId(), device.getId(), relationType);
}
private void createDevicesByFrom(int numOfDevices, String baseName) throws Exception {
for (int i = 0; i < numOfDevices; i++) {
Device device = buildSimpleDevice(baseName + i);
EntityRelation relation = createFromRelation(mainDevice, device, "CONTAINS");
doPost("/api/relation", relation).andExpect(status().isOk());
}
}
private void createDevicesByTo(int numOfDevices, String baseName) throws Exception {
for (int i = 0; i < numOfDevices; i++) {
Device device = buildSimpleDevice(baseName + i);
EntityRelation relation = createFromRelation(device, mainDevice, "CONTAINS");
doPost("/api/relation", relation).andExpect(status().isOk());
}
}
private void assertFoundRelations(List<EntityRelation> relations, int numOfDevices) {
Assert.assertNotNull("Relations is not found!", relations);
Assert.assertEquals("List of found relations is not equal to number of created relations!",
numOfDevices, relations.size());
}
private void assertFoundList(String url, int numOfDevices) throws Exception {
@SuppressWarnings("unchecked")
List<EntityRelation> relations = doGet(url, List.class);
assertFoundRelations(relations, numOfDevices);
}
private void assertRelationsInfosByFrom(List<EntityRelationInfo> relationsInfos) {
for (EntityRelationInfo info : relationsInfos) {
Assert.assertEquals("Wrong FROM entityId!", mainDevice.getId(), info.getFrom());
Assert.assertTrue("Wrong FROM name!", info.getToName().contains(BASE_DEVICE_NAME));
Assert.assertEquals("Wrong relationType!", "CONTAINS", info.getType());
}
}
private void assertRelationsInfosByTo(List<EntityRelationInfo> relationsInfos) {
for (EntityRelationInfo info : relationsInfos) {
Assert.assertEquals("Wrong TO entityId!", mainDevice.getId(), info.getTo());
Assert.assertTrue("Wrong TO name!", info.getFromName().contains(BASE_DEVICE_NAME));
Assert.assertEquals("Wrong relationType!", "CONTAINS", info.getType());
}
}
}

View File

@ -0,0 +1,23 @@
/**
* Copyright © 2016-2022 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.controller.sql;
import org.thingsboard.server.controller.BaseEntityRelationControllerTest;
import org.thingsboard.server.dao.service.DaoSqlTest;
@DaoSqlTest
public class EntityRelationControllerSqlTest extends BaseEntityRelationControllerTest {
}