Device Credentials service

This commit is contained in:
Andrii Shvaika 2022-05-10 15:35:04 +03:00
parent 5243b873e0
commit 96a9b22a67
5 changed files with 133 additions and 12 deletions

View File

@ -0,0 +1,34 @@
/**
* 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.dao.device;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.cache.CacheManager;
import org.springframework.stereotype.Service;
import org.thingsboard.server.common.data.CacheConstants;
import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.security.DeviceCredentials;
import org.thingsboard.server.dao.cache.CaffeineTbTransactionalCache;
@ConditionalOnProperty(prefix = "cache", value = "type", havingValue = "caffeine", matchIfMissing = true)
@Service("DeviceCredentialsCache")
public class DeviceCredentialsCaffeineCache extends CaffeineTbTransactionalCache<String, DeviceCredentials> {
public DeviceCredentialsCaffeineCache(CacheManager cacheManager) {
super(cacheManager, CacheConstants.DEVICE_CREDENTIALS_CACHE);
}
}

View File

@ -0,0 +1,28 @@
/**
* 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.dao.device;
import lombok.Data;
import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.TenantId;
@Data
class DeviceCredentialsEvictEvent {
private final String newCedentialsId;
private final String oldCredentialsId;
}

View File

@ -0,0 +1,36 @@
/**
* 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.dao.device;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.stereotype.Service;
import org.thingsboard.server.cache.CacheSpecsMap;
import org.thingsboard.server.cache.TBRedisCacheConfiguration;
import org.thingsboard.server.common.data.CacheConstants;
import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.security.DeviceCredentials;
import org.thingsboard.server.dao.cache.RedisTbTransactionalCache;
import org.thingsboard.server.dao.cache.TbRedisSerializer;
@ConditionalOnProperty(prefix = "cache", value = "type", havingValue = "redis")
@Service("DeviceCredentialsCache")
public class DeviceCredentialsRedisCache extends RedisTbTransactionalCache<String, DeviceCredentials> {
public DeviceCredentialsRedisCache(TBRedisCacheConfiguration configuration, CacheSpecsMap cacheSpecsMap, RedisConnectionFactory connectionFactory) {
super(CacheConstants.DEVICE_CREDENTIALS_CACHE, cacheSpecsMap, connectionFactory, configuration, new TbRedisSerializer<>());
}
}

View File

@ -21,8 +21,10 @@ import org.eclipse.leshan.core.util.SecurityUtil;
import org.hibernate.exception.ConstraintViolationException; import org.hibernate.exception.ConstraintViolationException;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheEvict; import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.event.TransactionalEventListener;
import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.StringUtils;
import org.thingsboard.server.common.data.device.credentials.BasicMqttCredentials; import org.thingsboard.server.common.data.device.credentials.BasicMqttCredentials;
@ -40,7 +42,7 @@ import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.security.DeviceCredentials; import org.thingsboard.server.common.data.security.DeviceCredentials;
import org.thingsboard.server.common.msg.EncryptionUtil; import org.thingsboard.server.common.msg.EncryptionUtil;
import org.thingsboard.server.dao.entity.AbstractEntityService; import org.thingsboard.server.dao.entity.AbstractCachedEntityService;
import org.thingsboard.server.dao.exception.DataValidationException; import org.thingsboard.server.dao.exception.DataValidationException;
import org.thingsboard.server.dao.exception.DeviceCredentialsValidationException; import org.thingsboard.server.dao.exception.DeviceCredentialsValidationException;
import org.thingsboard.server.dao.service.DataValidator; import org.thingsboard.server.dao.service.DataValidator;
@ -51,7 +53,7 @@ import static org.thingsboard.server.dao.service.Validator.validateString;
@Service @Service
@Slf4j @Slf4j
public class DeviceCredentialsServiceImpl extends AbstractEntityService implements DeviceCredentialsService { public class DeviceCredentialsServiceImpl extends AbstractCachedEntityService<String, DeviceCredentials, DeviceCredentialsEvictEvent> implements DeviceCredentialsService {
@Autowired @Autowired
private DeviceCredentialsDao deviceCredentialsDao; private DeviceCredentialsDao deviceCredentialsDao;
@ -59,6 +61,15 @@ public class DeviceCredentialsServiceImpl extends AbstractEntityService implemen
@Autowired @Autowired
private DataValidator<DeviceCredentials> credentialsValidator; private DataValidator<DeviceCredentials> credentialsValidator;
@TransactionalEventListener(classes = DeviceCredentialsEvictEvent.class)
@Override
public void handleEvictEvent(DeviceCredentialsEvictEvent event) {
cache.evict(event.getNewCedentialsId());
if (StringUtils.isNotEmpty(event.getOldCredentialsId()) && !event.getNewCedentialsId().equals(event.getOldCredentialsId())) {
cache.evict(event.getOldCredentialsId());
}
}
@Override @Override
public DeviceCredentials findDeviceCredentialsByDeviceId(TenantId tenantId, DeviceId deviceId) { public DeviceCredentials findDeviceCredentialsByDeviceId(TenantId tenantId, DeviceId deviceId) {
log.trace("Executing findDeviceCredentialsByDeviceId [{}]", deviceId); log.trace("Executing findDeviceCredentialsByDeviceId [{}]", deviceId);
@ -67,19 +78,21 @@ public class DeviceCredentialsServiceImpl extends AbstractEntityService implemen
} }
@Override @Override
@Cacheable(cacheNames = DEVICE_CREDENTIALS_CACHE, key = "'deviceCredentials_' + #credentialsId", unless = "#result == null")
public DeviceCredentials findDeviceCredentialsByCredentialsId(String credentialsId) { public DeviceCredentials findDeviceCredentialsByCredentialsId(String credentialsId) {
log.trace("Executing findDeviceCredentialsByCredentialsId [{}]", credentialsId); log.trace("Executing findDeviceCredentialsByCredentialsId [{}]", credentialsId);
validateString(credentialsId, "Incorrect credentialsId " + credentialsId); validateString(credentialsId, "Incorrect credentialsId " + credentialsId);
return deviceCredentialsDao.findByCredentialsId(TenantId.SYS_TENANT_ID, credentialsId); return cache.getAndPutInTransaction(credentialsId,
() -> deviceCredentialsDao.findByCredentialsId(TenantId.SYS_TENANT_ID, credentialsId),
false);
} }
@Transactional(propagation = Propagation.SUPPORTS)
@Override @Override
@CacheEvict(cacheNames = DEVICE_CREDENTIALS_CACHE, keyGenerator = "previousDeviceCredentialsId", beforeInvocation = true)
public DeviceCredentials updateDeviceCredentials(TenantId tenantId, DeviceCredentials deviceCredentials) { public DeviceCredentials updateDeviceCredentials(TenantId tenantId, DeviceCredentials deviceCredentials) {
return saveOrUpdate(tenantId, deviceCredentials); return saveOrUpdate(tenantId, deviceCredentials);
} }
@Transactional(propagation = Propagation.SUPPORTS)
@Override @Override
public DeviceCredentials createDeviceCredentials(TenantId tenantId, DeviceCredentials deviceCredentials) { public DeviceCredentials createDeviceCredentials(TenantId tenantId, DeviceCredentials deviceCredentials) {
return saveOrUpdate(tenantId, deviceCredentials); return saveOrUpdate(tenantId, deviceCredentials);
@ -93,7 +106,13 @@ public class DeviceCredentialsServiceImpl extends AbstractEntityService implemen
log.trace("Executing updateDeviceCredentials [{}]", deviceCredentials); log.trace("Executing updateDeviceCredentials [{}]", deviceCredentials);
credentialsValidator.validate(deviceCredentials, id -> tenantId); credentialsValidator.validate(deviceCredentials, id -> tenantId);
try { try {
return deviceCredentialsDao.saveAndFlush(tenantId, deviceCredentials); DeviceCredentials oldDeviceCredentials = null;
if (deviceCredentials.getDeviceId() != null) {
oldDeviceCredentials = deviceCredentialsDao.findByDeviceId(tenantId, deviceCredentials.getDeviceId().getId());
}
var value = deviceCredentialsDao.saveAndFlush(tenantId, deviceCredentials);
publishEvictEvent(new DeviceCredentialsEvictEvent(value.getCredentialsId(), oldDeviceCredentials != null ? oldDeviceCredentials.getCredentialsId() : null));
return value;
} catch (Exception t) { } catch (Exception t) {
ConstraintViolationException e = extractConstraintViolationException(t).orElse(null); ConstraintViolationException e = extractConstraintViolationException(t).orElse(null);
if (e != null && e.getConstraintName() != null if (e != null && e.getConstraintName() != null
@ -182,8 +201,7 @@ public class DeviceCredentialsServiceImpl extends AbstractEntityService implemen
deviceCredentials.setCredentialsValue(JacksonUtil.toString(lwM2MCredentials)); deviceCredentials.setCredentialsValue(JacksonUtil.toString(lwM2MCredentials));
X509ClientCredential x509ClientConfig = (X509ClientCredential) clientCredentials; X509ClientCredential x509ClientConfig = (X509ClientCredential) clientCredentials;
if ((StringUtils.isNotBlank(x509ClientConfig.getCert()))) { if ((StringUtils.isNotBlank(x509ClientConfig.getCert()))) {
String sha3Hash = EncryptionUtil.getSha3Hash(x509ClientConfig.getCert()); credentialsId = EncryptionUtil.getSha3Hash(x509ClientConfig.getCert());
credentialsId = sha3Hash;
} else { } else {
credentialsId = x509ClientConfig.getEndpoint(); credentialsId = x509ClientConfig.getEndpoint();
} }
@ -366,11 +384,12 @@ public class DeviceCredentialsServiceImpl extends AbstractEntityService implemen
} }
} }
@Transactional(propagation = Propagation.SUPPORTS)
@Override @Override
@CacheEvict(cacheNames = DEVICE_CREDENTIALS_CACHE, key = "'deviceCredentials_' + #deviceCredentials.credentialsId")
public void deleteDeviceCredentials(TenantId tenantId, DeviceCredentials deviceCredentials) { public void deleteDeviceCredentials(TenantId tenantId, DeviceCredentials deviceCredentials) {
log.trace("Executing deleteDeviceCredentials [{}]", deviceCredentials); log.trace("Executing deleteDeviceCredentials [{}]", deviceCredentials);
deviceCredentialsDao.removeById(tenantId, deviceCredentials.getUuidId()); deviceCredentialsDao.removeById(tenantId, deviceCredentials.getUuidId());
publishEvictEvent(new DeviceCredentialsEvictEvent(deviceCredentials.getCredentialsId(), null));
} }
} }

View File

@ -32,6 +32,7 @@ import org.thingsboard.server.common.data.security.DeviceCredentials;
import org.thingsboard.server.common.data.security.DeviceCredentialsType; import org.thingsboard.server.common.data.security.DeviceCredentialsType;
import org.thingsboard.server.dao.device.DeviceCredentialsDao; import org.thingsboard.server.dao.device.DeviceCredentialsDao;
import org.thingsboard.server.dao.device.DeviceCredentialsService; import org.thingsboard.server.dao.device.DeviceCredentialsService;
import org.thingsboard.server.dao.device.DeviceDao;
import org.thingsboard.server.dao.device.DeviceService; import org.thingsboard.server.dao.device.DeviceService;
import java.util.UUID; import java.util.UUID;
@ -120,7 +121,10 @@ public abstract class BaseDeviceCredentialsCacheTest extends AbstractServiceTest
when(deviceCredentialsDao.findById(SYSTEM_TENANT_ID, deviceCredentialsId)).thenReturn(createDummyDeviceCredentialsEntity(CREDENTIALS_ID_1)); when(deviceCredentialsDao.findById(SYSTEM_TENANT_ID, deviceCredentialsId)).thenReturn(createDummyDeviceCredentialsEntity(CREDENTIALS_ID_1));
when(deviceService.findDeviceById(SYSTEM_TENANT_ID, new DeviceId(deviceId))).thenReturn(new Device()); when(deviceService.findDeviceById(SYSTEM_TENANT_ID, new DeviceId(deviceId))).thenReturn(new Device());
deviceCredentialsService.updateDeviceCredentials(SYSTEM_TENANT_ID, createDummyDeviceCredentials(deviceCredentialsId, CREDENTIALS_ID_2, deviceId)); var dummy = createDummyDeviceCredentials(deviceCredentialsId, CREDENTIALS_ID_2, deviceId);
when(deviceCredentialsDao.saveAndFlush(SYSTEM_TENANT_ID, dummy)).thenReturn(dummy);
deviceCredentialsService.updateDeviceCredentials(SYSTEM_TENANT_ID, dummy);
when(deviceCredentialsDao.findByCredentialsId(SYSTEM_TENANT_ID, CREDENTIALS_ID_1)).thenReturn(null); when(deviceCredentialsDao.findByCredentialsId(SYSTEM_TENANT_ID, CREDENTIALS_ID_1)).thenReturn(null);