From 091ef99908f4fb111665669865b4d92821fb0e5b Mon Sep 17 00:00:00 2001 From: Daniel Beck <1831569+daniel-beck@users.noreply.github.com> Date: Thu, 16 Feb 2023 10:12:53 +0100 Subject: [PATCH] Validate core dependencies (#685) * Validate core dependencies * Fail non-fatally in DirectoryTreeBuilder when a version is invalid --------- Co-authored-by: Daniel Beck --- .../update_center/DirectoryTreeBuilder.java | 6 ++- .../java/io/jenkins/update_center/HPI.java | 41 +++++++++++++++---- .../java/io/jenkins/update_center/Plugin.java | 5 +++ .../PluginUpdateCenterEntry.java | 10 +++-- .../json/PluginVersionsRoot.java | 1 + .../update_center/json/UpdateCenterRoot.java | 12 +++++- .../VersionCappedMavenRepository.java | 2 +- .../io/jenkins/update_center/HPITest.java | 22 ++++++++++ 8 files changed, 84 insertions(+), 15 deletions(-) create mode 100644 src/test/java/io/jenkins/update_center/HPITest.java diff --git a/src/main/java/io/jenkins/update_center/DirectoryTreeBuilder.java b/src/main/java/io/jenkins/update_center/DirectoryTreeBuilder.java index 172e21537..66b7c5f3f 100644 --- a/src/main/java/io/jenkins/update_center/DirectoryTreeBuilder.java +++ b/src/main/java/io/jenkins/update_center/DirectoryTreeBuilder.java @@ -188,7 +188,11 @@ private void buildIndex(File dir, String title, String subtitle, try (IndexHtmlBuilder index = service.newIndexHtmlBuilder(dir, title).withSubtitle(subtitle)) { index.add(permalink, "permalink to the latest"); for (MavenArtifact a : list) { - index.add(a); + try { + index.add(a); + } catch (IOException ex) { + LOGGER.log(Level.INFO, "Failed to add " + a, ex); + } } } } diff --git a/src/main/java/io/jenkins/update_center/HPI.java b/src/main/java/io/jenkins/update_center/HPI.java index 6ae30ef2c..725b60ccf 100644 --- a/src/main/java/io/jenkins/update_center/HPI.java +++ b/src/main/java/io/jenkins/update_center/HPI.java @@ -86,20 +86,45 @@ public URL getDownloadUrl() throws MalformedURLException { return new URL(StringUtils.removeEnd(DOWNLOADS_ROOT_URL, "/") + "/plugins/" + artifact.artifactId + "/" + version + "/" + artifact.artifactId + ".hpi"); } + /** + * Check whether a specified core dependency is valid. + * + * Dependencies on incrementals, RCs, or similar should not appear in update sites, so this only considers dependencies on versions looking like weekly or LTS versions to be valid. + * Currently, no effort is made to confirm that the specified version actually exists, e.g. a dependency on 2.99999999 is considered valid. + * + * @param version the specified core dependency version + * @return true if valid, false otherwise + */ + public static boolean isValidCoreDependency(String version) { + return version.matches("[12][.](0|[1-9][0-9]*)([.][1-9])?"); + } + + /** + * Perform validation (e.g., manifest contents) and throws an exception if it fails. + */ + public void validate() throws IOException { + getRequiredJenkinsVersion(); + } + public String getRequiredJenkinsVersion() throws IOException { String v = getManifestAttributes().getValue("Jenkins-Version"); - if (v!=null) return v; + if (v != null) { + if (!isValidCoreDependency(v)) { + throw new IOException("Invalid Jenkins-Version in " + this + ": " + v); + } + return v; + } v = getManifestAttributes().getValue("Hudson-Version"); if (fixNull(v) != null) { - try { - VersionNumber n = new VersionNumber(v); - if (n.compareTo(JenkinsWar.HUDSON_CUT_OFF)<=0) - return v; // Hudson <= 1.395 is treated as Jenkins - // TODO: Jenkins-Version started appearing from Jenkins 1.401 POM. - // so maybe Hudson > 1.400 shouldn't be considered as a Jenkins plugin? - } catch (IllegalArgumentException e) { + if (!isValidCoreDependency(v)) { + throw new IOException("Invalid Hudson-Version in " + this + ": " + v); } + VersionNumber n = new VersionNumber(v); + if (n.compareTo(JenkinsWar.HUDSON_CUT_OFF)<=0) + return v; // Hudson <= 1.395 is treated as Jenkins + // TODO: Jenkins-Version started appearing from Jenkins 1.401 POM. + // so maybe Hudson > 1.400 shouldn't be considered as a Jenkins plugin? } // Parent versions 1.393 to 1.398 failed to record requiredCore. diff --git a/src/main/java/io/jenkins/update_center/Plugin.java b/src/main/java/io/jenkins/update_center/Plugin.java index c9fdd2116..9626a8366 100644 --- a/src/main/java/io/jenkins/update_center/Plugin.java +++ b/src/main/java/io/jenkins/update_center/Plugin.java @@ -151,4 +151,9 @@ public String getArtifactId() { public TreeMap getArtifacts() { return artifacts; } + + @Override + public String toString() { + return "Plugin '" + artifactId + "'"; + } } diff --git a/src/main/java/io/jenkins/update_center/PluginUpdateCenterEntry.java b/src/main/java/io/jenkins/update_center/PluginUpdateCenterEntry.java index ddba0bcc5..7d0fe88d9 100644 --- a/src/main/java/io/jenkins/update_center/PluginUpdateCenterEntry.java +++ b/src/main/java/io/jenkins/update_center/PluginUpdateCenterEntry.java @@ -40,7 +40,7 @@ private PluginUpdateCenterEntry(String artifactId, HPI latestOffered, HPI previo this.previousOffered = previousOffered; } - public PluginUpdateCenterEntry(Plugin plugin) { + public PluginUpdateCenterEntry(Plugin plugin) throws IOException { this.artifactId = plugin.getArtifactId(); HPI previous = null, latest = null; @@ -49,7 +49,7 @@ public PluginUpdateCenterEntry(Plugin plugin) { while (latest == null && it.hasNext()) { HPI h = it.next(); try { - h.getManifest(); + h.validate(); } catch (IOException e) { LOGGER.log(Level.WARNING, "Failed to resolve "+h+". Dropping this version.",e); continue; @@ -60,7 +60,7 @@ public PluginUpdateCenterEntry(Plugin plugin) { while (previous == null && it.hasNext()) { HPI h = it.next(); try { - h.getManifest(); + h.validate(); } catch (IOException e) { LOGGER.log(Level.WARNING, "Failed to resolve "+h+". Dropping this version.",e); continue; @@ -68,6 +68,10 @@ public PluginUpdateCenterEntry(Plugin plugin) { previous = h; } + if (latest == null) { + throw new IOException("Plugin '" + artifactId + "' has no valid release"); + } + this.latestOffered = latest; this.previousOffered = previous == latest ? null : previous; } diff --git a/src/main/java/io/jenkins/update_center/json/PluginVersionsRoot.java b/src/main/java/io/jenkins/update_center/json/PluginVersionsRoot.java index 9bf8c1ee7..dc2d62cd5 100644 --- a/src/main/java/io/jenkins/update_center/json/PluginVersionsRoot.java +++ b/src/main/java/io/jenkins/update_center/json/PluginVersionsRoot.java @@ -26,6 +26,7 @@ public Map getPlugins() throws IOException { if (plugins == null) { plugins = new TreeMap<>(repository.listJenkinsPlugins().stream().collect(Collectors.toMap(Plugin::getArtifactId, plugin -> new PluginVersions(plugin.getArtifacts())))); } + plugins.entrySet().removeIf(e -> e.getValue().releases.isEmpty()); return plugins; } } diff --git a/src/main/java/io/jenkins/update_center/json/UpdateCenterRoot.java b/src/main/java/io/jenkins/update_center/json/UpdateCenterRoot.java index f8a2aee7b..5bdb0f21e 100644 --- a/src/main/java/io/jenkins/update_center/json/UpdateCenterRoot.java +++ b/src/main/java/io/jenkins/update_center/json/UpdateCenterRoot.java @@ -18,10 +18,14 @@ import java.util.List; import java.util.Map; import java.util.TreeMap; +import java.util.logging.Level; +import java.util.logging.Logger; import java.util.stream.Collectors; import org.apache.commons.lang3.StringUtils; public class UpdateCenterRoot extends WithSignature { + private static final Logger LOGGER = Logger.getLogger(UpdateCenterRoot.class.getName()); + @JSONField @SuppressFBWarnings(value = "SS_SHOULD_BE_STATIC", justification = "Accessed by JSON serializer") public final String updateCenterVersion = "1"; @@ -62,8 +66,12 @@ public UpdateCenterRoot(String id, String connectionCheckUrl, MavenRepository re deprecations = new TreeMap<>(Deprecations.getDeprecatedPlugins().collect(Collectors.toMap(Functions.identity(), UpdateCenterRoot::deprecationForPlugin))); for (Plugin plugin : repo.listJenkinsPlugins()) { - PluginUpdateCenterEntry entry = new PluginUpdateCenterEntry(plugin); - plugins.put(plugin.getArtifactId(), entry); + try { + PluginUpdateCenterEntry entry = new PluginUpdateCenterEntry(plugin); + plugins.put(plugin.getArtifactId(), entry); + } catch (IOException ex) { + LOGGER.log(Level.INFO, "Failed to add update center entry for: " + plugin, ex); + } } core = new UpdateCenterCore(repo.getJenkinsWarsByVersionNumber()); diff --git a/src/main/java/io/jenkins/update_center/wrappers/VersionCappedMavenRepository.java b/src/main/java/io/jenkins/update_center/wrappers/VersionCappedMavenRepository.java index fa99a3d9d..fadf9cea5 100644 --- a/src/main/java/io/jenkins/update_center/wrappers/VersionCappedMavenRepository.java +++ b/src/main/java/io/jenkins/update_center/wrappers/VersionCappedMavenRepository.java @@ -74,7 +74,7 @@ public Collection listJenkinsPlugins() throws IOException { } } } catch (IOException x) { - LOGGER.log(Level.WARNING, "Failed to filter plugin for plugin: " + h.getArtifactId(), x); + LOGGER.log(Level.WARNING, "Failed to filter version " + e.getKey() + " by core dependency for plugin: " + h.getArtifactId(), x); } } diff --git a/src/test/java/io/jenkins/update_center/HPITest.java b/src/test/java/io/jenkins/update_center/HPITest.java new file mode 100644 index 000000000..ec846ff72 --- /dev/null +++ b/src/test/java/io/jenkins/update_center/HPITest.java @@ -0,0 +1,22 @@ +package io.jenkins.update_center; + +import org.junit.Test; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +public class HPITest { + @Test + public void isValidCoreDependencyTest() { + assertTrue(HPI.isValidCoreDependency("1.0")); + assertTrue(HPI.isValidCoreDependency("1.654")); + assertTrue(HPI.isValidCoreDependency("2.0")); + assertTrue(HPI.isValidCoreDependency("2.1")); + assertTrue(HPI.isValidCoreDependency("2.1000")); + assertFalse(HPI.isValidCoreDependency("2.00")); + assertFalse(HPI.isValidCoreDependency("2.01")); + assertFalse(HPI.isValidCoreDependency("2.100-SNAPSHOT")); + assertFalse(HPI.isValidCoreDependency("2.0-rc-1")); + assertFalse(HPI.isValidCoreDependency("2.0-rc-1.vabcd1234")); + } +}