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; } }