Skip to content

Commit

Permalink
Add tests and remove async from getFeaturedModFiles for testability
Browse files Browse the repository at this point in the history
  • Loading branch information
Sheikah45 authored and Brutus5000 committed Aug 9, 2022
1 parent 33dff8f commit 436a4b0
Show file tree
Hide file tree
Showing 9 changed files with 59 additions and 26 deletions.
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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());
}
}
3 changes: 3 additions & 0 deletions src/inttest/resources/config/application.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
5 changes: 5 additions & 0 deletions src/inttest/resources/sql/prepFeaturedMods.sql
Original file line number Diff line number Diff line change
@@ -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');
Expand Down
8 changes: 7 additions & 1 deletion src/main/java/com/faforever/api/config/FafApiProperties.java
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down Expand Up @@ -137,7 +138,6 @@ public static class Avatar {
@Data
public static class FeaturedMod {
private String fileUrlFormat;
private String cloudflareHmacSecret;
}

@Data
Expand All @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,10 @@ public Map<String, Integer> getFileIds(String modName) {
return legacyFeaturedModFileRepository.getFileIds(modName);
}

public Optional<FeaturedMod> findModById(int id) {
return featuredModRepository.findById(id);
}

public Optional<FeaturedMod> findModByTechnicalName(String name) {
return featuredModRepository.findOneByTechnicalName(name);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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 {
Expand All @@ -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<JsonApiDocument> 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<Integer, FeaturedMod> 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<Resource> 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<FeaturedModFile, Resource> modFileMapper() {
Expand Down
6 changes: 4 additions & 2 deletions src/main/resources/config/application-local.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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}
Expand Down Expand Up @@ -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
4 changes: 3 additions & 1 deletion src/main/resources/config/application.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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}
Expand Down

0 comments on commit 436a4b0

Please sign in to comment.