diff --git a/application/src/main/java/org/thingsboard/server/controller/BaseController.java b/application/src/main/java/org/thingsboard/server/controller/BaseController.java index ed5975a218..8ae04357a4 100644 --- a/application/src/main/java/org/thingsboard/server/controller/BaseController.java +++ b/application/src/main/java/org/thingsboard/server/controller/BaseController.java @@ -15,9 +15,12 @@ */ package org.thingsboard.server.controller; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.web.bind.annotation.ExceptionHandler; @@ -27,6 +30,8 @@ import org.thingsboard.server.common.data.alarm.Alarm; import org.thingsboard.server.common.data.alarm.AlarmId; import org.thingsboard.server.common.data.alarm.AlarmInfo; import org.thingsboard.server.common.data.asset.Asset; +import org.thingsboard.server.common.data.audit.ActionStatus; +import org.thingsboard.server.common.data.audit.ActionType; import org.thingsboard.server.common.data.id.*; import org.thingsboard.server.common.data.page.TextPageLink; import org.thingsboard.server.common.data.page.TimePageLink; @@ -73,6 +78,10 @@ public abstract class BaseController { public static final String INCORRECT_TENANT_ID = "Incorrect tenantId "; public static final String YOU_DON_T_HAVE_PERMISSION_TO_PERFORM_THIS_OPERATION = "You don't have permission to perform this operation!"; + + @Value("${audit_log.exceptions.enabled}") + private boolean auditLogExceptionsEnabled; + @Autowired private ThingsboardErrorResponseHandler errorResponseHandler; @@ -131,6 +140,11 @@ public abstract class BaseController { return handleException(exception, true); } + ThingsboardException handleException(Exception exception, ActionType actionType, String actionData) { + logExceptionToAuditLog(exception, actionType, actionData); + return handleException(exception, true); + } + private ThingsboardException handleException(Exception exception, boolean logException) { if (logException) { log.error("Error [{}]", exception.getMessage()); @@ -153,6 +167,36 @@ public abstract class BaseController { } } + private void logExceptionToAuditLog(Exception exception, ActionType actionType, String actionData) { + try { + if (auditLogExceptionsEnabled) { + SecurityUser currentUser = getCurrentUser(); + EntityId entityId; + CustomerId customerId; + if (!currentUser.getCustomerId().getId().equals(ModelConstants.NULL_UUID)) { + entityId = currentUser.getCustomerId(); + customerId = currentUser.getCustomerId(); + } else { + entityId = currentUser.getTenantId(); + customerId = new CustomerId(ModelConstants.NULL_UUID); + } + + JsonNode actionDataNode = new ObjectMapper().createObjectNode().put("actionData", actionData); + + auditLogService.logEntityAction(currentUser, + entityId, + null, + customerId, + actionType, + actionDataNode, + ActionStatus.FAILURE, + exception.getMessage()); + } + } catch (Exception e) { + log.error("Exception happend during saving to audit log", e); + } + } + T checkNotNull(T reference) throws ThingsboardException { if (reference == null) { throw new ThingsboardException("Requested item wasn't found!", ThingsboardErrorCode.ITEM_NOT_FOUND); @@ -545,4 +589,24 @@ public abstract class BaseController { serverPort); return baseUrl; } + + protected void logEntityDeleted(EntityId entityId, String entityName, CustomerId customerId) throws ThingsboardException { + logEntitySuccess(entityId, entityName, customerId, ActionType.DELETED); + } + + protected void logEntityAddedOrUpdated(EntityId entityId, String entityName, CustomerId customerId, boolean isAddAction) throws ThingsboardException { + logEntitySuccess(entityId, entityName, customerId, isAddAction ? ActionType.ADDED : ActionType.UPDATED); + } + + protected void logEntitySuccess(EntityId entityId, String entityName, CustomerId customerId, ActionType actionType) throws ThingsboardException { + auditLogService.logEntityAction( + getCurrentUser(), + entityId, + entityName, + customerId, + actionType, + null, + ActionStatus.SUCCESS, + null); + } } diff --git a/application/src/main/java/org/thingsboard/server/controller/DeviceController.java b/application/src/main/java/org/thingsboard/server/controller/DeviceController.java index 0844ce0bf4..65fcaeaa88 100644 --- a/application/src/main/java/org/thingsboard/server/controller/DeviceController.java +++ b/application/src/main/java/org/thingsboard/server/controller/DeviceController.java @@ -85,20 +85,11 @@ public class DeviceController extends BaseController { savedDevice.getName(), savedDevice.getType()); - auditLogService.logEntityAction( - getCurrentUser(), - savedDevice.getId(), - savedDevice.getName(), - savedDevice.getCustomerId(), - device.getId() == null ? ActionType.ADDED : ActionType.UPDATED, - null, - ActionStatus.SUCCESS, - null); - + logEntityAddedOrUpdated(savedDevice.getId(), savedDevice.getName(), savedDevice.getCustomerId(), device.getId() == null); return savedDevice; } catch (Exception e) { - throw handleException(e); + throw handleException(e, device.getId() == null ? ActionType.ADDED : ActionType.UPDATED, "addDevice(" + device + ")"); } } @@ -111,17 +102,9 @@ public class DeviceController extends BaseController { DeviceId deviceId = new DeviceId(toUUID(strDeviceId)); Device device = checkDeviceId(deviceId); deviceService.deleteDevice(deviceId); - auditLogService.logEntityAction( - getCurrentUser(), - device.getId(), - device.getName(), - device.getCustomerId(), - ActionType.DELETED, - null, - ActionStatus.SUCCESS, - null); + logEntityDeleted(device.getId(), device.getName(), device.getCustomerId()); } catch (Exception e) { - throw handleException(e); + throw handleException(e, ActionType.DELETED, "deleteDevice(" + strDeviceId + ")"); } } @@ -200,18 +183,10 @@ public class DeviceController extends BaseController { Device device = checkDeviceId(deviceCredentials.getDeviceId()); DeviceCredentials result = checkNotNull(deviceCredentialsService.updateDeviceCredentials(deviceCredentials)); actorService.onCredentialsUpdate(getCurrentUser().getTenantId(), deviceCredentials.getDeviceId()); - auditLogService.logEntityAction( - getCurrentUser(), - device.getId(), - device.getName(), - device.getCustomerId(), - ActionType.CREDENTIALS_UPDATED, - null, - ActionStatus.SUCCESS, - null); + logEntitySuccess(device.getId(), device.getName(), device.getCustomerId(), ActionType.CREDENTIALS_UPDATED); return result; } catch (Exception e) { - throw handleException(e); + throw handleException(e, ActionType.CREDENTIALS_UPDATED, "saveDeviceCredentials(" + deviceCredentials + ")"); } } diff --git a/application/src/main/resources/thingsboard.yml b/application/src/main/resources/thingsboard.yml index 876a1d7964..83ff3a9848 100644 --- a/application/src/main/resources/thingsboard.yml +++ b/application/src/main/resources/thingsboard.yml @@ -286,4 +286,11 @@ spring: # Audit log parameters audit_log: # Enable/disable audit log functionality. - enabled: "${AUDIT_LOG_ENABLED:true}" \ No newline at end of file + enabled: "${AUDIT_LOG_ENABLED:true}" + # Specify partitioning size for audit log by tenant id storage. Example MINUTES, HOURS, DAYS, MONTHS + by_tenant_partitioning: "${AUDIT_LOG_BY_TENANT_PARTITIONING:MONTHS}" + # Number of days as history period if startTime and endTime are not specified + default_query_period: "${AUDIT_LOG_DEFAULT_QUERY_PERIOD:30}" + exceptions: + # Enable/disable audit log functionality for exceptions. + enabled: "${AUDIT_LOG_EXCEPTIONS_ENABLED:true}" \ No newline at end of file diff --git a/dao/src/main/java/org/thingsboard/server/dao/audit/CassandraAuditLogDao.java b/dao/src/main/java/org/thingsboard/server/dao/audit/CassandraAuditLogDao.java index 8262933711..085d798ea5 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/audit/CassandraAuditLogDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/audit/CassandraAuditLogDao.java @@ -81,10 +81,13 @@ public class CassandraAuditLogDao extends CassandraAbstractSearchTimeDao findAuditLogsByTenantIdAndCustomerId(TenantId tenantId, CustomerId customerId, TimePageLink pageLink) { - return null; + return new TimePageData<>(null, pageLink); } @Override public TimePageData findAuditLogsByTenantIdAndUserId(TenantId tenantId, UserId userId, TimePageLink pageLink) { - return null; + return new TimePageData<>(null, pageLink); } @Override public TimePageData findAuditLogsByTenantIdAndEntityId(TenantId tenantId, EntityId entityId, TimePageLink pageLink) { - return null; + return new TimePageData<>(null, pageLink); } @Override public TimePageData findAuditLogsByTenantId(TenantId tenantId, TimePageLink pageLink) { - return null; + return new TimePageData<>(null, pageLink); } @Override public ListenableFuture> logEntityAction(User user, EntityId entityId, String entityName, CustomerId customerId, ActionType actionType, JsonNode actionData, ActionStatus actionStatus, String actionFailureDetails) { - return null; + return Futures.immediateFuture(Collections.emptyList()); } } diff --git a/dao/src/test/resources/application-test.properties b/dao/src/test/resources/application-test.properties index a09a5a74f9..4a73d37f08 100644 --- a/dao/src/test/resources/application-test.properties +++ b/dao/src/test/resources/application-test.properties @@ -10,6 +10,9 @@ zk.zk_dir=/thingsboard updates.enabled=false audit_log.enabled=true +audit_log.exceptions.enabled=false +audit_log.by_tenant_partitioning=MONTHS +audit_log.default_query_period=30 caching.specs.relations.timeToLiveInMinutes=1440 caching.specs.relations.maxSize=100000