From c399701421879e09a6d1e0fbb420ea7e181d89ce Mon Sep 17 00:00:00 2001 From: Josh Soref Date: Fri, 8 Mar 2019 14:19:15 -0500 Subject: [PATCH 1/8] Revert switching to array utils This incorrect change was introduced as part of #3913 --- core/src/main/java/jenkins/util/TreeString.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/core/src/main/java/jenkins/util/TreeString.java b/core/src/main/java/jenkins/util/TreeString.java index 09ea85a93e0b..6d5488914400 100644 --- a/core/src/main/java/jenkins/util/TreeString.java +++ b/core/src/main/java/jenkins/util/TreeString.java @@ -26,7 +26,6 @@ import java.io.Serializable; import java.util.Map; -import org.apache.commons.lang.ArrayUtils; import org.apache.commons.lang.StringUtils; import com.thoughtworks.xstream.XStream; @@ -125,7 +124,9 @@ public boolean equals(final Object rhs) { public int hashCode() { int h = parent == null ? 0 : parent.hashCode(); - h = 31 * h + ArrayUtils.hashCode(label); + for (char c : label) { + h = 31 * h + c; + } assert toString().hashCode() == h; return h; From f5a8e8d8a36a6f483232a2de988ace265c4ed786 Mon Sep 17 00:00:00 2001 From: Jesse Glick Date: Mon, 29 Oct 2018 09:52:34 -0400 Subject: [PATCH 2/8] Noting JEP-210 impact on ConsoleNote encoding as suggested in https://github.com/jenkinsci/workflow-durable-task-step-plugin/pull/80#discussion_r228612840. (cherry picked from commit 8d73038a27420fdee0f5b4f97b47229c243345e8) --- .../main/java/hudson/console/ConsoleLogFilter.java | 6 ++++++ core/src/main/java/hudson/console/ConsoleNote.java | 11 +++++++++++ 2 files changed, 17 insertions(+) diff --git a/core/src/main/java/hudson/console/ConsoleLogFilter.java b/core/src/main/java/hudson/console/ConsoleLogFilter.java index ec1b0c05920c..99fe50eca374 100644 --- a/core/src/main/java/hudson/console/ConsoleLogFilter.java +++ b/core/src/main/java/hudson/console/ConsoleLogFilter.java @@ -36,6 +36,8 @@ import javax.annotation.Nonnull; import java.io.IOException; import java.io.OutputStream; +import java.io.Serializable; +import jenkins.util.JenkinsJVM; /** * A hook to allow filtering of information that is written to the console log. @@ -43,6 +45,10 @@ * direct access to the underlying {@link OutputStream} so it's possible to suppress * data, which isn't possible from the other interfaces. * ({@link ArgumentListBuilder#add(String, boolean)} is a simpler way to suppress a single password.) + *

Implementations which are {@link Serializable} may be sent to an agent JVM for processing. + * In particular, this happens under JEP-210. + * In this case, the implementation should not assume that {@link JenkinsJVM#isJenkinsJVM}, + * and if generating {@link ConsoleNote}s will need to encode them on the master side first. * @author dty * @since 1.383 * @see BuildWrapper#decorateLogger diff --git a/core/src/main/java/hudson/console/ConsoleNote.java b/core/src/main/java/hudson/console/ConsoleNote.java index 1dec066e56d1..2b646cab4b50 100644 --- a/core/src/main/java/hudson/console/ConsoleNote.java +++ b/core/src/main/java/hudson/console/ConsoleNote.java @@ -110,6 +110,17 @@ * is also important, although {@link ConsoleNote}s that failed to deserialize will be simply ignored, so the * worst thing that can happen is that you just lose some notes. * + *

+ * Note that {@link #encode}, {@link #encodeTo(OutputStream)}, and {@link #encodeTo(Writer)} + * should be called on the Jenkins master. + * If called from an agent JVM, a signature will be missing and so as per + * SECURITY-382 + * the console note will be ignored. + * This may happen, in particular, if the note was generated by a {@link ConsoleLogFilter} sent to the agent. + * Alternative solutions include using a {@link ConsoleAnnotatorFactory} where practical; + * or generating the encoded form of the note on the master side and sending it to the agent, + * for example by saving that form as instance fields in a {@link ConsoleLogFilter} implementation. + * *

Behaviour, JavaScript, and CSS

*

* {@link ConsoleNote} can have associated {@code script.js} and {@code style.css} (put them From f25a7d7c0ffb30695f14a0eaaf04046b7c85b4bc Mon Sep 17 00:00:00 2001 From: Jesse Glick Date: Fri, 26 Oct 2018 14:58:57 -0400 Subject: [PATCH 3/8] Introduced LineTransformationOutputStream.Delegating. MavenConsoleAnnotator had neglected to implement flush properly. (cherry picked from commit 4c6be45e6f1aed49f9eae87be3a98086267fd436) --- .../ConsoleAnnotationOutputStream.java | 2 +- .../LineTransformationOutputStream.java | 30 ++++++++++++++++++- .../console/PlainTextConsoleOutputStream.java | 19 ++---------- .../tasks/_maven/MavenConsoleAnnotator.java | 10 ++----- .../hudson/console/ConsoleLogFilterTest.java | 4 +-- .../jenkins/tasks/SimpleBuildWrapperTest.java | 6 ++-- 6 files changed, 39 insertions(+), 32 deletions(-) diff --git a/core/src/main/java/hudson/console/ConsoleAnnotationOutputStream.java b/core/src/main/java/hudson/console/ConsoleAnnotationOutputStream.java index 5c731a17ddc5..00a84b6f4612 100644 --- a/core/src/main/java/hudson/console/ConsoleAnnotationOutputStream.java +++ b/core/src/main/java/hudson/console/ConsoleAnnotationOutputStream.java @@ -49,7 +49,7 @@ * @since 1.349 */ public class ConsoleAnnotationOutputStream extends LineTransformationOutputStream { - private final Writer out; + private final Writer out; // not an OutputStream so cannot use LineTransformationOutputStream.Delegating private final T context; private ConsoleAnnotator ann; diff --git a/core/src/main/java/hudson/console/LineTransformationOutputStream.java b/core/src/main/java/hudson/console/LineTransformationOutputStream.java index 95bb0baf6989..37f760df2088 100644 --- a/core/src/main/java/hudson/console/LineTransformationOutputStream.java +++ b/core/src/main/java/hudson/console/LineTransformationOutputStream.java @@ -32,7 +32,7 @@ * Filtering {@link OutputStream} that buffers text by line, so that the derived class * can perform some manipulation based on the contents of the whole line. * - * TODO: Mac is supposed to be CR-only. This class needs to handle that. + *

Subclass {@link Delegating} in the typical case that you are decorating an underlying stream. * * @author Kohsuke Kawaguchi * @since 1.349 @@ -110,4 +110,32 @@ protected String trimEOL(String line) { } private static final int LF = 0x0A; + + /** + * Convenience subclass for cases where you wish to process lines being sent to an underlying stream. + * {@link #eol} will typically {@link OutputStream#write(byte[], int, int)} to {@link #out}. + * Flushing or closing the decorated stream will behave properly. + * @since FIXME + */ + public static abstract class Delegating extends LineTransformationOutputStream { + + protected final OutputStream out; + + protected Delegating(OutputStream out) { + this.out = out; + } + + @Override + public void flush() throws IOException { + out.flush(); + } + + @Override + public void close() throws IOException { + super.close(); + out.close(); + } + + } + } diff --git a/core/src/main/java/hudson/console/PlainTextConsoleOutputStream.java b/core/src/main/java/hudson/console/PlainTextConsoleOutputStream.java index ee02d177d48c..6eb4c9f4eab6 100644 --- a/core/src/main/java/hudson/console/PlainTextConsoleOutputStream.java +++ b/core/src/main/java/hudson/console/PlainTextConsoleOutputStream.java @@ -28,21 +28,19 @@ import java.io.DataInputStream; import java.io.IOException; import java.io.OutputStream; -import java.util.logging.Logger; /** * Filters out console notes. * * @author Kohsuke Kawaguchi */ -public class PlainTextConsoleOutputStream extends LineTransformationOutputStream { - private final OutputStream out; +public class PlainTextConsoleOutputStream extends LineTransformationOutputStream.Delegating { /** * */ public PlainTextConsoleOutputStream(OutputStream out) { - this.out = out; + super(out); } /** @@ -77,17 +75,4 @@ protected void eol(byte[] in, int sz) throws IOException { out.write(in,written,sz-written); } - @Override - public void flush() throws IOException { - out.flush(); - } - - @Override - public void close() throws IOException { - super.close(); - out.close(); - } - - - private static final Logger LOGGER = Logger.getLogger(PlainTextConsoleOutputStream.class.getName()); } diff --git a/core/src/main/java/hudson/tasks/_maven/MavenConsoleAnnotator.java b/core/src/main/java/hudson/tasks/_maven/MavenConsoleAnnotator.java index dfbe86cbc26f..f3f2be19c241 100644 --- a/core/src/main/java/hudson/tasks/_maven/MavenConsoleAnnotator.java +++ b/core/src/main/java/hudson/tasks/_maven/MavenConsoleAnnotator.java @@ -36,12 +36,11 @@ * * @author Kohsuke Kawaguchi */ -public class MavenConsoleAnnotator extends LineTransformationOutputStream { - private final OutputStream out; +public class MavenConsoleAnnotator extends LineTransformationOutputStream.Delegating { private final Charset charset; public MavenConsoleAnnotator(OutputStream out, Charset charset) { - this.out = out; + super(out); this.charset = charset; } @@ -75,9 +74,4 @@ protected void eol(byte[] b, int len) throws IOException { out.write(b,0,len); } - @Override - public void close() throws IOException { - super.close(); - out.close(); - } } diff --git a/test/src/test/java/hudson/console/ConsoleLogFilterTest.java b/test/src/test/java/hudson/console/ConsoleLogFilterTest.java index b75a43228d02..175f7d9624a7 100644 --- a/test/src/test/java/hudson/console/ConsoleLogFilterTest.java +++ b/test/src/test/java/hudson/console/ConsoleLogFilterTest.java @@ -38,8 +38,8 @@ public OutputStream decorateLogger(Run build, OutputStream logger) throws IOExce } @Override - public OutputStream decorateLogger(final Computer c, final OutputStream out) throws IOException, InterruptedException { - return new LineTransformationOutputStream() { + public OutputStream decorateLogger(final Computer c, OutputStream out) throws IOException, InterruptedException { + return new LineTransformationOutputStream.Delegating(out) { @Override protected void eol(byte[] b, int len) throws IOException { out.write(("[["+c.getName()+"]] ").getBytes()); diff --git a/test/src/test/java/jenkins/tasks/SimpleBuildWrapperTest.java b/test/src/test/java/jenkins/tasks/SimpleBuildWrapperTest.java index a967f330761f..57cda69be7d3 100644 --- a/test/src/test/java/jenkins/tasks/SimpleBuildWrapperTest.java +++ b/test/src/test/java/jenkins/tasks/SimpleBuildWrapperTest.java @@ -187,10 +187,10 @@ public static class WrapperWithLogger extends SimpleBuildWrapper { private static class UpcaseFilter extends ConsoleLogFilter implements Serializable { private static final long serialVersionUID = 1; @SuppressWarnings("rawtypes") // inherited - @Override public OutputStream decorateLogger(AbstractBuild _ignore, final OutputStream logger) throws IOException, InterruptedException { - return new LineTransformationOutputStream() { + @Override public OutputStream decorateLogger(AbstractBuild _ignore, OutputStream logger) throws IOException, InterruptedException { + return new LineTransformationOutputStream.Delegating(logger) { @Override protected void eol(byte[] b, int len) throws IOException { - logger.write(new String(b, 0, len).toUpperCase(Locale.ROOT).getBytes()); + out.write(new String(b, 0, len).toUpperCase(Locale.ROOT).getBytes()); } }; } From 0260547d3809b557a350f39e051b5e2564eb917b Mon Sep 17 00:00:00 2001 From: Gavin Mogan Date: Tue, 2 Apr 2019 11:06:36 -0700 Subject: [PATCH 4/8] JENKINS-56856 - URL Encode data before submitting it to the endpoint just incase there's bad data in it --- war/src/main/js/util/jenkins.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/war/src/main/js/util/jenkins.js b/war/src/main/js/util/jenkins.js index 43c031fe3314..155374992b4e 100644 --- a/war/src/main/js/util/jenkins.js +++ b/war/src/main/js/util/jenkins.js @@ -284,8 +284,11 @@ exports.buildFormPost = function($form) { var wnd = exports.getWindow($form); var form = $form[0]; if(wnd.buildFormTree(form)) { - return $form.serialize() + - '&core:apply=&Submit=Save&json=' + $form.find('input[name=json]').val(); + return $form.serialize() + jquery.param({ + 'core:apply': '', + 'Submit': 'Save', + 'json': $form.find('input[name=json]').val() + }); } return ''; }; From c12c02b5ca5427d2d349f62c830b4ae0647f024b Mon Sep 17 00:00:00 2001 From: Matt Sicker Date: Thu, 4 Apr 2019 11:17:25 -0500 Subject: [PATCH 5/8] Add f:secretTextarea UI analogous to f:password Just as f:password is provided for secrets instead of f:textinput, this f:secretTextarea is provided for secrets instead of f:textarea where said secrets are multiline in nature. While this component does not provide any way to view an existing secret, it provides UIs to add and replace secrets. Signed-off-by: Matt Sicker --- .../resources/lib/form/secretTextarea.jelly | 109 +++++++++++ .../lib/form/secretTextarea.properties | 29 +++ .../lib/form/secretTextarea/secret.css | 77 ++++++++ .../lib/form/secretTextarea/secret.js | 77 ++++++++ .../java/lib/form/SecretTextareaTest.java | 175 ++++++++++++++++++ .../TestBuilder/config.jelly | 33 ++++ 6 files changed, 500 insertions(+) create mode 100644 core/src/main/resources/lib/form/secretTextarea.jelly create mode 100644 core/src/main/resources/lib/form/secretTextarea.properties create mode 100644 core/src/main/resources/lib/form/secretTextarea/secret.css create mode 100644 core/src/main/resources/lib/form/secretTextarea/secret.js create mode 100644 test/src/test/java/lib/form/SecretTextareaTest.java create mode 100644 test/src/test/resources/lib/form/SecretTextareaTest/TestBuilder/config.jelly diff --git a/core/src/main/resources/lib/form/secretTextarea.jelly b/core/src/main/resources/lib/form/secretTextarea.jelly new file mode 100644 index 000000000000..5d476d0c0f86 --- /dev/null +++ b/core/src/main/resources/lib/form/secretTextarea.jelly @@ -0,0 +1,109 @@ + + + + + for editing multi-line secrets. + +Example usage: + + + + + + + + + + + + + ]]> + + Used for databinding. Must be compatible with hudson.util.Secret for round-trip ciphertext. + + + Name to use for form input name. Calculated from @field by default. + + + Value of the secret. Calculated from instance[@field] by default. + This value must be of type hudson.util.Secret. + The value will be encrypted when sent to the client if the client has Item.CONFIGURE permissions. + + + Placeholder text for input field when displayed. + + + + + + + + + + + +

+
+
+ + + ${%NoStoredValue} + + + + + + + + + + + ${%Concealed} + + + +
+
+ +
+
+
+ + diff --git a/core/src/main/resources/lib/form/secretTextarea.properties b/core/src/main/resources/lib/form/secretTextarea.properties new file mode 100644 index 000000000000..ebd0491095e4 --- /dev/null +++ b/core/src/main/resources/lib/form/secretTextarea.properties @@ -0,0 +1,29 @@ +# +# The MIT License +# +# Copyright (c) 2019 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. +# + +Add=Add +Replace=Replace +EnterSecret=Enter New Secret Below +Concealed=Concealed for Confidentiality +NoStoredValue=No Stored Value diff --git a/core/src/main/resources/lib/form/secretTextarea/secret.css b/core/src/main/resources/lib/form/secretTextarea/secret.css new file mode 100644 index 000000000000..4960c64aed6f --- /dev/null +++ b/core/src/main/resources/lib/form/secretTextarea/secret.css @@ -0,0 +1,77 @@ +/* + * The MIT License + * + * Copyright (c) 2019 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. + */ + +.secret-header { + border: 1px solid #ccc; + border-radius: 3px; + background: #f9f9f9; + display: flex; + justify-content: space-around; +} + +.secret-header:not(:only-child) { + border-bottom-left-radius: 0; + border-bottom-right-radius: 0; +} + +.secret-header > div { + flex-grow: 1; + display: inline-flex; + align-items: center; + padding: 1.5em 1.75em; +} + +.secret-legend > svg { + margin-right: 1em; +} + +.secret-update { + justify-content: flex-end; +} + +.secret-input { + border: solid 1px #ccc; + border-top: none; + border-radius: 0 0 3px 3px; +} + +.secret-input textarea { + width: 100%; + font-family: monospace; + border: none; + padding: 1em; +} + +.secret input[type='button'] { + background: #4b99d0; + color: #fff; + border-radius: 4px; + border: none; + padding: 1em 2em; +} + +.secret input[type='button']:hover { + background: #5092be; + cursor: pointer; +} diff --git a/core/src/main/resources/lib/form/secretTextarea/secret.js b/core/src/main/resources/lib/form/secretTextarea/secret.js new file mode 100644 index 000000000000..c2b312144075 --- /dev/null +++ b/core/src/main/resources/lib/form/secretTextarea/secret.js @@ -0,0 +1,77 @@ +/* + * The MIT License + * + * Copyright (c) 2019 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. + */ + +Behaviour.specify('.secret', 'secret-button', 0, function (e) { + var secretUpdateBtn = e.querySelector('.secret-update-btn'); + if (secretUpdateBtn === null) return; + + var id = 'secret-' + (iota++); + var name = e.getAttribute('data-name'); + var placeholder = e.getAttribute('data-placeholder'); + var prompt = e.getAttribute('data-prompt'); + + var appendSecretInput = function () { + var textarea = document.createElement('textarea'); + textarea.setAttribute('id', id); + textarea.setAttribute('name', name); + if (placeholder !== null && placeholder !== '') { + textarea.setAttribute('placeholder', placeholder); + } + var secretInput = document.createElement('div'); + secretInput.setAttribute('class', 'secret-input'); + secretInput.appendChild(textarea); + e.appendChild(secretInput); + } + + var clearSecretValue = function () { + var secretValue = e.querySelector('input[type="hidden"]'); + if (secretValue !== null) { + secretValue.parentNode.removeChild(secretValue); + } + } + + var replaceUpdateButton = function () { + var secretLabel = document.createElement('label'); + secretLabel.setAttribute('for', id); + secretLabel.appendChild(document.createTextNode(prompt)); + secretUpdateBtn.parentNode.replaceChild(secretLabel, secretUpdateBtn); + } + + var removeSecretLegendLabel = function () { + var secretLegend = e.querySelector('.secret-legend'); + var secretLegendText = secretLegend.querySelector('span'); + if (secretLegendText !== null) { + secretLegend.removeChild(secretLegendText); + } + } + + secretUpdateBtn.onclick = function () { + appendSecretInput(); + clearSecretValue(); + replaceUpdateButton(); + removeSecretLegendLabel(); + // fix UI bug when DOM changes + Event.fire(window, 'jenkins:bottom-sticker-adjust'); + }; +}); diff --git a/test/src/test/java/lib/form/SecretTextareaTest.java b/test/src/test/java/lib/form/SecretTextareaTest.java new file mode 100644 index 000000000000..93cc37bda57d --- /dev/null +++ b/test/src/test/java/lib/form/SecretTextareaTest.java @@ -0,0 +1,175 @@ +/* + * The MIT License + * + * Copyright (c) 2019 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 lib.form; + +import com.gargoylesoftware.htmlunit.html.HtmlForm; +import com.gargoylesoftware.htmlunit.html.HtmlHiddenInput; +import com.gargoylesoftware.htmlunit.html.HtmlTextInput; +import hudson.model.AbstractProject; +import hudson.model.Project; +import hudson.tasks.BuildStepDescriptor; +import hudson.tasks.Builder; +import hudson.util.Secret; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.jvnet.hudson.test.JenkinsRule; +import org.jvnet.hudson.test.JenkinsRule.WebClient; +import org.jvnet.hudson.test.TestExtension; +import org.kohsuke.stapler.DataBoundConstructor; +import org.kohsuke.stapler.DataBoundSetter; +import org.xml.sax.SAXException; + +import java.io.IOException; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertNotNull; + +public class SecretTextareaTest { + + private Project project; + private WebClient wc; + + @Rule public JenkinsRule j = new JenkinsRule(); + + @Before + public void setUp() throws IOException { + project = j.createFreeStyleProject(); + project.getBuildersList().add(TestBuilder.newDefault()); + wc = j.createWebClient(); + } + + @Test + public void addEmptySecret() throws Exception { + j.configRoundtrip(project); + assertTestBuilderDataBoundEqual(TestBuilder.newDefault()); + } + + @Test + public void addSecret() throws Exception { + setProjectSecret("testValue"); + assertTestBuilderDataBoundEqual(TestBuilder.fromString("testValue")); + } + + @Test + public void addSecretAndUpdateDescription() throws Exception { + setProjectSecret("Original Value"); + assertTestBuilderDataBoundEqual(TestBuilder.fromString("Original Value")); + HtmlForm configForm = goToConfigForm(); + HtmlTextInput description = configForm.getInputByName("_.description"); + description.setText("New description"); + j.submit(configForm); + assertTestBuilderDataBoundEqual(TestBuilder.fromStringWithDescription("Original Value", "New description")); + } + + @Test + public void addSecretAndUpdateSecretWithEmptyValue() throws Exception { + setProjectSecret("First"); + assertTestBuilderDataBoundEqual(TestBuilder.fromString("First")); + HtmlForm configForm = goToConfigForm(); + String hiddenValue = getHiddenSecretValue(configForm); + assertNotNull(hiddenValue); + assertNotEquals("First", hiddenValue); + assertEquals("First", Secret.fromString(hiddenValue).getPlainText()); + clickSecretUpdateButton(configForm); + j.submit(configForm); + assertTestBuilderDataBoundEqual(TestBuilder.fromString("")); + } + + private void assertTestBuilderDataBoundEqual(TestBuilder other) throws Exception { + j.assertEqualDataBoundBeans(other, project.getBuildersList().get(TestBuilder.class)); + } + + private void setProjectSecret(String secret) throws Exception { + HtmlForm configForm = goToConfigForm(); + clickSecretUpdateButton(configForm); + configForm.getTextAreaByName("_.secret").setText(secret); + j.submit(configForm); + } + + private HtmlForm goToConfigForm() throws IOException, SAXException { + return wc.getPage(project, "configure").getFormByName("config"); + } + + private static void clickSecretUpdateButton(HtmlForm configForm) throws IOException { + configForm.getOneHtmlElementByAttribute("input", "class", "secret-update-btn").click(); + } + + private static String getHiddenSecretValue(HtmlForm configForm) { + HtmlHiddenInput hiddenSecret = configForm.getInputByName("_.secret"); + return hiddenSecret == null ? null : hiddenSecret.getValueAttribute(); + } + + public static class TestBuilder extends Builder { + private final Secret secret; + private String description = ""; + + private static TestBuilder newDefault() { + return new TestBuilder(null); + } + + private static TestBuilder fromString(String secret) { + return new TestBuilder(Secret.fromString(secret)); + } + + private static TestBuilder fromStringWithDescription(String secret, String description) { + TestBuilder b = fromString(secret); + b.setDescription(description); + return b; + } + + @DataBoundConstructor + public TestBuilder(Secret secret) { + this.secret = secret; + } + + public Secret getSecret() { + return secret; + } + + public String getDescription() { + return description; + } + + @DataBoundSetter + public void setDescription(String description) { + this.description = description; + } + + @TestExtension + public static class DescriptorImpl extends BuildStepDescriptor { + @Override + public String getDisplayName() { + return "Test Secret"; + } + + @Override + public boolean isApplicable(Class jobType) { + return true; + } + } + } +} \ No newline at end of file diff --git a/test/src/test/resources/lib/form/SecretTextareaTest/TestBuilder/config.jelly b/test/src/test/resources/lib/form/SecretTextareaTest/TestBuilder/config.jelly new file mode 100644 index 000000000000..dfd396de7dda --- /dev/null +++ b/test/src/test/resources/lib/form/SecretTextareaTest/TestBuilder/config.jelly @@ -0,0 +1,33 @@ + + + + + + + + + + + From ec245d1e73c55aed41cc27b478943f8313386fa1 Mon Sep 17 00:00:00 2001 From: Gavin Mogan Date: Thu, 4 Apr 2019 11:42:15 -0700 Subject: [PATCH 6/8] merge the two form parameters with a & instead of just concatinating them --- war/src/main/js/util/jenkins.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/war/src/main/js/util/jenkins.js b/war/src/main/js/util/jenkins.js index 155374992b4e..cc962deff460 100644 --- a/war/src/main/js/util/jenkins.js +++ b/war/src/main/js/util/jenkins.js @@ -284,7 +284,7 @@ exports.buildFormPost = function($form) { var wnd = exports.getWindow($form); var form = $form[0]; if(wnd.buildFormTree(form)) { - return $form.serialize() + jquery.param({ + return $form.serialize() + "&" + jquery.param({ 'core:apply': '', 'Submit': 'Save', 'json': $form.find('input[name=json]').val() From 38faed357c7c5c3948164a70e71c39d48fd53c96 Mon Sep 17 00:00:00 2001 From: Kohsuke Kawaguchi Date: Sun, 7 Apr 2019 01:32:46 -0700 Subject: [PATCH 7/8] [maven-release-plugin] prepare release jenkins-2.171 --- cli/pom.xml | 2 +- core/pom.xml | 2 +- pom.xml | 4 ++-- test-jdk8/pom.xml | 2 +- test-pom/pom.xml | 2 +- test/pom.xml | 2 +- war/pom.xml | 2 +- 7 files changed, 8 insertions(+), 8 deletions(-) diff --git a/cli/pom.xml b/cli/pom.xml index b02df3123ec1..ca82cffe9ed3 100644 --- a/cli/pom.xml +++ b/cli/pom.xml @@ -5,7 +5,7 @@ org.jenkins-ci.main jenkins-parent - ${revision}${changelist} + 2.171 cli diff --git a/core/pom.xml b/core/pom.xml index 388f0a793073..28e72298d37e 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -29,7 +29,7 @@ THE SOFTWARE. org.jenkins-ci.main jenkins-parent - ${revision}${changelist} + 2.171 jenkins-core diff --git a/pom.xml b/pom.xml index 6e9b1e1b1e21..7d121bd65146 100644 --- a/pom.xml +++ b/pom.xml @@ -34,7 +34,7 @@ THE SOFTWARE. org.jenkins-ci.main jenkins-parent - ${revision}${changelist} + 2.171 pom Jenkins main module @@ -60,7 +60,7 @@ THE SOFTWARE. scm:git:git://github.com/jenkinsci/jenkins.git scm:git:ssh://git@github.com/jenkinsci/jenkins.git https://github.com/jenkinsci/jenkins - ${scmTag} + jenkins-2.171 diff --git a/test-jdk8/pom.xml b/test-jdk8/pom.xml index 70c4870837b6..232004c136b7 100644 --- a/test-jdk8/pom.xml +++ b/test-jdk8/pom.xml @@ -28,7 +28,7 @@ THE SOFTWARE. org.jenkins-ci.main jenkins-test-parent - ${revision}${changelist} + 2.171 ../test-pom diff --git a/test-pom/pom.xml b/test-pom/pom.xml index c6514ccfdb0e..433305fa14d2 100644 --- a/test-pom/pom.xml +++ b/test-pom/pom.xml @@ -28,7 +28,7 @@ THE SOFTWARE. org.jenkins-ci.main jenkins-parent - ${revision}${changelist} + 2.171 jenkins-test-parent diff --git a/test/pom.xml b/test/pom.xml index 4c5abecc6f4f..2b0940fac9b3 100644 --- a/test/pom.xml +++ b/test/pom.xml @@ -28,7 +28,7 @@ THE SOFTWARE. org.jenkins-ci.main jenkins-test-parent - ${revision}${changelist} + 2.171 ../test-pom diff --git a/war/pom.xml b/war/pom.xml index bc8f98da189e..ad55f5a8402d 100644 --- a/war/pom.xml +++ b/war/pom.xml @@ -28,7 +28,7 @@ THE SOFTWARE. org.jenkins-ci.main jenkins-parent - ${revision}${changelist} + 2.171 jenkins-war From 32b4bfb3a23bd186f4f9486185c031c7f406b858 Mon Sep 17 00:00:00 2001 From: Kohsuke Kawaguchi Date: Sun, 7 Apr 2019 01:32:54 -0700 Subject: [PATCH 8/8] [maven-release-plugin] prepare for next development iteration --- cli/pom.xml | 2 +- core/pom.xml | 2 +- pom.xml | 6 +++--- test-jdk8/pom.xml | 2 +- test-pom/pom.xml | 2 +- test/pom.xml | 2 +- war/pom.xml | 2 +- 7 files changed, 9 insertions(+), 9 deletions(-) diff --git a/cli/pom.xml b/cli/pom.xml index ca82cffe9ed3..b02df3123ec1 100644 --- a/cli/pom.xml +++ b/cli/pom.xml @@ -5,7 +5,7 @@ org.jenkins-ci.main jenkins-parent - 2.171 + ${revision}${changelist} cli diff --git a/core/pom.xml b/core/pom.xml index 28e72298d37e..388f0a793073 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -29,7 +29,7 @@ THE SOFTWARE. org.jenkins-ci.main jenkins-parent - 2.171 + ${revision}${changelist} jenkins-core diff --git a/pom.xml b/pom.xml index 7d121bd65146..b2ba0dae7f16 100644 --- a/pom.xml +++ b/pom.xml @@ -34,7 +34,7 @@ THE SOFTWARE. org.jenkins-ci.main jenkins-parent - 2.171 + ${revision}${changelist} pom Jenkins main module @@ -60,7 +60,7 @@ THE SOFTWARE. scm:git:git://github.com/jenkinsci/jenkins.git scm:git:ssh://git@github.com/jenkinsci/jenkins.git https://github.com/jenkinsci/jenkins - jenkins-2.171 + ${scmTag} @@ -76,7 +76,7 @@ THE SOFTWARE. - 2.171 + 2.172 -SNAPSHOT