Added get by user id and customer id

Improved partitions query
This commit is contained in:
Volodymyr Babak 2018-02-14 19:51:23 +02:00
parent ead30d700b
commit 23b21b2912
15 changed files with 562 additions and 102 deletions

View File

@ -18,20 +18,64 @@ package org.thingsboard.server.controller;
import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
import org.thingsboard.server.common.data.audit.AuditLog; import org.thingsboard.server.common.data.audit.AuditLog;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.EntityIdFactory; import org.thingsboard.server.common.data.id.EntityIdFactory;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.id.UserId;
import org.thingsboard.server.common.data.page.TimePageData; import org.thingsboard.server.common.data.page.TimePageData;
import org.thingsboard.server.common.data.page.TimePageLink; import org.thingsboard.server.common.data.page.TimePageLink;
import org.thingsboard.server.exception.ThingsboardException; import org.thingsboard.server.exception.ThingsboardException;
import java.util.UUID;
@RestController @RestController
@RequestMapping("/api") @RequestMapping("/api")
public class AuditLogController extends BaseController { public class AuditLogController extends BaseController {
@PreAuthorize("hasAuthority('TENANT_ADMIN')") @PreAuthorize("hasAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/audit/logs/{entityType}/{entityId}", params = {"limit"}, method = RequestMethod.GET) @RequestMapping(value = "/audit/logs/customer/{customerId}", params = {"limit"}, method = RequestMethod.GET)
@ResponseBody @ResponseBody
public TimePageData<AuditLog> getAuditLogs( public TimePageData<AuditLog> getAuditLogsByCustomerId(
@PathVariable("customerId") String strCustomerId,
@RequestParam int limit,
@RequestParam(required = false) Long startTime,
@RequestParam(required = false) Long endTime,
@RequestParam(required = false, defaultValue = "false") boolean ascOrder,
@RequestParam(required = false) String offset) throws ThingsboardException {
try {
checkParameter("CustomerId", strCustomerId);
TenantId tenantId = getCurrentUser().getTenantId();
TimePageLink pageLink = createPageLink(limit, startTime, endTime, ascOrder, offset);
return checkNotNull(auditLogService.findAuditLogsByTenantIdAndCustomerId(tenantId, new CustomerId(UUID.fromString(strCustomerId)), pageLink));
} catch (Exception e) {
throw handleException(e);
}
}
@PreAuthorize("hasAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/audit/logs/user/{userId}", params = {"limit"}, method = RequestMethod.GET)
@ResponseBody
public TimePageData<AuditLog> getAuditLogsByUserId(
@PathVariable("userId") String strUserId,
@RequestParam int limit,
@RequestParam(required = false) Long startTime,
@RequestParam(required = false) Long endTime,
@RequestParam(required = false, defaultValue = "false") boolean ascOrder,
@RequestParam(required = false) String offset) throws ThingsboardException {
try {
checkParameter("UserId", strUserId);
TenantId tenantId = getCurrentUser().getTenantId();
TimePageLink pageLink = createPageLink(limit, startTime, endTime, ascOrder, offset);
return checkNotNull(auditLogService.findAuditLogsByTenantIdAndUserId(tenantId, new UserId(UUID.fromString(strUserId)), pageLink));
} catch (Exception e) {
throw handleException(e);
}
}
@PreAuthorize("hasAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/audit/logs/entity/{entityType}/{entityId}", params = {"limit"}, method = RequestMethod.GET)
@ResponseBody
public TimePageData<AuditLog> getAuditLogsByEntityId(
@PathVariable("entityType") String strEntityType, @PathVariable("entityType") String strEntityType,
@PathVariable("entityId") String strEntityId, @PathVariable("entityId") String strEntityId,
@RequestParam int limit, @RequestParam int limit,

View File

@ -85,12 +85,16 @@ public class DeviceController extends BaseController {
savedDevice.getName(), savedDevice.getName(),
savedDevice.getType()); savedDevice.getType());
// TODO: refactor to ANNOTATION usage auditLogService.logEntityAction(
if (device.getId() == null) { getCurrentUser(),
logDeviceAction(savedDevice, ActionType.ADDED); savedDevice.getId(),
} else { savedDevice.getName(),
logDeviceAction(savedDevice, ActionType.UPDATED); savedDevice.getCustomerId(),
} device.getId() == null ? ActionType.ADDED : ActionType.UPDATED,
null,
ActionStatus.SUCCESS,
null);
return savedDevice; return savedDevice;
} catch (Exception e) { } catch (Exception e) {
@ -107,8 +111,15 @@ public class DeviceController extends BaseController {
DeviceId deviceId = new DeviceId(toUUID(strDeviceId)); DeviceId deviceId = new DeviceId(toUUID(strDeviceId));
Device device = checkDeviceId(deviceId); Device device = checkDeviceId(deviceId);
deviceService.deleteDevice(deviceId); deviceService.deleteDevice(deviceId);
// TODO: refactor to ANNOTATION usage auditLogService.logEntityAction(
logDeviceAction(device, ActionType.DELETED); getCurrentUser(),
device.getId(),
device.getName(),
device.getCustomerId(),
ActionType.DELETED,
null,
ActionStatus.SUCCESS,
null);
} catch (Exception e) { } catch (Exception e) {
throw handleException(e); throw handleException(e);
} }
@ -189,8 +200,15 @@ public class DeviceController extends BaseController {
Device device = checkDeviceId(deviceCredentials.getDeviceId()); Device device = checkDeviceId(deviceCredentials.getDeviceId());
DeviceCredentials result = checkNotNull(deviceCredentialsService.updateDeviceCredentials(deviceCredentials)); DeviceCredentials result = checkNotNull(deviceCredentialsService.updateDeviceCredentials(deviceCredentials));
actorService.onCredentialsUpdate(getCurrentUser().getTenantId(), deviceCredentials.getDeviceId()); actorService.onCredentialsUpdate(getCurrentUser().getTenantId(), deviceCredentials.getDeviceId());
// TODO: refactor to ANNOTATION usage auditLogService.logEntityAction(
logDeviceAction(device, ActionType.CREDENTIALS_UPDATED); getCurrentUser(),
device.getId(),
device.getName(),
device.getCustomerId(),
ActionType.CREDENTIALS_UPDATED,
null,
ActionStatus.SUCCESS,
null);
return result; return result;
} catch (Exception e) { } catch (Exception e) {
throw handleException(e); throw handleException(e);
@ -321,19 +339,4 @@ public class DeviceController extends BaseController {
throw handleException(e); throw handleException(e);
} }
} }
// TODO: refactor to ANNOTATION usage
private void logDeviceAction(Device device, ActionType actionType) throws ThingsboardException {
auditLogService.logAction(
getCurrentUser().getTenantId(),
device.getId(),
device.getName(),
device.getCustomerId(),
getCurrentUser().getId(),
getCurrentUser().getName(),
actionType,
null,
ActionStatus.SUCCESS,
null);
}
} }

View File

@ -244,7 +244,6 @@ spring:
username: "${SPRING_DATASOURCE_USERNAME:sa}" username: "${SPRING_DATASOURCE_USERNAME:sa}"
password: "${SPRING_DATASOURCE_PASSWORD:}" password: "${SPRING_DATASOURCE_PASSWORD:}"
# PostgreSQL DAO Configuration # PostgreSQL DAO Configuration
#spring: #spring:
# data: # data:
@ -260,3 +259,8 @@ spring:
# url: "${SPRING_DATASOURCE_URL:jdbc:postgresql://localhost:5432/thingsboard}" # url: "${SPRING_DATASOURCE_URL:jdbc:postgresql://localhost:5432/thingsboard}"
# username: "${SPRING_DATASOURCE_USERNAME:postgres}" # username: "${SPRING_DATASOURCE_USERNAME:postgres}"
# password: "${SPRING_DATASOURCE_PASSWORD:postgres}" # password: "${SPRING_DATASOURCE_PASSWORD:postgres}"
# Audit log parameters
audit_log:
# Enable/disable audit log functionality.
enabled: "${AUDIT_LOG_ENABLED:true}"

View File

@ -27,6 +27,7 @@ import org.thingsboard.server.common.data.audit.AuditLog;
import org.thingsboard.server.common.data.page.TimePageData; import org.thingsboard.server.common.data.page.TimePageData;
import org.thingsboard.server.common.data.page.TimePageLink; import org.thingsboard.server.common.data.page.TimePageLink;
import org.thingsboard.server.common.data.security.Authority; import org.thingsboard.server.common.data.security.Authority;
import org.thingsboard.server.dao.model.ModelConstants;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
@ -36,6 +37,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
public abstract class BaseAuditLogControllerTest extends AbstractControllerTest { public abstract class BaseAuditLogControllerTest extends AbstractControllerTest {
private Tenant savedTenant; private Tenant savedTenant;
private User tenantAdmin;
@Before @Before
public void beforeTest() throws Exception { public void beforeTest() throws Exception {
@ -46,14 +48,14 @@ public abstract class BaseAuditLogControllerTest extends AbstractControllerTest
savedTenant = doPost("/api/tenant", tenant, Tenant.class); savedTenant = doPost("/api/tenant", tenant, Tenant.class);
Assert.assertNotNull(savedTenant); Assert.assertNotNull(savedTenant);
User tenantAdmin = new User(); tenantAdmin = new User();
tenantAdmin.setAuthority(Authority.TENANT_ADMIN); tenantAdmin.setAuthority(Authority.TENANT_ADMIN);
tenantAdmin.setTenantId(savedTenant.getId()); tenantAdmin.setTenantId(savedTenant.getId());
tenantAdmin.setEmail("tenant2@thingsboard.org"); tenantAdmin.setEmail("tenant2@thingsboard.org");
tenantAdmin.setFirstName("Joe"); tenantAdmin.setFirstName("Joe");
tenantAdmin.setLastName("Downs"); tenantAdmin.setLastName("Downs");
createUserAndLogin(tenantAdmin, "testPassword1"); tenantAdmin = createUserAndLogin(tenantAdmin, "testPassword1");
} }
@After @After
@ -65,7 +67,7 @@ public abstract class BaseAuditLogControllerTest extends AbstractControllerTest
} }
@Test @Test
public void testSaveDeviceAuditLogs() throws Exception { public void testAuditLogs() throws Exception {
for (int i = 0; i < 178; i++) { for (int i = 0; i < 178; i++) {
Device device = new Device(); Device device = new Device();
device.setName("Device" + i); device.setName("Device" + i);
@ -87,10 +89,38 @@ public abstract class BaseAuditLogControllerTest extends AbstractControllerTest
} while (pageData.hasNext()); } while (pageData.hasNext());
Assert.assertEquals(178, loadedAuditLogs.size()); Assert.assertEquals(178, loadedAuditLogs.size());
loadedAuditLogs = new ArrayList<>();
pageLink = new TimePageLink(23);
do {
pageData = doGetTypedWithTimePageLink("/api/audit/logs/customer/" + ModelConstants.NULL_UUID + "?",
new TypeReference<TimePageData<AuditLog>>() {
}, pageLink);
loadedAuditLogs.addAll(pageData.getData());
if (pageData.hasNext()) {
pageLink = pageData.getNextPageLink();
}
} while (pageData.hasNext());
Assert.assertEquals(178, loadedAuditLogs.size());
loadedAuditLogs = new ArrayList<>();
pageLink = new TimePageLink(23);
do {
pageData = doGetTypedWithTimePageLink("/api/audit/logs/user/" + tenantAdmin.getId().getId().toString() + "?",
new TypeReference<TimePageData<AuditLog>>() {
}, pageLink);
loadedAuditLogs.addAll(pageData.getData());
if (pageData.hasNext()) {
pageLink = pageData.getNextPageLink();
}
} while (pageData.hasNext());
Assert.assertEquals(178, loadedAuditLogs.size());
} }
@Test @Test
public void testUpdateDeviceAuditLogs() throws Exception { public void testAuditLogs_byTenantIdAndEntityId() throws Exception {
Device device = new Device(); Device device = new Device();
device.setName("Device name"); device.setName("Device name");
device.setType("default"); device.setType("default");
@ -104,7 +134,7 @@ public abstract class BaseAuditLogControllerTest extends AbstractControllerTest
TimePageLink pageLink = new TimePageLink(23); TimePageLink pageLink = new TimePageLink(23);
TimePageData<AuditLog> pageData; TimePageData<AuditLog> pageData;
do { do {
pageData = doGetTypedWithTimePageLink("/api/audit/logs/DEVICE/" + savedDevice.getId().getId() + "?", pageData = doGetTypedWithTimePageLink("/api/audit/logs/entity/DEVICE/" + savedDevice.getId().getId() + "?",
new TypeReference<TimePageData<AuditLog>>() { new TypeReference<TimePageData<AuditLog>>() {
}, pageLink); }, pageLink);
loadedAuditLogs.addAll(pageData.getData()); loadedAuditLogs.addAll(pageData.getData());

View File

@ -17,7 +17,9 @@ package org.thingsboard.server.dao.audit;
import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.ListenableFuture;
import org.thingsboard.server.common.data.audit.AuditLog; import org.thingsboard.server.common.data.audit.AuditLog;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.UserId;
import org.thingsboard.server.common.data.page.TimePageLink; import org.thingsboard.server.common.data.page.TimePageLink;
import java.util.List; import java.util.List;
@ -29,9 +31,17 @@ public interface AuditLogDao {
ListenableFuture<Void> saveByTenantIdAndEntityId(AuditLog auditLog); ListenableFuture<Void> saveByTenantIdAndEntityId(AuditLog auditLog);
ListenableFuture<Void> saveByTenantIdAndCustomerId(AuditLog auditLog);
ListenableFuture<Void> saveByTenantIdAndUserId(AuditLog auditLog);
ListenableFuture<Void> savePartitionsByTenantId(AuditLog auditLog); ListenableFuture<Void> savePartitionsByTenantId(AuditLog auditLog);
List<AuditLog> findAuditLogsByTenantIdAndEntityId(UUID tenantId, EntityId entityId, TimePageLink pageLink); List<AuditLog> findAuditLogsByTenantIdAndEntityId(UUID tenantId, EntityId entityId, TimePageLink pageLink);
List<AuditLog> findAuditLogsByTenantIdAndCustomerId(UUID tenantId, CustomerId customerId, TimePageLink pageLink);
List<AuditLog> findAuditLogsByTenantIdAndUserId(UUID tenantId, UserId userId, TimePageLink pageLink);
List<AuditLog> findAuditLogsByTenantId(UUID tenantId, TimePageLink pageLink); List<AuditLog> findAuditLogsByTenantId(UUID tenantId, TimePageLink pageLink);
} }

View File

@ -0,0 +1,70 @@
/**
* Copyright © 2016-2017 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.dao.audit;
import lombok.Getter;
import org.thingsboard.server.common.data.page.TimePageLink;
import org.thingsboard.server.dao.model.nosql.AuditLogEntity;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
public class AuditLogQueryCursor {
@Getter
private final UUID tenantId;
@Getter
private final List<AuditLogEntity> data;
@Getter
private final TimePageLink pageLink;
private final List<Long> partitions;
private int partitionIndex;
private int currentLimit;
public AuditLogQueryCursor(UUID tenantId, TimePageLink pageLink, List<Long> partitions) {
this.tenantId = tenantId;
this.partitions = partitions;
this.partitionIndex = partitions.size() - 1;
this.data = new ArrayList<>();
this.currentLimit = pageLink.getLimit();
this.pageLink = pageLink;
}
public boolean hasNextPartition() {
return partitionIndex >= 0;
}
public boolean isFull() {
return currentLimit <= 0;
}
public long getNextPartition() {
long partition = partitions.get(partitionIndex);
partitionIndex--;
return partition;
}
public int getCurrentLimit() {
return currentLimit;
}
public void addData(List<AuditLogEntity> newData) {
currentLimit -= newData.size();
data.addAll(newData);
}
}

View File

@ -17,6 +17,7 @@ package org.thingsboard.server.dao.audit;
import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.JsonNode;
import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.ListenableFuture;
import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.audit.ActionStatus; import org.thingsboard.server.common.data.audit.ActionStatus;
import org.thingsboard.server.common.data.audit.ActionType; import org.thingsboard.server.common.data.audit.ActionType;
import org.thingsboard.server.common.data.audit.AuditLog; import org.thingsboard.server.common.data.audit.AuditLog;
@ -31,20 +32,21 @@ import java.util.List;
public interface AuditLogService { public interface AuditLogService {
TimePageData<AuditLog> findAuditLogsByTenantIdAndCustomerId(TenantId tenantId, CustomerId customerId, TimePageLink pageLink);
TimePageData<AuditLog> findAuditLogsByTenantIdAndUserId(TenantId tenantId, UserId userId, TimePageLink pageLink);
TimePageData<AuditLog> findAuditLogsByTenantIdAndEntityId(TenantId tenantId, EntityId entityId, TimePageLink pageLink); TimePageData<AuditLog> findAuditLogsByTenantIdAndEntityId(TenantId tenantId, EntityId entityId, TimePageLink pageLink);
TimePageData<AuditLog> findAuditLogsByTenantId(TenantId tenantId, TimePageLink pageLink); TimePageData<AuditLog> findAuditLogsByTenantId(TenantId tenantId, TimePageLink pageLink);
ListenableFuture<List<Void>> logAction(TenantId tenantId, ListenableFuture<List<Void>> logEntityAction(User user,
EntityId entityId, EntityId entityId,
String entityName, String entityName,
CustomerId customerId, CustomerId customerId,
UserId userId, ActionType actionType,
String userName, JsonNode actionData,
ActionType actionType, ActionStatus actionStatus,
JsonNode actionData, String actionFailureDetails);
ActionStatus actionStatus,
String actionFailureDetails);
} }

View File

@ -15,20 +15,20 @@
*/ */
package org.thingsboard.server.dao.audit; package org.thingsboard.server.dao.audit;
import com.datastax.driver.core.utils.UUIDs;
import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.JsonNode;
import com.google.common.collect.Lists; import com.google.common.collect.Lists;
import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.ListenableFuture;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.audit.ActionStatus; import org.thingsboard.server.common.data.audit.ActionStatus;
import org.thingsboard.server.common.data.audit.ActionType; import org.thingsboard.server.common.data.audit.ActionType;
import org.thingsboard.server.common.data.audit.AuditLog; import org.thingsboard.server.common.data.audit.AuditLog;
import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.*;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.id.UserId;
import org.thingsboard.server.common.data.page.TimePageData; import org.thingsboard.server.common.data.page.TimePageData;
import org.thingsboard.server.common.data.page.TimePageLink; import org.thingsboard.server.common.data.page.TimePageLink;
import org.thingsboard.server.dao.exception.DataValidationException; import org.thingsboard.server.dao.exception.DataValidationException;
@ -41,6 +41,7 @@ import static org.thingsboard.server.dao.service.Validator.validateId;
@Slf4j @Slf4j
@Service @Service
@ConditionalOnProperty(prefix = "audit_log", value = "enabled", havingValue = "true")
public class AuditLogServiceImpl implements AuditLogService { public class AuditLogServiceImpl implements AuditLogService {
private static final String INCORRECT_TENANT_ID = "Incorrect tenantId "; private static final String INCORRECT_TENANT_ID = "Incorrect tenantId ";
@ -49,6 +50,24 @@ public class AuditLogServiceImpl implements AuditLogService {
@Autowired @Autowired
private AuditLogDao auditLogDao; private AuditLogDao auditLogDao;
@Override
public TimePageData<AuditLog> findAuditLogsByTenantIdAndCustomerId(TenantId tenantId, CustomerId customerId, TimePageLink pageLink) {
log.trace("Executing findAuditLogsByTenantIdAndCustomerId [{}], [{}], [{}]", tenantId, customerId, pageLink);
validateId(tenantId, INCORRECT_TENANT_ID + tenantId);
validateId(customerId, "Incorrect customerId " + customerId);
List<AuditLog> auditLogs = auditLogDao.findAuditLogsByTenantIdAndCustomerId(tenantId.getId(), customerId, pageLink);
return new TimePageData<>(auditLogs, pageLink);
}
@Override
public TimePageData<AuditLog> findAuditLogsByTenantIdAndUserId(TenantId tenantId, UserId userId, TimePageLink pageLink) {
log.trace("Executing findAuditLogsByTenantIdAndUserId [{}], [{}], [{}]", tenantId, userId, pageLink);
validateId(tenantId, INCORRECT_TENANT_ID + tenantId);
validateId(userId, "Incorrect userId" + userId);
List<AuditLog> auditLogs = auditLogDao.findAuditLogsByTenantIdAndUserId(tenantId.getId(), userId, pageLink);
return new TimePageData<>(auditLogs, pageLink);
}
@Override @Override
public TimePageData<AuditLog> findAuditLogsByTenantIdAndEntityId(TenantId tenantId, EntityId entityId, TimePageLink pageLink) { public TimePageData<AuditLog> findAuditLogsByTenantIdAndEntityId(TenantId tenantId, EntityId entityId, TimePageLink pageLink) {
log.trace("Executing findAuditLogsByTenantIdAndEntityId [{}], [{}], [{}]", tenantId, entityId, pageLink); log.trace("Executing findAuditLogsByTenantIdAndEntityId [{}], [{}], [{}]", tenantId, entityId, pageLink);
@ -66,6 +85,28 @@ public class AuditLogServiceImpl implements AuditLogService {
return new TimePageData<>(auditLogs, pageLink); return new TimePageData<>(auditLogs, pageLink);
} }
@Override
public ListenableFuture<List<Void>> logEntityAction(User user,
EntityId entityId,
String entityName,
CustomerId customerId,
ActionType actionType,
JsonNode actionData,
ActionStatus actionStatus,
String actionFailureDetails) {
return logAction(
user.getTenantId(),
entityId,
entityName,
customerId,
user.getId(),
user.getName(),
actionType,
actionData,
actionStatus,
actionFailureDetails);
}
private AuditLog createAuditLogEntry(TenantId tenantId, private AuditLog createAuditLogEntry(TenantId tenantId,
EntityId entityId, EntityId entityId,
String entityName, String entityName,
@ -77,6 +118,7 @@ public class AuditLogServiceImpl implements AuditLogService {
ActionStatus actionStatus, ActionStatus actionStatus,
String actionFailureDetails) { String actionFailureDetails) {
AuditLog result = new AuditLog(); AuditLog result = new AuditLog();
result.setId(new AuditLogId(UUIDs.timeBased()));
result.setTenantId(tenantId); result.setTenantId(tenantId);
result.setEntityId(entityId); result.setEntityId(entityId);
result.setEntityName(entityName); result.setEntityName(entityName);
@ -90,17 +132,16 @@ public class AuditLogServiceImpl implements AuditLogService {
return result; return result;
} }
@Override private ListenableFuture<List<Void>> logAction(TenantId tenantId,
public ListenableFuture<List<Void>> logAction(TenantId tenantId, EntityId entityId,
EntityId entityId, String entityName,
String entityName, CustomerId customerId,
CustomerId customerId, UserId userId,
UserId userId, String userName,
String userName, ActionType actionType,
ActionType actionType, JsonNode actionData,
JsonNode actionData, ActionStatus actionStatus,
ActionStatus actionStatus, String actionFailureDetails) {
String actionFailureDetails) {
AuditLog auditLogEntry = createAuditLogEntry(tenantId, entityId, entityName, customerId, userId, userName, AuditLog auditLogEntry = createAuditLogEntry(tenantId, entityId, entityName, customerId, userId, userName,
actionType, actionData, actionStatus, actionFailureDetails); actionType, actionData, actionStatus, actionFailureDetails);
log.trace("Executing logAction [{}]", auditLogEntry); log.trace("Executing logAction [{}]", auditLogEntry);
@ -109,6 +150,8 @@ public class AuditLogServiceImpl implements AuditLogService {
futures.add(auditLogDao.savePartitionsByTenantId(auditLogEntry)); futures.add(auditLogDao.savePartitionsByTenantId(auditLogEntry));
futures.add(auditLogDao.saveByTenantId(auditLogEntry)); futures.add(auditLogDao.saveByTenantId(auditLogEntry));
futures.add(auditLogDao.saveByTenantIdAndEntityId(auditLogEntry)); futures.add(auditLogDao.saveByTenantIdAndEntityId(auditLogEntry));
futures.add(auditLogDao.saveByTenantIdAndCustomerId(auditLogEntry));
futures.add(auditLogDao.saveByTenantIdAndUserId(auditLogEntry));
return Futures.allAsList(futures); return Futures.allAsList(futures);
} }

View File

@ -19,7 +19,8 @@ import com.datastax.driver.core.BoundStatement;
import com.datastax.driver.core.PreparedStatement; import com.datastax.driver.core.PreparedStatement;
import com.datastax.driver.core.ResultSet; import com.datastax.driver.core.ResultSet;
import com.datastax.driver.core.ResultSetFuture; import com.datastax.driver.core.ResultSetFuture;
import com.datastax.driver.core.utils.UUIDs; import com.datastax.driver.core.querybuilder.QueryBuilder;
import com.datastax.driver.core.querybuilder.Select;
import com.google.common.base.Function; import com.google.common.base.Function;
import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.ListenableFuture;
@ -29,8 +30,9 @@ import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.env.Environment; import org.springframework.core.env.Environment;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.thingsboard.server.common.data.audit.AuditLog; import org.thingsboard.server.common.data.audit.AuditLog;
import org.thingsboard.server.common.data.id.AuditLogId; import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.UserId;
import org.thingsboard.server.common.data.page.TimePageLink; import org.thingsboard.server.common.data.page.TimePageLink;
import org.thingsboard.server.dao.DaoUtil; import org.thingsboard.server.dao.DaoUtil;
import org.thingsboard.server.dao.model.ModelConstants; import org.thingsboard.server.dao.model.ModelConstants;
@ -52,6 +54,7 @@ import java.util.Optional;
import java.util.UUID; import java.util.UUID;
import java.util.concurrent.ExecutorService; import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors; import java.util.concurrent.Executors;
import java.util.stream.Collectors;
import static com.datastax.driver.core.querybuilder.QueryBuilder.eq; import static com.datastax.driver.core.querybuilder.QueryBuilder.eq;
import static org.thingsboard.server.dao.model.ModelConstants.*; import static org.thingsboard.server.dao.model.ModelConstants.*;
@ -82,7 +85,11 @@ public class CassandraAuditLogDao extends CassandraAbstractSearchTimeDao<AuditLo
private String partitioning; private String partitioning;
private TsPartitionDate tsFormat; private TsPartitionDate tsFormat;
private PreparedStatement[] saveStmts; private PreparedStatement partitionInsertStmt;
private PreparedStatement saveByTenantStmt;
private PreparedStatement saveByTenantIdAndUserIdStmt;
private PreparedStatement saveByTenantIdAndEntityIdStmt;
private PreparedStatement saveByTenantIdAndCustomerIdStmt;
private boolean isInstall() { private boolean isInstall() {
return environment.acceptsProfiles("install"); return environment.acceptsProfiles("install");
@ -123,11 +130,9 @@ public class CassandraAuditLogDao extends CassandraAbstractSearchTimeDao<AuditLo
public ListenableFuture<Void> saveByTenantId(AuditLog auditLog) { public ListenableFuture<Void> saveByTenantId(AuditLog auditLog) {
log.debug("Save saveByTenantId [{}] ", auditLog); log.debug("Save saveByTenantId [{}] ", auditLog);
AuditLogId auditLogId = new AuditLogId(UUIDs.timeBased());
long partition = toPartitionTs(LocalDate.now().atStartOfDay().toInstant(ZoneOffset.UTC).toEpochMilli()); long partition = toPartitionTs(LocalDate.now().atStartOfDay().toInstant(ZoneOffset.UTC).toEpochMilli());
BoundStatement stmt = getSaveByTenantStmt().bind(); BoundStatement stmt = getSaveByTenantStmt().bind();
stmt.setUUID(0, auditLogId.getId()) stmt = stmt.setUUID(0, auditLog.getId().getId())
.setUUID(1, auditLog.getTenantId().getId()) .setUUID(1, auditLog.getTenantId().getId())
.setUUID(2, auditLog.getEntityId().getId()) .setUUID(2, auditLog.getEntityId().getId())
.setString(3, auditLog.getEntityId().getEntityType().name()) .setString(3, auditLog.getEntityId().getEntityType().name())
@ -140,17 +145,44 @@ public class CassandraAuditLogDao extends CassandraAbstractSearchTimeDao<AuditLo
public ListenableFuture<Void> saveByTenantIdAndEntityId(AuditLog auditLog) { public ListenableFuture<Void> saveByTenantIdAndEntityId(AuditLog auditLog) {
log.debug("Save saveByTenantIdAndEntityId [{}] ", auditLog); log.debug("Save saveByTenantIdAndEntityId [{}] ", auditLog);
AuditLogId auditLogId = new AuditLogId(UUIDs.timeBased());
BoundStatement stmt = getSaveByTenantIdAndEntityIdStmt().bind(); BoundStatement stmt = getSaveByTenantIdAndEntityIdStmt().bind();
stmt.setUUID(0, auditLogId.getId()) stmt = setSaveStmtVariables(stmt, auditLog);
.setUUID(1, auditLog.getTenantId().getId())
.setUUID(2, auditLog.getEntityId().getId())
.setString(3, auditLog.getEntityId().getEntityType().name())
.setString(4, auditLog.getActionType().name());
return getFuture(executeAsyncWrite(stmt), rs -> null); return getFuture(executeAsyncWrite(stmt), rs -> null);
} }
@Override
public ListenableFuture<Void> saveByTenantIdAndCustomerId(AuditLog auditLog) {
log.debug("Save saveByTenantIdAndCustomerId [{}] ", auditLog);
BoundStatement stmt = getSaveByTenantIdAndCustomerIdStmt().bind();
stmt = setSaveStmtVariables(stmt, auditLog);
return getFuture(executeAsyncWrite(stmt), rs -> null);
}
@Override
public ListenableFuture<Void> saveByTenantIdAndUserId(AuditLog auditLog) {
log.debug("Save saveByTenantIdAndUserId [{}] ", auditLog);
BoundStatement stmt = getSaveByTenantIdAndUserIdStmt().bind();
stmt = setSaveStmtVariables(stmt, auditLog);
return getFuture(executeAsyncWrite(stmt), rs -> null);
}
private BoundStatement setSaveStmtVariables(BoundStatement stmt, AuditLog auditLog) {
return stmt.setUUID(0, auditLog.getId().getId())
.setUUID(1, auditLog.getTenantId().getId())
.setUUID(2, auditLog.getCustomerId().getId())
.setUUID(3, auditLog.getEntityId().getId())
.setString(4, auditLog.getEntityId().getEntityType().name())
.setString(5, auditLog.getEntityName())
.setUUID(6, auditLog.getUserId().getId())
.setString(7, auditLog.getUserName())
.setString(8, auditLog.getActionType().name())
.setString(9, auditLog.getActionData() != null ? auditLog.getActionData().toString() : null)
.setString(10, auditLog.getActionStatus().name())
.setString(11, auditLog.getActionFailureDetails());
}
@Override @Override
public ListenableFuture<Void> savePartitionsByTenantId(AuditLog auditLog) { public ListenableFuture<Void> savePartitionsByTenantId(AuditLog auditLog) {
log.debug("Save savePartitionsByTenantId [{}] ", auditLog); log.debug("Save savePartitionsByTenantId [{}] ", auditLog);
@ -163,35 +195,66 @@ public class CassandraAuditLogDao extends CassandraAbstractSearchTimeDao<AuditLo
return getFuture(executeAsyncWrite(stmt), rs -> null); return getFuture(executeAsyncWrite(stmt), rs -> null);
} }
private PreparedStatement getPartitionInsertStmt() { private PreparedStatement getSaveByTenantIdAndEntityIdStmt() {
// TODO: ADD CACHE LOGIC if (saveByTenantIdAndEntityIdStmt == null) {
return getSession().prepare(INSERT_INTO + ModelConstants.AUDIT_LOG_BY_TENANT_ID_PARTITIONS_CF + saveByTenantIdAndEntityIdStmt = getSaveByTenantIdAndCFName(ModelConstants.AUDIT_LOG_BY_ENTITY_ID_CF);
"(" + ModelConstants.AUDIT_LOG_TENANT_ID_PROPERTY + }
"," + ModelConstants.AUDIT_LOG_PARTITION_PROPERTY + ")" + return saveByTenantIdAndEntityIdStmt;
" VALUES(?, ?)");
} }
private PreparedStatement getSaveByTenantIdAndEntityIdStmt() { private PreparedStatement getSaveByTenantIdAndCustomerIdStmt() {
// TODO: ADD CACHE LOGIC if (saveByTenantIdAndCustomerIdStmt == null) {
return getSession().prepare(INSERT_INTO + ModelConstants.AUDIT_LOG_BY_ENTITY_ID_CF + saveByTenantIdAndCustomerIdStmt = getSaveByTenantIdAndCFName(ModelConstants.AUDIT_LOG_BY_CUSTOMER_ID_CF);
}
return saveByTenantIdAndCustomerIdStmt;
}
private PreparedStatement getSaveByTenantIdAndUserIdStmt() {
if (saveByTenantIdAndUserIdStmt == null) {
saveByTenantIdAndUserIdStmt = getSaveByTenantIdAndCFName(ModelConstants.AUDIT_LOG_BY_USER_ID_CF);
}
return saveByTenantIdAndUserIdStmt;
}
private PreparedStatement getSaveByTenantIdAndCFName(String cfName) {
return getSession().prepare(INSERT_INTO + cfName +
"(" + ModelConstants.AUDIT_LOG_ID_PROPERTY + "(" + ModelConstants.AUDIT_LOG_ID_PROPERTY +
"," + ModelConstants.AUDIT_LOG_TENANT_ID_PROPERTY + "," + ModelConstants.AUDIT_LOG_TENANT_ID_PROPERTY +
"," + ModelConstants.AUDIT_LOG_CUSTOMER_ID_PROPERTY +
"," + ModelConstants.AUDIT_LOG_ENTITY_ID_PROPERTY + "," + ModelConstants.AUDIT_LOG_ENTITY_ID_PROPERTY +
"," + ModelConstants.AUDIT_LOG_ENTITY_TYPE_PROPERTY + "," + ModelConstants.AUDIT_LOG_ENTITY_TYPE_PROPERTY +
"," + ModelConstants.AUDIT_LOG_ACTION_TYPE_PROPERTY + ")" + "," + ModelConstants.AUDIT_LOG_ENTITY_NAME_PROPERTY +
" VALUES(?, ?, ?, ?, ?)"); "," + ModelConstants.AUDIT_LOG_USER_ID_PROPERTY +
"," + ModelConstants.AUDIT_LOG_USER_NAME_PROPERTY +
"," + ModelConstants.AUDIT_LOG_ACTION_TYPE_PROPERTY +
"," + ModelConstants.AUDIT_LOG_ACTION_DATA_PROPERTY +
"," + ModelConstants.AUDIT_LOG_ACTION_STATUS_PROPERTY +
"," + ModelConstants.AUDIT_LOG_ACTION_FAILURE_DETAILS_PROPERTY + ")" +
" VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)");
}
private PreparedStatement getPartitionInsertStmt() {
if (partitionInsertStmt == null) {
partitionInsertStmt = getSession().prepare(INSERT_INTO + ModelConstants.AUDIT_LOG_BY_TENANT_ID_PARTITIONS_CF +
"(" + ModelConstants.AUDIT_LOG_TENANT_ID_PROPERTY +
"," + ModelConstants.AUDIT_LOG_PARTITION_PROPERTY + ")" +
" VALUES(?, ?)");
}
return partitionInsertStmt;
} }
private PreparedStatement getSaveByTenantStmt() { private PreparedStatement getSaveByTenantStmt() {
// TODO: ADD CACHE LOGIC if (saveByTenantStmt == null) {
return getSession().prepare(INSERT_INTO + ModelConstants.AUDIT_LOG_BY_TENANT_ID_CF + saveByTenantStmt = getSession().prepare(INSERT_INTO + ModelConstants.AUDIT_LOG_BY_TENANT_ID_CF +
"(" + ModelConstants.AUDIT_LOG_ID_PROPERTY + "(" + ModelConstants.AUDIT_LOG_ID_PROPERTY +
"," + ModelConstants.AUDIT_LOG_TENANT_ID_PROPERTY + "," + ModelConstants.AUDIT_LOG_TENANT_ID_PROPERTY +
"," + ModelConstants.AUDIT_LOG_ENTITY_ID_PROPERTY + "," + ModelConstants.AUDIT_LOG_ENTITY_ID_PROPERTY +
"," + ModelConstants.AUDIT_LOG_ENTITY_TYPE_PROPERTY + "," + ModelConstants.AUDIT_LOG_ENTITY_TYPE_PROPERTY +
"," + ModelConstants.AUDIT_LOG_ACTION_TYPE_PROPERTY + "," + ModelConstants.AUDIT_LOG_ACTION_TYPE_PROPERTY +
"," + ModelConstants.AUDIT_LOG_PARTITION_PROPERTY + ")" + "," + ModelConstants.AUDIT_LOG_PARTITION_PROPERTY + ")" +
" VALUES(?, ?, ?, ?, ?, ?)"); " VALUES(?, ?, ?, ?, ?, ?)");
}
return saveByTenantStmt;
} }
private long toPartitionTs(long ts) { private long toPartitionTs(long ts) {
@ -199,7 +262,6 @@ public class CassandraAuditLogDao extends CassandraAbstractSearchTimeDao<AuditLo
return tsFormat.truncatedTo(time).toInstant(ZoneOffset.UTC).toEpochMilli(); return tsFormat.truncatedTo(time).toInstant(ZoneOffset.UTC).toEpochMilli();
} }
@Override @Override
public List<AuditLog> findAuditLogsByTenantIdAndEntityId(UUID tenantId, EntityId entityId, TimePageLink pageLink) { public List<AuditLog> findAuditLogsByTenantIdAndEntityId(UUID tenantId, EntityId entityId, TimePageLink pageLink) {
log.trace("Try to find audit logs by tenant [{}], entity [{}] and pageLink [{}]", tenantId, entityId, pageLink); log.trace("Try to find audit logs by tenant [{}], entity [{}] and pageLink [{}]", tenantId, entityId, pageLink);
@ -212,31 +274,76 @@ public class CassandraAuditLogDao extends CassandraAbstractSearchTimeDao<AuditLo
return DaoUtil.convertDataList(entities); return DaoUtil.convertDataList(entities);
} }
@Override
public List<AuditLog> findAuditLogsByTenantIdAndCustomerId(UUID tenantId, CustomerId customerId, TimePageLink pageLink) {
log.trace("Try to find audit logs by tenant [{}], customer [{}] and pageLink [{}]", tenantId, customerId, pageLink);
List<AuditLogEntity> entities = findPageWithTimeSearch(AUDIT_LOG_BY_CUSTOMER_ID_CF,
Arrays.asList(eq(ModelConstants.AUDIT_LOG_TENANT_ID_PROPERTY, tenantId),
eq(ModelConstants.AUDIT_LOG_CUSTOMER_ID_PROPERTY, customerId.getId())),
pageLink);
log.trace("Found audit logs by tenant [{}], customer [{}] and pageLink [{}]", tenantId, customerId, pageLink);
return DaoUtil.convertDataList(entities);
}
@Override
public List<AuditLog> findAuditLogsByTenantIdAndUserId(UUID tenantId, UserId userId, TimePageLink pageLink) {
log.trace("Try to find audit logs by tenant [{}], user [{}] and pageLink [{}]", tenantId, userId, pageLink);
List<AuditLogEntity> entities = findPageWithTimeSearch(AUDIT_LOG_BY_USER_ID_CF,
Arrays.asList(eq(ModelConstants.AUDIT_LOG_TENANT_ID_PROPERTY, tenantId),
eq(ModelConstants.AUDIT_LOG_USER_ID_PROPERTY, userId.getId())),
pageLink);
log.trace("Found audit logs by tenant [{}], user [{}] and pageLink [{}]", tenantId, userId, pageLink);
return DaoUtil.convertDataList(entities);
}
@Override @Override
public List<AuditLog> findAuditLogsByTenantId(UUID tenantId, TimePageLink pageLink) { public List<AuditLog> findAuditLogsByTenantId(UUID tenantId, TimePageLink pageLink) {
log.trace("Try to find audit logs by tenant [{}] and pageLink [{}]", tenantId, pageLink); log.trace("Try to find audit logs by tenant [{}] and pageLink [{}]", tenantId, pageLink);
// TODO: ADD AUDIT LOG PARTITION CURSOR LOGIC
long minPartition; long minPartition;
long maxPartition;
if (pageLink.getStartTime() != null && pageLink.getStartTime() != 0) { if (pageLink.getStartTime() != null && pageLink.getStartTime() != 0) {
minPartition = toPartitionTs(pageLink.getStartTime()); minPartition = toPartitionTs(pageLink.getStartTime());
} else { } else {
minPartition = toPartitionTs(LocalDate.now().minusMonths(1).atStartOfDay().toInstant(ZoneOffset.UTC).toEpochMilli()); minPartition = toPartitionTs(LocalDate.now().minusMonths(1).atStartOfDay().toInstant(ZoneOffset.UTC).toEpochMilli());
} }
long maxPartition;
if (pageLink.getEndTime() != null && pageLink.getEndTime() != 0) { if (pageLink.getEndTime() != null && pageLink.getEndTime() != 0) {
maxPartition = toPartitionTs(pageLink.getEndTime()); maxPartition = toPartitionTs(pageLink.getEndTime());
} else { } else {
maxPartition = toPartitionTs(LocalDate.now().atStartOfDay().toInstant(ZoneOffset.UTC).toEpochMilli()); maxPartition = toPartitionTs(LocalDate.now().atStartOfDay().toInstant(ZoneOffset.UTC).toEpochMilli());
} }
List<AuditLogEntity> entities = findPageWithTimeSearch(AUDIT_LOG_BY_TENANT_ID_CF,
Arrays.asList(eq(ModelConstants.AUDIT_LOG_TENANT_ID_PROPERTY, tenantId), List<Long> partitions = fetchPartitions(tenantId, minPartition, maxPartition)
eq(ModelConstants.AUDIT_LOG_PARTITION_PROPERTY, maxPartition)), .all()
pageLink); .stream()
.map(row -> row.getLong(ModelConstants.PARTITION_COLUMN))
.collect(Collectors.toList());
AuditLogQueryCursor cursor = new AuditLogQueryCursor(tenantId, pageLink, partitions);
List<AuditLogEntity> entities = fetchSequentiallyWithLimit(cursor);
log.trace("Found audit logs by tenant [{}] and pageLink [{}]", tenantId, pageLink); log.trace("Found audit logs by tenant [{}] and pageLink [{}]", tenantId, pageLink);
return DaoUtil.convertDataList(entities); return DaoUtil.convertDataList(entities);
} }
private List<AuditLogEntity> fetchSequentiallyWithLimit(AuditLogQueryCursor cursor) {
if (cursor.isFull() || !cursor.hasNextPartition()) {
return cursor.getData();
} else {
cursor.addData(findPageWithTimeSearch(AUDIT_LOG_BY_TENANT_ID_CF,
Arrays.asList(eq(ModelConstants.AUDIT_LOG_TENANT_ID_PROPERTY, cursor.getTenantId()),
eq(ModelConstants.AUDIT_LOG_PARTITION_PROPERTY, cursor.getNextPartition())),
cursor.getPageLink()));
return fetchSequentiallyWithLimit(cursor);
}
}
private ResultSet fetchPartitions(UUID tenantId, long minPartition, long maxPartition) {
Select.Where select = QueryBuilder.select(ModelConstants.AUDIT_LOG_PARTITION_PROPERTY).from(ModelConstants.AUDIT_LOG_BY_TENANT_ID_PARTITIONS_CF)
.where(eq(ModelConstants.AUDIT_LOG_TENANT_ID_PROPERTY, tenantId));
select.and(QueryBuilder.gte(ModelConstants.PARTITION_COLUMN, minPartition));
select.and(QueryBuilder.lte(ModelConstants.PARTITION_COLUMN, maxPartition));
return getSession().execute(select);
}
} }

View File

@ -0,0 +1,61 @@
/**
* Copyright © 2016-2017 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.dao.audit;
import com.fasterxml.jackson.databind.JsonNode;
import com.google.common.util.concurrent.ListenableFuture;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.audit.ActionStatus;
import org.thingsboard.server.common.data.audit.ActionType;
import org.thingsboard.server.common.data.audit.AuditLog;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.id.UserId;
import org.thingsboard.server.common.data.page.TimePageData;
import org.thingsboard.server.common.data.page.TimePageLink;
import java.util.List;
@ConditionalOnProperty(prefix = "audit_log", value = "enabled", havingValue = "false")
public class DummyAuditLogServiceImpl implements AuditLogService {
@Override
public TimePageData<AuditLog> findAuditLogsByTenantIdAndCustomerId(TenantId tenantId, CustomerId customerId, TimePageLink pageLink) {
return null;
}
@Override
public TimePageData<AuditLog> findAuditLogsByTenantIdAndUserId(TenantId tenantId, UserId userId, TimePageLink pageLink) {
return null;
}
@Override
public TimePageData<AuditLog> findAuditLogsByTenantIdAndEntityId(TenantId tenantId, EntityId entityId, TimePageLink pageLink) {
return null;
}
@Override
public TimePageData<AuditLog> findAuditLogsByTenantId(TenantId tenantId, TimePageLink pageLink) {
return null;
}
@Override
public ListenableFuture<List<Void>> logEntityAction(User user, EntityId entityId, String entityName, CustomerId customerId, ActionType actionType, JsonNode actionData, ActionStatus actionStatus, String actionFailureDetails) {
return null;
}
}

View File

@ -147,6 +147,8 @@ public class ModelConstants {
public static final String AUDIT_LOG_COLUMN_FAMILY_NAME = "audit_log"; public static final String AUDIT_LOG_COLUMN_FAMILY_NAME = "audit_log";
public static final String AUDIT_LOG_BY_ENTITY_ID_CF = "audit_log_by_entity_id"; public static final String AUDIT_LOG_BY_ENTITY_ID_CF = "audit_log_by_entity_id";
public static final String AUDIT_LOG_BY_CUSTOMER_ID_CF = "audit_log_by_customer_id";
public static final String AUDIT_LOG_BY_USER_ID_CF = "audit_log_by_user_id";
public static final String AUDIT_LOG_BY_TENANT_ID_CF = "audit_log_by_tenant_id"; public static final String AUDIT_LOG_BY_TENANT_ID_CF = "audit_log_by_tenant_id";
public static final String AUDIT_LOG_BY_TENANT_ID_PARTITIONS_CF = "audit_log_by_tenant_id_partitions"; public static final String AUDIT_LOG_BY_TENANT_ID_PARTITIONS_CF = "audit_log_by_tenant_id_partitions";

View File

@ -41,4 +41,20 @@ public interface AuditLogRepository extends CrudRepository<AuditLogEntity, Strin
@Param("entityType") EntityType entityType, @Param("entityType") EntityType entityType,
@Param("idOffset") String idOffset, @Param("idOffset") String idOffset,
Pageable pageable); Pageable pageable);
@Query("SELECT al FROM AuditLogEntity al WHERE al.tenantId = :tenantId " +
"AND al.customerId = :customerId " +
"AND al.id > :idOffset ORDER BY al.id")
List<AuditLogEntity> findByTenantIdAndCustomerId(@Param("tenantId") String tenantId,
@Param("customerId") String customerId,
@Param("idOffset") String idOffset,
Pageable pageable);
@Query("SELECT al FROM AuditLogEntity al WHERE al.tenantId = :tenantId " +
"AND al.userId = :userId " +
"AND al.id > :idOffset ORDER BY al.id")
List<AuditLogEntity> findByTenantIdAndUserId(@Param("tenantId") String tenantId,
@Param("userId") String userId,
@Param("idOffset") String idOffset,
Pageable pageable);
} }

View File

@ -23,7 +23,9 @@ import org.springframework.data.domain.PageRequest;
import org.springframework.data.repository.CrudRepository; import org.springframework.data.repository.CrudRepository;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.thingsboard.server.common.data.audit.AuditLog; import org.thingsboard.server.common.data.audit.AuditLog;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.UserId;
import org.thingsboard.server.common.data.page.TimePageLink; import org.thingsboard.server.common.data.page.TimePageLink;
import org.thingsboard.server.dao.DaoUtil; import org.thingsboard.server.dao.DaoUtil;
import org.thingsboard.server.dao.audit.AuditLogDao; import org.thingsboard.server.dao.audit.AuditLogDao;
@ -76,6 +78,16 @@ public class JpaAuditLogDao extends JpaAbstractDao<AuditLogEntity, AuditLog> imp
return insertService.submit(() -> null); return insertService.submit(() -> null);
} }
@Override
public ListenableFuture<Void> saveByTenantIdAndCustomerId(AuditLog auditLog) {
return insertService.submit(() -> null);
}
@Override
public ListenableFuture<Void> saveByTenantIdAndUserId(AuditLog auditLog) {
return insertService.submit(() -> null);
}
@Override @Override
public ListenableFuture<Void> savePartitionsByTenantId(AuditLog auditLog) { public ListenableFuture<Void> savePartitionsByTenantId(AuditLog auditLog) {
return insertService.submit(() -> null); return insertService.submit(() -> null);
@ -92,6 +104,26 @@ public class JpaAuditLogDao extends JpaAbstractDao<AuditLogEntity, AuditLog> imp
new PageRequest(0, pageLink.getLimit()))); new PageRequest(0, pageLink.getLimit())));
} }
@Override
public List<AuditLog> findAuditLogsByTenantIdAndCustomerId(UUID tenantId, CustomerId customerId, TimePageLink pageLink) {
return DaoUtil.convertDataList(
auditLogRepository.findByTenantIdAndCustomerId(
fromTimeUUID(tenantId),
fromTimeUUID(customerId.getId()),
pageLink.getIdOffset() == null ? NULL_UUID_STR : fromTimeUUID(pageLink.getIdOffset()),
new PageRequest(0, pageLink.getLimit())));
}
@Override
public List<AuditLog> findAuditLogsByTenantIdAndUserId(UUID tenantId, UserId userId, TimePageLink pageLink) {
return DaoUtil.convertDataList(
auditLogRepository.findByTenantIdAndUserId(
fromTimeUUID(tenantId),
fromTimeUUID(userId.getId()),
pageLink.getIdOffset() == null ? NULL_UUID_STR : fromTimeUUID(pageLink.getIdOffset()),
new PageRequest(0, pageLink.getLimit())));
}
@Override @Override
public List<AuditLog> findAuditLogsByTenantId(UUID tenantId, TimePageLink pageLink) { public List<AuditLog> findAuditLogsByTenantId(UUID tenantId, TimePageLink pageLink) {
return DaoUtil.convertDataList( return DaoUtil.convertDataList(

View File

@ -566,6 +566,40 @@ CREATE TABLE IF NOT EXISTS thingsboard.audit_log_by_entity_id (
PRIMARY KEY ((tenant_id, entity_id, entity_type), id) PRIMARY KEY ((tenant_id, entity_id, entity_type), id)
); );
CREATE TABLE IF NOT EXISTS thingsboard.audit_log_by_customer_id (
tenant_id timeuuid,
id timeuuid,
customer_id timeuuid,
entity_id timeuuid,
entity_type text,
entity_name text,
user_id timeuuid,
user_name text,
action_type text,
action_data text,
action_status text,
action_failure_details text,
PRIMARY KEY ((tenant_id, customer_id), id)
);
CREATE TABLE IF NOT EXISTS thingsboard.audit_log_by_user_id (
tenant_id timeuuid,
id timeuuid,
customer_id timeuuid,
entity_id timeuuid,
entity_type text,
entity_name text,
user_id timeuuid,
user_name text,
action_type text,
action_data text,
action_status text,
action_failure_details text,
PRIMARY KEY ((tenant_id, user_id), id)
);
CREATE TABLE IF NOT EXISTS thingsboard.audit_log_by_tenant_id ( CREATE TABLE IF NOT EXISTS thingsboard.audit_log_by_tenant_id (
tenant_id timeuuid, tenant_id timeuuid,
id timeuuid, id timeuuid,

View File

@ -8,3 +8,5 @@ zk.url=localhost:2181
zk.zk_dir=/thingsboard zk.zk_dir=/thingsboard
updates.enabled=false updates.enabled=false
audit_log.enabled=true