diff --git a/changelog.html b/changelog.html index 9624e55e5b..474480ac10 100644 --- a/changelog.html +++ b/changelog.html @@ -55,7 +55,9 @@ Upcoming changes
diff --git a/core/src/main/java/hudson/model/DownloadService.java b/core/src/main/java/hudson/model/DownloadService.java index 707491dbc1..ff5e3811c9 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 77b5926c4b..7a1db1368f 100644 --- a/core/src/main/java/hudson/model/UpdateSite.java +++ b/core/src/main/java/hudson/model/UpdateSite.java @@ -60,6 +60,14 @@ import jenkins.util.JSONSignatureValidator; import net.sf.json.JSONException; import net.sf.json.JSONObject; import org.apache.commons.io.IOUtils; +import org.apache.commons.io.IOUtils; +import javax.annotation.CheckForNull; +import javax.annotation.Nonnull; +import jenkins.model.Jenkins; +import jenkins.model.DownloadSettings; +import jenkins.util.JSONSignatureValidator; +import net.sf.json.JSONException; +import net.sf.json.JSONObject; import org.apache.commons.lang.StringUtils; import org.kohsuke.accmod.Restricted; import org.kohsuke.accmod.restrictions.NoExternalUse; @@ -174,9 +182,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 0cf410c28b..e16898ab86 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/resources/hudson/model/UpdateCenter/PageDecoratorImpl/footer.jelly b/core/src/main/resources/hudson/model/UpdateCenter/PageDecoratorImpl/footer.jelly index 777f1192c7..684bc26bb0 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. -->