diff --git a/Dockerfile b/Dockerfile index 15ab38d..1d65b16 100644 --- a/Dockerfile +++ b/Dockerfile @@ -27,10 +27,11 @@ COPY --from=webappbuild /www/ /usr/local/tomee/webapps/ROOT/ # Copy the war file to the webapps directory COPY --from=javabuild /app/build/libs/irma_email_issuer.war /usr/local/tomee/webapps/ -COPY ./src/main/resources/email-en.html /config/email-en.html -COPY ./src/main/resources/email-nl.html /config/email-nl.html +COPY ./src/main/resources/email-en.html /email-templates/email-en.html +COPY ./src/main/resources/email-nl.html /email-templates/email-nl.html ENV IRMA_CONF="/config/" +ENV EMAIL_TEMPLATE_DIR="/email-templates/" EXPOSE 8080 # Copy the config file to the webapp. This is done at runtime so that the config file can be mounted as a volume. diff --git a/src/main/java/foundation/privacybydesign/email/EmailConfiguration.java b/src/main/java/foundation/privacybydesign/email/EmailConfiguration.java index a2c6ca9..a423b24 100644 --- a/src/main/java/foundation/privacybydesign/email/EmailConfiguration.java +++ b/src/main/java/foundation/privacybydesign/email/EmailConfiguration.java @@ -66,7 +66,7 @@ public static void load() { public String getVerifyEmailBody(String language) { try { - return new String(EmailConfiguration.getResource("email-" + language + ".html")); + return new String(EmailConfiguration.getEmailTemplate("email-" + language + ".html")); } catch (IOException e) { logger.error("Failed to read email file"); throw new RuntimeException(e); diff --git a/src/main/java/foundation/privacybydesign/email/common/BaseConfiguration.java b/src/main/java/foundation/privacybydesign/email/common/BaseConfiguration.java index c91c1e6..fa38fb2 100644 --- a/src/main/java/foundation/privacybydesign/email/common/BaseConfiguration.java +++ b/src/main/java/foundation/privacybydesign/email/common/BaseConfiguration.java @@ -20,26 +20,31 @@ import java.security.spec.X509EncodedKeySpec; import java.util.ArrayList; import java.util.HashMap; +import java.nio.file.Path; public class BaseConfiguration { // Override these in a static {} block public static Class> clazz; public static Logger logger = LoggerFactory.getLogger(BaseConfiguration.class); - public static String filename = "config.json"; + public static String configFileName = "config.json"; public static String environmentVarPrefix = "IRMA_CONF_"; public static String confDirEnvironmentVarName = "IRMA_CONF"; + public static String emailTemplateDirVarName = "EMAIL_TEMPLATE_DIR"; + public static String emailTemplateDir; public static String confDirName; + public static String templatefDirName; public static boolean printOnLoad = false; public static boolean testing = false; // Return this from a static getInstance() public static BaseConfiguration instance; private static URI confPath; + private static URI templatePath; public static void load() { try { - String json = new String(getResource(filename)); + String json = new String(getResource(configFileName)); instance = GsonUtil.getGson().fromJson(json, clazz); logger.info("Using configuration directory: " + BaseConfiguration.getConfigurationDirectory().toString()); } catch (IOException|JsonSyntaxException e) { @@ -68,6 +73,32 @@ public static byte[] getResource(String filename) throws IOException { return convertStreamToByteArray(getResourceStream(filename), 2048); } + public static FileInputStream getEmailTemplateStream(String filename) throws IOException { + validateFilename(filename); + Path resolvedPath = resolvePath(getTemplateDirectory(), filename); + logger.info("trying to load: " + resolvedPath.toString()); + return new FileInputStream(resolvedPath.toFile()); + } + + private static void validateFilename(String filename) { + if (filename == null || filename.isEmpty() || filename.contains("..")) { + throw new IllegalArgumentException("Invalid filename: " + filename); + } + // Optional: Add further filename validation, such as allowed extensions + } + + private static Path resolvePath(URI baseDirectory, String filename) { + Path resolvedPath = new File(baseDirectory).toPath().resolve(filename).normalize(); + if (!resolvedPath.startsWith(new File(baseDirectory).toPath())) { + throw new SecurityException("Path traversal attempt detected for file: " + filename); + } + return resolvedPath; + } + + public static byte[] getEmailTemplate(String filename) throws IOException { + return convertStreamToByteArray(getEmailTemplateStream(filename), 2048); + } + public static byte[] convertStreamToByteArray(InputStream stream, int size) throws IOException { byte[] buffer = new byte[size]; ByteArrayOutputStream os = new ByteArrayOutputStream(); @@ -211,12 +242,45 @@ public static URI getEnvironmentVariableConfDir() throws URISyntaxException { return pathToURI(envDir, true); } + public static URI getEnvironmentVariableTemplateDir() throws URISyntaxException { + String envDir = System.getenv(emailTemplateDirVarName); + if (envDir == null || envDir.length() == 0) + return null; + return pathToURI(envDir, true); + } + + public static URI getTemplateDirectory() throws IllegalStateException, IllegalArgumentException { + if (templatePath != null) + return templatePath; + try { + URI envCandidate = getEnvironmentVariableTemplateDir(); + if (envCandidate != null) { + if (isEmailTemplateDirectory(envCandidate)) { + logger.info("Taking template directory specified by environment variable " + emailTemplateDirVarName); + templatePath = envCandidate; + return templatePath; + } else { + // If the user specified an incorrect path (s)he will want to know, so bail out here + throw new IllegalArgumentException("Specified path in " + emailTemplateDirVarName + + " is not a valid configuration directory"); + } + } + throw new IllegalStateException("No valid template directory found"); + } catch (URISyntaxException e) { + throw new IllegalArgumentException(e); + } + } + /** * Returns true if the specified path is a valid configuration directory. A directory * is considered a valid configuration directory if it contains a file called $filename. */ public static boolean isConfDirectory(URI candidate) { - return candidate != null && new File(candidate.resolve(filename)).isFile(); + return candidate != null && new File(candidate.resolve(configFileName)).isFile(); + } + + public static boolean isEmailTemplateDirectory(URI candidate) { + return candidate != null && new File(candidate.resolve("email-en.html")).isFile(); } /** @@ -228,7 +292,7 @@ public static URI GetJavaResourcesDirectory() throws URISyntaxException { // seems to be to ask for an existing file or directory within the resources. That is, // BaseConfiguration.class.getClassLoader().getResource("/") or variants thereof // give an incorrect path. - String testfile = BaseConfiguration.testing ? "config.test.json" : filename; + String testfile = BaseConfiguration.testing ? "config.test.json" : configFileName; URL url = BaseConfiguration.class.getClassLoader().getResource(testfile); if (url != null) // Construct an URI of the parent path return pathToURI(new File(url.getPath()).getParent(), true);