diff --git a/core/src/main/java/hudson/Functions.java b/core/src/main/java/hudson/Functions.java
index 3902d952f5..bbbe85a7f4 100644
--- a/core/src/main/java/hudson/Functions.java
+++ b/core/src/main/java/hudson/Functions.java
@@ -386,6 +386,11 @@ public class Functions {
return null;
}
+ @Restricted(NoExternalUse.class)
+ public static boolean useHidingPasswordFields() {
+ return SystemProperties.getBoolean(Functions.class.getName() + ".hidingPasswordFields", true);
+ }
+
/**
* URL decomposed for easier computation of relevant URLs.
*
diff --git a/core/src/main/resources/lib/form/password.jelly b/core/src/main/resources/lib/form/password.jelly
index eae77c32b0..caaf04e0ff 100644
--- a/core/src/main/resources/lib/form/password.jelly
+++ b/core/src/main/resources/lib/form/password.jelly
@@ -61,12 +61,73 @@ THE SOFTWARE.
-
-
+
+
+
+
+
+
+
+
+
+
+
+ ${%Concealed}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/core/src/main/resources/lib/form/password/password.css b/core/src/main/resources/lib/form/password/password.css
new file mode 100644
index 0000000000..250c55c7de
--- /dev/null
+++ b/core/src/main/resources/lib/form/password/password.css
@@ -0,0 +1,55 @@
+/*
+ * 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.
+ */
+
+.hidden-password-placeholder {
+ display: flex;
+}
+
+.hidden-password-legend {
+ border: 1px solid #ccc;
+ border-radius: 3px;
+ background: #f9f9f9;
+ flex-grow: 1;
+ align-items: center;
+ display: inline-flex;
+}
+
+.hidden-password-legend > svg {
+ margin-right: 1em;
+ margin-left: 0.5em;
+}
+
+.hidden-password input[type='button'] {
+ background: #4b99d0;
+ color: #fff;
+ border-radius: 4px;
+ border: none;
+ padding: 7px;
+ margin-left: 5px;
+}
+
+.hidden-password input[type='button']:hover {
+ background: #5092be;
+ cursor: pointer;
+}
diff --git a/core/src/main/resources/lib/form/password/password.js b/core/src/main/resources/lib/form/password/password.js
new file mode 100644
index 0000000000..e4f44cceff
--- /dev/null
+++ b/core/src/main/resources/lib/form/password/password.js
@@ -0,0 +1,38 @@
+/*
+ * 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('.hidden-password', 'hidden-password-button', 0, function (e) {
+ var secretUpdateBtn = e.querySelector('.hidden-password-update-btn');
+ if (secretUpdateBtn === null) return;
+
+ var id = 'hidden-password-' + (iota++);
+
+ secretUpdateBtn.onclick = function () {
+ e.querySelector('.hidden-password-field').setAttribute('type', 'password');
+ e.querySelector('.hidden-password-placeholder').remove();
+ secretUpdateBtn.remove();
+ // fix UI bug when DOM changes
+ Event.fire(window, 'jenkins:bottom-sticker-adjust');
+ };
+});
diff --git a/test/src/test/java/hudson/model/PasswordParameterDefinitionTest.java b/test/src/test/java/hudson/model/PasswordParameterDefinitionTest.java
index f67f630958..2d06d402d1 100644
--- a/test/src/test/java/hudson/model/PasswordParameterDefinitionTest.java
+++ b/test/src/test/java/hudson/model/PasswordParameterDefinitionTest.java
@@ -82,6 +82,7 @@ public class PasswordParameterDefinitionTest {
// Another control case: anyone can enter a different value.
HtmlForm form = wc.withBasicApiToken(dev).getPage(p, "build?delay=0sec").getFormByName("parameters");
+ form.getElementsByAttribute("input", "class", "hidden-password-update-btn").get(0).click();
HtmlPasswordInput input = form.getInputByName("value");
input.setText("rumor");
j.submit(form);
@@ -99,6 +100,7 @@ public class PasswordParameterDefinitionTest {
// Another control case: blank values.
form = wc.withBasicApiToken(dev).getPage(p, "build?delay=0sec").getFormByName("parameters");
+ form.getElementsByAttribute("input", "class", "hidden-password-update-btn").get(0).click();
input = form.getInputByName("value");
input.setText("");
j.submit(form);
diff --git a/test/src/test/java/jenkins/security/RedactSecretJsonInErrorMessageSanitizerHtmlTest.java b/test/src/test/java/jenkins/security/RedactSecretJsonInErrorMessageSanitizerHtmlTest.java
index 2ac05c82bf..9fae40fd01 100644
--- a/test/src/test/java/jenkins/security/RedactSecretJsonInErrorMessageSanitizerHtmlTest.java
+++ b/test/src/test/java/jenkins/security/RedactSecretJsonInErrorMessageSanitizerHtmlTest.java
@@ -82,10 +82,9 @@ public class RedactSecretJsonInErrorMessageSanitizerHtmlTest {
String textLevelOne = "plain-2";
String pwdLevelOneA = "secret-2";
- String pwdLevelOneB = "secret-3";
+ String pwdLevelOneB = "pre-set secret"; // set in Jelly
((HtmlInput) page.getElementById("text-level-one")).setValueAttribute(textLevelOne);
((HtmlInput) page.getElementById("pwd-level-one-a")).setValueAttribute(pwdLevelOneA);
- ((HtmlInput) page.getElementById("pwd-level-one-b")).setValueAttribute(pwdLevelOneB);
HtmlForm form = page.getFormByName("config");
Page formSubmitPage = j.submit(form);
diff --git a/test/src/test/java/lib/form/PasswordTest.java b/test/src/test/java/lib/form/PasswordTest.java
index 042f232340..f6449d7bee 100644
--- a/test/src/test/java/lib/form/PasswordTest.java
+++ b/test/src/test/java/lib/form/PasswordTest.java
@@ -26,7 +26,7 @@ package lib.form;
import com.gargoylesoftware.htmlunit.Page;
import com.gargoylesoftware.htmlunit.html.HtmlInput;
import com.gargoylesoftware.htmlunit.html.HtmlPage;
-import com.gargoylesoftware.htmlunit.html.HtmlPasswordInput;
+import com.gargoylesoftware.htmlunit.html.HtmlTextInput;
import hudson.cli.CopyJobCommand;
import hudson.cli.GetJobCommand;
import hudson.model.*;
@@ -186,8 +186,8 @@ public class PasswordTest {
@Issue("SECURITY-616")
public void testCheckMethod() throws Exception {
FreeStyleProject p = j.createFreeStyleProject("p");
- p.addProperty(new VulnerableProperty(Secret.fromString("")));
- HtmlPasswordInput field = j.createWebClient().getPage(p, "configure").getFormByName("config").getInputByName("_.secret");
+ p.addProperty(new VulnerableProperty(null));
+ HtmlTextInput field = j.createWebClient().getPage(p, "configure").getFormByName("config").getInputByName("_.secret");
while (VulnerableProperty.DescriptorImpl.incomingURL == null) { // waitForBackgroundJavaScript does not work well
Thread.sleep(100); // form validation of saved value
}
diff --git a/test/src/test/resources/jenkins/security/RedactSecretJsonInErrorMessageSanitizerHtmlTest/TestPassword/index.jelly b/test/src/test/resources/jenkins/security/RedactSecretJsonInErrorMessageSanitizerHtmlTest/TestPassword/index.jelly
index 151e1d9000..3e75324cac 100644
--- a/test/src/test/resources/jenkins/security/RedactSecretJsonInErrorMessageSanitizerHtmlTest/TestPassword/index.jelly
+++ b/test/src/test/resources/jenkins/security/RedactSecretJsonInErrorMessageSanitizerHtmlTest/TestPassword/index.jelly
@@ -40,7 +40,7 @@ THE SOFTWARE.
-
+
diff --git a/war/src/main/webapp/scripts/hudson-behavior.js b/war/src/main/webapp/scripts/hudson-behavior.js
index 04270d61a5..acbdb2e0e0 100644
--- a/war/src/main/webapp/scripts/hudson-behavior.js
+++ b/war/src/main/webapp/scripts/hudson-behavior.js
@@ -2615,6 +2615,9 @@ function buildFormTree(form) {
default:
p = findParent(e);
addProperty(p, e.name, e.value);
+ if (e.hasClassName("complex-password-field")) {
+ addProperty(p, "$redact", shortenName(e.name));
+ }
break;
}
}