Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add support for deploymentruleset #816

Draft
wants to merge 11 commits into
base: master
Choose a base branch
from
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";

}