diff --git a/core/src/main/java/hudson/model/UpdateSite.java b/core/src/main/java/hudson/model/UpdateSite.java
index 01bf9c6ddb7976c41092b9e43ad75ddfa205db31..3a234941a423a3bb4d149daadc68882f9af4d965 100644
--- a/core/src/main/java/hudson/model/UpdateSite.java
+++ b/core/src/main/java/hudson/model/UpdateSite.java
@@ -26,6 +26,7 @@
package hudson.model;
import hudson.ClassicPluginStrategy;
+import hudson.ExtensionList;
import hudson.PluginManager;
import hudson.PluginWrapper;
import hudson.Util;
@@ -46,7 +47,9 @@ import java.security.GeneralSecurityException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
+import java.util.HashSet;
import java.util.List;
+import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
@@ -55,17 +58,24 @@ import java.util.concurrent.Callable;
import java.util.concurrent.Future;
import java.util.logging.Level;
import java.util.logging.Logger;
+import java.util.regex.Pattern;
+import java.util.regex.PatternSyntaxException;
import javax.annotation.CheckForNull;
import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
import jenkins.model.Jenkins;
import jenkins.model.DownloadSettings;
+import jenkins.security.UpdateSiteWarningsConfiguration;
import jenkins.util.JSONSignatureValidator;
import jenkins.util.SystemProperties;
+import net.sf.json.JSONArray;
import net.sf.json.JSONException;
import net.sf.json.JSONObject;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils;
import org.kohsuke.accmod.Restricted;
+import org.kohsuke.accmod.restrictions.DoNotUse;
import org.kohsuke.accmod.restrictions.NoExternalUse;
import org.kohsuke.stapler.DataBoundConstructor;
import org.kohsuke.stapler.HttpResponse;
@@ -513,6 +523,12 @@ public class UpdateSite {
* Plugins in the repository, keyed by their artifact IDs.
*/
public final Map plugins = new TreeMap(String.CASE_INSENSITIVE_ORDER);
+ /**
+ * List of warnings (mostly security) published with the update site.
+ *
+ * @since TODO
+ */
+ private final Set warnings = new HashSet();
/**
* If this is non-null, Jenkins is going to check the connectivity to this URL to make sure
@@ -528,6 +544,18 @@ public class UpdateSite {
} else {
core = null;
}
+
+ JSONArray w = o.optJSONArray("warnings");
+ if (w != null) {
+ for (int i = 0; i < w.size(); i++) {
+ try {
+ warnings.add(new Warning(w.getJSONObject(i)));
+ } catch (JSONException ex) {
+ LOGGER.log(Level.WARNING, "Failed to parse JSON for warning", ex);
+ }
+ }
+ }
+
for(Map.Entry e : (Set>)o.getJSONObject("plugins").entrySet()) {
Plugin p = new Plugin(sourceId, e.getValue());
// JENKINS-33308 - include implied dependencies for older plugins that may need them
@@ -545,6 +573,16 @@ public class UpdateSite {
connectionCheckUrl = (String)o.get("connectionCheckUrl");
}
+ /**
+ * Returns the set of warnings
+ * @return the set of warnings
+ * @since TODO
+ */
+ @Restricted(NoExternalUse.class)
+ public Set getWarnings() {
+ return this.warnings;
+ }
+
/**
* Is there a new version of the core?
*/
@@ -646,6 +684,232 @@ public class UpdateSite {
}
+ /**
+ * A version range for {@code Warning}s indicates which versions of a given plugin are affected
+ * by it.
+ *
+ * {@link #name}, {@link #firstVersion} and {@link #lastVersion} fields are only used for administrator notices.
+ *
+ * The {@link #pattern} is used to determine whether a given warning applies to the current installation.
+ *
+ * @since TODO
+ */
+ @Restricted(NoExternalUse.class)
+ public static final class WarningVersionRange {
+ /**
+ * Human-readable English name for this version range, e.g. 'regular', 'LTS', '2.6 line'.
+ */
+ @Nullable
+ public final String name;
+
+ /**
+ * First version in this version range to be subject to the warning.
+ */
+ @Nullable
+ public final String firstVersion;
+
+ /**
+ * Last version in this version range to be subject to the warning.
+ */
+ @Nullable
+ public final String lastVersion;
+
+ /**
+ * Regular expression pattern for this version range that matches all included version numbers.
+ */
+ @Nonnull
+ private final Pattern pattern;
+
+ public WarningVersionRange(JSONObject o) {
+ this.name = Util.fixEmpty(o.optString("name"));
+ this.firstVersion = Util.fixEmpty(o.optString("firstVersion"));
+ this.lastVersion = Util.fixEmpty(o.optString("lastVersion"));
+ Pattern p;
+ try {
+ p = Pattern.compile(o.getString("pattern"));
+ } catch (PatternSyntaxException ex) {
+ LOGGER.log(Level.WARNING, "Failed to compile pattern '" + o.getString("pattern") + "', using '.*' instead", ex);
+ p = Pattern.compile(".*");
+ }
+ this.pattern = p;
+ }
+
+ public boolean includes(VersionNumber number) {
+ return pattern.matcher(number.toString()).matches();
+ }
+ }
+
+ /**
+ * Represents a warning about a certain component, mostly related to known security issues.
+ *
+ * @see UpdateSiteWarningsConfiguration
+ * @see jenkins.security.UpdateSiteWarningsMonitor
+ *
+ * @since TODO
+ */
+ @Restricted(NoExternalUse.class)
+ public static final class Warning {
+
+ public enum Type {
+ CORE,
+ PLUGIN,
+ UNKNOWN
+ }
+
+ /**
+ * The type classifier for this warning.
+ */
+ @Nonnull
+ public /* final */ Type type;
+
+ /**
+ * The globally unique ID of this warning.
+ *
+ * This is typically the CVE identifier or SECURITY issue (Jenkins project);
+ * possibly with a unique suffix (e.g. artifactId) if either applies to multiple components.
+ */
+ @Exported
+ @Nonnull
+ public final String id;
+
+ /**
+ * The name of the affected component.
+ *
+ * - If type is 'core', this is 'core' by convention.
+ *
- If type is 'plugin', this is the artifactId of the affected plugin
+ *
+ */
+ @Exported
+ @Nonnull
+ public final String component;
+
+ /**
+ * A short, English language explanation for this warning.
+ */
+ @Exported
+ @Nonnull
+ public final String message;
+
+ /**
+ * A URL with more information about this, typically a security advisory. For use in administrator notices
+ * only, so
+ */
+ @Exported
+ @Nonnull
+ public final String url;
+
+ /**
+ * A list of named version ranges specifying which versions of the named component this warning applies to.
+ *
+ * If this list is empty, all versions of the component are considered to be affected by this warning.
+ */
+ @Exported
+ @Nonnull
+ public final List versionRanges;
+
+ /**
+ *
+ * @param o the {@link JSONObject} representing the warning
+ * @throws JSONException if the argument does not match the expected format
+ */
+ @Restricted(NoExternalUse.class)
+ public Warning(JSONObject o) {
+ try {
+ this.type = Type.valueOf(o.getString("type").toUpperCase(Locale.US));
+ } catch (IllegalArgumentException ex) {
+ this.type = Type.UNKNOWN;
+ }
+ this.id = o.getString("id");
+ this.component = o.getString("name");
+ this.message = o.getString("message");
+ this.url = o.getString("url");
+
+ if (o.has("versions")) {
+ List ranges = new ArrayList<>();
+ JSONArray versions = o.getJSONArray("versions");
+ for (int i = 0; i < versions.size(); i++) {
+ WarningVersionRange range = new WarningVersionRange(versions.getJSONObject(i));
+ ranges.add(range);
+ }
+ this.versionRanges = Collections.unmodifiableList(ranges);
+ } else {
+ this.versionRanges = Collections.emptyList();
+ }
+ }
+
+ /**
+ * Two objects are considered equal if they are the same type and have the same ID.
+ *
+ * @param o the other object
+ * @return true iff this object and the argument are considered equal
+ */
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (!(o instanceof Warning)) return false;
+
+ Warning warning = (Warning) o;
+
+ return id.equals(warning.id);
+ }
+
+ @Override
+ public int hashCode() {
+ return id.hashCode();
+ }
+
+ public boolean isPluginWarning(@Nonnull String pluginName) {
+ return type == Type.PLUGIN && pluginName.equals(this.component);
+ }
+
+ /**
+ * Returns true if this warning is relevant to the current configuration
+ * @return true if this warning is relevant to the current configuration
+ */
+ public boolean isRelevant() {
+ switch (this.type) {
+ case CORE:
+ VersionNumber current = Jenkins.getVersion();
+
+ if (!isRelevantToVersion(current)) {
+ return false;
+ }
+ return true;
+ case PLUGIN:
+
+ // check whether plugin is installed
+ PluginWrapper plugin = Jenkins.getInstance().getPluginManager().getPlugin(this.component);
+ if (plugin == null) {
+ return false;
+ }
+
+ // check whether warning is relevant to installed version
+ VersionNumber currentCore = plugin.getVersionNumber();
+ if (!isRelevantToVersion(currentCore)) {
+ return false;
+ }
+ return true;
+ case UNKNOWN:
+ default:
+ return false;
+ }
+ }
+
+ public boolean isRelevantToVersion(@Nonnull VersionNumber version) {
+ if (this.versionRanges.isEmpty()) {
+ // no version ranges specified, so all versions are affected
+ return true;
+ }
+
+ for (UpdateSite.WarningVersionRange range : this.versionRanges) {
+ if (range.includes(version)) {
+ return true;
+ }
+ }
+ return false;
+ }
+ }
+
public final class Plugin extends Entry {
/**
* Optional URL to the Wiki page that discusses this plugin.
@@ -863,6 +1127,49 @@ public class UpdateSite {
return true;
}
+ /**
+ * @since TODO
+ */
+ @CheckForNull
+ @Restricted(NoExternalUse.class)
+ public Set getWarnings() {
+ ExtensionList list = ExtensionList.lookup(UpdateSiteWarningsConfiguration.class);
+ if (list.size() == 0) {
+ return Collections.emptySet();
+ }
+
+ Set warnings = new HashSet<>();
+
+ UpdateSiteWarningsConfiguration configuration = list.get(0);
+
+ for (Warning warning: configuration.getAllWarnings()) {
+ if (configuration.isIgnored(warning)) {
+ // warning is currently being ignored
+ continue;
+ }
+ if (!warning.isPluginWarning(this.name)) {
+ // warning is not about this plugin
+ continue;
+ }
+
+ if (!warning.isRelevantToVersion(new VersionNumber(this.version))) {
+ // warning is not relevant to this version
+ continue;
+ }
+ warnings.add(warning);
+ }
+
+ return warnings;
+ }
+
+ /**
+ * @since TODO
+ */
+ @Restricted(DoNotUse.class)
+ public boolean hasWarnings() {
+ return getWarnings().size() > 0;
+ }
+
/**
* @deprecated as of 1.326
* Use {@link #deploy()}.
diff --git a/core/src/main/java/jenkins/security/UpdateSiteWarningsConfiguration.java b/core/src/main/java/jenkins/security/UpdateSiteWarningsConfiguration.java
new file mode 100644
index 0000000000000000000000000000000000000000..08bce4881bf79169ba05d4db4f724cfab1ab9246
--- /dev/null
+++ b/core/src/main/java/jenkins/security/UpdateSiteWarningsConfiguration.java
@@ -0,0 +1,124 @@
+/*
+ * The MIT License
+ *
+ * Copyright (c) 2016, CloudBees, Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+package jenkins.security;
+
+import hudson.Extension;
+import hudson.PluginWrapper;
+import hudson.model.UpdateSite;
+import jenkins.model.GlobalConfiguration;
+import jenkins.model.GlobalConfigurationCategory;
+import jenkins.model.Jenkins;
+import net.sf.json.JSONObject;
+import org.kohsuke.accmod.Restricted;
+import org.kohsuke.accmod.restrictions.NoExternalUse;
+import org.kohsuke.stapler.StaplerRequest;
+
+import javax.annotation.CheckForNull;
+import javax.annotation.Nonnull;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * Configuration for update site-provided warnings.
+ *
+ * @see UpdateSiteWarningsMonitor
+ *
+ * @since TODO
+ */
+@Extension
+@Restricted(NoExternalUse.class)
+public class UpdateSiteWarningsConfiguration extends GlobalConfiguration {
+
+ private HashSet ignoredWarnings = new HashSet<>();
+
+ @Override
+ public GlobalConfigurationCategory getCategory() {
+ return GlobalConfigurationCategory.get(GlobalConfigurationCategory.Security.class);
+ }
+
+ public UpdateSiteWarningsConfiguration() {
+ load();
+ }
+
+ @Nonnull
+ public Set getIgnoredWarnings() {
+ return Collections.unmodifiableSet(ignoredWarnings);
+ }
+
+ public boolean isIgnored(@Nonnull UpdateSite.Warning warning) {
+ return ignoredWarnings.contains(warning.id);
+ }
+
+ @CheckForNull
+ public PluginWrapper getPlugin(@Nonnull UpdateSite.Warning warning) {
+ if (warning.type != UpdateSite.Warning.Type.PLUGIN) {
+ return null;
+ }
+ return Jenkins.getInstance().getPluginManager().getPlugin(warning.component);
+ }
+
+ @Nonnull
+ public Set getAllWarnings() {
+ HashSet allWarnings = new HashSet<>();
+
+ for (UpdateSite site : Jenkins.getInstance().getUpdateCenter().getSites()) {
+ UpdateSite.Data data = site.getData();
+ if (data != null) {
+ allWarnings.addAll(data.getWarnings());
+ }
+ }
+ return allWarnings;
+ }
+
+ @Nonnull
+ public Set getApplicableWarnings() {
+ Set allWarnings = getAllWarnings();
+
+ HashSet applicableWarnings = new HashSet<>();
+ for (UpdateSite.Warning warning: allWarnings) {
+ if (warning.isRelevant()) {
+ applicableWarnings.add(warning);
+ }
+ }
+
+ return Collections.unmodifiableSet(applicableWarnings);
+ }
+
+
+ @Override
+ public boolean configure(StaplerRequest req, JSONObject json) throws FormException {
+ HashSet newIgnoredWarnings = new HashSet<>();
+ for (Object key : json.keySet()) {
+ String warningKey = key.toString();
+ if (!json.getBoolean(warningKey)) {
+ newIgnoredWarnings.add(warningKey);
+ }
+ }
+ this.ignoredWarnings = newIgnoredWarnings;
+ this.save();
+ return true;
+ }
+}
diff --git a/core/src/main/java/jenkins/security/UpdateSiteWarningsMonitor.java b/core/src/main/java/jenkins/security/UpdateSiteWarningsMonitor.java
new file mode 100644
index 0000000000000000000000000000000000000000..378717448dec4ea6929746ade5722d84f24584c7
--- /dev/null
+++ b/core/src/main/java/jenkins/security/UpdateSiteWarningsMonitor.java
@@ -0,0 +1,177 @@
+/*
+ * The MIT License
+ *
+ * Copyright (c) 2016, CloudBees, Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+package jenkins.security;
+
+import hudson.Extension;
+import hudson.ExtensionList;
+import hudson.PluginWrapper;
+import hudson.model.AdministrativeMonitor;
+import hudson.model.UpdateSite;
+import hudson.util.HttpResponses;
+import jenkins.model.Jenkins;
+import org.kohsuke.accmod.Restricted;
+import org.kohsuke.accmod.restrictions.NoExternalUse;
+import org.kohsuke.stapler.HttpResponse;
+import org.kohsuke.stapler.QueryParameter;
+import org.kohsuke.stapler.interceptor.RequirePOST;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+
+/**
+ * Administrative monitor showing plugin/core warnings published by the configured update site to the user.
+ *
+ * Terminology overview:
+ *
+ *
+ * - Applicable warnings are those relevant to currently installed components
+ *
- Active warnings are those actually shown to users.
+ *
- Hidden warnings are those _not_ shown to users due to them being configured to be hidden.
+ *
- Inapplicable warnings are those that are not applicable.
+ *
+ *
+ * The following sets may be non-empty:
+ *
+ *
+ * - Intersection of applicable and active
+ *
- Intersection of applicable and hidden
+ *
- Intersection of hidden and inapplicable (although not really relevant)
+ *
- Intersection of inapplicable and neither hidden nor active
+ *
+ *
+ * The following sets must necessarily be empty:
+ *
+ *
+ * - Intersection of applicable and inapplicable
+ *
- Intersection of active and hidden
+ *
- Intersection of active and inapplicable
+ *
+ *
+ * @since TODO
+ */
+@Extension
+@Restricted(NoExternalUse.class)
+public class UpdateSiteWarningsMonitor extends AdministrativeMonitor {
+ @Override
+ public boolean isActivated() {
+ return !getActiveCoreWarnings().isEmpty() || !getActivePluginWarningsByPlugin().isEmpty();
+ }
+
+ public List getActiveCoreWarnings() {
+ List CoreWarnings = new ArrayList<>();
+
+ for (UpdateSite.Warning warning : getActiveWarnings()) {
+ if (warning.type != UpdateSite.Warning.Type.CORE) {
+ // this is not a core warning
+ continue;
+ }
+ CoreWarnings.add(warning);
+ }
+ return CoreWarnings;
+ }
+
+ public Map> getActivePluginWarningsByPlugin() {
+ Map> activePluginWarningsByPlugin = new HashMap<>();
+
+ for (UpdateSite.Warning warning : getActiveWarnings()) {
+ if (warning.type != UpdateSite.Warning.Type.PLUGIN) {
+ // this is not a plugin warning
+ continue;
+ }
+
+ String pluginName = warning.component;
+
+ PluginWrapper plugin = Jenkins.getInstance().getPluginManager().getPlugin(pluginName);
+
+ if (!activePluginWarningsByPlugin.containsKey(plugin)) {
+ activePluginWarningsByPlugin.put(plugin, new ArrayList());
+ }
+ activePluginWarningsByPlugin.get(plugin).add(warning);
+ }
+ return activePluginWarningsByPlugin;
+
+ }
+
+ private Set getActiveWarnings() {
+ ExtensionList configurations = ExtensionList.lookup(UpdateSiteWarningsConfiguration.class);
+ if (configurations.isEmpty()) {
+ return Collections.emptySet();
+ }
+ UpdateSiteWarningsConfiguration configuration = configurations.get(0);
+
+ HashSet activeWarnings = new HashSet<>();
+
+ for (UpdateSite.Warning warning : configuration.getApplicableWarnings()) {
+ if (!configuration.getIgnoredWarnings().contains(warning.id)) {
+ activeWarnings.add(warning);
+ }
+ }
+
+ return Collections.unmodifiableSet(activeWarnings);
+ }
+
+ /**
+ * Redirects the user to the plugin manager or security configuration
+ */
+ @RequirePOST
+ public HttpResponse doForward(@QueryParameter String fix, @QueryParameter String configure) {
+ if (fix != null) {
+ return HttpResponses.redirectViaContextPath("pluginManager");
+ }
+ if (configure != null) {
+ return HttpResponses.redirectViaContextPath("configureSecurity");
+ }
+
+ // shouldn't happen
+ return HttpResponses.redirectViaContextPath("/");
+ }
+
+ /**
+ * Returns true iff there are applicable but ignored (i.e. hidden) warnings.
+ *
+ * @return true iff there are applicable but ignored (i.e. hidden) warnings.
+ */
+ public boolean hasApplicableHiddenWarnings() {
+ ExtensionList configurations = ExtensionList.lookup(UpdateSiteWarningsConfiguration.class);
+ if (configurations.isEmpty()) {
+ return false;
+ }
+
+ UpdateSiteWarningsConfiguration configuration = configurations.get(0);
+
+ return getActiveWarnings().size() < configuration.getApplicableWarnings().size();
+ }
+
+ @Override
+ public String getDisplayName() {
+ return Messages.UpdateSiteWarningsMonitor_DisplayName();
+ }
+}
diff --git a/core/src/main/resources/hudson/PluginManager/table.jelly b/core/src/main/resources/hudson/PluginManager/table.jelly
index 7c289a7c82775f297151270b776dc19c907bc530..7511c82584d95ee677399e5c67aafd3b67ba9162 100644
--- a/core/src/main/resources/hudson/PluginManager/table.jelly
+++ b/core/src/main/resources/hudson/PluginManager/table.jelly
@@ -110,6 +110,15 @@ THE SOFTWARE.
${%depCoreWarning(p.getNeededDependenciesRequiredCore().toString())}
+
+
+
|
diff --git a/core/src/main/resources/hudson/PluginManager/table.properties b/core/src/main/resources/hudson/PluginManager/table.properties
index e7cd25852c8301c07874ae00edfff1db0fb23c67..63c161534cefcfc384899a32d6c4174d3094c1d3 100644
--- a/core/src/main/resources/hudson/PluginManager/table.properties
+++ b/core/src/main/resources/hudson/PluginManager/table.properties
@@ -34,3 +34,6 @@ depCoreWarning=\
Warning: This plugin requires dependent plugins that require Jenkins {0} or newer. \
Jenkins will refuse to load the dependent plugins requiring a newer version of Jenkins, \
and in turn loading this plugin will fail.
+securityWarning=\
+ Warning: This plugin version may not be safe to use. Please review the following security notices:
+
diff --git a/core/src/main/resources/jenkins/security/Messages.properties b/core/src/main/resources/jenkins/security/Messages.properties
index e09dca3e79758277e4fc1be3e1a943366d9053c2..f0a98fb4a2978195bde2b4aad095bb2d06f36cc8 100644
--- a/core/src/main/resources/jenkins/security/Messages.properties
+++ b/core/src/main/resources/jenkins/security/Messages.properties
@@ -24,4 +24,5 @@ ApiTokenProperty.DisplayName=API Token
ApiTokenProperty.ChangeToken.TokenIsHidden=Token is hidden
ApiTokenProperty.ChangeToken.Success=Updated. See the new token in the field above
ApiTokenProperty.ChangeToken.SuccessHidden=Updated. You need to login as the user to see the token
-RekeySecretAdminMonitor.DisplayName=Re-keying
\ No newline at end of file
+RekeySecretAdminMonitor.DisplayName=Re-keying
+UpdateSiteWarningsMonitor.DisplayName=Update Site Warnings
diff --git a/core/src/main/resources/jenkins/security/UpdateSiteWarningsConfiguration/config.groovy b/core/src/main/resources/jenkins/security/UpdateSiteWarningsConfiguration/config.groovy
new file mode 100644
index 0000000000000000000000000000000000000000..ac82bf1891838da370792124a6c966aa0969c3fb
--- /dev/null
+++ b/core/src/main/resources/jenkins/security/UpdateSiteWarningsConfiguration/config.groovy
@@ -0,0 +1,70 @@
+/*
+ * The MIT License
+ *
+ * Copyright (c) 2016, CloudBees, Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+package jenkins.security.UpdateSiteWarningsConfiguration
+
+f = namespace(lib.FormTagLib)
+st = namespace("jelly:stapler")
+
+st.adjunct(includes: "jenkins.security.UpdateSiteWarningsConfiguration.style")
+
+def printEntry(warning, title, checked) {
+ f.block {
+ f.checkbox(name: warning.id,
+ title: title,
+ checked: checked,
+ class: 'hideWarnings');
+ div(class: "setting-description") {
+ a(warning.url, href: warning.url)
+ }
+ }
+}
+
+f.section(title:_("Hidden security warnings")) {
+
+ f.advanced(title: _("Security warnings"), align:"left") {
+ f.block {
+ text(_("blurb"))
+ }
+ f.entry(title: _("Security warnings"),
+ help: '/descriptorByName/UpdateSiteWarningsConfiguration/help') {
+ table(width:"100%") {
+
+ descriptor.applicableWarnings.each { warning ->
+ if (warning.type == hudson.model.UpdateSite.Warning.Type.CORE) {
+ printEntry(warning,
+ _("warning.core", warning.message),
+ !descriptor.isIgnored(warning))
+ }
+ else if (warning.type == hudson.model.UpdateSite.Warning.Type.PLUGIN) {
+ def plugin = descriptor.getPlugin(warning)
+ printEntry(warning,
+ _("warning.plugin", plugin.displayName, warning.message),
+ !descriptor.isIgnored(warning))
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/core/src/main/resources/jenkins/security/UpdateSiteWarningsConfiguration/config.properties b/core/src/main/resources/jenkins/security/UpdateSiteWarningsConfiguration/config.properties
new file mode 100644
index 0000000000000000000000000000000000000000..333445cd3f55d04fdad68413e2b9d5c64a64ee88
--- /dev/null
+++ b/core/src/main/resources/jenkins/security/UpdateSiteWarningsConfiguration/config.properties
@@ -0,0 +1,27 @@
+# The MIT License
+#
+# Copyright (c) 2016, CloudBees, Inc.
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+# THE SOFTWARE.
+
+warning.core = Jenkins core: {0}
+warning.plugin = {0}: {1}
+
+blurb = This section allows you to disable warnings published on the update site. \
+ Checked warnings are visible (the default), unchecked warnings are hidden.
diff --git a/core/src/main/resources/jenkins/security/UpdateSiteWarningsConfiguration/help.html b/core/src/main/resources/jenkins/security/UpdateSiteWarningsConfiguration/help.html
new file mode 100644
index 0000000000000000000000000000000000000000..1cb7b1df5d426ff875b5b05e071973536503885e
--- /dev/null
+++ b/core/src/main/resources/jenkins/security/UpdateSiteWarningsConfiguration/help.html
@@ -0,0 +1,14 @@
+
+ This list contains all warnings relevant to currently installed components published by the configured update sites.
+ These are typically security-related.
+ Warnings that have been published but are not relevant to currently installed components (either because the affected component isn't installed, or an unaffected version is installed) are not shown here.
+
+
+ Checked entries (the default) are active, i.e. they're shown to administrators in an administrative monitor.
+ Entries can be unchecked to hide them.
+ This can be useful if you've evaluated a specific warning and are confident it does not apply to your environment or configuration, and continued use of the specified component does not constitute a security problem.
+
+
+ Please note that only specific warnings can be disabled; it is not possible to disable all warnings about a certain component.
+ If you wish to disable the display of warnings entirely, then you can disable the administrative monitor in Configure System.
+
diff --git a/core/src/main/resources/jenkins/security/UpdateSiteWarningsConfiguration/style.css b/core/src/main/resources/jenkins/security/UpdateSiteWarningsConfiguration/style.css
new file mode 100644
index 0000000000000000000000000000000000000000..ff1dd9bb5ec4b03dd528550df62ff71dd1f7f089
--- /dev/null
+++ b/core/src/main/resources/jenkins/security/UpdateSiteWarningsConfiguration/style.css
@@ -0,0 +1,4 @@
+.hideWarnings:not(:checked) + label {
+ color: grey;
+ text-decoration: line-through;
+}
diff --git a/core/src/main/resources/jenkins/security/UpdateSiteWarningsMonitor/message.groovy b/core/src/main/resources/jenkins/security/UpdateSiteWarningsMonitor/message.groovy
new file mode 100644
index 0000000000000000000000000000000000000000..09e9bfe161a335af50f14cb51f31c2372cf0c2dd
--- /dev/null
+++ b/core/src/main/resources/jenkins/security/UpdateSiteWarningsMonitor/message.groovy
@@ -0,0 +1,77 @@
+/*
+ * The MIT License
+ *
+ * Copyright (c) 2016, CloudBees, Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+package jenkins.security.UpdateSiteWarningsMonitor
+
+def f = namespace(lib.FormTagLib)
+
+def listWarnings(warnings) {
+ warnings.each { warning ->
+ li {
+ a(warning.message, href: warning.url)
+ }
+ }
+}
+
+def coreWarnings = my.activeCoreWarnings
+def pluginWarnings = my.activePluginWarningsByPlugin
+
+div(class: "error") {
+ text(_("blurb"))
+ ul {
+ if (!coreWarnings.isEmpty()) {
+ li {
+ text(_("coreTitle", jenkins.model.Jenkins.version))
+ ul {
+ listWarnings(coreWarnings)
+ }
+ }
+ }
+
+ if (!pluginWarnings.isEmpty()) {
+ li {
+ pluginWarnings.each { plugin, warnings ->
+ a(_("pluginTitle", plugin.displayName, plugin.version), href: plugin.url)
+
+ ul {
+ listWarnings(warnings)
+ }
+ }
+ }
+ }
+ }
+
+ if (my.hasApplicableHiddenWarnings()) {
+ text(_("more"))
+ }
+}
+
+form(method: "post", action: "${rootURL}/${it.url}/forward") {
+ div {
+ if (!pluginWarnings.isEmpty()) {
+ f.submit(name: 'fix', value: _("pluginManager.link"))
+ }
+ f.submit(name: 'configure', value: _("configureSecurity.link"))
+ }
+}
diff --git a/core/src/main/resources/jenkins/security/UpdateSiteWarningsMonitor/message.properties b/core/src/main/resources/jenkins/security/UpdateSiteWarningsMonitor/message.properties
new file mode 100644
index 0000000000000000000000000000000000000000..35d33182f298088612131b234614789d69aa30bf
--- /dev/null
+++ b/core/src/main/resources/jenkins/security/UpdateSiteWarningsMonitor/message.properties
@@ -0,0 +1,31 @@
+# The MIT License
+#
+# Copyright (c) 2016, CloudBees, Inc.
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+# THE SOFTWARE.
+
+pluginTitle = {0} {1}:
+coreTitle = Jenkins {0} core and libraries:
+
+blurb = Warnings have been published for the following currently installed components:
+more = Additional warnings are hidden due to the current security configuration.
+
+
+pluginManager.link = Go to plugin manager
+configureSecurity.link = Configure which of these warnings are shown
diff --git a/test/src/test/java/hudson/model/UpdateSiteTest.java b/test/src/test/java/hudson/model/UpdateSiteTest.java
index 8e22d43f769a20cfb238b655314dcbef76b4d7fd..8e49e20f2de295d2c3168b6500d6e08078ce8e18 100644
--- a/test/src/test/java/hudson/model/UpdateSiteTest.java
+++ b/test/src/test/java/hudson/model/UpdateSiteTest.java
@@ -33,6 +33,7 @@ import java.io.IOException;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.Arrays;
+import java.util.Collections;
import java.util.HashSet;
import javax.servlet.ServletException;
@@ -41,6 +42,8 @@ import javax.servlet.http.HttpServletResponse;
import static org.junit.Assert.*;
+import jenkins.security.UpdateSiteWarningsConfiguration;
+import jenkins.security.UpdateSiteWarningsMonitor;
import org.apache.commons.io.FileUtils;
import org.eclipse.jetty.server.HttpConnection;
import org.eclipse.jetty.server.Request;
@@ -52,6 +55,7 @@ import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.jvnet.hudson.test.JenkinsRule;
+import org.jvnet.hudson.test.recipes.LocalData;
public class UpdateSiteTest {
@@ -127,5 +131,24 @@ public class UpdateSiteTest {
assertEquals(FormValidation.ok(), us.updateDirectly(/* TODO the certificate is now expired, and downloading a fresh copy did not seem to help */false).get());
assertNotNull(us.getPlugin("AdaptivePlugin"));
}
-
+
+ @Test public void lackOfDataDoesNotFailWarningsCode() throws Exception {
+ assertNull("plugin data is not present", j.jenkins.getUpdateCenter().getSite("default").getData());
+
+ // nothing breaking?
+ j.jenkins.getExtensionList(UpdateSiteWarningsMonitor.class).get(0).getActivePluginWarningsByPlugin();
+ j.jenkins.getExtensionList(UpdateSiteWarningsMonitor.class).get(0).getActiveCoreWarnings();
+ j.jenkins.getExtensionList(UpdateSiteWarningsConfiguration.class).get(0).getAllWarnings();
+ }
+
+ @Test public void incompleteWarningsJson() throws Exception {
+ PersistedList sites = j.jenkins.getUpdateCenter().getSites();
+ sites.clear();
+ URL url = new URL(baseUrl, "/plugins/warnings-update-center-malformed.json");
+ UpdateSite site = new UpdateSite(UpdateCenter.ID_DEFAULT, url.toString());
+ sites.add(site);
+ assertEquals(FormValidation.ok(), site.updateDirectly(false).get());
+ assertEquals("number of warnings", 7, site.getData().getWarnings().size());
+ assertNotEquals("plugin data is present", Collections.emptyMap(), site.getData().plugins);
+ }
}
diff --git a/test/src/test/resources/plugins/warnings-update-center-malformed.json b/test/src/test/resources/plugins/warnings-update-center-malformed.json
new file mode 100644
index 0000000000000000000000000000000000000000..ba99e7aa55d760d677947773404c0af0ed05cff6
--- /dev/null
+++ b/test/src/test/resources/plugins/warnings-update-center-malformed.json
@@ -0,0 +1,264 @@
+{
+ "connectionCheckUrl": "http://www.google.com/",
+ "core": {
+ "buildDate": "Dec 18, 2016",
+ "name": "core",
+ "sha1": "x4rDgtNYm9l7zJ16RVuxMRgGoQE=",
+ "url": "http://updates.jenkins-ci.org/download/war/2.37/jenkins.war",
+ "version": "2.37"
+ },
+ "id": "jenkins40494",
+ "plugins": {
+ "display-url-api": {
+ "buildDate": "Sep 22, 2016",
+ "dependencies": [
+ {
+ "name": "junit",
+ "optional": false,
+ "version": "1.3"
+ }
+ ],
+ "developers": [
+ {
+ "developerId": "jdumay",
+ "email": "jdumay@cloudbees.com",
+ "name": "James Dumay"
+ }
+ ],
+ "excerpt": "\\\\ Provides the DisplayURLProvider extension point to provide alternate URLs for use in notifications. URLs can be requested/extended for these UI locations: * Root page. * Job. * Run. * Run changes. * Test result. ",
+ "gav": "org.jenkins-ci.plugins:display-url-api:0.5",
+ "labels": [],
+ "name": "display-url-api",
+ "previousTimestamp": "2016-09-22T09:35:08.00Z",
+ "previousVersion": "0.4",
+ "releaseTimestamp": "2016-09-22T10:42:00.00Z",
+ "requiredCore": "1.625.3",
+ "scm": "github.com",
+ "sha1": "QEykyLSFuZTnFyHrzZEOgzSsYcU=",
+ "title": "Display URL API",
+ "url": "http://updates.jenkins-ci.org/download/plugins/display-url-api/0.5/display-url-api.hpi",
+ "version": "0.5",
+ "wiki": "https://wiki.jenkins-ci.org/display/JENKINS/Display+URL+API+Plugin"
+ },
+ "junit": {
+ "buildDate": "Oct 17, 2016",
+ "dependencies": [
+ {
+ "name": "structs",
+ "optional": false,
+ "version": "1.2"
+ }
+ ],
+ "developers": [
+ {
+ "developerId": "ogondza"
+ }
+ ],
+ "excerpt": "Allows JUnit-format test results to be published.",
+ "gav": "org.jenkins-ci.plugins:junit:1.19",
+ "labels": [
+ "report"
+ ],
+ "name": "junit",
+ "previousTimestamp": "2016-08-08T15:10:34.00Z",
+ "previousVersion": "1.18",
+ "releaseTimestamp": "2016-10-17T12:15:20.00Z",
+ "requiredCore": "1.580.1",
+ "scm": "github.com",
+ "sha1": "f3jcYlxz6/8PK43W3KL5LFtz7ro=",
+ "title": "JUnit Plugin",
+ "url": "http://updates.jenkins-ci.org/download/plugins/junit/1.19/junit.hpi",
+ "version": "1.19",
+ "wiki": "https://wiki.jenkins-ci.org/display/JENKINS/JUnit+Plugin"
+ },
+ "mailer": {
+ "buildDate": "Sep 04, 2016",
+ "dependencies": [
+ {
+ "name": "display-url-api",
+ "optional": false,
+ "version": "0.2"
+ }
+ ],
+ "developers": [
+ {
+ "developerId": "andresrc"
+ }
+ ],
+ "excerpt": "This plugin allows you to configure email notifications for build results. This is a break-out of the original core based email component. ",
+ "gav": "org.jenkins-ci.plugins:mailer:1.18",
+ "labels": [],
+ "name": "mailer",
+ "previousTimestamp": "2016-04-20T08:46:22.00Z",
+ "previousVersion": "1.17",
+ "releaseTimestamp": "2016-09-04T09:14:16.00Z",
+ "requiredCore": "1.625.3",
+ "scm": "github.com",
+ "sha1": "poG1EauZFM5lZE5hCBx5mqr/mMA=",
+ "title": "Jenkins Mailer Plugin",
+ "url": "http://updates.jenkins-ci.org/download/plugins/mailer/1.18/mailer.hpi",
+ "version": "1.18",
+ "wiki": "https://wiki.jenkins-ci.org/display/JENKINS/Mailer"
+ },
+ "extra-columns": {
+ "buildDate": "Apr 11, 2016",
+ "dependencies": [
+ {
+ "name": "junit",
+ "optional": false,
+ "version": "1.11"
+ }
+ ],
+ "developers": [
+ {
+ "developerId": "fredg",
+ "name": "Fred G."
+ }
+ ],
+ "excerpt": "This is a general listview-column plugin that currently contains the following columns: Test Result, Configure Project button, Disable/Enable Project button, Project Description, Build Description, SCM Type, Last/Current Build Console output, Job Type, Build Duration, Build Parameters.",
+ "gav": "org.jenkins-ci.plugins:extra-columns:1.17",
+ "labels": [
+ "listview-column"
+ ],
+ "name": "extra-columns",
+ "previousTimestamp": "2015-12-11T01:18:48.00Z",
+ "previousVersion": "1.16",
+ "releaseTimestamp": "2016-04-11T22:36:22.00Z",
+ "requiredCore": "1.475",
+ "scm": "github.com",
+ "sha1": "8y9Y91n7/Aw47G3pCxzJzTd94J0=",
+ "title": "Extra Columns Plugin",
+ "url": "http://updates.jenkins-ci.org/download/plugins/extra-columns/1.17/extra-columns.hpi",
+ "version": "1.17",
+ "wiki": "https://wiki.jenkins-ci.org/display/JENKINS/Extra+Columns+Plugin"
+ },
+ "structs": {
+ "buildDate": "Aug 30, 2016",
+ "dependencies": [],
+ "developers": [
+ {
+ "developerId": "jglick"
+ }
+ ],
+ "excerpt": "Library plugin for DSL plugins that need concise names for Jenkins extensions",
+ "gav": "org.jenkins-ci.plugins:structs:1.5",
+ "labels": [],
+ "name": "structs",
+ "previousTimestamp": "2016-08-26T14:11:44.00Z",
+ "previousVersion": "1.4",
+ "releaseTimestamp": "2016-08-30T14:10:10.00Z",
+ "requiredCore": "1.580.1",
+ "scm": "github.com",
+ "sha1": "fK+F0PEfSS//DOqmNJvX2rQYabo=",
+ "title": "Structs Plugin",
+ "url": "http://updates.jenkins-ci.org/download/plugins/structs/1.5/structs.hpi",
+ "version": "1.5",
+ "wiki": "https://wiki.jenkins-ci.org/display/JENKINS/Structs+plugin"
+ }
+ },
+ "warnings": [
+ {
+ "id": "SECURITY-208",
+ "type": "plugin",
+ "name": "google-login",
+ "message": "Authentication bypass vulnerability",
+ "url": "https://wiki.jenkins-ci.org/display/SECURITY/Jenkins+Security+Advisory+2015-10-12",
+ "versions": [
+ {
+ "lastVersion": "1.1",
+ "pattern": "1[.][01](|[.-].*)"
+ }
+ ]
+ },
+ {
+ "id": "SECURITY-136",
+ "type": "plugin",
+ "name": "extra-columns",
+ "message": "Stored XSS vulnerability",
+ "url": "https://wiki.jenkins-ci.org/display/SECURITY/Jenkins+Security+Advisory+2016-04-11",
+ "versions": [
+ {
+ "lastVersion": "1.16",
+ "pattern": "1[.](\\d|1[0123456])(|[.-].*)"
+ }
+ ]
+ },
+ {
+ "id": "SECURITY-258",
+ "type": "plugin",
+ "name": "extra-columns",
+ "message": "Groovy sandbox protection incomplete",
+ "url": "https://wiki.jenkins-ci.org/display/SECURITY/Jenkins+Security+Advisory+2016-04-11",
+ "versions": [
+ {
+ "lastVersion": "1.18",
+ "pattern": "1[.](\\d|1[012345678])(|[.-].*)"
+ }
+ ]
+ },
+ {
+ "id": "SECURITY-85",
+ "type": "plugin",
+ "name": "tap",
+ "message": "Path traversal vulnerability",
+ "url": "https://wiki.jenkins-ci.org/display/SECURITY/Jenkins+Security+Advisory+2016-06-20",
+ "versions": [
+ {
+ "lastVersion": "1.24",
+ "pattern": "1[.](\\d|1\\d|2[01234])(|[.-].*)"
+ }
+ ]
+ },
+ {
+ "id": "SECURITY-278",
+ "type": "plugin",
+ "name": "image-gallery",
+ "message": "Path traversal vulnerability",
+ "url": "https://wiki.jenkins-ci.org/display/SECURITY/Jenkins+Security+Advisory+2016-06-20",
+ "versions": [
+ {
+ "lastVersion": "1.3",
+ "pattern": "(0[.].*|1[.][0123])(|[.-].*)"
+ }
+ ]
+ },
+ {
+ "id": "SECURITY-290",
+ "type": "plugin",
+ "name": "build-failure-analyzer",
+ "message": "Cross-site scripting vulnerability",
+ "url": "https://wiki.jenkins-ci.org/display/SECURITY/Jenkins+Security+Advisory+2016-06-20",
+ "versions": [
+ {
+ "lastVersion": "1.15.0",
+ "pattern": "1[.](\\d|1[012345])[.]\\d+(|[.-].*)"
+ }
+ ]
+ },
+ {
+ "id": "SECURITY-305",
+ "type": "plugin",
+ "versions": [
+ {
+ "lastVersion": "1.7.24",
+ "pattern": "1[.]7[.](\\d(|[.-].*)|24)"
+ }
+ ]
+ },
+ {
+ "id": "SECURITY-309",
+ "type": "plugin",
+ "name": "cucumber-reports",
+ "message": "Plugin disables Content-Security-Policy for files served by Jenkins",
+ "url": "https://wiki.jenkins-ci.org/display/SECURITY/Jenkins+Security+Advisory+2016-07-27",
+ "versions": [
+ {
+ "firstVersion": "1.3.0",
+ "lastVersion": "2.5.1",
+ "pattern": "(1[.][34]|2[.][012345])(|[.-].*)"
+ }
+ ]
+ }
+ ],
+ "updateCenterVersion": "1"
+}
\ No newline at end of file
diff --git a/war/src/main/webapp/css/style.css b/war/src/main/webapp/css/style.css
index 9491f28afb05baa282fc15dcdce5ab63e0c0ab9e..3f2812f2cb71621513315eace4cd38fc51b0da19 100644
--- a/war/src/main/webapp/css/style.css
+++ b/war/src/main/webapp/css/style.css
@@ -1580,6 +1580,13 @@ TEXTAREA.rich-editor {
color: #FF0000;
}
+#plugins .securityWarning {
+ white-space: normal;
+ margin-top: 0.5em;
+ padding-left: 2em;
+ color: #FF0000;
+}
+
/* ========================= progress bar ========================= */
table.progress-bar {