Commit 23b21b29128230673bc8cc89a1e32410d022174f

Authored by Volodymyr Babak
1 parent ead30d70

Added get by user id and customer id

Improved partitions query
@@ -18,20 +18,64 @@ package org.thingsboard.server.controller; @@ -18,20 +18,64 @@ package org.thingsboard.server.controller;
18 import org.springframework.security.access.prepost.PreAuthorize; 18 import org.springframework.security.access.prepost.PreAuthorize;
19 import org.springframework.web.bind.annotation.*; 19 import org.springframework.web.bind.annotation.*;
20 import org.thingsboard.server.common.data.audit.AuditLog; 20 import org.thingsboard.server.common.data.audit.AuditLog;
  21 +import org.thingsboard.server.common.data.id.CustomerId;
21 import org.thingsboard.server.common.data.id.EntityIdFactory; 22 import org.thingsboard.server.common.data.id.EntityIdFactory;
22 import org.thingsboard.server.common.data.id.TenantId; 23 import org.thingsboard.server.common.data.id.TenantId;
  24 +import org.thingsboard.server.common.data.id.UserId;
23 import org.thingsboard.server.common.data.page.TimePageData; 25 import org.thingsboard.server.common.data.page.TimePageData;
24 import org.thingsboard.server.common.data.page.TimePageLink; 26 import org.thingsboard.server.common.data.page.TimePageLink;
25 import org.thingsboard.server.exception.ThingsboardException; 27 import org.thingsboard.server.exception.ThingsboardException;
26 28
  29 +import java.util.UUID;
  30 +
27 @RestController 31 @RestController
28 @RequestMapping("/api") 32 @RequestMapping("/api")
29 public class AuditLogController extends BaseController { 33 public class AuditLogController extends BaseController {
30 34
31 @PreAuthorize("hasAuthority('TENANT_ADMIN')") 35 @PreAuthorize("hasAuthority('TENANT_ADMIN')")
32 - @RequestMapping(value = "/audit/logs/{entityType}/{entityId}", params = {"limit"}, method = RequestMethod.GET) 36 + @RequestMapping(value = "/audit/logs/customer/{customerId}", params = {"limit"}, method = RequestMethod.GET)
33 @ResponseBody 37 @ResponseBody
34 - public TimePageData<AuditLog> getAuditLogs( 38 + public TimePageData<AuditLog> getAuditLogsByCustomerId(
  39 + @PathVariable("customerId") String strCustomerId,
  40 + @RequestParam int limit,
  41 + @RequestParam(required = false) Long startTime,
  42 + @RequestParam(required = false) Long endTime,
  43 + @RequestParam(required = false, defaultValue = "false") boolean ascOrder,
  44 + @RequestParam(required = false) String offset) throws ThingsboardException {
  45 + try {
  46 + checkParameter("CustomerId", strCustomerId);
  47 + TenantId tenantId = getCurrentUser().getTenantId();
  48 + TimePageLink pageLink = createPageLink(limit, startTime, endTime, ascOrder, offset);
  49 + return checkNotNull(auditLogService.findAuditLogsByTenantIdAndCustomerId(tenantId, new CustomerId(UUID.fromString(strCustomerId)), pageLink));
  50 + } catch (Exception e) {
  51 + throw handleException(e);
  52 + }
  53 + }
  54 +
  55 + @PreAuthorize("hasAuthority('TENANT_ADMIN')")
  56 + @RequestMapping(value = "/audit/logs/user/{userId}", params = {"limit"}, method = RequestMethod.GET)
  57 + @ResponseBody
  58 + public TimePageData<AuditLog> getAuditLogsByUserId(
  59 + @PathVariable("userId") String strUserId,
  60 + @RequestParam int limit,
  61 + @RequestParam(required = false) Long startTime,
  62 + @RequestParam(required = false) Long endTime,
  63 + @RequestParam(required = false, defaultValue = "false") boolean ascOrder,
  64 + @RequestParam(required = false) String offset) throws ThingsboardException {
  65 + try {
  66 + checkParameter("UserId", strUserId);
  67 + TenantId tenantId = getCurrentUser().getTenantId();
  68 + TimePageLink pageLink = createPageLink(limit, startTime, endTime, ascOrder, offset);
  69 + return checkNotNull(auditLogService.findAuditLogsByTenantIdAndUserId(tenantId, new UserId(UUID.fromString(strUserId)), pageLink));
  70 + } catch (Exception e) {
  71 + throw handleException(e);
  72 + }
  73 + }
  74 +
  75 + @PreAuthorize("hasAuthority('TENANT_ADMIN')")
  76 + @RequestMapping(value = "/audit/logs/entity/{entityType}/{entityId}", params = {"limit"}, method = RequestMethod.GET)
  77 + @ResponseBody
  78 + public TimePageData<AuditLog> getAuditLogsByEntityId(
35 @PathVariable("entityType") String strEntityType, 79 @PathVariable("entityType") String strEntityType,
36 @PathVariable("entityId") String strEntityId, 80 @PathVariable("entityId") String strEntityId,
37 @RequestParam int limit, 81 @RequestParam int limit,
@@ -85,12 +85,16 @@ public class DeviceController extends BaseController { @@ -85,12 +85,16 @@ public class DeviceController extends BaseController {
85 savedDevice.getName(), 85 savedDevice.getName(),
86 savedDevice.getType()); 86 savedDevice.getType());
87 87
88 - // TODO: refactor to ANNOTATION usage  
89 - if (device.getId() == null) {  
90 - logDeviceAction(savedDevice, ActionType.ADDED);  
91 - } else {  
92 - logDeviceAction(savedDevice, ActionType.UPDATED);  
93 - } 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 +
94 98
95 return savedDevice; 99 return savedDevice;
96 } catch (Exception e) { 100 } catch (Exception e) {
@@ -107,8 +111,15 @@ public class DeviceController extends BaseController { @@ -107,8 +111,15 @@ public class DeviceController extends BaseController {
107 DeviceId deviceId = new DeviceId(toUUID(strDeviceId)); 111 DeviceId deviceId = new DeviceId(toUUID(strDeviceId));
108 Device device = checkDeviceId(deviceId); 112 Device device = checkDeviceId(deviceId);
109 deviceService.deleteDevice(deviceId); 113 deviceService.deleteDevice(deviceId);
110 - // TODO: refactor to ANNOTATION usage  
111 - logDeviceAction(device, ActionType.DELETED); 114 + auditLogService.logEntityAction(
  115 + getCurrentUser(),
  116 + device.getId(),
  117 + device.getName(),
  118 + device.getCustomerId(),
  119 + ActionType.DELETED,
  120 + null,
  121 + ActionStatus.SUCCESS,
  122 + null);
112 } catch (Exception e) { 123 } catch (Exception e) {
113 throw handleException(e); 124 throw handleException(e);
114 } 125 }
@@ -189,8 +200,15 @@ public class DeviceController extends BaseController { @@ -189,8 +200,15 @@ public class DeviceController extends BaseController {
189 Device device = checkDeviceId(deviceCredentials.getDeviceId()); 200 Device device = checkDeviceId(deviceCredentials.getDeviceId());
190 DeviceCredentials result = checkNotNull(deviceCredentialsService.updateDeviceCredentials(deviceCredentials)); 201 DeviceCredentials result = checkNotNull(deviceCredentialsService.updateDeviceCredentials(deviceCredentials));
191 actorService.onCredentialsUpdate(getCurrentUser().getTenantId(), deviceCredentials.getDeviceId()); 202 actorService.onCredentialsUpdate(getCurrentUser().getTenantId(), deviceCredentials.getDeviceId());
192 - // TODO: refactor to ANNOTATION usage  
193 - logDeviceAction(device, ActionType.CREDENTIALS_UPDATED); 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);
194 return result; 212 return result;
195 } catch (Exception e) { 213 } catch (Exception e) {
196 throw handleException(e); 214 throw handleException(e);
@@ -321,19 +339,4 @@ public class DeviceController extends BaseController { @@ -321,19 +339,4 @@ public class DeviceController extends BaseController {
321 throw handleException(e); 339 throw handleException(e);
322 } 340 }
323 } 341 }
324 -  
325 - // TODO: refactor to ANNOTATION usage  
326 - private void logDeviceAction(Device device, ActionType actionType) throws ThingsboardException {  
327 - auditLogService.logAction(  
328 - getCurrentUser().getTenantId(),  
329 - device.getId(),  
330 - device.getName(),  
331 - device.getCustomerId(),  
332 - getCurrentUser().getId(),  
333 - getCurrentUser().getName(),  
334 - actionType,  
335 - null,  
336 - ActionStatus.SUCCESS,  
337 - null);  
338 - }  
339 } 342 }
@@ -244,7 +244,6 @@ spring: @@ -244,7 +244,6 @@ spring:
244 username: "${SPRING_DATASOURCE_USERNAME:sa}" 244 username: "${SPRING_DATASOURCE_USERNAME:sa}"
245 password: "${SPRING_DATASOURCE_PASSWORD:}" 245 password: "${SPRING_DATASOURCE_PASSWORD:}"
246 246
247 -  
248 # PostgreSQL DAO Configuration 247 # PostgreSQL DAO Configuration
249 #spring: 248 #spring:
250 # data: 249 # data:
@@ -260,3 +259,8 @@ spring: @@ -260,3 +259,8 @@ spring:
260 # url: "${SPRING_DATASOURCE_URL:jdbc:postgresql://localhost:5432/thingsboard}" 259 # url: "${SPRING_DATASOURCE_URL:jdbc:postgresql://localhost:5432/thingsboard}"
261 # username: "${SPRING_DATASOURCE_USERNAME:postgres}" 260 # username: "${SPRING_DATASOURCE_USERNAME:postgres}"
262 # password: "${SPRING_DATASOURCE_PASSWORD:postgres}" 261 # password: "${SPRING_DATASOURCE_PASSWORD:postgres}"
  262 +
  263 +# Audit log parameters
  264 +audit_log:
  265 + # Enable/disable audit log functionality.
  266 + enabled: "${AUDIT_LOG_ENABLED:true}"
@@ -27,6 +27,7 @@ import org.thingsboard.server.common.data.audit.AuditLog; @@ -27,6 +27,7 @@ import org.thingsboard.server.common.data.audit.AuditLog;
27 import org.thingsboard.server.common.data.page.TimePageData; 27 import org.thingsboard.server.common.data.page.TimePageData;
28 import org.thingsboard.server.common.data.page.TimePageLink; 28 import org.thingsboard.server.common.data.page.TimePageLink;
29 import org.thingsboard.server.common.data.security.Authority; 29 import org.thingsboard.server.common.data.security.Authority;
  30 +import org.thingsboard.server.dao.model.ModelConstants;
30 31
31 import java.util.ArrayList; 32 import java.util.ArrayList;
32 import java.util.List; 33 import java.util.List;
@@ -36,6 +37,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers. @@ -36,6 +37,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
36 public abstract class BaseAuditLogControllerTest extends AbstractControllerTest { 37 public abstract class BaseAuditLogControllerTest extends AbstractControllerTest {
37 38
38 private Tenant savedTenant; 39 private Tenant savedTenant;
  40 + private User tenantAdmin;
39 41
40 @Before 42 @Before
41 public void beforeTest() throws Exception { 43 public void beforeTest() throws Exception {
@@ -46,14 +48,14 @@ public abstract class BaseAuditLogControllerTest extends AbstractControllerTest @@ -46,14 +48,14 @@ public abstract class BaseAuditLogControllerTest extends AbstractControllerTest
46 savedTenant = doPost("/api/tenant", tenant, Tenant.class); 48 savedTenant = doPost("/api/tenant", tenant, Tenant.class);
47 Assert.assertNotNull(savedTenant); 49 Assert.assertNotNull(savedTenant);
48 50
49 - User tenantAdmin = new User(); 51 + tenantAdmin = new User();
50 tenantAdmin.setAuthority(Authority.TENANT_ADMIN); 52 tenantAdmin.setAuthority(Authority.TENANT_ADMIN);
51 tenantAdmin.setTenantId(savedTenant.getId()); 53 tenantAdmin.setTenantId(savedTenant.getId());
52 tenantAdmin.setEmail("tenant2@thingsboard.org"); 54 tenantAdmin.setEmail("tenant2@thingsboard.org");
53 tenantAdmin.setFirstName("Joe"); 55 tenantAdmin.setFirstName("Joe");
54 tenantAdmin.setLastName("Downs"); 56 tenantAdmin.setLastName("Downs");
55 57
56 - createUserAndLogin(tenantAdmin, "testPassword1"); 58 + tenantAdmin = createUserAndLogin(tenantAdmin, "testPassword1");
57 } 59 }
58 60
59 @After 61 @After
@@ -65,7 +67,7 @@ public abstract class BaseAuditLogControllerTest extends AbstractControllerTest @@ -65,7 +67,7 @@ public abstract class BaseAuditLogControllerTest extends AbstractControllerTest
65 } 67 }
66 68
67 @Test 69 @Test
68 - public void testSaveDeviceAuditLogs() throws Exception { 70 + public void testAuditLogs() throws Exception {
69 for (int i = 0; i < 178; i++) { 71 for (int i = 0; i < 178; i++) {
70 Device device = new Device(); 72 Device device = new Device();
71 device.setName("Device" + i); 73 device.setName("Device" + i);
@@ -87,10 +89,38 @@ public abstract class BaseAuditLogControllerTest extends AbstractControllerTest @@ -87,10 +89,38 @@ public abstract class BaseAuditLogControllerTest extends AbstractControllerTest
87 } while (pageData.hasNext()); 89 } while (pageData.hasNext());
88 90
89 Assert.assertEquals(178, loadedAuditLogs.size()); 91 Assert.assertEquals(178, loadedAuditLogs.size());
  92 +
  93 + loadedAuditLogs = new ArrayList<>();
  94 + pageLink = new TimePageLink(23);
  95 + do {
  96 + pageData = doGetTypedWithTimePageLink("/api/audit/logs/customer/" + ModelConstants.NULL_UUID + "?",
  97 + new TypeReference<TimePageData<AuditLog>>() {
  98 + }, pageLink);
  99 + loadedAuditLogs.addAll(pageData.getData());
  100 + if (pageData.hasNext()) {
  101 + pageLink = pageData.getNextPageLink();
  102 + }
  103 + } while (pageData.hasNext());
  104 +
  105 + Assert.assertEquals(178, loadedAuditLogs.size());
  106 +
  107 + loadedAuditLogs = new ArrayList<>();
  108 + pageLink = new TimePageLink(23);
  109 + do {
  110 + pageData = doGetTypedWithTimePageLink("/api/audit/logs/user/" + tenantAdmin.getId().getId().toString() + "?",
  111 + new TypeReference<TimePageData<AuditLog>>() {
  112 + }, pageLink);
  113 + loadedAuditLogs.addAll(pageData.getData());
  114 + if (pageData.hasNext()) {
  115 + pageLink = pageData.getNextPageLink();
  116 + }
  117 + } while (pageData.hasNext());
  118 +
  119 + Assert.assertEquals(178, loadedAuditLogs.size());
90 } 120 }
91 121
92 @Test 122 @Test
93 - public void testUpdateDeviceAuditLogs() throws Exception { 123 + public void testAuditLogs_byTenantIdAndEntityId() throws Exception {
94 Device device = new Device(); 124 Device device = new Device();
95 device.setName("Device name"); 125 device.setName("Device name");
96 device.setType("default"); 126 device.setType("default");
@@ -104,7 +134,7 @@ public abstract class BaseAuditLogControllerTest extends AbstractControllerTest @@ -104,7 +134,7 @@ public abstract class BaseAuditLogControllerTest extends AbstractControllerTest
104 TimePageLink pageLink = new TimePageLink(23); 134 TimePageLink pageLink = new TimePageLink(23);
105 TimePageData<AuditLog> pageData; 135 TimePageData<AuditLog> pageData;
106 do { 136 do {
107 - pageData = doGetTypedWithTimePageLink("/api/audit/logs/DEVICE/" + savedDevice.getId().getId() + "?", 137 + pageData = doGetTypedWithTimePageLink("/api/audit/logs/entity/DEVICE/" + savedDevice.getId().getId() + "?",
108 new TypeReference<TimePageData<AuditLog>>() { 138 new TypeReference<TimePageData<AuditLog>>() {
109 }, pageLink); 139 }, pageLink);
110 loadedAuditLogs.addAll(pageData.getData()); 140 loadedAuditLogs.addAll(pageData.getData());
@@ -17,7 +17,9 @@ package org.thingsboard.server.dao.audit; @@ -17,7 +17,9 @@ package org.thingsboard.server.dao.audit;
17 17
18 import com.google.common.util.concurrent.ListenableFuture; 18 import com.google.common.util.concurrent.ListenableFuture;
19 import org.thingsboard.server.common.data.audit.AuditLog; 19 import org.thingsboard.server.common.data.audit.AuditLog;
  20 +import org.thingsboard.server.common.data.id.CustomerId;
20 import org.thingsboard.server.common.data.id.EntityId; 21 import org.thingsboard.server.common.data.id.EntityId;
  22 +import org.thingsboard.server.common.data.id.UserId;
21 import org.thingsboard.server.common.data.page.TimePageLink; 23 import org.thingsboard.server.common.data.page.TimePageLink;
22 24
23 import java.util.List; 25 import java.util.List;
@@ -29,9 +31,17 @@ public interface AuditLogDao { @@ -29,9 +31,17 @@ public interface AuditLogDao {
29 31
30 ListenableFuture<Void> saveByTenantIdAndEntityId(AuditLog auditLog); 32 ListenableFuture<Void> saveByTenantIdAndEntityId(AuditLog auditLog);
31 33
  34 + ListenableFuture<Void> saveByTenantIdAndCustomerId(AuditLog auditLog);
  35 +
  36 + ListenableFuture<Void> saveByTenantIdAndUserId(AuditLog auditLog);
  37 +
32 ListenableFuture<Void> savePartitionsByTenantId(AuditLog auditLog); 38 ListenableFuture<Void> savePartitionsByTenantId(AuditLog auditLog);
33 39
34 List<AuditLog> findAuditLogsByTenantIdAndEntityId(UUID tenantId, EntityId entityId, TimePageLink pageLink); 40 List<AuditLog> findAuditLogsByTenantIdAndEntityId(UUID tenantId, EntityId entityId, TimePageLink pageLink);
35 41
  42 + List<AuditLog> findAuditLogsByTenantIdAndCustomerId(UUID tenantId, CustomerId customerId, TimePageLink pageLink);
  43 +
  44 + List<AuditLog> findAuditLogsByTenantIdAndUserId(UUID tenantId, UserId userId, TimePageLink pageLink);
  45 +
36 List<AuditLog> findAuditLogsByTenantId(UUID tenantId, TimePageLink pageLink); 46 List<AuditLog> findAuditLogsByTenantId(UUID tenantId, TimePageLink pageLink);
37 } 47 }
  1 +/**
  2 + * Copyright © 2016-2017 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.dao.audit;
  17 +
  18 +import lombok.Getter;
  19 +import org.thingsboard.server.common.data.page.TimePageLink;
  20 +import org.thingsboard.server.dao.model.nosql.AuditLogEntity;
  21 +
  22 +import java.util.ArrayList;
  23 +import java.util.List;
  24 +import java.util.UUID;
  25 +
  26 +public class AuditLogQueryCursor {
  27 + @Getter
  28 + private final UUID tenantId;
  29 + @Getter
  30 + private final List<AuditLogEntity> data;
  31 + @Getter
  32 + private final TimePageLink pageLink;
  33 +
  34 + private final List<Long> partitions;
  35 +
  36 + private int partitionIndex;
  37 + private int currentLimit;
  38 +
  39 + public AuditLogQueryCursor(UUID tenantId, TimePageLink pageLink, List<Long> partitions) {
  40 + this.tenantId = tenantId;
  41 + this.partitions = partitions;
  42 + this.partitionIndex = partitions.size() - 1;
  43 + this.data = new ArrayList<>();
  44 + this.currentLimit = pageLink.getLimit();
  45 + this.pageLink = pageLink;
  46 + }
  47 +
  48 + public boolean hasNextPartition() {
  49 + return partitionIndex >= 0;
  50 + }
  51 +
  52 + public boolean isFull() {
  53 + return currentLimit <= 0;
  54 + }
  55 +
  56 + public long getNextPartition() {
  57 + long partition = partitions.get(partitionIndex);
  58 + partitionIndex--;
  59 + return partition;
  60 + }
  61 +
  62 + public int getCurrentLimit() {
  63 + return currentLimit;
  64 + }
  65 +
  66 + public void addData(List<AuditLogEntity> newData) {
  67 + currentLimit -= newData.size();
  68 + data.addAll(newData);
  69 + }
  70 +}
@@ -17,6 +17,7 @@ package org.thingsboard.server.dao.audit; @@ -17,6 +17,7 @@ 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.ListenableFuture; 19 import com.google.common.util.concurrent.ListenableFuture;
  20 +import org.thingsboard.server.common.data.User;
20 import org.thingsboard.server.common.data.audit.ActionStatus; 21 import org.thingsboard.server.common.data.audit.ActionStatus;
21 import org.thingsboard.server.common.data.audit.ActionType; 22 import org.thingsboard.server.common.data.audit.ActionType;
22 import org.thingsboard.server.common.data.audit.AuditLog; 23 import org.thingsboard.server.common.data.audit.AuditLog;
@@ -31,20 +32,21 @@ import java.util.List; @@ -31,20 +32,21 @@ import java.util.List;
31 32
32 public interface AuditLogService { 33 public interface AuditLogService {
33 34
  35 + TimePageData<AuditLog> findAuditLogsByTenantIdAndCustomerId(TenantId tenantId, CustomerId customerId, TimePageLink pageLink);
  36 +
  37 + TimePageData<AuditLog> findAuditLogsByTenantIdAndUserId(TenantId tenantId, UserId userId, TimePageLink pageLink);
  38 +
34 TimePageData<AuditLog> findAuditLogsByTenantIdAndEntityId(TenantId tenantId, EntityId entityId, TimePageLink pageLink); 39 TimePageData<AuditLog> findAuditLogsByTenantIdAndEntityId(TenantId tenantId, EntityId entityId, TimePageLink pageLink);
35 40
36 TimePageData<AuditLog> findAuditLogsByTenantId(TenantId tenantId, TimePageLink pageLink); 41 TimePageData<AuditLog> findAuditLogsByTenantId(TenantId tenantId, TimePageLink pageLink);
37 42
38 - ListenableFuture<List<Void>> logAction(TenantId tenantId,  
39 - EntityId entityId,  
40 - String entityName,  
41 - CustomerId customerId,  
42 - UserId userId,  
43 - String userName,  
44 - ActionType actionType,  
45 - JsonNode actionData,  
46 - ActionStatus actionStatus,  
47 - String actionFailureDetails);  
48 - 43 + ListenableFuture<List<Void>> logEntityAction(User user,
  44 + EntityId entityId,
  45 + String entityName,
  46 + CustomerId customerId,
  47 + ActionType actionType,
  48 + JsonNode actionData,
  49 + ActionStatus actionStatus,
  50 + String actionFailureDetails);
49 51
50 } 52 }
@@ -15,20 +15,20 @@ @@ -15,20 +15,20 @@
15 */ 15 */
16 package org.thingsboard.server.dao.audit; 16 package org.thingsboard.server.dao.audit;
17 17
  18 +import com.datastax.driver.core.utils.UUIDs;
18 import com.fasterxml.jackson.databind.JsonNode; 19 import com.fasterxml.jackson.databind.JsonNode;
19 import com.google.common.collect.Lists; 20 import com.google.common.collect.Lists;
20 import com.google.common.util.concurrent.Futures; 21 import com.google.common.util.concurrent.Futures;
21 import com.google.common.util.concurrent.ListenableFuture; 22 import com.google.common.util.concurrent.ListenableFuture;
22 import lombok.extern.slf4j.Slf4j; 23 import lombok.extern.slf4j.Slf4j;
23 import org.springframework.beans.factory.annotation.Autowired; 24 import org.springframework.beans.factory.annotation.Autowired;
  25 +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
24 import org.springframework.stereotype.Service; 26 import org.springframework.stereotype.Service;
  27 +import org.thingsboard.server.common.data.User;
25 import org.thingsboard.server.common.data.audit.ActionStatus; 28 import org.thingsboard.server.common.data.audit.ActionStatus;
26 import org.thingsboard.server.common.data.audit.ActionType; 29 import org.thingsboard.server.common.data.audit.ActionType;
27 import org.thingsboard.server.common.data.audit.AuditLog; 30 import org.thingsboard.server.common.data.audit.AuditLog;
28 -import org.thingsboard.server.common.data.id.CustomerId;  
29 -import org.thingsboard.server.common.data.id.EntityId;  
30 -import org.thingsboard.server.common.data.id.TenantId;  
31 -import org.thingsboard.server.common.data.id.UserId; 31 +import org.thingsboard.server.common.data.id.*;
32 import org.thingsboard.server.common.data.page.TimePageData; 32 import org.thingsboard.server.common.data.page.TimePageData;
33 import org.thingsboard.server.common.data.page.TimePageLink; 33 import org.thingsboard.server.common.data.page.TimePageLink;
34 import org.thingsboard.server.dao.exception.DataValidationException; 34 import org.thingsboard.server.dao.exception.DataValidationException;
@@ -41,6 +41,7 @@ import static org.thingsboard.server.dao.service.Validator.validateId; @@ -41,6 +41,7 @@ import static org.thingsboard.server.dao.service.Validator.validateId;
41 41
42 @Slf4j 42 @Slf4j
43 @Service 43 @Service
  44 +@ConditionalOnProperty(prefix = "audit_log", value = "enabled", havingValue = "true")
44 public class AuditLogServiceImpl implements AuditLogService { 45 public class AuditLogServiceImpl implements AuditLogService {
45 46
46 private static final String INCORRECT_TENANT_ID = "Incorrect tenantId "; 47 private static final String INCORRECT_TENANT_ID = "Incorrect tenantId ";
@@ -50,6 +51,24 @@ public class AuditLogServiceImpl implements AuditLogService { @@ -50,6 +51,24 @@ public class AuditLogServiceImpl implements AuditLogService {
50 private AuditLogDao auditLogDao; 51 private AuditLogDao auditLogDao;
51 52
52 @Override 53 @Override
  54 + public TimePageData<AuditLog> findAuditLogsByTenantIdAndCustomerId(TenantId tenantId, CustomerId customerId, TimePageLink pageLink) {
  55 + log.trace("Executing findAuditLogsByTenantIdAndCustomerId [{}], [{}], [{}]", tenantId, customerId, pageLink);
  56 + validateId(tenantId, INCORRECT_TENANT_ID + tenantId);
  57 + validateId(customerId, "Incorrect customerId " + customerId);
  58 + List<AuditLog> auditLogs = auditLogDao.findAuditLogsByTenantIdAndCustomerId(tenantId.getId(), customerId, pageLink);
  59 + return new TimePageData<>(auditLogs, pageLink);
  60 + }
  61 +
  62 + @Override
  63 + public TimePageData<AuditLog> findAuditLogsByTenantIdAndUserId(TenantId tenantId, UserId userId, TimePageLink pageLink) {
  64 + log.trace("Executing findAuditLogsByTenantIdAndUserId [{}], [{}], [{}]", tenantId, userId, pageLink);
  65 + validateId(tenantId, INCORRECT_TENANT_ID + tenantId);
  66 + validateId(userId, "Incorrect userId" + userId);
  67 + List<AuditLog> auditLogs = auditLogDao.findAuditLogsByTenantIdAndUserId(tenantId.getId(), userId, pageLink);
  68 + return new TimePageData<>(auditLogs, pageLink);
  69 + }
  70 +
  71 + @Override
53 public TimePageData<AuditLog> findAuditLogsByTenantIdAndEntityId(TenantId tenantId, EntityId entityId, TimePageLink pageLink) { 72 public TimePageData<AuditLog> findAuditLogsByTenantIdAndEntityId(TenantId tenantId, EntityId entityId, TimePageLink pageLink) {
54 log.trace("Executing findAuditLogsByTenantIdAndEntityId [{}], [{}], [{}]", tenantId, entityId, pageLink); 73 log.trace("Executing findAuditLogsByTenantIdAndEntityId [{}], [{}], [{}]", tenantId, entityId, pageLink);
55 validateId(tenantId, INCORRECT_TENANT_ID + tenantId); 74 validateId(tenantId, INCORRECT_TENANT_ID + tenantId);
@@ -66,6 +85,28 @@ public class AuditLogServiceImpl implements AuditLogService { @@ -66,6 +85,28 @@ public class AuditLogServiceImpl implements AuditLogService {
66 return new TimePageData<>(auditLogs, pageLink); 85 return new TimePageData<>(auditLogs, pageLink);
67 } 86 }
68 87
  88 + @Override
  89 + public ListenableFuture<List<Void>> logEntityAction(User user,
  90 + EntityId entityId,
  91 + String entityName,
  92 + CustomerId customerId,
  93 + ActionType actionType,
  94 + JsonNode actionData,
  95 + ActionStatus actionStatus,
  96 + String actionFailureDetails) {
  97 + return logAction(
  98 + user.getTenantId(),
  99 + entityId,
  100 + entityName,
  101 + customerId,
  102 + user.getId(),
  103 + user.getName(),
  104 + actionType,
  105 + actionData,
  106 + actionStatus,
  107 + actionFailureDetails);
  108 + }
  109 +
69 private AuditLog createAuditLogEntry(TenantId tenantId, 110 private AuditLog createAuditLogEntry(TenantId tenantId,
70 EntityId entityId, 111 EntityId entityId,
71 String entityName, 112 String entityName,
@@ -77,6 +118,7 @@ public class AuditLogServiceImpl implements AuditLogService { @@ -77,6 +118,7 @@ public class AuditLogServiceImpl implements AuditLogService {
77 ActionStatus actionStatus, 118 ActionStatus actionStatus,
78 String actionFailureDetails) { 119 String actionFailureDetails) {
79 AuditLog result = new AuditLog(); 120 AuditLog result = new AuditLog();
  121 + result.setId(new AuditLogId(UUIDs.timeBased()));
80 result.setTenantId(tenantId); 122 result.setTenantId(tenantId);
81 result.setEntityId(entityId); 123 result.setEntityId(entityId);
82 result.setEntityName(entityName); 124 result.setEntityName(entityName);
@@ -90,17 +132,16 @@ public class AuditLogServiceImpl implements AuditLogService { @@ -90,17 +132,16 @@ public class AuditLogServiceImpl implements AuditLogService {
90 return result; 132 return result;
91 } 133 }
92 134
93 - @Override  
94 - public ListenableFuture<List<Void>> logAction(TenantId tenantId,  
95 - EntityId entityId,  
96 - String entityName,  
97 - CustomerId customerId,  
98 - UserId userId,  
99 - String userName,  
100 - ActionType actionType,  
101 - JsonNode actionData,  
102 - ActionStatus actionStatus,  
103 - String actionFailureDetails) { 135 + private ListenableFuture<List<Void>> logAction(TenantId tenantId,
  136 + EntityId entityId,
  137 + String entityName,
  138 + CustomerId customerId,
  139 + UserId userId,
  140 + String userName,
  141 + ActionType actionType,
  142 + JsonNode actionData,
  143 + ActionStatus actionStatus,
  144 + String actionFailureDetails) {
104 AuditLog auditLogEntry = createAuditLogEntry(tenantId, entityId, entityName, customerId, userId, userName, 145 AuditLog auditLogEntry = createAuditLogEntry(tenantId, entityId, entityName, customerId, userId, userName,
105 actionType, actionData, actionStatus, actionFailureDetails); 146 actionType, actionData, actionStatus, actionFailureDetails);
106 log.trace("Executing logAction [{}]", auditLogEntry); 147 log.trace("Executing logAction [{}]", auditLogEntry);
@@ -109,6 +150,8 @@ public class AuditLogServiceImpl implements AuditLogService { @@ -109,6 +150,8 @@ public class AuditLogServiceImpl implements AuditLogService {
109 futures.add(auditLogDao.savePartitionsByTenantId(auditLogEntry)); 150 futures.add(auditLogDao.savePartitionsByTenantId(auditLogEntry));
110 futures.add(auditLogDao.saveByTenantId(auditLogEntry)); 151 futures.add(auditLogDao.saveByTenantId(auditLogEntry));
111 futures.add(auditLogDao.saveByTenantIdAndEntityId(auditLogEntry)); 152 futures.add(auditLogDao.saveByTenantIdAndEntityId(auditLogEntry));
  153 + futures.add(auditLogDao.saveByTenantIdAndCustomerId(auditLogEntry));
  154 + futures.add(auditLogDao.saveByTenantIdAndUserId(auditLogEntry));
112 return Futures.allAsList(futures); 155 return Futures.allAsList(futures);
113 } 156 }
114 157
@@ -19,7 +19,8 @@ import com.datastax.driver.core.BoundStatement; @@ -19,7 +19,8 @@ import com.datastax.driver.core.BoundStatement;
19 import com.datastax.driver.core.PreparedStatement; 19 import com.datastax.driver.core.PreparedStatement;
20 import com.datastax.driver.core.ResultSet; 20 import com.datastax.driver.core.ResultSet;
21 import com.datastax.driver.core.ResultSetFuture; 21 import com.datastax.driver.core.ResultSetFuture;
22 -import com.datastax.driver.core.utils.UUIDs; 22 +import com.datastax.driver.core.querybuilder.QueryBuilder;
  23 +import com.datastax.driver.core.querybuilder.Select;
23 import com.google.common.base.Function; 24 import com.google.common.base.Function;
24 import com.google.common.util.concurrent.Futures; 25 import com.google.common.util.concurrent.Futures;
25 import com.google.common.util.concurrent.ListenableFuture; 26 import com.google.common.util.concurrent.ListenableFuture;
@@ -29,8 +30,9 @@ import org.springframework.beans.factory.annotation.Value; @@ -29,8 +30,9 @@ import org.springframework.beans.factory.annotation.Value;
29 import org.springframework.core.env.Environment; 30 import org.springframework.core.env.Environment;
30 import org.springframework.stereotype.Component; 31 import org.springframework.stereotype.Component;
31 import org.thingsboard.server.common.data.audit.AuditLog; 32 import org.thingsboard.server.common.data.audit.AuditLog;
32 -import org.thingsboard.server.common.data.id.AuditLogId; 33 +import org.thingsboard.server.common.data.id.CustomerId;
33 import org.thingsboard.server.common.data.id.EntityId; 34 import org.thingsboard.server.common.data.id.EntityId;
  35 +import org.thingsboard.server.common.data.id.UserId;
34 import org.thingsboard.server.common.data.page.TimePageLink; 36 import org.thingsboard.server.common.data.page.TimePageLink;
35 import org.thingsboard.server.dao.DaoUtil; 37 import org.thingsboard.server.dao.DaoUtil;
36 import org.thingsboard.server.dao.model.ModelConstants; 38 import org.thingsboard.server.dao.model.ModelConstants;
@@ -52,6 +54,7 @@ import java.util.Optional; @@ -52,6 +54,7 @@ import java.util.Optional;
52 import java.util.UUID; 54 import java.util.UUID;
53 import java.util.concurrent.ExecutorService; 55 import java.util.concurrent.ExecutorService;
54 import java.util.concurrent.Executors; 56 import java.util.concurrent.Executors;
  57 +import java.util.stream.Collectors;
55 58
56 import static com.datastax.driver.core.querybuilder.QueryBuilder.eq; 59 import static com.datastax.driver.core.querybuilder.QueryBuilder.eq;
57 import static org.thingsboard.server.dao.model.ModelConstants.*; 60 import static org.thingsboard.server.dao.model.ModelConstants.*;
@@ -82,7 +85,11 @@ public class CassandraAuditLogDao extends CassandraAbstractSearchTimeDao<AuditLo @@ -82,7 +85,11 @@ public class CassandraAuditLogDao extends CassandraAbstractSearchTimeDao<AuditLo
82 private String partitioning; 85 private String partitioning;
83 private TsPartitionDate tsFormat; 86 private TsPartitionDate tsFormat;
84 87
85 - private PreparedStatement[] saveStmts; 88 + private PreparedStatement partitionInsertStmt;
  89 + private PreparedStatement saveByTenantStmt;
  90 + private PreparedStatement saveByTenantIdAndUserIdStmt;
  91 + private PreparedStatement saveByTenantIdAndEntityIdStmt;
  92 + private PreparedStatement saveByTenantIdAndCustomerIdStmt;
86 93
87 private boolean isInstall() { 94 private boolean isInstall() {
88 return environment.acceptsProfiles("install"); 95 return environment.acceptsProfiles("install");
@@ -123,11 +130,9 @@ public class CassandraAuditLogDao extends CassandraAbstractSearchTimeDao<AuditLo @@ -123,11 +130,9 @@ public class CassandraAuditLogDao extends CassandraAbstractSearchTimeDao<AuditLo
123 public ListenableFuture<Void> saveByTenantId(AuditLog auditLog) { 130 public ListenableFuture<Void> saveByTenantId(AuditLog auditLog) {
124 log.debug("Save saveByTenantId [{}] ", auditLog); 131 log.debug("Save saveByTenantId [{}] ", auditLog);
125 132
126 - AuditLogId auditLogId = new AuditLogId(UUIDs.timeBased());  
127 -  
128 long partition = toPartitionTs(LocalDate.now().atStartOfDay().toInstant(ZoneOffset.UTC).toEpochMilli()); 133 long partition = toPartitionTs(LocalDate.now().atStartOfDay().toInstant(ZoneOffset.UTC).toEpochMilli());
129 BoundStatement stmt = getSaveByTenantStmt().bind(); 134 BoundStatement stmt = getSaveByTenantStmt().bind();
130 - stmt.setUUID(0, auditLogId.getId()) 135 + stmt = stmt.setUUID(0, auditLog.getId().getId())
131 .setUUID(1, auditLog.getTenantId().getId()) 136 .setUUID(1, auditLog.getTenantId().getId())
132 .setUUID(2, auditLog.getEntityId().getId()) 137 .setUUID(2, auditLog.getEntityId().getId())
133 .setString(3, auditLog.getEntityId().getEntityType().name()) 138 .setString(3, auditLog.getEntityId().getEntityType().name())
@@ -140,18 +145,45 @@ public class CassandraAuditLogDao extends CassandraAbstractSearchTimeDao<AuditLo @@ -140,18 +145,45 @@ public class CassandraAuditLogDao extends CassandraAbstractSearchTimeDao<AuditLo
140 public ListenableFuture<Void> saveByTenantIdAndEntityId(AuditLog auditLog) { 145 public ListenableFuture<Void> saveByTenantIdAndEntityId(AuditLog auditLog) {
141 log.debug("Save saveByTenantIdAndEntityId [{}] ", auditLog); 146 log.debug("Save saveByTenantIdAndEntityId [{}] ", auditLog);
142 147
143 - AuditLogId auditLogId = new AuditLogId(UUIDs.timeBased());  
144 -  
145 BoundStatement stmt = getSaveByTenantIdAndEntityIdStmt().bind(); 148 BoundStatement stmt = getSaveByTenantIdAndEntityIdStmt().bind();
146 - stmt.setUUID(0, auditLogId.getId())  
147 - .setUUID(1, auditLog.getTenantId().getId())  
148 - .setUUID(2, auditLog.getEntityId().getId())  
149 - .setString(3, auditLog.getEntityId().getEntityType().name())  
150 - .setString(4, auditLog.getActionType().name()); 149 + stmt = setSaveStmtVariables(stmt, auditLog);
151 return getFuture(executeAsyncWrite(stmt), rs -> null); 150 return getFuture(executeAsyncWrite(stmt), rs -> null);
152 } 151 }
153 152
154 @Override 153 @Override
  154 + public ListenableFuture<Void> saveByTenantIdAndCustomerId(AuditLog auditLog) {
  155 + log.debug("Save saveByTenantIdAndCustomerId [{}] ", auditLog);
  156 +
  157 + BoundStatement stmt = getSaveByTenantIdAndCustomerIdStmt().bind();
  158 + stmt = setSaveStmtVariables(stmt, auditLog);
  159 + return getFuture(executeAsyncWrite(stmt), rs -> null);
  160 + }
  161 +
  162 + @Override
  163 + public ListenableFuture<Void> saveByTenantIdAndUserId(AuditLog auditLog) {
  164 + log.debug("Save saveByTenantIdAndUserId [{}] ", auditLog);
  165 +
  166 + BoundStatement stmt = getSaveByTenantIdAndUserIdStmt().bind();
  167 + stmt = setSaveStmtVariables(stmt, auditLog);
  168 + return getFuture(executeAsyncWrite(stmt), rs -> null);
  169 + }
  170 +
  171 + private BoundStatement setSaveStmtVariables(BoundStatement stmt, AuditLog auditLog) {
  172 + return stmt.setUUID(0, auditLog.getId().getId())
  173 + .setUUID(1, auditLog.getTenantId().getId())
  174 + .setUUID(2, auditLog.getCustomerId().getId())
  175 + .setUUID(3, auditLog.getEntityId().getId())
  176 + .setString(4, auditLog.getEntityId().getEntityType().name())
  177 + .setString(5, auditLog.getEntityName())
  178 + .setUUID(6, auditLog.getUserId().getId())
  179 + .setString(7, auditLog.getUserName())
  180 + .setString(8, auditLog.getActionType().name())
  181 + .setString(9, auditLog.getActionData() != null ? auditLog.getActionData().toString() : null)
  182 + .setString(10, auditLog.getActionStatus().name())
  183 + .setString(11, auditLog.getActionFailureDetails());
  184 + }
  185 +
  186 + @Override
155 public ListenableFuture<Void> savePartitionsByTenantId(AuditLog auditLog) { 187 public ListenableFuture<Void> savePartitionsByTenantId(AuditLog auditLog) {
156 log.debug("Save savePartitionsByTenantId [{}] ", auditLog); 188 log.debug("Save savePartitionsByTenantId [{}] ", auditLog);
157 189
@@ -163,35 +195,66 @@ public class CassandraAuditLogDao extends CassandraAbstractSearchTimeDao<AuditLo @@ -163,35 +195,66 @@ public class CassandraAuditLogDao extends CassandraAbstractSearchTimeDao<AuditLo
163 return getFuture(executeAsyncWrite(stmt), rs -> null); 195 return getFuture(executeAsyncWrite(stmt), rs -> null);
164 } 196 }
165 197
166 - private PreparedStatement getPartitionInsertStmt() {  
167 - // TODO: ADD CACHE LOGIC  
168 - return getSession().prepare(INSERT_INTO + ModelConstants.AUDIT_LOG_BY_TENANT_ID_PARTITIONS_CF +  
169 - "(" + ModelConstants.AUDIT_LOG_TENANT_ID_PROPERTY +  
170 - "," + ModelConstants.AUDIT_LOG_PARTITION_PROPERTY + ")" +  
171 - " VALUES(?, ?)"); 198 + private PreparedStatement getSaveByTenantIdAndEntityIdStmt() {
  199 + if (saveByTenantIdAndEntityIdStmt == null) {
  200 + saveByTenantIdAndEntityIdStmt = getSaveByTenantIdAndCFName(ModelConstants.AUDIT_LOG_BY_ENTITY_ID_CF);
  201 + }
  202 + return saveByTenantIdAndEntityIdStmt;
172 } 203 }
173 204
174 - private PreparedStatement getSaveByTenantIdAndEntityIdStmt() {  
175 - // TODO: ADD CACHE LOGIC  
176 - return getSession().prepare(INSERT_INTO + ModelConstants.AUDIT_LOG_BY_ENTITY_ID_CF +  
177 - "(" + ModelConstants.AUDIT_LOG_ID_PROPERTY +  
178 - "," + ModelConstants.AUDIT_LOG_TENANT_ID_PROPERTY +  
179 - "," + ModelConstants.AUDIT_LOG_ENTITY_ID_PROPERTY +  
180 - "," + ModelConstants.AUDIT_LOG_ENTITY_TYPE_PROPERTY +  
181 - "," + ModelConstants.AUDIT_LOG_ACTION_TYPE_PROPERTY + ")" +  
182 - " VALUES(?, ?, ?, ?, ?)"); 205 + private PreparedStatement getSaveByTenantIdAndCustomerIdStmt() {
  206 + if (saveByTenantIdAndCustomerIdStmt == null) {
  207 + saveByTenantIdAndCustomerIdStmt = getSaveByTenantIdAndCFName(ModelConstants.AUDIT_LOG_BY_CUSTOMER_ID_CF);
  208 + }
  209 + return saveByTenantIdAndCustomerIdStmt;
183 } 210 }
184 211
185 - private PreparedStatement getSaveByTenantStmt() {  
186 - // TODO: ADD CACHE LOGIC  
187 - return getSession().prepare(INSERT_INTO + ModelConstants.AUDIT_LOG_BY_TENANT_ID_CF + 212 + private PreparedStatement getSaveByTenantIdAndUserIdStmt() {
  213 + if (saveByTenantIdAndUserIdStmt == null) {
  214 + saveByTenantIdAndUserIdStmt = getSaveByTenantIdAndCFName(ModelConstants.AUDIT_LOG_BY_USER_ID_CF);
  215 + }
  216 + return saveByTenantIdAndUserIdStmt;
  217 + }
  218 +
  219 + private PreparedStatement getSaveByTenantIdAndCFName(String cfName) {
  220 + return getSession().prepare(INSERT_INTO + cfName +
188 "(" + ModelConstants.AUDIT_LOG_ID_PROPERTY + 221 "(" + ModelConstants.AUDIT_LOG_ID_PROPERTY +
189 "," + ModelConstants.AUDIT_LOG_TENANT_ID_PROPERTY + 222 "," + ModelConstants.AUDIT_LOG_TENANT_ID_PROPERTY +
  223 + "," + ModelConstants.AUDIT_LOG_CUSTOMER_ID_PROPERTY +
190 "," + ModelConstants.AUDIT_LOG_ENTITY_ID_PROPERTY + 224 "," + ModelConstants.AUDIT_LOG_ENTITY_ID_PROPERTY +
191 "," + ModelConstants.AUDIT_LOG_ENTITY_TYPE_PROPERTY + 225 "," + ModelConstants.AUDIT_LOG_ENTITY_TYPE_PROPERTY +
  226 + "," + ModelConstants.AUDIT_LOG_ENTITY_NAME_PROPERTY +
  227 + "," + ModelConstants.AUDIT_LOG_USER_ID_PROPERTY +
  228 + "," + ModelConstants.AUDIT_LOG_USER_NAME_PROPERTY +
192 "," + ModelConstants.AUDIT_LOG_ACTION_TYPE_PROPERTY + 229 "," + ModelConstants.AUDIT_LOG_ACTION_TYPE_PROPERTY +
193 - "," + ModelConstants.AUDIT_LOG_PARTITION_PROPERTY + ")" +  
194 - " VALUES(?, ?, ?, ?, ?, ?)"); 230 + "," + ModelConstants.AUDIT_LOG_ACTION_DATA_PROPERTY +
  231 + "," + ModelConstants.AUDIT_LOG_ACTION_STATUS_PROPERTY +
  232 + "," + ModelConstants.AUDIT_LOG_ACTION_FAILURE_DETAILS_PROPERTY + ")" +
  233 + " VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)");
  234 + }
  235 +
  236 + private PreparedStatement getPartitionInsertStmt() {
  237 + if (partitionInsertStmt == null) {
  238 + partitionInsertStmt = getSession().prepare(INSERT_INTO + ModelConstants.AUDIT_LOG_BY_TENANT_ID_PARTITIONS_CF +
  239 + "(" + ModelConstants.AUDIT_LOG_TENANT_ID_PROPERTY +
  240 + "," + ModelConstants.AUDIT_LOG_PARTITION_PROPERTY + ")" +
  241 + " VALUES(?, ?)");
  242 + }
  243 + return partitionInsertStmt;
  244 + }
  245 +
  246 + private PreparedStatement getSaveByTenantStmt() {
  247 + if (saveByTenantStmt == null) {
  248 + saveByTenantStmt = getSession().prepare(INSERT_INTO + ModelConstants.AUDIT_LOG_BY_TENANT_ID_CF +
  249 + "(" + ModelConstants.AUDIT_LOG_ID_PROPERTY +
  250 + "," + ModelConstants.AUDIT_LOG_TENANT_ID_PROPERTY +
  251 + "," + ModelConstants.AUDIT_LOG_ENTITY_ID_PROPERTY +
  252 + "," + ModelConstants.AUDIT_LOG_ENTITY_TYPE_PROPERTY +
  253 + "," + ModelConstants.AUDIT_LOG_ACTION_TYPE_PROPERTY +
  254 + "," + ModelConstants.AUDIT_LOG_PARTITION_PROPERTY + ")" +
  255 + " VALUES(?, ?, ?, ?, ?, ?)");
  256 + }
  257 + return saveByTenantStmt;
195 } 258 }
196 259
197 private long toPartitionTs(long ts) { 260 private long toPartitionTs(long ts) {
@@ -199,7 +262,6 @@ public class CassandraAuditLogDao extends CassandraAbstractSearchTimeDao<AuditLo @@ -199,7 +262,6 @@ public class CassandraAuditLogDao extends CassandraAbstractSearchTimeDao<AuditLo
199 return tsFormat.truncatedTo(time).toInstant(ZoneOffset.UTC).toEpochMilli(); 262 return tsFormat.truncatedTo(time).toInstant(ZoneOffset.UTC).toEpochMilli();
200 } 263 }
201 264
202 -  
203 @Override 265 @Override
204 public List<AuditLog> findAuditLogsByTenantIdAndEntityId(UUID tenantId, EntityId entityId, TimePageLink pageLink) { 266 public List<AuditLog> findAuditLogsByTenantIdAndEntityId(UUID tenantId, EntityId entityId, TimePageLink pageLink) {
205 log.trace("Try to find audit logs by tenant [{}], entity [{}] and pageLink [{}]", tenantId, entityId, pageLink); 267 log.trace("Try to find audit logs by tenant [{}], entity [{}] and pageLink [{}]", tenantId, entityId, pageLink);
@@ -213,30 +275,75 @@ public class CassandraAuditLogDao extends CassandraAbstractSearchTimeDao<AuditLo @@ -213,30 +275,75 @@ public class CassandraAuditLogDao extends CassandraAbstractSearchTimeDao<AuditLo
213 } 275 }
214 276
215 @Override 277 @Override
  278 + public List<AuditLog> findAuditLogsByTenantIdAndCustomerId(UUID tenantId, CustomerId customerId, TimePageLink pageLink) {
  279 + log.trace("Try to find audit logs by tenant [{}], customer [{}] and pageLink [{}]", tenantId, customerId, pageLink);
  280 + List<AuditLogEntity> entities = findPageWithTimeSearch(AUDIT_LOG_BY_CUSTOMER_ID_CF,
  281 + Arrays.asList(eq(ModelConstants.AUDIT_LOG_TENANT_ID_PROPERTY, tenantId),
  282 + eq(ModelConstants.AUDIT_LOG_CUSTOMER_ID_PROPERTY, customerId.getId())),
  283 + pageLink);
  284 + log.trace("Found audit logs by tenant [{}], customer [{}] and pageLink [{}]", tenantId, customerId, pageLink);
  285 + return DaoUtil.convertDataList(entities);
  286 + }
  287 +
  288 + @Override
  289 + public List<AuditLog> findAuditLogsByTenantIdAndUserId(UUID tenantId, UserId userId, TimePageLink pageLink) {
  290 + log.trace("Try to find audit logs by tenant [{}], user [{}] and pageLink [{}]", tenantId, userId, pageLink);
  291 + List<AuditLogEntity> entities = findPageWithTimeSearch(AUDIT_LOG_BY_USER_ID_CF,
  292 + Arrays.asList(eq(ModelConstants.AUDIT_LOG_TENANT_ID_PROPERTY, tenantId),
  293 + eq(ModelConstants.AUDIT_LOG_USER_ID_PROPERTY, userId.getId())),
  294 + pageLink);
  295 + log.trace("Found audit logs by tenant [{}], user [{}] and pageLink [{}]", tenantId, userId, pageLink);
  296 + return DaoUtil.convertDataList(entities);
  297 + }
  298 +
  299 + @Override
216 public List<AuditLog> findAuditLogsByTenantId(UUID tenantId, TimePageLink pageLink) { 300 public List<AuditLog> findAuditLogsByTenantId(UUID tenantId, TimePageLink pageLink) {
217 log.trace("Try to find audit logs by tenant [{}] and pageLink [{}]", tenantId, pageLink); 301 log.trace("Try to find audit logs by tenant [{}] and pageLink [{}]", tenantId, pageLink);
218 302
219 - // TODO: ADD AUDIT LOG PARTITION CURSOR LOGIC  
220 -  
221 long minPartition; 303 long minPartition;
222 - long maxPartition;  
223 -  
224 if (pageLink.getStartTime() != null && pageLink.getStartTime() != 0) { 304 if (pageLink.getStartTime() != null && pageLink.getStartTime() != 0) {
225 minPartition = toPartitionTs(pageLink.getStartTime()); 305 minPartition = toPartitionTs(pageLink.getStartTime());
226 } else { 306 } else {
227 minPartition = toPartitionTs(LocalDate.now().minusMonths(1).atStartOfDay().toInstant(ZoneOffset.UTC).toEpochMilli()); 307 minPartition = toPartitionTs(LocalDate.now().minusMonths(1).atStartOfDay().toInstant(ZoneOffset.UTC).toEpochMilli());
228 } 308 }
229 309
  310 + long maxPartition;
230 if (pageLink.getEndTime() != null && pageLink.getEndTime() != 0) { 311 if (pageLink.getEndTime() != null && pageLink.getEndTime() != 0) {
231 maxPartition = toPartitionTs(pageLink.getEndTime()); 312 maxPartition = toPartitionTs(pageLink.getEndTime());
232 } else { 313 } else {
233 maxPartition = toPartitionTs(LocalDate.now().atStartOfDay().toInstant(ZoneOffset.UTC).toEpochMilli()); 314 maxPartition = toPartitionTs(LocalDate.now().atStartOfDay().toInstant(ZoneOffset.UTC).toEpochMilli());
234 } 315 }
235 - List<AuditLogEntity> entities = findPageWithTimeSearch(AUDIT_LOG_BY_TENANT_ID_CF,  
236 - Arrays.asList(eq(ModelConstants.AUDIT_LOG_TENANT_ID_PROPERTY, tenantId),  
237 - eq(ModelConstants.AUDIT_LOG_PARTITION_PROPERTY, maxPartition)),  
238 - pageLink); 316 +
  317 + List<Long> partitions = fetchPartitions(tenantId, minPartition, maxPartition)
  318 + .all()
  319 + .stream()
  320 + .map(row -> row.getLong(ModelConstants.PARTITION_COLUMN))
  321 + .collect(Collectors.toList());
  322 +
  323 + AuditLogQueryCursor cursor = new AuditLogQueryCursor(tenantId, pageLink, partitions);
  324 + List<AuditLogEntity> entities = fetchSequentiallyWithLimit(cursor);
239 log.trace("Found audit logs by tenant [{}] and pageLink [{}]", tenantId, pageLink); 325 log.trace("Found audit logs by tenant [{}] and pageLink [{}]", tenantId, pageLink);
240 return DaoUtil.convertDataList(entities); 326 return DaoUtil.convertDataList(entities);
241 } 327 }
  328 +
  329 + private List<AuditLogEntity> fetchSequentiallyWithLimit(AuditLogQueryCursor cursor) {
  330 + if (cursor.isFull() || !cursor.hasNextPartition()) {
  331 + return cursor.getData();
  332 + } else {
  333 + cursor.addData(findPageWithTimeSearch(AUDIT_LOG_BY_TENANT_ID_CF,
  334 + Arrays.asList(eq(ModelConstants.AUDIT_LOG_TENANT_ID_PROPERTY, cursor.getTenantId()),
  335 + eq(ModelConstants.AUDIT_LOG_PARTITION_PROPERTY, cursor.getNextPartition())),
  336 + cursor.getPageLink()));
  337 + return fetchSequentiallyWithLimit(cursor);
  338 + }
  339 + }
  340 +
  341 + private ResultSet fetchPartitions(UUID tenantId, long minPartition, long maxPartition) {
  342 + Select.Where select = QueryBuilder.select(ModelConstants.AUDIT_LOG_PARTITION_PROPERTY).from(ModelConstants.AUDIT_LOG_BY_TENANT_ID_PARTITIONS_CF)
  343 + .where(eq(ModelConstants.AUDIT_LOG_TENANT_ID_PROPERTY, tenantId));
  344 + select.and(QueryBuilder.gte(ModelConstants.PARTITION_COLUMN, minPartition));
  345 + select.and(QueryBuilder.lte(ModelConstants.PARTITION_COLUMN, maxPartition));
  346 + return getSession().execute(select);
  347 + }
  348 +
242 } 349 }
  1 +/**
  2 + * Copyright © 2016-2017 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.dao.audit;
  17 +
  18 +import com.fasterxml.jackson.databind.JsonNode;
  19 +import com.google.common.util.concurrent.ListenableFuture;
  20 +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
  21 +import org.thingsboard.server.common.data.User;
  22 +import org.thingsboard.server.common.data.audit.ActionStatus;
  23 +import org.thingsboard.server.common.data.audit.ActionType;
  24 +import org.thingsboard.server.common.data.audit.AuditLog;
  25 +import org.thingsboard.server.common.data.id.CustomerId;
  26 +import org.thingsboard.server.common.data.id.EntityId;
  27 +import org.thingsboard.server.common.data.id.TenantId;
  28 +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.TimePageLink;
  31 +
  32 +import java.util.List;
  33 +
  34 +@ConditionalOnProperty(prefix = "audit_log", value = "enabled", havingValue = "false")
  35 +public class DummyAuditLogServiceImpl implements AuditLogService {
  36 +
  37 + @Override
  38 + public TimePageData<AuditLog> findAuditLogsByTenantIdAndCustomerId(TenantId tenantId, CustomerId customerId, TimePageLink pageLink) {
  39 + return null;
  40 + }
  41 +
  42 + @Override
  43 + public TimePageData<AuditLog> findAuditLogsByTenantIdAndUserId(TenantId tenantId, UserId userId, TimePageLink pageLink) {
  44 + return null;
  45 + }
  46 +
  47 + @Override
  48 + public TimePageData<AuditLog> findAuditLogsByTenantIdAndEntityId(TenantId tenantId, EntityId entityId, TimePageLink pageLink) {
  49 + return null;
  50 + }
  51 +
  52 + @Override
  53 + public TimePageData<AuditLog> findAuditLogsByTenantId(TenantId tenantId, TimePageLink pageLink) {
  54 + return null;
  55 + }
  56 +
  57 + @Override
  58 + public ListenableFuture<List<Void>> logEntityAction(User user, EntityId entityId, String entityName, CustomerId customerId, ActionType actionType, JsonNode actionData, ActionStatus actionStatus, String actionFailureDetails) {
  59 + return null;
  60 + }
  61 +}
@@ -147,6 +147,8 @@ public class ModelConstants { @@ -147,6 +147,8 @@ public class ModelConstants {
147 public static final String AUDIT_LOG_COLUMN_FAMILY_NAME = "audit_log"; 147 public static final String AUDIT_LOG_COLUMN_FAMILY_NAME = "audit_log";
148 148
149 public static final String AUDIT_LOG_BY_ENTITY_ID_CF = "audit_log_by_entity_id"; 149 public static final String AUDIT_LOG_BY_ENTITY_ID_CF = "audit_log_by_entity_id";
  150 + public static final String AUDIT_LOG_BY_CUSTOMER_ID_CF = "audit_log_by_customer_id";
  151 + public static final String AUDIT_LOG_BY_USER_ID_CF = "audit_log_by_user_id";
150 public static final String AUDIT_LOG_BY_TENANT_ID_CF = "audit_log_by_tenant_id"; 152 public static final String AUDIT_LOG_BY_TENANT_ID_CF = "audit_log_by_tenant_id";
151 public static final String AUDIT_LOG_BY_TENANT_ID_PARTITIONS_CF = "audit_log_by_tenant_id_partitions"; 153 public static final String AUDIT_LOG_BY_TENANT_ID_PARTITIONS_CF = "audit_log_by_tenant_id_partitions";
152 154
@@ -41,4 +41,20 @@ public interface AuditLogRepository extends CrudRepository<AuditLogEntity, Strin @@ -41,4 +41,20 @@ public interface AuditLogRepository extends CrudRepository<AuditLogEntity, Strin
41 @Param("entityType") EntityType entityType, 41 @Param("entityType") EntityType entityType,
42 @Param("idOffset") String idOffset, 42 @Param("idOffset") String idOffset,
43 Pageable pageable); 43 Pageable pageable);
  44 +
  45 + @Query("SELECT al FROM AuditLogEntity al WHERE al.tenantId = :tenantId " +
  46 + "AND al.customerId = :customerId " +
  47 + "AND al.id > :idOffset ORDER BY al.id")
  48 + List<AuditLogEntity> findByTenantIdAndCustomerId(@Param("tenantId") String tenantId,
  49 + @Param("customerId") String customerId,
  50 + @Param("idOffset") String idOffset,
  51 + Pageable pageable);
  52 +
  53 + @Query("SELECT al FROM AuditLogEntity al WHERE al.tenantId = :tenantId " +
  54 + "AND al.userId = :userId " +
  55 + "AND al.id > :idOffset ORDER BY al.id")
  56 + List<AuditLogEntity> findByTenantIdAndUserId(@Param("tenantId") String tenantId,
  57 + @Param("userId") String userId,
  58 + @Param("idOffset") String idOffset,
  59 + Pageable pageable);
44 } 60 }
@@ -23,7 +23,9 @@ import org.springframework.data.domain.PageRequest; @@ -23,7 +23,9 @@ import org.springframework.data.domain.PageRequest;
23 import org.springframework.data.repository.CrudRepository; 23 import org.springframework.data.repository.CrudRepository;
24 import org.springframework.stereotype.Component; 24 import org.springframework.stereotype.Component;
25 import org.thingsboard.server.common.data.audit.AuditLog; 25 import org.thingsboard.server.common.data.audit.AuditLog;
  26 +import org.thingsboard.server.common.data.id.CustomerId;
26 import org.thingsboard.server.common.data.id.EntityId; 27 import org.thingsboard.server.common.data.id.EntityId;
  28 +import org.thingsboard.server.common.data.id.UserId;
27 import org.thingsboard.server.common.data.page.TimePageLink; 29 import org.thingsboard.server.common.data.page.TimePageLink;
28 import org.thingsboard.server.dao.DaoUtil; 30 import org.thingsboard.server.dao.DaoUtil;
29 import org.thingsboard.server.dao.audit.AuditLogDao; 31 import org.thingsboard.server.dao.audit.AuditLogDao;
@@ -77,6 +79,16 @@ public class JpaAuditLogDao extends JpaAbstractDao<AuditLogEntity, AuditLog> imp @@ -77,6 +79,16 @@ public class JpaAuditLogDao extends JpaAbstractDao<AuditLogEntity, AuditLog> imp
77 } 79 }
78 80
79 @Override 81 @Override
  82 + public ListenableFuture<Void> saveByTenantIdAndCustomerId(AuditLog auditLog) {
  83 + return insertService.submit(() -> null);
  84 + }
  85 +
  86 + @Override
  87 + public ListenableFuture<Void> saveByTenantIdAndUserId(AuditLog auditLog) {
  88 + return insertService.submit(() -> null);
  89 + }
  90 +
  91 + @Override
80 public ListenableFuture<Void> savePartitionsByTenantId(AuditLog auditLog) { 92 public ListenableFuture<Void> savePartitionsByTenantId(AuditLog auditLog) {
81 return insertService.submit(() -> null); 93 return insertService.submit(() -> null);
82 } 94 }
@@ -93,6 +105,26 @@ public class JpaAuditLogDao extends JpaAbstractDao<AuditLogEntity, AuditLog> imp @@ -93,6 +105,26 @@ public class JpaAuditLogDao extends JpaAbstractDao<AuditLogEntity, AuditLog> imp
93 } 105 }
94 106
95 @Override 107 @Override
  108 + public List<AuditLog> findAuditLogsByTenantIdAndCustomerId(UUID tenantId, CustomerId customerId, TimePageLink pageLink) {
  109 + return DaoUtil.convertDataList(
  110 + auditLogRepository.findByTenantIdAndCustomerId(
  111 + fromTimeUUID(tenantId),
  112 + fromTimeUUID(customerId.getId()),
  113 + pageLink.getIdOffset() == null ? NULL_UUID_STR : fromTimeUUID(pageLink.getIdOffset()),
  114 + new PageRequest(0, pageLink.getLimit())));
  115 + }
  116 +
  117 + @Override
  118 + public List<AuditLog> findAuditLogsByTenantIdAndUserId(UUID tenantId, UserId userId, TimePageLink pageLink) {
  119 + return DaoUtil.convertDataList(
  120 + auditLogRepository.findByTenantIdAndUserId(
  121 + fromTimeUUID(tenantId),
  122 + fromTimeUUID(userId.getId()),
  123 + pageLink.getIdOffset() == null ? NULL_UUID_STR : fromTimeUUID(pageLink.getIdOffset()),
  124 + new PageRequest(0, pageLink.getLimit())));
  125 + }
  126 +
  127 + @Override
96 public List<AuditLog> findAuditLogsByTenantId(UUID tenantId, TimePageLink pageLink) { 128 public List<AuditLog> findAuditLogsByTenantId(UUID tenantId, TimePageLink pageLink) {
97 return DaoUtil.convertDataList( 129 return DaoUtil.convertDataList(
98 auditLogRepository.findByTenantId( 130 auditLogRepository.findByTenantId(
@@ -566,6 +566,40 @@ CREATE TABLE IF NOT EXISTS thingsboard.audit_log_by_entity_id ( @@ -566,6 +566,40 @@ CREATE TABLE IF NOT EXISTS thingsboard.audit_log_by_entity_id (
566 PRIMARY KEY ((tenant_id, entity_id, entity_type), id) 566 PRIMARY KEY ((tenant_id, entity_id, entity_type), id)
567 ); 567 );
568 568
  569 +CREATE TABLE IF NOT EXISTS thingsboard.audit_log_by_customer_id (
  570 + tenant_id timeuuid,
  571 + id timeuuid,
  572 + customer_id timeuuid,
  573 + entity_id timeuuid,
  574 + entity_type text,
  575 + entity_name text,
  576 + user_id timeuuid,
  577 + user_name text,
  578 + action_type text,
  579 + action_data text,
  580 + action_status text,
  581 + action_failure_details text,
  582 + PRIMARY KEY ((tenant_id, customer_id), id)
  583 +);
  584 +
  585 +CREATE TABLE IF NOT EXISTS thingsboard.audit_log_by_user_id (
  586 + tenant_id timeuuid,
  587 + id timeuuid,
  588 + customer_id timeuuid,
  589 + entity_id timeuuid,
  590 + entity_type text,
  591 + entity_name text,
  592 + user_id timeuuid,
  593 + user_name text,
  594 + action_type text,
  595 + action_data text,
  596 + action_status text,
  597 + action_failure_details text,
  598 + PRIMARY KEY ((tenant_id, user_id), id)
  599 +);
  600 +
  601 +
  602 +
569 CREATE TABLE IF NOT EXISTS thingsboard.audit_log_by_tenant_id ( 603 CREATE TABLE IF NOT EXISTS thingsboard.audit_log_by_tenant_id (
570 tenant_id timeuuid, 604 tenant_id timeuuid,
571 id timeuuid, 605 id timeuuid,
@@ -7,4 +7,6 @@ zk.enabled=false @@ -7,4 +7,6 @@ zk.enabled=false
7 zk.url=localhost:2181 7 zk.url=localhost:2181
8 zk.zk_dir=/thingsboard 8 zk.zk_dir=/thingsboard
9 9
10 -updates.enabled=false  
  10 +updates.enabled=false
  11 +
  12 +audit_log.enabled=true