diff --git a/bom/pom.xml b/bom/pom.xml index b9b8d1292cf7..a56b2ea141df 100644 --- a/bom/pom.xml +++ b/bom/pom.xml @@ -194,7 +194,7 @@ THE SOFTWARE. org.apache.commons commons-compress - 1.23.0 + 1.24.0 org.codehaus.groovy diff --git a/core/src/main/java/hudson/ProxyConfiguration.java b/core/src/main/java/hudson/ProxyConfiguration.java index a165fa6e3151..08f7131289b2 100644 --- a/core/src/main/java/hudson/ProxyConfiguration.java +++ b/core/src/main/java/hudson/ProxyConfiguration.java @@ -31,7 +31,9 @@ import hudson.model.Descriptor; import hudson.model.Saveable; import hudson.model.listeners.SaveableListener; +import hudson.util.DaemonThreadFactory; import hudson.util.FormValidation; +import hudson.util.NamingThreadFactory; import hudson.util.Scrambler; import hudson.util.Secret; import hudson.util.XStream2; @@ -58,6 +60,8 @@ import java.util.Collections; import java.util.List; import java.util.Objects; +import java.util.concurrent.Executor; +import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; import java.util.regex.Pattern; import jenkins.UserAgentURLConnectionDecorator; @@ -370,6 +374,8 @@ public static HttpClient newHttpClient() { return newHttpClientBuilder().followRedirects(HttpClient.Redirect.NORMAL).build(); } + private static final Executor httpClientExecutor = Executors.newCachedThreadPool(new NamingThreadFactory(new DaemonThreadFactory(), "Jenkins HttpClient")); + /** * Create a new {@link HttpClient.Builder} preconfigured with Jenkins-specific default settings. * @@ -397,6 +403,7 @@ public static HttpClient.Builder newHttpClientBuilder() { if (DEFAULT_CONNECT_TIMEOUT_MILLIS > 0) { httpClientBuilder.connectTimeout(Duration.ofMillis(DEFAULT_CONNECT_TIMEOUT_MILLIS)); } + httpClientBuilder.executor(httpClientExecutor); return httpClientBuilder; } diff --git a/core/src/main/java/hudson/util/XStream2.java b/core/src/main/java/hudson/util/XStream2.java index 5c6ebc566731..cf1b5e9bcaab 100644 --- a/core/src/main/java/hudson/util/XStream2.java +++ b/core/src/main/java/hudson/util/XStream2.java @@ -475,12 +475,8 @@ protected Class computeValue(Class type) { return computeConverterClass(type); } }; - private final ClassValue cache = new ClassValue() { - @Override - protected Converter computeValue(Class type) { - return computeConverter(type); - } - }; + private final ConcurrentHashMap, Converter> cache = + new ConcurrentHashMap<>(); private AssociatedConverterImpl(XStream xstream) { this.xstream = xstream; @@ -491,7 +487,9 @@ private Converter findConverter(@CheckForNull Class t) { if (t == null) { return null; } - return cache.get(t); + Converter result = cache.computeIfAbsent(t, unused -> computeConverter(t)); + // ConcurrentHashMap does not allow null, so use this object to represent null + return result == this ? null : result; } @CheckForNull @@ -515,7 +513,8 @@ private static Class computeConverterClass(@NonNull private Converter computeConverter(@NonNull Class t) { Class cl = classCache.get(t); if (cl == null) { - return null; + // See above.. this object in cache represents null + return this; } try { Constructor c = cl.getConstructors()[0]; diff --git a/core/src/main/java/jenkins/telemetry/impl/SecurityConfiguration.java b/core/src/main/java/jenkins/telemetry/impl/SecurityConfiguration.java new file mode 100644 index 000000000000..a59a34bcbf4c --- /dev/null +++ b/core/src/main/java/jenkins/telemetry/impl/SecurityConfiguration.java @@ -0,0 +1,79 @@ +/* + * The MIT License + * + * Copyright (c) 2023, 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. + */ + +package jenkins.telemetry.impl; + +import edu.umd.cs.findbugs.annotations.NonNull; +import hudson.Extension; +import hudson.ExtensionList; +import hudson.TcpSlaveAgentListener; +import hudson.security.csrf.CrumbIssuer; +import java.time.LocalDate; +import jenkins.model.Jenkins; +import jenkins.security.apitoken.ApiTokenPropertyConfiguration; +import jenkins.telemetry.Telemetry; +import net.sf.json.JSONObject; + +@Extension +public class SecurityConfiguration extends Telemetry { + @NonNull + @Override + public String getDisplayName() { + return "Basic information about security-related settings"; + } + + @NonNull + @Override + public LocalDate getStart() { + return LocalDate.of(2023, 8, 1); + } + + @NonNull + @Override + public LocalDate getEnd() { + return LocalDate.of(2023, 12, 1); + } + + @Override + public JSONObject createContent() { + final Jenkins j = Jenkins.get(); + final JSONObject o = new JSONObject(); + o.put("components", buildComponentInformation()); + + o.put("authorizationStrategy", j.getAuthorizationStrategy().getClass().getName()); + o.put("securityRealm", j.getSecurityRealm().getClass().getName()); + final CrumbIssuer crumbIssuer = j.getCrumbIssuer(); + o.put("crumbIssuer", crumbIssuer == null ? null : crumbIssuer.getClass().getName()); + o.put("markupFormatter", j.getMarkupFormatter().getClass().getName()); + final TcpSlaveAgentListener tcpSlaveAgentListener = j.getTcpSlaveAgentListener(); + o.put("inboundAgentListener", tcpSlaveAgentListener == null ? null : tcpSlaveAgentListener.configuredPort != -1); + + final ApiTokenPropertyConfiguration apiTokenPropertyConfiguration = ExtensionList.lookupSingleton(ApiTokenPropertyConfiguration.class); + o.put("apiTokenCreationOfLegacyTokenEnabled", apiTokenPropertyConfiguration.isCreationOfLegacyTokenEnabled()); + o.put("apiTokenTokenGenerationOnCreationEnabled", apiTokenPropertyConfiguration.isTokenGenerationOnCreationEnabled()); + o.put("apiTokenUsageStatisticsEnabled", apiTokenPropertyConfiguration.isUsageStatisticsEnabled()); + + return o; + } +} diff --git a/core/src/main/resources/jenkins/telemetry/impl/SecurityConfiguration/description.jelly b/core/src/main/resources/jenkins/telemetry/impl/SecurityConfiguration/description.jelly new file mode 100644 index 000000000000..85dd418c67e6 --- /dev/null +++ b/core/src/main/resources/jenkins/telemetry/impl/SecurityConfiguration/description.jelly @@ -0,0 +1,17 @@ + + + This trial collects basic information about security settings: +
    +
  • The type of the currently configured security realm, e.g., hudson.security.HudsonPrivateSecurityRealm
  • +
  • The type of the currently configured authorization strategy, e.g., hudson.security.ProjectMatrixAuthorizationStrategy
  • +
  • The type of the currently configured crumb issuer, e.g., hudson.security.csrf.DefaultCrumbIssuer
  • +
  • The type of the currently configured markup formatter, e.g., hudson.markup.RawHtmlMarkupFormatter
  • +
  • Whether the TCP port for inbound agents is enabled (fixed or random) or disabled
  • +
  • Whether the API token option labeled Generate a legacy API token for each newly created user (Not recommended) is enabled or disabled
  • +
  • Whether the API token option labeled Allow users to manually create a legacy API token (Not recommended) is enabled or disabled
  • +
  • Whether the API token option labeled Enable API Token usage statistics is enabled or disabled
  • +
+ + Additionally this trial collects the list of installed plugins, their version, and the version of Jenkins. + This data will be used to understand the popularity of the various implementations for each of these features. +
diff --git a/test/src/test/java/hudson/ProxyConfigurationTest.java b/test/src/test/java/hudson/ProxyConfigurationTest.java new file mode 100644 index 000000000000..f1f796968525 --- /dev/null +++ b/test/src/test/java/hudson/ProxyConfigurationTest.java @@ -0,0 +1,71 @@ +/* + * The MIT License + * + * Copyright 2023 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. + */ + +package hudson; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; + +import hudson.model.InvisibleAction; +import hudson.model.UnprotectedRootAction; +import java.net.URI; +import org.junit.Assume; +import org.junit.Rule; +import org.junit.Test; +import org.jvnet.hudson.test.JenkinsRule; +import org.jvnet.hudson.test.TestExtension; +import org.kohsuke.stapler.HttpResponse; +import org.kohsuke.stapler.HttpResponses; + +public final class ProxyConfigurationTest { + + @Rule + public JenkinsRule r = new JenkinsRule(); + + @Test + public void httpClientExecutor() throws Exception { + Assume.assumeFalse("Too slow on Windows", Functions.isWindows()); + for (int i = 0; i < 50_000; i++) { + if (i % 1_000 == 0) { + System.err.println("#" + i); + } + assertThat(ProxyConfiguration.newHttpClient().send(ProxyConfiguration.newHttpRequestBuilder(URI.create(r.getURL() + "ping/")).build(), + java.net.http.HttpResponse.BodyHandlers.discarding()).statusCode(), + is(200)); + } + } + + @TestExtension("httpClientExecutor") + public static final class Ping extends InvisibleAction implements UnprotectedRootAction { + @Override + public String getUrlName() { + return "ping"; + } + + public HttpResponse doIndex() { + return HttpResponses.ok(); + } + } + +} diff --git a/war/src/main/js/components/dropdowns/jumplists.js b/war/src/main/js/components/dropdowns/jumplists.js index 9158be7c3d64..baa54711190e 100644 --- a/war/src/main/js/components/dropdowns/jumplists.js +++ b/war/src/main/js/components/dropdowns/jumplists.js @@ -11,7 +11,7 @@ function init() { * Appends a ⌄ button at the end of links which support jump lists */ function generateJumplistAccessors() { - document.querySelectorAll("A.model-link").forEach((link) => { + behaviorShim.specify("A.model-link", "-jumplist-", 999, (link) => { const isFirefox = navigator.userAgent.indexOf("Firefox") !== -1; // Firefox adds unwanted lines when copying buttons in text, so use a span instead const dropdownChevron = document.createElement(