diff --git a/core/src/main/java/hudson/model/DownloadService.java b/core/src/main/java/hudson/model/DownloadService.java index 707491dbc18939c70ec4f5d107e1b4f0c9625436..ff5e3811c91b961f5fa730e4b33bb5ed05189eb6 100644 --- a/core/src/main/java/hudson/model/DownloadService.java +++ b/core/src/main/java/hudson/model/DownloadService.java @@ -66,7 +66,7 @@ public class DownloadService extends PageDecorator { * Builds up an HTML fragment that starts all the download jobs. */ public String generateFragment() { - if (!DownloadSettings.get().isUseBrowser()) { + if (!DownloadSettings.usePostBack()) { return ""; } if (neverUpdate) return ""; @@ -170,6 +170,30 @@ public class DownloadService extends PageDecorator { } } + /** + * Loads JSON from a JSON-with-{@code postMessage} URL. + * @param src a URL to a JSON HTML file (typically including {@code id} and {@code version} query parameters) + * @return the embedded JSON text + * @throws IOException if either downloading or processing failed + */ + @Restricted(NoExternalUse.class) + public static String loadJSONHTML(URL src) throws IOException { + InputStream is = ProxyConfiguration.open(src).getInputStream(); + try { + String jsonp = IOUtils.toString(is, "UTF-8"); + String preamble = "window.parent.postMessage(JSON.stringify("; + int start = jsonp.indexOf(preamble); + int end = jsonp.lastIndexOf("),'*');"); + if (start >= 0 && end > start) { + return jsonp.substring(start + preamble.length(), end).trim(); + } else { + throw new IOException("Could not find JSON in " + src); + } + } finally { + is.close(); + } + } + /** * Represents a periodically updated JSON data file obtained from a remote URL. * @@ -283,9 +307,7 @@ public class DownloadService extends PageDecorator { * This is where the browser sends us the data. */ public void doPostBack(StaplerRequest req, StaplerResponse rsp) throws IOException { - if (!DownloadSettings.get().isUseBrowser()) { - throw new IOException("not allowed"); - } + DownloadSettings.checkPostBackAccess(); long dataTimestamp = System.currentTimeMillis(); due = dataTimestamp+getInterval(); // success or fail, don't try too often @@ -317,7 +339,7 @@ public class DownloadService extends PageDecorator { @Restricted(NoExternalUse.class) public FormValidation updateNow() throws IOException { - return load(loadJSON(new URL(getUrl() + "?id=" + URLEncoder.encode(getId(), "UTF-8") + "&version=" + URLEncoder.encode(Jenkins.VERSION, "UTF-8"))), System.currentTimeMillis()); + return load(loadJSONHTML(new URL(getUrl() + ".html?id=" + URLEncoder.encode(getId(), "UTF-8") + "&version=" + URLEncoder.encode(Jenkins.VERSION, "UTF-8"))), System.currentTimeMillis()); } /** diff --git a/core/src/main/java/hudson/model/UpdateSite.java b/core/src/main/java/hudson/model/UpdateSite.java index 77b5926c4b7d38ba3b7a7775aed46fe6c2fd510c..0616a3a01f3c1b5962b748fde2d36149577afaf0 100644 --- a/core/src/main/java/hudson/model/UpdateSite.java +++ b/core/src/main/java/hudson/model/UpdateSite.java @@ -174,9 +174,7 @@ public class UpdateSite { * This is the endpoint that receives the update center data file from the browser. */ public FormValidation doPostBack(StaplerRequest req) throws IOException, GeneralSecurityException { - if (!DownloadSettings.get().isUseBrowser()) { - throw new IOException("not allowed"); - } + DownloadSettings.checkPostBackAccess(); return updateData(IOUtils.toString(req.getInputStream(),"UTF-8"), true); } diff --git a/core/src/main/java/jenkins/model/DownloadSettings.java b/core/src/main/java/jenkins/model/DownloadSettings.java index 0cf410c28b662aea2ff5bb44e8a961c92dd630b1..e16898ab86dc83dc6816f31c744f860c91376e8c 100644 --- a/core/src/main/java/jenkins/model/DownloadSettings.java +++ b/core/src/main/java/jenkins/model/DownloadSettings.java @@ -25,6 +25,8 @@ package jenkins.model; import hudson.Extension; +import hudson.Main; +import hudson.model.AdministrativeMonitor; import hudson.model.AsyncPeriodicWork; import hudson.model.DownloadService; import hudson.model.TaskListener; @@ -32,6 +34,7 @@ import hudson.model.UpdateSite; import hudson.util.FormValidation; import java.io.IOException; import net.sf.json.JSONObject; +import org.acegisecurity.AccessDeniedException; import org.kohsuke.accmod.Restricted; import org.kohsuke.accmod.restrictions.NoExternalUse; import org.kohsuke.stapler.HttpResponse; @@ -49,7 +52,7 @@ import org.kohsuke.stapler.StaplerRequest; return Jenkins.getInstance().getInjector().getInstance(DownloadSettings.class); } - private boolean useBrowser = true; // historical default, not necessarily recommended + private boolean useBrowser = false; public DownloadSettings() { load(); @@ -69,6 +72,21 @@ import org.kohsuke.stapler.StaplerRequest; save(); } + @Override public GlobalConfigurationCategory getCategory() { + return GlobalConfigurationCategory.get(GlobalConfigurationCategory.Security.class); + } + + public static boolean usePostBack() { + return get().isUseBrowser() && Jenkins.getInstance().hasPermission(Jenkins.ADMINISTER); + } + + public static void checkPostBackAccess() throws AccessDeniedException { + if (!get().isUseBrowser()) { + throw new AccessDeniedException("browser-based download disabled"); + } + Jenkins.getInstance().checkPermission(Jenkins.ADMINISTER); + } + @Extension public static final class DailyCheck extends AsyncPeriodicWork { public DailyCheck() { @@ -79,10 +97,24 @@ import org.kohsuke.stapler.StaplerRequest; return DAY; } + @Override public long getInitialDelay() { + return Main.isUnitTest ? DAY : 0; + } + @Override protected void execute(TaskListener listener) throws IOException, InterruptedException { if (get().isUseBrowser()) { return; } + boolean due = false; + for (UpdateSite site : Jenkins.getInstance().getUpdateCenter().getSites()) { + if (site.isDue()) { + due = true; + break; + } + } + if (!due) { + return; + } HttpResponse rsp = Jenkins.getInstance().getPluginManager().doCheckUpdatesServer(); if (rsp instanceof FormValidation) { listener.error(((FormValidation) rsp).renderHtml()); @@ -91,4 +123,12 @@ import org.kohsuke.stapler.StaplerRequest; } + @Extension public static final class Warning extends AdministrativeMonitor { + + @Override public boolean isActivated() { + return DownloadSettings.get().isUseBrowser(); + } + + } + } diff --git a/core/src/main/java/jenkins/util/JSONSignatureValidator.java b/core/src/main/java/jenkins/util/JSONSignatureValidator.java index b36f4e399f8f1a1f2d2be0c0e1c1acdac620a03b..76d422cd90743110d7279db0ecb926efc6d10acc 100644 --- a/core/src/main/java/jenkins/util/JSONSignatureValidator.java +++ b/core/src/main/java/jenkins/util/JSONSignatureValidator.java @@ -135,6 +135,9 @@ public class JSONSignatureValidator { // which isn't useful at all Set anchors = new HashSet(); // CertificateUtil.getDefaultRootCAs(); Jenkins j = Jenkins.getInstance(); + if (j == null) { + return anchors; + } for (String cert : (Set) j.servletContext.getResourcePaths("/WEB-INF/update-center-rootCAs")) { if (cert.endsWith("/") || cert.endsWith(".txt")) { continue; // skip directories also any text files that are meant to be documentation diff --git a/core/src/main/resources/hudson/model/UpdateCenter/PageDecoratorImpl/footer.jelly b/core/src/main/resources/hudson/model/UpdateCenter/PageDecoratorImpl/footer.jelly index 777f1192c77f7b3216ceb9c42a866c432ad58083..684bc26bb054257e39347db2cd2415503f5ce9ae 100644 --- a/core/src/main/resources/hudson/model/UpdateCenter/PageDecoratorImpl/footer.jelly +++ b/core/src/main/resources/hudson/model/UpdateCenter/PageDecoratorImpl/footer.jelly @@ -31,8 +31,8 @@ THE SOFTWARE. --> - - + + \ No newline at end of file diff --git a/test/src/test/resources/hudson/model/hudson.tools.JDKInstaller.json.html b/test/src/test/resources/hudson/model/hudson.tools.JDKInstaller.json.html new file mode 100644 index 0000000000000000000000000000000000000000..5b315032c5f5b12216d2bd423e2c4f50e765db7d --- /dev/null +++ b/test/src/test/resources/hudson/model/hudson.tools.JDKInstaller.json.html @@ -0,0 +1,6498 @@ + \ No newline at end of file