Skip to content

Commit

Permalink
Merge pull request #9631 from NotMyFault/2nd-backporting-2.462.2
Browse files Browse the repository at this point in the history
Second backporting for 2.462.2
  • Loading branch information
basil authored Aug 20, 2024
2 parents 816597f + 85b9afe commit 91b4d80
Show file tree
Hide file tree
Showing 9 changed files with 198 additions and 37 deletions.
2 changes: 1 addition & 1 deletion bom/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ THE SOFTWARE.
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-framework-bom</artifactId>
<version>5.3.37</version>
<version>5.3.39</version>
<type>pom</type>
<scope>import</scope>
</dependency>
Expand Down
28 changes: 25 additions & 3 deletions core/src/main/resources/lib/form/secretTextarea.jelly
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
<!--
~ The MIT License
~
~ Copyright (c) 2019 CloudBees, Inc.
~ Copyright (c) 2019-2024 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
Expand Down Expand Up @@ -56,17 +56,32 @@ Example usage:
<st:attribute name="placeholder">
Placeholder text for input field when displayed.
</st:attribute>
<st:attribute name="checkUrl">
If specified, the value entered in this input field will be checked (via AJAX)
against this URL, and errors will be rendered under the text field.

If @field is specified, this will be inferred automatically,
which is the recommended approach.
</st:attribute>
<st:attribute name="checkMethod" use="optional" type="String">
Specify 'get' (must be lowercase) to change the HTTP method used for the AJAX requests to @checkUrl from a POST to a GET.
If any other value is specified then requests will use POST.
The historical default was GET and 'post' had to be specified to change that, but this was changed in Jenkins 2.285.
</st:attribute>

</st:documentation>

<f:prepareDatabinding/>
<j:set var="id" value="secretTextAreadId-${h.generateId()}" />

<j:set var="name" value="${attrs.name ?: '_.'+attrs.field}"/>
<j:set var="value" value="${h.getPasswordValue(attrs.value ?: instance[attrs.field])}"/>
<j:set var="addText" value="${%Add}"/>
<j:set var="replaceText" value="${%Replace}"/>
<j:set var="buttonText" value="${value == null ? addText : replaceText}"/>

<st:adjunct includes="lib.form.secretTextarea.secret"/>
<div class="secret" data-name="${name}" data-placeholder="${attrs.placeholder ?: ''}" data-prompt="${%EnterSecret}">
<div class="secret">
<div class="secret-header">
<div class="secret-legend">
<j:choose>
Expand All @@ -76,14 +91,21 @@ Example usage:
<j:otherwise>
<l:icon src="symbol-lock-closed" class="icon-md"/>
<span>${%Concealed}</span>
<input type="hidden" name="${name}" value="${value}"/>
</j:otherwise>
</j:choose>
</div>
<div class="secret-update">
<button type="button" class="secret-update-btn jenkins-button jenkins-button--primary">${buttonText}</button>
<label hidden="hidden" for="${id}">${%EnterSecret}</label>
</div>
</div>
<div class="secret-input">
<textarea id="${id}" hidden="hidden" name="${name}" placeholder="${placeholder}"
class="${attrs.checkUrl!=null?' jenkins-input validated':''}"
checkUrl="${attrs.checkUrl}" checkDependsOn="${attrs.checkDependsOn}" checkMethod="${attrs.checkMethod}">
<st:out value="${value}" />
</textarea>
</div>
</div>

</j:jelly>
6 changes: 5 additions & 1 deletion core/src/main/resources/lib/form/secretTextarea/secret.css
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/*
* The MIT License
*
* Copyright (c) 2019 CloudBees, Inc.
* Copyright (c) 2019-2024 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
Expand Down Expand Up @@ -65,3 +65,7 @@
border: none;
padding: 1em;
}

.secret * [hidden] {
display: none !important;
}
35 changes: 10 additions & 25 deletions core/src/main/resources/lib/form/secretTextarea/secret.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,36 +28,21 @@ Behaviour.specify(".secret", "secret-button", 0, function (e) {
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 unhideSecretInput = function () {
var textArea = e.querySelector('textarea[hidden="hidden"]');
textArea.removeAttribute("hidden");
};

var clearSecretValue = function () {
var secretValue = e.querySelector('input[type="hidden"]');
if (secretValue !== null) {
secretValue.parentNode.removeChild(secretValue);
}
var secretValue = e.querySelector("textarea");
secretValue.value = "";
};

var replaceUpdateButton = function () {
var secretLabel = document.createElement("label");
secretLabel.setAttribute("for", id);
secretLabel.appendChild(document.createTextNode(prompt));
secretUpdateBtn.parentNode.replaceChild(secretLabel, secretUpdateBtn);
secretUpdateBtn.setAttribute("hidden", "hidden");
// unhide the span text
var prompt = e.querySelector('label[hidden="hidden"]');
prompt.removeAttribute("hidden");
};

var removeSecretLegendLabel = function () {
Expand All @@ -69,7 +54,7 @@ Behaviour.specify(".secret", "secret-button", 0, function (e) {
};

secretUpdateBtn.onclick = function () {
appendSecretInput();
unhideSecretInput();
clearSecretValue();
replaceUpdateButton();
removeSecretLegendLabel();
Expand Down
2 changes: 1 addition & 1 deletion test/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -228,7 +228,7 @@ THE SOFTWARE.
<dependency>
<groupId>org.jenkins-ci.plugins</groupId>
<artifactId>junit</artifactId>
<version>1265.v65b_14fa_f12f0</version>
<version>1291.v60776881903c</version>
<scope>test</scope>
</dependency>
<dependency>
Expand Down
125 changes: 124 additions & 1 deletion test/src/test/java/hudson/util/FormFieldValidatorTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,19 @@
package hudson.util;


import static org.hamcrest.CoreMatchers.allOf;
import static org.hamcrest.CoreMatchers.containsString;
import static org.hamcrest.MatcherAssert.assertThat;

import hudson.model.AbstractProject;
import hudson.model.FreeStyleProject;
import hudson.tasks.BuildStepDescriptor;
import hudson.tasks.BuildStepMonitor;
import hudson.tasks.Builder;
import hudson.tasks.Publisher;
import hudson.tasks.Recorder;
import java.io.IOException;
import java.util.concurrent.atomic.AtomicInteger;
import org.htmlunit.ScriptResult;
import org.htmlunit.WebResponseListener;
import org.htmlunit.html.HtmlPage;
Expand All @@ -42,6 +48,7 @@
import org.jvnet.hudson.test.JenkinsRule;
import org.jvnet.hudson.test.TestExtension;
import org.kohsuke.stapler.DataBoundConstructor;
import org.kohsuke.stapler.DataBoundSetter;
import org.kohsuke.stapler.QueryParameter;
import org.xml.sax.SAXException;

Expand Down Expand Up @@ -134,7 +141,7 @@ public boolean isApplicable(Class<? extends AbstractProject> jobType) {
@Issue("JENKINS-3382")
public void negative() throws Exception {
BrokenFormValidatorBuilder.DescriptorImpl d = new BrokenFormValidatorBuilder.DescriptorImpl();
Publisher.all().add(d);
Recorder.all().add(d);
try {
FreeStyleProject p = j.createFreeStyleProject();
p.getPublishersList().add(new BrokenFormValidatorBuilder());
Expand All @@ -152,4 +159,120 @@ public void negative() throws Exception {
Publisher.all().remove(d);
}
}

@Issue("JENKINS-73404")
@Test
public void testValidationforComponents() throws Exception {
FreeStyleProject p = j.createFreeStyleProject();
p.getBuildersList().add(new ValidatingDescribable());
try (JenkinsRule.WebClient wc = j.createWebClient()) {
HtmlPage page = wc.getPage(p, "configure");
assertThat(page.asNormalizedText(), allOf(
containsString("FormValidation: Password (empty)"),
containsString("FormValidation: Password (populated)"),
containsString("FormValidation: Textarea"),
containsString("FormValidation: SecretTextarea (empty)"),
containsString("FormValidation: SecretTextarea (populated)")));

}

}

public static class ValidatingDescribable extends Builder {

private Secret emptyPassword;
// give the secret some data so that it is hidden and not a regular field!
private Secret populatedPassword = Secret.fromString("secret!");
private String textarea;
private Secret emptySecretTextarea;
private Secret populatedSecretTextarea = Secret.fromString("sensitive!");;

@DataBoundConstructor
public ValidatingDescribable() {
}

public Secret getEmptyPassword() {
return emptyPassword;
}

@DataBoundSetter
public void setEmptyPassword(Secret emptyPassword) {
this.emptyPassword = emptyPassword;
}

public Secret getPopulatedPassword() {
return populatedPassword;
}

@DataBoundSetter
public void setPopulatedPassword(Secret populatedPassword) {
this.populatedPassword = populatedPassword;
}

public String getTextarea() {
return textarea;
}

@DataBoundSetter
public void setTextarea(String textarea) {
this.textarea = textarea;
}

public Secret getEmptySecretTextarea() {
return emptySecretTextarea;
}

@DataBoundSetter
public void setEmptySecretTextarea(Secret emptySecretTextarea) {
this.emptySecretTextarea = emptySecretTextarea;
}

public Secret getPopulatedSecretTextarea() {
return populatedSecretTextarea;
}

@DataBoundSetter
public void setPopulatedSecretTextarea(Secret populatedSecretTextarea) {
this.populatedSecretTextarea = populatedSecretTextarea;
}

@TestExtension
public static class DescriptorImpl extends BuildStepDescriptor<Builder> {
// not used for the test class but useful for interactive debugging to check the validation has been called
AtomicInteger i = new AtomicInteger();

@Override
public String getDisplayName() {
return "Validation Testing";
}

public FormValidation doCheckEmptyPassword(@QueryParameter String value) {
return FormValidation.ok("FormValidation: Password (empty)" + i.getAndIncrement());
}

public FormValidation doCheckPopulatedPassword(@QueryParameter String value) {
return FormValidation.ok("FormValidation: Password (populated)" + i.getAndIncrement());
}

public FormValidation doCheckTextarea(@QueryParameter String value) {
return FormValidation.ok("FormValidation: Textarea" + i.getAndIncrement());
}

public FormValidation doCheckEmptySecretTextarea(@QueryParameter String value) {
return FormValidation.ok("FormValidation: SecretTextarea (empty)" + i.getAndIncrement());
}

public FormValidation doCheckPopulatedSecretTextarea(@QueryParameter String value) {
return FormValidation.ok("FormValidation: SecretTextarea (populated)" + i.getAndIncrement());
}

@Override
public boolean isApplicable(Class jobType) {
return true;
}

}

}

}
15 changes: 11 additions & 4 deletions test/src/test/java/lib/form/SecretTextareaTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@
import java.io.IOException;
import org.htmlunit.html.HtmlElement;
import org.htmlunit.html.HtmlForm;
import org.htmlunit.html.HtmlHiddenInput;
import org.htmlunit.html.HtmlTextArea;
import org.htmlunit.html.HtmlTextInput;
import org.junit.Before;
import org.junit.Rule;
Expand Down Expand Up @@ -120,8 +120,8 @@ private static void clickSecretUpdateButton(HtmlForm configForm) throws IOExcept
}

private static String getHiddenSecretValue(HtmlForm configForm) {
HtmlHiddenInput hiddenSecret = configForm.getInputByName("_.secret");
return hiddenSecret == null ? null : hiddenSecret.getValue();
HtmlTextArea hiddenSecret = configForm.getTextAreaByName("_.secret");
return hiddenSecret == null ? null : hiddenSecret.getTextContent();
}

public static class TestBuilder extends Builder {
Expand All @@ -144,7 +144,7 @@ private static TestBuilder fromStringWithDescription(String secret, String descr

@DataBoundConstructor
public TestBuilder(Secret secret) {
this.secret = secret;
this.secret = fixEmptySecret(secret);
}

public Secret getSecret() {
Expand All @@ -160,6 +160,13 @@ public void setDescription(String description) {
this.description = description;
}

private static Secret fixEmptySecret(Secret possiblyEmpty) {
if (possiblyEmpty == null || possiblyEmpty.getPlainText().isEmpty()) {
return null;
}
return possiblyEmpty;
}

@TestExtension
public static class DescriptorImpl extends BuildStepDescriptor<Builder> {
@NonNull
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<?jelly escape-by-default='true'?>
<j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler" xmlns:d="jelly:define" xmlns:l="/lib/layout" xmlns:t="/lib/hudson" xmlns:f="/lib/form">

<f:entry title="Password (empty)" field="emptyPassword">
<f:password/>
</f:entry>
<f:entry title="Password (populated)" field="populatedPassword">
<f:password/>
</f:entry>
<f:entry title="Textarea" field="textarea">
<f:textarea/>
</f:entry>
<f:entry title="Secret Textarea (empty)" field="emptySecretTextarea">
<f:secretTextarea/>
</f:entry>
<f:entry title="Secret Textarea (populated)" field="populatedSecretTextarea">
<f:secretTextarea/>
</f:entry>

</j:jelly>
2 changes: 1 addition & 1 deletion war/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -253,7 +253,7 @@ THE SOFTWARE.
<!-- detached after 1.577 -->
<groupId>org.jenkins-ci.plugins</groupId>
<artifactId>junit</artifactId>
<version>1265.v65b_14fa_f12f0</version>
<version>1291.v60776881903c</version>
<type>hpi</type>
</artifactItem>
<artifactItem>
Expand Down

0 comments on commit 91b4d80

Please sign in to comment.