Showing
6 changed files
with
93 additions
and
39 deletions
@@ -15,9 +15,12 @@ | @@ -15,9 +15,12 @@ | ||
15 | */ | 15 | */ |
16 | package org.thingsboard.server.controller; | 16 | package org.thingsboard.server.controller; |
17 | 17 | ||
18 | +import com.fasterxml.jackson.databind.JsonNode; | ||
19 | +import com.fasterxml.jackson.databind.ObjectMapper; | ||
18 | import lombok.extern.slf4j.Slf4j; | 20 | import lombok.extern.slf4j.Slf4j; |
19 | import org.apache.commons.lang3.StringUtils; | 21 | import org.apache.commons.lang3.StringUtils; |
20 | import org.springframework.beans.factory.annotation.Autowired; | 22 | import org.springframework.beans.factory.annotation.Autowired; |
23 | +import org.springframework.beans.factory.annotation.Value; | ||
21 | import org.springframework.security.core.Authentication; | 24 | import org.springframework.security.core.Authentication; |
22 | import org.springframework.security.core.context.SecurityContextHolder; | 25 | import org.springframework.security.core.context.SecurityContextHolder; |
23 | import org.springframework.web.bind.annotation.ExceptionHandler; | 26 | import org.springframework.web.bind.annotation.ExceptionHandler; |
@@ -27,6 +30,8 @@ import org.thingsboard.server.common.data.alarm.Alarm; | @@ -27,6 +30,8 @@ import org.thingsboard.server.common.data.alarm.Alarm; | ||
27 | import org.thingsboard.server.common.data.alarm.AlarmId; | 30 | import org.thingsboard.server.common.data.alarm.AlarmId; |
28 | import org.thingsboard.server.common.data.alarm.AlarmInfo; | 31 | import org.thingsboard.server.common.data.alarm.AlarmInfo; |
29 | import org.thingsboard.server.common.data.asset.Asset; | 32 | import org.thingsboard.server.common.data.asset.Asset; |
33 | +import org.thingsboard.server.common.data.audit.ActionStatus; | ||
34 | +import org.thingsboard.server.common.data.audit.ActionType; | ||
30 | import org.thingsboard.server.common.data.id.*; | 35 | import org.thingsboard.server.common.data.id.*; |
31 | import org.thingsboard.server.common.data.page.TextPageLink; | 36 | import org.thingsboard.server.common.data.page.TextPageLink; |
32 | import org.thingsboard.server.common.data.page.TimePageLink; | 37 | import org.thingsboard.server.common.data.page.TimePageLink; |
@@ -73,6 +78,10 @@ public abstract class BaseController { | @@ -73,6 +78,10 @@ public abstract class BaseController { | ||
73 | 78 | ||
74 | public static final String INCORRECT_TENANT_ID = "Incorrect tenantId "; | 79 | public static final String INCORRECT_TENANT_ID = "Incorrect tenantId "; |
75 | public static final String YOU_DON_T_HAVE_PERMISSION_TO_PERFORM_THIS_OPERATION = "You don't have permission to perform this operation!"; | 80 | public static final String YOU_DON_T_HAVE_PERMISSION_TO_PERFORM_THIS_OPERATION = "You don't have permission to perform this operation!"; |
81 | + | ||
82 | + @Value("${audit_log.exceptions.enabled}") | ||
83 | + private boolean auditLogExceptionsEnabled; | ||
84 | + | ||
76 | @Autowired | 85 | @Autowired |
77 | private ThingsboardErrorResponseHandler errorResponseHandler; | 86 | private ThingsboardErrorResponseHandler errorResponseHandler; |
78 | 87 | ||
@@ -131,6 +140,11 @@ public abstract class BaseController { | @@ -131,6 +140,11 @@ public abstract class BaseController { | ||
131 | return handleException(exception, true); | 140 | return handleException(exception, true); |
132 | } | 141 | } |
133 | 142 | ||
143 | + ThingsboardException handleException(Exception exception, ActionType actionType, String actionData) { | ||
144 | + logExceptionToAuditLog(exception, actionType, actionData); | ||
145 | + return handleException(exception, true); | ||
146 | + } | ||
147 | + | ||
134 | private ThingsboardException handleException(Exception exception, boolean logException) { | 148 | private ThingsboardException handleException(Exception exception, boolean logException) { |
135 | if (logException) { | 149 | if (logException) { |
136 | log.error("Error [{}]", exception.getMessage()); | 150 | log.error("Error [{}]", exception.getMessage()); |
@@ -153,6 +167,36 @@ public abstract class BaseController { | @@ -153,6 +167,36 @@ public abstract class BaseController { | ||
153 | } | 167 | } |
154 | } | 168 | } |
155 | 169 | ||
170 | + private void logExceptionToAuditLog(Exception exception, ActionType actionType, String actionData) { | ||
171 | + try { | ||
172 | + if (auditLogExceptionsEnabled) { | ||
173 | + SecurityUser currentUser = getCurrentUser(); | ||
174 | + EntityId entityId; | ||
175 | + CustomerId customerId; | ||
176 | + if (!currentUser.getCustomerId().getId().equals(ModelConstants.NULL_UUID)) { | ||
177 | + entityId = currentUser.getCustomerId(); | ||
178 | + customerId = currentUser.getCustomerId(); | ||
179 | + } else { | ||
180 | + entityId = currentUser.getTenantId(); | ||
181 | + customerId = new CustomerId(ModelConstants.NULL_UUID); | ||
182 | + } | ||
183 | + | ||
184 | + JsonNode actionDataNode = new ObjectMapper().createObjectNode().put("actionData", actionData); | ||
185 | + | ||
186 | + auditLogService.logEntityAction(currentUser, | ||
187 | + entityId, | ||
188 | + null, | ||
189 | + customerId, | ||
190 | + actionType, | ||
191 | + actionDataNode, | ||
192 | + ActionStatus.FAILURE, | ||
193 | + exception.getMessage()); | ||
194 | + } | ||
195 | + } catch (Exception e) { | ||
196 | + log.error("Exception happend during saving to audit log", e); | ||
197 | + } | ||
198 | + } | ||
199 | + | ||
156 | <T> T checkNotNull(T reference) throws ThingsboardException { | 200 | <T> T checkNotNull(T reference) throws ThingsboardException { |
157 | if (reference == null) { | 201 | if (reference == null) { |
158 | throw new ThingsboardException("Requested item wasn't found!", ThingsboardErrorCode.ITEM_NOT_FOUND); | 202 | throw new ThingsboardException("Requested item wasn't found!", ThingsboardErrorCode.ITEM_NOT_FOUND); |
@@ -545,4 +589,24 @@ public abstract class BaseController { | @@ -545,4 +589,24 @@ public abstract class BaseController { | ||
545 | serverPort); | 589 | serverPort); |
546 | return baseUrl; | 590 | return baseUrl; |
547 | } | 591 | } |
592 | + | ||
593 | + protected void logEntityDeleted(EntityId entityId, String entityName, CustomerId customerId) throws ThingsboardException { | ||
594 | + logEntitySuccess(entityId, entityName, customerId, ActionType.DELETED); | ||
595 | + } | ||
596 | + | ||
597 | + protected void logEntityAddedOrUpdated(EntityId entityId, String entityName, CustomerId customerId, boolean isAddAction) throws ThingsboardException { | ||
598 | + logEntitySuccess(entityId, entityName, customerId, isAddAction ? ActionType.ADDED : ActionType.UPDATED); | ||
599 | + } | ||
600 | + | ||
601 | + protected void logEntitySuccess(EntityId entityId, String entityName, CustomerId customerId, ActionType actionType) throws ThingsboardException { | ||
602 | + auditLogService.logEntityAction( | ||
603 | + getCurrentUser(), | ||
604 | + entityId, | ||
605 | + entityName, | ||
606 | + customerId, | ||
607 | + actionType, | ||
608 | + null, | ||
609 | + ActionStatus.SUCCESS, | ||
610 | + null); | ||
611 | + } | ||
548 | } | 612 | } |
@@ -85,20 +85,11 @@ public class DeviceController extends BaseController { | @@ -85,20 +85,11 @@ public class DeviceController extends BaseController { | ||
85 | savedDevice.getName(), | 85 | savedDevice.getName(), |
86 | savedDevice.getType()); | 86 | savedDevice.getType()); |
87 | 87 | ||
88 | - auditLogService.logEntityAction( | ||
89 | - getCurrentUser(), | ||
90 | - savedDevice.getId(), | ||
91 | - savedDevice.getName(), | ||
92 | - savedDevice.getCustomerId(), | ||
93 | - device.getId() == null ? ActionType.ADDED : ActionType.UPDATED, | ||
94 | - null, | ||
95 | - ActionStatus.SUCCESS, | ||
96 | - null); | ||
97 | - | 88 | + logEntityAddedOrUpdated(savedDevice.getId(), savedDevice.getName(), savedDevice.getCustomerId(), device.getId() == null); |
98 | 89 | ||
99 | return savedDevice; | 90 | return savedDevice; |
100 | } catch (Exception e) { | 91 | } catch (Exception e) { |
101 | - throw handleException(e); | 92 | + throw handleException(e, device.getId() == null ? ActionType.ADDED : ActionType.UPDATED, "addDevice(" + device + ")"); |
102 | } | 93 | } |
103 | } | 94 | } |
104 | 95 | ||
@@ -111,17 +102,9 @@ public class DeviceController extends BaseController { | @@ -111,17 +102,9 @@ public class DeviceController extends BaseController { | ||
111 | DeviceId deviceId = new DeviceId(toUUID(strDeviceId)); | 102 | DeviceId deviceId = new DeviceId(toUUID(strDeviceId)); |
112 | Device device = checkDeviceId(deviceId); | 103 | Device device = checkDeviceId(deviceId); |
113 | deviceService.deleteDevice(deviceId); | 104 | deviceService.deleteDevice(deviceId); |
114 | - auditLogService.logEntityAction( | ||
115 | - getCurrentUser(), | ||
116 | - device.getId(), | ||
117 | - device.getName(), | ||
118 | - device.getCustomerId(), | ||
119 | - ActionType.DELETED, | ||
120 | - null, | ||
121 | - ActionStatus.SUCCESS, | ||
122 | - null); | 105 | + logEntityDeleted(device.getId(), device.getName(), device.getCustomerId()); |
123 | } catch (Exception e) { | 106 | } catch (Exception e) { |
124 | - throw handleException(e); | 107 | + throw handleException(e, ActionType.DELETED, "deleteDevice(" + strDeviceId + ")"); |
125 | } | 108 | } |
126 | } | 109 | } |
127 | 110 | ||
@@ -200,18 +183,10 @@ public class DeviceController extends BaseController { | @@ -200,18 +183,10 @@ public class DeviceController extends BaseController { | ||
200 | Device device = checkDeviceId(deviceCredentials.getDeviceId()); | 183 | Device device = checkDeviceId(deviceCredentials.getDeviceId()); |
201 | DeviceCredentials result = checkNotNull(deviceCredentialsService.updateDeviceCredentials(deviceCredentials)); | 184 | DeviceCredentials result = checkNotNull(deviceCredentialsService.updateDeviceCredentials(deviceCredentials)); |
202 | actorService.onCredentialsUpdate(getCurrentUser().getTenantId(), deviceCredentials.getDeviceId()); | 185 | actorService.onCredentialsUpdate(getCurrentUser().getTenantId(), deviceCredentials.getDeviceId()); |
203 | - auditLogService.logEntityAction( | ||
204 | - getCurrentUser(), | ||
205 | - device.getId(), | ||
206 | - device.getName(), | ||
207 | - device.getCustomerId(), | ||
208 | - ActionType.CREDENTIALS_UPDATED, | ||
209 | - null, | ||
210 | - ActionStatus.SUCCESS, | ||
211 | - null); | 186 | + logEntitySuccess(device.getId(), device.getName(), device.getCustomerId(), ActionType.CREDENTIALS_UPDATED); |
212 | return result; | 187 | return result; |
213 | } catch (Exception e) { | 188 | } catch (Exception e) { |
214 | - throw handleException(e); | 189 | + throw handleException(e, ActionType.CREDENTIALS_UPDATED, "saveDeviceCredentials(" + deviceCredentials + ")"); |
215 | } | 190 | } |
216 | } | 191 | } |
217 | 192 |
@@ -286,4 +286,11 @@ spring: | @@ -286,4 +286,11 @@ spring: | ||
286 | # Audit log parameters | 286 | # Audit log parameters |
287 | audit_log: | 287 | audit_log: |
288 | # Enable/disable audit log functionality. | 288 | # Enable/disable audit log functionality. |
289 | - enabled: "${AUDIT_LOG_ENABLED:true}" | ||
289 | + enabled: "${AUDIT_LOG_ENABLED:true}" | ||
290 | + # Specify partitioning size for audit log by tenant id storage. Example MINUTES, HOURS, DAYS, MONTHS | ||
291 | + by_tenant_partitioning: "${AUDIT_LOG_BY_TENANT_PARTITIONING:MONTHS}" | ||
292 | + # Number of days as history period if startTime and endTime are not specified | ||
293 | + default_query_period: "${AUDIT_LOG_DEFAULT_QUERY_PERIOD:30}" | ||
294 | + exceptions: | ||
295 | + # Enable/disable audit log functionality for exceptions. | ||
296 | + enabled: "${AUDIT_LOG_EXCEPTIONS_ENABLED:true}" |
@@ -81,10 +81,13 @@ public class CassandraAuditLogDao extends CassandraAbstractSearchTimeDao<AuditLo | @@ -81,10 +81,13 @@ public class CassandraAuditLogDao extends CassandraAbstractSearchTimeDao<AuditLo | ||
81 | 81 | ||
82 | protected ExecutorService readResultsProcessingExecutor; | 82 | protected ExecutorService readResultsProcessingExecutor; |
83 | 83 | ||
84 | - @Value("${cassandra.query.ts_key_value_partitioning}") | 84 | + @Value("${audit_log.by_tenant_partitioning}") |
85 | private String partitioning; | 85 | private String partitioning; |
86 | private TsPartitionDate tsFormat; | 86 | private TsPartitionDate tsFormat; |
87 | 87 | ||
88 | + @Value("${audit_log.default_query_period}") | ||
89 | + private Integer defaultQueryPeriodInDays; | ||
90 | + | ||
88 | private PreparedStatement partitionInsertStmt; | 91 | private PreparedStatement partitionInsertStmt; |
89 | private PreparedStatement saveByTenantStmt; | 92 | private PreparedStatement saveByTenantStmt; |
90 | private PreparedStatement saveByTenantIdAndUserIdStmt; | 93 | private PreparedStatement saveByTenantIdAndUserIdStmt; |
@@ -304,7 +307,7 @@ public class CassandraAuditLogDao extends CassandraAbstractSearchTimeDao<AuditLo | @@ -304,7 +307,7 @@ public class CassandraAuditLogDao extends CassandraAbstractSearchTimeDao<AuditLo | ||
304 | if (pageLink.getStartTime() != null && pageLink.getStartTime() != 0) { | 307 | if (pageLink.getStartTime() != null && pageLink.getStartTime() != 0) { |
305 | minPartition = toPartitionTs(pageLink.getStartTime()); | 308 | minPartition = toPartitionTs(pageLink.getStartTime()); |
306 | } else { | 309 | } else { |
307 | - minPartition = toPartitionTs(LocalDate.now().minusMonths(1).atStartOfDay().toInstant(ZoneOffset.UTC).toEpochMilli()); | 310 | + minPartition = toPartitionTs(LocalDate.now().minusDays(defaultQueryPeriodInDays).atStartOfDay().toInstant(ZoneOffset.UTC).toEpochMilli()); |
308 | } | 311 | } |
309 | 312 | ||
310 | long maxPartition; | 313 | long maxPartition; |
@@ -16,6 +16,7 @@ | @@ -16,6 +16,7 @@ | ||
16 | package org.thingsboard.server.dao.audit; | 16 | package org.thingsboard.server.dao.audit; |
17 | 17 | ||
18 | import com.fasterxml.jackson.databind.JsonNode; | 18 | import com.fasterxml.jackson.databind.JsonNode; |
19 | +import com.google.common.util.concurrent.Futures; | ||
19 | import com.google.common.util.concurrent.ListenableFuture; | 20 | import com.google.common.util.concurrent.ListenableFuture; |
20 | import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; | 21 | import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; |
21 | import org.thingsboard.server.common.data.User; | 22 | import org.thingsboard.server.common.data.User; |
@@ -29,6 +30,7 @@ import org.thingsboard.server.common.data.id.UserId; | @@ -29,6 +30,7 @@ import org.thingsboard.server.common.data.id.UserId; | ||
29 | import org.thingsboard.server.common.data.page.TimePageData; | 30 | import org.thingsboard.server.common.data.page.TimePageData; |
30 | import org.thingsboard.server.common.data.page.TimePageLink; | 31 | import org.thingsboard.server.common.data.page.TimePageLink; |
31 | 32 | ||
33 | +import java.util.Collections; | ||
32 | import java.util.List; | 34 | import java.util.List; |
33 | 35 | ||
34 | @ConditionalOnProperty(prefix = "audit_log", value = "enabled", havingValue = "false") | 36 | @ConditionalOnProperty(prefix = "audit_log", value = "enabled", havingValue = "false") |
@@ -36,26 +38,26 @@ public class DummyAuditLogServiceImpl implements AuditLogService { | @@ -36,26 +38,26 @@ public class DummyAuditLogServiceImpl implements AuditLogService { | ||
36 | 38 | ||
37 | @Override | 39 | @Override |
38 | public TimePageData<AuditLog> findAuditLogsByTenantIdAndCustomerId(TenantId tenantId, CustomerId customerId, TimePageLink pageLink) { | 40 | public TimePageData<AuditLog> findAuditLogsByTenantIdAndCustomerId(TenantId tenantId, CustomerId customerId, TimePageLink pageLink) { |
39 | - return null; | 41 | + return new TimePageData<>(null, pageLink); |
40 | } | 42 | } |
41 | 43 | ||
42 | @Override | 44 | @Override |
43 | public TimePageData<AuditLog> findAuditLogsByTenantIdAndUserId(TenantId tenantId, UserId userId, TimePageLink pageLink) { | 45 | public TimePageData<AuditLog> findAuditLogsByTenantIdAndUserId(TenantId tenantId, UserId userId, TimePageLink pageLink) { |
44 | - return null; | 46 | + return new TimePageData<>(null, pageLink); |
45 | } | 47 | } |
46 | 48 | ||
47 | @Override | 49 | @Override |
48 | public TimePageData<AuditLog> findAuditLogsByTenantIdAndEntityId(TenantId tenantId, EntityId entityId, TimePageLink pageLink) { | 50 | public TimePageData<AuditLog> findAuditLogsByTenantIdAndEntityId(TenantId tenantId, EntityId entityId, TimePageLink pageLink) { |
49 | - return null; | 51 | + return new TimePageData<>(null, pageLink); |
50 | } | 52 | } |
51 | 53 | ||
52 | @Override | 54 | @Override |
53 | public TimePageData<AuditLog> findAuditLogsByTenantId(TenantId tenantId, TimePageLink pageLink) { | 55 | public TimePageData<AuditLog> findAuditLogsByTenantId(TenantId tenantId, TimePageLink pageLink) { |
54 | - return null; | 56 | + return new TimePageData<>(null, pageLink); |
55 | } | 57 | } |
56 | 58 | ||
57 | @Override | 59 | @Override |
58 | public ListenableFuture<List<Void>> logEntityAction(User user, EntityId entityId, String entityName, CustomerId customerId, ActionType actionType, JsonNode actionData, ActionStatus actionStatus, String actionFailureDetails) { | 60 | public ListenableFuture<List<Void>> logEntityAction(User user, EntityId entityId, String entityName, CustomerId customerId, ActionType actionType, JsonNode actionData, ActionStatus actionStatus, String actionFailureDetails) { |
59 | - return null; | 61 | + return Futures.immediateFuture(Collections.emptyList()); |
60 | } | 62 | } |
61 | } | 63 | } |
@@ -10,6 +10,9 @@ zk.zk_dir=/thingsboard | @@ -10,6 +10,9 @@ zk.zk_dir=/thingsboard | ||
10 | updates.enabled=false | 10 | updates.enabled=false |
11 | 11 | ||
12 | audit_log.enabled=true | 12 | audit_log.enabled=true |
13 | +audit_log.exceptions.enabled=false | ||
14 | +audit_log.by_tenant_partitioning=MONTHS | ||
15 | +audit_log.default_query_period=30 | ||
13 | 16 | ||
14 | caching.specs.relations.timeToLiveInMinutes=1440 | 17 | caching.specs.relations.timeToLiveInMinutes=1440 |
15 | caching.specs.relations.maxSize=100000 | 18 | caching.specs.relations.maxSize=100000 |