add security features + java property support

- set `ALLOW_PLANTUML_INCLUDE` only once and decentralized inside the `DiagramResponse` class and call this init method after initializing the server
- set `PLANTUML_SECURITY_PROFILE` to `INTERNET` by default (BREAKING CHANGES)
- add possibility to set PlantUML system properties over a file with `PLANTUML_PROPERTY_FILE`
- adjust documentation
- add "Breaking changes" hint to README
This commit is contained in:
HeinrichAD
2023-06-10 18:34:27 +02:00
committed by PlantUML
parent 5fa6cbc82f
commit 09a7ce4973
7 changed files with 88 additions and 54 deletions

View File

@@ -48,6 +48,8 @@ import net.sourceforge.plantuml.core.DiagramDescription;
import net.sourceforge.plantuml.core.ImageData;
import net.sourceforge.plantuml.error.PSystemError;
import net.sourceforge.plantuml.preproc.Defines;
import net.sourceforge.plantuml.security.SecurityProfile;
import net.sourceforge.plantuml.security.SecurityUtils;
import net.sourceforge.plantuml.version.Version;
/**
@@ -61,13 +63,18 @@ public class DiagramResponse {
*/
private static final String POWERED_BY = "PlantUML Version " + Version.versionString();
/**
* PLANTUML_CONFIG_FILE content.
*/
private static final List<String> CONFIG = new ArrayList<>();
/**
* Cache/flag to ensure that the `init()` method is called only once.
*/
private static boolean initialized = false;
static {
OptionFlags.ALLOW_INCLUDE = false;
if ("true".equalsIgnoreCase(System.getenv("ALLOW_PLANTUML_INCLUDE"))) {
OptionFlags.ALLOW_INCLUDE = true;
}
init();
}
/**
@@ -96,6 +103,43 @@ public class DiagramResponse {
request = req;
}
/**
* Initialize PlantUML configurations and properties as well as loading the PlantUML config file.
*/
public static void init() {
if (initialized) {
return;
}
initialized = true;
// set allow include to false by default
OptionFlags.ALLOW_INCLUDE = false;
if ("true".equalsIgnoreCase(System.getenv("ALLOW_PLANTUML_INCLUDE"))) {
OptionFlags.ALLOW_INCLUDE = true;
}
// set security profile to INTERNET by default
// NOTE: this property is cached inside PlantUML and cannot be changed after the first call of PlantUML
System.setProperty("PLANTUML_SECURITY_PROFILE", SecurityProfile.INTERNET.toString());
if (System.getenv("PLANTUML_SECURITY_PROFILE") != null) {
System.setProperty("PLANTUML_SECURITY_PROFILE", System.getenv("PLANTUML_SECURITY_PROFILE"));
}
// load properties from file
if (System.getenv("PLANTUML_PROPERTY_FILE") != null) {
try (FileReader propertyFileReader = new FileReader(System.getenv("PLANTUML_PROPERTY_FILE"))) {
System.getProperties().load(propertyFileReader);
} catch (IOException e) {
e.printStackTrace();
}
}
// load PlantUML config file
if (System.getenv("PLANTUML_CONFIG_FILE") != null) {
try (BufferedReader br = new BufferedReader(new FileReader(System.getenv("PLANTUML_CONFIG_FILE")))) {
br.lines().forEach(CONFIG::add);
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* Render and send a specific uml diagram.
*
@@ -108,23 +152,8 @@ public class DiagramResponse {
response.addHeader("Access-Control-Allow-Origin", "*");
response.setContentType(getContentType());
if (CONFIG.size() == 0 && System.getenv("PLANTUML_CONFIG_FILE") != null) {
// Read config
final BufferedReader br = new BufferedReader(new FileReader(System.getenv("PLANTUML_CONFIG_FILE")));
if (br == null) {
return;
}
try {
String s = null;
while ((s = br.readLine()) != null) {
CONFIG.add(s);
}
} finally {
br.close();
}
}
SourceStringReader reader = new SourceStringReader(Defines.createEmpty(), uml, CONFIG);
final Defines defines = getPreProcDefines();
SourceStringReader reader = new SourceStringReader(defines, uml, CONFIG);
if (CONFIG.size() > 0 && reader.getBlocks().get(0).getDiagram().getWarningOrError() != null) {
reader = new SourceStringReader(uml);
}
@@ -156,6 +185,23 @@ public class DiagramResponse {
diagram.exportDiagram(response.getOutputStream(), idx, new FileFormatOption(format));
}
/**
* Get PlantUML preprocessor defines.
*
* @return preprocessor defines
*/
private Defines getPreProcDefines() {
final Defines defines;
if (SecurityUtils.getSecurityProfile() == SecurityProfile.UNSECURE) {
// set dirpath to current dir but keep filename and filenameNoExtension undefined
defines = Defines.createWithFileName(new java.io.File("dummy.puml"));
defines.overrideFilename("");
} else {
defines = Defines.createEmpty();
}
return defines;
}
/**
* Is block uml unmodified?
*

View File

@@ -34,7 +34,6 @@ import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import net.sourceforge.plantuml.OptionFlags;
import net.sourceforge.plantuml.api.PlantumlUtils;
import net.sourceforge.plantuml.code.NoPlantumlCompressionException;
import net.sourceforge.plantuml.png.MetadataTag;
@@ -55,6 +54,12 @@ import net.sourceforge.plantuml.servlet.utility.UrlDataExtractor;
@SuppressWarnings("SERIAL")
public class PlantUmlServlet extends AsciiCoderServlet {
static {
// Initialize the PlantUML server.
// You could say that this is like the `static void main(String[] args)` of the PlantUML server.
DiagramResponse.init();
}
/**
* Default encoded uml text.
* Bob -> Alice : hello
@@ -66,13 +71,6 @@ public class PlantUmlServlet extends AsciiCoderServlet {
return "uml";
}
static {
OptionFlags.ALLOW_INCLUDE = false;
if ("true".equalsIgnoreCase(System.getenv("ALLOW_PLANTUML_INCLUDE"))) {
OptionFlags.ALLOW_INCLUDE = true;
}
}
/**
* Encode arbitrary string to HTML string.
*

View File

@@ -41,7 +41,6 @@ import jakarta.servlet.http.HttpServletResponse;
import net.sourceforge.plantuml.BlockUml;
import net.sourceforge.plantuml.FileFormat;
import net.sourceforge.plantuml.OptionFlags;
import net.sourceforge.plantuml.SourceStringReader;
import net.sourceforge.plantuml.core.Diagram;
import net.sourceforge.plantuml.core.UmlSource;
@@ -54,13 +53,6 @@ import net.sourceforge.plantuml.core.UmlSource;
@SuppressWarnings("SERIAL")
public class ProxyServlet extends HttpServlet {
static {
OptionFlags.ALLOW_INCLUDE = false;
if ("true".equalsIgnoreCase(System.getenv("ALLOW_PLANTUML_INCLUDE"))) {
OptionFlags.ALLOW_INCLUDE = true;
}
}
public static boolean forbiddenURL(String full) {
if (full == null) {
return true;

View File

@@ -34,7 +34,6 @@ import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import net.sourceforge.plantuml.FileFormat;
import net.sourceforge.plantuml.OptionFlags;
import net.sourceforge.plantuml.servlet.utility.UmlExtractor;
import net.sourceforge.plantuml.servlet.utility.UrlDataExtractor;
@@ -44,13 +43,6 @@ import net.sourceforge.plantuml.servlet.utility.UrlDataExtractor;
@SuppressWarnings("SERIAL")
public abstract class UmlDiagramService extends HttpServlet {
static {
OptionFlags.ALLOW_INCLUDE = false;
if ("true".equalsIgnoreCase(System.getenv("ALLOW_PLANTUML_INCLUDE"))) {
OptionFlags.ALLOW_INCLUDE = true;
}
}
@Override
public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
final String url = request.getRequestURI();

View File

@@ -29,7 +29,6 @@ import java.net.URLDecoder;
import net.sourceforge.plantuml.FileFormat;
import net.sourceforge.plantuml.FileFormatOption;
import net.sourceforge.plantuml.OptionFlags;
import net.sourceforge.plantuml.SourceStringReader;
import net.sourceforge.plantuml.code.Transcoder;
import net.sourceforge.plantuml.code.TranscoderUtil;
@@ -42,13 +41,6 @@ import net.sourceforge.plantuml.core.ImageData;
*/
public abstract class UmlExtractor {
static {
OptionFlags.ALLOW_INCLUDE = false;
if ("true".equalsIgnoreCase(System.getenv("ALLOW_PLANTUML_INCLUDE"))) {
OptionFlags.ALLOW_INCLUDE = true;
}
}
/**
* Build the complete UML source from the compressed source extracted from the
* HTTP URI.