Merge pull request #6642 from ViacheslavKlimov/fix/device-profile-permissions-checks

[3.4] Fix permission checks for DeviceProfileController
This commit is contained in:
Andrew Shvayka 2022-06-22 13:02:09 +03:00 committed by GitHub
commit c93772273c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 212 additions and 4 deletions

View File

@ -108,7 +108,7 @@ public class DeviceProfileController extends BaseController {
checkParameter(DEVICE_PROFILE_ID, strDeviceProfileId); checkParameter(DEVICE_PROFILE_ID, strDeviceProfileId);
try { try {
DeviceProfileId deviceProfileId = new DeviceProfileId(toUUID(strDeviceProfileId)); DeviceProfileId deviceProfileId = new DeviceProfileId(toUUID(strDeviceProfileId));
return checkNotNull(deviceProfileService.findDeviceProfileInfoById(getTenantId(), deviceProfileId)); return new DeviceProfileInfo(checkDeviceProfileId(deviceProfileId, Operation.READ));
} catch (Exception e) { } catch (Exception e) {
throw handleException(e); throw handleException(e);
} }

View File

@ -237,6 +237,7 @@ public abstract class AbstractWebTest extends AbstractInMemoryStorageTest {
loginSysAdmin(); loginSysAdmin();
doDelete("/api/tenant/" + tenantId.getId().toString()) doDelete("/api/tenant/" + tenantId.getId().toString())
.andExpect(status().isOk()); .andExpect(status().isOk());
deleteDifferentTenant();
verifyNoTenantsLeft(); verifyNoTenantsLeft();
@ -334,6 +335,7 @@ public abstract class AbstractWebTest extends AbstractInMemoryStorageTest {
loginSysAdmin(); loginSysAdmin();
doDelete("/api/tenant/" + savedDifferentTenant.getId().getId().toString()) doDelete("/api/tenant/" + savedDifferentTenant.getId().getId().toString())
.andExpect(status().isOk()); .andExpect(status().isOk());
savedDifferentTenant = null;
} }
} }

View File

@ -126,6 +126,16 @@ public abstract class BaseDeviceProfileControllerTest extends AbstractController
Assert.assertEquals(savedDeviceProfile, foundDeviceProfile); Assert.assertEquals(savedDeviceProfile, foundDeviceProfile);
} }
@Test
public void whenGetDeviceProfileById_thenPermissionsAreChecked() throws Exception {
DeviceProfile deviceProfile = createDeviceProfile("Device profile 1", null);
deviceProfile = doPost("/api/deviceProfile", deviceProfile, DeviceProfile.class);
loginDifferentTenant();
doGet("/api/deviceProfile/" + deviceProfile.getId())
.andExpect(status().isForbidden());
}
@Test @Test
public void testFindDeviceProfileInfoById() throws Exception { public void testFindDeviceProfileInfoById() throws Exception {
DeviceProfile deviceProfile = this.createDeviceProfile("Device Profile"); DeviceProfile deviceProfile = this.createDeviceProfile("Device Profile");
@ -137,6 +147,16 @@ public abstract class BaseDeviceProfileControllerTest extends AbstractController
Assert.assertEquals(savedDeviceProfile.getType(), foundDeviceProfileInfo.getType()); Assert.assertEquals(savedDeviceProfile.getType(), foundDeviceProfileInfo.getType());
} }
@Test
public void whenGetDeviceProfileInfoById_thenPermissionsAreChecked() throws Exception {
DeviceProfile deviceProfile = createDeviceProfile("Device profile 1", null);
deviceProfile = doPost("/api/deviceProfile", deviceProfile, DeviceProfile.class);
loginDifferentTenant();
doGet("/api/deviceProfileInfo/" + deviceProfile.getId())
.andExpect(status().isForbidden());
}
@Test @Test
public void testFindDefaultDeviceProfileInfo() throws Exception { public void testFindDefaultDeviceProfileInfo() throws Exception {
DeviceProfileInfo foundDefaultDeviceProfileInfo = doGet("/api/deviceProfileInfo/default", DeviceProfileInfo.class); DeviceProfileInfo foundDefaultDeviceProfileInfo = doGet("/api/deviceProfileInfo/default", DeviceProfileInfo.class);

View File

@ -63,4 +63,9 @@ public class DeviceProfileInfo extends EntityInfo {
this.transportType = transportType; this.transportType = transportType;
} }
public DeviceProfileInfo(DeviceProfile profile) {
this(profile.getId(), profile.getName(), profile.getImage(), profile.getDefaultDashboardId(),
profile.getType(), profile.getTransportType());
}
} }

View File

@ -197,7 +197,8 @@ class DeviceState {
private void processAlarmDeleteNotification(TbContext ctx, TbMsg msg) { private void processAlarmDeleteNotification(TbContext ctx, TbMsg msg) {
Alarm alarm = JacksonUtil.fromString(msg.getData(), Alarm.class); Alarm alarm = JacksonUtil.fromString(msg.getData(), Alarm.class);
alarmStates.values().removeIf(alarmState -> alarmState.getCurrentAlarm().getId().equals(alarm.getId())); alarmStates.values().removeIf(alarmState -> alarmState.getCurrentAlarm() != null
&& alarmState.getCurrentAlarm().getId().equals(alarm.getId()));
ctx.tellSuccess(msg); ctx.tellSuccess(msg);
} }
@ -219,11 +220,15 @@ class DeviceState {
} }
if (!keys.isEmpty()) { if (!keys.isEmpty()) {
EntityKeyType keyType = getKeyTypeFromScope(scope); EntityKeyType keyType = getKeyTypeFromScope(scope);
keys.forEach(key -> latestValues.removeValue(new EntityKey(keyType, key))); Set<AlarmConditionFilterKey> removedKeys = keys.stream().map(key -> new EntityKey(keyType, key))
.peek(latestValues::removeValue)
.map(DataSnapshot::toConditionKey).collect(Collectors.toSet());
SnapshotUpdate update = new SnapshotUpdate(AlarmConditionKeyType.ATTRIBUTE, removedKeys);
for (DeviceProfileAlarm alarm : deviceProfile.getAlarmSettings()) { for (DeviceProfileAlarm alarm : deviceProfile.getAlarmSettings()) {
AlarmState alarmState = alarmStates.computeIfAbsent(alarm.getId(), AlarmState alarmState = alarmStates.computeIfAbsent(alarm.getId(),
a -> new AlarmState(this.deviceProfile, deviceId, alarm, getOrInitPersistedAlarmState(alarm), dynamicPredicateValueCtx)); a -> new AlarmState(this.deviceProfile, deviceId, alarm, getOrInitPersistedAlarmState(alarm), dynamicPredicateValueCtx));
stateChanged |= alarmState.process(ctx, msg, latestValues, null); stateChanged |= alarmState.process(ctx, msg, latestValues, update);
} }
} }
ctx.tellSuccess(msg); ctx.tellSuccess(msg);

View File

@ -0,0 +1,176 @@
/**
* 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.rule.engine.profile;
import com.google.common.util.concurrent.Futures;
import org.junit.Before;
import org.junit.Test;
import org.mockito.ArgumentCaptor;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.rule.engine.api.RuleEngineAlarmService;
import org.thingsboard.rule.engine.api.TbContext;
import org.thingsboard.server.common.data.DataConstants;
import org.thingsboard.server.common.data.DeviceProfile;
import org.thingsboard.server.common.data.alarm.Alarm;
import org.thingsboard.server.common.data.alarm.AlarmSeverity;
import org.thingsboard.server.common.data.device.profile.AlarmCondition;
import org.thingsboard.server.common.data.device.profile.AlarmConditionFilter;
import org.thingsboard.server.common.data.device.profile.AlarmConditionFilterKey;
import org.thingsboard.server.common.data.device.profile.AlarmConditionKeyType;
import org.thingsboard.server.common.data.device.profile.AlarmRule;
import org.thingsboard.server.common.data.device.profile.DeviceProfileAlarm;
import org.thingsboard.server.common.data.device.profile.DeviceProfileData;
import org.thingsboard.server.common.data.device.profile.SimpleAlarmConditionSpec;
import org.thingsboard.server.common.data.id.AlarmId;
import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.query.BooleanFilterPredicate;
import org.thingsboard.server.common.data.query.EntityKeyValueType;
import org.thingsboard.server.common.data.query.FilterPredicateValue;
import org.thingsboard.server.common.msg.TbMsg;
import org.thingsboard.server.common.msg.TbMsgMetaData;
import org.thingsboard.server.common.msg.session.SessionMsgType;
import org.thingsboard.server.dao.attributes.AttributesService;
import org.thingsboard.server.dao.device.DeviceService;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import java.util.UUID;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyCollection;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
public class DeviceStateTest {
private TbContext ctx;
@Before
public void beforeEach() {
ctx = mock(TbContext.class);
when(ctx.getDeviceService()).thenReturn(mock(DeviceService.class));
AttributesService attributesService = mock(AttributesService.class);
when(attributesService.find(any(), any(), any(), anyCollection())).thenReturn(Futures.immediateFuture(Collections.emptyList()));
when(ctx.getAttributesService()).thenReturn(attributesService);
RuleEngineAlarmService alarmService = mock(RuleEngineAlarmService.class);
when(alarmService.findLatestByOriginatorAndType(any(), any(), any())).thenReturn(Futures.immediateFuture(null));
when(alarmService.createOrUpdateAlarm(any())).thenAnswer(invocationOnMock -> {
Alarm alarm = invocationOnMock.getArgument(0);
alarm.setId(new AlarmId(UUID.randomUUID()));
return alarm;
});
when(ctx.getAlarmService()).thenReturn(alarmService);
when(ctx.newMsg(any(), any(), any(), any(), any(), any())).thenAnswer(invocationOnMock -> {
String data = invocationOnMock.getArgument(invocationOnMock.getArguments().length - 1);
return TbMsg.newMsg(null, null, new TbMsgMetaData(), data);
});
}
@Test
public void whenAttributeIsDeleted_thenUnneededAlarmRulesAreNotReevaluated() throws Exception {
DeviceProfileAlarm alarmConfig = createAlarmConfigWithBoolAttrCondition("enabled", false);
DeviceId deviceId = new DeviceId(UUID.randomUUID());
DeviceState deviceState = createDeviceState(deviceId, alarmConfig);
TbMsg attributeUpdateMsg = TbMsg.newMsg(SessionMsgType.POST_ATTRIBUTES_REQUEST.name(),
deviceId, new TbMsgMetaData(), "{ \"enabled\": false }");
deviceState.process(ctx, attributeUpdateMsg);
ArgumentCaptor<TbMsg> resultMsgCaptor = ArgumentCaptor.forClass(TbMsg.class);
verify(ctx).enqueueForTellNext(resultMsgCaptor.capture(), eq("Alarm Created"));
Alarm alarm = JacksonUtil.fromString(resultMsgCaptor.getValue().getData(), Alarm.class);
deviceState.process(ctx, TbMsg.newMsg(DataConstants.ALARM_CLEAR, deviceId, new TbMsgMetaData(), JacksonUtil.toString(alarm)));
reset(ctx);
String deletedAttributes = "{ \"attributes\": [ \"other\" ] }";
deviceState.process(ctx, TbMsg.newMsg(DataConstants.ATTRIBUTES_DELETED, deviceId, new TbMsgMetaData(), deletedAttributes));
verify(ctx, never()).enqueueForTellNext(any(), anyString());
}
@Test
public void whenDeletingClearedAlarm_thenNoError() throws Exception {
DeviceProfileAlarm alarmConfig = createAlarmConfigWithBoolAttrCondition("enabled", false);
DeviceId deviceId = new DeviceId(UUID.randomUUID());
DeviceState deviceState = createDeviceState(deviceId, alarmConfig);
TbMsg attributeUpdateMsg = TbMsg.newMsg(SessionMsgType.POST_ATTRIBUTES_REQUEST.name(),
deviceId, new TbMsgMetaData(), "{ \"enabled\": false }");
deviceState.process(ctx, attributeUpdateMsg);
ArgumentCaptor<TbMsg> resultMsgCaptor = ArgumentCaptor.forClass(TbMsg.class);
verify(ctx).enqueueForTellNext(resultMsgCaptor.capture(), eq("Alarm Created"));
Alarm alarm = JacksonUtil.fromString(resultMsgCaptor.getValue().getData(), Alarm.class);
deviceState.process(ctx, TbMsg.newMsg(DataConstants.ALARM_CLEAR, deviceId, new TbMsgMetaData(), JacksonUtil.toString(alarm)));
TbMsg alarmDeleteNotification = TbMsg.newMsg(DataConstants.ALARM_DELETE, deviceId, new TbMsgMetaData(), JacksonUtil.toString(alarm));
assertDoesNotThrow(() -> {
deviceState.process(ctx, alarmDeleteNotification);
});
}
private DeviceState createDeviceState(DeviceId deviceId, DeviceProfileAlarm... alarmConfigs) {
DeviceProfile deviceProfile = new DeviceProfile();
DeviceProfileData profileData = new DeviceProfileData();
profileData.setAlarms(List.of(alarmConfigs));
deviceProfile.setProfileData(profileData);
ProfileState profileState = new ProfileState(deviceProfile);
return new DeviceState(ctx, new TbDeviceProfileNodeConfiguration(),
deviceId, profileState, null);
}
private DeviceProfileAlarm createAlarmConfigWithBoolAttrCondition(String key, boolean value) {
AlarmConditionFilter condition = new AlarmConditionFilter();
condition.setKey(new AlarmConditionFilterKey(AlarmConditionKeyType.ATTRIBUTE, key));
condition.setValueType(EntityKeyValueType.BOOLEAN);
BooleanFilterPredicate predicate = new BooleanFilterPredicate();
predicate.setOperation(BooleanFilterPredicate.BooleanOperation.EQUAL);
predicate.setValue(new FilterPredicateValue<>(value));
condition.setPredicate(predicate);
DeviceProfileAlarm alarmConfig = new DeviceProfileAlarm();
alarmConfig.setId("MyAlarmID");
alarmConfig.setAlarmType("MyAlarm");
AlarmRule alarmRule = new AlarmRule();
AlarmCondition alarmCondition = new AlarmCondition();
alarmCondition.setSpec(new SimpleAlarmConditionSpec());
alarmCondition.setCondition(List.of(condition));
alarmRule.setCondition(alarmCondition);
alarmConfig.setCreateRules(new TreeMap<>(Map.of(AlarmSeverity.CRITICAL, alarmRule)));
return alarmConfig;
}
}