From 934d742cc6b70edda21c99cc523f6baea8c1f612 Mon Sep 17 00:00:00 2001 From: Vladyslav_Prykhodko Date: Mon, 16 Mar 2026 15:18:37 +0200 Subject: [PATCH 1/2] Fix missing translation for Polylines toggle in map settings Replace hardcoded "Polylines" text with existing i18n key widgets.maps.overlays.polylines to match other overlay toggles. Fixes #15234 --- .../widget/lib/settings/common/map/map-settings.component.html | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/map-settings.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/map-settings.component.html index 4f95435f35..083178ddb3 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/map-settings.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/map-settings.component.html @@ -45,8 +45,7 @@ {{ 'widgets.maps.overlays.markers' | translate }} {{ 'widgets.maps.overlays.polygons' | translate }} {{ 'widgets.maps.overlays.circles' | translate }} - //todo translation - Polylines + {{ 'widgets.maps.overlays.polylines' | translate }} Date: Wed, 18 Mar 2026 10:52:07 +0200 Subject: [PATCH 2/2] Sanitize database error messages Route DataAccessException and PersistenceException (including bare ConstraintViolationException) to a unified handler that extracts the constraint name and returns "Constraint violation: " instead of the raw PSQLException message. Other DB errors continue to return the generic "Database error" response. Adds DaoUtil.extractConstraintViolation helper and an integration test that verifies no SQL details leak when an FK constraint is violated. --- .../ThingsboardErrorResponseHandler.java | 19 +++++++++++++---- .../server/controller/AbstractWebTest.java | 2 +- .../controller/TenantControllerTest.java | 21 +++++++++++++++++++ .../org/thingsboard/server/dao/DaoUtil.java | 10 +++++++++ 4 files changed, 47 insertions(+), 5 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/exception/ThingsboardErrorResponseHandler.java b/application/src/main/java/org/thingsboard/server/exception/ThingsboardErrorResponseHandler.java index 3dd15b7255..bcf897f8fb 100644 --- a/application/src/main/java/org/thingsboard/server/exception/ThingsboardErrorResponseHandler.java +++ b/application/src/main/java/org/thingsboard/server/exception/ThingsboardErrorResponseHandler.java @@ -15,6 +15,7 @@ */ package org.thingsboard.server.exception; +import jakarta.persistence.PersistenceException; import jakarta.servlet.RequestDispatcher; import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletRequest; @@ -50,6 +51,7 @@ import org.thingsboard.server.common.data.exception.ThingsboardErrorCode; import org.thingsboard.server.common.data.exception.ThingsboardException; import org.thingsboard.server.common.msg.tools.MaxPayloadSizeExceededException; import org.thingsboard.server.common.msg.tools.TbRateLimitsException; +import org.thingsboard.server.dao.DaoUtil; import org.thingsboard.server.service.security.exception.AuthMethodNotSupportedException; import org.thingsboard.server.service.security.exception.JwtExpiredTokenException; import org.thingsboard.server.service.security.exception.UserPasswordExpiredException; @@ -154,8 +156,8 @@ public class ThingsboardErrorResponseHandler extends ResponseEntityExceptionHand handleAuthenticationException((AuthenticationException) exception, response); } else if (exception instanceof MaxPayloadSizeExceededException) { handleMaxPayloadSizeExceededException(response, (MaxPayloadSizeExceededException) exception); - } else if (exception instanceof DataAccessException e) { - handleDatabaseException(e, response); + } else if (exception instanceof DataAccessException || exception instanceof PersistenceException) { + handleDatabaseException(exception, response); } else { response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value()); JacksonUtil.writeValue(response.getWriter(), ThingsboardErrorResponse.of(exception.getMessage(), @@ -209,8 +211,17 @@ public class ThingsboardErrorResponseHandler extends ResponseEntityExceptionHand private void handleDatabaseException(Throwable databaseException, HttpServletResponse response) throws IOException { ThingsboardErrorResponse errorResponse; - if (databaseException instanceof ConstraintViolationException) { - errorResponse = ThingsboardErrorResponse.of(ExceptionUtils.getRootCause(databaseException).getMessage(), ThingsboardErrorCode.BAD_REQUEST_PARAMS, HttpStatus.BAD_REQUEST); + ConstraintViolationException constraintViolationException = DaoUtil.extractConstraintViolation(databaseException); + if (constraintViolationException != null) { + log.debug("Constraint violation: {}", ExceptionUtils.getRootCauseMessage(databaseException)); + String constraintName = constraintViolationException.getConstraintName(); + String userMessage; + if (constraintName != null && !constraintName.isEmpty()) { + userMessage = "Constraint violation: " + constraintName; + } else { + userMessage = "Constraint violation"; + } + errorResponse = ThingsboardErrorResponse.of(userMessage, ThingsboardErrorCode.BAD_REQUEST_PARAMS, HttpStatus.BAD_REQUEST); } else { log.warn("Database error: {} - {}", databaseException.getClass().getSimpleName(), ExceptionUtils.getRootCauseMessage(databaseException)); errorResponse = ThingsboardErrorResponse.of("Database error", ThingsboardErrorCode.DATABASE, HttpStatus.INTERNAL_SERVER_ERROR); diff --git a/application/src/test/java/org/thingsboard/server/controller/AbstractWebTest.java b/application/src/test/java/org/thingsboard/server/controller/AbstractWebTest.java index 5f4b7e952f..bbf3a3467e 100644 --- a/application/src/test/java/org/thingsboard/server/controller/AbstractWebTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/AbstractWebTest.java @@ -1061,7 +1061,7 @@ public abstract class AbstractWebTest extends AbstractInMemoryStorageTest { assertThat(findRelationsByTo(entityTo)).hasSize(1); doDelete(urlDelete) - .andExpect(status().isInternalServerError()); + .andExpect(status().isBadRequest()); assertThat(findRelationsByTo(entityTo)).hasSize(1); } finally { diff --git a/application/src/test/java/org/thingsboard/server/controller/TenantControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/TenantControllerTest.java index 0f87dc1fd3..cc217b05d5 100644 --- a/application/src/test/java/org/thingsboard/server/controller/TenantControllerTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/TenantControllerTest.java @@ -43,6 +43,7 @@ import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.exception.TenantNotFoundException; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.id.TenantProfileId; import org.thingsboard.server.common.data.msg.TbMsgType; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; @@ -868,6 +869,26 @@ public class TenantControllerTest extends AbstractControllerTest { Mockito.reset(tbClusterService); } + @Test + public void testSaveTenantWithNonExistentTenantProfileId() throws Exception { + loginSysAdmin(); + Tenant tenant = new Tenant(); + tenant.setTitle("My tenant"); + tenant.setTenantProfileId(new TenantProfileId(UUID.randomUUID())); + + String responseBody = doPost("/api/tenant", tenant) + .andExpect(status().isBadRequest()) + .andReturn().getResponse().getContentAsString(); + + // Verify sanitized message format + assertThat(responseBody).contains("Constraint violation: fk_tenant_profile"); + // Verify raw SQL details are not returned + assertThat(responseBody).doesNotContain("could not execute statement"); + assertThat(responseBody).doesNotContain("insert or update on table"); + assertThat(responseBody).doesNotContain("tenant_profile_id"); + assertThat(responseBody).doesNotContain("is not present in table"); + } + private void testBroadcastEntityStateChangeEventNeverTenant() { Mockito.verify(tbClusterService, never()).onTenantChange(Mockito.any(Tenant.class), Mockito.isNull()); diff --git a/dao/src/main/java/org/thingsboard/server/dao/DaoUtil.java b/dao/src/main/java/org/thingsboard/server/dao/DaoUtil.java index a19ba6c0e7..4b469851cd 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/DaoUtil.java +++ b/dao/src/main/java/org/thingsboard/server/dao/DaoUtil.java @@ -15,6 +15,7 @@ */ package org.thingsboard.server.dao; +import org.hibernate.exception.ConstraintViolationException; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Pageable; @@ -202,4 +203,13 @@ public final class DaoUtil { .collect(Collectors.toList()); } + public static ConstraintViolationException extractConstraintViolation(Throwable t) { + if (t instanceof ConstraintViolationException cve) { + return cve; + } else if (t != null && t.getCause() instanceof ConstraintViolationException cve) { + return cve; + } + return null; + } + }