-
Notifications
You must be signed in to change notification settings - Fork 29
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add Cacheable URLs for FeatureModFiles (#638)
- Loading branch information
Showing
6 changed files
with
159 additions
and
35 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
52 changes: 52 additions & 0 deletions
52
src/main/java/com/faforever/api/cloudflare/CloudflareService.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
package com.faforever.api.cloudflare; | ||
|
||
import com.faforever.api.config.FafApiProperties; | ||
import org.springframework.stereotype.Service; | ||
|
||
import javax.crypto.Mac; | ||
import javax.crypto.spec.SecretKeySpec; | ||
import java.net.URI; | ||
import java.net.URLEncoder; | ||
import java.nio.charset.StandardCharsets; | ||
import java.security.InvalidKeyException; | ||
import java.security.NoSuchAlgorithmException; | ||
import java.time.Instant; | ||
import java.util.Base64; | ||
|
||
@Service | ||
public class CloudflareService { | ||
|
||
private static final String HMAC_SHA256 = "HmacSHA256"; | ||
|
||
private final Mac mac = Mac.getInstance(HMAC_SHA256); | ||
|
||
public CloudflareService(FafApiProperties fafApiProperties) throws NoSuchAlgorithmException, InvalidKeyException { | ||
String secret = fafApiProperties.getCloudflare().getHmacSecret(); | ||
mac.init(new SecretKeySpec(secret.getBytes(StandardCharsets.UTF_8), HMAC_SHA256)); | ||
} | ||
|
||
/** | ||
* Builds hmac token for cloudflare firewall verification as specified | ||
* <a href="https://support.cloudflare.com/hc/en-us/articles/115001376488-Configuring-Token-Authentication">here</a> | ||
* @param uri uri to generate the hmac token for | ||
* @return string representing the hmac token formatted as {timestamp}-{hashedContent} | ||
*/ | ||
public String generateCloudFlareHmacToken(String uri) { | ||
return generateCloudFlareHmacToken(URI.create(uri)); | ||
} | ||
|
||
/** | ||
* Builds hmac token for cloudflare firewall verification as specified | ||
* <a href="https://support.cloudflare.com/hc/en-us/articles/115001376488-Configuring-Token-Authentication">here</a> | ||
* @param uri uri to generate the hmac token for | ||
* @return string representing the hmac token formatted as {timestamp}-{hashedContent} | ||
*/ | ||
public String generateCloudFlareHmacToken(URI uri) { | ||
long timeStamp = Instant.now().getEpochSecond(); | ||
|
||
byte[] macMessage = (uri.getPath() + timeStamp).getBytes(StandardCharsets.UTF_8); | ||
|
||
String hmacEncoded = URLEncoder.encode(new String(Base64.getEncoder().encode(mac.doFinal(macMessage)), StandardCharsets.UTF_8), StandardCharsets.UTF_8); | ||
return "%d-%s".formatted(timeStamp, hmacEncoded); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
36 changes: 12 additions & 24 deletions
36
src/main/java/com/faforever/api/featuredmods/FeaturedModFileEnricher.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,51 +1,39 @@ | ||
package com.faforever.api.featuredmods; | ||
|
||
import com.faforever.api.cloudflare.CloudflareService; | ||
import com.faforever.api.config.FafApiProperties; | ||
import org.springframework.stereotype.Component; | ||
import org.springframework.web.util.UriComponentsBuilder; | ||
|
||
import javax.crypto.Mac; | ||
import javax.crypto.spec.SecretKeySpec; | ||
import javax.inject.Inject; | ||
import javax.persistence.PostLoad; | ||
import java.net.URI; | ||
import java.net.URLEncoder; | ||
import java.nio.charset.StandardCharsets; | ||
import java.security.InvalidKeyException; | ||
import java.security.NoSuchAlgorithmException; | ||
import java.time.Instant; | ||
import java.util.Base64; | ||
|
||
@Component | ||
public class FeaturedModFileEnricher { | ||
|
||
private static final String HMAC_SHA256 = "HmacSHA256"; | ||
|
||
private static FafApiProperties fafApiProperties; | ||
private static CloudflareService cloudflareService; | ||
|
||
@Inject | ||
public void init(FafApiProperties fafApiProperties) { | ||
public void init(FafApiProperties fafApiProperties, CloudflareService cloudflareService) { | ||
FeaturedModFileEnricher.fafApiProperties = fafApiProperties; | ||
FeaturedModFileEnricher.cloudflareService = cloudflareService; | ||
} | ||
|
||
@PostLoad | ||
public void enhance(FeaturedModFile featuredModFile) throws NoSuchAlgorithmException, InvalidKeyException { | ||
public void enhance(FeaturedModFile featuredModFile) { | ||
String folder = featuredModFile.getFolderName(); | ||
String urlFormat = fafApiProperties.getFeaturedMod().getFileUrlFormat(); | ||
String secret = fafApiProperties.getCloudflare().getHmacSecret(); | ||
long timeStamp = Instant.now().getEpochSecond(); | ||
URI featuredModUri = URI.create(urlFormat.formatted(folder, featuredModFile.getOriginalFileName())); | ||
String urlString = 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 | ||
Mac mac = Mac.getInstance(HMAC_SHA256); | ||
mac.init(new SecretKeySpec(secret.getBytes(StandardCharsets.UTF_8), HMAC_SHA256)); | ||
byte[] macMessage = (featuredModUri.getPath() + timeStamp).getBytes(StandardCharsets.UTF_8); | ||
String hmacToken = cloudflareService.generateCloudFlareHmacToken(urlString); | ||
String hmacParam = fafApiProperties.getCloudflare().getHmacParam(); | ||
|
||
String hmacEncoded = URLEncoder.encode(new String(Base64.getEncoder().encode(mac.doFinal(macMessage)), StandardCharsets.UTF_8), StandardCharsets.UTF_8); | ||
String parameterValue = "%d-%s".formatted(timeStamp, hmacEncoded); | ||
|
||
String queryParam = fafApiProperties.getCloudflare().getHmacParam(); | ||
featuredModFile.setUrl(UriComponentsBuilder.fromUri(featuredModUri).queryParam(queryParam, parameterValue).build().toString()); | ||
featuredModFile.setUrl(UriComponentsBuilder.fromUriString(urlString).queryParam(hmacParam, hmacToken).build().toString()); | ||
featuredModFile.setCacheableUrl(urlString); | ||
featuredModFile.setHmacToken(hmacToken); | ||
featuredModFile.setHmacParameter(hmacParam); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
47 changes: 47 additions & 0 deletions
47
src/test/java/com/faforever/api/cloudflare/CloudflareServiceTest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
package com.faforever.api.cloudflare; | ||
|
||
import com.faforever.api.config.FafApiProperties; | ||
import org.junit.jupiter.api.BeforeEach; | ||
import org.junit.jupiter.api.Test; | ||
import org.junit.jupiter.api.extension.ExtendWith; | ||
import org.mockito.junit.jupiter.MockitoExtension; | ||
|
||
import javax.crypto.Mac; | ||
import javax.crypto.spec.SecretKeySpec; | ||
import java.net.URLEncoder; | ||
import java.nio.charset.StandardCharsets; | ||
import java.util.Base64; | ||
|
||
import static org.junit.jupiter.api.Assertions.assertEquals; | ||
|
||
@ExtendWith(MockitoExtension.class) | ||
public class CloudflareServiceTest { | ||
|
||
private static final String HMAC_SHA256 = "HmacSHA256"; | ||
|
||
private FafApiProperties fafApiProperties = new FafApiProperties(); | ||
private CloudflareService instance; | ||
|
||
@BeforeEach | ||
public void setup() throws Exception { | ||
String secret = "foo"; | ||
fafApiProperties.getCloudflare().setHmacSecret(secret); | ||
|
||
instance = new CloudflareService(fafApiProperties); | ||
} | ||
|
||
@Test | ||
public void hmacTokenGeneration() throws Exception { | ||
String token = instance.generateCloudFlareHmacToken("http://example.com/bar"); | ||
|
||
String[] tokenParts = token.split("-"); | ||
String timeStamp = tokenParts[0]; | ||
|
||
Mac mac = Mac.getInstance(HMAC_SHA256); | ||
mac.init(new SecretKeySpec(fafApiProperties.getCloudflare().getHmacSecret().getBytes(StandardCharsets.UTF_8), HMAC_SHA256)); | ||
byte[] macMessage = ("/bar" + timeStamp).getBytes(StandardCharsets.UTF_8); | ||
|
||
String hmacEncoded = URLEncoder.encode(new String(Base64.getEncoder().encode(mac.doFinal(macMessage)), StandardCharsets.UTF_8), StandardCharsets.UTF_8); | ||
assertEquals(hmacEncoded, tokenParts[1]); | ||
} | ||
} |