diff --git a/common/src/main/resources/net/adoptopenjdk/icedteaweb/i18n/Messages.properties b/common/src/main/resources/net/adoptopenjdk/icedteaweb/i18n/Messages.properties index cb8f46377..0a402c295 100644 --- a/common/src/main/resources/net/adoptopenjdk/icedteaweb/i18n/Messages.properties +++ b/common/src/main/resources/net/adoptopenjdk/icedteaweb/i18n/Messages.properties @@ -1103,7 +1103,13 @@ CPInvalidPort=Invalid port number given.\n[Valid port numbers are 1-65535] CPInvalidPortTitle=Error on input. # Control Panel - ServerwhitelistPanel -SWPInvalidURL=Server not in whitelist. +SWPInvalidURL=Server URL not in whitelist. +SWPCol0Header=Specified Whitelist +SWPCol1Header=Validated Whitelist +SWPVALIDATEHOST=* allowed only in host''s first part and as only character. +SWPVALIDATEIPHOST=* not allowed in IP address. +SWPINVALIDIPHOST=Invalid IP address. +SWPINVALIDWLURL=Invalid Whitelist Url. # command line control panel CLNoInfo=No information available (is this a valid option?). diff --git a/common/src/main/resources/net/adoptopenjdk/icedteaweb/i18n/Messages_de.properties b/common/src/main/resources/net/adoptopenjdk/icedteaweb/i18n/Messages_de.properties index cea75f3ef..5177b5be0 100644 --- a/common/src/main/resources/net/adoptopenjdk/icedteaweb/i18n/Messages_de.properties +++ b/common/src/main/resources/net/adoptopenjdk/icedteaweb/i18n/Messages_de.properties @@ -956,7 +956,13 @@ CPInvalidPort=Ung\u00FCltige Anschlussnummer eingegeben.\n[G\u00FCltige Anschlus CPInvalidPortTitle=Eingabefehler # Control Panel - ServerwhitelistPanel -SWPInvalidURL=Server nicht in Whitelist. +SWPInvalidURL=Server URL nicht in Whitelist. +SWPCol0Header=Angegebene Whitelist +SWPCol1Header=Validierte Whitelist +SWPVALIDATEHOST=* Nur im ersten Teil des Hosts und als einziges Zeichen zul\u00E4ssig. +SWPVALIDATEIPHOST=* in IP-Adresse nicht zul\u00E4ssig. +SWPINVALIDIPHOST=Ung\u00FCltige IP-Adresse. +SWPINVALIDWLURL=Ung\u00FCltige Whitelist-URL. # command line control panel CLNoInfo=Keine Informationen verf\u00FCgbar (ist dies eine g\u00FCltige Option?). diff --git a/core/src/main/java/net/adoptopenjdk/icedteaweb/client/controlpanel/ControlPanel.java b/core/src/main/java/net/adoptopenjdk/icedteaweb/client/controlpanel/ControlPanel.java index a546094ff..0a9c4cb75 100644 --- a/core/src/main/java/net/adoptopenjdk/icedteaweb/client/controlpanel/ControlPanel.java +++ b/core/src/main/java/net/adoptopenjdk/icedteaweb/client/controlpanel/ControlPanel.java @@ -53,6 +53,7 @@ import java.awt.Component; import java.awt.Dimension; import java.awt.FlowLayout; +import java.awt.Font; import java.io.IOException; import java.util.ArrayList; import java.util.Comparator; @@ -231,6 +232,7 @@ private JPanel createMainSettingsPanel(final ControlPanelStyle style) { final JList settingsList = new JList<>(panels.keySet().toArray(new String[0])); + settingsList.setFont(new Font(settingsList.getFont().getName(), Font.BOLD, settingsList.getFont().getSize())); settingsList.addListSelectionListener(e -> cardLayout.show(settingsPanel, settingsList.getSelectedValue())); settingsList.setSelectedIndex(0); DefaultListCellRenderer cellRenderer = new DefaultListCellRenderer() { diff --git a/core/src/main/java/net/adoptopenjdk/icedteaweb/client/controlpanel/panels/ServerWhitelistPanel.java b/core/src/main/java/net/adoptopenjdk/icedteaweb/client/controlpanel/panels/ServerWhitelistPanel.java index 8b2a60ee6..bda85743e 100644 --- a/core/src/main/java/net/adoptopenjdk/icedteaweb/client/controlpanel/panels/ServerWhitelistPanel.java +++ b/core/src/main/java/net/adoptopenjdk/icedteaweb/client/controlpanel/panels/ServerWhitelistPanel.java @@ -20,15 +20,25 @@ import net.adoptopenjdk.icedteaweb.Assert; import net.adoptopenjdk.icedteaweb.client.controlpanel.NamedBorderPanel; import net.adoptopenjdk.icedteaweb.i18n.Translator; +import net.adoptopenjdk.icedteaweb.jdk89access.SunMiscLauncher; import net.sourceforge.jnlp.config.DeploymentConfiguration; +import net.sourceforge.jnlp.util.whitelist.UrlWhiteListUtils; +import net.sourceforge.jnlp.util.whitelist.WhitelistEntry; -import javax.swing.JList; +import javax.swing.BorderFactory; +import javax.swing.ImageIcon; import javax.swing.JScrollPane; +import javax.swing.JTable; +import javax.swing.SwingConstants; +import javax.swing.table.AbstractTableModel; +import javax.swing.table.DefaultTableCellRenderer; +import javax.swing.table.TableColumnModel; +import javax.swing.table.TableModel; import java.awt.BorderLayout; +import java.awt.Component; import java.util.List; -import java.util.Vector; -import static net.sourceforge.jnlp.config.ConfigurationConstants.KEY_SECURITY_SERVER_WHITELIST; +import static net.adoptopenjdk.icedteaweb.i18n.Translator.R; /** * This provides a way for the user to display the server white list defined in deployment.properties. @@ -36,6 +46,8 @@ @SuppressWarnings("serial") public class ServerWhitelistPanel extends NamedBorderPanel { + private static final ImageIcon WARNING_ICON = SunMiscLauncher.getSecureImageIcon("net/sourceforge/jnlp/resources/warn16.png"); + /** * This creates a new instance of the server white list panel. * @@ -46,9 +58,95 @@ public ServerWhitelistPanel(final DeploymentConfiguration config) { Assert.requireNonNull(config, "config"); - final List whitelist = config.getPropertyAsList(KEY_SECURITY_SERVER_WHITELIST); - final JList jList = new JList<>(new Vector<>(whitelist)); - jList.setFixedCellHeight(20); - add(new JScrollPane(jList), BorderLayout.CENTER); + final List whitelist = UrlWhiteListUtils.getApplicationUrlWhiteList(); + + final JTable table = new JTable(createTableModel(whitelist)); + table.getTableHeader().setReorderingAllowed(false); + table.setFillsViewportHeight(true); + table.setAutoResizeMode(JTable.AUTO_RESIZE_ALL_COLUMNS); + table.setAutoCreateRowSorter(true); + + final TableColumnModel colModel = table.getColumnModel(); + colModel.getColumn(0).setPreferredWidth(100); + colModel.getColumn(1).setPreferredWidth(250); + + table.getColumnModel().getColumn(1).setCellRenderer(new EffectiveWhitelistCellRenderer()); + final JScrollPane scrollPane = new JScrollPane(table); + scrollPane.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10)); + add(scrollPane, BorderLayout.CENTER); + } + + private TableModel createTableModel(final List whitelist) { + final String[] colNames = {R("SWPCol0Header"), R("SWPCol1Header")}; + return new AbstractTableModel() { + @Override + public int getRowCount() { + return whitelist.size(); + } + + public String getColumnName(int col) { + return colNames[col]; + } + + @Override + public int getColumnCount() { + return colNames.length; + } + + @Override + public Object getValueAt(int rowIndex, int columnIndex) { + WhitelistEntry whitelistEntry = whitelist.get(rowIndex); + switch (columnIndex) { + case 0: + return whitelistEntry.getRawWhitelistEntry(); + case 1: + return new WhitelistEntryState(whitelistEntry); + default: + throw new IllegalArgumentException("column index > 1 - this should not happen"); + } + } + }; + } + + private static class WhitelistEntryState { + private final WhitelistEntry entry; + + public WhitelistEntryState(WhitelistEntry entry) { + this.entry = entry; + } + + public boolean isValid() { + return entry.isValid(); + } + + public String getMessage() { + return isValid() ? entry.getEffectiveWhitelistEntry() : R("SWPINVALIDWLURL") + ": " + entry.getErrorMessage(); + } + } + + private static class EffectiveWhitelistCellRenderer extends DefaultTableCellRenderer { + + private EffectiveWhitelistCellRenderer() { + setIconTextGap(5); + setVerticalTextPosition(SwingConstants.CENTER); + } + + @Override + public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) { + super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column); + final WhitelistEntryState wleState = (WhitelistEntryState) value; + if (wleState != null) { + setText(wleState.getMessage()); + if (!wleState.isValid()) { + setIcon(WARNING_ICON); + } else { + setIcon(null); + } + } else { + setText(null); + setIcon(null); + } + return this; + } } } diff --git a/core/src/main/java/net/adoptopenjdk/icedteaweb/resources/ResourceHandler.java b/core/src/main/java/net/adoptopenjdk/icedteaweb/resources/ResourceHandler.java index 4839b83a2..470140a59 100644 --- a/core/src/main/java/net/adoptopenjdk/icedteaweb/resources/ResourceHandler.java +++ b/core/src/main/java/net/adoptopenjdk/icedteaweb/resources/ResourceHandler.java @@ -2,7 +2,6 @@ import net.adoptopenjdk.icedteaweb.Assert; -import net.adoptopenjdk.icedteaweb.StringUtils; import net.adoptopenjdk.icedteaweb.client.BasicExceptionDialog; import net.adoptopenjdk.icedteaweb.i18n.Translator; import net.adoptopenjdk.icedteaweb.logging.Logger; @@ -12,19 +11,16 @@ import net.adoptopenjdk.icedteaweb.resources.initializer.ResourceInitializer; import net.sourceforge.jnlp.cache.CacheUtil; import net.sourceforge.jnlp.runtime.JNLPRuntime; -import net.sourceforge.jnlp.util.IpUtil; +import net.sourceforge.jnlp.util.whitelist.UrlWhiteListUtils; import java.io.File; import java.net.URL; -import java.util.List; import java.util.concurrent.CompletableFuture; import java.util.concurrent.Executor; import java.util.concurrent.Future; -import java.util.stream.Collectors; import static net.adoptopenjdk.icedteaweb.resources.Resource.Status.DOWNLOADED; import static net.adoptopenjdk.icedteaweb.resources.Resource.Status.ERROR; -import static net.sourceforge.jnlp.config.ConfigurationConstants.KEY_SECURITY_SERVER_WHITELIST; import static net.sourceforge.jnlp.util.UrlUtils.FILE_PROTOCOL; import static net.sourceforge.jnlp.util.UrlUtils.decodeUrlQuietly; @@ -125,22 +121,10 @@ private void validateWithWhitelist() { final URL url = resource.getLocation(); Assert.requireNonNull(url, "url"); - final List whitelist = JNLPRuntime.getConfiguration().getPropertyAsList(KEY_SECURITY_SERVER_WHITELIST) - .stream().filter(s -> !StringUtils.isBlank(s)).collect(Collectors.toList()); - - if (whitelist.isEmpty()) { - return; // empty whitelist == allow all connections - } - - // if host is null or "" or it is localhost or loopback - if (IpUtil.isLocalhostOrLoopback(url)) { - return; // local server need not be in whitelist - } - - final String urlString = url.getProtocol() + "://" + url.getHost() + ((url.getPort() != -1) ? ":" + url.getPort() : ""); - - if (!whitelist.contains(urlString)) { - BasicExceptionDialog.show(new SecurityException(Translator.R("SWPInvalidURL") + ": " + resource.getLocation())); + // Validate with whitelist specified in deployment.properties. localhost is considered valid. + final boolean found = UrlWhiteListUtils.isUrlInApplicationUrlWhitelist(url); + if (!found) { + BasicExceptionDialog.show(new SecurityException(Translator.R("SWPInvalidURL") + ": " + url)); LOG.error("Resource URL not In Whitelist: {}", resource.getLocation()); JNLPRuntime.exit(-1); } diff --git a/core/src/main/java/net/sourceforge/jnlp/util/IpUtil.java b/core/src/main/java/net/sourceforge/jnlp/util/IpUtil.java index 6d92c84da..57db91a75 100644 --- a/core/src/main/java/net/sourceforge/jnlp/util/IpUtil.java +++ b/core/src/main/java/net/sourceforge/jnlp/util/IpUtil.java @@ -7,11 +7,11 @@ import java.net.URL; public class IpUtil { - public static boolean isLocalhostOrLoopback(URL url) { + public static boolean isLocalhostOrLoopback(final URL url) { return isLocalhostOrLoopback(url.getHost()); } - public static boolean isLocalhostOrLoopback(URI uri) { + public static boolean isLocalhostOrLoopback(final URI uri) { return isLocalhostOrLoopback(uri.getHost()); } @@ -20,7 +20,7 @@ public static boolean isLocalhostOrLoopback(URI uri) { * @return true if the given host string is blank or represents or resolves to the hostname or the IP address * of localhost or the loopback address. */ - static boolean isLocalhostOrLoopback(String host) { + static boolean isLocalhostOrLoopback(final String host) { if (StringUtils.isBlank(host)) { return true; // java.net.InetAddress.getByName(host).isLoopbackAddress() returns true } diff --git a/core/src/main/java/net/sourceforge/jnlp/util/whitelist/UrlWhiteListUtils.java b/core/src/main/java/net/sourceforge/jnlp/util/whitelist/UrlWhiteListUtils.java new file mode 100644 index 000000000..ad5925022 --- /dev/null +++ b/core/src/main/java/net/sourceforge/jnlp/util/whitelist/UrlWhiteListUtils.java @@ -0,0 +1,163 @@ +package net.sourceforge.jnlp.util.whitelist; + +import net.adoptopenjdk.icedteaweb.Assert; +import net.adoptopenjdk.icedteaweb.StringUtils; +import net.adoptopenjdk.icedteaweb.logging.Logger; +import net.adoptopenjdk.icedteaweb.logging.LoggerFactory; +import net.sourceforge.jnlp.runtime.JNLPRuntime; +import net.sourceforge.jnlp.util.IpUtil; + +import java.net.MalformedURLException; +import java.net.URL; +import java.util.List; +import java.util.Objects; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; +import java.util.stream.Collectors; + +import static net.adoptopenjdk.icedteaweb.i18n.Translator.R; +import static net.sourceforge.jnlp.config.ConfigurationConstants.KEY_SECURITY_SERVER_WHITELIST; + +public class UrlWhiteListUtils { + private static final String WILDCARD = "*"; + private static final String HOST_PART_SEP = "."; + private static final String HOST_PART_REGEX = "\\."; + public static final String HTTPS = "https"; + public static final String HTTP = "http"; + private static final String PROTOCOL_SEPARATOR = "://"; + + private static final Logger LOG = LoggerFactory.getLogger(UrlWhiteListUtils.class); + + private static List applicationUrlWhiteList; + private static final Lock whiteListLock = new ReentrantLock(); + + public static List getApplicationUrlWhiteList() { + whiteListLock.lock(); + try { + if (applicationUrlWhiteList == null) { + applicationUrlWhiteList = JNLPRuntime.getConfiguration().getPropertyAsList(KEY_SECURITY_SERVER_WHITELIST) + .stream() + .filter(s -> !StringUtils.isBlank(s)) + .map(UrlWhiteListUtils::validateWhitelistUrl) + .collect(Collectors.toList()); + } + return applicationUrlWhiteList; + } finally { + whiteListLock.unlock(); + } + } + + public static boolean isUrlInApplicationUrlWhitelist(final URL url) { + return isUrlInWhitelist(url, getApplicationUrlWhiteList()); + } + + static boolean isUrlInWhitelist(final URL url, final List whiteList) { + Assert.requireNonNull(url, "url"); + Assert.requireNonNull(whiteList, "whiteList"); + + if (whiteList.isEmpty()) { + return true; // empty whitelist == allow all connections + } + + // is it localhost or loopback + if (IpUtil.isLocalhostOrLoopback(url)) { + return true; // local server need not be in whitelist + } + + return whiteList.stream().anyMatch(wlEntry -> wlEntry.matches(url)); + } + + static WhitelistEntry validateWhitelistUrl(final String wlUrlStr) { + Assert.requireNonNull(wlUrlStr, "wlUrlStr"); + try { + final String validatedWLUrlProtocol = validateWhitelistUrlProtocol(wlUrlStr); + final String validatedWLUrlStr = validateWhitelistUrlPort(validatedWLUrlProtocol); + final URL validatedUrl = validateWhitelistUrlHost(validatedWLUrlStr); + return WhitelistEntry.validWhitelistEntry(wlUrlStr, validatedUrl); + } catch (Exception e) { + return WhitelistEntry.invalidWhitelistEntry(wlUrlStr, e.getMessage()); + } + } + + private static String validateWhitelistUrlProtocol(final String wlUrlStr) throws MalformedURLException { + final String[] splitProtocol = wlUrlStr.split(PROTOCOL_SEPARATOR); + if (splitProtocol.length == 1) { + final char firstChar = wlUrlStr.charAt(0); + if (PROTOCOL_SEPARATOR.indexOf(firstChar) == -1) { // firstChar is not / or : + return HTTPS + PROTOCOL_SEPARATOR + wlUrlStr; + } + } + try { + new URL(wlUrlStr); + } catch (Exception e) { + if (e.getMessage().contains("protocol")) { + throw e; + } + } + return wlUrlStr; + } + + private static String validateWhitelistUrlPort(final String wlUrlStr) throws MalformedURLException { + try { + final URL wlUrl = new URL(wlUrlStr); + // if port is missing then take it as default port for the protocol + if (wlUrl.getPort() == -1) { + return wlUrl.getProtocol() + PROTOCOL_SEPARATOR + wlUrl.getHost() + ":" + wlUrl.getDefaultPort(); + } + } catch (Exception e) { + // if port is illegal due to * then replace * with "" + final int ind = wlUrlStr.lastIndexOf(":"); + if (e.getCause() instanceof NumberFormatException && Objects.equals(wlUrlStr.substring(ind + 1), WILDCARD)) { + return wlUrlStr.substring(0, ind); + } + throw e; + } + return wlUrlStr; + } + + private static URL validateWhitelistUrlHost(final String wlUrlStr) throws Exception { + final URL wlURL = new URL(wlUrlStr); + final String hostStr = wlURL.getHost(); + + // Whitelist Host is * + if (Objects.equals(hostStr, WILDCARD)) { + return wlURL; + } + + final boolean isIPHost = isIP(hostStr); + final String[] hostParts = hostStr.split(HOST_PART_REGEX); + for (int i = 0; i < hostParts.length; i++) { + if (isIPHost) { + validateIPPart(hostParts[i]); + } else { // non IP host + // * is allowed only in first part and it should be the only char + if (hostParts[i].contains(WILDCARD) && (hostParts[i].length() > 1 || i != 0)) { + throw new Exception(R("SWPVALIDATEHOST")); + } + } + } + + return wlURL; + } + + // IP Address => all digits, 4 parts, *, - + private static boolean isIP(final String wlUrlStr) { + final boolean hasValidChars = wlUrlStr.replace(HOST_PART_SEP.charAt(0), '0').chars().allMatch(c -> Character.isDigit(c) || c == WILDCARD.charAt(0) || c == '-'); + final String[] ipParts = wlUrlStr.split(HOST_PART_REGEX); + return hasValidChars && ipParts.length == 4; + } + + private static void validateIPPart(final String ipPart) throws Exception { + if (ipPart.contains(WILDCARD) || ipPart.contains("-")) { + throw new Exception(R("SWPINVALIDIPHOST")); + } + try { + final int ipPartInt = Integer.parseInt(ipPart); + if (ipPartInt < 0 || ipPartInt > 255) { + throw new Exception(R("SWPINVALIDIPHOST")); + } + } catch (NumberFormatException nfe) { + throw new Exception(R("SWPINVALIDIPHOST")); + } + } +} diff --git a/core/src/main/java/net/sourceforge/jnlp/util/whitelist/WhitelistEntry.java b/core/src/main/java/net/sourceforge/jnlp/util/whitelist/WhitelistEntry.java new file mode 100644 index 000000000..05c4d4a2e --- /dev/null +++ b/core/src/main/java/net/sourceforge/jnlp/util/whitelist/WhitelistEntry.java @@ -0,0 +1,98 @@ +package net.sourceforge.jnlp.util.whitelist; + +import net.adoptopenjdk.icedteaweb.Assert; + +import java.net.URL; +import java.util.Objects; +import java.util.Optional; + +/** + * ... + */ +public class WhitelistEntry { + private static final String WILDCARD = "*"; + private static final String HOST_PART_REGEX = "\\."; + + private final String rawWhitelistEntry; + private final URL effectiveWhitelistEntry; + private final String errorMessage; + + static WhitelistEntry validWhitelistEntry(final String wlEntry, URL effectiveWhitelistEntry) { + Assert.requireNonNull(wlEntry, "wlEntry"); + Assert.requireNonNull(effectiveWhitelistEntry, "effectiveWhitelistEntry"); + return new WhitelistEntry(wlEntry, effectiveWhitelistEntry, null); + } + + static WhitelistEntry invalidWhitelistEntry(final String wlEntry, final String errorMessage) { + Assert.requireNonNull(wlEntry, "wlEntry"); + Assert.requireNonNull(errorMessage, "errorMessage"); + return new WhitelistEntry(wlEntry, null, errorMessage); + } + + private WhitelistEntry(final String rawWhitelistEntry, final URL effectiveWhitelistEntry, final String errorMessage) { + this.rawWhitelistEntry = rawWhitelistEntry; + this.effectiveWhitelistEntry = effectiveWhitelistEntry; + this.errorMessage = errorMessage; + } + + public String getRawWhitelistEntry() { + return rawWhitelistEntry; + } + + public String getEffectiveWhitelistEntry() { + return Optional.ofNullable(effectiveWhitelistEntry).map(Objects::toString).orElse(null); + } + + public String getErrorMessage() { + return errorMessage; + } + + public boolean isValid() { + return errorMessage == null; + } + + public boolean matches(URL url) { + if (!isValid() || url == null) { + // ignore invalid url or whitelist entries + return false; + } else { + return isProtocolMatching(url) && isHostMatching(url) && isPortMatching(url); + } + } + private boolean isProtocolMatching(URL url) { + return Objects.equals(effectiveWhitelistEntry.getProtocol(), url.getProtocol()); + } + + private boolean isHostMatching(URL url) { + // proto://*:port + if (Objects.equals(effectiveWhitelistEntry.getHost(), WILDCARD)) { + return true; + } + + final String[] wlUrlHostParts = effectiveWhitelistEntry.getHost().split(HOST_PART_REGEX); + final String[] urlHostParts = url.getHost().split(HOST_PART_REGEX); + + if (wlUrlHostParts.length != urlHostParts.length) { + return false; + } + + boolean result = true; + for (int i = 0; i < wlUrlHostParts.length; i++) { + // hostparts are equal if whitelist url has * or they are same + result = result && (Objects.equals(wlUrlHostParts[i], WILDCARD) || Objects.equals(wlUrlHostParts[i], urlHostParts[i])); + } + return result; + } + + private boolean isPortMatching(URL url) { + if (effectiveWhitelistEntry.getPort() != -1) { + // url does not have port then force default port as we do the same for whitelist url + if (url.getPort() == -1) { + return effectiveWhitelistEntry.getPort() == url.getDefaultPort(); + } else { + return effectiveWhitelistEntry.getPort() == url.getPort(); + } + } + return true; + } +} diff --git a/core/src/main/resources/net/sourceforge/jnlp/resources/warn16.png b/core/src/main/resources/net/sourceforge/jnlp/resources/warn16.png new file mode 100644 index 000000000..c23a28ff9 Binary files /dev/null and b/core/src/main/resources/net/sourceforge/jnlp/resources/warn16.png differ diff --git a/core/src/test/java/net/sourceforge/jnlp/util/IpUtilTest.java b/core/src/test/java/net/sourceforge/jnlp/util/IpUtilTest.java index 3b769482f..834ac5e3d 100644 --- a/core/src/test/java/net/sourceforge/jnlp/util/IpUtilTest.java +++ b/core/src/test/java/net/sourceforge/jnlp/util/IpUtilTest.java @@ -1,5 +1,7 @@ package net.sourceforge.jnlp.util; +import inet.ipaddr.HostNameException; +import org.junit.Assert; import org.junit.Test; import java.net.MalformedURLException; diff --git a/core/src/test/java/net/sourceforge/jnlp/util/whitelist/UrlWhiteListUtilsTest.java b/core/src/test/java/net/sourceforge/jnlp/util/whitelist/UrlWhiteListUtilsTest.java new file mode 100644 index 000000000..e886cf7f6 --- /dev/null +++ b/core/src/test/java/net/sourceforge/jnlp/util/whitelist/UrlWhiteListUtilsTest.java @@ -0,0 +1,264 @@ +package net.sourceforge.jnlp.util.whitelist; + +import net.adoptopenjdk.icedteaweb.StringUtils; +import org.junit.Assert; +import org.junit.Test; + +import java.net.URL; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +public class UrlWhiteListUtilsTest { + + @Test + public void validateWhitelistUrlString() { + assertEffectiveUrl("http://subdomain.domain.com:8888", "http://subdomain.domain.com:8888"); + assertEffectiveUrl("https://subdomain.domain.com:9999", "https://subdomain.domain.com:9999"); + assertEffectiveUrl("https://123.134.145.156:9999", "https://123.134.145.156:9999"); + assertEffectiveUrl("http://123.134.145.156:8888", "http://123.134.145.156:8888"); + + assertEffectiveUrl("https://domain.com:443", "domain.com"); + assertEffectiveUrl("https://*.domain.com:443", "*.domain.com"); + assertEffectiveUrl("https://123.134.145.156:443", "123.134.145.156:443"); + + assertEffectiveUrl("http://subdomain.domain.com:80", "http://subdomain.domain.com"); + assertEffectiveUrl("http://subdomain.domain.com:80", "http://subdomain.domain.com/abc/efg"); + assertEffectiveUrl("https://subdomain.domain.com:443", "https://subdomain.domain.com"); + assertEffectiveUrl("https://subdomain.domain.com:443", "https://subdomain.domain.com/abc/efg"); + assertEffectiveUrl("https://123.134.145.156:443", "https://123.134.145.156"); + + assertEffectiveUrl("http://subdomain.domain.com", "http://subdomain.domain.com:*"); + assertEffectiveUrl("https://subdomain.domain.com", "https://subdomain.domain.com:*"); + assertEffectiveUrl("https://123.134.145.156", "https://123.134.145.156:*"); + + assertEffectiveUrl("https://*:443", "*"); + assertEffectiveUrl("http://*:80", "http://*:80"); + assertEffectiveUrl("https://*:443", "https://*:443"); + + assertEffectiveUrl("http://*", "http://*:*"); + assertEffectiveUrl("https://*", "https://*:*"); + } + + private void assertEffectiveUrl(String expected, String wlUrlStr) { + Assert.assertEquals(expected, UrlWhiteListUtils.validateWhitelistUrl(wlUrlStr).getEffectiveWhitelistEntry()); + } + + @Test + public void validateIllegalWhitelistUrlString() { + assertInvalidWhitelistEntry("https://subdomain.domain.com:1*"); + assertInvalidWhitelistEntry("https://*jvms.domain.com:443"); + assertInvalidWhitelistEntry("https://jvms.*.com:443"); + assertInvalidWhitelistEntry("https://xyz.dom*.com:443"); + assertInvalidWhitelistEntry("*.domain.com:ABC"); + assertInvalidWhitelistEntry("//*.domain.com:123"); + assertInvalidWhitelistEntry(":*.domain.com:123"); + assertInvalidWhitelistEntry("://*.domain.com:123"); + assertInvalidWhitelistEntry(":/*.domain.com:123"); + assertInvalidWhitelistEntry("/*.domain.com:123"); + // Seem illegal but are valid + assertValidWhitelistEntry("123.domain.com:123"); + assertValidWhitelistEntry("123.123.123:123"); + assertValidWhitelistEntry("-123.domain.com:123"); + } + + @Test + public void validateIllegalWhitelistIPUrlString() { + assertInvalidWhitelistEntry("https://123.*.156.145"); + assertInvalidWhitelistEntry("https://123.1*.0.156"); + assertInvalidWhitelistEntry("https://123.134.145.-1"); + assertInvalidWhitelistEntry("https://256.134.145.255"); + } + + private void assertInvalidWhitelistEntry(String wlUrlStr) { + Assert.assertFalse(UrlWhiteListUtils.validateWhitelistUrl(wlUrlStr).isValid()); + } + + private void assertValidWhitelistEntry(String wlUrlStr) { + Assert.assertTrue(UrlWhiteListUtils.validateWhitelistUrl(wlUrlStr).isValid()); + } + + @Test + public void urlInWhiteList() throws Exception { + List wList = getValidatedWhitelist( + "https://rfy.m-b.com", + "https://*.m-b.com", + "https://rfy.*.com", + "https://rfy.m-b.*", + "https://*.*.*:446", + "https://*:447", + "https://*.mydomain.com", + "http://*.mydomain.com", + "*.cintra.net", + "*.dmlr.com" + ); + + // "https://rfry.m-b.com" + URL url = new URL("https://rfy.m-b.com:443/some_URL"); + Assert.assertTrue(UrlWhiteListUtils.isUrlInWhitelist(url, wList)); //+ " https://rfy.m-b.com:443/some_URL"); + + // "https://rfy.m-b.com:443" + url = new URL("https://rfy.m-b.com:445/some_URL"); + Assert.assertFalse(UrlWhiteListUtils.isUrlInWhitelist(url, wList)); // + " https://rfy.m-b.com:445/some_URL"); + + // "https://*.m-b.com" + url = new URL("https://rfyA.m-b.com:443/some_URL"); + Assert.assertTrue(UrlWhiteListUtils.isUrlInWhitelist(url, wList)); // + " https://rfy1.m-b.com:443/some_URL"); + + // "https://rfy.*.com" + url = new URL("https://rfy.m-b1.com:443/some_URL"); + Assert.assertFalse(UrlWhiteListUtils.isUrlInWhitelist(url, wList)); // + " https://rfy.m-b1.com:443/some_URL"); + + // "https://*.m-b.com" + url = new URL("https://rfy.m-b.org:443/some_URL"); + Assert.assertFalse(UrlWhiteListUtils.isUrlInWhitelist(url, wList)); // + " https://rfy.m-b.org:443/some_URL"); + + url = new URL("https://rfy.m-b.com:443/some_URL"); + Assert.assertTrue(UrlWhiteListUtils.isUrlInWhitelist(url, wList)); // + " https://rfy.m-b.org:443/some_URL"); + + // "https://*.*.*:446" + url = new URL("https://rfy1.m-b1.org:446/some_URL"); + Assert.assertFalse(UrlWhiteListUtils.isUrlInWhitelist(url, wList)); // + " https://rfy1.m-b1.org:446/some_URL"); + + // "https://*:447" + url = new URL("https://rfy1.m-b1.org:447/some_URL"); + Assert.assertTrue(UrlWhiteListUtils.isUrlInWhitelist(url, wList)); // + " https://rfy1.m-b1.org:446/some_URL"); + + // "https://*:446" + url = new URL("https://rfy1.m-b1.com:445/some_URL"); + Assert.assertFalse(UrlWhiteListUtils.isUrlInWhitelist(url, wList)); // + " https://rfy1.m-b1.com:445/some_URL"); + + // "https://*.mydomain.com" + url = new URL("https://abc.mydomain.com:443/some_URL"); + Assert.assertTrue(UrlWhiteListUtils.isUrlInWhitelist(url, wList)); // + " https://rfy1.m-b1.org:446/some_URL"); + + // "https://*.mydomain.com" + url = new URL("https://abc.mydomain.com:444/some_URL"); + Assert.assertFalse(UrlWhiteListUtils.isUrlInWhitelist(url, wList)); // + " https://rfy1.m-b1.org:446/some_URL"); + + // "http://*.mydomain.com" + url = new URL("http://abc.mydomain.com:80/some_URL"); + Assert.assertTrue(UrlWhiteListUtils.isUrlInWhitelist(url, wList)); // + " https://rfy1.m-b1.org:446/some_URL"); + + // "http://*.mydomain.com" + url = new URL("http://abc.mydomain.com:81/some_URL"); + Assert.assertFalse(UrlWhiteListUtils.isUrlInWhitelist(url, wList)); // + " https://rfy1.m-b1.org:446/some_URL"); + + // "*.cintra.net" + url = new URL("https://abc.cintra.net:443/some_URL"); + Assert.assertTrue(UrlWhiteListUtils.isUrlInWhitelist(url, wList)); // + " https://rfy1.m-b1.org:446/some_URL"); + + url = new URL("http://abc.cintra.net:443/some_URL"); + Assert.assertFalse(UrlWhiteListUtils.isUrlInWhitelist(url, wList)); // + " https://rfy1.m-b1.org:446/some_URL"); + + // "*.dmlr.com" + url = new URL("https://abc.dmlr.com:443/some_URL"); + Assert.assertTrue(UrlWhiteListUtils.isUrlInWhitelist(url, wList)); // + " https://rfy1.m-b1.org:446/some_URL"); + + // "*.dmlr.com" + url = new URL("https://abc.dmlr.com:44/some_URL"); + Assert.assertFalse(UrlWhiteListUtils.isUrlInWhitelist(url, wList)); // + " https://rfy1.m-b1.org:446/some_URL"); + } + + @Test + public void demoUrlInWhiteList() throws Exception { + List wList = getValidatedWhitelist( + "docs.oracle.com", + "*.oracle.org", + "docs.*.net" + ); + + URL url = new URL("https://docs.oracle.com/j2se/tutorial"); + Assert.assertTrue(UrlWhiteListUtils.isUrlInWhitelist(url, wList)); + + url = new URL("https://any.oracle.org:443/j2se/tutorial"); + Assert.assertTrue(UrlWhiteListUtils.isUrlInWhitelist(url, wList)); + + url = new URL("https://any.one.oracle.org:443/j2se/tutorial"); + Assert.assertFalse(UrlWhiteListUtils.isUrlInWhitelist(url, wList)); + + url = new URL("https://any.net:443/j2se/tutorial"); + Assert.assertFalse(UrlWhiteListUtils.isUrlInWhitelist(url, wList)); + + url = new URL("https://docs.any.net:443/j2se/tutorial"); + Assert.assertFalse(UrlWhiteListUtils.isUrlInWhitelist(url, wList)); + } + + @Test + public void ipUrlInWhiteList() throws Exception { + List wList = getValidatedWhitelist( + "123.134.145.156", + "123.134.145.156:167", + "*.134.145.156:167", + "http://124.134.145.156", + "http://125.134.145.157:*", + "https://123.134.145.156", + "https://126.134.145.156:333", + "http://124.134.145.156:333", + "http://124.134.145.156:335/abc/efg" + ); + + URL url = new URL("https://123.134.145.156/tutorial"); + Assert.assertTrue(UrlWhiteListUtils.isUrlInWhitelist(url, wList)); + + url = new URL("https://123.134.145.156:443/tutorial"); + Assert.assertTrue(UrlWhiteListUtils.isUrlInWhitelist(url, wList)); + + url = new URL("https://124.134.145.156:443"); + Assert.assertFalse(UrlWhiteListUtils.isUrlInWhitelist(url, wList)); + + url = new URL("http://124.134.145.156/tutorial"); + Assert.assertTrue(UrlWhiteListUtils.isUrlInWhitelist(url, wList)); + + url = new URL("http://124.134.145.156:80/tutorial"); + Assert.assertTrue(UrlWhiteListUtils.isUrlInWhitelist(url, wList)); + + url = new URL("http://125.134.145.157:90/tutorial"); + Assert.assertTrue(UrlWhiteListUtils.isUrlInWhitelist(url, wList)); + + url = new URL("https://123.134.145.156:167/j2se/tutorial"); + Assert.assertTrue(UrlWhiteListUtils.isUrlInWhitelist(url, wList)); + + url = new URL("http://124.134.145.156:333/j2se/tutorial"); + Assert.assertTrue(UrlWhiteListUtils.isUrlInWhitelist(url, wList)); + + url = new URL("http://124.134.145.158:333/j2se/tutorial"); + Assert.assertFalse(UrlWhiteListUtils.isUrlInWhitelist(url, wList)); + + url = new URL("https://126.134.145.156:333/j2se/tutorial"); + Assert.assertTrue(UrlWhiteListUtils.isUrlInWhitelist(url, wList)); + + url = new URL("http://124.134.145.156:335/abc/efg"); + Assert.assertTrue(UrlWhiteListUtils.isUrlInWhitelist(url, wList)); + + url = new URL("http://124.134.145.156:336/abcd/efg"); + Assert.assertFalse(UrlWhiteListUtils.isUrlInWhitelist(url, wList)); + } + + @Test + public void wildCard() throws Exception { + List wList = getValidatedWhitelist( + "*", + "http://*" + ); + + URL url = new URL("https://123.134.145.156/tutorial"); + Assert.assertTrue(UrlWhiteListUtils.isUrlInWhitelist(url, wList)); + + url = new URL("https://abc.efg.com/tutorial"); + Assert.assertTrue(UrlWhiteListUtils.isUrlInWhitelist(url, wList)); + + url = new URL("http://abc.com/tutorial"); + Assert.assertTrue(UrlWhiteListUtils.isUrlInWhitelist(url, wList)); + + url = new URL("http://123.134.145.156:80/tutorial"); + Assert.assertTrue(UrlWhiteListUtils.isUrlInWhitelist(url, wList)); + } + + private static List getValidatedWhitelist(String... wildcardWhiteList) { + return Stream.of(wildcardWhiteList) + .filter(s -> !StringUtils.isBlank(s)) + .map(UrlWhiteListUtils::validateWhitelistUrl) + .collect(Collectors.toList()); + } +} diff --git a/core/src/test/java/net/sourceforge/jnlp/util/whitelist/WhitelistEntryTest.java b/core/src/test/java/net/sourceforge/jnlp/util/whitelist/WhitelistEntryTest.java new file mode 100644 index 000000000..bef7ee83a --- /dev/null +++ b/core/src/test/java/net/sourceforge/jnlp/util/whitelist/WhitelistEntryTest.java @@ -0,0 +1,115 @@ +package net.sourceforge.jnlp.util.whitelist; + +import org.junit.Test; + +import java.net.URL; + +import static net.sourceforge.jnlp.util.whitelist.UrlWhiteListUtils.validateWhitelistUrl; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +public class WhitelistEntryTest { + + @Test + public void invalidEntryShouldMatchNothing() throws Exception { + final WhitelistEntry invalidEntry = validateWhitelistUrl(""); + + assertInvalid(invalidEntry); + + assertNoMatch(invalidEntry, null); + assertNoMatch(invalidEntry, "http://localhost"); + assertNoMatch(invalidEntry, "https://192.168.1.1"); + } + + @Test + public void exactEntryShouldOnlyMatchExactUrl() throws Exception { + final WhitelistEntry validEntry = validateWhitelistUrl("http://test.com:88"); + + assertMatch(validEntry, "http://test.com:88"); + + assertNoMatch(validEntry, "https://test.com:88"); // wrong protocol + assertNoMatch(validEntry, "http://test2.com:88"); // wrong host + assertNoMatch(validEntry, "http://test.com:888"); // wrong port + } + + @Test + public void defaultPortShouldMatchExplicitAndAbsentValue() throws Exception { + final WhitelistEntry explicitHttpEntry = validateWhitelistUrl("http://test.com:80"); + final WhitelistEntry implicitHttpEntry = validateWhitelistUrl("http://test.com"); + final WhitelistEntry explicitHttpsEntry = validateWhitelistUrl("https://test.com:443"); + final WhitelistEntry implicitHttpsEntry = validateWhitelistUrl("https://test.com"); + + assertMatch(explicitHttpEntry, "http://test.com:80"); + assertMatch(explicitHttpEntry, "http://test.com"); + assertMatch(implicitHttpEntry, "http://test.com:80"); + assertMatch(implicitHttpEntry, "http://test.com"); + + assertNoMatch(explicitHttpEntry, "http://test.com:88"); // wrong port + assertNoMatch(implicitHttpEntry, "http://test.com:88"); // wrong port + + assertMatch(explicitHttpsEntry, "https://test.com:443"); + assertMatch(explicitHttpsEntry, "https://test.com"); + assertMatch(implicitHttpsEntry, "https://test.com:443"); + assertMatch(implicitHttpsEntry, "https://test.com"); + + assertNoMatch(explicitHttpsEntry, "https://test.com:444"); // wrong port + assertNoMatch(implicitHttpsEntry, "https://test.com:444"); // wrong port + } + + @Test + public void wildcardPortShouldMatchAnyPort() throws Exception { + final WhitelistEntry validEntry = validateWhitelistUrl("http://test.com:*"); + + assertMatch(validEntry, "http://test.com"); + assertMatch(validEntry, "http://test.com:80"); + assertMatch(validEntry, "http://test.com:443"); + assertMatch(validEntry, "http://test.com:1"); + assertMatch(validEntry, "http://test.com:10"); + assertMatch(validEntry, "http://test.com:100"); + assertMatch(validEntry, "http://test.com:1000"); + assertMatch(validEntry, "http://test.com:10000"); + } + + @Test + public void wildcardHostPartShouldMatchAnyPrefix() throws Exception { + final WhitelistEntry validEntry = validateWhitelistUrl("http://*.test.com"); + + assertMatch(validEntry, "http://sub.test.com"); + // FIXME: assertMatch(validEntry, "http://sub.sub.test.com"); + + assertNoMatch(validEntry, "http://test.com"); // missing subdomain + assertNoMatch(validEntry, "http://sub.test2.com"); // wrong top level domain + } + + @Test + public void wildcardHostShouldMatchAnyHost() throws Exception { + final WhitelistEntry validEntry = validateWhitelistUrl("https://*:456"); + + assertMatch(validEntry, "https://test.com:456"); + assertMatch(validEntry, "https://probe.net:456"); + } + + @Test + public void defaultProtocolShouldMatchOnlyHttps() throws Exception { + final WhitelistEntry validEntry = validateWhitelistUrl("test.com"); + + assertMatch(validEntry, "https://test.com"); + + assertNoMatch(validEntry, "http://test.com"); // wrong protocol + assertNoMatch(validEntry, "ftp://test.com"); // wrong protocol + } + + private void assertInvalid(WhitelistEntry entry) { + assertFalse(entry.isValid()); + } + + private void assertMatch(WhitelistEntry entry, String urlString) throws Exception { + final URL url = urlString != null ? new URL(urlString) : null; + assertTrue("Expected " + urlString + " to match " + entry.getRawWhitelistEntry(), entry.matches(url)); + } + + private void assertNoMatch(WhitelistEntry entry, String urlString) throws Exception { + final URL url = urlString != null ? new URL(urlString) : null; + assertFalse(entry.matches(url)); + } +}