From 70383a2e81feecf6e76dd5c09e0f62034a886016 Mon Sep 17 00:00:00 2001 From: Matt Magoffin Date: Fri, 8 Nov 2024 06:36:29 +1300 Subject: [PATCH 1/4] Change method name for clarity. --- .../central/web/support/WebServiceGlobalControllerSupport.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/solarnet/common/src/main/java/net/solarnetwork/central/web/support/WebServiceGlobalControllerSupport.java b/solarnet/common/src/main/java/net/solarnetwork/central/web/support/WebServiceGlobalControllerSupport.java index 1a70a5200..fe7666eeb 100644 --- a/solarnet/common/src/main/java/net/solarnetwork/central/web/support/WebServiceGlobalControllerSupport.java +++ b/solarnet/common/src/main/java/net/solarnetwork/central/web/support/WebServiceGlobalControllerSupport.java @@ -332,7 +332,7 @@ public Result handleAuthenticationException(AuthenticationException e, WebReq @ExceptionHandler(AccessDeniedException.class) @ResponseBody @ResponseStatus(code = HttpStatus.FORBIDDEN) - public Result handleAuthenticationException(AccessDeniedException e, WebRequest request) { + public Result handleAccessDeniedException(AccessDeniedException e, WebRequest request) { log.info("AccessDeniedException in request {}: {}", requestDescription(request), e.getMessage()); return error(null, e.getMessage()); } From bce72c552a4f207398152542cb8769398d89d986 Mon Sep 17 00:00:00 2001 From: Matt Magoffin Date: Fri, 8 Nov 2024 07:10:51 +1300 Subject: [PATCH 2/4] Fix exception handling of "too large" request for multipart/form-data requests. --- .../SecurityTokenAuthenticationFilter.java | 14 +++---- ...curityTokenAuthenticationFilterTests.java} | 42 +++++++++++++++---- 2 files changed, 42 insertions(+), 14 deletions(-) rename solarnet/common/src/test/java/net/solarnetwork/central/common/security/web/test/{SecurityTokenAuthenticationFilterTest.java => SecurityTokenAuthenticationFilterTests.java} (96%) diff --git a/solarnet/common/src/main/java/net/solarnetwork/central/security/web/SecurityTokenAuthenticationFilter.java b/solarnet/common/src/main/java/net/solarnetwork/central/security/web/SecurityTokenAuthenticationFilter.java index 190dc1c9e..8d98915ed 100644 --- a/solarnet/common/src/main/java/net/solarnetwork/central/security/web/SecurityTokenAuthenticationFilter.java +++ b/solarnet/common/src/main/java/net/solarnetwork/central/security/web/SecurityTokenAuthenticationFilter.java @@ -161,15 +161,15 @@ protected void doFilterInternal(HttpServletRequest req, HttpServletResponse res, (int) settings.getMinimumSpoolLength().toBytes(), settings.getSpoolDirectory()); HttpServletResponse response = res; - // for multipart requests, force the InputStream to be resolved now so the parameters - // are not parsed by the servlet container - if ( req.getContentType() != null && MediaType.MULTIPART_FORM_DATA - .isCompatibleWith(MimeType.valueOf(req.getContentType())) ) { - request.getContentSHA256(); - } - AuthenticationData data; try { + // for multipart requests, force the InputStream to be resolved now so the parameters + // are not parsed by the servlet container + if ( req.getContentType() != null && MediaType.MULTIPART_FORM_DATA + .isCompatibleWith(MimeType.valueOf(req.getContentType())) ) { + request.getContentSHA256(); + } + data = AuthenticationDataFactory.authenticationDataForAuthorizationHeader(request); } catch ( net.solarnetwork.web.jakarta.security.SecurityException e ) { deny(request, response, new MaxUploadSizeExceededException( diff --git a/solarnet/common/src/test/java/net/solarnetwork/central/common/security/web/test/SecurityTokenAuthenticationFilterTest.java b/solarnet/common/src/test/java/net/solarnetwork/central/common/security/web/test/SecurityTokenAuthenticationFilterTests.java similarity index 96% rename from solarnet/common/src/test/java/net/solarnetwork/central/common/security/web/test/SecurityTokenAuthenticationFilterTest.java rename to solarnet/common/src/test/java/net/solarnetwork/central/common/security/web/test/SecurityTokenAuthenticationFilterTests.java index 85c4cfcea..21b740145 100644 --- a/solarnet/common/src/test/java/net/solarnetwork/central/common/security/web/test/SecurityTokenAuthenticationFilterTest.java +++ b/solarnet/common/src/test/java/net/solarnetwork/central/common/security/web/test/SecurityTokenAuthenticationFilterTests.java @@ -1,5 +1,5 @@ /* ================================================================== - * SecurityTokenAuthenticationFilterTest.java - Dec 13, 2012 6:08:36 AM + * SecurityTokenAuthenticationFilterTests.java - Dec 13, 2012 6:08:36 AM * * Copyright 2007-2012 SolarNetwork.net Dev Team * @@ -38,6 +38,7 @@ import static org.junit.Assert.assertNotNull; import java.io.IOException; import java.io.UnsupportedEncodingException; +import java.nio.charset.StandardCharsets; import java.time.Instant; import java.util.ArrayList; import java.util.Date; @@ -45,10 +46,6 @@ import java.util.LinkedHashSet; import java.util.List; import java.util.Map; -import jakarta.servlet.FilterChain; -import jakarta.servlet.ServletException; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; import org.easymock.EasyMock; import org.junit.Before; import org.junit.Test; @@ -62,6 +59,10 @@ import org.springframework.security.core.userdetails.User; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.util.AntPathMatcher; +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; import net.solarnetwork.central.security.AuthenticatedToken; import net.solarnetwork.central.security.BasicSecurityPolicy; import net.solarnetwork.central.security.SecurityTokenType; @@ -74,9 +75,9 @@ * Unit tests for the {@link SecurityTokenAuthenticationFilter} class. * * @author matt - * @version 2.1 + * @version 2.2 */ -public class SecurityTokenAuthenticationFilterTest { +public class SecurityTokenAuthenticationFilterTests { private static final String HTTP_HEADER_AUTH = "Authorization"; private static final String TEST_AUTH_TOKEN = "12345678901234567890"; @@ -797,4 +798,31 @@ public void apiPathV2MultiWithInvertedDenied() throws ServletException, IOExcept validateUnauthorizedResponse(AuthenticationScheme.V2, "Access denied"); } + @Test + public void multipartFormDataRequestTooLargeV2() throws ServletException, IOException { + // GIVEN + final Date now = new Date(); + MockHttpServletRequest request = new MockHttpServletRequest("POST", "/mock/path/here"); + request.setContentType(MediaType.MULTIPART_FORM_DATA_VALUE); + request.setContent("foo=bar".getBytes(StandardCharsets.UTF_8)); + request.addHeader("Date", now); + setupAuthorizationHeader(request, + createAuthorizationHeaderV2Value(TEST_AUTH_TOKEN, TEST_PASSWORD, request, now)); + + // create new request as we read the input stream above + request = new MockHttpServletRequest("POST", "/mock/path/here"); + request.setContentType(MediaType.MULTIPART_FORM_DATA_VALUE); + request.setContent("foo=bar".getBytes(StandardCharsets.UTF_8)); + request.addHeader("Date", now); + + // WHEN + filter.setMaxRequestBodySize(1); + replay(filterChain, userDetailsService); + filter.doFilter(request, response, filterChain); + + // THEN + verify(filterChain, userDetailsService); + assertThat("Status code", response.getStatus(), is(403)); + } + } From f3bc49038f3716f4b5b62a210de1d3416910214c Mon Sep 17 00:00:00 2001 From: Matt Magoffin Date: Mon, 11 Nov 2024 11:07:52 +1300 Subject: [PATCH 3/4] Optimize the SQL query used for the /range/sources APIs to avoid joins on sn_node and sn_loc. Those joins only needed to return the time zone of the nodes, but these APIs do not expose that. --- .../net/solarnetwork/central/query/biz/dao/DaoQueryBiz.java | 5 +++-- .../central/query/biz/dao/test/DaoQueryBizTests.java | 4 +++- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/solarnet/solarquery/src/main/java/net/solarnetwork/central/query/biz/dao/DaoQueryBiz.java b/solarnet/solarquery/src/main/java/net/solarnetwork/central/query/biz/dao/DaoQueryBiz.java index 623bc1430..2e9f16aaa 100644 --- a/solarnet/solarquery/src/main/java/net/solarnetwork/central/query/biz/dao/DaoQueryBiz.java +++ b/solarnet/solarquery/src/main/java/net/solarnetwork/central/query/biz/dao/DaoQueryBiz.java @@ -74,6 +74,7 @@ import net.solarnetwork.central.datum.v2.domain.Datum; import net.solarnetwork.central.datum.v2.domain.DatumDateInterval; import net.solarnetwork.central.datum.v2.domain.DatumPK; +import net.solarnetwork.central.datum.v2.domain.ObjectDatumStreamMetadataId; import net.solarnetwork.central.datum.v2.domain.ReadingDatum; import net.solarnetwork.central.datum.v2.support.DatumUtils; import net.solarnetwork.central.datum.v2.support.StreamDatumFilteredResultsProcessor; @@ -177,7 +178,7 @@ public Set findAvailableSources(GeneralNodeDatumFilter filter) { BasicDatumCriteria c = DatumUtils.criteriaFromFilter(filter); c.setObjectKind(ObjectDatumKind.Node); validateDatumCriteria(c); - Iterable results = metaDao.findDatumStreamMetadata(c); + Iterable results = metaDao.findDatumStreamMetadataIds(c); return stream(results.spliterator(), false) .map(e -> new NodeSourcePK(e.getObjectId(), e.getSourceId())) .collect(toCollection(LinkedHashSet::new)); @@ -200,7 +201,7 @@ public Set findAvailableSources(SecurityActor actor, DatumFilter f } else { return Collections.emptySet(); } - Iterable results = metaDao.findDatumStreamMetadata(c); + Iterable results = metaDao.findDatumStreamMetadataIds(c); return stream(results.spliterator(), false) .map(e -> new NodeSourcePK(e.getObjectId(), e.getSourceId())) .collect(toCollection(LinkedHashSet::new)); diff --git a/solarnet/solarquery/src/test/java/net/solarnetwork/central/query/biz/dao/test/DaoQueryBizTests.java b/solarnet/solarquery/src/test/java/net/solarnetwork/central/query/biz/dao/test/DaoQueryBizTests.java index 035b70065..92d132b88 100644 --- a/solarnet/solarquery/src/test/java/net/solarnetwork/central/query/biz/dao/test/DaoQueryBizTests.java +++ b/solarnet/solarquery/src/test/java/net/solarnetwork/central/query/biz/dao/test/DaoQueryBizTests.java @@ -28,6 +28,7 @@ import static java.util.Collections.singletonMap; import static java.util.UUID.randomUUID; import static net.solarnetwork.central.datum.v2.domain.BasicObjectDatumStreamMetadata.emptyMeta; +import static net.solarnetwork.central.datum.v2.domain.ObjectDatumStreamMetadataId.idForMetadata; import static net.solarnetwork.domain.datum.DatumProperties.propertiesOf; import static net.solarnetwork.domain.datum.DatumPropertiesStatistics.statisticsOf; import static net.solarnetwork.util.NumberUtils.decimalArray; @@ -341,7 +342,8 @@ public void findSources_dataToken() { Capture filterCaptor = new Capture<>(); ObjectDatumStreamMetadata meta = emptyMeta(UUID.randomUUID(), "UTC", ObjectDatumKind.Node, TEST_NODE_ID, TEST_SOURCE_ID); - expect(metaDao.findDatumStreamMetadata(capture(filterCaptor))).andReturn(singleton(meta)); + expect(metaDao.findDatumStreamMetadataIds(capture(filterCaptor))) + .andReturn(singleton(idForMetadata(meta))); // WHEN replayAll(); From da5b70557760c62d617070b570da15b244df21a3 Mon Sep 17 00:00:00 2001 From: Matt Magoffin Date: Mon, 11 Nov 2024 11:09:44 +1300 Subject: [PATCH 4/4] Bump versions. --- solarnet/common/build.gradle | 2 +- solarnet/solarquery/build.gradle | 2 +- solarnet/solaruser/build.gradle | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/solarnet/common/build.gradle b/solarnet/common/build.gradle index 87cfde318..431d4335d 100644 --- a/solarnet/common/build.gradle +++ b/solarnet/common/build.gradle @@ -16,7 +16,7 @@ dependencyManagement { } description = 'SolarNet: Common' -version = '2.21.1' +version = '2.21.2' base { archivesName = 'solarnet-common' diff --git a/solarnet/solarquery/build.gradle b/solarnet/solarquery/build.gradle index e359b8e2b..fb74bcb1b 100644 --- a/solarnet/solarquery/build.gradle +++ b/solarnet/solarquery/build.gradle @@ -9,7 +9,7 @@ apply plugin: 'org.springframework.boot' apply plugin: 'io.spring.dependency-management' description = 'SolarQuery' -version = '2.8.2' +version = '2.8.3' base { archivesName = 'solarquery' diff --git a/solarnet/solaruser/build.gradle b/solarnet/solaruser/build.gradle index 43d90860d..32deed449 100644 --- a/solarnet/solaruser/build.gradle +++ b/solarnet/solaruser/build.gradle @@ -11,7 +11,7 @@ apply plugin: 'org.springframework.boot' apply plugin: 'io.spring.dependency-management' description = 'SolarUser' -version = '2.28.1' +version = '2.28.2' base { archivesName = 'solaruser'