Skip to content
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package net.adoptopenjdk.icedteaweb.deploymentrules;

/**
* Action object of Rule from the ruleset file
* Stores the attributes value from id tag permission and version.
* If permission is run, then location which is the url whitelisted is permitted to be accessible.
*/
class Action {

private String permission;
private String version;
private String message;

public String getPermission() {
return permission;
}

public void setPermission(final String permission) {
this.permission = permission;
}

public String getVersion() {
return version;
}

public void setVersion(final String version) {
this.version = version;
}

public String getMessage() {
return message;
}

public void setMessage(final String message) {
this.message = message;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package net.adoptopenjdk.icedteaweb.deploymentrules;

/**
* Certificate object of Rule from the ruleset file
* Stores the attributes value from action tag hash.
* This is class is rarely used yet and can be extended when a
* UI component to display the entire ruleset.xml file and edit it will be enhanced
*/
class Certificate {

private String hash;

public String getHash() {
return hash;
}

public void setHash(String hash) {
this.hash = hash;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package net.adoptopenjdk.icedteaweb.deploymentrules;

import java.net.URL;

/**
* See https://docs.oracle.com/javase/8/docs/technotes/guides/deploy/deployment_rules.html#CIHDCEDE
*/
class Rule {
private String location;
private Certificate certificate;
private Action action;

public String getLocation() {
return location;
}

public void setLocation(final String location) {
this.location = location;
}

public Certificate getCertificate() {
return certificate;
}

public void setCertificate(final Certificate certificate) {
this.certificate = certificate;
}

public Action getAction() {
return action;
}

public void setAction(final Action action) {
this.action = action;
}

public boolean matches(URL url) {
// TODO: implement according to https://docs.oracle.com/javase/10/deploy/deployment-rule-set.htm#GUID-413F29CF-81B5-4154-9C52-22D993819C2B
// Maybe take some inspiration from ParsedWhitelistEntry.matches(URL)
return false;
}

public boolean isAllowedToRun() {
// TODO: return true if this is allowed to run according to the action
return false;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package net.adoptopenjdk.icedteaweb.deploymentrules;

import net.adoptopenjdk.icedteaweb.io.IOUtils;
import net.adoptopenjdk.icedteaweb.xmlparser.ParseException;
import net.adoptopenjdk.icedteaweb.xmlparser.XMLParser;
import net.adoptopenjdk.icedteaweb.xmlparser.XmlNode;
import net.adoptopenjdk.icedteaweb.xmlparser.XmlParserFactory;

import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

import static net.adoptopenjdk.icedteaweb.xmlparser.ParserType.MALFORMED;

class RulesetJarFile {

private static final String RULESET_XML = "ruleset.xml";

private final File jarFile;

public RulesetJarFile(String rulesetPath) {
jarFile = new File(rulesetPath);
}

public XmlNode getRulesetXml() throws ParseException {
final File rulesetJarFile = jarFile;

if (!rulesetJarFile.exists()) {
throw new RuntimeException("Ruleset jar file is missing");
}

final String content = getRulesetXmlContent(rulesetJarFile);
return parseXml(content);
}

public String getRulesetXmlContent(File rulesetJarFile) throws ParseException {
try {
final JarFile file = new JarFile(rulesetJarFile);
final JarEntry entry = file.getJarEntry(RULESET_XML);
if (entry == null) {
throw new ParseException("could not find a " + RULESET_XML + " in the jar " + rulesetJarFile);
}

try (final InputStream in = file.getInputStream(entry)) {
return IOUtils.readContentAsUtf8String(in);
}
} catch (IOException e) {
throw new ParseException("file IO exception accessing the ruleset or some network issues", e);
}
}

private XmlNode parseXml(String content) throws ParseException {
try {
final InputStream is = new ByteArrayInputStream(content.getBytes(StandardCharsets.UTF_8));
final XMLParser xmlParser = XmlParserFactory.getParser(MALFORMED);
return xmlParser.getRootNode(is);
} catch (ParseException e) {
throw new ParseException("Could not parser the root Node" + e.getMessage());
}
}

public boolean isNotPresent() {
return !jarFile.exists();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
package net.adoptopenjdk.icedteaweb.deploymentrules;

import net.adoptopenjdk.icedteaweb.xmlparser.ParseException;
import net.adoptopenjdk.icedteaweb.xmlparser.XmlNode;

import java.util.ArrayList;
import java.util.List;

import static net.adoptopenjdk.icedteaweb.xmlparser.NodeUtils.getAttribute;
import static net.adoptopenjdk.icedteaweb.xmlparser.NodeUtils.getChildNodes;


/**
* Contains methods to parse an XML document into a DeploymentRuleSetFile.
* Implements JNLP specification version 1.0.
*
* See DTD: https://docs.oracle.com/javase/8/docs/technotes/guides/deploy/deployment_rules.html#CIHBAJBB
* See: https://docs.oracle.com/javase/8/docs/technotes/guides/deploy/deployment_rules.html#CIHGIDJI
*/
class RulesetParser {

private static final String ID_ELEMENT = "id";
private static final String RULE_SET_ELEMENT = "ruleset";

//From rule starts the actual list of rule and locations stored.
private static final String RULE_ELEMENT = "rule";
private static final String ACTION_ELEMENT = "action";
//id element
private static final String LOCATION_ATTRIBUTE = "location";
//certificate element
private static final String HASH_ATTRIBUTE = "hash";
//action element
private static final String VERSION_ATTRIBUTE = "version";
private static final String PERMISSION_ATTRIBUTE = "permission";

/**
* Create a parser for the Deployment rule set file
* Reads the jar and ruleset.xml file is read and parsed. Adds a deploymentRuleSet tag to cover the legalities
* If any with using a Oracle ruleset.xml.
* <p>
*
* @param root the root XmlNode
* @throws ParseException if the DeploymentRuleSet string is invalid
*/
public List<Rule> getRules(final XmlNode root) throws ParseException {
// ensure it's a DeploymentRuleSet node
if (root == null || !root.getNodeName().equals(RULE_SET_ELEMENT)) {
throw new ParseException("Root element is not a <" + RULE_SET_ELEMENT + "> element.");
}
return getRulesFromRuleset(root);
}

/**
* Extracts rules from the xml tree.
* The {@code version} attribute of the {@code ruleset} element is ignored
* since there will be no new version of the schema in the future.
*
* @param parent the root node
* @return all rules found.
* @throws ParseException if the xml is not valid.
*/
private List<Rule> getRulesFromRuleset(final XmlNode parent) throws ParseException {
final List<Rule> result = new ArrayList<>();
final XmlNode[] rules = getChildNodes(parent, RULE_ELEMENT);

if (rules.length == 0) {
throw new ParseException("No rule <rule> element specified.");
}

for (final XmlNode rule : rules) {
result.add(getRule(rule));
}
return result;
}

private Rule getRule(final XmlNode node) {

// create rules
final Rule rule = new Rule();

// step through the elements
// first populate the id tag attribute
final XmlNode potentialIdPart = node.getFirstChild();
if (potentialIdPart.getNodeName().equals(ID_ELEMENT)) {
//certificate element
final String hash = getAttribute(potentialIdPart, HASH_ATTRIBUTE, null);
//id element
final Certificate certs = new Certificate();
certs.setHash(hash);

final String location = getAttribute(potentialIdPart, LOCATION_ATTRIBUTE, null);
rule.setCertificate(certs);
rule.setLocation(location);
}

// next populate the action tag attribute.
final XmlNode potentialActionPart = potentialIdPart.getNextSibling();
if (potentialActionPart.getNodeName().equals(ACTION_ELEMENT)) {
final Action action = new Action();
//action element
final String permission = getAttribute(potentialActionPart, PERMISSION_ATTRIBUTE, null);
final String version = getAttribute(potentialActionPart, VERSION_ATTRIBUTE, null);
action.setPermission(permission);
action.setVersion(version);
rule.setAction(action);
}

return rule;
}
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
package net.adoptopenjdk.icedteaweb.deploymentrules;

import net.adoptopenjdk.icedteaweb.Assert;
import net.adoptopenjdk.icedteaweb.logging.Logger;
import net.adoptopenjdk.icedteaweb.logging.LoggerFactory;
import net.adoptopenjdk.icedteaweb.xmlparser.ParseException;
import net.adoptopenjdk.icedteaweb.xmlparser.XmlNode;
import net.sourceforge.jnlp.runtime.JNLPRuntime;
import net.sourceforge.jnlp.util.IpUtil;

import java.net.URL;
import java.util.List;

import static java.util.Collections.emptyList;
import static net.sourceforge.jnlp.config.ConfigurationConstants.KEY_DEPLOYMENT_RULE_SET;

public class UrlDeploymentRulesSetUtils {

private static final Logger LOG = LoggerFactory.getLogger(UrlDeploymentRulesSetUtils.class);

private static List<Rule> deploymentRules;

public static boolean isUrlInDeploymentRuleSet(final URL url) {
Assert.requireNonNull(url, "url");
return isUrlInDeploymentRuleSetUrl(url, getApplicationLinkDeploymentRuleSetList());
}

private static boolean isUrlInDeploymentRuleSetUrl(final URL url, final List<Rule> deploymentRuleSetList) {
if (deploymentRuleSetList.isEmpty()) {
return true; // empty deploymentRuleset == allow all connection
}

if (IpUtil.isLocalhostOrLoopback(url)) {
return true; // localhost need not be in whitelist
}

return deploymentRuleSetList.stream()
.filter(wlEntry -> wlEntry.matches(url))
.findFirst()
.map(Rule::isAllowedToRun)
.orElse(Boolean.TRUE);
}

private static List<Rule> getApplicationLinkDeploymentRuleSetList() {
if (deploymentRules == null) {
deploymentRules = loadDeploymentRuleSetLinksFromConfiguration();
}
return deploymentRules;
}

private static List<Rule> loadDeploymentRuleSetLinksFromConfiguration() {
try {
final String rulesetPath = JNLPRuntime.getConfiguration().getProperty(KEY_DEPLOYMENT_RULE_SET);
return parseDeploymentRuleSet(rulesetPath);
} catch (ParseException e) {
LOG.error("Please Check config property " + KEY_DEPLOYMENT_RULE_SET + ". This should point to a valid DeploymentRuleSet jar file: ", e);
return emptyList();
}
}

private static List<Rule> parseDeploymentRuleSet(String rulesetPath) throws ParseException {
final RulesetJarFile rulesetJarFile = new RulesetJarFile(rulesetPath);
if (rulesetJarFile.isNotPresent()) {
return emptyList();
}

final XmlNode root = rulesetJarFile.getRulesetXml();
final RulesetParser parser = new RulesetParser();
return parser.getRules(root);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
import java.util.concurrent.Executor;
import java.util.concurrent.Future;

import static net.adoptopenjdk.icedteaweb.deploymentrules.UrlDeploymentRulesSetUtils.isUrlInDeploymentRuleSet;
import static net.adoptopenjdk.icedteaweb.resources.ResourceStatus.DOWNLOADED;
import static net.adoptopenjdk.icedteaweb.resources.ResourceStatus.ERROR;
import static net.sourceforge.jnlp.cache.CacheUtil.isNonCacheable;
Expand Down Expand Up @@ -89,10 +90,12 @@ private static Resource processResource(final Resource resource) {
}

private static void validateWithWhitelist(URL url) {
// Validate with whitelist specified in deployment.properties. localhost is considered valid.
if (isUrlInWhitelist(url, getApplicationUrlWhiteList())) {
// Validate with whitelist specified in deployment.properties or deployment ruleset.
// localhost is considered valid.
if (isUrlInWhitelist(url, getApplicationUrlWhiteList()) || isUrlInDeploymentRuleSet(url)) {
return;
}

BasicExceptionDialog.show(new SecurityException(Translator.R("SWPInvalidURL") + ": " + url));
LOG.error("Resource URL not In Whitelist: {}", url);
JNLPRuntime.exit(-1);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -299,4 +299,8 @@ public interface ConfigurationConstants {
*/
String KEY_HTTPCONNECTION_CONNECT_TIMEOUT = "deployment.connection.connectTimeout";
String KEY_HTTPCONNECTION_READ_TIMEOUT = "deployment.connection.readTimeout";

/* deployment ruleset properties*/
String KEY_DEPLOYMENT_RULE_SET = "deployment.deploymentruleset.jar";

}