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]+$");
+ }
+}