Browse Source

Merge remote-tracking branch 'origin/rc' into master-rc

pull/14974/head
Viacheslav Klimov 2 days ago
parent
commit
c59cd4a791
  1. 9
      application/src/main/java/org/thingsboard/server/controller/AuditLogController.java
  2. 6
      application/src/main/java/org/thingsboard/server/controller/BaseController.java
  3. 6
      application/src/main/java/org/thingsboard/server/controller/TenantProfileController.java
  4. 3
      application/src/main/java/org/thingsboard/server/service/action/EntityActionService.java
  5. 4
      application/src/main/java/org/thingsboard/server/service/entitiy/AbstractTbEntityService.java
  6. 3
      application/src/main/java/org/thingsboard/server/service/entitiy/DefaultTbLogEntityActionService.java
  7. 3
      application/src/main/java/org/thingsboard/server/service/entitiy/TbLogEntityActionService.java
  8. 67
      application/src/main/java/org/thingsboard/server/service/entitiy/tenant/profile/DefaultTbTenantProfileService.java
  9. 8
      application/src/main/java/org/thingsboard/server/service/entitiy/tenant/profile/TbTenantProfileService.java
  10. 5
      application/src/test/java/org/thingsboard/server/controller/AbstractNotifyEntityTest.java
  11. 3
      application/src/test/java/org/thingsboard/server/controller/AbstractWebTest.java
  12. 94
      application/src/test/java/org/thingsboard/server/controller/AuditLogControllerTest.java
  13. 31
      application/src/test/java/org/thingsboard/server/controller/TenantProfileControllerTest.java
  14. 3
      common/dao-api/src/main/java/org/thingsboard/server/dao/audit/AuditLogService.java
  15. 2
      common/dao-api/src/main/java/org/thingsboard/server/dao/tenant/TenantProfileService.java
  16. 2
      common/edge-api/src/main/proto/edge.proto
  17. 5
      dao/src/main/java/org/thingsboard/server/dao/audit/AuditLogServiceImpl.java
  18. 12
      dao/src/main/java/org/thingsboard/server/dao/tenant/TenantProfileServiceImpl.java
  19. 8
      dao/src/test/java/org/thingsboard/server/dao/service/TenantProfileServiceTest.java
  20. 3
      ui-ngx/src/app/core/services/menu.models.ts
  21. 2
      ui-ngx/src/app/modules/home/pages/audit-log/audit-log-routing.module.ts
  22. 4
      ui-ngx/src/app/modules/home/pages/tenant-profile/tenant-profile-tabs.component.html

9
application/src/main/java/org/thingsboard/server/controller/AuditLogController.java

@ -50,6 +50,7 @@ import static org.thingsboard.server.controller.ControllerConstants.PAGE_DATA_PA
import static org.thingsboard.server.controller.ControllerConstants.PAGE_NUMBER_DESCRIPTION;
import static org.thingsboard.server.controller.ControllerConstants.PAGE_SIZE_DESCRIPTION;
import static org.thingsboard.server.controller.ControllerConstants.SORT_ORDER_DESCRIPTION;
import static org.thingsboard.server.controller.ControllerConstants.SYSTEM_OR_TENANT_AUTHORITY_PARAGRAPH;
import static org.thingsboard.server.controller.ControllerConstants.TENANT_AUTHORITY_PARAGRAPH;
import static org.thingsboard.server.controller.ControllerConstants.USER_ID_PARAM_DESCRIPTION;
@ -138,8 +139,8 @@ public class AuditLogController extends BaseController {
notes = "Returns a page of audit logs related to the actions on the targeted entity. " +
"Basically, this API call is used to get the full lifecycle of some specific entity. " +
"For example to see when a device was created, updated, assigned to some customer, or even deleted from the system. " +
PAGE_DATA_PARAMETERS + TENANT_AUTHORITY_PARAGRAPH)
@PreAuthorize("hasAuthority('TENANT_ADMIN')")
PAGE_DATA_PARAMETERS + SYSTEM_OR_TENANT_AUTHORITY_PARAGRAPH)
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')")
@RequestMapping(value = "/audit/logs/entity/{entityType}/{entityId}", params = {"pageSize", "page"}, method = RequestMethod.GET)
@ResponseBody
public PageData<AuditLog> getAuditLogsByEntityId(
@ -173,8 +174,8 @@ public class AuditLogController extends BaseController {
@ApiOperation(value = "Get all audit logs (getAuditLogs)",
notes = "Returns a page of audit logs related to all entities in the scope of the current user's Tenant. " +
PAGE_DATA_PARAMETERS + TENANT_AUTHORITY_PARAGRAPH)
@PreAuthorize("hasAuthority('TENANT_ADMIN')")
PAGE_DATA_PARAMETERS + SYSTEM_OR_TENANT_AUTHORITY_PARAGRAPH)
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')")
@RequestMapping(value = "/audit/logs", params = {"pageSize", "page"}, method = RequestMethod.GET)
@ResponseBody
public PageData<AuditLog> getAuditLogs(

6
application/src/main/java/org/thingsboard/server/controller/BaseController.java

@ -881,10 +881,8 @@ public abstract class BaseController {
protected <E extends HasName & HasId<? extends EntityId>> void logEntityAction(SecurityUser user, EntityType entityType, E entity, E savedEntity, ActionType actionType, Exception e) {
EntityId entityId = savedEntity != null ? savedEntity.getId() : emptyId(entityType);
if (!user.isSystemAdmin()) {
entityActionService.logEntityAction(user, entityId, savedEntity != null ? savedEntity : entity,
user.getCustomerId(), actionType, e);
}
entityActionService.logEntityAction(user, entityId, savedEntity != null ? savedEntity : entity,
user.getCustomerId(), actionType, e);
}
protected <E extends HasName & HasId<? extends EntityId>> E doSaveAndLog(EntityType entityType, E entity, BiFunction<TenantId, E, E> savingFunction) throws Exception {

6
application/src/main/java/org/thingsboard/server/controller/TenantProfileController.java

@ -191,7 +191,7 @@ public class TenantProfileController extends BaseController {
oldProfile = checkTenantProfileId(tenantProfile.getId(), Operation.WRITE);
}
return tbTenantProfileService.save(getTenantId(), tenantProfile, oldProfile);
return tbTenantProfileService.save(getTenantId(), tenantProfile, oldProfile, getCurrentUser());
}
@ApiOperation(value = "Delete Tenant Profile (deleteTenantProfile)",
@ -204,7 +204,7 @@ public class TenantProfileController extends BaseController {
checkParameter("tenantProfileId", strTenantProfileId);
TenantProfileId tenantProfileId = new TenantProfileId(toUUID(strTenantProfileId));
TenantProfile profile = checkTenantProfileId(tenantProfileId, Operation.DELETE);
tbTenantProfileService.delete(getTenantId(), profile);
tbTenantProfileService.delete(getTenantId(), profile, getCurrentUser());
}
@ApiOperation(value = "Make tenant profile default (setDefaultTenantProfile)",
@ -217,7 +217,7 @@ public class TenantProfileController extends BaseController {
checkParameter("tenantProfileId", strTenantProfileId);
TenantProfileId tenantProfileId = new TenantProfileId(toUUID(strTenantProfileId));
TenantProfile tenantProfile = checkTenantProfileId(tenantProfileId, Operation.WRITE);
tenantProfileService.setDefaultTenantProfile(getTenantId(), tenantProfileId);
tenantProfile = tbTenantProfileService.setDefaultTenantProfile(getTenantId(), tenantProfile, getCurrentUser());
return tenantProfile;
}

3
application/src/main/java/org/thingsboard/server/service/action/EntityActionService.java

@ -17,6 +17,7 @@ package org.thingsboard.server.service.action;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import jakarta.validation.constraints.NotNull;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
@ -235,7 +236,7 @@ public class EntityActionService {
}
}
public <E extends HasName, I extends EntityId> void logEntityAction(User user, I entityId, E entity, CustomerId customerId,
public <E extends HasName, I extends EntityId> void logEntityAction(User user, @NotNull I entityId, E entity, CustomerId customerId,
ActionType actionType, Exception e, Object... additionalInfo) {
if (customerId == null || customerId.isNullUid()) {
customerId = user.getCustomerId();

4
application/src/main/java/org/thingsboard/server/service/entitiy/AbstractTbEntityService.java

@ -97,6 +97,10 @@ public abstract class AbstractTbEntityService {
return (I) EntityIdFactory.getByTypeAndUuid(entityType, ModelConstants.NULL_UUID);
}
protected <I extends EntityId> I getOrEmptyId(I entityId, EntityType entityType) {
return entityId == null ? emptyId(entityType) : entityId;
}
protected ListenableFuture<UUID> autoCommit(User user, EntityId entityId) {
if (vcService != null) {
return vcService.autoCommit(user, entityId);

3
application/src/main/java/org/thingsboard/server/service/entitiy/DefaultTbLogEntityActionService.java

@ -15,6 +15,7 @@
*/
package org.thingsboard.server.service.entitiy;
import jakarta.validation.constraints.NotNull;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
@ -60,7 +61,7 @@ public class DefaultTbLogEntityActionService implements TbLogEntityActionService
}
@Override
public <E extends HasName, I extends EntityId> void logEntityAction(TenantId tenantId, I entityId, E entity,
public <E extends HasName, I extends EntityId> void logEntityAction(TenantId tenantId, @NotNull I entityId, E entity,
CustomerId customerId, ActionType actionType,
User user, Exception e, Object... additionalInfo) {
if (user != null) {

3
application/src/main/java/org/thingsboard/server/service/entitiy/TbLogEntityActionService.java

@ -15,6 +15,7 @@
*/
package org.thingsboard.server.service.entitiy;
import jakarta.validation.constraints.NotNull;
import org.thingsboard.server.common.data.HasName;
import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.audit.ActionType;
@ -37,7 +38,7 @@ public interface TbLogEntityActionService {
<E extends HasName, I extends EntityId> void logEntityAction(TenantId tenantId, I entityId, E entity, CustomerId customerId,
ActionType actionType, User user, Object... additionalInfo);
<E extends HasName, I extends EntityId> void logEntityAction(TenantId tenantId, I entityId, E entity, CustomerId customerId,
<E extends HasName, I extends EntityId> void logEntityAction(TenantId tenantId, @NotNull I entityId, E entity, CustomerId customerId,
ActionType actionType, User user, Exception e,
Object... additionalInfo);

67
application/src/main/java/org/thingsboard/server/service/entitiy/tenant/profile/DefaultTbTenantProfileService.java

@ -15,21 +15,28 @@
*/
package org.thingsboard.server.service.entitiy.tenant.profile;
import jakarta.validation.constraints.NotNull;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.thingsboard.server.common.data.TenantProfile;
import org.thingsboard.server.common.data.audit.ActionType;
import org.thingsboard.server.common.data.exception.ThingsboardErrorCode;
import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.User;
import org.thingsboard.server.dao.tenant.TbTenantProfileCache;
import org.thingsboard.server.dao.tenant.TenantProfileService;
import org.thingsboard.server.dao.tenant.TenantService;
import org.thingsboard.server.exception.DataValidationException;
import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.entitiy.AbstractTbEntityService;
import org.thingsboard.server.service.entitiy.queue.TbQueueService;
import java.util.List;
import static org.thingsboard.server.common.data.EntityType.TENANT_PROFILE;
@Slf4j
@Service
@TbCoreComponent
@ -41,18 +48,62 @@ public class DefaultTbTenantProfileService extends AbstractTbEntityService imple
private final TbTenantProfileCache tenantProfileCache;
@Override
public TenantProfile save(TenantId tenantId, TenantProfile tenantProfile, TenantProfile oldTenantProfile) throws ThingsboardException {
TenantProfile savedTenantProfile = checkNotNull(tenantProfileService.saveTenantProfile(tenantId, tenantProfile));
tenantProfileCache.put(savedTenantProfile);
public TenantProfile
save(TenantId tenantId, TenantProfile tenantProfile, TenantProfile oldTenantProfile, User user) throws ThingsboardException {
ActionType actionType = tenantProfile.getId() == null ? ActionType.ADDED : ActionType.UPDATED;
try {
TenantProfile savedTenantProfile = checkNotNull(tenantProfileService.saveTenantProfile(tenantId, tenantProfile));
tenantProfileCache.put(savedTenantProfile);
logEntityActionService.logEntityAction(tenantId, savedTenantProfile.getId(), savedTenantProfile, null,
actionType, user);
List<TenantId> tenantIds = tenantService.findTenantIdsByTenantProfileId(savedTenantProfile.getId());
tbQueueService.updateQueuesByTenants(tenantIds, savedTenantProfile, oldTenantProfile);
List<TenantId> tenantIds = tenantService.findTenantIdsByTenantProfileId(savedTenantProfile.getId());
tbQueueService.updateQueuesByTenants(tenantIds, savedTenantProfile, oldTenantProfile);
return savedTenantProfile;
} catch (ThingsboardException e) {
log.debug("Failed to save tenant profile because ThingsboardException [{}]", tenantProfile, e);
logEntityActionService.logEntityAction(tenantId, getOrEmptyId(tenantProfile.getId(), TENANT_PROFILE), tenantProfile, actionType, user, e);
throw e;
} catch (DataValidationException e) {
log.debug("Failed to save tenant profile because data validation [{}]", tenantProfile, e);
logEntityActionService.logEntityAction(tenantId, getOrEmptyId(tenantProfile.getId(), TENANT_PROFILE), tenantProfile, actionType, user, e);
throw new ThingsboardException(e.getMessage(), e, ThingsboardErrorCode.BAD_REQUEST_PARAMS);
} catch (Exception e) {
log.debug("Failed to save tenant profile because Exception [{}]", tenantProfile, e);
logEntityActionService.logEntityAction(tenantId, getOrEmptyId(tenantProfile.getId(), TENANT_PROFILE), tenantProfile, actionType, user, e);
throw new ThingsboardException(e.getMessage(), e, ThingsboardErrorCode.GENERAL);
}
return savedTenantProfile;
}
@Override
public void delete(TenantId tenantId, TenantProfile tenantProfile) throws ThingsboardException {
tenantProfileService.deleteTenantProfile(tenantId, tenantProfile.getId());
public void delete(TenantId tenantId, @NotNull TenantProfile tenantProfile, User user) throws ThingsboardException {
ActionType actionType = ActionType.DELETED;
try {
tenantProfileService.deleteTenantProfile(tenantId, tenantProfile.getId());
logEntityActionService.logEntityAction(tenantId, tenantProfile.getId(), tenantProfile, null, actionType, user);
} catch (Exception e) {
logEntityActionService.logEntityAction(tenantId, tenantProfile.getId(), tenantProfile, null, actionType, user, e);
throw e;
}
}
@Override
public TenantProfile setDefaultTenantProfile(TenantId tenantId, @NotNull TenantProfile tenantProfile, User user) throws ThingsboardException {
ActionType actionType = ActionType.UPDATED;
try {
TenantProfile savedTenantProfile = tenantProfileService.setDefaultTenantProfile(tenantId, tenantProfile.getId());
logEntityActionService.logEntityAction(tenantId, tenantProfile.getId(), savedTenantProfile, null, actionType, user);
return savedTenantProfile;
} catch (DataValidationException e) {
log.debug("Failed to set default tenant profile due to data validation [{}]", tenantProfile, e);
logEntityActionService.logEntityAction(tenantId, tenantProfile.getId(), tenantProfile, actionType, user, e);
throw new ThingsboardException(e.getMessage(), e, ThingsboardErrorCode.BAD_REQUEST_PARAMS);
} catch (Exception e) {
log.debug("Failed to set default tenant profile [{}]", tenantProfile, e);
logEntityActionService.logEntityAction(tenantId, tenantProfile.getId(), tenantProfile, actionType, user, e);
throw new ThingsboardException(e.getMessage(), e, ThingsboardErrorCode.GENERAL);
}
}
}

8
application/src/main/java/org/thingsboard/server/service/entitiy/tenant/profile/TbTenantProfileService.java

@ -15,13 +15,17 @@
*/
package org.thingsboard.server.service.entitiy.tenant.profile;
import jakarta.validation.constraints.NotNull;
import org.thingsboard.server.common.data.TenantProfile;
import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.User;
public interface TbTenantProfileService {
TenantProfile save(TenantId tenantId, TenantProfile tenantProfile, TenantProfile oldTenantProfile) throws ThingsboardException;
TenantProfile save(TenantId tenantId, TenantProfile tenantProfile, TenantProfile oldTenantProfile, User user) throws ThingsboardException;
void delete(TenantId tenantId, TenantProfile tenantProfile) throws ThingsboardException;
void delete(TenantId tenantId, @NotNull TenantProfile tenantProfile, User user) throws ThingsboardException;
TenantProfile setDefaultTenantProfile(TenantId tenantId, @NotNull TenantProfile tenantProfile, User user) throws ThingsboardException;
}

5
application/src/test/java/org/thingsboard/server/controller/AbstractNotifyEntityTest.java

@ -18,6 +18,8 @@ package org.thingsboard.server.controller;
import lombok.extern.slf4j.Slf4j;
import org.mockito.ArgumentMatcher;
import org.mockito.Mockito;
import org.springframework.boot.info.BuildProperties;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.boot.test.mock.mockito.SpyBean;
import org.thingsboard.server.cluster.TbClusterService;
import org.thingsboard.server.common.data.EdgeUtils;
@ -59,6 +61,9 @@ public abstract class AbstractNotifyEntityTest extends AbstractWebTest {
@SpyBean
protected AuditLogService auditLogService;
@MockBean
BuildProperties buildProperties;
protected final String msgErrorPermission = "You don't have permission to perform this operation!";
protected final String msgErrorShouldBeSpecified = "should be specified";
protected final String msgErrorNotFound = "Requested item wasn't found!";

3
application/src/test/java/org/thingsboard/server/controller/AbstractWebTest.java

@ -1268,7 +1268,8 @@ public abstract class AbstractWebTest extends AbstractInMemoryStorageTest {
TenantProfile oldTenantProfile = tenantProfileService.findDefaultTenantProfile(TenantId.SYS_TENANT_ID);
TenantProfile tenantProfile = JacksonUtil.clone(oldTenantProfile);
updater.accept(tenantProfile);
tbTenantProfileService.save(TenantId.SYS_TENANT_ID, tenantProfile, oldTenantProfile);
// user should be sysadmin as this operation allowed only for sysadmins. But for the simplification of the test - already existed variable provided. This affects only an audit log content
tbTenantProfileService.save(TenantId.SYS_TENANT_ID, tenantProfile, oldTenantProfile, tenantAdminUser);
}
protected OAuth2Client createOauth2Client(TenantId tenantId, String title) {

94
application/src/test/java/org/thingsboard/server/controller/AuditLogControllerTest.java

@ -28,6 +28,7 @@ import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.test.mock.mockito.SpyBean;
import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.TenantProfile;
import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.audit.ActionType;
import org.thingsboard.server.common.data.audit.AuditLog;
@ -111,48 +112,49 @@ public class AuditLogControllerTest extends AbstractControllerTest {
doPost("/api/device", device, Device.class);
}
List<AuditLog> loadedAuditLogs = new ArrayList<>();
TimePageLink pageLink = new TimePageLink(5);
PageData<AuditLog> pageData;
do {
pageData = doGetTypedWithTimePageLink("/api/audit/logs?",
new TypeReference<PageData<AuditLog>>() {
}, pageLink);
loadedAuditLogs.addAll(pageData.getData());
if (pageData.hasNext()) {
pageLink = pageLink.nextPageLink();
}
} while (pageData.hasNext());
List<AuditLog> loadedAuditLogs = getAuditLogs(5, "/api/audit/logs?");
Assert.assertEquals(11 + 1, loadedAuditLogs.size());
loadedAuditLogs = new ArrayList<>();
pageLink = new TimePageLink(5);
do {
pageData = doGetTypedWithTimePageLink("/api/audit/logs/customer/" + ModelConstants.NULL_UUID + "?",
new TypeReference<PageData<AuditLog>>() {
}, pageLink);
loadedAuditLogs.addAll(pageData.getData());
if (pageData.hasNext()) {
pageLink = pageLink.nextPageLink();
}
} while (pageData.hasNext());
loadedAuditLogs = getAuditLogs(5, "/api/audit/logs/customer/" + ModelConstants.NULL_UUID + "?");
Assert.assertEquals(11 + 1, loadedAuditLogs.size());
loadedAuditLogs = getAuditLogs(5, "/api/audit/logs/user/" + tenantAdmin.getId().getId().toString() + "?");
Assert.assertEquals(11 + 1, loadedAuditLogs.size());
}
@Test
public void testAuditLogsSysAdmin() throws Exception {
loginSysAdmin();
List<AuditLog> loadedAuditLogsBefore = getAuditLogs(100, "/api/audit/logs?");
for (int i = 0; i < 3; i++) {
TenantProfile tenantProfile = new TenantProfile();
tenantProfile.setName("Profile " + UUID.randomUUID());
doPost("/api/tenantProfile", tenantProfile, TenantProfile.class);
}
loadedAuditLogs = new ArrayList<>();
pageLink = new TimePageLink(5);
List<AuditLog> loadedAuditLogs = getAuditLogs(100, "/api/audit/logs?");
Assert.assertEquals("Have X audit log before this test + New tenant profiles in the test", loadedAuditLogsBefore.size() + 3, loadedAuditLogs.size());
}
private List<AuditLog> getAuditLogs(int pageSize, String urlTemplate) throws Exception {
List<AuditLog> loadedAuditLogs = new ArrayList<>();
TimePageLink pageLink = new TimePageLink(pageSize);
PageData<AuditLog> pageData;
do {
pageData = doGetTypedWithTimePageLink("/api/audit/logs/user/" + tenantAdmin.getId().getId().toString() + "?",
new TypeReference<PageData<AuditLog>>() {
pageData = doGetTypedWithTimePageLink(urlTemplate,
new TypeReference<>() {
}, pageLink);
loadedAuditLogs.addAll(pageData.getData());
if (pageData.hasNext()) {
pageLink = pageLink.nextPageLink();
}
} while (pageData.hasNext());
Assert.assertEquals(11 + 1, loadedAuditLogs.size());
return loadedAuditLogs;
}
@Test
@ -166,22 +168,32 @@ public class AuditLogControllerTest extends AbstractControllerTest {
savedDevice = doPost("/api/device", savedDevice, Device.class);
}
List<AuditLog> loadedAuditLogs = new ArrayList<>();
TimePageLink pageLink = new TimePageLink(5);
PageData<AuditLog> pageData;
do {
pageData = doGetTypedWithTimePageLink("/api/audit/logs/entity/DEVICE/" + savedDevice.getId().getId() + "?",
new TypeReference<PageData<AuditLog>>() {
}, pageLink);
loadedAuditLogs.addAll(pageData.getData());
if (pageData.hasNext()) {
pageLink = pageLink.nextPageLink();
}
} while (pageData.hasNext());
List<AuditLog> loadedAuditLogs = getAuditLogs(5, "/api/audit/logs/entity/DEVICE/" + savedDevice.getId().getId() + "?");
Assert.assertEquals(11 + 1, loadedAuditLogs.size());
}
@Test
public void testAuditLogs_byTenantIdAndEntityId_Sysadmin() throws Exception {
loginSysAdmin();
//created
TenantProfile tenantProfile = new TenantProfile();
tenantProfile.setName("Profile " + UUID.randomUUID());
tenantProfile = doPost("/api/tenantProfile", tenantProfile, TenantProfile.class);
//updated
tenantProfile.setName(tenantProfile.getName() + "(old)");
tenantProfile = doPost("/api/tenantProfile", tenantProfile, TenantProfile.class);
List<AuditLog> loadedAuditLogs = getAuditLogs(5, "/api/audit/logs/entity/" +tenantProfile.getId().getEntityType()+ "/" + tenantProfile.getId().getId() + "?");
Assert.assertEquals("Audit logs count by Tenant Profile entity", 2, loadedAuditLogs.size());
//cleanup
doDelete("/api/tenantProfile/" + tenantProfile.getId().getId().toString());
}
@Test
public void whenSavingNewAuditLog_thenCheckAndCreatePartitionIfNotExists() throws ParseException {
long entityTs = ISO_8601_EXTENDED_DATETIME_TIME_ZONE_FORMAT.parse("2024-01-01T01:43:11Z").getTime();

31
application/src/test/java/org/thingsboard/server/controller/TenantProfileControllerTest.java

@ -16,6 +16,7 @@
package org.thingsboard.server.controller;
import com.fasterxml.jackson.core.type.TypeReference;
import org.awaitility.Awaitility;
import org.junit.Assert;
import org.junit.Test;
import org.mockito.ArgumentMatcher;
@ -25,9 +26,12 @@ import org.thingsboard.server.common.data.EntityInfo;
import org.thingsboard.server.common.data.StringUtils;
import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.TenantProfile;
import org.thingsboard.server.common.data.audit.AuditLog;
import org.thingsboard.server.common.data.audit.ActionType;
import org.thingsboard.server.common.data.id.TenantProfileId;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.common.data.page.TimePageLink;
import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent;
import org.thingsboard.server.common.data.queue.ProcessingStrategy;
import org.thingsboard.server.common.data.queue.ProcessingStrategyType;
@ -47,6 +51,7 @@ import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;
import java.util.stream.Collectors;
@ -81,12 +86,17 @@ public class TenantProfileControllerTest extends AbstractControllerTest {
testBroadcastEntityStateChangeEventTimeManyTimeTenantProfile(savedTenantProfile, ComponentLifecycleEvent.CREATED, 1);
awaitAuditLog("Wait for async audit log to be persisted (ADDED expected)", savedTenantProfile.getId(), ActionType.ADDED);
savedTenantProfile.setName("New tenant profile");
doPost("/api/tenantProfile", savedTenantProfile, TenantProfile.class);
TenantProfile foundTenantProfile = doGet("/api/tenantProfile/" + savedTenantProfile.getId().getId().toString(), TenantProfile.class);
Assert.assertEquals(foundTenantProfile.getName(), savedTenantProfile.getName());
testBroadcastEntityStateChangeEventTimeManyTimeTenantProfile(savedTenantProfile, ComponentLifecycleEvent.UPDATED, 1);
awaitAuditLog("Wait for async audit log to be persisted (UPDATED expected)", savedTenantProfile.getId(), ActionType.UPDATED);
}
@Test
@ -180,6 +190,8 @@ public class TenantProfileControllerTest extends AbstractControllerTest {
Assert.assertNotNull(foundDefaultTenantProfile);
Assert.assertEquals(savedTenantProfile.getName(), foundDefaultTenantProfile.getName());
Assert.assertEquals(savedTenantProfile.getId(), foundDefaultTenantProfile.getId());
awaitAuditLog("Wait for async audit log to be persisted (UPDATED expected for NEW DEFAULT Tenant Profile)", savedTenantProfile.getId(), ActionType.UPDATED);
}
@Test
@ -245,6 +257,8 @@ public class TenantProfileControllerTest extends AbstractControllerTest {
doDelete("/api/tenantProfile/" + savedTenantProfile.getId().getId().toString())
.andExpect(status().isOk());
awaitAuditLog("Wait for async audit log to be persisted for deleted tenant profile", savedTenantProfile.getId(), ActionType.DELETED);
testBroadcastEntityStateChangeEventTimeManyTimeTenantProfile(savedTenantProfile, ComponentLifecycleEvent.DELETED, 1);
doGet("/api/tenantProfile/" + savedTenantProfile.getId().getId().toString())
@ -393,6 +407,22 @@ public class TenantProfileControllerTest extends AbstractControllerTest {
testBroadcastEntityStateChangeEventNeverTenantProfile();
}
private void awaitAuditLog(String awaitMessage, TenantProfileId tenantProfileId, ActionType expectedAction) throws Exception {
Awaitility.await(awaitMessage)
.atMost(TIMEOUT, TimeUnit.SECONDS)
.until(() ->
doGetTypedWithTimePageLink(
"/api/audit/logs/entity/TENANT_PROFILE/" + tenantProfileId.getId() + "?",
new TypeReference<PageData<AuditLog>>() {
},
new TimePageLink(5))
.getData()
.stream()
.anyMatch(log -> log.getActionType() == expectedAction)
);
}
private TenantProfile createTenantProfile(String name) {
TenantProfile tenantProfile = new TenantProfile();
tenantProfile.setName(name);
@ -429,7 +459,6 @@ public class TenantProfileControllerTest extends AbstractControllerTest {
tenantProfile.setProfileData(profileData);
}
private void testBroadcastEntityStateChangeEventTimeManyTimeTenantProfile(TenantProfile tenantProfile, ComponentLifecycleEvent event, int cntTime) {
ArgumentMatcher<TenantProfile> matcherTenantProfile = cntTime == 1 ? argument -> argument.equals(tenantProfile) :
argument -> argument.getClass().equals(TenantProfile.class);

3
common/dao-api/src/main/java/org/thingsboard/server/dao/audit/AuditLogService.java

@ -16,6 +16,7 @@
package org.thingsboard.server.dao.audit;
import com.google.common.util.concurrent.ListenableFuture;
import jakarta.validation.constraints.NotNull;
import org.thingsboard.server.common.data.HasName;
import org.thingsboard.server.common.data.audit.ActionType;
import org.thingsboard.server.common.data.audit.AuditLog;
@ -43,7 +44,7 @@ public interface AuditLogService {
CustomerId customerId,
UserId userId,
String userName,
I entityId,
@NotNull I entityId,
E entity,
ActionType actionType,
Exception e, Object... additionalInfo);

2
common/dao-api/src/main/java/org/thingsboard/server/dao/tenant/TenantProfileService.java

@ -46,7 +46,7 @@ public interface TenantProfileService extends EntityDaoService {
EntityInfo findDefaultTenantProfileInfo(TenantId tenantId);
boolean setDefaultTenantProfile(TenantId tenantId, TenantProfileId tenantProfileId);
TenantProfile setDefaultTenantProfile(TenantId tenantId, TenantProfileId tenantProfileId);
void deleteTenantProfiles(TenantId tenantId);

2
common/edge-api/src/main/proto/edge.proto

@ -46,7 +46,9 @@ enum EdgeVersion {
V_4_2_0 = 12;
V_4_3_0 = 13;
V_4_2_1_2 = 14;
V_4_2_1_3 = 420;
V_4_3_0_1 = 15;
V_4_3_0_2 = 430;
V_LATEST = 999;
}

5
dao/src/main/java/org/thingsboard/server/dao/audit/AuditLogServiceImpl.java

@ -20,6 +20,7 @@ import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import jakarta.validation.constraints.NotNull;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
@ -117,9 +118,9 @@ public class AuditLogServiceImpl implements AuditLogService {
@Override
public <E extends HasName, I extends EntityId> ListenableFuture<Void>
logEntityAction(TenantId tenantId, CustomerId customerId, UserId userId, String userName, I entityId, E entity,
logEntityAction(TenantId tenantId, CustomerId customerId, UserId userId, String userName, @NotNull I entityId, E entity,
ActionType actionType, Exception e, Object... additionalInfo) {
if (canLog(entityId.getEntityType(), actionType)) {
if (canLog(entityId.getEntityType(), actionType) || (tenantId != null && tenantId.isSysTenantId())) {
JsonNode actionData = constructActionData(entityId, entity, actionType, additionalInfo);
ActionStatus actionStatus = ActionStatus.SUCCESS;
String failureDetails = "";

12
dao/src/main/java/org/thingsboard/server/dao/tenant/TenantProfileServiceImpl.java

@ -190,7 +190,7 @@ public class TenantProfileServiceImpl extends AbstractCachedEntityService<Tenant
}
@Override
public boolean setDefaultTenantProfile(TenantId tenantId, TenantProfileId tenantProfileId) {
public TenantProfile setDefaultTenantProfile(TenantId tenantId, TenantProfileId tenantProfileId) {
log.trace("Executing setDefaultTenantProfile [{}]", tenantProfileId);
validateId(tenantId, id -> INCORRECT_TENANT_ID + id);
validateId(tenantProfileId, id -> INCORRECT_TENANT_PROFILE_ID + id);
@ -198,22 +198,18 @@ public class TenantProfileServiceImpl extends AbstractCachedEntityService<Tenant
if (!tenantProfile.isDefault()) {
tenantProfile.setDefault(true);
TenantProfile previousDefaultTenantProfile = findDefaultTenantProfile(tenantId);
boolean changed = false;
if (previousDefaultTenantProfile == null) {
tenantProfileDao.save(tenantId, tenantProfile);
tenantProfile = tenantProfileDao.save(tenantId, tenantProfile);
publishEvictEvent(new TenantProfileEvictEvent(tenantProfileId, true));
changed = true;
} else if (!previousDefaultTenantProfile.getId().equals(tenantProfile.getId())) {
previousDefaultTenantProfile.setDefault(false);
tenantProfileDao.save(tenantId, previousDefaultTenantProfile);
tenantProfileDao.save(tenantId, tenantProfile);
tenantProfile = tenantProfileDao.save(tenantId, tenantProfile);
publishEvictEvent(new TenantProfileEvictEvent(previousDefaultTenantProfile.getId(), false));
publishEvictEvent(new TenantProfileEvictEvent(tenantProfileId, true));
changed = true;
}
return changed;
}
return false;
return tenantProfile;
}
@Override

8
dao/src/test/java/org/thingsboard/server/dao/service/TenantProfileServiceTest.java

@ -164,13 +164,13 @@ public class TenantProfileServiceTest extends AbstractServiceTest {
TenantProfile savedTenantProfile1 = tenantProfileService.saveTenantProfile(TenantId.SYS_TENANT_ID, tenantProfile1);
TenantProfile savedTenantProfile2 = tenantProfileService.saveTenantProfile(TenantId.SYS_TENANT_ID, tenantProfile2);
boolean result = tenantProfileService.setDefaultTenantProfile(TenantId.SYS_TENANT_ID, savedTenantProfile1.getId());
Assert.assertTrue(result);
TenantProfile setDefaultTenantProfile1 = tenantProfileService.setDefaultTenantProfile(TenantId.SYS_TENANT_ID, savedTenantProfile1.getId());
Assert.assertNotEquals(savedTenantProfile1.isDefault(), setDefaultTenantProfile1.isDefault());
TenantProfile defaultTenantProfile = tenantProfileService.findDefaultTenantProfile(TenantId.SYS_TENANT_ID);
Assert.assertNotNull(defaultTenantProfile);
Assert.assertEquals(savedTenantProfile1.getId(), defaultTenantProfile.getId());
result = tenantProfileService.setDefaultTenantProfile(TenantId.SYS_TENANT_ID, savedTenantProfile2.getId());
Assert.assertTrue(result);
TenantProfile setDefaultTenantProfile2 = tenantProfileService.setDefaultTenantProfile(TenantId.SYS_TENANT_ID, savedTenantProfile2.getId());
Assert.assertNotEquals(savedTenantProfile2.isDefault(), setDefaultTenantProfile2.isDefault());
defaultTenantProfile = tenantProfileService.findDefaultTenantProfile(TenantId.SYS_TENANT_ID);
Assert.assertNotNull(defaultTenantProfile);
Assert.assertEquals(savedTenantProfile2.getId(), defaultTenantProfile.getId());

3
ui-ngx/src/app/core/services/menu.models.ts

@ -816,7 +816,8 @@ const defaultUserMenuMap = new Map<Authority, MenuReference[]>([
{id: MenuId.domains},
{id: MenuId.clients}
]
}
},
{id: MenuId.audit_log}
]
}
]

2
ui-ngx/src/app/modules/home/pages/audit-log/audit-log-routing.module.ts

@ -25,7 +25,7 @@ export const auditLogsRoutes: Routes = [
path: 'auditLogs',
component: AuditLogTableComponent,
data: {
auth: [Authority.TENANT_ADMIN],
auth: [Authority.TENANT_ADMIN, Authority.SYS_ADMIN],
title: 'audit-log.audit-logs',
breadcrumb: {
menuId: MenuId.audit_log

4
ui-ngx/src/app/modules/home/pages/tenant-profile/tenant-profile-tabs.component.html

@ -31,4 +31,8 @@
[entityName]="entity.name">
</tb-attribute-table>
</mat-tab>
<mat-tab *ngIf="!isEdit" #auditLogsTab="matTab"
label="{{ 'audit-log.audit-logs' | translate }}">
<tb-audit-log-table detailsMode="true" [active]="auditLogsTab.isActive" [auditLogMode]="auditLogModes.ENTITY" [entityId]="entity.id"></tb-audit-log-table>
</mat-tab>
}

Loading…
Cancel
Save