diff --git a/bom/pom.xml b/bom/pom.xml index 870a925f93c7..ab691c2e3331 100644 --- a/bom/pom.xml +++ b/bom/pom.xml @@ -40,7 +40,7 @@ THE SOFTWARE. 9.6 2.0.12 - 1822.v120278426e1c + 1839.ved17667b_a_eb_5 2.4.21 diff --git a/core/src/main/java/hudson/Functions.java b/core/src/main/java/hudson/Functions.java index d27160aaa8a2..dbf031d2461a 100644 --- a/core/src/main/java/hudson/Functions.java +++ b/core/src/main/java/hudson/Functions.java @@ -2242,10 +2242,19 @@ public static boolean isWipeOutPermissionEnabled() { return SystemProperties.getBoolean("hudson.security.WipeOutPermission"); } + @Deprecated public static String createRenderOnDemandProxy(JellyContext context, String attributesToCapture) { return Stapler.getCurrentRequest().createJavaScriptProxy(new RenderOnDemandClosure(context, attributesToCapture)); } + /** + * Called from renderOnDemand.jelly to generate the parameters for the proxy object generation. + */ + @Restricted(NoExternalUse.class) + public static StaplerRequest.RenderOnDemandParameters createRenderOnDemandProxyParameters(JellyContext context, String attributesToCapture) { + return Stapler.getCurrentRequest().createJavaScriptProxyParameters(new RenderOnDemandClosure(context, attributesToCapture)); + } + public static String getCurrentDescriptorByNameUrl() { return Descriptor.getCurrentDescriptorByNameUrl(); } diff --git a/core/src/main/resources/lib/layout/renderOnDemand.jelly b/core/src/main/resources/lib/layout/renderOnDemand.jelly index 029fb82d4cb8..dc2e2ecbde3a 100644 --- a/core/src/main/resources/lib/layout/renderOnDemand.jelly +++ b/core/src/main/resources/lib/layout/renderOnDemand.jelly @@ -36,8 +36,12 @@ THE SOFTWARE. + render-on-demand ${attrs.clazz} - ${h.createRenderOnDemandProxy(context,attrs.capture)} + ${parameters.proxyMethod} + ${parameters.url} + ${parameters.crumb} + ${parameters.urlNames} diff --git a/test/src/test/java/org/kohsuke/stapler/BindTest.java b/test/src/test/java/org/kohsuke/stapler/BindTest.java new file mode 100644 index 000000000000..5c32adf15a58 --- /dev/null +++ b/test/src/test/java/org/kohsuke/stapler/BindTest.java @@ -0,0 +1,176 @@ +package org.kohsuke.stapler; + +import static org.hamcrest.CoreMatchers.containsString; +import static org.hamcrest.CoreMatchers.endsWith; +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.startsWith; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.Assert.assertThrows; + +import hudson.ExtensionList; +import hudson.model.InvisibleAction; +import hudson.model.RootAction; +import java.util.Arrays; +import java.util.List; +import org.apache.commons.lang.StringUtils; +import org.htmlunit.Page; +import org.htmlunit.ScriptException; +import org.htmlunit.html.HtmlPage; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.jvnet.hudson.test.JenkinsRule; +import org.jvnet.hudson.test.TestExtension; +import org.kohsuke.stapler.bind.JavaScriptMethod; +import org.kohsuke.stapler.bind.WithWellKnownURL; + +@RunWith(Parameterized.class) +public class BindTest { + @Rule + public JenkinsRule j = new JenkinsRule(); + + @Parameterized.Parameters + public static List contexts() { + return Arrays.asList("/jenkins", ""); + } + + public BindTest(String contextPath) { + j.contextPath = contextPath; + } + + @Test + public void bindNormal() throws Exception { + final RootActionImpl root = ExtensionList.lookupSingleton(RootActionImpl.class); + try (JenkinsRule.WebClient wc = j.createWebClient()) { + final HtmlPage htmlPage = wc.goTo(root.getUrlName()); + final String scriptUrl = htmlPage + .getElementsByTagName("script") + .stream() + .filter(it -> it.getAttribute("src").startsWith(j.contextPath + "/$stapler/bound/script" + j.contextPath + "/$stapler/bound/")) + .findFirst() + .orElseThrow() + .getAttribute("src"); + + final Page script = wc.goTo(StringUtils.removeStart(scriptUrl, j.contextPath + "/"), "application/javascript"); + final String content = script.getWebResponse().getContentAsString(); + assertThat(content, startsWith("varname = makeStaplerProxy('" + j.contextPath + "/$stapler/bound/")); + assertThat(content, endsWith("','test',['annotatedJsMethod1','byName1']);")); + } + assertThat(root.invocations, is(1)); + } + + @Test + public void bindWithWellKnownURL() throws Exception { + final RootActionWithWellKnownURL root = ExtensionList.lookupSingleton(RootActionWithWellKnownURL.class); + try (JenkinsRule.WebClient wc = j.createWebClient()) { + final HtmlPage htmlPage = wc.goTo(root.getUrlName()); + final String scriptUrl = htmlPage + .getElementsByTagName("script") + .stream() + .filter(it -> it.getAttribute("src").startsWith(j.contextPath + "/$stapler/bound/script" + j.contextPath + "/theWellKnownRoot?")) + .findFirst() + .orElseThrow() + .getAttribute("src"); + + final Page script = wc.goTo(StringUtils.removeStart(scriptUrl, j.contextPath + "/"), "application/javascript"); + assertThat(script.getWebResponse().getContentAsString(), is("varname = makeStaplerProxy('" + j.contextPath + "/theWellKnownRoot','test',['annotatedJsMethod2','byName2']);")); + } + assertThat(root.invocations, is(1)); + } + + @Test + public void bindNull() throws Exception { + final RootActionImpl root = ExtensionList.lookupSingleton(RootActionImpl.class); + try (JenkinsRule.WebClient wc = j.createWebClient()) { + final ScriptException exception = assertThrows(ScriptException.class, () -> wc.goTo(root.getUrlName() + "/null")); + assertThat(exception.getFailingLineNumber(), is(2)); + assertThat(exception.getFailingColumnNumber(), is(0)); + assertThat(exception.getMessage(), containsString("TypeError: Cannot call method \"byName1\" of null")); + + final HtmlPage htmlPage = exception.getPage(); + final String scriptUrl = htmlPage.getElementsByTagName("script").stream().filter(it -> it.getAttribute("src").equals(j.contextPath + "/$stapler/bound/script/null?var=varname")).findFirst().orElseThrow().getAttribute("src"); + + final Page script = wc.goTo(StringUtils.removeStart(scriptUrl, j.contextPath + "/"), "application/javascript"); + final String content = script.getWebResponse().getContentAsString(); + assertThat(content, is("varname = null;")); + } + assertThat(root.invocations, is(0)); + } + + @Test + public void bindUnsafe() throws Exception { + final RootActionImpl root = ExtensionList.lookupSingleton(RootActionImpl.class); + try (JenkinsRule.WebClient wc = j.createWebClient()) { + final HtmlPage htmlPage = wc.goTo(root.getUrlName() + "/unsafe-var"); + final String content = htmlPage + .getElementsByTagName("script") + .stream() + .filter(it -> it.getTextContent().contains("makeStaplerProxy")) + .findFirst() + .orElseThrow() + .getTextContent(); + + assertThat(content, startsWith("window['varname']=makeStaplerProxy('" + j.contextPath + "/$stapler/bound/")); + assertThat(content, endsWith("','test',['annotatedJsMethod1','byName1']);")); + } + assertThat(root.invocations, is(1)); + } + + @Test + public void bindInlineNull() throws Exception { + final RootActionImpl root = ExtensionList.lookupSingleton(RootActionImpl.class); + try (JenkinsRule.WebClient wc = j.createWebClient()) { + final HtmlPage htmlPage = wc.goTo(root.getUrlName() + "/inline-null"); + final String content = htmlPage + .getElementsByTagName("script") + .stream() + .filter(it -> it.getTextContent().contains("var inline")) + .findFirst() + .orElseThrow() + .getTextContent(); + + assertThat(content, containsString("var inline = null")); + } + assertThat(root.invocations, is(0)); + } + + @TestExtension + public static class RootActionImpl extends InvisibleAction implements RootAction { + private int invocations; + + @Override + public String getUrlName() { + return "theRoot"; + } + + @JavaScriptMethod + public void annotatedJsMethod1(String foo) {} + + public void jsByName1() { + invocations++; + } + } + + @TestExtension + public static class RootActionWithWellKnownURL extends InvisibleAction implements RootAction, WithWellKnownURL { + private int invocations; + + @Override + public String getUrlName() { + return "theWellKnownRoot"; + } + + @Override + public String getWellKnownUrl() { + return "/" + getUrlName(); + } + + @JavaScriptMethod + public void annotatedJsMethod2(String foo) {} + + public void jsByName2() { + invocations++; + } + } +} diff --git a/test/src/test/resources/org/kohsuke/stapler/BindTest/RootActionImpl/adjunct.js b/test/src/test/resources/org/kohsuke/stapler/BindTest/RootActionImpl/adjunct.js new file mode 100644 index 000000000000..952a9de74812 --- /dev/null +++ b/test/src/test/resources/org/kohsuke/stapler/BindTest/RootActionImpl/adjunct.js @@ -0,0 +1,2 @@ +// eslint-disable-next-line no-undef +varname.byName1(); diff --git a/test/src/test/resources/org/kohsuke/stapler/BindTest/RootActionImpl/index.jelly b/test/src/test/resources/org/kohsuke/stapler/BindTest/RootActionImpl/index.jelly new file mode 100644 index 000000000000..1de10c5ac222 --- /dev/null +++ b/test/src/test/resources/org/kohsuke/stapler/BindTest/RootActionImpl/index.jelly @@ -0,0 +1,10 @@ + + + + +

Root Action

+ + +
+
+
diff --git a/test/src/test/resources/org/kohsuke/stapler/BindTest/RootActionImpl/inline-null.jelly b/test/src/test/resources/org/kohsuke/stapler/BindTest/RootActionImpl/inline-null.jelly new file mode 100644 index 000000000000..b99131131c1a --- /dev/null +++ b/test/src/test/resources/org/kohsuke/stapler/BindTest/RootActionImpl/inline-null.jelly @@ -0,0 +1,11 @@ + + + + +

Root Action

+ +
+
+
diff --git a/test/src/test/resources/org/kohsuke/stapler/BindTest/RootActionImpl/null.jelly b/test/src/test/resources/org/kohsuke/stapler/BindTest/RootActionImpl/null.jelly new file mode 100644 index 000000000000..cb81f08c8de5 --- /dev/null +++ b/test/src/test/resources/org/kohsuke/stapler/BindTest/RootActionImpl/null.jelly @@ -0,0 +1,10 @@ + + + + +

Root Action with null

+ + +
+
+
diff --git a/test/src/test/resources/org/kohsuke/stapler/BindTest/RootActionImpl/unsafe-var.jelly b/test/src/test/resources/org/kohsuke/stapler/BindTest/RootActionImpl/unsafe-var.jelly new file mode 100644 index 000000000000..ccb261682ba4 --- /dev/null +++ b/test/src/test/resources/org/kohsuke/stapler/BindTest/RootActionImpl/unsafe-var.jelly @@ -0,0 +1,10 @@ + + + + +

Root Action

+ + +
+
+
diff --git a/test/src/test/resources/org/kohsuke/stapler/BindTest/RootActionWithWellKnownURL/adjunct.js b/test/src/test/resources/org/kohsuke/stapler/BindTest/RootActionWithWellKnownURL/adjunct.js new file mode 100644 index 000000000000..268b06ff66a4 --- /dev/null +++ b/test/src/test/resources/org/kohsuke/stapler/BindTest/RootActionWithWellKnownURL/adjunct.js @@ -0,0 +1,2 @@ +// eslint-disable-next-line no-undef +varname.byName2(); diff --git a/test/src/test/resources/org/kohsuke/stapler/BindTest/RootActionWithWellKnownURL/index.jelly b/test/src/test/resources/org/kohsuke/stapler/BindTest/RootActionWithWellKnownURL/index.jelly new file mode 100644 index 000000000000..563805f4889c --- /dev/null +++ b/test/src/test/resources/org/kohsuke/stapler/BindTest/RootActionWithWellKnownURL/index.jelly @@ -0,0 +1,10 @@ + + + + +

Root Action

+ + +
+
+
diff --git a/war/src/main/webapp/scripts/hudson-behavior.js b/war/src/main/webapp/scripts/hudson-behavior.js index 3be3a4fddadb..efbfb8d17959 100644 --- a/war/src/main/webapp/scripts/hudson-behavior.js +++ b/war/src/main/webapp/scripts/hudson-behavior.js @@ -996,7 +996,13 @@ function renderOnDemand(e, callback, noBehaviour) { if (!e || !e.classList.contains("render-on-demand")) { return; } - var proxy = eval(e.getAttribute("proxy")); + + let proxyMethod = e.getAttribute("data-proxy-method"); + let proxyUrl = e.getAttribute("data-proxy-url"); + let proxyCrumb = e.getAttribute("data-proxy-crumb"); + let proxyUrlNames = e.getAttribute("data-proxy-url-names").split(","); + + var proxy = window[proxyMethod](proxyUrl, proxyCrumb, proxyUrlNames); proxy.render(function (t) { var contextTagName = e.parentNode.tagName; var c;