Skip to content

Commit 90e693b

Browse files
daniel-beckoleg-nenashev
authored andcommitted
Hide password form fields by default (#3991)
* Hide password form fields by default * Trying to bypass enforced autocompletion by just having a test field at first Something like onfocus didn't work, you'd tab through form elements and unless you filled in the user name, changing the form field to password would cause it to autocomplete. It looks like, at least in Mac/Firefox, going from plain text to password in the 'oninput' event handler works. The plain text is revealed neither with typing nor pasting. * Update core/src/main/resources/lib/form/password.jelly Co-Authored-By: daniel-beck <[email protected]> * Use previously defined value * Make new password form fields opt-out * Add support for redacting form secrets in new password field * Have a password value pre-set in Jelly * Fix method * Fix test by clicking on the button to change the password * Forgot period separator between class and property name
1 parent 5f413e5 commit 90e693b

File tree

9 files changed

+177
-14
lines changed

9 files changed

+177
-14
lines changed

core/src/main/java/hudson/Functions.java

+5
Original file line numberDiff line numberDiff line change
@@ -386,6 +386,11 @@ public static Area getScreenResolution() {
386386
return null;
387387
}
388388

389+
@Restricted(NoExternalUse.class)
390+
public static boolean useHidingPasswordFields() {
391+
return SystemProperties.getBoolean(Functions.class.getName() + ".hidingPasswordFields", true);
392+
}
393+
389394
/**
390395
* URL decomposed for easier computation of relevant URLs.
391396
*

core/src/main/resources/lib/form/password.jelly

+69-8
Original file line numberDiff line numberDiff line change
@@ -61,12 +61,73 @@ THE SOFTWARE.
6161
</st:attribute>
6262
</st:documentation>
6363
<f:prepareDatabinding />
64-
<m:input xmlns:m="jelly:hudson.util.jelly.MorphTagLibrary"
65-
class="setting-input ${attrs.checkUrl!=null?'validated ':''}${attrs.clazz}"
66-
name ="${attrs.name ?: '_.'+attrs.field}"
67-
value="${h.getPasswordValue(attrs.value ?: instance[attrs.field])}"
68-
type="password"
69-
checkMethod="post"
70-
ATTRIBUTES="${attrs}" EXCEPT="field clazz value" />
71-
<!-- TODO consider customizedFields -->
64+
<j:choose>
65+
<j:when test="${h.useHidingPasswordFields()}">
66+
<j:set var="value" value="${h.getPasswordValue(attrs.value ?: instance[attrs.field])}"/>
67+
<j:choose>
68+
<j:when test="${ value != null }">
69+
<st:adjunct includes="lib.form.password.password"/>
70+
<div class="hidden-password">
71+
<m:input xmlns:m="jelly:hudson.util.jelly.MorphTagLibrary"
72+
class="complex-password-field hidden-password-field setting-input ${attrs.checkUrl!=null?'validated ':''}${attrs.clazz}"
73+
name="${attrs.name ?: '_.'+attrs.field}"
74+
value="${value}"
75+
type="hidden"
76+
checkMethod="post"
77+
ATTRIBUTES="${attrs}" EXCEPT="field clazz value" />
78+
<div class="hidden-password-placeholder">
79+
<div class="hidden-password-legend">
80+
<svg width="20px" height="25px" viewBox="0 0 25 32" version="1.1" xmlns="http://www.w3.org/2000/svg">
81+
<!--
82+
Based on Material Design.
83+
84+
Licensed under the Apache License, Version 2.0 (the "License");
85+
you may not use this file except in compliance with the License.
86+
You may obtain a copy of the License at
87+
88+
http://www.apache.org/licenses/LICENSE-2.0
89+
90+
Unless required by applicable law or agreed to in writing, software
91+
distributed under the License is distributed on an "AS IS" BASIS,
92+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
93+
See the License for the specific language governing permissions and
94+
limitations under the License.
95+
-->
96+
<g stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
97+
<g transform="translate(-504.000000, -199.000000)" fill="#788594">
98+
<path d="M520.914667,209.666667 L511.466667,209.666667 L511.466667,206.619333 C511.466667,204.014 513.584667,201.895333 516.190667,201.895333 C518.796667,201.895333 520.914667,204.014 520.914667,206.619333 L520.914667,209.666667 Z M516.190667,223.381333 C514.514,223.381333 513.143333,222.01 513.143333,220.333333 C513.143333,218.657333 514.514,217.286 516.190667,217.286 C517.867333,217.286 519.238,218.657333 519.238,220.333333 C519.238,222.01 517.867333,223.381333 516.190667,223.381333 Z M516.190667,199 C511.984667,199 508.571333,202.414 508.571333,206.619333 L508.571333,209.666667 L507.048,209.666667 C505.372,209.666667 504,211.038 504,212.714667 L504,227.952667 C504,229.628667 505.372,231 507.048,231 L525.334,231 C527.01,231 528.380667,229.628667 528.380667,227.952667 L528.380667,212.714667 C528.380667,211.038 527.01,209.666667 525.334,209.666667 L523.81,209.666667 L523.81,206.619333 C523.81,202.414 520.396667,199 516.190667,199 Z"/>
99+
</g>
100+
</g>
101+
</svg>
102+
<span>${%Concealed}</span>
103+
</div>
104+
<div class="hidden-password-update">
105+
<input type="button" class="hidden-password-update-btn" value="${%Change Password}"/>
106+
</div>
107+
</div>
108+
</div>
109+
</j:when>
110+
<j:otherwise>
111+
<m:input xmlns:m="jelly:hudson.util.jelly.MorphTagLibrary"
112+
class="complex-password-field setting-input ${attrs.checkUrl!=null?'validated ':''}${attrs.clazz}"
113+
name="${attrs.name ?: '_.'+attrs.field}"
114+
value="${value}"
115+
type="text"
116+
oninput="this.setAttribute('type', 'password'); return true;"
117+
checkMethod="post"
118+
ATTRIBUTES="${attrs}" EXCEPT="field clazz value" />
119+
<!-- TODO consider customizedFields -->
120+
</j:otherwise>
121+
</j:choose>
122+
</j:when>
123+
<j:otherwise>
124+
<m:input xmlns:m="jelly:hudson.util.jelly.MorphTagLibrary"
125+
class="setting-input ${attrs.checkUrl!=null?'validated ':''}${attrs.clazz}"
126+
name ="${attrs.name ?: '_.'+attrs.field}"
127+
value="${h.getPasswordValue(attrs.value ?: instance[attrs.field])}"
128+
type="password"
129+
checkMethod="post"
130+
ATTRIBUTES="${attrs}" EXCEPT="field clazz value" />
131+
</j:otherwise>
132+
</j:choose>
72133
</j:jelly>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
/*
2+
* The MIT License
3+
*
4+
* Copyright (c) 2019 CloudBees, Inc.
5+
*
6+
* Permission is hereby granted, free of charge, to any person obtaining a copy
7+
* of this software and associated documentation files (the "Software"), to deal
8+
* in the Software without restriction, including without limitation the rights
9+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10+
* copies of the Software, and to permit persons to whom the Software is
11+
* furnished to do so, subject to the following conditions:
12+
*
13+
* The above copyright notice and this permission notice shall be included in
14+
* all copies or substantial portions of the Software.
15+
*
16+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22+
* THE SOFTWARE.
23+
*/
24+
25+
.hidden-password-placeholder {
26+
display: flex;
27+
}
28+
29+
.hidden-password-legend {
30+
border: 1px solid #ccc;
31+
border-radius: 3px;
32+
background: #f9f9f9;
33+
flex-grow: 1;
34+
align-items: center;
35+
display: inline-flex;
36+
}
37+
38+
.hidden-password-legend > svg {
39+
margin-right: 1em;
40+
margin-left: 0.5em;
41+
}
42+
43+
.hidden-password input[type='button'] {
44+
background: #4b99d0;
45+
color: #fff;
46+
border-radius: 4px;
47+
border: none;
48+
padding: 7px;
49+
margin-left: 5px;
50+
}
51+
52+
.hidden-password input[type='button']:hover {
53+
background: #5092be;
54+
cursor: pointer;
55+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
/*
2+
* The MIT License
3+
*
4+
* Copyright (c) 2019 CloudBees, Inc.
5+
*
6+
* Permission is hereby granted, free of charge, to any person obtaining a copy
7+
* of this software and associated documentation files (the "Software"), to deal
8+
* in the Software without restriction, including without limitation the rights
9+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10+
* copies of the Software, and to permit persons to whom the Software is
11+
* furnished to do so, subject to the following conditions:
12+
*
13+
* The above copyright notice and this permission notice shall be included in
14+
* all copies or substantial portions of the Software.
15+
*
16+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22+
* THE SOFTWARE.
23+
*/
24+
25+
Behaviour.specify('.hidden-password', 'hidden-password-button', 0, function (e) {
26+
var secretUpdateBtn = e.querySelector('.hidden-password-update-btn');
27+
if (secretUpdateBtn === null) return;
28+
29+
var id = 'hidden-password-' + (iota++);
30+
31+
secretUpdateBtn.onclick = function () {
32+
e.querySelector('.hidden-password-field').setAttribute('type', 'password');
33+
e.querySelector('.hidden-password-placeholder').remove();
34+
secretUpdateBtn.remove();
35+
// fix UI bug when DOM changes
36+
Event.fire(window, 'jenkins:bottom-sticker-adjust');
37+
};
38+
});

test/src/test/java/hudson/model/PasswordParameterDefinitionTest.java

+2
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ public class PasswordParameterDefinitionTest {
8282

8383
// Another control case: anyone can enter a different value.
8484
HtmlForm form = wc.withBasicApiToken(dev).getPage(p, "build?delay=0sec").getFormByName("parameters");
85+
form.getElementsByAttribute("input", "class", "hidden-password-update-btn").get(0).click();
8586
HtmlPasswordInput input = form.getInputByName("value");
8687
input.setText("rumor");
8788
j.submit(form);
@@ -99,6 +100,7 @@ public class PasswordParameterDefinitionTest {
99100

100101
// Another control case: blank values.
101102
form = wc.withBasicApiToken(dev).getPage(p, "build?delay=0sec").getFormByName("parameters");
103+
form.getElementsByAttribute("input", "class", "hidden-password-update-btn").get(0).click();
102104
input = form.getInputByName("value");
103105
input.setText("");
104106
j.submit(form);

test/src/test/java/jenkins/security/RedactSecretJsonInErrorMessageSanitizerHtmlTest.java

+1-2
Original file line numberDiff line numberDiff line change
@@ -82,10 +82,9 @@ public void passwordsAreRedacted_andOtherStayTheSame() throws Exception {
8282

8383
String textLevelOne = "plain-2";
8484
String pwdLevelOneA = "secret-2";
85-
String pwdLevelOneB = "secret-3";
85+
String pwdLevelOneB = "pre-set secret"; // set in Jelly
8686
((HtmlInput) page.getElementById("text-level-one")).setValueAttribute(textLevelOne);
8787
((HtmlInput) page.getElementById("pwd-level-one-a")).setValueAttribute(pwdLevelOneA);
88-
((HtmlInput) page.getElementById("pwd-level-one-b")).setValueAttribute(pwdLevelOneB);
8988

9089
HtmlForm form = page.getFormByName("config");
9190
Page formSubmitPage = j.submit(form);

test/src/test/java/lib/form/PasswordTest.java

+3-3
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
import com.gargoylesoftware.htmlunit.Page;
2727
import com.gargoylesoftware.htmlunit.html.HtmlInput;
2828
import com.gargoylesoftware.htmlunit.html.HtmlPage;
29-
import com.gargoylesoftware.htmlunit.html.HtmlPasswordInput;
29+
import com.gargoylesoftware.htmlunit.html.HtmlTextInput;
3030
import hudson.cli.CopyJobCommand;
3131
import hudson.cli.GetJobCommand;
3232
import hudson.model.*;
@@ -186,8 +186,8 @@ public void testExposedCiphertext() throws Exception {
186186
@Issue("SECURITY-616")
187187
public void testCheckMethod() throws Exception {
188188
FreeStyleProject p = j.createFreeStyleProject("p");
189-
p.addProperty(new VulnerableProperty(Secret.fromString("")));
190-
HtmlPasswordInput field = j.createWebClient().getPage(p, "configure").getFormByName("config").getInputByName("_.secret");
189+
p.addProperty(new VulnerableProperty(null));
190+
HtmlTextInput field = j.createWebClient().getPage(p, "configure").getFormByName("config").getInputByName("_.secret");
191191
while (VulnerableProperty.DescriptorImpl.incomingURL == null) { // waitForBackgroundJavaScript does not work well
192192
Thread.sleep(100); // form validation of saved value
193193
}

test/src/test/resources/jenkins/security/RedactSecretJsonInErrorMessageSanitizerHtmlTest/TestPassword/index.jelly

+1-1
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ THE SOFTWARE.
4040
<f:password id="pwd-level-one-a"/>
4141
</f:entry>
4242
<f:entry field="pwd-level-one-b">
43-
<f:password id="pwd-level-one-b"/>
43+
<f:password id="pwd-level-one-b" value="pre-set secret" />
4444
</f:entry>
4545
</f:section>
4646

war/src/main/webapp/scripts/hudson-behavior.js

+3
Original file line numberDiff line numberDiff line change
@@ -2615,6 +2615,9 @@ function buildFormTree(form) {
26152615
default:
26162616
p = findParent(e);
26172617
addProperty(p, e.name, e.value);
2618+
if (e.hasClassName("complex-password-field")) {
2619+
addProperty(p, "$redact", shortenName(e.name));
2620+
}
26182621
break;
26192622
}
26202623
}

0 commit comments

Comments
 (0)