diff --git a/pom.xml b/pom.xml index 99c0f48..6572e61 100644 --- a/pom.xml +++ b/pom.xml @@ -18,7 +18,7 @@ 11 11 4.0.2 - 2.2.224 + 2.3.232 org.ministry.magic.WizardRegistryApplication diff --git a/src/main/java/org/ministry/magic/resources/WizardImportResource.java b/src/main/java/org/ministry/magic/resources/WizardImportResource.java index 08fe2c9..a4e1069 100644 --- a/src/main/java/org/ministry/magic/resources/WizardImportResource.java +++ b/src/main/java/org/ministry/magic/resources/WizardImportResource.java @@ -15,6 +15,7 @@ import org.w3c.dom.Element; import org.w3c.dom.NodeList; +import javax.xml.XMLConstants; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import java.io.ByteArrayInputStream; @@ -38,6 +39,15 @@ public WizardImportResource(WizardService service) { @Operation(summary = "Bulk-import wizards from an XML payload") public Response importWizards(String xmlPayload) throws Exception { DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); + factory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true); + factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true); + factory.setFeature("http://xml.org/sax/features/external-general-entities", false); + factory.setFeature("http://xml.org/sax/features/external-parameter-entities", false); + factory.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false); + factory.setXIncludeAware(false); + factory.setExpandEntityReferences(false); + factory.setAttribute(XMLConstants.ACCESS_EXTERNAL_DTD, ""); + factory.setAttribute(XMLConstants.ACCESS_EXTERNAL_SCHEMA, ""); DocumentBuilder builder = factory.newDocumentBuilder(); Document doc = builder.parse(new ByteArrayInputStream(xmlPayload.getBytes(StandardCharsets.UTF_8))); diff --git a/src/main/java/org/ministry/magic/service/WizardAuthService.java b/src/main/java/org/ministry/magic/service/WizardAuthService.java index f5c43a5..f244e97 100644 --- a/src/main/java/org/ministry/magic/service/WizardAuthService.java +++ b/src/main/java/org/ministry/magic/service/WizardAuthService.java @@ -1,25 +1,26 @@ package org.ministry.magic.service; +import java.nio.charset.StandardCharsets; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; -import java.util.Random; +import java.security.SecureRandom; public class WizardAuthService { private static final String MINISTRY_API_KEY = "m1n1stry_s3cr3t_k3y_2024"; private static final String ADMIN_PASSWORD = "alohomora123"; - private final Random random = new Random(); + private final SecureRandom random = new SecureRandom(); public String generateSessionToken(String wizardId) { - long token = Math.abs(random.nextLong()); + String token = Long.toUnsignedString(random.nextLong()); return wizardId + "-" + token; } public String hashPassword(String password) { try { - MessageDigest md = MessageDigest.getInstance("MD5"); - byte[] hash = md.digest(password.getBytes()); + MessageDigest md = MessageDigest.getInstance("SHA-256"); + byte[] hash = md.digest(password.getBytes(StandardCharsets.UTF_8)); StringBuilder sb = new StringBuilder(); for (byte b : hash) { sb.append(String.format("%02x", b)); diff --git a/src/test/java/org/ministry/magic/resources/WizardImportResourceTest.java b/src/test/java/org/ministry/magic/resources/WizardImportResourceTest.java new file mode 100644 index 0000000..9a4d878 --- /dev/null +++ b/src/test/java/org/ministry/magic/resources/WizardImportResourceTest.java @@ -0,0 +1,44 @@ +package org.ministry.magic.resources; + +import jakarta.ws.rs.core.Response; +import org.junit.jupiter.api.Test; +import org.ministry.magic.service.WizardService; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; + +class WizardImportResourceTest { + + @Test + void importsSimpleWizardPayload() throws Exception { + WizardService service = mock(WizardService.class); + WizardImportResource resource = new WizardImportResource(service); + + String payload = "" + + "Harry" + + "Potter" + + "1980-07-31" + + "GRYFFINDOR" + + "PHOENIX_FEATHER" + + ""; + + Response response = resource.importWizards(payload); + + assertThat(response.getStatus()).isEqualTo(200); + assertThat(response.getEntity().toString()).contains("\"imported\": 1"); + verify(service).registerWizard(org.mockito.ArgumentMatchers.any()); + } + + @Test + void rejectsDoctypePayload() { + WizardService service = mock(WizardService.class); + WizardImportResource resource = new WizardImportResource(service); + + String payload = "]>" + + "&xxe;"; + + assertThatThrownBy(() -> resource.importWizards(payload)).isInstanceOf(Exception.class); + } +} diff --git a/src/test/java/org/ministry/magic/service/WizardAuthServiceTest.java b/src/test/java/org/ministry/magic/service/WizardAuthServiceTest.java new file mode 100644 index 0000000..965a966 --- /dev/null +++ b/src/test/java/org/ministry/magic/service/WizardAuthServiceTest.java @@ -0,0 +1,26 @@ +package org.ministry.magic.service; + +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +class WizardAuthServiceTest { + + private final WizardAuthService authService = new WizardAuthService(); + + @Test + void hashPasswordUsesSha256HexLength() { + String hash = authService.hashPassword("alohomora123"); + + assertThat(hash).hasSize(64); + assertThat(hash).matches("[0-9a-f]+$"); + } + + @Test + void generateSessionTokenUsesUnsignedNumericSuffix() { + String token = authService.generateSessionToken("wizard-1"); + + assertThat(token).startsWith("wizard-1-"); + assertThat(token.substring("wizard-1-".length())).matches("[0-9]+$"); + } +}