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 extends ConverterMatcher> 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 extends ConverterMatcher> computeConverterClass(@NonNull
private Converter computeConverter(@NonNull Class> t) {
Class extends ConverterMatcher> 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(