From 92e7c2cc2b24f81dff32d9a181d874c75b95b884 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 27 Nov 2025 08:26:08 +0000 Subject: [PATCH 1/8] Bump org.springframework.boot from 3.5.6 to 4.0.0 Bumps [org.springframework.boot](https://github.com/spring-projects/spring-boot) from 3.5.6 to 4.0.0. - [Release notes](https://github.com/spring-projects/spring-boot/releases) - [Commits](https://github.com/spring-projects/spring-boot/compare/v3.5.6...v4.0.0) --- updated-dependencies: - dependency-name: org.springframework.boot dependency-version: 4.0.0 dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index afb98979..f8b8540a 100644 --- a/build.gradle +++ b/build.gradle @@ -11,7 +11,7 @@ buildscript { plugins { id "java" - id "org.springframework.boot" version "3.5.6" + id "org.springframework.boot" version "4.0.0" id "idea" id "com.adarshr.test-logger" version "4.0.0" } From 7c1ad0eced396bca001320840051aa67bb23b375 Mon Sep 17 00:00:00 2001 From: kubo Date: Sun, 30 Nov 2025 23:31:14 +0100 Subject: [PATCH 2/8] WIP SB 4.0.0 --- build.gradle | 7 ++- .../api/AbstractIntegrationTest.java | 4 +- .../api/clan/ClanControllerTest.java | 14 +++--- .../api/config/LeagueDbTestContainers.java | 2 +- .../api/config/MainDbTestContainers.java | 2 +- .../api/user/UsersControllerTest.java | 50 +++++++++---------- .../api/challonge/ChallongeController.java | 6 +-- .../com/faforever/api/clan/ClanService.java | 2 +- .../api/config/FafDatasourceConfig.java | 2 +- .../api/config/FafDatastoreConfig.java | 4 +- .../api/config/LeagueDatasourceConfig.java | 2 +- .../api/config/LeagueDatastoreConfig.java | 2 +- .../com/faforever/api/config/MvcConfig.java | 21 ++------ .../api/config/RabbitConfiguration.java | 12 ++--- .../api/config/elide/ElideConfig.java | 5 ++ .../config/security/MethodSecurityConfig.java | 4 +- .../config/security/WebSecurityConfig.java | 2 +- .../api/data/converter/JsonConverter.java | 9 ++-- .../com/faforever/api/data/domain/Mod.java | 2 +- .../api/deployment/ExeUploaderController.java | 4 +- .../api/error/ErrorJsonSerializer.java | 28 ++++++----- .../faforever/api/forum/NodebbService.java | 8 +-- .../com/faforever/api/map/MapsController.java | 14 +++--- .../api/security/FafTokenService.java | 8 +-- .../faforever/api/security/JwtService.java | 2 +- .../faforever/api/user/RecaptchaService.java | 2 +- .../com/faforever/api/user/SteamService.java | 8 ++- .../faforever/api/user/UsersController.java | 4 +- .../resources/config/application-local.yml | 2 +- .../faforever/api/clan/ClanServiceTest.java | 2 +- .../deployment/ExeUploaderControllerTest.java | 2 +- .../api/email/JavaEmailSenderTest.java | 3 +- .../GlobalControllerExceptionHandlerTest.java | 12 ++--- .../api/security/FafTokenServiceTest.java | 6 +-- .../api/user/RecaptchaServiceTest.java | 2 +- 35 files changed, 124 insertions(+), 135 deletions(-) diff --git a/build.gradle b/build.gradle index f8b8540a..bc9d69dd 100644 --- a/build.gradle +++ b/build.gradle @@ -143,9 +143,12 @@ dependencies { implementation("org.checkerframework:checker-qual:3.51.0") implementation("org.springframework.boot:spring-boot-starter-oauth2-resource-server") implementation("org.springframework.boot:spring-boot-starter-data-jpa") - implementation("org.springframework.boot:spring-boot-starter-web") + implementation("org.springframework.boot:spring-boot-starter-webmvc") + implementation("org.springframework.boot:spring-boot-starter-jackson") + implementation("org.springframework.boot:spring-boot-starter-restclient") implementation("org.springframework.boot:spring-boot-starter-amqp") implementation("org.springframework.boot:spring-boot-starter-security") + implementation("org.springframework.security:spring-security-access") implementation("org.springframework.boot:spring-boot-starter-thymeleaf") implementation("org.springframework.boot:spring-boot-starter-mail") implementation("org.springframework.boot:spring-boot-starter-validation") @@ -154,6 +157,8 @@ dependencies { implementation("com.github.ben-manes.caffeine:caffeine") testImplementation("org.springframework.boot:spring-boot-starter-test") + testImplementation("org.springframework.boot:spring-boot-starter-webmvc-test") + testImplementation("org.springframework.boot:spring-boot-starter-security-test") testImplementation("org.springframework.restdocs:spring-restdocs-mockmvc") testImplementation("org.springframework.security:spring-security-test") testImplementation("com.jayway.jsonpath:json-path") diff --git a/src/inttest/java/com/faforever/api/AbstractIntegrationTest.java b/src/inttest/java/com/faforever/api/AbstractIntegrationTest.java index 60121360..b5d4f6d7 100644 --- a/src/inttest/java/com/faforever/api/AbstractIntegrationTest.java +++ b/src/inttest/java/com/faforever/api/AbstractIntegrationTest.java @@ -11,8 +11,6 @@ import com.faforever.commons.api.dto.ModerationReport; import com.faforever.commons.api.dto.Player; import com.faforever.commons.api.dto.Tutorial; -import com.fasterxml.jackson.annotation.JsonInclude.Include; -import com.fasterxml.jackson.databind.ObjectMapper; import com.github.jasminb.jsonapi.JSONAPIDocument; import com.github.jasminb.jsonapi.ResourceConverter; import com.github.jasminb.jsonapi.exceptions.DocumentSerializationException; @@ -38,6 +36,8 @@ import org.testcontainers.containers.Network; import org.testcontainers.containers.RabbitMQContainer; import org.testcontainers.junit.jupiter.Testcontainers; +import tools.jackson.annotation.JsonInclude.Include; +import tools.jackson.databind.ObjectMapper; import jakarta.transaction.Transactional; import java.time.format.DateTimeFormatter; diff --git a/src/inttest/java/com/faforever/api/clan/ClanControllerTest.java b/src/inttest/java/com/faforever/api/clan/ClanControllerTest.java index c79c7737..06d95d55 100644 --- a/src/inttest/java/com/faforever/api/clan/ClanControllerTest.java +++ b/src/inttest/java/com/faforever/api/clan/ClanControllerTest.java @@ -7,11 +7,11 @@ import com.faforever.api.player.PlayerRepository; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.http.HttpHeaders; import org.springframework.test.context.jdbc.Sql; import org.springframework.test.context.jdbc.Sql.ExecutionPhase; import org.springframework.test.web.servlet.MvcResult; import org.springframework.test.web.servlet.ResultActions; +import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; import static org.hamcrest.Matchers.is; @@ -45,7 +45,7 @@ public class ClanControllerTest extends AbstractIntegrationTest { @Test public void meDataWithoutClan() throws Exception { - Player player = playerRepository.findById(USERID_USER).orElseThrow(); + Player player = playerRepository.getReferenceById(USERID_USER); mockMvc.perform(get("/clans/me") .with(getOAuthTokenForUserId(USERID_USER))) @@ -78,7 +78,7 @@ public void createClanWithSuccess() throws Exception { assertNull(player.getClan()); assertFalse(clanRepository.findOneByName(NEW_CLAN_NAME).isPresent()); - MultiValueMap params = new HttpHeaders(); + MultiValueMap params = new LinkedMultiValueMap<>(); params.add("name", NEW_CLAN_NAME); params.add("tag", NEW_CLAN_TAG); params.add("description", NEW_CLAN_DESCRIPTION); @@ -98,7 +98,7 @@ public void createClanWithSuccess() throws Exception { @Test public void createClanWithoutAuth() throws Exception { - MultiValueMap params = new HttpHeaders(); + MultiValueMap params = new LinkedMultiValueMap<>(); params.add("name", NEW_CLAN_NAME); params.add("tag", NEW_CLAN_TAG); params.add("description", NEW_CLAN_DESCRIPTION); @@ -116,7 +116,7 @@ public void createClanWithExistingName() throws Exception { assertNull(player.getClan()); assertTrue(clanRepository.findOneByName(EXISTING_CLAN).isPresent()); - MultiValueMap params = new HttpHeaders(); + MultiValueMap params = new LinkedMultiValueMap<>(); params.add("name", EXISTING_CLAN); params.add("tag", NEW_CLAN_TAG); params.add("description", NEW_CLAN_DESCRIPTION); @@ -138,7 +138,7 @@ public void createClanWithExistingTag() throws Exception { assertNull(player.getClan()); assertFalse(clanRepository.findOneByName(NEW_CLAN_NAME).isPresent()); - MultiValueMap params = new HttpHeaders(); + MultiValueMap params = new LinkedMultiValueMap<>(); params.add("name", NEW_CLAN_NAME); params.add("tag", "123"); params.add("description", NEW_CLAN_DESCRIPTION); @@ -160,7 +160,7 @@ public void createSecondClan() throws Exception { assertNotNull(player.getClan()); assertFalse(clanRepository.findOneByName(NEW_CLAN_NAME).isPresent()); - MultiValueMap params = new HttpHeaders(); + MultiValueMap params = new LinkedMultiValueMap<>(); params.add("name", NEW_CLAN_NAME); params.add("tag", NEW_CLAN_TAG); params.add("description", NEW_CLAN_DESCRIPTION); diff --git a/src/inttest/java/com/faforever/api/config/LeagueDbTestContainers.java b/src/inttest/java/com/faforever/api/config/LeagueDbTestContainers.java index 544aaeb4..902d5e01 100644 --- a/src/inttest/java/com/faforever/api/config/LeagueDbTestContainers.java +++ b/src/inttest/java/com/faforever/api/config/LeagueDbTestContainers.java @@ -5,8 +5,8 @@ import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; -import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties; import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.boot.jdbc.autoconfigure.DataSourceProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.testcontainers.containers.GenericContainer; diff --git a/src/inttest/java/com/faforever/api/config/MainDbTestContainers.java b/src/inttest/java/com/faforever/api/config/MainDbTestContainers.java index f1095a06..335e6bbe 100644 --- a/src/inttest/java/com/faforever/api/config/MainDbTestContainers.java +++ b/src/inttest/java/com/faforever/api/config/MainDbTestContainers.java @@ -5,8 +5,8 @@ import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; -import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties; import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.boot.jdbc.autoconfigure.DataSourceProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Primary; diff --git a/src/inttest/java/com/faforever/api/user/UsersControllerTest.java b/src/inttest/java/com/faforever/api/user/UsersControllerTest.java index f8d053dc..ef179736 100644 --- a/src/inttest/java/com/faforever/api/user/UsersControllerTest.java +++ b/src/inttest/java/com/faforever/api/user/UsersControllerTest.java @@ -12,10 +12,10 @@ import com.faforever.api.security.OAuthScope; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.http.HttpHeaders; import org.springframework.security.test.context.support.WithAnonymousUser; import org.springframework.test.context.bean.override.mockito.MockitoBean; import org.springframework.test.web.servlet.MvcResult; +import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; import java.time.Duration; @@ -63,7 +63,7 @@ public class UsersControllerTest extends AbstractIntegrationTest { @Test public void registerWithSuccess() throws Exception { - MultiValueMap params = new HttpHeaders(); + MultiValueMap params = new LinkedMultiValueMap<>(); params.add("username", NEW_USER); params.add("email", NEW_EMAIL); @@ -76,7 +76,7 @@ public void registerWithSuccess() throws Exception { @Test public void registerWithAuthentication() throws Exception { - MultiValueMap params = new HttpHeaders(); + MultiValueMap params = new LinkedMultiValueMap<>(); params.add("username", NEW_USER); params.add("email", NEW_EMAIL); params.add("password", NEW_PASSWORD); @@ -109,7 +109,7 @@ public void activateWithSuccess() throws Exception { @Test public void changePasswordWithSuccess() throws Exception { - MultiValueMap params = new HttpHeaders(); + MultiValueMap params = new LinkedMultiValueMap<>(); params.add("currentPassword", AUTH_USER); params.add("newPassword", NEW_PASSWORD); @@ -125,7 +125,7 @@ public void changePasswordWithSuccess() throws Exception { @Test public void changePasswordWithoutScope() throws Exception { - MultiValueMap params = new HttpHeaders(); + MultiValueMap params = new LinkedMultiValueMap<>(); params.add("currentPassword", AUTH_USER); params.add("newPassword", NEW_PASSWORD); @@ -138,7 +138,7 @@ public void changePasswordWithoutScope() throws Exception { @Test public void changePasswordWithWrongPassword() throws Exception { - MultiValueMap params = new HttpHeaders(); + MultiValueMap params = new LinkedMultiValueMap<>(); params.add("currentPassword", "wrongPassword"); params.add("newPassword", NEW_PASSWORD); @@ -154,7 +154,7 @@ public void changePasswordWithWrongPassword() throws Exception { @Test public void changeEmailWithSuccess() throws Exception { - MultiValueMap params = new HttpHeaders(); + MultiValueMap params = new LinkedMultiValueMap<>(); params.add("currentPassword", AUTH_USER); params.add("newEmail", NEW_EMAIL); @@ -170,7 +170,7 @@ public void changeEmailWithSuccess() throws Exception { @Test public void changeEmailWithoutScope() throws Exception { - MultiValueMap params = new HttpHeaders(); + MultiValueMap params = new LinkedMultiValueMap<>(); params.add("currentPassword", AUTH_USER); params.add("newEmail", NEW_EMAIL); @@ -183,7 +183,7 @@ public void changeEmailWithoutScope() throws Exception { @Test public void changeEmailWithWrongPassword() throws Exception { - MultiValueMap params = new HttpHeaders(); + MultiValueMap params = new LinkedMultiValueMap<>(); params.add("currentPassword", "wrongPassword"); params.add("newEmail", NEW_EMAIL); @@ -199,7 +199,7 @@ public void changeEmailWithWrongPassword() throws Exception { @Test public void changeEmailWithInvalidEmail() throws Exception { - MultiValueMap params = new HttpHeaders(); + MultiValueMap params = new LinkedMultiValueMap<>(); params.add("currentPassword", AUTH_USER); params.add("newEmail", "invalid-email"); @@ -215,7 +215,7 @@ public void changeEmailWithInvalidEmail() throws Exception { @Test public void resetPasswordWithUsername() throws Exception { - MultiValueMap params = new HttpHeaders(); + MultiValueMap params = new LinkedMultiValueMap<>(); params.add("identifier", AUTH_USER); mockMvc.perform( @@ -228,7 +228,7 @@ public void resetPasswordWithUsername() throws Exception { @Test public void resetPasswordWithEmail() throws Exception { - MultiValueMap params = new HttpHeaders(); + MultiValueMap params = new LinkedMultiValueMap<>(); params.add("identifier", "user@faforever.com"); mockMvc.perform( @@ -344,7 +344,7 @@ public void linkToSteamAlreadyLinkedAccount() throws Exception { @Test public void changeUsernameUnauthorized() throws Exception { - MultiValueMap params = new HttpHeaders(); + MultiValueMap params = new LinkedMultiValueMap<>(); params.add("newUsername", NEW_USER); mockMvc.perform( @@ -355,7 +355,7 @@ public void changeUsernameUnauthorized() throws Exception { @Test public void changeUsernameWithWrongScope() throws Exception { - MultiValueMap params = new HttpHeaders(); + MultiValueMap params = new LinkedMultiValueMap<>(); params.add("newUsername", NEW_USER); mockMvc.perform( @@ -369,7 +369,7 @@ public void changeUsernameWithWrongScope() throws Exception { public void changeUsernameSuccess() throws Exception { assertThat(userRepository.findById(1).orElseThrow().getLogin(), is(AUTH_USER)); - MultiValueMap params = new HttpHeaders(); + MultiValueMap params = new LinkedMultiValueMap<>(); params.add("newUsername", NEW_USER); mockMvc.perform( @@ -385,7 +385,7 @@ public void changeUsernameSuccess() throws Exception { public void changeUsernameForcedByUser() throws Exception { assertThat(userRepository.findById(1).orElseThrow().getLogin(), is(AUTH_USER)); - MultiValueMap params = new HttpHeaders(); + MultiValueMap params = new LinkedMultiValueMap<>(); params.add("newUsername", NEW_USER); mockMvc.perform( @@ -400,7 +400,7 @@ public void changeUsernameForcedByUser() throws Exception { @Test public void changeUsernameForcedByModerator() throws Exception { - MultiValueMap params = new HttpHeaders(); + MultiValueMap params = new LinkedMultiValueMap<>(); params.add("newUsername", NEW_USER); mockMvc.perform( @@ -414,7 +414,7 @@ public void changeUsernameForcedByModerator() throws Exception { @Test public void changeUsernameForcedByModeratorWithoutScope() throws Exception { - MultiValueMap params = new HttpHeaders(); + MultiValueMap params = new LinkedMultiValueMap<>(); params.add("newUsername", NEW_USER); mockMvc.perform( @@ -426,7 +426,7 @@ public void changeUsernameForcedByModeratorWithoutScope() throws Exception { @Test public void changeUsernameForcedByModeratorWithoutRole() throws Exception { - MultiValueMap params = new HttpHeaders(); + MultiValueMap params = new LinkedMultiValueMap<>(); params.add("newUsername", NEW_USER); mockMvc.perform( @@ -440,7 +440,7 @@ public void changeUsernameForcedByModeratorWithoutRole() throws Exception { public void changeUsernameTooEarly() throws Exception { assertThat(userRepository.findById(2).orElseThrow().getLogin(), is(AUTH_MODERATOR)); - MultiValueMap params = new HttpHeaders(); + MultiValueMap params = new LinkedMultiValueMap<>(); params.add("newUsername", NEW_USER); MvcResult result = mockMvc.perform( @@ -457,7 +457,7 @@ public void changeUsernameTooEarly() throws Exception { @Test public void changeUsernameTooEarlyButForced() throws Exception { - MultiValueMap params = new HttpHeaders(); + MultiValueMap params = new LinkedMultiValueMap<>(); params.add("newUsername", NEW_USER); mockMvc.perform( @@ -480,7 +480,7 @@ public void resyncAccountSuccess() throws Exception { @Test public void buildGogProfileToken() throws Exception { - MultiValueMap params = new HttpHeaders(); + MultiValueMap params = new LinkedMultiValueMap<>(); params.add("gogUsername", "someUsername"); when(gogService.buildGogToken(any())).thenReturn("theToken"); @@ -496,7 +496,7 @@ public void buildGogProfileToken() throws Exception { @Test public void linkToGogWithoutOAuthScopeFails() throws Exception { - MultiValueMap params = new HttpHeaders(); + MultiValueMap params = new LinkedMultiValueMap<>(); params.add("gogUsername", "someUsername"); mockMvc.perform( @@ -508,7 +508,7 @@ public void linkToGogWithoutOAuthScopeFails() throws Exception { @Test public void linkToGogSuccess() throws Exception { - MultiValueMap params = new HttpHeaders(); + MultiValueMap params = new LinkedMultiValueMap<>(); params.add("gogUsername", "someUsername"); when(gogService.buildGogToken(any())).thenReturn("theToken"); @@ -524,7 +524,7 @@ public void linkToGogSuccess() throws Exception { @Test public void linkToGogAlreadyLinked() throws Exception { - MultiValueMap params = new HttpHeaders(); + MultiValueMap params = new LinkedMultiValueMap<>(); params.add("gogUsername", "username"); when(gogService.buildGogToken(any())).thenReturn("theToken"); diff --git a/src/main/java/com/faforever/api/challonge/ChallongeController.java b/src/main/java/com/faforever/api/challonge/ChallongeController.java index fe311970..9a02eb53 100644 --- a/src/main/java/com/faforever/api/challonge/ChallongeController.java +++ b/src/main/java/com/faforever/api/challonge/ChallongeController.java @@ -4,7 +4,7 @@ import com.faforever.api.config.FafApiProperties.Challonge; import lombok.RequiredArgsConstructor; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; -import org.springframework.boot.web.client.RestTemplateBuilder; +import org.springframework.boot.restclient.RestTemplateBuilder; import org.springframework.cache.annotation.Cacheable; import org.springframework.context.annotation.Scope; import org.springframework.context.annotation.ScopedProxyMode; @@ -62,14 +62,14 @@ private static String translateRoute(HttpServletRequest request) { @Async @Cacheable(cacheNames = CHALLONGE_READ_CACHE_NAME) - @RequestMapping(path = "/**", method = GET, produces = MediaType.APPLICATION_JSON_UTF8_VALUE) + @RequestMapping(path = "/**", method = GET, produces = MediaType.APPLICATION_JSON_VALUE) public CompletableFuture> get(HttpServletRequest request) { return CompletableFuture.completedFuture(restTemplate.getForEntity(translateRoute(request), String.class, Map.of())); } @Async @Secured("ROLE_TOURNAMENT_DIRECTOR") - @RequestMapping(path = "/**", method = {POST, PUT, DELETE}, produces = MediaType.APPLICATION_JSON_UTF8_VALUE) + @RequestMapping(path = "/**", method = {POST, PUT, DELETE}, produces = MediaType.APPLICATION_JSON_VALUE) public CompletableFuture> write(@RequestBody(required = false) Object body, HttpMethod method, HttpServletRequest request) { return CompletableFuture.completedFuture(restTemplate.exchange(translateRoute(request), method, new HttpEntity<>(body), String.class)); } diff --git a/src/main/java/com/faforever/api/clan/ClanService.java b/src/main/java/com/faforever/api/clan/ClanService.java index 507a6013..d3600a1d 100644 --- a/src/main/java/com/faforever/api/clan/ClanService.java +++ b/src/main/java/com/faforever/api/clan/ClanService.java @@ -14,11 +14,11 @@ import com.faforever.api.player.PlayerRepository; import com.faforever.api.player.PlayerService; import com.faforever.api.security.JwtService; -import com.fasterxml.jackson.databind.ObjectMapper; import lombok.RequiredArgsConstructor; import lombok.SneakyThrows; import org.springframework.security.core.Authentication; import org.springframework.stereotype.Service; +import tools.jackson.databind.ObjectMapper; import java.time.Instant; import java.time.temporal.ChronoUnit; diff --git a/src/main/java/com/faforever/api/config/FafDatasourceConfig.java b/src/main/java/com/faforever/api/config/FafDatasourceConfig.java index 20568759..28de4655 100644 --- a/src/main/java/com/faforever/api/config/FafDatasourceConfig.java +++ b/src/main/java/com/faforever/api/config/FafDatasourceConfig.java @@ -3,8 +3,8 @@ import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.autoconfigure.condition.NoneNestedConditions; -import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties; import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.boot.jdbc.autoconfigure.DataSourceProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Conditional; import org.springframework.context.annotation.Configuration; diff --git a/src/main/java/com/faforever/api/config/FafDatastoreConfig.java b/src/main/java/com/faforever/api/config/FafDatastoreConfig.java index a404fc20..eec802b8 100644 --- a/src/main/java/com/faforever/api/config/FafDatastoreConfig.java +++ b/src/main/java/com/faforever/api/config/FafDatastoreConfig.java @@ -7,15 +7,15 @@ import org.hibernate.cfg.AvailableSettings; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.beans.factory.support.DefaultListableBeanFactory; -import org.springframework.boot.orm.jpa.EntityManagerFactoryBuilder; +import org.springframework.boot.jpa.EntityManagerFactoryBuilder; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Primary; import org.springframework.context.annotation.Scope; import org.springframework.data.jpa.repository.config.EnableJpaRepositories; -import org.springframework.orm.hibernate5.SpringBeanContainer; import org.springframework.orm.jpa.JpaTransactionManager; import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean; +import org.springframework.orm.jpa.hibernate.SpringBeanContainer; import org.springframework.transaction.PlatformTransactionManager; import org.springframework.transaction.TransactionDefinition; import org.springframework.transaction.support.DefaultTransactionDefinition; diff --git a/src/main/java/com/faforever/api/config/LeagueDatasourceConfig.java b/src/main/java/com/faforever/api/config/LeagueDatasourceConfig.java index e0564fde..5273194b 100644 --- a/src/main/java/com/faforever/api/config/LeagueDatasourceConfig.java +++ b/src/main/java/com/faforever/api/config/LeagueDatasourceConfig.java @@ -3,8 +3,8 @@ import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.autoconfigure.condition.NoneNestedConditions; -import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties; import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.boot.jdbc.autoconfigure.DataSourceProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Conditional; import org.springframework.context.annotation.Configuration; diff --git a/src/main/java/com/faforever/api/config/LeagueDatastoreConfig.java b/src/main/java/com/faforever/api/config/LeagueDatastoreConfig.java index 8af4fc21..e892fe5d 100644 --- a/src/main/java/com/faforever/api/config/LeagueDatastoreConfig.java +++ b/src/main/java/com/faforever/api/config/LeagueDatastoreConfig.java @@ -5,7 +5,7 @@ import com.yahoo.elide.spring.orm.jpa.EntityManagerProxySupplier; import com.yahoo.elide.spring.orm.jpa.PlatformJpaTransactionSupplier; import org.springframework.beans.factory.annotation.Qualifier; -import org.springframework.boot.orm.jpa.EntityManagerFactoryBuilder; +import org.springframework.boot.jpa.EntityManagerFactoryBuilder; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Scope; diff --git a/src/main/java/com/faforever/api/config/MvcConfig.java b/src/main/java/com/faforever/api/config/MvcConfig.java index 367cdaf1..6fb9ed3f 100644 --- a/src/main/java/com/faforever/api/config/MvcConfig.java +++ b/src/main/java/com/faforever/api/config/MvcConfig.java @@ -3,17 +3,13 @@ import org.springframework.context.annotation.Configuration; import org.springframework.core.Ordered; import org.springframework.format.FormatterRegistry; -import org.springframework.http.converter.HttpMessageConverter; -import org.springframework.web.servlet.config.annotation.ContentNegotiationConfigurer; +import org.springframework.http.converter.HttpMessageConverters; import org.springframework.web.servlet.config.annotation.CorsRegistry; import org.springframework.web.servlet.config.annotation.EnableWebMvc; -import org.springframework.web.servlet.config.annotation.PathMatchConfigurer; import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; import org.springframework.web.servlet.config.annotation.ViewControllerRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; -import java.util.List; - @EnableWebMvc @Configuration public class MvcConfig implements WebMvcConfigurer { @@ -31,17 +27,6 @@ public void addResourceHandlers(ResourceHandlerRegistry registry) { .addResourceLocations("classpath:/META-INF/resources/"); } - @Override - public void configurePathMatch(PathMatchConfigurer configurer) { - configurer.setUseRegisteredSuffixPatternMatch(true); - } - - @Override - public void configureContentNegotiation(final ContentNegotiationConfigurer configurer) { - // Turn off suffix-based content negotiation - configurer.favorPathExtension(false); - } - @Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/**") @@ -49,8 +34,8 @@ public void addCorsMappings(CorsRegistry registry) { } @Override - public void extendMessageConverters(List> converters) { - converters.add(new IgnoreOctetStreamToObjectHttpMessageConverter()); + public void configureMessageConverters(HttpMessageConverters.ServerBuilder builder) { + builder.addCustomConverter(new IgnoreOctetStreamToObjectHttpMessageConverter()); } @Override diff --git a/src/main/java/com/faforever/api/config/RabbitConfiguration.java b/src/main/java/com/faforever/api/config/RabbitConfiguration.java index 7ddd4b91..13e5282f 100644 --- a/src/main/java/com/faforever/api/config/RabbitConfiguration.java +++ b/src/main/java/com/faforever/api/config/RabbitConfiguration.java @@ -11,12 +11,12 @@ import org.springframework.amqp.core.TopicExchange; import org.springframework.amqp.rabbit.config.RetryInterceptorBuilder; import org.springframework.amqp.rabbit.config.SimpleRabbitListenerContainerFactory; +import org.springframework.amqp.rabbit.config.StatelessRetryOperationsInterceptor; import org.springframework.amqp.rabbit.connection.ConnectionFactory; import org.springframework.amqp.rabbit.retry.RejectAndDontRequeueRecoverer; -import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter; +import org.springframework.amqp.support.converter.JacksonJsonMessageConverter; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import org.springframework.retry.interceptor.RetryOperationsInterceptor; import java.util.Arrays; import java.util.Collection; @@ -42,10 +42,10 @@ public class RabbitConfiguration { * (afterwards it will be nacked) */ @Bean - public RetryOperationsInterceptor retryInterceptor() { + public StatelessRetryOperationsInterceptor retryInterceptor() { return RetryInterceptorBuilder.StatelessRetryInterceptorBuilder .stateless() - .maxAttempts(3) + .maxRetries(2) .backOffOptions(1000, 2.0, 10_000) .recoverer(new RejectAndDontRequeueRecoverer()) .build(); @@ -58,13 +58,13 @@ public RetryOperationsInterceptor retryInterceptor() { @Bean public SimpleRabbitListenerContainerFactory rabbitListenerContainerFactory( ConnectionFactory connectionFactory, - RetryOperationsInterceptor retryInterceptor + StatelessRetryOperationsInterceptor retryInterceptor ) { var factory = new SimpleRabbitListenerContainerFactory(); factory.setDefaultRequeueRejected(false); factory.setConnectionFactory(connectionFactory); factory.setAdviceChain(retryInterceptor); - factory.setMessageConverter(new Jackson2JsonMessageConverter()); + factory.setMessageConverter(new JacksonJsonMessageConverter()); return factory; } diff --git a/src/main/java/com/faforever/api/config/elide/ElideConfig.java b/src/main/java/com/faforever/api/config/elide/ElideConfig.java index 9e09d442..ca40e984 100644 --- a/src/main/java/com/faforever/api/config/elide/ElideConfig.java +++ b/src/main/java/com/faforever/api/config/elide/ElideConfig.java @@ -34,6 +34,11 @@ public class ElideConfig { public static final String DEFAULT_CACHE_NAME = "Elide.defaultCache"; + @Bean + ObjectMapper objectMapper() { + return new ObjectMapper(); + } + @Bean MultiplexManager multiplexDataStore( DataStore fafDataStore, diff --git a/src/main/java/com/faforever/api/config/security/MethodSecurityConfig.java b/src/main/java/com/faforever/api/config/security/MethodSecurityConfig.java index 927fe29b..5b160842 100644 --- a/src/main/java/com/faforever/api/config/security/MethodSecurityConfig.java +++ b/src/main/java/com/faforever/api/config/security/MethodSecurityConfig.java @@ -3,11 +3,11 @@ import com.faforever.api.security.method.CustomMethodSecurityExpressionHandler; import org.springframework.context.annotation.Configuration; import org.springframework.security.access.expression.method.MethodSecurityExpressionHandler; -import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; +import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity; import org.springframework.security.config.annotation.method.configuration.GlobalMethodSecurityConfiguration; @Configuration -@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true) +@EnableMethodSecurity(securedEnabled = true) public class MethodSecurityConfig extends GlobalMethodSecurityConfiguration { @Override protected MethodSecurityExpressionHandler createExpressionHandler() { diff --git a/src/main/java/com/faforever/api/config/security/WebSecurityConfig.java b/src/main/java/com/faforever/api/config/security/WebSecurityConfig.java index c78eb09e..4372c40c 100644 --- a/src/main/java/com/faforever/api/config/security/WebSecurityConfig.java +++ b/src/main/java/com/faforever/api/config/security/WebSecurityConfig.java @@ -35,7 +35,7 @@ public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Excepti oauth2Config.bearerTokenResolver(bearerTokenResolver); oauth2Config.jwt(jwtConfig -> jwtConfig.jwtAuthenticationConverter(new FafAuthenticationConverter())); }); - http.authorizeRequests(authorizeConfig -> { + http.authorizeHttpRequests(authorizeConfig -> { authorizeConfig.requestMatchers(HttpMethod.OPTIONS).permitAll(); // Swagger UI authorizeConfig.requestMatchers( diff --git a/src/main/java/com/faforever/api/data/converter/JsonConverter.java b/src/main/java/com/faforever/api/data/converter/JsonConverter.java index 37b20c5b..db899c18 100644 --- a/src/main/java/com/faforever/api/data/converter/JsonConverter.java +++ b/src/main/java/com/faforever/api/data/converter/JsonConverter.java @@ -1,13 +1,12 @@ package com.faforever.api.data.converter; -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.ObjectMapper; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import tools.jackson.core.JacksonException; +import tools.jackson.databind.ObjectMapper; import jakarta.persistence.AttributeConverter; import jakarta.persistence.Converter; -import java.io.IOException; import java.util.Map; @Slf4j @@ -23,7 +22,7 @@ public String convertToDatabaseColumn(Map jsonPayload) { String jsonAsString = null; try { jsonAsString = objectMapper.writeValueAsString(jsonPayload); - } catch (final JsonProcessingException e) { + } catch (final JacksonException e) { log.error("Failed to convert Json object {} to String", jsonPayload, e); } @@ -37,7 +36,7 @@ public Map convertToEntityAttribute(String jsonAsString) { if (jsonAsString != null) { try { jsonPayload = objectMapper.readValue(jsonAsString, Map.class); - } catch (final IOException e) { + } catch (final JacksonException e) { log.error("Failed to read stringified Json {}", jsonAsString, e); } } diff --git a/src/main/java/com/faforever/api/data/domain/Mod.java b/src/main/java/com/faforever/api/data/domain/Mod.java index 0f7323df..a889b5d8 100644 --- a/src/main/java/com/faforever/api/data/domain/Mod.java +++ b/src/main/java/com/faforever/api/data/domain/Mod.java @@ -9,7 +9,6 @@ import org.hibernate.annotations.JoinColumnOrFormula; import org.hibernate.annotations.JoinColumnsOrFormulas; import org.hibernate.annotations.JoinFormula; -import org.hibernate.validator.constraints.NotEmpty; import org.jetbrains.annotations.Nullable; import jakarta.persistence.CascadeType; @@ -23,6 +22,7 @@ import jakarta.persistence.Table; import jakarta.persistence.Transient; import jakarta.validation.Valid; +import jakarta.validation.constraints.NotEmpty; import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.Size; import java.util.ArrayList; diff --git a/src/main/java/com/faforever/api/deployment/ExeUploaderController.java b/src/main/java/com/faforever/api/deployment/ExeUploaderController.java index bf91c583..e26405f3 100644 --- a/src/main/java/com/faforever/api/deployment/ExeUploaderController.java +++ b/src/main/java/com/faforever/api/deployment/ExeUploaderController.java @@ -18,7 +18,7 @@ import java.util.Map; -import static org.springframework.http.MediaType.APPLICATION_JSON_UTF8_VALUE; +import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE; import static org.springframework.http.MediaType.TEXT_HTML_VALUE; @RestController @@ -46,7 +46,7 @@ public ModelAndView showValidationForm(Map model) { @ApiResponse(responseCode = "200", description = "Success"), @ApiResponse(responseCode = "401", description = "Unauthorized"), @ApiResponse(responseCode = "500", description = "Failure")}) - @RequestMapping(path = "/upload", method = RequestMethod.POST, produces = APPLICATION_JSON_UTF8_VALUE) + @RequestMapping(path = "/upload", method = RequestMethod.POST, produces = APPLICATION_JSON_VALUE) public void upload(@RequestParam("file") MultipartFile file, @RequestParam("modName") String modName, @RequestParam("apiKey") String apiKey diff --git a/src/main/java/com/faforever/api/error/ErrorJsonSerializer.java b/src/main/java/com/faforever/api/error/ErrorJsonSerializer.java index 1503fd3e..2d433fbf 100644 --- a/src/main/java/com/faforever/api/error/ErrorJsonSerializer.java +++ b/src/main/java/com/faforever/api/error/ErrorJsonSerializer.java @@ -1,27 +1,29 @@ package com.faforever.api.error; import com.faforever.api.logging.RequestIdFilter; -import com.fasterxml.jackson.core.JsonGenerator; -import com.fasterxml.jackson.databind.JsonSerializer; -import com.fasterxml.jackson.databind.SerializerProvider; import org.slf4j.MDC; -import org.springframework.boot.jackson.JsonComponent; +import org.springframework.boot.jackson.JacksonComponent; +import tools.jackson.core.JacksonException; +import tools.jackson.core.JsonGenerator; +import tools.jackson.databind.SerializationContext; +import tools.jackson.databind.ValueSerializer; -import java.io.IOException; import java.text.MessageFormat; -@JsonComponent -public class ErrorJsonSerializer extends JsonSerializer { +@JacksonComponent +public class ErrorJsonSerializer extends ValueSerializer { + @Override - public void serialize(Error error, JsonGenerator gen, SerializerProvider serializers) throws IOException { + public void serialize(Error error, JsonGenerator gen, SerializationContext ctxt) throws JacksonException { ErrorCode errorCode = error.getErrorCode(); gen.writeStartObject(); - gen.writeNumberField("code", errorCode.getCode()); - gen.writeStringField("requestId", MDC.get(RequestIdFilter.REQUEST_ID_KEY)); - gen.writeStringField("title", MessageFormat.format(errorCode.getTitle(), error.getArgs())); - gen.writeStringField("detail", MessageFormat.format(errorCode.getDetail(), error.getArgs())); - gen.writeObjectField("args", error.getArgs()); + gen.writeNumberProperty("code", errorCode.getCode()); + gen.writeStringProperty("requestId", MDC.get(RequestIdFilter.REQUEST_ID_KEY)); + gen.writeStringProperty("title", MessageFormat.format(errorCode.getTitle(), error.getArgs())); + gen.writeStringProperty("detail", MessageFormat.format(errorCode.getDetail(), error.getArgs())); + gen.writeObjectPropertyStart("args", error.getArgs()); gen.writeEndObject(); + } } diff --git a/src/main/java/com/faforever/api/forum/NodebbService.java b/src/main/java/com/faforever/api/forum/NodebbService.java index 24d1f983..9b09a553 100644 --- a/src/main/java/com/faforever/api/forum/NodebbService.java +++ b/src/main/java/com/faforever/api/forum/NodebbService.java @@ -53,7 +53,7 @@ public void userDataChanged(UserUpdatedEvent event) { } private Optional getNodebbUserId(int userId) { - URI uri = UriComponentsBuilder.fromHttpUrl(properties.getNodebb().getBaseUrl()) + URI uri = UriComponentsBuilder.fromUriString(properties.getNodebb().getBaseUrl()) // This is not an official NodeBB api url, it's coming from our own sso plugin .pathSegment("api", "v3", "plugins", "sso", "user", String.valueOf(userId)) .queryParam("_uid", getAdminUserId()) @@ -74,7 +74,7 @@ private Optional getNodebbUserId(int userId) { } private void updateUsernameData(int nodebbUserId, UserUpdatedEvent event) { - URI uri = UriComponentsBuilder.fromHttpUrl(properties.getNodebb().getBaseUrl()) + URI uri = UriComponentsBuilder.fromUriString(properties.getNodebb().getBaseUrl()) .pathSegment("api", "v3", "users", String.valueOf(nodebbUserId)) .build() .toUri(); @@ -85,7 +85,7 @@ private void updateUsernameData(int nodebbUserId, UserUpdatedEvent event) { } private void updateEmailData(int nodebbUserId, UserUpdatedEvent event) { - URI uri = UriComponentsBuilder.fromHttpUrl(properties.getNodebb().getBaseUrl()) + URI uri = UriComponentsBuilder.fromUriString(properties.getNodebb().getBaseUrl()) .pathSegment("api", "v3", "users", String.valueOf(nodebbUserId), "emails") .build() .toUri(); @@ -103,7 +103,7 @@ private HttpEntity buildAuthorizedRequest(T payload) { LinkedMultiValueMap headers = new LinkedMultiValueMap<>(); headers.add(HttpHeaders.AUTHORIZATION, "Bearer " + properties.getNodebb().getMasterToken()); - return new HttpEntity<>(payload, headers); + return new HttpEntity<>(payload, HttpHeaders.readOnlyHttpHeaders(headers)); } @JsonIgnoreProperties(ignoreUnknown = true) diff --git a/src/main/java/com/faforever/api/map/MapsController.java b/src/main/java/com/faforever/api/map/MapsController.java index f73ba98a..37bb7b35 100644 --- a/src/main/java/com/faforever/api/map/MapsController.java +++ b/src/main/java/com/faforever/api/map/MapsController.java @@ -6,8 +6,6 @@ import com.faforever.api.error.ErrorCode; import com.faforever.api.player.PlayerService; import com.faforever.api.security.OAuthScope; -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.ObjectMapper; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.responses.ApiResponses; @@ -22,11 +20,13 @@ import org.springframework.web.bind.annotation.RestController; import org.springframework.web.multipart.MultipartFile; import org.springframework.web.servlet.ModelAndView; +import tools.jackson.databind.JsonNode; +import tools.jackson.databind.ObjectMapper; import java.io.IOException; import java.util.Map; -import static org.springframework.http.MediaType.APPLICATION_JSON_UTF8_VALUE; +import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE; @RestController @RequestMapping(path = "/maps") @@ -39,7 +39,7 @@ public class MapsController { private final PlayerService playerService; - @RequestMapping(path = "/validate", method = RequestMethod.GET, produces = APPLICATION_JSON_UTF8_VALUE) + @RequestMapping(path = "/validate", method = RequestMethod.GET, produces = APPLICATION_JSON_VALUE) public ModelAndView showValidationForm(Map model) { return new ModelAndView("validate_map_metadata.html"); } @@ -52,7 +52,7 @@ public ModelAndView showValidationForm(Map model) { @RequestMapping( path = "/validateMapName", method = RequestMethod.POST, - produces = APPLICATION_JSON_UTF8_VALUE + produces = APPLICATION_JSON_VALUE ) public MapNameValidationResponse validateMapName(@RequestParam("mapName") String mapName) { return mapService.requestMapNameValidation(mapName); @@ -65,7 +65,7 @@ public MapNameValidationResponse validateMapName(@RequestParam("mapName") String @RequestMapping( path = "/validateScenarioLua", method = RequestMethod.POST, - produces = APPLICATION_JSON_UTF8_VALUE + produces = APPLICATION_JSON_VALUE ) public void validateScenarioLua(@RequestParam(name = "scenarioLua") String scenarioLua) { mapService.validateScenarioLua(scenarioLua); @@ -76,7 +76,7 @@ public void validateScenarioLua(@RequestParam(name = "scenarioLua") String scena @ApiResponse(responseCode = "200", description = "Success"), @ApiResponse(responseCode = "401", description = "Unauthorized"), @ApiResponse(responseCode = "500", description = "Failure")}) - @RequestMapping(path = "/upload", method = RequestMethod.POST, produces = APPLICATION_JSON_UTF8_VALUE) + @RequestMapping(path = "/upload", method = RequestMethod.POST, produces = APPLICATION_JSON_VALUE) @PreAuthorize("hasScope('" + OAuthScope._UPLOAD_MAP + "')") public void uploadMap(@RequestParam("file") MultipartFile file, @Deprecated @RequestParam(value = "metadata", required = false) String metadataJsonString, diff --git a/src/main/java/com/faforever/api/security/FafTokenService.java b/src/main/java/com/faforever/api/security/FafTokenService.java index 07fa8efc..28596db8 100644 --- a/src/main/java/com/faforever/api/security/FafTokenService.java +++ b/src/main/java/com/faforever/api/security/FafTokenService.java @@ -4,9 +4,6 @@ import com.faforever.api.error.ApiException; import com.faforever.api.error.ErrorCode; import com.faforever.api.security.crypto.CertificateUtils; -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.core.type.TypeReference; -import com.fasterxml.jackson.databind.ObjectMapper; import com.nimbusds.jose.JOSEException; import com.nimbusds.jose.JOSEObjectType; import com.nimbusds.jose.JWSAlgorithm; @@ -21,6 +18,9 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import org.springframework.util.Assert; +import tools.jackson.core.JacksonException; +import tools.jackson.core.type.TypeReference; +import tools.jackson.databind.ObjectMapper; import jakarta.validation.constraints.NotNull; import java.nio.file.Files; @@ -108,7 +108,7 @@ public Map resolveToken(@NotNull FafTokenType expectedTokenType, claims = objectMapper.readValue(payload, new TypeReference<>() { }); - } catch (ParseException | JOSEException | JsonProcessingException | IllegalArgumentException e) { + } catch (ParseException | JOSEException | JacksonException | IllegalArgumentException e) { log.warn("Unparseable token: {}", token); throw ApiException.of(ErrorCode.TOKEN_INVALID); } diff --git a/src/main/java/com/faforever/api/security/JwtService.java b/src/main/java/com/faforever/api/security/JwtService.java index 41eabcb6..9eaec75f 100644 --- a/src/main/java/com/faforever/api/security/JwtService.java +++ b/src/main/java/com/faforever/api/security/JwtService.java @@ -4,7 +4,6 @@ import com.faforever.api.error.ApiException; import com.faforever.api.error.ErrorCode; import com.faforever.api.security.crypto.CertificateUtils; -import com.fasterxml.jackson.databind.ObjectMapper; import com.nimbusds.jose.JOSEException; import com.nimbusds.jose.JOSEObjectType; import com.nimbusds.jose.JWSAlgorithm; @@ -15,6 +14,7 @@ import com.nimbusds.jose.crypto.RSASSAVerifier; import com.nimbusds.jose.jwk.RSAKey; import org.springframework.stereotype.Service; +import tools.jackson.databind.ObjectMapper; import jakarta.inject.Inject; import java.io.IOException; diff --git a/src/main/java/com/faforever/api/user/RecaptchaService.java b/src/main/java/com/faforever/api/user/RecaptchaService.java index 11b92ca5..4c4df2b8 100644 --- a/src/main/java/com/faforever/api/user/RecaptchaService.java +++ b/src/main/java/com/faforever/api/user/RecaptchaService.java @@ -6,7 +6,7 @@ import com.fasterxml.jackson.annotation.JsonProperty; import lombok.extern.slf4j.Slf4j; import org.jetbrains.annotations.Nullable; -import org.springframework.boot.web.client.RestTemplateBuilder; +import org.springframework.boot.restclient.RestTemplateBuilder; import org.springframework.stereotype.Service; import org.springframework.web.client.RestTemplate; diff --git a/src/main/java/com/faforever/api/user/SteamService.java b/src/main/java/com/faforever/api/user/SteamService.java index ca9f3266..08ef3e4e 100644 --- a/src/main/java/com/faforever/api/user/SteamService.java +++ b/src/main/java/com/faforever/api/user/SteamService.java @@ -6,9 +6,6 @@ import com.faforever.api.data.domain.LinkedServiceType; import com.faforever.api.error.ApiException; import com.faforever.api.error.ErrorCode; - -import java.net.http.HttpResponse; - import lombok.RequiredArgsConstructor; import lombok.SneakyThrows; import lombok.extern.slf4j.Slf4j; @@ -22,6 +19,7 @@ import java.net.URI; import java.net.http.HttpClient; import java.net.http.HttpRequest; +import java.net.http.HttpResponse; import java.net.http.HttpResponse.BodyHandlers; import java.util.Map; import java.util.Optional; @@ -36,7 +34,7 @@ public class SteamService { String buildLoginUrl(String redirectUrl) { log.debug("Building steam login url for redirect url: {}", redirectUrl); - return UriComponentsBuilder.fromHttpUrl(properties.getSteam().getLoginUrlFormat()) + return UriComponentsBuilder.fromUriString(properties.getSteam().getLoginUrlFormat()) .queryParam("openid.ns", "http://specs.openid.net/auth/2.0") .queryParam("openid.mode", "checkid_setup") .queryParam("openid.return_to", redirectUrl) @@ -78,7 +76,7 @@ boolean ownsForgedAlliance(String steamId) { void validateSteamRedirect(HttpServletRequest request) { log.debug("Checking valid OpenID 2.0 redirect against Steam API, query string: {}", request.getQueryString()); - UriComponentsBuilder builder = UriComponentsBuilder.fromHttpUrl(properties.getSteam().getLoginUrlFormat()); + UriComponentsBuilder builder = UriComponentsBuilder.fromUriString(properties.getSteam().getLoginUrlFormat()); request.getParameterMap().forEach(builder::queryParam); builder.replaceQueryParam("openid.mode", "check_authentication"); diff --git a/src/main/java/com/faforever/api/user/UsersController.java b/src/main/java/com/faforever/api/user/UsersController.java index 02775d38..64a312d6 100644 --- a/src/main/java/com/faforever/api/user/UsersController.java +++ b/src/main/java/com/faforever/api/user/UsersController.java @@ -8,7 +8,6 @@ import com.faforever.api.security.OAuthScope; import com.faforever.api.user.UserService.CallbackResult; import com.faforever.api.utils.RemoteAddressUtil; -import com.fasterxml.jackson.databind.ObjectMapper; import io.swagger.v3.oas.annotations.Operation; import lombok.RequiredArgsConstructor; import lombok.SneakyThrows; @@ -21,6 +20,7 @@ import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.util.UriComponentsBuilder; +import tools.jackson.databind.ObjectMapper; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; @@ -188,7 +188,7 @@ private void redirectCallbackResult(HttpServletResponse response, CallbackResult if (result.errors().isEmpty()) { response.sendRedirect(result.callbackUrl()); } else { - UriComponentsBuilder uriBuilder = UriComponentsBuilder.fromHttpUrl(result.callbackUrl()); + UriComponentsBuilder uriBuilder = UriComponentsBuilder.fromUriString(result.callbackUrl()); String errorsJson = objectMapper.writeValueAsString(result.errors()); uriBuilder.queryParam("errors", errorsJson); response.sendRedirect(uriBuilder.toUriString()); diff --git a/src/main/resources/config/application-local.yml b/src/main/resources/config/application-local.yml index 947c6642..53e58a7e 100644 --- a/src/main/resources/config/application-local.yml +++ b/src/main/resources/config/application-local.yml @@ -90,4 +90,4 @@ spring: issuer-uri: https://hydra.faforever.com/ logging: level: - com.faforever.api: debug + com.faforever.api: info diff --git a/src/test/java/com/faforever/api/clan/ClanServiceTest.java b/src/test/java/com/faforever/api/clan/ClanServiceTest.java index 7888d747..d3f1ac2a 100644 --- a/src/test/java/com/faforever/api/clan/ClanServiceTest.java +++ b/src/test/java/com/faforever/api/clan/ClanServiceTest.java @@ -12,7 +12,6 @@ import com.faforever.api.player.PlayerRepository; import com.faforever.api.player.PlayerService; import com.faforever.api.security.JwtService; -import com.fasterxml.jackson.databind.ObjectMapper; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.ArgumentCaptor; @@ -21,6 +20,7 @@ import org.mockito.Mockito; import org.mockito.Spy; import org.mockito.junit.jupiter.MockitoExtension; +import tools.jackson.databind.ObjectMapper; import java.io.IOException; import java.util.Optional; diff --git a/src/test/java/com/faforever/api/deployment/ExeUploaderControllerTest.java b/src/test/java/com/faforever/api/deployment/ExeUploaderControllerTest.java index 06edeea9..d5cc4a7d 100644 --- a/src/test/java/com/faforever/api/deployment/ExeUploaderControllerTest.java +++ b/src/test/java/com/faforever/api/deployment/ExeUploaderControllerTest.java @@ -6,7 +6,7 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mock; -import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.boot.webmvc.test.autoconfigure.WebMvcTest; import org.springframework.context.annotation.Import; import org.springframework.mock.web.MockMultipartFile; import org.springframework.test.context.bean.override.mockito.MockitoBean; diff --git a/src/test/java/com/faforever/api/email/JavaEmailSenderTest.java b/src/test/java/com/faforever/api/email/JavaEmailSenderTest.java index bf0c8485..9f719df4 100644 --- a/src/test/java/com/faforever/api/email/JavaEmailSenderTest.java +++ b/src/test/java/com/faforever/api/email/JavaEmailSenderTest.java @@ -1,6 +1,5 @@ package com.faforever.api.email; -import org.eclipse.angus.mail.smtp.SMTPMessage; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -38,7 +37,7 @@ public void sendMail() throws Exception { ArgumentCaptor captor = ArgumentCaptor.forClass(MimeMessagePreparator.class); verify(mailSender).send(captor.capture()); - MimeMessage mimeMessage = new SMTPMessage((Session) null); + MimeMessage mimeMessage = new MimeMessage((Session) null); captor.getValue().prepare(mimeMessage); assertThat(mimeMessage.getAllRecipients()[0], is(new InternetAddress("toEmail"))); diff --git a/src/test/java/com/faforever/api/error/GlobalControllerExceptionHandlerTest.java b/src/test/java/com/faforever/api/error/GlobalControllerExceptionHandlerTest.java index 573692e5..ffce14b9 100644 --- a/src/test/java/com/faforever/api/error/GlobalControllerExceptionHandlerTest.java +++ b/src/test/java/com/faforever/api/error/GlobalControllerExceptionHandlerTest.java @@ -2,9 +2,6 @@ import com.faforever.api.data.domain.Clan; import com.faforever.api.data.domain.Player; - -import jakarta.servlet.ServletException; - import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; @@ -12,17 +9,16 @@ import org.junit.jupiter.params.provider.MethodSource; import org.springframework.http.HttpMethod; import org.springframework.http.HttpStatus; +import org.springframework.web.HttpRequestMethodNotSupportedException; +import org.springframework.web.servlet.resource.NoResourceFoundException; +import jakarta.servlet.ServletException; import jakarta.validation.ConstraintViolation; import jakarta.validation.ConstraintViolationException; import jakarta.validation.Validation; import jakarta.validation.ValidationException; import jakarta.validation.Validator; import jakarta.validation.ValidatorFactory; - -import org.springframework.web.HttpRequestMethodNotSupportedException; -import org.springframework.web.servlet.resource.NoResourceFoundException; - import java.text.MessageFormat; import java.util.Set; import java.util.stream.Stream; @@ -133,7 +129,7 @@ public void testConstraintViolationException() { public static Stream servletExceptionSource() { return Stream.of( Arguments.of(new HttpRequestMethodNotSupportedException(HttpMethod.DELETE.name())), - Arguments.of(new NoResourceFoundException(HttpMethod.POST, "test/path")) + Arguments.of(new NoResourceFoundException(HttpMethod.POST, "", "test/path")) ); } } diff --git a/src/test/java/com/faforever/api/security/FafTokenServiceTest.java b/src/test/java/com/faforever/api/security/FafTokenServiceTest.java index bebb973c..7c80e9bc 100644 --- a/src/test/java/com/faforever/api/security/FafTokenServiceTest.java +++ b/src/test/java/com/faforever/api/security/FafTokenServiceTest.java @@ -4,15 +4,15 @@ import com.faforever.api.error.ApiException; import com.faforever.api.error.ErrorCode; import com.faforever.api.security.crypto.CertificateUtils; -import com.fasterxml.jackson.core.type.TypeReference; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; import com.nimbusds.jose.JWSObject; import com.nimbusds.jose.crypto.RSASSASigner; import com.nimbusds.jose.crypto.RSASSAVerifier; import com.nimbusds.jose.jwk.RSAKey; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import tools.jackson.core.type.TypeReference; +import tools.jackson.databind.ObjectMapper; +import tools.jackson.datatype.jsr310.JavaTimeModule; import java.nio.file.Files; import java.nio.file.Paths; diff --git a/src/test/java/com/faforever/api/user/RecaptchaServiceTest.java b/src/test/java/com/faforever/api/user/RecaptchaServiceTest.java index b7ca9a76..16699a31 100644 --- a/src/test/java/com/faforever/api/user/RecaptchaServiceTest.java +++ b/src/test/java/com/faforever/api/user/RecaptchaServiceTest.java @@ -11,7 +11,7 @@ import org.mockito.Answers; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; -import org.springframework.boot.web.client.RestTemplateBuilder; +import org.springframework.boot.restclient.RestTemplateBuilder; import org.springframework.web.client.RestTemplate; import java.time.OffsetDateTime; From 707bb7e7472d372cf9251c667890174a498880a5 Mon Sep 17 00:00:00 2001 From: kubo Date: Wed, 3 Dec 2025 20:36:02 +0100 Subject: [PATCH 3/8] WIP SB 4.0.0 --- build.gradle | 1 + .../java/com/faforever/api/AbstractIntegrationTest.java | 4 ++-- .../faforever/api/config/security/MethodSecurityConfig.java | 6 +++--- .../java/com/faforever/api/error/ErrorJsonSerializer.java | 3 +-- src/main/java/com/faforever/api/map/MapsController.java | 3 ++- .../com/faforever/api/security/FafTokenServiceTest.java | 2 -- 6 files changed, 9 insertions(+), 10 deletions(-) diff --git a/build.gradle b/build.gradle index bc9d69dd..30b2920c 100644 --- a/build.gradle +++ b/build.gradle @@ -145,6 +145,7 @@ dependencies { implementation("org.springframework.boot:spring-boot-starter-data-jpa") implementation("org.springframework.boot:spring-boot-starter-webmvc") implementation("org.springframework.boot:spring-boot-starter-jackson") + implementation("org.springframework.boot:spring-boot-jackson2") implementation("org.springframework.boot:spring-boot-starter-restclient") implementation("org.springframework.boot:spring-boot-starter-amqp") implementation("org.springframework.boot:spring-boot-starter-security") diff --git a/src/inttest/java/com/faforever/api/AbstractIntegrationTest.java b/src/inttest/java/com/faforever/api/AbstractIntegrationTest.java index b5d4f6d7..60121360 100644 --- a/src/inttest/java/com/faforever/api/AbstractIntegrationTest.java +++ b/src/inttest/java/com/faforever/api/AbstractIntegrationTest.java @@ -11,6 +11,8 @@ import com.faforever.commons.api.dto.ModerationReport; import com.faforever.commons.api.dto.Player; import com.faforever.commons.api.dto.Tutorial; +import com.fasterxml.jackson.annotation.JsonInclude.Include; +import com.fasterxml.jackson.databind.ObjectMapper; import com.github.jasminb.jsonapi.JSONAPIDocument; import com.github.jasminb.jsonapi.ResourceConverter; import com.github.jasminb.jsonapi.exceptions.DocumentSerializationException; @@ -36,8 +38,6 @@ import org.testcontainers.containers.Network; import org.testcontainers.containers.RabbitMQContainer; import org.testcontainers.junit.jupiter.Testcontainers; -import tools.jackson.annotation.JsonInclude.Include; -import tools.jackson.databind.ObjectMapper; import jakarta.transaction.Transactional; import java.time.format.DateTimeFormatter; diff --git a/src/main/java/com/faforever/api/config/security/MethodSecurityConfig.java b/src/main/java/com/faforever/api/config/security/MethodSecurityConfig.java index 5b160842..00516c6f 100644 --- a/src/main/java/com/faforever/api/config/security/MethodSecurityConfig.java +++ b/src/main/java/com/faforever/api/config/security/MethodSecurityConfig.java @@ -1,15 +1,15 @@ package com.faforever.api.config.security; import com.faforever.api.security.method.CustomMethodSecurityExpressionHandler; +import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.access.expression.method.MethodSecurityExpressionHandler; import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity; -import org.springframework.security.config.annotation.method.configuration.GlobalMethodSecurityConfiguration; @Configuration @EnableMethodSecurity(securedEnabled = true) -public class MethodSecurityConfig extends GlobalMethodSecurityConfiguration { - @Override +public class MethodSecurityConfig { + @Bean protected MethodSecurityExpressionHandler createExpressionHandler() { return new CustomMethodSecurityExpressionHandler(); } diff --git a/src/main/java/com/faforever/api/error/ErrorJsonSerializer.java b/src/main/java/com/faforever/api/error/ErrorJsonSerializer.java index 2d433fbf..8ef4b303 100644 --- a/src/main/java/com/faforever/api/error/ErrorJsonSerializer.java +++ b/src/main/java/com/faforever/api/error/ErrorJsonSerializer.java @@ -22,8 +22,7 @@ public void serialize(Error error, JsonGenerator gen, SerializationContext ctxt) gen.writeStringProperty("requestId", MDC.get(RequestIdFilter.REQUEST_ID_KEY)); gen.writeStringProperty("title", MessageFormat.format(errorCode.getTitle(), error.getArgs())); gen.writeStringProperty("detail", MessageFormat.format(errorCode.getDetail(), error.getArgs())); - gen.writeObjectPropertyStart("args", error.getArgs()); + gen.writePOJOProperty("args", error.getArgs()); gen.writeEndObject(); - } } diff --git a/src/main/java/com/faforever/api/map/MapsController.java b/src/main/java/com/faforever/api/map/MapsController.java index 37bb7b35..17233f16 100644 --- a/src/main/java/com/faforever/api/map/MapsController.java +++ b/src/main/java/com/faforever/api/map/MapsController.java @@ -20,6 +20,7 @@ import org.springframework.web.bind.annotation.RestController; import org.springframework.web.multipart.MultipartFile; import org.springframework.web.servlet.ModelAndView; +import tools.jackson.core.JacksonException; import tools.jackson.databind.JsonNode; import tools.jackson.databind.ObjectMapper; @@ -92,7 +93,7 @@ public void uploadMap(@RequestParam("file") MultipartFile file, try { JsonNode node = objectMapper.readTree(metadataJsonString); ranked = node.path("isRanked").asBoolean(false); - } catch (IOException e) { + } catch (JacksonException e) { log.debug("Could not parse metadata", e); throw ApiException.of(ErrorCode.INVALID_METADATA, e.getMessage()); } diff --git a/src/test/java/com/faforever/api/security/FafTokenServiceTest.java b/src/test/java/com/faforever/api/security/FafTokenServiceTest.java index 7c80e9bc..2eba5f69 100644 --- a/src/test/java/com/faforever/api/security/FafTokenServiceTest.java +++ b/src/test/java/com/faforever/api/security/FafTokenServiceTest.java @@ -12,7 +12,6 @@ import org.junit.jupiter.api.Test; import tools.jackson.core.type.TypeReference; import tools.jackson.databind.ObjectMapper; -import tools.jackson.datatype.jsr310.JavaTimeModule; import java.nio.file.Files; import java.nio.file.Paths; @@ -77,7 +76,6 @@ public FafTokenServiceTest() throws Exception { @BeforeEach public void setUp() throws Exception { objectMapper = new ObjectMapper(); - objectMapper.registerModule(new JavaTimeModule()); FafApiProperties properties = new FafApiProperties(); properties.getJwt().setSecretKeyPath(Paths.get("test-pki-private.key")); From f1ac044eabd5ea54f4561619c64143693038f9b8 Mon Sep 17 00:00:00 2001 From: kubo Date: Tue, 23 Dec 2025 01:33:07 +0100 Subject: [PATCH 4/8] WIP SB 4.0.0 --- .../com/faforever/api/clan/ClanControllerTest.java | 2 +- .../api/moderationreport/ModerationReportTest.java | 2 +- .../com/faforever/api/user/MeControllerTest.java | 2 +- .../com/faforever/api/user/UsersControllerTest.java | 9 +++++---- src/inttest/resources/config/application.yml | 4 +--- .../com/faforever/api/config/elide/ElideConfig.java | 5 ----- .../api/config/security/WebSecurityConfig.java | 11 +++++++++++ .../CustomMethodSecurityExpressionHandler.java | 13 +++++++++---- .../method/CustomMethodSecurityExpressionRoot.java | 5 ++++- src/main/resources/config/application.yml | 1 - src/test/resources/config/application.yml | 3 --- 11 files changed, 33 insertions(+), 24 deletions(-) diff --git a/src/inttest/java/com/faforever/api/clan/ClanControllerTest.java b/src/inttest/java/com/faforever/api/clan/ClanControllerTest.java index 06d95d55..4c4d9806 100644 --- a/src/inttest/java/com/faforever/api/clan/ClanControllerTest.java +++ b/src/inttest/java/com/faforever/api/clan/ClanControllerTest.java @@ -106,7 +106,7 @@ public void createClanWithoutAuth() throws Exception { mockMvc.perform( post("/clans/create") .params(params)) - .andExpect(status().isForbidden()); + .andExpect(status().isUnauthorized()); } @Test diff --git a/src/inttest/java/com/faforever/api/moderationreport/ModerationReportTest.java b/src/inttest/java/com/faforever/api/moderationreport/ModerationReportTest.java index c94447cc..f29d873b 100644 --- a/src/inttest/java/com/faforever/api/moderationreport/ModerationReportTest.java +++ b/src/inttest/java/com/faforever/api/moderationreport/ModerationReportTest.java @@ -63,7 +63,7 @@ public void anonymousUserCannotCreateValidModerationReport() throws Exception { post("/data/moderationReport") .header(HttpHeaders.CONTENT_TYPE, JSON_API_MEDIA_TYPE) .content(createJsonApiContent(validModerationReport))) - .andExpect(status().isForbidden()); + .andExpect(status().isUnauthorized()); } @Test diff --git a/src/inttest/java/com/faforever/api/user/MeControllerTest.java b/src/inttest/java/com/faforever/api/user/MeControllerTest.java index 09c7521b..f5f2797c 100644 --- a/src/inttest/java/com/faforever/api/user/MeControllerTest.java +++ b/src/inttest/java/com/faforever/api/user/MeControllerTest.java @@ -18,7 +18,7 @@ public class MeControllerTest extends AbstractIntegrationTest { @Test public void withoutTokenUnauthorized() throws Exception { mockMvc.perform(get("/me")) - .andExpect(status().isForbidden()); + .andExpect(status().isUnauthorized()); } @Test diff --git a/src/inttest/java/com/faforever/api/user/UsersControllerTest.java b/src/inttest/java/com/faforever/api/user/UsersControllerTest.java index ef179736..43ebe746 100644 --- a/src/inttest/java/com/faforever/api/user/UsersControllerTest.java +++ b/src/inttest/java/com/faforever/api/user/UsersControllerTest.java @@ -232,7 +232,7 @@ public void resetPasswordWithEmail() throws Exception { params.add("identifier", "user@faforever.com"); mockMvc.perform( - post("/users/requestPasswordReset") + post("/users/requestPasswordReset") .params(params)) .andExpect(status().isOk()); @@ -256,13 +256,14 @@ public void performPasswordReset() throws Exception { public void buildSteamLinkUrlUnauthorized() throws Exception { mockMvc.perform( post("/users/buildSteamLinkUrl?callbackUrl=foo")) - .andExpect(status().isForbidden()); + .andExpect(status().isUnauthorized()); } @Test public void buildSteamLinkUrlWithWrongScope() throws Exception { mockMvc.perform( - post("/users/buildSteamLinkUrl?callbackUrl=foo")) + post("/users/buildSteamLinkUrl?callbackUrl=foo") + .with(getOAuthTokenForUserId(USERID_MODERATOR, OAuthScope._LOBBY))) .andExpect(status().isForbidden()); } @@ -350,7 +351,7 @@ public void changeUsernameUnauthorized() throws Exception { mockMvc.perform( post("/users/changeUsername") .params(params)) - .andExpect(status().isForbidden()); + .andExpect(status().isUnauthorized()); } @Test diff --git a/src/inttest/resources/config/application.yml b/src/inttest/resources/config/application.yml index a19bff1d..2605b754 100644 --- a/src/inttest/resources/config/application.yml +++ b/src/inttest/resources/config/application.yml @@ -19,9 +19,6 @@ spring: ddl-auto: ${DATABASE_DDL_AUTO:none} naming: physical-strategy: org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl - properties: - hibernate: - current_session_context_class: org.springframework.orm.hibernate5.SpringSessionContext h2: console: enabled: true @@ -110,3 +107,4 @@ logging: level: org.hibernate.SQL: DEBUG org.hibernate.engine.spi.EntityEntry: TRACE + org.springframework.security: DEBUG diff --git a/src/main/java/com/faforever/api/config/elide/ElideConfig.java b/src/main/java/com/faforever/api/config/elide/ElideConfig.java index ca40e984..9e09d442 100644 --- a/src/main/java/com/faforever/api/config/elide/ElideConfig.java +++ b/src/main/java/com/faforever/api/config/elide/ElideConfig.java @@ -34,11 +34,6 @@ public class ElideConfig { public static final String DEFAULT_CACHE_NAME = "Elide.defaultCache"; - @Bean - ObjectMapper objectMapper() { - return new ObjectMapper(); - } - @Bean MultiplexManager multiplexDataStore( DataStore fafDataStore, diff --git a/src/main/java/com/faforever/api/config/security/WebSecurityConfig.java b/src/main/java/com/faforever/api/config/security/WebSecurityConfig.java index 4372c40c..c361fe17 100644 --- a/src/main/java/com/faforever/api/config/security/WebSecurityConfig.java +++ b/src/main/java/com/faforever/api/config/security/WebSecurityConfig.java @@ -50,6 +50,17 @@ public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Excepti "/favicon.ico", "/robots.txt" ).permitAll(); + authorizeConfig.requestMatchers( + "/exe/upload", + "/game/*/replay", + "/users/register", + "/users/activate", + "/users/requestPasswordReset", + "/users/requestPasswordReset", + "/users/performPasswordReset", + "/users/linkToSteam/**" + ).permitAll(); + authorizeConfig.anyRequest().authenticated(); }); // @formatter:on return http.build(); diff --git a/src/main/java/com/faforever/api/security/method/CustomMethodSecurityExpressionHandler.java b/src/main/java/com/faforever/api/security/method/CustomMethodSecurityExpressionHandler.java index 0ab4c68a..1d8c8f2f 100644 --- a/src/main/java/com/faforever/api/security/method/CustomMethodSecurityExpressionHandler.java +++ b/src/main/java/com/faforever/api/security/method/CustomMethodSecurityExpressionHandler.java @@ -1,12 +1,16 @@ package com.faforever.api.security.method; import org.aopalliance.intercept.MethodInvocation; +import org.jspecify.annotations.Nullable; +import org.springframework.expression.EvaluationContext; +import org.springframework.expression.spel.support.StandardEvaluationContext; import org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler; -import org.springframework.security.access.expression.method.MethodSecurityExpressionOperations; import org.springframework.security.authentication.AuthenticationTrustResolver; import org.springframework.security.authentication.AuthenticationTrustResolverImpl; import org.springframework.security.core.Authentication; +import java.util.function.Supplier; + /** * Wraps the CustomMethodSecurityExpressionRoot into an expression handler */ @@ -14,13 +18,14 @@ public class CustomMethodSecurityExpressionHandler extends DefaultMethodSecurity private final AuthenticationTrustResolver trustResolver = new AuthenticationTrustResolverImpl(); @Override - protected MethodSecurityExpressionOperations createSecurityExpressionRoot(Authentication authentication, MethodInvocation invocation) { + public EvaluationContext createEvaluationContext(Supplier authentication, MethodInvocation mi) { + StandardEvaluationContext context = (StandardEvaluationContext) super.createEvaluationContext(authentication, mi); var root = new CustomMethodSecurityExpressionRoot(authentication); root.setPermissionEvaluator(getPermissionEvaluator()); root.setTrustResolver(trustResolver); root.setRoleHierarchy(getRoleHierarchy()); - - return root; + context.setRootObject(root); + return context; } } diff --git a/src/main/java/com/faforever/api/security/method/CustomMethodSecurityExpressionRoot.java b/src/main/java/com/faforever/api/security/method/CustomMethodSecurityExpressionRoot.java index 7f5fc362..bcbcce8f 100644 --- a/src/main/java/com/faforever/api/security/method/CustomMethodSecurityExpressionRoot.java +++ b/src/main/java/com/faforever/api/security/method/CustomMethodSecurityExpressionRoot.java @@ -1,9 +1,12 @@ package com.faforever.api.security.method; +import org.jspecify.annotations.Nullable; import org.springframework.security.access.expression.SecurityExpressionRoot; import org.springframework.security.access.expression.method.MethodSecurityExpressionOperations; import org.springframework.security.core.Authentication; +import java.util.function.Supplier; + import static com.faforever.api.security.FafScope.SCOPE_PREFIX; /** @@ -14,7 +17,7 @@ public class CustomMethodSecurityExpressionRoot extends SecurityExpressionRoot i private Object filterObject; private Object returnObject; - public CustomMethodSecurityExpressionRoot(Authentication authentication) { + public CustomMethodSecurityExpressionRoot(Supplier authentication) { super(authentication); } diff --git a/src/main/resources/config/application.yml b/src/main/resources/config/application.yml index 3185e6aa..cbd60d30 100644 --- a/src/main/resources/config/application.yml +++ b/src/main/resources/config/application.yml @@ -128,7 +128,6 @@ spring: physical-strategy: org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl properties: hibernate: - current_session_context_class: org.springframework.orm.hibernate5.SpringSessionContext dialect: org.hibernate.dialect.MariaDBDialect jackson: serialization: diff --git a/src/test/resources/config/application.yml b/src/test/resources/config/application.yml index 4159defb..00487209 100644 --- a/src/test/resources/config/application.yml +++ b/src/test/resources/config/application.yml @@ -10,9 +10,6 @@ spring: show-sql: true hibernate: ddl-auto: create - properties: - hibernate: - current_session_context_class: org.springframework.orm.hibernate5.SpringSessionContext h2: console: enabled: true From 43e623fb922e70a1dce912b5d3d994258ab9409f Mon Sep 17 00:00:00 2001 From: kubo Date: Wed, 24 Dec 2025 17:22:30 +0100 Subject: [PATCH 5/8] WIP SB 4.0.0 --- .../api/data/jackson3compat/Data.java | 125 ++++++++++++ .../data/jackson3compat/DataDeserializer.java | 45 +++++ .../data/jackson3compat/DataSerializer.java | 36 ++++ .../data/jackson3compat/JsonApiDocument.java | 110 ++++++++++ .../data/jackson3compat/KeySerializer.java | 45 +++++ .../api/data/jackson3compat/Meta.java | 40 ++++ .../data/jackson3compat/MetaDeserializer.java | 28 +++ .../api/data/jackson3compat/Resource.java | 189 ++++++++++++++++++ .../featuredmods/FeaturedModsController.java | 6 +- .../com/faforever/api/user/MeController.java | 6 +- 10 files changed, 624 insertions(+), 6 deletions(-) create mode 100644 src/main/java/com/faforever/api/data/jackson3compat/Data.java create mode 100644 src/main/java/com/faforever/api/data/jackson3compat/DataDeserializer.java create mode 100644 src/main/java/com/faforever/api/data/jackson3compat/DataSerializer.java create mode 100644 src/main/java/com/faforever/api/data/jackson3compat/JsonApiDocument.java create mode 100644 src/main/java/com/faforever/api/data/jackson3compat/KeySerializer.java create mode 100644 src/main/java/com/faforever/api/data/jackson3compat/Meta.java create mode 100644 src/main/java/com/faforever/api/data/jackson3compat/MetaDeserializer.java create mode 100644 src/main/java/com/faforever/api/data/jackson3compat/Resource.java diff --git a/src/main/java/com/faforever/api/data/jackson3compat/Data.java b/src/main/java/com/faforever/api/data/jackson3compat/Data.java new file mode 100644 index 00000000..0d21bcc0 --- /dev/null +++ b/src/main/java/com/faforever/api/data/jackson3compat/Data.java @@ -0,0 +1,125 @@ +package com.faforever.api.data.jackson3compat; + +import com.yahoo.elide.core.dictionary.RelationshipType; +import com.yahoo.elide.jsonapi.models.ResourceIdentifier; +import lombok.ToString; +import reactor.core.publisher.Flux; +import tools.jackson.databind.annotation.JsonDeserialize; +import tools.jackson.databind.annotation.JsonSerialize; + +import java.util.Collection; +import java.util.Comparator; + +@JsonSerialize(using = DataSerializer.class) +@JsonDeserialize(using = DataDeserializer.class) +@ToString +public class Data { + private Flux values; + private final RelationshipType relationshipType; + + /** + * Constructor. + * + * @param value singleton resource + */ + public Data(T value) { + if (value == null) { + this.values = Flux.empty(); + } else { + this.values = Flux.just(value); + } + this.relationshipType = RelationshipType.MANY_TO_ONE; // Any "toOne" + } + + /** + * Constructor. + * + * @param values List of resources + */ + public Data(Flux values) { + this(values, RelationshipType.MANY_TO_MANY); + } + + /** + * Constructor. + * + * @param values List of resources + * @param relationshipType toOne or toMany + */ + public Data(Flux values, RelationshipType relationshipType) { + this.values = values; + this.relationshipType = relationshipType; + } + + /** + * Constructor. + * + * @param values List of resources + */ + public Data(Collection values) { + this(values, RelationshipType.MANY_TO_MANY); + } + + /** + * Constructor. + * + * @param values List of resources + * @param relationshipType toOne or toMany + */ + public Data(Collection values, RelationshipType relationshipType) { + this.values = Flux.fromIterable(values); + this.relationshipType = relationshipType; + } + + /** + * Sort method using provided sort function. + * + * @param sortFunction comparator to sort data with + */ + public void sort(Comparator sortFunction) { + this.values = this.values.sort(sortFunction); + } + + /** + * Fetches the resources. + * + * @return the resources + */ + public Collection get() { + return values.collectList().block(); + } + + /** + * Determine whether or not the contained type is toOne. + * + * @return True if toOne, false if toMany + */ + public boolean isToOne() { + return relationshipType.isToOne(); + } + + /** + * Fetch the item if the data is toOne. + * + * @return T if toOne + * @throws IllegalAccessError when the data is not toOne + */ + public T getSingleValue() { + if (isToOne()) { + return values.singleOrEmpty().block(); + } + + throw new IllegalAccessError("Data is not toOne"); + } + + /** + * Gets the resource identifiers. + * + * @return the resource identifiers + */ + public Collection toResourceIdentifiers() { + return values + .map(object -> object != null ? ((Resource) object).toResourceIdentifier() : null) + .collectList().block(); + } +} diff --git a/src/main/java/com/faforever/api/data/jackson3compat/DataDeserializer.java b/src/main/java/com/faforever/api/data/jackson3compat/DataDeserializer.java new file mode 100644 index 00000000..8de6d8d7 --- /dev/null +++ b/src/main/java/com/faforever/api/data/jackson3compat/DataDeserializer.java @@ -0,0 +1,45 @@ +/* + * Copyright 2015, Yahoo Inc. + * Licensed under the Apache License, Version 2.0 + * See LICENSE file in project root for terms. + */ +package com.faforever.api.data.jackson3compat; + +import tools.jackson.core.JacksonException; +import tools.jackson.core.JsonParser; +import tools.jackson.databind.DatabindException; +import tools.jackson.databind.DeserializationContext; +import tools.jackson.databind.JsonNode; +import tools.jackson.databind.ValueDeserializer; + +import java.util.ArrayList; +import java.util.List; + +/** + * Custom deserializer for top-level data. + */ +public class DataDeserializer extends ValueDeserializer> { + + @Override + public Data deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws JacksonException { + JsonNode node = jsonParser.readValueAsTree(); + if (node.isArray()) { + List resources = new ArrayList<>(); + for (JsonNode n : node) { + Resource r = deserializationContext.readTreeAsValue(n, Resource.class); + validateResource(jsonParser, r); + resources.add(r); + } + return new Data<>(resources); + } + Resource resource = deserializationContext.readTreeAsValue(node, Resource.class); + validateResource(jsonParser, resource); + return new Data<>(resource); + } + + private void validateResource(JsonParser jsonParser, Resource resource) { + if (resource.getType() == null || resource.getType().isEmpty()) { + throw DatabindException.from(jsonParser, "Resource 'type' field is missing or empty."); + } + } +} diff --git a/src/main/java/com/faforever/api/data/jackson3compat/DataSerializer.java b/src/main/java/com/faforever/api/data/jackson3compat/DataSerializer.java new file mode 100644 index 00000000..a3ef77f6 --- /dev/null +++ b/src/main/java/com/faforever/api/data/jackson3compat/DataSerializer.java @@ -0,0 +1,36 @@ +/* + * Copyright 2015, Yahoo Inc. + * Licensed under the Apache License, Version 2.0 + * See LICENSE file in project root for terms. + */ +package com.faforever.api.data.jackson3compat; + +import org.apache.commons.collections4.CollectionUtils; +import org.apache.commons.collections4.IterableUtils; +import tools.jackson.core.JacksonException; +import tools.jackson.core.JsonGenerator; +import tools.jackson.databind.SerializationContext; +import tools.jackson.databind.ValueSerializer; + +import java.util.Collection; +import java.util.Collections; + +/** + * Custom serializer for top-level data. + */ +public class DataSerializer extends ValueSerializer> { + + @Override + public void serialize(Data data, JsonGenerator jsonGenerator, SerializationContext ctxt) throws JacksonException { + Collection list = data.get(); + if (data.isToOne()) { + if (CollectionUtils.isEmpty(list)) { + jsonGenerator.writePOJO(null); + return; + } + jsonGenerator.writePOJO(IterableUtils.first(list)); + return; + } + jsonGenerator.writePOJO((list == null) ? Collections.emptyList() : list); + } +} diff --git a/src/main/java/com/faforever/api/data/jackson3compat/JsonApiDocument.java b/src/main/java/com/faforever/api/data/jackson3compat/JsonApiDocument.java new file mode 100644 index 00000000..03d488cf --- /dev/null +++ b/src/main/java/com/faforever/api/data/jackson3compat/JsonApiDocument.java @@ -0,0 +1,110 @@ +/* + * Copyright 2015, Yahoo Inc. + * Licensed under the Apache License, Version 2.0 + * See LICENSE file in project root for terms. + */ +package com.faforever.api.data.jackson3compat; + +import com.fasterxml.jackson.annotation.JsonInclude; +import lombok.ToString; +import org.apache.commons.lang3.builder.HashCodeBuilder; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Optional; + +/** + * JSON API Document. + */ +@ToString +public class JsonApiDocument { + private Data data; + private Meta meta; + private final Map links; + private final LinkedHashSet includedRecs; + private final List included; + + public JsonApiDocument() { + this(null); + } + + public JsonApiDocument(Data data) { + links = new LinkedHashMap<>(); + included = new ArrayList<>(); + includedRecs = new LinkedHashSet<>(); + this.data = data; + } + + public void setData(Data data) { + this.data = data; + } + + public Data getData() { + return data; + } + + public void setMeta(Meta meta) { + this.meta = meta; + } + + @JsonInclude(JsonInclude.Include.NON_NULL) + public Meta getMeta() { + return meta; + } + + @JsonInclude(JsonInclude.Include.NON_NULL) + public Map getLinks() { + return links.isEmpty() ? null : links; + } + + public void addLink(String key, String val) { + this.links.put(key, val); + } + + @JsonInclude(JsonInclude.Include.NON_NULL) + public List getIncluded() { + return included.isEmpty() ? null : included; + } + + public void addIncluded(Resource resource) { + if (!includedRecs.contains(resource)) { + included.add(resource); + includedRecs.add(resource); + } + } + + @Override + public int hashCode() { + Collection resources = data == null ? null : data.get(); + return new HashCodeBuilder(37, 79) + .append(resources == null ? 0 : resources.stream().mapToInt(Object::hashCode).sum()) + .append(links) + .append(included == null ? 0 : included.stream().mapToInt(Object::hashCode).sum()) + .build(); + } + + @Override + public boolean equals(Object obj) { + if (!(obj instanceof JsonApiDocument)) { + return false; + } + JsonApiDocument other = (JsonApiDocument) obj; + Collection resources = Optional.ofNullable(data).map(Data::get).orElseGet(Collections::emptySet); + Collection otherResources = + Optional.ofNullable(other.data).map(Data::get).orElseGet(Collections::emptySet); + + if (resources.size() != otherResources.size() || !resources.stream().allMatch(otherResources::contains)) { + return false; + } + // TODO: Verify links and meta? + if (other.getIncluded() == null) { + return included.isEmpty(); + } + return included.stream().allMatch(other.getIncluded()::contains); + } +} diff --git a/src/main/java/com/faforever/api/data/jackson3compat/KeySerializer.java b/src/main/java/com/faforever/api/data/jackson3compat/KeySerializer.java new file mode 100644 index 00000000..8dd58342 --- /dev/null +++ b/src/main/java/com/faforever/api/data/jackson3compat/KeySerializer.java @@ -0,0 +1,45 @@ +/* + * Copyright 2016, Yahoo Inc. + * Licensed under the Apache License, Version 2.0 + * See LICENSE file in project root for terms. + */ +package com.faforever.api.data.jackson3compat; + +import tools.jackson.core.JacksonException; +import tools.jackson.core.JsonGenerator; +import tools.jackson.databind.SerializationContext; +import tools.jackson.databind.ser.std.StdSerializer; + +import java.util.Date; + +/** + * Custom serializer for Serializing Map Keys. + * Change from StdKeySerializer - In cases of enum value it uses + * name() instead of defaulting to toString() since that may be overridden + */ +public class KeySerializer extends StdSerializer { + + protected KeySerializer() { + super(Object.class); + } + + @Override + public void serialize(Object value, JsonGenerator jgen, SerializationContext provider) throws JacksonException { + String str; + Class cls = value.getClass(); + + if (cls == String.class) { + str = (String) value; + } else if (Date.class.isAssignableFrom(cls)) { + provider.defaultSerializeDateKey((Date) value, jgen); + return; + } else if (cls == Class.class) { + str = ((Class) value).getName(); + } else if (cls.isEnum()) { + str = ((Enum) value).name(); + } else { + str = value.toString(); + } + jgen.writeName(str); + } +} diff --git a/src/main/java/com/faforever/api/data/jackson3compat/Meta.java b/src/main/java/com/faforever/api/data/jackson3compat/Meta.java new file mode 100644 index 00000000..39737209 --- /dev/null +++ b/src/main/java/com/faforever/api/data/jackson3compat/Meta.java @@ -0,0 +1,40 @@ +/* + * Copyright 2015, Yahoo Inc. + * Licensed under the Apache License, Version 2.0 + * See LICENSE file in project root for terms. + */ +package com.faforever.api.data.jackson3compat; + +import com.fasterxml.jackson.annotation.JsonAnyGetter; +import com.fasterxml.jackson.annotation.JsonAutoDetect; +import com.yahoo.elide.jsonapi.models.KeyValMap; +import tools.jackson.databind.annotation.JsonDeserialize; + +import java.util.Map; + +/** + * Model for representing JSON API meta information. + */ +@JsonAutoDetect +@JsonDeserialize(using = MetaDeserializer.class) +public class Meta extends KeyValMap { + + /** + * Constructor. + * + * @param map Object containing meta information + */ + public Meta(Map map) { + super(map); + } + + /** + * Expose the meta map so that it will be included in the returned JSON-API document. + * + * @return the meta map + */ + @JsonAnyGetter + public Map getMetaMap() { + return map; + } +} diff --git a/src/main/java/com/faforever/api/data/jackson3compat/MetaDeserializer.java b/src/main/java/com/faforever/api/data/jackson3compat/MetaDeserializer.java new file mode 100644 index 00000000..f3ebfdc6 --- /dev/null +++ b/src/main/java/com/faforever/api/data/jackson3compat/MetaDeserializer.java @@ -0,0 +1,28 @@ +/* + * Copyright 2019, Yahoo Inc. + * Licensed under the Apache License, Version 2.0 + * See LICENSE file in project root for terms. + */ +package com.faforever.api.data.jackson3compat; + +import com.yahoo.elide.jsonapi.models.Meta; +import tools.jackson.core.JacksonException; +import tools.jackson.core.JsonParser; +import tools.jackson.databind.DeserializationContext; +import tools.jackson.databind.JsonNode; +import tools.jackson.databind.ValueDeserializer; + +import java.util.Map; + +/** + * Custom deserializer for top-level meta object. + */ +public class MetaDeserializer extends ValueDeserializer { + + @Override + public Meta deserialize(JsonParser jsonParser, DeserializationContext ctxt) throws JacksonException { + JsonNode node = jsonParser.readValueAsTree(); + // Optional top-level meta member must be an object + return node.isObject() ? new Meta(ctxt.readTreeAsValue(node, Map.class)) : null; + } +} diff --git a/src/main/java/com/faforever/api/data/jackson3compat/Resource.java b/src/main/java/com/faforever/api/data/jackson3compat/Resource.java new file mode 100644 index 00000000..fdce0fdc --- /dev/null +++ b/src/main/java/com/faforever/api/data/jackson3compat/Resource.java @@ -0,0 +1,189 @@ +/* + * Copyright 2015, Yahoo Inc. + * Licensed under the Apache License, Version 2.0 + * See LICENSE file in project root for terms. + */ +package com.faforever.api.data.jackson3compat; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.yahoo.elide.core.PersistentResource; +import com.yahoo.elide.core.RequestScope; +import com.yahoo.elide.core.dictionary.EntityDictionary; +import com.yahoo.elide.core.exceptions.ForbiddenAccessException; +import com.yahoo.elide.core.exceptions.InvalidObjectIdentifierException; +import com.yahoo.elide.core.exceptions.UnknownEntityException; +import com.yahoo.elide.core.request.EntityProjection; +import com.yahoo.elide.core.type.Type; +import com.yahoo.elide.jsonapi.models.Relationship; +import com.yahoo.elide.jsonapi.models.ResourceIdentifier; +import lombok.ToString; +import org.apache.commons.collections4.MapUtils; +import org.apache.commons.lang3.builder.HashCodeBuilder; +import tools.jackson.databind.annotation.JsonSerialize; + +import java.util.Map; +import java.util.Objects; + +/** + * Resource wrapper around serialized/deserialized JSON API + * + * NOTE: We violate the DRY principle to create a clear separation of concern. That is, + * the Resource is a distinct from an internal Record. In fact, they are not + * interchangeable even though they represent very similar data + */ +@ToString +public class Resource { + + //Doesn't work currently - https://github.com/FasterXML/jackson-databind/issues/230 + @JsonProperty(required = true) + private String type; + private String id; + private String lid; + private Map attributes; + private Map relationships; + private Map links; + private Meta meta; + + public Resource(String type, String id) { + this.type = type; + this.id = id; + if (id == null) { + throw new InvalidObjectIdentifierException(id, type); + } + } + + public Resource(@JsonProperty("type") String type, + @JsonProperty("id") String id, + @JsonProperty("lid") String lid, + @JsonProperty("attributes") Map attributes, + @JsonProperty("relationships") Map relationships, + @JsonProperty("links") Map links, + @JsonProperty("meta") Meta meta) { + this.type = type; + this.id = id; + this.lid = lid; + this.attributes = attributes; + this.relationships = relationships; + this.links = links; + this.meta = meta; + if (this.id == null) { + this.id = lid; + } + } + + @JsonInclude(JsonInclude.Include.NON_NULL) + public String getId() { + return id; + } + + @JsonInclude(JsonInclude.Include.NON_NULL) + public String getLid() { + return lid; + } + + public void setRelationships(Map relationships) { + this.relationships = relationships; + } + + @JsonInclude(JsonInclude.Include.NON_NULL) + public Map getRelationships() { + return MapUtils.isEmpty(relationships) ? null : relationships; + } + + @JsonInclude(JsonInclude.Include.NON_NULL) + @JsonSerialize(keyUsing = KeySerializer.class) + public Map getAttributes() { + return MapUtils.isEmpty(attributes) ? null : attributes; + } + + public void setId(String id) { + this.id = id; + } + + public void setLid(String lid) { + this.lid = lid; + } + + public void setAttributes(Map obj) { + this.attributes = obj; + } + + public void setType(String type) { + this.type = type; + } + + public String getType() { + return type; + } + + @JsonInclude(JsonInclude.Include.NON_NULL) + public Meta getMeta() { + return meta; + } + + public void setMeta(Meta meta) { + this.meta = meta; + } + + @JsonInclude(JsonInclude.Include.NON_NULL) + public Map getLinks() { + return links; + } + + public void setLinks(Map links) { + this.links = links; + } + + /** + * Convert Resource to resource identifier. + * + * @return linkage + */ + public ResourceIdentifier toResourceIdentifier() { + return new ResourceIdentifier(type, id != null ? id : lid); + } + + @Override + public int hashCode() { + // We hope that type and id are effectively final after jackson constructs the object... + return new HashCodeBuilder(37, 17).append(type).append(id != null ? id : lid).build(); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj instanceof Resource) { + Resource that = (Resource) obj; + return Objects.equals(this.id, that.id) + && Objects.equals(this.lid, that.lid) + && Objects.equals(this.attributes, that.attributes) + && Objects.equals(this.type, that.type) + && Objects.equals(this.relationships, that.relationships); + } + return false; + } + + public PersistentResource toPersistentResource(RequestScope requestScope) + throws ForbiddenAccessException, InvalidObjectIdentifierException { + EntityDictionary dictionary = requestScope.getDictionary(); + + Type cls = dictionary.getEntityClass(type, requestScope.getRoute().getApiVersion()); + + if (cls == null) { + throw new UnknownEntityException(type); + } + String identifier = id != null ? id : lid; + if (identifier == null) { + throw new InvalidObjectIdentifierException(identifier, type); + } + + EntityProjection projection = EntityProjection.builder() + .type(cls) + .build(); + + return PersistentResource.loadRecord(projection, identifier, requestScope); + } +} diff --git a/src/main/java/com/faforever/api/featuredmods/FeaturedModsController.java b/src/main/java/com/faforever/api/featuredmods/FeaturedModsController.java index 36647f14..ea63fbc4 100644 --- a/src/main/java/com/faforever/api/featuredmods/FeaturedModsController.java +++ b/src/main/java/com/faforever/api/featuredmods/FeaturedModsController.java @@ -1,12 +1,12 @@ package com.faforever.api.featuredmods; import com.faforever.api.data.domain.FeaturedMod; +import com.faforever.api.data.jackson3compat.Data; +import com.faforever.api.data.jackson3compat.JsonApiDocument; +import com.faforever.api.data.jackson3compat.Resource; import com.faforever.api.error.ApiException; import com.faforever.api.error.Error; import com.faforever.api.security.OAuthScope; -import com.yahoo.elide.jsonapi.models.Data; -import com.yahoo.elide.jsonapi.models.JsonApiDocument; -import com.yahoo.elide.jsonapi.models.Resource; import io.swagger.v3.oas.annotations.Operation; import lombok.RequiredArgsConstructor; import org.springframework.security.access.prepost.PreAuthorize; diff --git a/src/main/java/com/faforever/api/user/MeController.java b/src/main/java/com/faforever/api/user/MeController.java index 13b0346a..70f58e3d 100644 --- a/src/main/java/com/faforever/api/user/MeController.java +++ b/src/main/java/com/faforever/api/user/MeController.java @@ -2,12 +2,12 @@ import com.faforever.api.data.domain.Player; import com.faforever.api.data.domain.UserGroup; +import com.faforever.api.data.jackson3compat.Data; +import com.faforever.api.data.jackson3compat.JsonApiDocument; +import com.faforever.api.data.jackson3compat.Resource; import com.faforever.api.player.PlayerService; import com.faforever.api.security.FafUserAuthenticationToken; import com.faforever.api.security.UserSupplier; -import com.yahoo.elide.jsonapi.models.Data; -import com.yahoo.elide.jsonapi.models.JsonApiDocument; -import com.yahoo.elide.jsonapi.models.Resource; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.responses.ApiResponse; import lombok.Builder; From af944ee423609c8f701a08e2771d79bd686c0c1f Mon Sep 17 00:00:00 2001 From: kubo Date: Sat, 27 Dec 2025 00:07:27 +0100 Subject: [PATCH 6/8] SB 4.0.0 --- build.gradle | 5 +- .../api/clan/ClanControllerTest.java | 2 +- src/inttest/resources/config/application.yml | 10 ++-- ...tetStreamToObjectHttpMessageConverter.java | 48 ------------------- .../com/faforever/api/config/MvcConfig.java | 6 --- .../resources/config/application-local.yml | 2 +- 6 files changed, 10 insertions(+), 63 deletions(-) delete mode 100644 src/main/java/com/faforever/api/config/IgnoreOctetStreamToObjectHttpMessageConverter.java diff --git a/build.gradle b/build.gradle index 30b2920c..15e70c39 100644 --- a/build.gradle +++ b/build.gradle @@ -28,7 +28,6 @@ jar.enabled = false repositories { mavenCentral() - maven { url "https://jitpack.io" } } compileJava.dependsOn(processResources) @@ -171,6 +170,7 @@ dependencies { // Manually managed dependencies def elideVersion = "7.1.15" def springdocVersion = "2.8.13" + def commonsVersion = "20251112-0a26247" implementation("com.yahoo.elide:elide-core:${elideVersion}") implementation("com.yahoo.elide:elide-model-config:${elideVersion}") implementation("com.yahoo.elide:elide-spring-boot-autoconfigure:${elideVersion}") @@ -178,7 +178,8 @@ dependencies { implementation("com.yahoo.elide:elide-datastore-jpa:${elideVersion}") implementation("com.yahoo.elide:elide-datastore-multiplex:${elideVersion}") implementation("org.springdoc:springdoc-openapi-starter-webmvc-ui:${springdocVersion}") - implementation("com.github.FAForever:faf-java-commons:0e5d22ffff6e4dea81bac494d532627fcca8ebc4") + implementation("com.faforever.commons:data:${commonsVersion}") + implementation("com.faforever.commons:api:${commonsVersion}") implementation("org.kohsuke:github-api:1.330") implementation("org.eclipse.jgit:org.eclipse.jgit:7.3.0.202506031305-r") implementation("org.jetbrains:annotations:26.0.2-1") diff --git a/src/inttest/java/com/faforever/api/clan/ClanControllerTest.java b/src/inttest/java/com/faforever/api/clan/ClanControllerTest.java index 4c4d9806..4e7c3d82 100644 --- a/src/inttest/java/com/faforever/api/clan/ClanControllerTest.java +++ b/src/inttest/java/com/faforever/api/clan/ClanControllerTest.java @@ -45,7 +45,7 @@ public class ClanControllerTest extends AbstractIntegrationTest { @Test public void meDataWithoutClan() throws Exception { - Player player = playerRepository.getReferenceById(USERID_USER); + Player player = playerRepository.findById(USERID_USER).orElseThrow(); mockMvc.perform(get("/clans/me") .with(getOAuthTokenForUserId(USERID_USER))) diff --git a/src/inttest/resources/config/application.yml b/src/inttest/resources/config/application.yml index 2605b754..bdcda8e0 100644 --- a/src/inttest/resources/config/application.yml +++ b/src/inttest/resources/config/application.yml @@ -103,8 +103,8 @@ faf-api: region: auto user-upload-bucket: user-uploads -logging: - level: - org.hibernate.SQL: DEBUG - org.hibernate.engine.spi.EntityEntry: TRACE - org.springframework.security: DEBUG +#logging: +# level: +# org.hibernate.SQL: DEBUG +# org.hibernate.engine.spi.EntityEntry: TRACE +# org.springframework.security: DEBUG diff --git a/src/main/java/com/faforever/api/config/IgnoreOctetStreamToObjectHttpMessageConverter.java b/src/main/java/com/faforever/api/config/IgnoreOctetStreamToObjectHttpMessageConverter.java deleted file mode 100644 index 5842e719..00000000 --- a/src/main/java/com/faforever/api/config/IgnoreOctetStreamToObjectHttpMessageConverter.java +++ /dev/null @@ -1,48 +0,0 @@ -package com.faforever.api.config; - -import com.faforever.api.map.MapUploadMetadata; -import org.springframework.http.HttpInputMessage; -import org.springframework.http.HttpOutputMessage; -import org.springframework.http.MediaType; -import org.springframework.http.converter.AbstractHttpMessageConverter; -import org.springframework.lang.Nullable; -import org.springframework.security.core.Authentication; -import org.springframework.util.StreamUtils; -import org.springframework.web.multipart.MultipartFile; - -import java.io.IOException; - -/** - * This class is used to support having both @RequestParam and @RequestPart with same multipart name in one request handler. - * When multipart request contains simple request param octet-stream, this class is used to ignore parsing - * of byte stream to {@link MapUploadMetadata}. - * See {@link com.faforever.api.map.MapsController#uploadMap(MultipartFile, String, MapUploadMetadata, Authentication)} - */ -public class IgnoreOctetStreamToObjectHttpMessageConverter extends AbstractHttpMessageConverter { - - - public IgnoreOctetStreamToObjectHttpMessageConverter() { - super(MediaType.APPLICATION_OCTET_STREAM, MediaType.ALL); - } - - - @Override - public boolean supports(Class clazz) { - return MapUploadMetadata.class == clazz; - } - - @Override - public byte[] readInternal(Class clazz, HttpInputMessage message) throws IOException { - return null; - } - - @Override - protected Long getContentLength(byte[] bytes, @Nullable MediaType contentType) { - return 0L; - } - - @Override - protected void writeInternal(byte[] bytes, HttpOutputMessage outputMessage) throws IOException { - StreamUtils.copy(bytes, outputMessage.getBody()); - } -} diff --git a/src/main/java/com/faforever/api/config/MvcConfig.java b/src/main/java/com/faforever/api/config/MvcConfig.java index 6fb9ed3f..e14af281 100644 --- a/src/main/java/com/faforever/api/config/MvcConfig.java +++ b/src/main/java/com/faforever/api/config/MvcConfig.java @@ -3,7 +3,6 @@ import org.springframework.context.annotation.Configuration; import org.springframework.core.Ordered; import org.springframework.format.FormatterRegistry; -import org.springframework.http.converter.HttpMessageConverters; import org.springframework.web.servlet.config.annotation.CorsRegistry; import org.springframework.web.servlet.config.annotation.EnableWebMvc; import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; @@ -33,11 +32,6 @@ public void addCorsMappings(CorsRegistry registry) { .allowedMethods("*"); } - @Override - public void configureMessageConverters(HttpMessageConverters.ServerBuilder builder) { - builder.addCustomConverter(new IgnoreOctetStreamToObjectHttpMessageConverter()); - } - @Override public void addFormatters(FormatterRegistry registry) { registry.addConverter(new NoopMultipartFileToStringConverter()); diff --git a/src/main/resources/config/application-local.yml b/src/main/resources/config/application-local.yml index 53e58a7e..947c6642 100644 --- a/src/main/resources/config/application-local.yml +++ b/src/main/resources/config/application-local.yml @@ -90,4 +90,4 @@ spring: issuer-uri: https://hydra.faforever.com/ logging: level: - com.faforever.api: info + com.faforever.api: debug From e13ac88579b552cd05057f51cbce946ca608cc55 Mon Sep 17 00:00:00 2001 From: kubo Date: Sat, 27 Dec 2025 00:55:40 +0100 Subject: [PATCH 7/8] SB 4.0.1 --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 15e70c39..3a775b21 100644 --- a/build.gradle +++ b/build.gradle @@ -11,7 +11,7 @@ buildscript { plugins { id "java" - id "org.springframework.boot" version "4.0.0" + id "org.springframework.boot" version "4.0.1" id "idea" id "com.adarshr.test-logger" version "4.0.0" } From 528c38469860754939b078dd5812647760665687 Mon Sep 17 00:00:00 2001 From: kubo Date: Sat, 27 Dec 2025 13:10:03 +0100 Subject: [PATCH 8/8] SB 4.0.1 --- src/inttest/resources/config/application.yml | 9 ++++----- .../faforever/api/config/security/WebSecurityConfig.java | 3 ++- src/main/java/com/faforever/api/map/MapsController.java | 4 ++-- .../java/com/faforever/api/security/FafTokenService.java | 6 +++--- src/main/java/com/faforever/api/security/JwtService.java | 6 +++--- .../java/com/faforever/api/user/UsersController.java | 1 - src/main/resources/config/application.yml | 2 +- .../com/faforever/api/security/FafTokenServiceTest.java | 6 +++--- 8 files changed, 18 insertions(+), 19 deletions(-) diff --git a/src/inttest/resources/config/application.yml b/src/inttest/resources/config/application.yml index bdcda8e0..7645f329 100644 --- a/src/inttest/resources/config/application.yml +++ b/src/inttest/resources/config/application.yml @@ -103,8 +103,7 @@ faf-api: region: auto user-upload-bucket: user-uploads -#logging: -# level: -# org.hibernate.SQL: DEBUG -# org.hibernate.engine.spi.EntityEntry: TRACE -# org.springframework.security: DEBUG +logging: + level: + org.hibernate.SQL: DEBUG + org.hibernate.engine.spi.EntityEntry: TRACE diff --git a/src/main/java/com/faforever/api/config/security/WebSecurityConfig.java b/src/main/java/com/faforever/api/config/security/WebSecurityConfig.java index c361fe17..9a8a7cd8 100644 --- a/src/main/java/com/faforever/api/config/security/WebSecurityConfig.java +++ b/src/main/java/com/faforever/api/config/security/WebSecurityConfig.java @@ -56,8 +56,9 @@ public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Excepti "/users/register", "/users/activate", "/users/requestPasswordReset", - "/users/requestPasswordReset", "/users/performPasswordReset", + "/users/buildSteamPasswordResetUrl", + "/users/requestPasswordResetViaSteam", "/users/linkToSteam/**" ).permitAll(); authorizeConfig.anyRequest().authenticated(); diff --git a/src/main/java/com/faforever/api/map/MapsController.java b/src/main/java/com/faforever/api/map/MapsController.java index 17233f16..f0c098a1 100644 --- a/src/main/java/com/faforever/api/map/MapsController.java +++ b/src/main/java/com/faforever/api/map/MapsController.java @@ -22,7 +22,7 @@ import org.springframework.web.servlet.ModelAndView; import tools.jackson.core.JacksonException; import tools.jackson.databind.JsonNode; -import tools.jackson.databind.ObjectMapper; +import tools.jackson.databind.json.JsonMapper; import java.io.IOException; import java.util.Map; @@ -36,7 +36,7 @@ public class MapsController { private final MapService mapService; private final FafApiProperties fafApiProperties; - private final ObjectMapper objectMapper; + private final JsonMapper objectMapper; private final PlayerService playerService; diff --git a/src/main/java/com/faforever/api/security/FafTokenService.java b/src/main/java/com/faforever/api/security/FafTokenService.java index 28596db8..cf44b429 100644 --- a/src/main/java/com/faforever/api/security/FafTokenService.java +++ b/src/main/java/com/faforever/api/security/FafTokenService.java @@ -20,7 +20,7 @@ import org.springframework.util.Assert; import tools.jackson.core.JacksonException; import tools.jackson.core.type.TypeReference; -import tools.jackson.databind.ObjectMapper; +import tools.jackson.databind.json.JsonMapper; import jakarta.validation.constraints.NotNull; import java.nio.file.Files; @@ -38,11 +38,11 @@ public class FafTokenService { static final String KEY_ACTION = "action"; static final String KEY_LIFETIME = "lifetime"; - private final ObjectMapper objectMapper; + private final JsonMapper objectMapper; private final RSASSASigner rsaSigner; private final RSASSAVerifier rsaVerifier; - public FafTokenService(ObjectMapper objectMapper, FafApiProperties properties) throws Exception { + public FafTokenService(JsonMapper objectMapper, FafApiProperties properties) throws Exception { String secretKey = Files.readString(properties.getJwt().getSecretKeyPath()); String publicKey = Files.readString(properties.getJwt().getPublicKeyPath()); diff --git a/src/main/java/com/faforever/api/security/JwtService.java b/src/main/java/com/faforever/api/security/JwtService.java index 9eaec75f..f6a99a3f 100644 --- a/src/main/java/com/faforever/api/security/JwtService.java +++ b/src/main/java/com/faforever/api/security/JwtService.java @@ -14,7 +14,7 @@ import com.nimbusds.jose.crypto.RSASSAVerifier; import com.nimbusds.jose.jwk.RSAKey; import org.springframework.stereotype.Service; -import tools.jackson.databind.ObjectMapper; +import tools.jackson.databind.json.JsonMapper; import jakarta.inject.Inject; import java.io.IOException; @@ -26,10 +26,10 @@ public class JwtService { private final RSASSASigner rsaSigner; private final RSASSAVerifier rsaVerifier; - private final ObjectMapper objectMapper; + private final JsonMapper objectMapper; @Inject - public JwtService(FafApiProperties fafApiProperties, ObjectMapper objectMapper) throws Exception { + public JwtService(FafApiProperties fafApiProperties, JsonMapper objectMapper) throws Exception { String secretKey = Files.readString(fafApiProperties.getJwt().getSecretKeyPath()); String publicKey = Files.readString(fafApiProperties.getJwt().getPublicKeyPath()); diff --git a/src/main/java/com/faforever/api/user/UsersController.java b/src/main/java/com/faforever/api/user/UsersController.java index 64a312d6..2a56a5a0 100644 --- a/src/main/java/com/faforever/api/user/UsersController.java +++ b/src/main/java/com/faforever/api/user/UsersController.java @@ -194,5 +194,4 @@ private void redirectCallbackResult(HttpServletResponse response, CallbackResult response.sendRedirect(uriBuilder.toUriString()); } } - } diff --git a/src/main/resources/config/application.yml b/src/main/resources/config/application.yml index cbd60d30..1b808fce 100644 --- a/src/main/resources/config/application.yml +++ b/src/main/resources/config/application.yml @@ -129,7 +129,7 @@ spring: properties: hibernate: dialect: org.hibernate.dialect.MariaDBDialect - jackson: + jackson2: serialization: WRITE_DATES_AS_TIMESTAMPS: false profiles: diff --git a/src/test/java/com/faforever/api/security/FafTokenServiceTest.java b/src/test/java/com/faforever/api/security/FafTokenServiceTest.java index 2eba5f69..a7ab975f 100644 --- a/src/test/java/com/faforever/api/security/FafTokenServiceTest.java +++ b/src/test/java/com/faforever/api/security/FafTokenServiceTest.java @@ -11,7 +11,7 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import tools.jackson.core.type.TypeReference; -import tools.jackson.databind.ObjectMapper; +import tools.jackson.databind.json.JsonMapper; import java.nio.file.Files; import java.nio.file.Paths; @@ -59,7 +59,7 @@ public class FafTokenServiceTest { private final RSASSASigner rsaSigner; private final RSASSAVerifier rsaVerifier; - private ObjectMapper objectMapper; + private JsonMapper objectMapper; private FafTokenService instance; public FafTokenServiceTest() throws Exception { @@ -75,7 +75,7 @@ public FafTokenServiceTest() throws Exception { @BeforeEach public void setUp() throws Exception { - objectMapper = new ObjectMapper(); + objectMapper = new JsonMapper(); FafApiProperties properties = new FafApiProperties(); properties.getJwt().setSecretKeyPath(Paths.get("test-pki-private.key"));