From 436a4b0508032bb3484f70e20fcc9611cab27cfd Mon Sep 17 00:00:00 2001 From: Sheikah45 Date: Mon, 8 Aug 2022 15:05:47 -0400 Subject: [PATCH] Add tests and remove async from getFeaturedModFiles for testability --- .../FeaturedModsControllerTest.java | 20 ++++++++++---- src/inttest/resources/config/application.yml | 3 +++ .../resources/sql/prepFeaturedMods.sql | 5 ++++ .../api/config/FafApiProperties.java | 8 +++++- .../featuredmods/FeaturedModFileEnricher.java | 9 ++++--- .../api/featuredmods/FeaturedModService.java | 4 +++ .../featuredmods/FeaturedModsController.java | 26 +++++++++---------- .../resources/config/application-local.yml | 6 +++-- src/main/resources/config/application.yml | 4 ++- 9 files changed, 59 insertions(+), 26 deletions(-) diff --git a/src/inttest/java/com/faforever/api/featuredmods/FeaturedModsControllerTest.java b/src/inttest/java/com/faforever/api/featuredmods/FeaturedModsControllerTest.java index 8d66aa542..03788d4ed 100644 --- a/src/inttest/java/com/faforever/api/featuredmods/FeaturedModsControllerTest.java +++ b/src/inttest/java/com/faforever/api/featuredmods/FeaturedModsControllerTest.java @@ -1,11 +1,13 @@ package com.faforever.api.featuredmods; import com.faforever.api.AbstractIntegrationTest; +import com.faforever.api.security.OAuthScope; import org.junit.jupiter.api.Test; import org.springframework.test.context.jdbc.Sql; import static org.hamcrest.Matchers.hasSize; import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.matchesRegex; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @@ -16,11 +18,19 @@ public class FeaturedModsControllerTest extends AbstractIntegrationTest { @Test - public void featuredModFileUrlCorrect() throws Exception { + public void featuredModFileUrlCorrectWithLobbyScope() throws Exception { mockMvc.perform(get("/featuredMods/0/files/latest") - .with(getOAuthTokenWithActiveUser(NO_SCOPE, NO_AUTHORITIES))) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.data", hasSize(1))) - .andExpect(jsonPath("$.data[0].attributes.url", is("USER"))); + .with(getOAuthTokenWithActiveUser(OAuthScope._LOBBY, NO_AUTHORITIES))) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.data", hasSize(1))) + .andExpect(jsonPath("$.data[0].type", is("featuredModFile"))) + .andExpect(jsonPath("$.data[0].attributes.url", matchesRegex(".*\\?verify=[0-9]+-.*"))); + } + + @Test + public void featuredModFileNotVisibleWithoutLobbyScope() throws Exception { + mockMvc.perform(get("/featuredMods/0/files/latest") + .with(getOAuthTokenWithActiveUser(NO_SCOPE, NO_AUTHORITIES))) + .andExpect(status().isForbidden()); } } diff --git a/src/inttest/resources/config/application.yml b/src/inttest/resources/config/application.yml index b42283a8a..aa04ab9cb 100644 --- a/src/inttest/resources/config/application.yml +++ b/src/inttest/resources/config/application.yml @@ -53,6 +53,9 @@ faf-api: max-size-bytes: 4096 image-width: 40 image-height: 20 + cloudflare: + hmac-secret: "banana" + hmac-param: "verify" clan: website-url-format: "http://example.com/%s" tutorial: diff --git a/src/inttest/resources/sql/prepFeaturedMods.sql b/src/inttest/resources/sql/prepFeaturedMods.sql index 56fb769f3..49c298809 100644 --- a/src/inttest/resources/sql/prepFeaturedMods.sql +++ b/src/inttest/resources/sql/prepFeaturedMods.sql @@ -1,4 +1,9 @@ -- game_featuredMods is populated by R__010_game_featuredMods.sql from Flyway +INSERT INTO `updates_faf` (`id`, `filename`, `path`) VALUES +(1, 'ForgedAlliance.exe', 'bin'); + +INSERT INTO `updates_faf_files` (`id`, `fileId`, `version`, `name`, `md5`, `obselete`) VALUES +(1703, 1, 3706, 'ForgedAlliance.3706.exe', 'c20b922a785cf5876c39b7696a16f162', 0); INSERT INTO `updates_fafbeta` (`id`, `filename`, `path`) VALUES (1, 'ForgedAlliance.exe', 'bin'); diff --git a/src/main/java/com/faforever/api/config/FafApiProperties.java b/src/main/java/com/faforever/api/config/FafApiProperties.java index dc47be6e0..8d4a19f17 100644 --- a/src/main/java/com/faforever/api/config/FafApiProperties.java +++ b/src/main/java/com/faforever/api/config/FafApiProperties.java @@ -23,6 +23,7 @@ public class FafApiProperties { private Replay replay = new Replay(); private Avatar avatar = new Avatar(); private Clan clan = new Clan(); + private Cloudflare cloudflare = new Cloudflare(); private FeaturedMod featuredMod = new FeaturedMod(); private GitHub gitHub = new GitHub(); private Deployment deployment = new Deployment(); @@ -137,7 +138,6 @@ public static class Avatar { @Data public static class FeaturedMod { private String fileUrlFormat; - private String cloudflareHmacSecret; } @Data @@ -146,6 +146,12 @@ public static class Clan { private String websiteUrlFormat; } + @Data + public static class Cloudflare { + private String hmacParam; + private String hmacSecret; + } + @Data public static class GitHub { private String webhookSecret; diff --git a/src/main/java/com/faforever/api/featuredmods/FeaturedModFileEnricher.java b/src/main/java/com/faforever/api/featuredmods/FeaturedModFileEnricher.java index 852d2edca..fd8ec3ef0 100644 --- a/src/main/java/com/faforever/api/featuredmods/FeaturedModFileEnricher.java +++ b/src/main/java/com/faforever/api/featuredmods/FeaturedModFileEnricher.java @@ -32,9 +32,9 @@ public void init(FafApiProperties fafApiProperties) { public void enhance(FeaturedModFile featuredModFile) throws NoSuchAlgorithmException, InvalidKeyException { String folder = featuredModFile.getFolderName(); String urlFormat = fafApiProperties.getFeaturedMod().getFileUrlFormat(); - String secret = fafApiProperties.getFeaturedMod().getCloudflareHmacSecret(); + String secret = fafApiProperties.getCloudflare().getHmacSecret(); long timeStamp = Instant.now().getEpochSecond(); - URI featuredModUri = URI.create(String.format(urlFormat, folder, featuredModFile.getOriginalFileName())); + URI featuredModUri = URI.create(urlFormat.formatted(folder, featuredModFile.getOriginalFileName())); // Builds hmac token for cloudflare firewall verification as specified at // https://support.cloudflare.com/hc/en-us/articles/115001376488-Configuring-Token-Authentication @@ -43,8 +43,9 @@ public void enhance(FeaturedModFile featuredModFile) throws NoSuchAlgorithmExcep byte[] macMessage = (featuredModUri.getPath() + timeStamp).getBytes(StandardCharsets.UTF_8); String hmacEncoded = URLEncoder.encode(new String(Base64.getEncoder().encode(mac.doFinal(macMessage)), StandardCharsets.UTF_8), StandardCharsets.UTF_8); - String parameter = "%d-%s".formatted(timeStamp, hmacEncoded); + String parameterValue = "%d-%s".formatted(timeStamp, hmacEncoded); - featuredModFile.setUrl(UriComponentsBuilder.fromUri(featuredModUri).queryParam("verify", parameter).build().toString()); + String queryParam = fafApiProperties.getCloudflare().getHmacParam(); + featuredModFile.setUrl(UriComponentsBuilder.fromUri(featuredModUri).queryParam(queryParam, parameterValue).build().toString()); } } diff --git a/src/main/java/com/faforever/api/featuredmods/FeaturedModService.java b/src/main/java/com/faforever/api/featuredmods/FeaturedModService.java index 0902440f5..19c2c4cdb 100644 --- a/src/main/java/com/faforever/api/featuredmods/FeaturedModService.java +++ b/src/main/java/com/faforever/api/featuredmods/FeaturedModService.java @@ -44,6 +44,10 @@ public Map getFileIds(String modName) { return legacyFeaturedModFileRepository.getFileIds(modName); } + public Optional findModById(int id) { + return featuredModRepository.findById(id); + } + public Optional findModByTechnicalName(String name) { return featuredModRepository.findOneByTechnicalName(name); } diff --git a/src/main/java/com/faforever/api/featuredmods/FeaturedModsController.java b/src/main/java/com/faforever/api/featuredmods/FeaturedModsController.java index 1706e554d..90deebcc5 100644 --- a/src/main/java/com/faforever/api/featuredmods/FeaturedModsController.java +++ b/src/main/java/com/faforever/api/featuredmods/FeaturedModsController.java @@ -1,13 +1,13 @@ package com.faforever.api.featuredmods; import com.faforever.api.data.domain.FeaturedMod; +import com.faforever.api.error.ApiException; +import com.faforever.api.error.Error; import com.faforever.api.security.OAuthScope; -import com.google.common.collect.Maps; import com.yahoo.elide.jsonapi.models.Data; import com.yahoo.elide.jsonapi.models.JsonApiDocument; import com.yahoo.elide.jsonapi.models.Resource; import io.swagger.annotations.ApiOperation; -import org.springframework.scheduling.annotation.Async; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; @@ -17,9 +17,10 @@ import java.util.List; import java.util.Map; import java.util.Optional; -import java.util.concurrent.CompletableFuture; import java.util.function.Function; +import static com.faforever.api.error.ErrorCode.FEATURED_MOD_UNKNOWN; + @RestController @RequestMapping(path = "/featuredMods") public class FeaturedModsController { @@ -30,28 +31,27 @@ public FeaturedModsController(FeaturedModService featuredModService) { this.featuredModService = featuredModService; } - @Async @RequestMapping(path = "/{modId}/files/{version}") @ApiOperation("Lists the required files for a specific featured mod version") @PreAuthorize("hasScope('" + OAuthScope._LOBBY + "')") - public CompletableFuture getFiles(@PathVariable("modId") int modId, - @PathVariable("version") String version, - @RequestParam(value = "page[number]", required = false) Integer page) { + public JsonApiDocument getFiles(@PathVariable("modId") int modId, + @PathVariable("version") String version, + @RequestParam(value = "page[number]", required = false) Integer page) { Integer innerPage = Optional.ofNullable(page).orElse(0); if (innerPage > 1) { - return CompletableFuture.completedFuture(new JsonApiDocument(new Data<>(List.of()))); + return new JsonApiDocument(new Data<>(List.of())); } - Map mods = Maps.uniqueIndex(featuredModService.getFeaturedMods(), FeaturedMod::getId); - FeaturedMod featuredMod = mods.get(modId); + FeaturedMod featuredMod = featuredModService.findModById(modId) + .orElseThrow(() -> new ApiException(new Error(FEATURED_MOD_UNKNOWN, modId))); Integer innerVersion = "latest".equals(version) ? null : Integer.valueOf(version); List values = featuredModService.getFiles(featuredMod.getTechnicalName(), innerVersion).stream() - .map(modFileMapper()) - .toList(); + .map(modFileMapper()) + .toList(); - return CompletableFuture.completedFuture(new JsonApiDocument(new Data<>(values))); + return new JsonApiDocument(new Data<>(values)); } private Function modFileMapper() { diff --git a/src/main/resources/config/application-local.yml b/src/main/resources/config/application-local.yml index 6fad0c84f..36d793f9d 100644 --- a/src/main/resources/config/application-local.yml +++ b/src/main/resources/config/application-local.yml @@ -22,9 +22,10 @@ faf-api: allowed-extensions: ${AVATAR_ALLOWED_FILE_EXTENSIONS:png} featured-mod: file-url-format: ${FEATURED_MOD_URL_FORMAT:https://localhost/legacy-featured-mod-files/%s/%s} - cloudflare-hmac-secret: ${CLOUDFLARE_HMAC_SECRET:banana} git-hub: deployment-environment: ${GITHUB_DEPLOYMENT_ENVIRONMENT:development} + cloudflare: + hmac-secret: ${CLOUDFLARE_HMAC_SECRET:banana} deployment: forged-alliance-exe-path: ${FORGED_ALLIANCE_EXE_PATH} repositories-directory: ${REPOSITORIES_DIRECTORY:build/cache/repos} @@ -73,7 +74,8 @@ spring: oauth2: resourceserver: jwt: - issuer-uri: ${JWT_FAF_HYDRA_ISSUER:https://hydra.test.faforever.com/} + jwk-set-uri: http://localhost:4444/.well-known/jwks.json + issuer-uri: http://faf-ory-hydra:4444/ logging: level: com.faforever.api: debug diff --git a/src/main/resources/config/application.yml b/src/main/resources/config/application.yml index 0a3972975..0afc337ca 100644 --- a/src/main/resources/config/application.yml +++ b/src/main/resources/config/application.yml @@ -14,6 +14,9 @@ faf-api: clan: invite-link-expire-duration-minutes: ${CLAN_INVITE_LINK_EXPIRE_DURATION_MINUTES:604800} website-url-format: ${CLAN_WEBSITE_URL_FORMAT:https://clans.${FAF_DOMAIN}/clan/%s} + cloudflare: + hmac-secret: ${CLOUDFLARE_HMAC_SECRET} + hmac-param: ${CLOUDFLARE_HMAC_PARAM:verify} database: schema-version: ${DATABASE_SCHEMA_VERSION:126} deployment: @@ -25,7 +28,6 @@ faf-api: forged-alliance-develop-exe-path: ${EXE_UPLOAD_DEVELOP_PATH:/content/legacy-featured-mod-files/updates_fafdevelop_files} featured-mod: file-url-format: ${FEATURED_MOD_URL_FORMAT:https://content.${FAF_DOMAIN}/legacy-featured-mod-files/%s/%s} - cloudflare-hmac-secret: ${CLOUDFLARE_HMAC_SECRET} git-hub: access-token: ${GITHUB_ACCESS_TOKEN:false} webhook-secret: ${GITHUB_WEBHOOK_SECRET:false}