Showing
6 changed files
with
93 additions
and
39 deletions
... | ... | @@ -15,9 +15,12 @@ |
15 | 15 | */ |
16 | 16 | package org.thingsboard.server.controller; |
17 | 17 | |
18 | +import com.fasterxml.jackson.databind.JsonNode; | |
19 | +import com.fasterxml.jackson.databind.ObjectMapper; | |
18 | 20 | import lombok.extern.slf4j.Slf4j; |
19 | 21 | import org.apache.commons.lang3.StringUtils; |
20 | 22 | import org.springframework.beans.factory.annotation.Autowired; |
23 | +import org.springframework.beans.factory.annotation.Value; | |
21 | 24 | import org.springframework.security.core.Authentication; |
22 | 25 | import org.springframework.security.core.context.SecurityContextHolder; |
23 | 26 | import org.springframework.web.bind.annotation.ExceptionHandler; |
... | ... | @@ -27,6 +30,8 @@ import org.thingsboard.server.common.data.alarm.Alarm; |
27 | 30 | import org.thingsboard.server.common.data.alarm.AlarmId; |
28 | 31 | import org.thingsboard.server.common.data.alarm.AlarmInfo; |
29 | 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 | 35 | import org.thingsboard.server.common.data.id.*; |
31 | 36 | import org.thingsboard.server.common.data.page.TextPageLink; |
32 | 37 | import org.thingsboard.server.common.data.page.TimePageLink; |
... | ... | @@ -73,6 +78,10 @@ public abstract class BaseController { |
73 | 78 | |
74 | 79 | public static final String INCORRECT_TENANT_ID = "Incorrect tenantId "; |
75 | 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 | 85 | @Autowired |
77 | 86 | private ThingsboardErrorResponseHandler errorResponseHandler; |
78 | 87 | |
... | ... | @@ -131,6 +140,11 @@ public abstract class BaseController { |
131 | 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 | 148 | private ThingsboardException handleException(Exception exception, boolean logException) { |
135 | 149 | if (logException) { |
136 | 150 | log.error("Error [{}]", exception.getMessage()); |
... | ... | @@ -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 | 200 | <T> T checkNotNull(T reference) throws ThingsboardException { |
157 | 201 | if (reference == null) { |
158 | 202 | throw new ThingsboardException("Requested item wasn't found!", ThingsboardErrorCode.ITEM_NOT_FOUND); |
... | ... | @@ -545,4 +589,24 @@ public abstract class BaseController { |
545 | 589 | serverPort); |
546 | 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 | 85 | savedDevice.getName(), |
86 | 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 | 90 | return savedDevice; |
100 | 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 | 102 | DeviceId deviceId = new DeviceId(toUUID(strDeviceId)); |
112 | 103 | Device device = checkDeviceId(deviceId); |
113 | 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 | 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 | 183 | Device device = checkDeviceId(deviceCredentials.getDeviceId()); |
201 | 184 | DeviceCredentials result = checkNotNull(deviceCredentialsService.updateDeviceCredentials(deviceCredentials)); |
202 | 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 | 187 | return result; |
213 | 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 | 286 | # Audit log parameters |
287 | 287 | audit_log: |
288 | 288 | # Enable/disable audit log functionality. |
289 | - enabled: "${AUDIT_LOG_ENABLED:true}" | |
\ No newline at end of file | ||
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}" | |
\ No newline at end of file | ... | ... |
... | ... | @@ -81,10 +81,13 @@ public class CassandraAuditLogDao extends CassandraAbstractSearchTimeDao<AuditLo |
81 | 81 | |
82 | 82 | protected ExecutorService readResultsProcessingExecutor; |
83 | 83 | |
84 | - @Value("${cassandra.query.ts_key_value_partitioning}") | |
84 | + @Value("${audit_log.by_tenant_partitioning}") | |
85 | 85 | private String partitioning; |
86 | 86 | private TsPartitionDate tsFormat; |
87 | 87 | |
88 | + @Value("${audit_log.default_query_period}") | |
89 | + private Integer defaultQueryPeriodInDays; | |
90 | + | |
88 | 91 | private PreparedStatement partitionInsertStmt; |
89 | 92 | private PreparedStatement saveByTenantStmt; |
90 | 93 | private PreparedStatement saveByTenantIdAndUserIdStmt; |
... | ... | @@ -304,7 +307,7 @@ public class CassandraAuditLogDao extends CassandraAbstractSearchTimeDao<AuditLo |
304 | 307 | if (pageLink.getStartTime() != null && pageLink.getStartTime() != 0) { |
305 | 308 | minPartition = toPartitionTs(pageLink.getStartTime()); |
306 | 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 | 313 | long maxPartition; | ... | ... |
... | ... | @@ -16,6 +16,7 @@ |
16 | 16 | package org.thingsboard.server.dao.audit; |
17 | 17 | |
18 | 18 | import com.fasterxml.jackson.databind.JsonNode; |
19 | +import com.google.common.util.concurrent.Futures; | |
19 | 20 | import com.google.common.util.concurrent.ListenableFuture; |
20 | 21 | import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; |
21 | 22 | import org.thingsboard.server.common.data.User; |
... | ... | @@ -29,6 +30,7 @@ import org.thingsboard.server.common.data.id.UserId; |
29 | 30 | import org.thingsboard.server.common.data.page.TimePageData; |
30 | 31 | import org.thingsboard.server.common.data.page.TimePageLink; |
31 | 32 | |
33 | +import java.util.Collections; | |
32 | 34 | import java.util.List; |
33 | 35 | |
34 | 36 | @ConditionalOnProperty(prefix = "audit_log", value = "enabled", havingValue = "false") |
... | ... | @@ -36,26 +38,26 @@ public class DummyAuditLogServiceImpl implements AuditLogService { |
36 | 38 | |
37 | 39 | @Override |
38 | 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 | 44 | @Override |
43 | 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 | 49 | @Override |
48 | 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 | 54 | @Override |
53 | 55 | public TimePageData<AuditLog> findAuditLogsByTenantId(TenantId tenantId, TimePageLink pageLink) { |
54 | - return null; | |
56 | + return new TimePageData<>(null, pageLink); | |
55 | 57 | } |
56 | 58 | |
57 | 59 | @Override |
58 | 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 | 10 | updates.enabled=false |
11 | 11 | |
12 | 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 | 17 | caching.specs.relations.timeToLiveInMinutes=1440 |
15 | 18 | caching.specs.relations.maxSize=100000 | ... | ... |