From ee76f917e8a92f8e49db6043ef16a45cfea362ad Mon Sep 17 00:00:00 2001 From: Riliane Date: Tue, 31 May 2022 12:12:05 +0300 Subject: [PATCH 01/31] JENKINS-68639 Check whether an icon path already contains the context path before appending the context path to it --- core/src/main/java/hudson/Functions.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/core/src/main/java/hudson/Functions.java b/core/src/main/java/hudson/Functions.java index a67bf746c3b88..5033bb5b4bb9a 100644 --- a/core/src/main/java/hudson/Functions.java +++ b/core/src/main/java/hudson/Functions.java @@ -2377,7 +2377,9 @@ public static String tryGetIconPath(String iconGuess, JellyContext context) { if (!iconGuess.startsWith("/")) { iconGuess = "/" + iconGuess; } - iconSource = rootURL + (iconGuess.startsWith("/images/") || iconGuess.startsWith("/plugin/") ? getResourcePath() : "") + iconGuess; + iconSource = iconGuess.startsWith(rootURL) ? "" : rootURL; + iconSource += (iconGuess.startsWith("/images/") || iconGuess.startsWith("/plugin/") ? getResourcePath() : + "") + iconGuess; } if (iconMetadata != null && iconMetadata.getClassSpec() != null) { From 78dd3193a4f8703a3c0fb45603448c2ed1932e26 Mon Sep 17 00:00:00 2001 From: Vincent Latombe Date: Wed, 1 Jun 2022 15:22:22 +0200 Subject: [PATCH 02/31] Refactor parts of Functions#tryGetIconPath for testability Add testing for Functions#guessIcon cases. Refactor icons translation to a static block as it should happen only once. --- core/src/main/java/hudson/Functions.java | 32 +++++++-------- .../java/org/jenkins/ui/icon/IconSet.java | 41 ++++++++++++++----- core/src/test/java/hudson/FunctionsTest.java | 13 ++++++ 3 files changed, 58 insertions(+), 28 deletions(-) diff --git a/core/src/main/java/hudson/Functions.java b/core/src/main/java/hudson/Functions.java index 5033bb5b4bb9a..a57bd943063b3 100644 --- a/core/src/main/java/hudson/Functions.java +++ b/core/src/main/java/hudson/Functions.java @@ -2363,32 +2363,30 @@ public static String tryGetIconPath(String iconGuess, JellyContext context) { currentRequest.getWebApp().getDispatchValidator().allowDispatch(currentRequest, Stapler.getCurrentResponse()); String rootURL = currentRequest.getContextPath(); Icon iconMetadata = tryGetIcon(iconGuess); - String iconSource = null; + String iconSource; if (iconMetadata != null) { - iconSource = iconMetadata.getQualifiedUrl(context); + iconSource = IconSet.tryTranslateTangoIconToSymbol(iconMetadata.getClassSpec(), () -> iconMetadata.getQualifiedUrl(context)); + } else { + iconSource = guessIcon(iconGuess, rootURL); } + return iconSource; + } - if (iconMetadata == null) { - //noinspection HttpUrlsUsage - if (iconGuess.startsWith("http://") || iconGuess.startsWith("https://")) { - return iconGuess; - } + static String guessIcon(String iconGuess, String rootURL) { + String iconSource; + //noinspection HttpUrlsUsage + if (iconGuess.startsWith("http://") || iconGuess.startsWith("https://")) { + iconSource = iconGuess; + } else { if (!iconGuess.startsWith("/")) { iconGuess = "/" + iconGuess; } - iconSource = iconGuess.startsWith(rootURL) ? "" : rootURL; - iconSource += (iconGuess.startsWith("/images/") || iconGuess.startsWith("/plugin/") ? getResourcePath() : - "") + iconGuess; - } - - if (iconMetadata != null && iconMetadata.getClassSpec() != null) { - String translatedIcon = IconSet.tryTranslateTangoIconToSymbol(iconMetadata.getClassSpec()); - if (translatedIcon != null) { - return translatedIcon; + if (iconGuess.startsWith(rootURL)) { + iconGuess = iconGuess.substring(rootURL.length()); } + iconSource = rootURL + (iconGuess.startsWith("/images/") || iconGuess.startsWith("/plugin/") ? getResourcePath() : "") + iconGuess; } - return iconSource; } diff --git a/core/src/main/java/org/jenkins/ui/icon/IconSet.java b/core/src/main/java/org/jenkins/ui/icon/IconSet.java index 13e6716d2a0d9..7e2e0d9d0c68b 100644 --- a/core/src/main/java/org/jenkins/ui/icon/IconSet.java +++ b/core/src/main/java/org/jenkins/ui/icon/IconSet.java @@ -24,6 +24,8 @@ package org.jenkins.ui.icon; +import edu.umd.cs.findbugs.annotations.CheckForNull; +import edu.umd.cs.findbugs.annotations.NonNull; import hudson.PluginWrapper; import hudson.Util; import java.io.IOException; @@ -34,6 +36,7 @@ import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; +import java.util.function.Supplier; import jenkins.model.Jenkins; import org.apache.commons.io.IOUtils; import org.apache.commons.jelly.JellyContext; @@ -565,16 +568,9 @@ private static void initializeSVGs() { } } - /** - * This is a temporary function to replace Tango icons across Jenkins and plugins with - * appropriate Jenkins Symbols - * - * @param tangoIcon A tango icon in the format 'icon-* size-*', e.g. 'icon-gear icon-lg' - * @return a Jenkins Symbol (if one exists) otherwise null - */ - @Restricted(NoExternalUse.class) - public static String tryTranslateTangoIconToSymbol(String tangoIcon) { + private static final Map ICON_TO_SYMBOL_TRANSLATIONS; + static { Map translations = new HashMap<>(); translations.put("icon-application-certificate", "symbol-ribbon"); translations.put("icon-document", "symbol-document-text"); @@ -598,9 +594,32 @@ public static String tryTranslateTangoIconToSymbol(String tangoIcon) { translations.put("icon-text", "symbol-details"); translations.put("icon-up", "symbol-arrow-up"); translations.put("icon-user", "symbol-people"); + ICON_TO_SYMBOL_TRANSLATIONS = translations; + } - String cleanedTangoIcon = cleanName(tangoIcon); - return translations.getOrDefault(cleanedTangoIcon, null); + /** + * This is a temporary function to replace Tango icons across Jenkins and plugins with + * appropriate Jenkins Symbols + * + * @param tangoIcon A tango icon in the format 'icon-* size-*', e.g. 'icon-gear icon-lg' + * @return a Jenkins Symbol (if one exists) otherwise null + */ + @Restricted(NoExternalUse.class) + public static String tryTranslateTangoIconToSymbol(@CheckForNull String tangoIcon) { + return tryTranslateTangoIconToSymbol(tangoIcon, () -> null); + } + + /** + * This is a temporary function to replace Tango icons across Jenkins and plugins with + * appropriate Jenkins Symbols + * + * @param tangoIcon A tango icon in the format 'icon-* size-*', e.g. 'icon-gear icon-lg' + * @param defaultValueSupplier A supplier function that will be called if no icon translation is found + * @return a Jenkins Symbol (if one exists) otherwise the value returned by the supplier + */ + @Restricted(NoExternalUse.class) + public static String tryTranslateTangoIconToSymbol(@CheckForNull String tangoIcon, @NonNull Supplier defaultValueSupplier) { + return tangoIcon == null ? null : ICON_TO_SYMBOL_TRANSLATIONS.getOrDefault(cleanName(tangoIcon), defaultValueSupplier.get()); } private static String cleanName(String tangoIcon) { diff --git a/core/src/test/java/hudson/FunctionsTest.java b/core/src/test/java/hudson/FunctionsTest.java index fff8391321fd3..30d6d31c97c8b 100644 --- a/core/src/test/java/hudson/FunctionsTest.java +++ b/core/src/test/java/hudson/FunctionsTest.java @@ -676,4 +676,17 @@ public void tryGetIcon_shouldReturnMetadataForUrl() throws Exception { public void tryGetIcon_shouldReturnNullForUnknown() throws Exception { assertThat(Functions.tryGetIcon("icon-nosuchicon"), is(nullValue())); } + + @Test + public void guessIcon() throws Exception { + Jenkins.RESOURCE_PATH = "/static/12345678"; + assertEquals("/jenkins/static/12345678/images/48x48/green.gif", Functions.guessIcon("jenkins/images/48x48/green.gif", "/jenkins")); + assertEquals("/jenkins/static/12345678/images/48x48/green.gif", Functions.guessIcon("/jenkins/images/48x48/green.gif", "/jenkins")); + assertEquals("/jenkins/static/12345678/images/48x48/green.gif", Functions.guessIcon("images/48x48/green.gif", "/jenkins")); + assertEquals("/jenkins/static/12345678/images/48x48/green.gif", Functions.guessIcon("/images/48x48/green.gif", "/jenkins")); + assertEquals("/jenkins/static/12345678/plugin/myartifactId/images/48x48/green.gif", Functions.guessIcon("/plugin/myartifactId/images/48x48/green.gif", "/jenkins")); + assertEquals("/jenkins/static/12345678/plugin/myartifactId/images/48x48/green.gif", Functions.guessIcon("/jenkins/plugin/myartifactId/images/48x48/green.gif", "/jenkins")); + assertEquals("http://acme.com/icon.svg", Functions.guessIcon("http://acme.com/icon.svg", "/jenkins")); + assertEquals("https://acme.com/icon.svg", Functions.guessIcon("https://acme.com/icon.svg", "/jenkins")); + } } From 957ef5902f2e40b6358e6d10f12b26f9dbd2f75a Mon Sep 17 00:00:00 2001 From: Daniel Beck Date: Thu, 9 Jun 2022 03:46:01 +0000 Subject: [PATCH 03/31] [SECURITY-2566] --- .../security/HudsonPrivateSecurityRealm.java | 23 ++++- ...HudsonPrivateSecurityRealmSEC2566Test.java | 89 +++++++++++++++++++ 2 files changed, 111 insertions(+), 1 deletion(-) create mode 100644 test/src/test/java/hudson/security/HudsonPrivateSecurityRealmSEC2566Test.java diff --git a/core/src/main/java/hudson/security/HudsonPrivateSecurityRealm.java b/core/src/main/java/hudson/security/HudsonPrivateSecurityRealm.java index 91376af16efd2..cbbe491e0e70c 100644 --- a/core/src/main/java/hudson/security/HudsonPrivateSecurityRealm.java +++ b/core/src/main/java/hudson/security/HudsonPrivateSecurityRealm.java @@ -55,6 +55,7 @@ import java.util.Collections; import java.util.HashMap; import java.util.List; +import java.util.Random; import java.util.logging.Logger; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -205,7 +206,14 @@ public Details load(String username) throws UsernameNotFoundException { @Override protected UserDetails authenticate2(String username, String password) throws AuthenticationException { - Details u = load(username); + Details u; + try { + u = load(username); + } catch (UsernameNotFoundException ex) { + // Waste time to prevent timing attacks distinguishing existing and non-existing user + PASSWORD_ENCODER.matches(password, ENCODED_INVALID_USER_PASSWORD); + throw ex; + } if (!u.isPasswordCorrect(password)) { throw new BadCredentialsException("Bad credentials"); } @@ -963,6 +971,19 @@ public boolean isPasswordHashed(String password) { public static final MultiPasswordEncoder PASSWORD_ENCODER = new MultiPasswordEncoder(); + /** + * This value is used to prevent timing discrepancies when trying to authenticate with an invalid username + * compared to just a wrong password. If the user doesn't exist, compare the provided password with this value. + */ + private static final String ENCODED_INVALID_USER_PASSWORD = PASSWORD_ENCODER.encode(generatePassword()); + + @SuppressFBWarnings(value = {"DMI_RANDOM_USED_ONLY_ONCE", "PREDICTABLE_RANDOM"}, justification = "https://github.com/spotbugs/spotbugs/issues/1539 and doesn't need to be secure, we're just not hardcoding a 'wrong' password") + private static String generatePassword() { + String password = new Random().ints(20, 33, 127).mapToObj(i -> (char) i) + .collect(StringBuilder::new, StringBuilder::appendCodePoint, StringBuilder::append).toString(); + return password; + } + @Extension @Symbol("local") public static final class DescriptorImpl extends Descriptor { @NonNull diff --git a/test/src/test/java/hudson/security/HudsonPrivateSecurityRealmSEC2566Test.java b/test/src/test/java/hudson/security/HudsonPrivateSecurityRealmSEC2566Test.java new file mode 100644 index 0000000000000..9e8c75a492a9d --- /dev/null +++ b/test/src/test/java/hudson/security/HudsonPrivateSecurityRealmSEC2566Test.java @@ -0,0 +1,89 @@ +package hudson.security; + +import hudson.ExtensionList; +import java.lang.reflect.Field; +import java.net.URL; +import java.net.URLConnection; +import java.nio.charset.StandardCharsets; +import java.util.Arrays; +import java.util.Base64; +import jenkins.security.SecurityListener; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.jvnet.hudson.test.Issue; +import org.jvnet.hudson.test.JenkinsRule; + +public class HudsonPrivateSecurityRealmSEC2566Test { + + @Rule + public JenkinsRule j = new JenkinsRule(); + + private HudsonPrivateSecurityRealmTest.SpySecurityListenerImpl spySecurityListener; + + @Before + public void linkExtension() { + spySecurityListener = ExtensionList.lookup(SecurityListener.class).get(HudsonPrivateSecurityRealmTest.SpySecurityListenerImpl.class); + } + + @Before + public void setup() throws Exception { + Field field = HudsonPrivateSecurityRealm.class.getDeclaredField("ID_REGEX"); + field.setAccessible(true); + field.set(null, null); + } + + @Test + @Issue("SECURITY-2566") + // @Ignore // TODO: Is this test too fragile to run? + public void noTimingDifferenceForInternalSecurityRealm() throws Exception { + final HudsonPrivateSecurityRealm realm = new HudsonPrivateSecurityRealm(false, false, null); + j.jenkins.setSecurityRealm(realm); + realm.createAccount("admin", "admin"); + final FullControlOnceLoggedInAuthorizationStrategy a = new FullControlOnceLoggedInAuthorizationStrategy(); + a.setAllowAnonymousRead(false); + j.jenkins.setAuthorizationStrategy(a); + + final URL url = j.getURL(); + + long[] correctUserTimings = new long[20]; + long[] incorrectUserTimings = new long[20]; + + { // Authenticate with correct user, incorrect password + for (int i = 0; i < correctUserTimings.length; i++) { + final URLConnection urlConnection = url.openConnection(); + urlConnection.setRequestProperty("Authorization", "Basic " + Base64.getEncoder().encodeToString("admin:wrong".getBytes(StandardCharsets.UTF_8))); + long start = System.nanoTime(); + try { + urlConnection.getContent(); // send request + } catch (Exception ex) { + // don't care + } + long end = System.nanoTime(); + correctUserTimings[i] = end - start; + } + } + + { // Authenticate with wrong user + for (int i = 0; i < incorrectUserTimings.length; i++) { + final URLConnection urlConnection = url.openConnection(); + urlConnection.setRequestProperty("Authorization", "Basic " + Base64.getEncoder().encodeToString("wrong:wrong".getBytes(StandardCharsets.UTF_8))); + long start = System.nanoTime(); + try { + urlConnection.getContent(); // send request + } catch (Exception ex) { + // don't care + } + long end = System.nanoTime(); + incorrectUserTimings[i] = end - start; + } + } + + // Compute the averages, ignoring the 2 fastest and slowest times in an attempt to weed out outliers + double incorrectAvg = Arrays.stream(incorrectUserTimings).sorted().skip(2).limit(16).average().orElse(0.0); + double correctAvg = Arrays.stream(correctUserTimings).sorted().skip(2).limit(16).average().orElse(0.0); + // expect roughly the same average times + Assert.assertEquals(correctAvg, incorrectAvg, correctAvg * 0.1); + } +} From 8080ec48b864f0b363f79b46a70e55595efedcab Mon Sep 17 00:00:00 2001 From: Daniel Beck Date: Thu, 9 Jun 2022 03:46:09 +0000 Subject: [PATCH 04/31] [SECURITY-2761] --- .../java/org/jenkins/ui/icon/IconSet.java | 2 +- .../jenkins/security/Security2761Test.java | 52 +++++++++++++++++++ .../Security2761Test/ViewHolder/index.jelly | 8 +++ 3 files changed, 61 insertions(+), 1 deletion(-) create mode 100644 test/src/test/java/jenkins/security/Security2761Test.java create mode 100644 test/src/test/resources/jenkins/security/Security2761Test/ViewHolder/index.jelly diff --git a/core/src/main/java/org/jenkins/ui/icon/IconSet.java b/core/src/main/java/org/jenkins/ui/icon/IconSet.java index 13e6716d2a0d9..6004ed4efadb6 100644 --- a/core/src/main/java/org/jenkins/ui/icon/IconSet.java +++ b/core/src/main/java/org/jenkins/ui/icon/IconSet.java @@ -74,7 +74,7 @@ public static void initPageVariables(JellyContext context) { private static String prependTitleIfRequired(String icon, String title) { if (StringUtils.isNotBlank(title)) { - return "" + title + "" + icon; + return "" + Util.xmlEscape(title) + "" + icon; } return icon; } diff --git a/test/src/test/java/jenkins/security/Security2761Test.java b/test/src/test/java/jenkins/security/Security2761Test.java new file mode 100644 index 0000000000000..fc2e5e403f492 --- /dev/null +++ b/test/src/test/java/jenkins/security/Security2761Test.java @@ -0,0 +1,52 @@ +package jenkins.security; + +import static org.hamcrest.CoreMatchers.not; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.containsString; +import static org.junit.Assert.assertFalse; + +import com.gargoylesoftware.htmlunit.html.HtmlPage; +import hudson.model.InvisibleAction; +import hudson.model.UnprotectedRootAction; +import java.net.URL; +import java.util.concurrent.atomic.AtomicBoolean; +import org.junit.Rule; +import org.junit.Test; +import org.jvnet.hudson.test.Issue; +import org.jvnet.hudson.test.JenkinsRule; +import org.jvnet.hudson.test.TestExtension; + +public class Security2761Test { + public static final String ACTION_URL = "security2761"; + + @Rule + public JenkinsRule j = new JenkinsRule(); + + @Issue("SECURITY-2761") + @Test + public void symbolIconAltIsEscaped() throws Exception { + final AtomicBoolean alerted = new AtomicBoolean(false); + + JenkinsRule.WebClient wc = j.createWebClient(); + wc.setAlertHandler((page, s) -> alerted.set(true)); + HtmlPage page = wc.getPage(new URL(wc.getContextPath() + ACTION_URL)); + String responseContent = page.getWebResponse().getContentAsString(); + wc.waitForBackgroundJavaScript(5000); + + assertThat(responseContent, not(containsString("<img src=x")); + assertFalse("no alert expected", alerted.get()); + } + + @TestExtension + public static class ViewHolder extends InvisibleAction implements UnprotectedRootAction { + @Override + public String getUrlName() { + return ACTION_URL; + } + + public String getTitle() { + return ""; + } + } +} diff --git a/test/src/test/resources/jenkins/security/Security2761Test/ViewHolder/index.jelly b/test/src/test/resources/jenkins/security/Security2761Test/ViewHolder/index.jelly new file mode 100644 index 0000000000000..cf08938b6d6c8 --- /dev/null +++ b/test/src/test/resources/jenkins/security/Security2761Test/ViewHolder/index.jelly @@ -0,0 +1,8 @@ + + + + + + + + From f71495a63f8b861e8ca3a5fcf5cc931fce55bc57 Mon Sep 17 00:00:00 2001 From: Daniel Beck Date: Thu, 9 Jun 2022 03:46:16 +0000 Subject: [PATCH 05/31] [SECURITY-2779] --- .../main/resources/lib/form/helpLink.jelly | 2 +- .../jenkins/security/Security2779Test.java | 84 +++++++++++++++++++ .../Security2779Test/ViewHolder/index.jelly | 13 +++ 3 files changed, 98 insertions(+), 1 deletion(-) create mode 100644 test/src/test/java/jenkins/security/Security2779Test.java create mode 100644 test/src/test/resources/jenkins/security/Security2779Test/ViewHolder/index.jelly diff --git a/core/src/main/resources/lib/form/helpLink.jelly b/core/src/main/resources/lib/form/helpLink.jelly index 0c1ab38aa7ebf..dcf279ed65850 100644 --- a/core/src/main/resources/lib/form/helpLink.jelly +++ b/core/src/main/resources/lib/form/helpLink.jelly @@ -53,7 +53,7 @@ THE SOFTWARE. - + ? diff --git a/test/src/test/java/jenkins/security/Security2779Test.java b/test/src/test/java/jenkins/security/Security2779Test.java new file mode 100644 index 0000000000000..a26d2c809cff7 --- /dev/null +++ b/test/src/test/java/jenkins/security/Security2779Test.java @@ -0,0 +1,84 @@ +package jenkins.security; + +import static org.hamcrest.CoreMatchers.containsString; +import static org.hamcrest.CoreMatchers.instanceOf; +import static org.hamcrest.CoreMatchers.not; +import static org.hamcrest.MatcherAssert.assertThat; + +import com.gargoylesoftware.htmlunit.AlertHandler; +import com.gargoylesoftware.htmlunit.ScriptResult; +import com.gargoylesoftware.htmlunit.html.HtmlPage; +import hudson.model.UnprotectedRootAction; +import java.util.Arrays; +import java.util.Collection; +import java.util.concurrent.atomic.AtomicInteger; +import org.junit.Assert; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.jvnet.hudson.test.JenkinsRule; +import org.jvnet.hudson.test.TestExtension; + +@RunWith(Parameterized.class) +public class Security2779Test { + public static final String URL_NAME = "security2779"; + private final String selector; + @Rule + public JenkinsRule j = new JenkinsRule(); + + @Parameterized.Parameters + public static Collection getSelectors() { + return Arrays.asList("#link-panel a", "#icon-panel svg"); + } + + public Security2779Test(String selector) { + this.selector = selector; + } + + @Test + public void noXssInHelp() throws Exception { + final AtomicInteger alerts = new AtomicInteger(); + final JenkinsRule.WebClient webClient = j.createWebClient(); + webClient.setAlertHandler((AlertHandler) (p, s) -> alerts.addAndGet(1)); + final HtmlPage page = webClient.goTo(URL_NAME); + final ScriptResult eventScript = page.executeJavaScript("document.querySelector('" + selector + "').dispatchEvent(new Event('mouseover'))"); + final Object eventResult = eventScript.getJavaScriptResult(); + assertThat(eventResult, instanceOf(boolean.class)); + Assert.assertTrue((boolean) eventResult); + webClient.waitForBackgroundJavaScript(2000); + Assert.assertEquals(0, alerts.get()); + + final ScriptResult innerHtmlScript = page.executeJavaScript("document.querySelector('#tt').innerHTML"); + Object jsResult = innerHtmlScript.getJavaScriptResult(); + assertThat(jsResult, instanceOf(String.class)); + String jsResultString = (String) jsResult; + + // assert leading space to identify unintentional double-escaping (&lt;) as test failure + assertThat("tooltip does not contain dangerous HTML", jsResultString, not(containsString(" "; + } + } +} diff --git a/test/src/test/resources/jenkins/security/Security2779Test/ViewHolder/index.jelly b/test/src/test/resources/jenkins/security/Security2779Test/ViewHolder/index.jelly new file mode 100644 index 0000000000000..3cd5e2e3d4b54 --- /dev/null +++ b/test/src/test/resources/jenkins/security/Security2779Test/ViewHolder/index.jelly @@ -0,0 +1,13 @@ + + + + + +
+ +
+
+
+
From 37bd66a43ad561f670db7440f493d69518741d27 Mon Sep 17 00:00:00 2001 From: Daniel Beck Date: Thu, 9 Jun 2022 03:46:22 +0000 Subject: [PATCH 06/31] [SECURITY-2777] --- core/src/main/java/hudson/Functions.java | 1 - .../jenkins/security/Security2777Test.java | 50 +++++++++++++++++++ .../ViewHolder/fragmentWithIcon.jelly | 6 +++ .../ViewHolder/fragmentWithoutIcon.jelly | 5 ++ .../Security2777Test/ViewHolder/index.jelly | 8 +++ 5 files changed, 69 insertions(+), 1 deletion(-) create mode 100644 test/src/test/java/jenkins/security/Security2777Test.java create mode 100644 test/src/test/resources/jenkins/security/Security2777Test/ViewHolder/fragmentWithIcon.jelly create mode 100644 test/src/test/resources/jenkins/security/Security2777Test/ViewHolder/fragmentWithoutIcon.jelly create mode 100644 test/src/test/resources/jenkins/security/Security2777Test/ViewHolder/index.jelly diff --git a/core/src/main/java/hudson/Functions.java b/core/src/main/java/hudson/Functions.java index a67bf746c3b88..7c54c14405248 100644 --- a/core/src/main/java/hudson/Functions.java +++ b/core/src/main/java/hudson/Functions.java @@ -2360,7 +2360,6 @@ public static String tryGetIconPath(String iconGuess, JellyContext context) { } StaplerRequest currentRequest = Stapler.getCurrentRequest(); - currentRequest.getWebApp().getDispatchValidator().allowDispatch(currentRequest, Stapler.getCurrentResponse()); String rootURL = currentRequest.getContextPath(); Icon iconMetadata = tryGetIcon(iconGuess); String iconSource = null; diff --git a/test/src/test/java/jenkins/security/Security2777Test.java b/test/src/test/java/jenkins/security/Security2777Test.java new file mode 100644 index 0000000000000..c02a9d456d1f4 --- /dev/null +++ b/test/src/test/java/jenkins/security/Security2777Test.java @@ -0,0 +1,50 @@ +package jenkins.security; + +import com.gargoylesoftware.htmlunit.FailingHttpStatusCodeException; +import hudson.model.UnprotectedRootAction; +import java.io.IOException; +import org.junit.Assert; +import org.junit.Rule; +import org.junit.Test; +import org.jvnet.hudson.test.JenkinsRule; +import org.jvnet.hudson.test.TestExtension; + +public class Security2777Test { + public static final String ACTION_URL = "security2777"; + + @Rule + public JenkinsRule j = new JenkinsRule(); + + @Test + public void testView() throws IOException { + final JenkinsRule.WebClient wc = j.createWebClient(); + + // no exception on action index page + wc.getPage(wc.getContextPath() + ACTION_URL); + + final FailingHttpStatusCodeException ex2 = Assert.assertThrows("no icon, no response", FailingHttpStatusCodeException.class, () -> wc.getPage(wc.getContextPath() + ACTION_URL + "/fragmentWithoutIcon")); + Assert.assertEquals("it's 404", 404, ex2.getStatusCode()); + + final FailingHttpStatusCodeException ex3 = Assert.assertThrows("icon, still no response", FailingHttpStatusCodeException.class, () -> wc.getPage(wc.getContextPath() + ACTION_URL + "/fragmentWithIcon")); + Assert.assertEquals("it's 404", 404, ex3.getStatusCode()); + } + + @TestExtension + public static class ViewHolder implements UnprotectedRootAction { + + @Override + public String getIconFileName() { + return null; + } + + @Override + public String getDisplayName() { + return null; + } + + @Override + public String getUrlName() { + return ACTION_URL; + } + } +} diff --git a/test/src/test/resources/jenkins/security/Security2777Test/ViewHolder/fragmentWithIcon.jelly b/test/src/test/resources/jenkins/security/Security2777Test/ViewHolder/fragmentWithIcon.jelly new file mode 100644 index 0000000000000..73d9b92b8f8c2 --- /dev/null +++ b/test/src/test/resources/jenkins/security/Security2777Test/ViewHolder/fragmentWithIcon.jelly @@ -0,0 +1,6 @@ + + + + +

Help!

+
diff --git a/test/src/test/resources/jenkins/security/Security2777Test/ViewHolder/fragmentWithoutIcon.jelly b/test/src/test/resources/jenkins/security/Security2777Test/ViewHolder/fragmentWithoutIcon.jelly new file mode 100644 index 0000000000000..29585bea6c239 --- /dev/null +++ b/test/src/test/resources/jenkins/security/Security2777Test/ViewHolder/fragmentWithoutIcon.jelly @@ -0,0 +1,5 @@ + + +

Help!

+

I'm just HTML!

+
diff --git a/test/src/test/resources/jenkins/security/Security2777Test/ViewHolder/index.jelly b/test/src/test/resources/jenkins/security/Security2777Test/ViewHolder/index.jelly new file mode 100644 index 0000000000000..c8961bf0ccb31 --- /dev/null +++ b/test/src/test/resources/jenkins/security/Security2777Test/ViewHolder/index.jelly @@ -0,0 +1,8 @@ + + + + +

Hello

+
+
+
From 49c4cda2d41719d66a4eb4d6f9c31ba8298f2fbf Mon Sep 17 00:00:00 2001 From: Daniel Beck Date: Thu, 9 Jun 2022 03:46:29 +0000 Subject: [PATCH 07/31] [SECURITY-2776][SECURITY-2780] --- .../java/org/jenkins/ui/icon/IconSet.java | 13 ++-- .../views/BuildButtonColumn/column.jelly | 2 +- core/src/main/resources/lib/layout/icon.jelly | 7 +- .../jenkins/security/Security2776Test.java | 76 +++++++++++++++++++ .../jenkins/security/Security2780Test.java | 41 ++++++++++ .../Security2776Test/ViewHolder/index.jelly | 33 ++++++++ 6 files changed, 163 insertions(+), 9 deletions(-) create mode 100644 test/src/test/java/jenkins/security/Security2776Test.java create mode 100644 test/src/test/java/jenkins/security/Security2780Test.java create mode 100644 test/src/test/resources/jenkins/security/Security2776Test/ViewHolder/index.jelly diff --git a/core/src/main/java/org/jenkins/ui/icon/IconSet.java b/core/src/main/java/org/jenkins/ui/icon/IconSet.java index 6004ed4efadb6..8803fb26ecec8 100644 --- a/core/src/main/java/org/jenkins/ui/icon/IconSet.java +++ b/core/src/main/java/org/jenkins/ui/icon/IconSet.java @@ -24,6 +24,7 @@ package org.jenkins.ui.icon; +import hudson.Functions; import hudson.PluginWrapper; import hudson.Util; import java.io.IOException; @@ -93,12 +94,12 @@ public static String getSymbol(String name, String title, String tooltip, String symbol = symbol.replaceAll("(tooltip=\")[^&]*?(\")", ""); symbol = symbol.replaceAll("(id=\")[^&]*?(\")", ""); if (!tooltip.isEmpty()) { - symbol = symbol.replaceAll("
- + diff --git a/core/src/main/resources/lib/layout/icon.jelly b/core/src/main/resources/lib/layout/icon.jelly index fe742d7c26bd6..a1e1083ba9756 100644 --- a/core/src/main/resources/lib/layout/icon.jelly +++ b/core/src/main/resources/lib/layout/icon.jelly @@ -43,9 +43,12 @@ THE SOFTWARE. onclick handler. Deprecated; assign an ID and look up the element that way to attach event handlers. - title, deprecated use tooltip instead + title, deprecated use tooltip instead, but beware of its support for HTML style - tooltip + + tooltip (supports HTML for PNG and symbol icons). + Make sure to call h.htmlAttributeEscape on all user-specified parts of the value to prevent cross-site scripting. + Icons based on classic (non-symbol) SVG do not support HTML tooltips due to how SECURITY-1955 was fixed in Jenkins 2.252 and 2.235.4, but since such icons can be upgraded to symbols, it is important to still escape user-specified parts of the text (resulting in double escaping while the icon is based on classic SVG). alt, adds invisible text suitable for screen-readers for symbols, sets the alt attribute for normal images diff --git a/test/src/test/java/jenkins/security/Security2776Test.java b/test/src/test/java/jenkins/security/Security2776Test.java new file mode 100644 index 0000000000000..007950497c4db --- /dev/null +++ b/test/src/test/java/jenkins/security/Security2776Test.java @@ -0,0 +1,76 @@ +package jenkins.security; + +import static org.hamcrest.CoreMatchers.containsString; +import static org.hamcrest.CoreMatchers.instanceOf; +import static org.hamcrest.MatcherAssert.assertThat; + +import com.gargoylesoftware.htmlunit.ScriptResult; +import com.gargoylesoftware.htmlunit.html.HtmlPage; +import hudson.Util; +import hudson.model.InvisibleAction; +import hudson.model.UnprotectedRootAction; +import java.io.IOException; +import java.util.concurrent.atomic.AtomicBoolean; +import org.junit.Assert; +import org.junit.Rule; +import org.junit.Test; +import org.jvnet.hudson.test.JenkinsRule; +import org.jvnet.hudson.test.TestExtension; +import org.xml.sax.SAXException; + +public class Security2776Test { + public static final String URL_NAME = "security2776"; + @Rule + public JenkinsRule j = new JenkinsRule(); + + @Test + public void escapedTooltipIsEscaped() throws Exception { + assertExpectedBehaviorForTooltip("#symbol-icons .unsafe svg", _getUnsafeTooltip(), true); + assertExpectedBehaviorForTooltip("#symbol-icons .safe svg", _getSafeTooltip(), false); + assertExpectedBehaviorForTooltip("#png-icons .unsafe img", _getUnsafeTooltip(), true); + assertExpectedBehaviorForTooltip("#png-icons .safe img", _getSafeTooltip(), false); + + // Outlier after the fix for SECURITY-1955 + assertExpectedBehaviorForTooltip("#svgIcons .unsafe svg", _getSafeTooltip(), false); + assertExpectedBehaviorForTooltip("#svgIcons .safe svg", Util.xmlEscape(_getSafeTooltip()), false); + } + + private void assertExpectedBehaviorForTooltip(String selector, String expectedTooltipContent, boolean alertExpected) throws IOException, SAXException { + final AtomicBoolean alerts = new AtomicBoolean(); + final JenkinsRule.WebClient wc = j.createWebClient(); + wc.setAlertHandler((p, s) -> alerts.set(true)); + final HtmlPage page = wc.goTo(URL_NAME); + page.executeJavaScript("document.querySelector('" + selector + "').dispatchEvent(new Event('mouseover'));"); + wc.waitForBackgroundJavaScript(2000L); + ScriptResult result = page.executeJavaScript("document.querySelector('#tt').innerHTML;"); + Object jsResult = result.getJavaScriptResult(); + assertThat(jsResult, instanceOf(String.class)); + String jsResultString = (String) jsResult; + assertThat(jsResultString, containsString(expectedTooltipContent)); + Assert.assertEquals(alertExpected ? "Alert expected" : "No alert expected", alertExpected, alerts.get()); + } + + private static String _getUnsafeTooltip() { + return ""; + } + + private static String _getSafeTooltip() { + return Util.xmlEscape(_getUnsafeTooltip()); + } + + @TestExtension + public static class ViewHolder extends InvisibleAction implements UnprotectedRootAction { + @Override + public String getUrlName() { + return URL_NAME; + } + + public String getUnsafeTooltip() { + return _getUnsafeTooltip(); + } + + public String getSafeTooltip() { + return _getSafeTooltip(); + } + } +} diff --git a/test/src/test/java/jenkins/security/Security2780Test.java b/test/src/test/java/jenkins/security/Security2780Test.java new file mode 100644 index 0000000000000..6ff98d5ac85db --- /dev/null +++ b/test/src/test/java/jenkins/security/Security2780Test.java @@ -0,0 +1,41 @@ +package jenkins.security; + +import static org.hamcrest.CoreMatchers.containsString; +import static org.hamcrest.CoreMatchers.instanceOf; +import static org.hamcrest.CoreMatchers.not; +import static org.hamcrest.MatcherAssert.assertThat; + +import com.gargoylesoftware.htmlunit.ScriptResult; +import com.gargoylesoftware.htmlunit.html.HtmlPage; +import hudson.model.FreeStyleProject; +import java.util.concurrent.atomic.AtomicBoolean; +import org.junit.Assert; +import org.junit.Rule; +import org.junit.Test; +import org.jvnet.hudson.test.JenkinsRule; + +public class Security2780Test { + @Rule + public JenkinsRule j = new JenkinsRule(); + + @Test + public void buildButtonTooltipHasNoXss() throws Exception { + FreeStyleProject project = this.j.createFreeStyleProject(); + project.setDisplayName(""); + JenkinsRule.WebClient wc = this.j.createWebClient(); + + AtomicBoolean alertTriggered = new AtomicBoolean(false); + wc.setAlertHandler((p, s) -> alertTriggered.set(true)); + HtmlPage page = wc.goTo(""); + page.executeJavaScript("document.querySelector('a.jenkins-table__button').dispatchEvent(new Event('mouseover'));"); + wc.waitForBackgroundJavaScript(2000L); + ScriptResult result = page.executeJavaScript("document.querySelector('#tt').innerHTML;"); + Object jsResult = result.getJavaScriptResult(); + assertThat(jsResult, instanceOf(String.class)); + String jsResultString = (String) jsResult; + + assertThat("No unsafe HTML expected in the tooltip", jsResultString, not(containsString(" + + + +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+
+
+
From 3191a1baaaf07d6c98cdb601217e83ce3087da24 Mon Sep 17 00:00:00 2001 From: Vincent Latombe Date: Thu, 9 Jun 2022 18:12:14 +0200 Subject: [PATCH 08/31] Add support for more edge cases --- core/src/main/java/hudson/Functions.java | 4 +++- core/src/test/java/hudson/FunctionsTest.java | 5 +++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/core/src/main/java/hudson/Functions.java b/core/src/main/java/hudson/Functions.java index a57bd943063b3..c92e5fe516ecd 100644 --- a/core/src/main/java/hudson/Functions.java +++ b/core/src/main/java/hudson/Functions.java @@ -2383,7 +2383,9 @@ static String guessIcon(String iconGuess, String rootURL) { iconGuess = "/" + iconGuess; } if (iconGuess.startsWith(rootURL)) { - iconGuess = iconGuess.substring(rootURL.length()); + if ((!rootURL.equals("/images") && !rootURL.equals("/plugin")) || iconGuess.startsWith(rootURL + rootURL)) { + iconGuess = iconGuess.substring(rootURL.length()); + } } iconSource = rootURL + (iconGuess.startsWith("/images/") || iconGuess.startsWith("/plugin/") ? getResourcePath() : "") + iconGuess; } diff --git a/core/src/test/java/hudson/FunctionsTest.java b/core/src/test/java/hudson/FunctionsTest.java index 30d6d31c97c8b..f9b42f97efca8 100644 --- a/core/src/test/java/hudson/FunctionsTest.java +++ b/core/src/test/java/hudson/FunctionsTest.java @@ -682,10 +682,15 @@ public void guessIcon() throws Exception { Jenkins.RESOURCE_PATH = "/static/12345678"; assertEquals("/jenkins/static/12345678/images/48x48/green.gif", Functions.guessIcon("jenkins/images/48x48/green.gif", "/jenkins")); assertEquals("/jenkins/static/12345678/images/48x48/green.gif", Functions.guessIcon("/jenkins/images/48x48/green.gif", "/jenkins")); + assertEquals("/static/12345678/images/48x48/green.gif", Functions.guessIcon("images/48x48/green.gif", "")); assertEquals("/jenkins/static/12345678/images/48x48/green.gif", Functions.guessIcon("images/48x48/green.gif", "/jenkins")); assertEquals("/jenkins/static/12345678/images/48x48/green.gif", Functions.guessIcon("/images/48x48/green.gif", "/jenkins")); + assertEquals("/images/static/12345678/images/48x48/green.gif", Functions.guessIcon("/images/48x48/green.gif", "/images")); + assertEquals("/static/12345678/plugin/myartifactId/images/48x48/green.gif", Functions.guessIcon("/plugin/myartifactId/images/48x48/green.gif", "")); assertEquals("/jenkins/static/12345678/plugin/myartifactId/images/48x48/green.gif", Functions.guessIcon("/plugin/myartifactId/images/48x48/green.gif", "/jenkins")); assertEquals("/jenkins/static/12345678/plugin/myartifactId/images/48x48/green.gif", Functions.guessIcon("/jenkins/plugin/myartifactId/images/48x48/green.gif", "/jenkins")); + assertEquals("/plugin/static/12345678/plugin/myartifactId/images/48x48/green.gif", Functions.guessIcon("/plugin/myartifactId/images/48x48/green.gif", "/plugin")); + assertEquals("/plugin/static/12345678/plugin/myartifactId/images/48x48/green.gif", Functions.guessIcon("/plugin/plugin/myartifactId/images/48x48/green.gif", "/plugin")); assertEquals("http://acme.com/icon.svg", Functions.guessIcon("http://acme.com/icon.svg", "/jenkins")); assertEquals("https://acme.com/icon.svg", Functions.guessIcon("https://acme.com/icon.svg", "/jenkins")); } From d43d0b51dd18bca980f7d384ec4a353a2a66b818 Mon Sep 17 00:00:00 2001 From: Jenkins Release Bot <66998184+jenkins-release-bot@users.noreply.github.com> Date: Tue, 14 Jun 2022 12:52:57 +0000 Subject: [PATCH 09/31] [maven-release-plugin] prepare release jenkins-2.355 --- bom/pom.xml | 2 +- cli/pom.xml | 2 +- core/pom.xml | 2 +- coverage/pom.xml | 2 +- pom.xml | 4 ++-- test/pom.xml | 2 +- war/pom.xml | 2 +- 7 files changed, 8 insertions(+), 8 deletions(-) diff --git a/bom/pom.xml b/bom/pom.xml index a2929a173b770..7d2fbaacc3a1a 100644 --- a/bom/pom.xml +++ b/bom/pom.xml @@ -28,7 +28,7 @@ THE SOFTWARE. org.jenkins-ci.main jenkins-parent - ${revision}${changelist} + 2.355 jenkins-bom diff --git a/cli/pom.xml b/cli/pom.xml index 2bfaf2dd6a86b..3ede421b866e7 100644 --- a/cli/pom.xml +++ b/cli/pom.xml @@ -5,7 +5,7 @@ org.jenkins-ci.main jenkins-parent - ${revision}${changelist} + 2.355 cli diff --git a/core/pom.xml b/core/pom.xml index 27554bf94680b..bab7826bdb88f 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -29,7 +29,7 @@ THE SOFTWARE. org.jenkins-ci.main jenkins-parent - ${revision}${changelist} + 2.355 jenkins-core diff --git a/coverage/pom.xml b/coverage/pom.xml index 9f27c8d11cf0c..d5ac8a2b6fd0b 100644 --- a/coverage/pom.xml +++ b/coverage/pom.xml @@ -5,7 +5,7 @@ org.jenkins-ci.main jenkins-parent - ${revision}${changelist} + 2.355 jenkins-coverage diff --git a/pom.xml b/pom.xml index 04a02264892e6..a31de388f4e64 100644 --- a/pom.xml +++ b/pom.xml @@ -34,7 +34,7 @@ THE SOFTWARE. org.jenkins-ci.main jenkins-parent - ${revision}${changelist} + 2.355 pom Jenkins main module @@ -61,7 +61,7 @@ THE SOFTWARE. scm:git:https://github.com/jenkinsci/jenkins.git scm:git:git@github.com:jenkinsci/jenkins.git - ${scmTag} + jenkins-2.355 https://github.com/jenkinsci/jenkins diff --git a/test/pom.xml b/test/pom.xml index 3bb7f0e85dcad..66cc33bc42271 100644 --- a/test/pom.xml +++ b/test/pom.xml @@ -28,7 +28,7 @@ THE SOFTWARE. org.jenkins-ci.main jenkins-parent - ${revision}${changelist} + 2.355 jenkins-test diff --git a/war/pom.xml b/war/pom.xml index dfc597a0ae463..6cde5ecb60371 100644 --- a/war/pom.xml +++ b/war/pom.xml @@ -28,7 +28,7 @@ THE SOFTWARE. org.jenkins-ci.main jenkins-parent - ${revision}${changelist} + 2.355 jenkins-war From 1c271f5e167656d3abbe0fb20837f60459187043 Mon Sep 17 00:00:00 2001 From: Jenkins Release Bot <66998184+jenkins-release-bot@users.noreply.github.com> Date: Tue, 14 Jun 2022 12:59:31 +0000 Subject: [PATCH 10/31] [maven-release-plugin] prepare for next development iteration --- bom/pom.xml | 2 +- cli/pom.xml | 2 +- core/pom.xml | 2 +- coverage/pom.xml | 2 +- pom.xml | 6 +++--- test/pom.xml | 2 +- war/pom.xml | 2 +- 7 files changed, 9 insertions(+), 9 deletions(-) diff --git a/bom/pom.xml b/bom/pom.xml index 7d2fbaacc3a1a..a2929a173b770 100644 --- a/bom/pom.xml +++ b/bom/pom.xml @@ -28,7 +28,7 @@ THE SOFTWARE. org.jenkins-ci.main jenkins-parent - 2.355 + ${revision}${changelist} jenkins-bom diff --git a/cli/pom.xml b/cli/pom.xml index 3ede421b866e7..2bfaf2dd6a86b 100644 --- a/cli/pom.xml +++ b/cli/pom.xml @@ -5,7 +5,7 @@ org.jenkins-ci.main jenkins-parent - 2.355 + ${revision}${changelist} cli diff --git a/core/pom.xml b/core/pom.xml index bab7826bdb88f..27554bf94680b 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -29,7 +29,7 @@ THE SOFTWARE. org.jenkins-ci.main jenkins-parent - 2.355 + ${revision}${changelist} jenkins-core diff --git a/coverage/pom.xml b/coverage/pom.xml index d5ac8a2b6fd0b..9f27c8d11cf0c 100644 --- a/coverage/pom.xml +++ b/coverage/pom.xml @@ -5,7 +5,7 @@ org.jenkins-ci.main jenkins-parent - 2.355 + ${revision}${changelist} jenkins-coverage diff --git a/pom.xml b/pom.xml index a31de388f4e64..31b24c5f66c1b 100644 --- a/pom.xml +++ b/pom.xml @@ -34,7 +34,7 @@ THE SOFTWARE. org.jenkins-ci.main jenkins-parent - 2.355 + ${revision}${changelist} pom Jenkins main module @@ -61,7 +61,7 @@ THE SOFTWARE. scm:git:https://github.com/jenkinsci/jenkins.git scm:git:git@github.com:jenkinsci/jenkins.git - jenkins-2.355 + ${scmTag} https://github.com/jenkinsci/jenkins @@ -71,7 +71,7 @@ THE SOFTWARE. - 2.355 + 2.356 -SNAPSHOT - 3025.vf64a_a_3da_6b_55 + 3028.va_a_436db_35078 3.14 From 2ac73871d2791499d517c54ba7b3289209eade65 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 22 Jun 2022 12:48:27 -0700 Subject: [PATCH 18/31] Bump `jenkins-test-harness` from 1753.v45c760e2400f to 1784.v83a_b_3ce99392 (#6668) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- test/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/pom.xml b/test/pom.xml index 3bb7f0e85dcad..c12f0e7211ad6 100644 --- a/test/pom.xml +++ b/test/pom.xml @@ -85,7 +85,7 @@ THE SOFTWARE. ${project.groupId} jenkins-test-harness - 1753.v45c760e2400f + 1784.v83a_b_3ce99392 test From d32b9681002281ea0938d27bc7802fa5112eee32 Mon Sep 17 00:00:00 2001 From: Basil Crow Date: Wed, 22 Jun 2022 12:49:05 -0700 Subject: [PATCH 19/31] Bump minimum supported Remoting version from 3.14 to 4.2.1 (#6671) --- pom.xml | 2 +- test/pom.xml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 9beeecda3ebc3..9151a996c4098 100644 --- a/pom.xml +++ b/pom.xml @@ -92,7 +92,7 @@ THE SOFTWARE. 3028.va_a_436db_35078 - 3.14 + 4.2.1 Max Medium diff --git a/test/pom.xml b/test/pom.xml index c12f0e7211ad6..39e114838800c 100644 --- a/test/pom.xml +++ b/test/pom.xml @@ -233,7 +233,7 @@ THE SOFTWARE. org.jenkins-ci.main remoting - 3.13 + 4.2 jar ${project.build.outputDirectory}/old-remoting remoting-unsupported.jar From 275fad3987a477c25c4f7f2123eb432c04a9d8ac Mon Sep 17 00:00:00 2001 From: Basil Crow Date: Wed, 22 Jun 2022 12:50:08 -0700 Subject: [PATCH 20/31] [JENKINS-68785] No log messages for inbound agents after the first message (#6672) --- .../main/java/jenkins/slaves/DefaultJnlpSlaveReceiver.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/core/src/main/java/jenkins/slaves/DefaultJnlpSlaveReceiver.java b/core/src/main/java/jenkins/slaves/DefaultJnlpSlaveReceiver.java index f130fdf27a730..54501a46345ac 100644 --- a/core/src/main/java/jenkins/slaves/DefaultJnlpSlaveReceiver.java +++ b/core/src/main/java/jenkins/slaves/DefaultJnlpSlaveReceiver.java @@ -150,14 +150,14 @@ public void afterProperties(@NonNull JnlpConnectionState event) { } @Override + @SuppressFBWarnings(value = "OS_OPEN_STREAM", justification = "Closed by hudson.slaves.SlaveComputer#kill") public void beforeChannel(@NonNull JnlpConnectionState event) { DefaultJnlpSlaveReceiver.State state = event.getStash(DefaultJnlpSlaveReceiver.State.class); final SlaveComputer computer = state.getNode(); final OutputStream log = computer.openLogFile(); state.setLog(log); - try (PrintWriter logw = new PrintWriter(new OutputStreamWriter(log, /* TODO switch agent logs to UTF-8 */ Charset.defaultCharset()), true)) { - logw.println("Inbound agent connected from " + event.getRemoteEndpointDescription()); - } + PrintWriter logw = new PrintWriter(new OutputStreamWriter(log, /* TODO switch agent logs to UTF-8 */ Charset.defaultCharset()), true); // Closed by hudson.slaves.SlaveComputer#kill + logw.println("Inbound agent connected from " + event.getRemoteEndpointDescription()); for (ChannelConfigurator cc : ChannelConfigurator.all()) { cc.onChannelBuilding(event.getChannelBuilder(), computer); } From 7dd5f999254c0e0461a8a664b0aca4681a49e110 Mon Sep 17 00:00:00 2001 From: Jan Faracik <43062514+janfaracik@users.noreply.github.com> Date: Wed, 22 Jun 2022 20:50:59 +0100 Subject: [PATCH 21/31] Remove `textarea-handle` and `richtextarea.jelly` (#6621) --- .../resources/lib/form/richtextarea.jelly | 41 ------------ .../main/resources/lib/form/textarea.jelly | 2 - .../main/resources/lib/layout/layout.jelly | 2 - war/src/main/less/base/style.less | 19 ------ .../main/webapp/images/textarea-handle.gif | Bin 52 -> 0 bytes .../main/webapp/scripts/hudson-behavior.js | 59 ------------------ 6 files changed, 123 deletions(-) delete mode 100644 core/src/main/resources/lib/form/richtextarea.jelly delete mode 100644 war/src/main/webapp/images/textarea-handle.gif diff --git a/core/src/main/resources/lib/form/richtextarea.jelly b/core/src/main/resources/lib/form/richtextarea.jelly deleted file mode 100644 index 00ee41bce4ac2..0000000000000 --- a/core/src/main/resources/lib/form/richtextarea.jelly +++ /dev/null @@ -1,41 +0,0 @@ - - - - - - Rich HTML editor from http://developer.yahoo.com/yui/editor/ - All the attributes are those of the <textarea> tag. - - - - - - - - \ No newline at end of file diff --git a/core/src/main/resources/lib/form/textarea.jelly b/core/src/main/resources/lib/form/textarea.jelly index d6fa4e02aa836..f0b234ebcb7c4 100644 --- a/core/src/main/resources/lib/form/textarea.jelly +++ b/core/src/main/resources/lib/form/textarea.jelly @@ -102,8 +102,6 @@ THE SOFTWARE. ${customizedFields.add(name)} - -
diff --git a/core/src/main/resources/lib/layout/layout.jelly b/core/src/main/resources/lib/layout/layout.jelly index fb140545b368c..b526bb2b57c05 100644 --- a/core/src/main/resources/lib/layout/layout.jelly +++ b/core/src/main/resources/lib/layout/layout.jelly @@ -141,7 +141,6 @@ THE SOFTWARE. -