diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 7b25e51631..a44d07b50c 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -58,7 +58,7 @@ updates: versions: [">=7.0.0"] - package-ecosystem: "maven" directory: "/" - target-branch: "stable-2.426" + target-branch: "stable-2.440" labels: - "into-lts" - "needs-justification" diff --git a/.github/release-drafter.yml b/.github/release-drafter.yml index 18da01b858..14058af657 100644 --- a/.github/release-drafter.yml +++ b/.github/release-drafter.yml @@ -7,7 +7,7 @@ tag-template: jenkins-$NEXT_MINOR_VERSION template: | _This is an automatically generated changelog draft for Jenkins weekly releases. - See https://www.jenkins.io/changelog/#v$NEXT_MINOR_VERSION for the official changelog for this release, or https://www.jenkins.io/changelog-old/#v$NEXT_MINOR_VERSION for releases older than around 7 months._ + See https://www.jenkins.io/changelog/$NEXT_MINOR_VERSION/ for the official changelog for this release. $CHANGES diff --git a/.github/workflows/announce-lts-rc.yml b/.github/workflows/announce-lts-rc.yml index b544abd78f..2f3c0de4e5 100644 --- a/.github/workflows/announce-lts-rc.yml +++ b/.github/workflows/announce-lts-rc.yml @@ -9,7 +9,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Post on Discourse - uses: roots/discourse-topic-github-release-action@fc9e50fa1a1ce6255ba4d03f104382845b79ad5f # v1.0.0 + uses: roots/discourse-topic-github-release-action@c30dc233349b7c6f24f52fb1c659cc64f13b5474 # v1.0.1 with: discourse-api-key: ${{ secrets.DISCOURSE_RELEASES_API_KEY }} discourse-base-url: https://community.jenkins.io/ diff --git a/.github/workflows/changelog.yml b/.github/workflows/changelog.yml index 8383062fcc..7634f375c1 100644 --- a/.github/workflows/changelog.yml +++ b/.github/workflows/changelog.yml @@ -24,7 +24,7 @@ jobs: # Drafts your next Release notes as Pull Requests are merged into "master" - name: Generate GitHub Release Draft id: release-drafter - uses: release-drafter/release-drafter@v5 + uses: release-drafter/release-drafter@v6 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Generates a YAML changelog file using https://github.com/jenkinsci/jenkins-core-changelog-generator diff --git a/.github/workflows/label-conflicting-pr.yml b/.github/workflows/label-conflicting-pr.yml index 00e1b80988..80079296c1 100644 --- a/.github/workflows/label-conflicting-pr.yml +++ b/.github/workflows/label-conflicting-pr.yml @@ -13,7 +13,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Label conflicting PRs - uses: eps1lon/actions-label-merge-conflict@v2.1.0 + uses: eps1lon/actions-label-merge-conflict@v3.0.0 with: dirtyLabel: "unresolved-merge-conflict" repoToken: "${{ secrets.GITHUB_TOKEN }}" diff --git a/.github/workflows/publish-release-artifact.yml b/.github/workflows/publish-release-artifact.yml index 86d55282eb..968df18529 100644 --- a/.github/workflows/publish-release-artifact.yml +++ b/.github/workflows/publish-release-artifact.yml @@ -16,11 +16,11 @@ jobs: is-rc: ${{ steps.set-version.outputs.is-rc }} steps: - uses: actions/checkout@v4 - - name: Set up JDK 11 + - name: Set up JDK 17 uses: actions/setup-java@v4 with: distribution: "temurin" - java-version: 11 + java-version: 17 cache: "maven" - name: Set version id: set-version @@ -73,7 +73,7 @@ jobs: wget -q https://get.jenkins.io/${REPO}/${PROJECT_VERSION}/${FILE_NAME} - name: Upload Release Asset id: upload-war - uses: softprops/action-gh-release@de2c0eb89ae2a093876385947365aca7b0e5f844 + uses: softprops/action-gh-release@9d7c94cfd0a1f3ed45544c887983e9fa900f0564 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: @@ -108,7 +108,7 @@ jobs: - name: Upload Release Asset id: upload-deb if: always() - uses: softprops/action-gh-release@de2c0eb89ae2a093876385947365aca7b0e5f844 + uses: softprops/action-gh-release@9d7c94cfd0a1f3ed45544c887983e9fa900f0564 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: @@ -144,7 +144,7 @@ jobs: - name: Upload Release Asset id: upload-rpm if: always() - uses: softprops/action-gh-release@de2c0eb89ae2a093876385947365aca7b0e5f844 + uses: softprops/action-gh-release@9d7c94cfd0a1f3ed45544c887983e9fa900f0564 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: @@ -180,7 +180,7 @@ jobs: - name: Upload Release Asset id: upload-msi if: always() - uses: softprops/action-gh-release@de2c0eb89ae2a093876385947365aca7b0e5f844 + uses: softprops/action-gh-release@9d7c94cfd0a1f3ed45544c887983e9fa900f0564 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: @@ -216,7 +216,7 @@ jobs: - name: Upload Release Asset id: upload-suse-rpm if: always() - uses: softprops/action-gh-release@de2c0eb89ae2a093876385947365aca7b0e5f844 + uses: softprops/action-gh-release@9d7c94cfd0a1f3ed45544c887983e9fa900f0564 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: diff --git a/.mvn/extensions.xml b/.mvn/extensions.xml index 1f36364094..a64ab6f242 100644 --- a/.mvn/extensions.xml +++ b/.mvn/extensions.xml @@ -1,4 +1,4 @@ - + io.jenkins.tools.incrementals git-changelist-maven-extension diff --git a/.mvn/jvm.config b/.mvn/jvm.config index f0106d1485..1d72cb7d8d 100644 --- a/.mvn/jvm.config +++ b/.mvn/jvm.config @@ -1 +1 @@ --Xmx1100m +-Xmx1400m diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 0e6dbffdda..77c012b44c 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -9,7 +9,7 @@ This page provides information about contributing code to the Jenkins core codeb 1. Fork the repository on GitHub 2. Clone the forked repository to your machine 3. Install the necessary development tools. In order to develop Jenkins, you need the following: - - Java Development Kit (JDK) 11 or 17. + - Java Development Kit (JDK) 11, 17 or 21. In the Jenkins project we usually use [Eclipse Temurin](https://adoptium.net/) or [OpenJDK](https://openjdk.java.net/), but you can use other JDKs as well. - Apache Maven 3.8.1 or above. You can [download Maven here](https://maven.apache.org/download.cgi). In the Jenkins project we usually use the most recent Maven release. diff --git a/ath.sh b/ath.sh index db84a79fff..e39c053ef6 100644 --- a/ath.sh +++ b/ath.sh @@ -6,7 +6,7 @@ set -o xtrace cd "$(dirname "$0")" # https://github.com/jenkinsci/acceptance-test-harness/releases -export ATH_VERSION=5740.vd30f30408987 +export ATH_VERSION=5814.vdc5d6d484b_40 if [[ $# -eq 0 ]]; then export JDK=17 diff --git a/bom/pom.xml b/bom/pom.xml index 28a945e232..45a4db8f22 100644 --- a/bom/pom.xml +++ b/bom/pom.xml @@ -22,7 +22,7 @@ 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. --> - + 4.0.0 @@ -38,9 +38,9 @@ THE SOFTWARE. The module contains dependencies that are used by a specific Jenkins version - 9.6 - 2.0.9 - 1822.v120278426e1c + 9.7 + 2.0.12 + 1843.ve7da_6a_9cf575 2.4.21 @@ -56,7 +56,7 @@ THE SOFTWARE. org.springframework spring-framework-bom - 5.3.31 + 5.3.33 pom import @@ -64,7 +64,7 @@ THE SOFTWARE. org.springframework.security spring-security-bom - 5.8.8 + 5.8.11 pom import @@ -82,7 +82,7 @@ THE SOFTWARE. com.google.guava guava - 32.1.3-jre + 33.1.0-jre @@ -113,7 +113,7 @@ THE SOFTWARE. commons-codec commons-codec - 1.16.0 + 1.16.1 commons-collections @@ -128,7 +128,7 @@ THE SOFTWARE. commons-io commons-io - 2.15.1 + 2.16.0 commons-jelly @@ -188,7 +188,7 @@ THE SOFTWARE. org.apache.commons commons-compress - 1.25.0 + 1.26.1 org.codehaus.groovy @@ -196,9 +196,9 @@ THE SOFTWARE. ${groovy.version} - org.connectbot.jbcrypt + org.connectbot jbcrypt - 1.0.0 + 1.0.2 @@ -366,7 +366,7 @@ THE SOFTWARE. commons-logging commons-logging - 1.3.0 + 1.3.1 provided diff --git a/cli/pom.xml b/cli/pom.xml index 21aabc88e6..342b406101 100644 --- a/cli/pom.xml +++ b/cli/pom.xml @@ -1,5 +1,5 @@ - + 4.0.0 @@ -15,7 +15,7 @@ https://github.com/jenkinsci/jenkins - 2.11.0 + 2.12.1 @@ -65,7 +65,7 @@ org.glassfish.tyrus.bundles tyrus-standalone-client-jdk - 2.1.4 + 2.1.5 true @@ -119,7 +119,7 @@ org.apache.maven.plugins maven-shade-plugin - 3.5.1 + 3.5.2 diff --git a/cli/src/main/java/hudson/cli/CLI.java b/cli/src/main/java/hudson/cli/CLI.java index 9aee43f6f9..684b01f549 100644 --- a/cli/src/main/java/hudson/cli/CLI.java +++ b/cli/src/main/java/hudson/cli/CLI.java @@ -58,14 +58,12 @@ import java.util.Properties; import java.util.logging.Handler; import java.util.logging.Level; import java.util.logging.Logger; -import javax.net.ssl.HostnameVerifier; import javax.net.ssl.HttpsURLConnection; import javax.net.ssl.SSLContext; -import javax.net.ssl.SSLSession; import javax.net.ssl.TrustManager; -import org.apache.commons.lang.StringUtils; import org.glassfish.tyrus.client.ClientManager; import org.glassfish.tyrus.client.ClientProperties; +import org.glassfish.tyrus.client.SslEngineConfigurator; import org.glassfish.tyrus.container.jdk.client.JdkClientContainer; /** @@ -134,6 +132,7 @@ public class CLI { String tokenEnv = System.getenv("JENKINS_API_TOKEN"); boolean strictHostKey = false; + boolean noCertificateCheck = false; while (!args.isEmpty()) { String head = args.get(0); @@ -179,17 +178,7 @@ public class CLI { } if (head.equals("-noCertificateCheck")) { LOGGER.info("Skipping HTTPS certificate checks altogether. Note that this is not secure at all."); - SSLContext context = SSLContext.getInstance("TLS"); - context.init(null, new TrustManager[]{new NoCheckTrustManager()}, new SecureRandom()); - HttpsURLConnection.setDefaultSSLSocketFactory(context.getSocketFactory()); - // bypass host name check, too. - HttpsURLConnection.setDefaultHostnameVerifier(new HostnameVerifier() { - @Override - @SuppressFBWarnings(value = "WEAK_HOSTNAME_VERIFIER", justification = "User set parameter to skip verifier.") - public boolean verify(String s, SSLSession sslSession) { - return true; - } - }); + noCertificateCheck = true; args = args.subList(1, args.size()); continue; } @@ -255,9 +244,9 @@ public class CLI { if (auth == null && bearer == null) { // -auth option not set - if (StringUtils.isNotBlank(userIdEnv) && StringUtils.isNotBlank(tokenEnv)) { - auth = StringUtils.defaultString(userIdEnv).concat(":").concat(StringUtils.defaultString(tokenEnv)); - } else if (StringUtils.isNotBlank(userIdEnv) || StringUtils.isNotBlank(tokenEnv)) { + if ((userIdEnv != null && !userIdEnv.isBlank()) && (tokenEnv != null && !tokenEnv.isBlank())) { + auth = userIdEnv.concat(":").concat(tokenEnv); + } else if ((userIdEnv != null && !userIdEnv.isBlank()) || (tokenEnv != null && !tokenEnv.isBlank())) { printUsage(Messages.CLI_BadAuth()); return -1; } // Otherwise, none credentials were set @@ -305,7 +294,7 @@ public class CLI { LOGGER.warning("Warning: -user ignored unless using -ssh"); } - CLIConnectionFactory factory = new CLIConnectionFactory(); + CLIConnectionFactory factory = new CLIConnectionFactory().noCertificateCheck(noCertificateCheck); String userInfo = new URL(url).getUserInfo(); if (userInfo != null) { factory = factory.basicAuth(userInfo); @@ -361,6 +350,13 @@ public class CLI { ClientManager client = ClientManager.createClient(JdkClientContainer.class.getName()); // ~ ContainerProvider.getWebSocketContainer() client.getProperties().put(ClientProperties.REDIRECT_ENABLED, true); // https://tyrus-project.github.io/documentation/1.13.1/index/tyrus-proprietary-config.html#d0e1775 + if (factory.noCertificateCheck) { + SSLContext sslContext = SSLContext.getInstance("TLS"); + sslContext.init(null, new TrustManager[] {new NoCheckTrustManager()}, new SecureRandom()); + SslEngineConfigurator sslEngineConfigurator = new SslEngineConfigurator(sslContext); + sslEngineConfigurator.setHostnameVerifier((s, sslSession) -> true); + client.getProperties().put(ClientProperties.SSL_ENGINE_CONFIGURATOR, sslEngineConfigurator); + } Session session = client.connectToServer(new CLIEndpoint(), ClientEndpointConfig.Builder.create().configurator(new Authenticator()).build(), URI.create(url.replaceFirst("^http", "ws") + "cli/ws")); PlainCLIProtocol.Output out = new PlainCLIProtocol.Output() { @Override @@ -386,8 +382,15 @@ public class CLI { } } - private static int plainHttpConnection(String url, List args, CLIConnectionFactory factory) throws IOException, InterruptedException { + private static int plainHttpConnection(String url, List args, CLIConnectionFactory factory) + throws GeneralSecurityException, IOException, InterruptedException { LOGGER.log(FINE, "Trying to connect to {0} via plain protocol over HTTP", url); + if (factory.noCertificateCheck) { + SSLContext sslContext = SSLContext.getInstance("TLS"); + sslContext.init(null, new TrustManager[] {new NoCheckTrustManager()}, new SecureRandom()); + HttpsURLConnection.setDefaultSSLSocketFactory(sslContext.getSocketFactory()); + HttpsURLConnection.setDefaultHostnameVerifier((s, sslSession) -> true); + } FullDuplexHttpStream streams = new FullDuplexHttpStream(new URL(url), "cli?remoting=false", factory.authorization); try (ClientSideImpl connection = new ClientSideImpl(new PlainCLIProtocol.FramedOutput(streams.getOutputStream()))) { connection.start(args); diff --git a/cli/src/main/java/hudson/cli/CLIConnectionFactory.java b/cli/src/main/java/hudson/cli/CLIConnectionFactory.java index 9b4295b07c..615f8bfa35 100644 --- a/cli/src/main/java/hudson/cli/CLIConnectionFactory.java +++ b/cli/src/main/java/hudson/cli/CLIConnectionFactory.java @@ -10,6 +10,7 @@ import java.util.Base64; */ public class CLIConnectionFactory { String authorization; + boolean noCertificateCheck; /** * For CLI connection that goes through HTTP, sometimes you need @@ -21,6 +22,16 @@ public class CLIConnectionFactory { return this; } + /** + * Skip TLS certificate and hostname verification checks. + * + * @since 2.444 + */ + public CLIConnectionFactory noCertificateCheck(boolean value) { + this.noCertificateCheck = value; + return this; + } + /** * Convenience method to call {@link #authorization} with the HTTP basic authentication. * Currently unused. diff --git a/core/pom.xml b/core/pom.xml index 4553986596..3d1f16532c 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -23,7 +23,7 @@ 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. --> - + 4.0.0 @@ -276,13 +276,20 @@ THE SOFTWARE. org.apache.commons commons-compress + + + + org.apache.commons + commons-lang3 + + org.codehaus.groovy groovy-all - org.connectbot.jbcrypt + org.connectbot jbcrypt diff --git a/core/src/main/java/hudson/ExtensionList.java b/core/src/main/java/hudson/ExtensionList.java index 202a76732b..c333b2de9f 100644 --- a/core/src/main/java/hudson/ExtensionList.java +++ b/core/src/main/java/hudson/ExtensionList.java @@ -468,7 +468,7 @@ public class ExtensionList extends AbstractList implements OnMaster { * @return the singleton instance of the given type in its list. * @throws IllegalStateException if there are no instances * - * @since TODO + * @since 2.435 */ public static @NonNull U lookupFirst(Class type) { var all = lookup(type); diff --git a/core/src/main/java/hudson/FilePath.java b/core/src/main/java/hudson/FilePath.java index cdc057ef9c..3525b931ef 100644 --- a/core/src/main/java/hudson/FilePath.java +++ b/core/src/main/java/hudson/FilePath.java @@ -28,6 +28,7 @@ package hudson; import static hudson.Util.fileToPath; import static hudson.Util.fixEmpty; +import static hudson.Util.fixEmptyAndTrim; import com.google.common.annotations.VisibleForTesting; import com.jcraft.jzlib.GZIPInputStream; @@ -86,6 +87,7 @@ import java.net.URI; import java.net.URL; import java.net.URLConnection; import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; import java.nio.file.CopyOption; import java.nio.file.FileSystemException; import java.nio.file.FileSystems; @@ -132,7 +134,6 @@ import org.apache.commons.compress.archivers.tar.TarArchiveEntry; import org.apache.commons.compress.archivers.tar.TarArchiveInputStream; import org.apache.commons.fileupload.FileItem; import org.apache.commons.io.input.CountingInputStream; -import org.apache.commons.lang.StringUtils; import org.apache.tools.ant.BuildException; import org.apache.tools.ant.DirectoryScanner; import org.apache.tools.ant.Project; @@ -344,21 +345,21 @@ public final class FilePath implements SerializableOnlyOverRemoting { String token = tokens.get(i); if (token.equals(".")) { tokens.remove(i); - if (tokens.size() > 0) + if (!tokens.isEmpty()) tokens.remove(i > 0 ? i - 1 : i); } else if (token.equals("..")) { if (i == 0) { // If absolute path, just remove: /../something // If relative path, not collapsible so leave as-is tokens.remove(0); - if (tokens.size() > 0) token += tokens.remove(0); + if (!tokens.isEmpty()) token += tokens.remove(0); if (!isAbsolute) buf.append(token); } else { // Normalize: remove something/.. plus separator before/after i -= 2; for (int j = 0; j < 3; j++) tokens.remove(i); if (i > 0) tokens.remove(i - 1); - else if (tokens.size() > 0) tokens.remove(0); + else if (!tokens.isEmpty()) tokens.remove(0); } } else i += 2; @@ -482,7 +483,6 @@ public final class FilePath implements SerializableOnlyOverRemoting { * @return The number of files/directories archived. * This is only really useful to check for a situation where nothing */ - @Restricted(NoExternalUse.class) public int zip(OutputStream out, DirScanner scanner, String verificationRoot, String prefix, OpenOption... openOptions) throws IOException, InterruptedException { ArchiverFactory archiverFactory = prefix == null ? ArchiverFactory.ZIP : ArchiverFactory.createZipWithPrefix(prefix, openOptions); return archive(archiverFactory, out, scanner, verificationRoot, openOptions); @@ -514,7 +514,6 @@ public final class FilePath implements SerializableOnlyOverRemoting { * @return The number of files/directories archived. * This is only really useful to check for a situation where nothing */ - @Restricted(NoExternalUse.class) public int archive(final ArchiverFactory factory, OutputStream os, final DirScanner scanner, String verificationRoot, OpenOption... openOptions) throws IOException, InterruptedException { final OutputStream out = channel != null ? new RemoteOutputStream(os) : os; @@ -761,7 +760,6 @@ public final class FilePath implements SerializableOnlyOverRemoting { } } - @Restricted(NoExternalUse.class) public boolean hasSymlink(FilePath verificationRoot, OpenOption... openOptions) throws IOException, InterruptedException { return act(new HasSymlink(verificationRoot == null ? null : verificationRoot.remote, openOptions)); } @@ -782,7 +780,6 @@ public final class FilePath implements SerializableOnlyOverRemoting { } } - @Restricted(NoExternalUse.class) public boolean containsSymlink(FilePath verificationRoot, OpenOption... openOptions) throws IOException, InterruptedException { return !list(new SymlinkRetainingFileFilter(verificationRoot, openOptions)).isEmpty(); } @@ -962,7 +959,7 @@ public final class FilePath implements SerializableOnlyOverRemoting { * * * @param archive - * The resource that represents the tgz/zip file. This URL must support the {@code Last-Modified} header. + * The resource that represents the tgz/zip file. This URL must support the {@code Last-Modified} header or the {@code ETag} header. * (For example, you could use {@link ClassLoader#getResource}.) * @param listener * If non-null, a message will be printed to this listener once this method decides to @@ -984,12 +981,18 @@ public final class FilePath implements SerializableOnlyOverRemoting { try { FilePath timestamp = this.child(".timestamp"); long lastModified = timestamp.lastModified(); + // https://httpwg.org/specs/rfc9110.html#field.etag is the ETag specification + // Read previously stored ETag if timestamp is available + String etag = timestamp.exists() ? fixEmptyAndTrim(timestamp.readToString()) : null; URLConnection con; try { con = ProxyConfiguration.open(archive); if (lastModified != 0) { con.setIfModifiedSince(lastModified); } + if (etag != null) { + con.setRequestProperty("If-None-Match", etag); + } con.connect(); } catch (IOException x) { if (this.exists()) { @@ -1016,7 +1019,7 @@ public final class FilePath implements SerializableOnlyOverRemoting { return false; } } - if (lastModified != 0) { + if (lastModified != 0 || etag != null) { if (responseCode == HttpURLConnection.HTTP_NOT_MODIFIED) { return false; } else if (responseCode != HttpURLConnection.HTTP_OK) { @@ -1027,8 +1030,12 @@ public final class FilePath implements SerializableOnlyOverRemoting { } long sourceTimestamp = con.getLastModified(); + String resultEtag = fixEmptyAndTrim(con.getHeaderField("ETag")); if (this.exists()) { + if (equalETags(etag, resultEtag)) { + return false; // already up to date + } if (lastModified != 0 && sourceTimestamp == lastModified) return false; // already up to date this.deleteContents(); @@ -1042,6 +1049,10 @@ public final class FilePath implements SerializableOnlyOverRemoting { // First try to download from the agent machine. try { act(new Unpack(archive)); + if (resultEtag != null && !equalETags(etag, resultEtag)) { + /* Store the ETag value in the timestamp file for later use */ + timestamp.write(resultEtag, "UTF-8"); + } timestamp.touch(sourceTimestamp); return true; } catch (IOException x) { @@ -1061,6 +1072,10 @@ public final class FilePath implements SerializableOnlyOverRemoting { throw new IOException(String.format("Failed to unpack %s (%d bytes read of total %d)", archive, cis.getByteCount(), con.getContentLength()), e); } + if (resultEtag != null && !equalETags(etag, resultEtag)) { + /* Store the ETag value in the timestamp file for later use */ + timestamp.write(resultEtag, "UTF-8"); + } timestamp.touch(sourceTimestamp); return true; } catch (IOException e) { @@ -1068,6 +1083,25 @@ public final class FilePath implements SerializableOnlyOverRemoting { } } + /* Return true if etag1 equals etag2 as defined by the etag specification + https://httpwg.org/specs/rfc9110.html#field.etag + */ + private boolean equalETags(String etag1, String etag2) { + if (etag1 == null || etag2 == null) { + return false; + } + if (etag1.equals(etag2)) { + return true; + } + /* Weak tags are identified by leading characters "W/" as a marker */ + /* Weak tag marker must not be considered in tag comparison. + This implements the weak comparison in the specification at + https://httpwg.org/specs/rfc9110.html#field.etag */ + String opaqueTag1 = etag1.startsWith("W/") ? etag1.substring(2) : etag1; + String opaqueTag2 = etag2.startsWith("W/") ? etag2.substring(2) : etag2; + return opaqueTag1.equals(opaqueTag2); + } + // this reads from arbitrary URL private static final class Unpack extends MasterToSlaveFileCallable { private final URL archive; @@ -1678,7 +1712,7 @@ public final class FilePath implements SerializableOnlyOverRemoting { public FilePath createTempDir(final String prefix, final String suffix) throws IOException, InterruptedException { try { String[] s; - if (StringUtils.isBlank(suffix)) { + if (suffix == null || suffix.isBlank()) { s = new String[]{prefix, "tmp"}; // see File.createTempFile - tmp is used if suffix is null } else { s = new String[]{prefix, suffix}; @@ -2019,7 +2053,6 @@ public final class FilePath implements SerializableOnlyOverRemoting { * @param openOptions the options to apply when opening. * @return Direct children of this directory. */ - @Restricted(NoExternalUse.class) @NonNull public List list(FilePath verificationRoot, OpenOption... openOptions) throws IOException, InterruptedException { return list(new OptionalDiscardingFileFilter(verificationRoot, openOptions)); @@ -2184,7 +2217,6 @@ public final class FilePath implements SerializableOnlyOverRemoting { return read(null, new OpenOption[0]); } - @Restricted(NoExternalUse.class) public InputStream read(FilePath rootPath, OpenOption... openOptions) throws IOException, InterruptedException { String rootPathString = rootPath == null ? null : rootPath.remote; if (channel == null) { @@ -2199,7 +2231,6 @@ public final class FilePath implements SerializableOnlyOverRemoting { return p.getIn(); } - @Restricted(NoExternalUse.class) public static InputStream newInputStreamDenyingSymlinkAsNeeded(File file, String verificationRoot, OpenOption... openOptions) throws IOException { InputStream inputStream = null; try { @@ -2216,7 +2247,6 @@ public final class FilePath implements SerializableOnlyOverRemoting { return inputStream; } - @Restricted(NoExternalUse.class) public static InputStream openInputStream(File file, OpenOption[] openOptions) throws IOException { return Files.newInputStream(fileToPath(file), stripLocalOptions(openOptions)); } @@ -2252,7 +2282,6 @@ public final class FilePath implements SerializableOnlyOverRemoting { } } - @Restricted(NoExternalUse.class) public static boolean isSymlink(File file, String root, OpenOption... openOptions) { if (isNoFollowLink(openOptions)) { if (Util.isSymlink(file.toPath())) { @@ -2268,7 +2297,6 @@ public final class FilePath implements SerializableOnlyOverRemoting { return isSymlink(visitorInfo.f, visitorInfo.verificationRoot, visitorInfo.openOptions); } - @Restricted(NoExternalUse.class) public static boolean isTmpDir(File file, String root, OpenOption... openOptions) { if (isIgnoreTmpDirs(openOptions)) { if (isTmpDir(file)) { @@ -2280,7 +2308,6 @@ public final class FilePath implements SerializableOnlyOverRemoting { return false; } - @Restricted(NoExternalUse.class) public static boolean isTmpDir(String filename, OpenOption... openOptions) { if (isIgnoreTmpDirs(openOptions)) { return isTmpDir(filename); @@ -2300,12 +2327,10 @@ public final class FilePath implements SerializableOnlyOverRemoting { return filename.length() > WorkspaceList.TMP_DIR_SUFFIX.length() && filename.endsWith(WorkspaceList.TMP_DIR_SUFFIX); } - @Restricted(NoExternalUse.class) public static boolean isNoFollowLink(OpenOption... openOptions) { return Arrays.asList(openOptions).contains(LinkOption.NOFOLLOW_LINKS); } - @Restricted(NoExternalUse.class) public static boolean isIgnoreTmpDirs(OpenOption... openOptions) { return Arrays.asList(openOptions).contains(DisplayOption.IGNORE_TMP_DIRS); } @@ -2810,8 +2835,8 @@ public final class FilePath implements SerializableOnlyOverRemoting { // local -> remote copy final Pipe pipe = Pipe.createLocalToRemote(); - Future future = target.actAsync(new ReadFromTar(target, pipe, description, compression)); - Future future2 = actAsync(new WriteToTar(scanner, pipe, compression)); + Future future = target.actAsync(new ReadFromTar(target, pipe, description, compression, StandardCharsets.UTF_8)); + Future future2 = actAsync(new WriteToTar(scanner, pipe, compression, StandardCharsets.UTF_8)); try { // JENKINS-9540 in case the reading side failed, report that error first future.get(); @@ -2823,9 +2848,9 @@ public final class FilePath implements SerializableOnlyOverRemoting { // remote -> local copy final Pipe pipe = Pipe.createRemoteToLocal(); - Future future = actAsync(new CopyRecursiveRemoteToLocal(pipe, scanner, compression)); + Future future = actAsync(new CopyRecursiveRemoteToLocal(pipe, scanner, compression, StandardCharsets.UTF_8)); try { - readFromTar(remote + '/' + description, new File(target.remote), compression.extract(pipe.getIn())); + readFromTar(remote + '/' + description, new File(target.remote), compression.extract(pipe.getIn()), StandardCharsets.UTF_8); } catch (IOException e) { // BuildException or IOException try { future.get(3, TimeUnit.SECONDS); @@ -2938,12 +2963,14 @@ public final class FilePath implements SerializableOnlyOverRemoting { private final String description; private final TarCompression compression; private final FilePath target; + private final String filenamesEncoding; - ReadFromTar(FilePath target, Pipe pipe, String description, @NonNull TarCompression compression) { + ReadFromTar(FilePath target, Pipe pipe, String description, @NonNull TarCompression compression, Charset filenamesEncoding) { this.target = target; this.pipe = pipe; this.description = description; this.compression = compression; + this.filenamesEncoding = filenamesEncoding.name(); } private static final long serialVersionUID = 1L; @@ -2951,7 +2978,7 @@ public final class FilePath implements SerializableOnlyOverRemoting { @Override public Void invoke(File f, VirtualChannel channel) throws IOException { try (InputStream in = pipe.getIn()) { - readFromTar(target.remote + '/' + description, f, compression.extract(in)); + readFromTar(target.remote + '/' + description, f, compression.extract(in), Charset.forName(filenamesEncoding)); return null; } } @@ -2961,18 +2988,20 @@ public final class FilePath implements SerializableOnlyOverRemoting { private final DirScanner scanner; private final Pipe pipe; private final TarCompression compression; + private final String filenamesEncoding; - WriteToTar(DirScanner scanner, Pipe pipe, @NonNull TarCompression compression) { + WriteToTar(DirScanner scanner, Pipe pipe, @NonNull TarCompression compression, Charset filenamesEncoding) { this.scanner = scanner; this.pipe = pipe; this.compression = compression; + this.filenamesEncoding = filenamesEncoding.name(); } private static final long serialVersionUID = 1L; @Override public Integer invoke(File f, VirtualChannel channel) throws IOException, InterruptedException { - return writeToTar(f, scanner, compression.compress(pipe.getOut())); + return writeToTar(f, scanner, compression.compress(pipe.getOut()), Charset.forName(filenamesEncoding)); } } @@ -2981,17 +3010,19 @@ public final class FilePath implements SerializableOnlyOverRemoting { private final Pipe pipe; private final DirScanner scanner; private final TarCompression compression; + private final String filenamesEncoding; - CopyRecursiveRemoteToLocal(Pipe pipe, DirScanner scanner, @NonNull TarCompression compression) { + CopyRecursiveRemoteToLocal(Pipe pipe, DirScanner scanner, @NonNull TarCompression compression, Charset filenamesEncoding) { this.pipe = pipe; this.scanner = scanner; this.compression = compression; + this.filenamesEncoding = filenamesEncoding.name(); } @Override public Integer invoke(File f, VirtualChannel channel) throws IOException { try (OutputStream out = pipe.getOut()) { - return writeToTar(f, scanner, compression.compress(out)); + return writeToTar(f, scanner, compression.compress(out), Charset.forName(filenamesEncoding)); } } } @@ -3023,22 +3054,26 @@ public final class FilePath implements SerializableOnlyOverRemoting { * @return * number of files/directories that are written. */ - private static Integer writeToTar(File baseDir, DirScanner scanner, OutputStream out) throws IOException { - Archiver tw = ArchiverFactory.TAR.create(out); + private static Integer writeToTar(File baseDir, DirScanner scanner, OutputStream out, Charset filenamesEncoding) throws IOException { + Archiver tw = ArchiverFactory.TAR.create(out, filenamesEncoding); try (tw) { scanner.scan(baseDir, tw); } return tw.countEntries(); } + private static void readFromTar(String name, File baseDir, InputStream in) throws IOException { + readFromTar(name, baseDir, in, Charset.defaultCharset()); + } + /** * Reads from a tar stream and stores obtained files to the base dir. * Supports large files > 10 GB since 1.627 when this was migrated to use commons-compress. */ - private static void readFromTar(String name, File baseDir, InputStream in) throws IOException { + private static void readFromTar(String name, File baseDir, InputStream in, Charset filenamesEncoding) throws IOException { // TarInputStream t = new TarInputStream(in); - try (TarArchiveInputStream t = new TarArchiveInputStream(in)) { + try (TarArchiveInputStream t = new TarArchiveInputStream(in, filenamesEncoding.name())) { TarArchiveEntry te; while ((te = t.getNextTarEntry()) != null) { File f = new File(baseDir, te.getName()); @@ -3675,7 +3710,6 @@ public final class FilePath implements SerializableOnlyOverRemoting { /** * Wraps {@link FileVisitor} to ignore symlinks. */ - @Restricted(NoExternalUse.class) public static FileVisitor ignoringSymlinks(final FileVisitor v, String verificationRoot, OpenOption... openOptions) { return validatingVisitor(FilePath::isNoFollowLink, visitorInfo -> !isSymlink(visitorInfo), @@ -3685,7 +3719,6 @@ public final class FilePath implements SerializableOnlyOverRemoting { /** * Wraps {@link FileVisitor} to ignore tmp directories. */ - @Restricted(NoExternalUse.class) public static FileVisitor ignoringTmpDirs(final FileVisitor v, String verificationRoot, OpenOption... openOptions) { return validatingVisitor(FilePath::isIgnoreTmpDirs, visitorInfo -> !isTmpDir(visitorInfo), @@ -3728,10 +3761,7 @@ public final class FilePath implements SerializableOnlyOverRemoting { /** * Check if the relative child is really a descendant after symlink resolution if any. - * - * TODO un-restrict it in a weekly after the patch */ - @Restricted(NoExternalUse.class) public boolean isDescendant(@NonNull String potentialChildRelativePath) throws IOException, InterruptedException { return act(new IsDescendant(potentialChildRelativePath)); } diff --git a/core/src/main/java/hudson/Functions.java b/core/src/main/java/hudson/Functions.java index a923ada89c..f4bd61351e 100644 --- a/core/src/main/java/hudson/Functions.java +++ b/core/src/main/java/hudson/Functions.java @@ -117,6 +117,9 @@ import java.net.URLEncoder; import java.nio.charset.StandardCharsets; import java.text.DateFormat; import java.text.DecimalFormat; +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; import java.util.ArrayList; import java.util.Arrays; import java.util.Calendar; @@ -163,7 +166,6 @@ import org.apache.commons.jelly.Script; import org.apache.commons.jelly.XMLOutput; import org.apache.commons.jexl.parser.ASTSizeFunction; import org.apache.commons.jexl.util.Introspector; -import org.apache.commons.lang.StringUtils; import org.jenkins.ui.icon.Icon; import org.jenkins.ui.icon.IconSet; import org.jvnet.tiger_types.Types; @@ -219,12 +221,12 @@ public class Functions { } public static String xsDate(Calendar cal) { - return Util.XS_DATETIME_FORMATTER.format(cal.getTime()); + return Util.XS_DATETIME_FORMATTER2.format(cal.toInstant()); } @Restricted(NoExternalUse.class) public static String iso8601DateTime(Date date) { - return Util.XS_DATETIME_FORMATTER.format(date); + return Util.XS_DATETIME_FORMATTER2.format(date.toInstant()); } /** @@ -236,7 +238,7 @@ public class Functions { } public static String rfc822Date(Calendar cal) { - return Util.RFC822_DATETIME_FORMATTER.format(cal.getTime()); + return DateTimeFormatter.RFC_1123_DATE_TIME.format(ZonedDateTime.ofInstant(cal.toInstant(), ZoneId.systemDefault())); } /** @@ -692,13 +694,13 @@ public class Functions { } @Restricted(NoExternalUse.class) - public static String getUserTimeZonePostfix() { + public static String getUserTimeZonePostfix(Date date) { if (!isUserTimeZoneOverride()) { return ""; } TimeZone tz = TimeZone.getTimeZone(getUserTimeZone()); - return tz.getDisplayName(tz.observesDaylightTime(), TimeZone.SHORT); + return tz.getDisplayName(tz.inDaylightTime(date), TimeZone.SHORT, getCurrentLocale()); } @Restricted(NoExternalUse.class) @@ -968,7 +970,7 @@ public class Functions { public static String getFooterURL() { if (footerURL == null) { footerURL = SystemProperties.getString("hudson.footerURL"); - if (StringUtils.isBlank(footerURL)) { + if (footerURL == null || footerURL.isBlank()) { footerURL = "https://www.jenkins.io/"; } } @@ -1909,7 +1911,7 @@ public class Functions { * Computes the link to the console for the run for the specified executable, taking {@link ConsoleUrlProvider} into account. * @param executable the executable (normally a {@link Run}) * @return the absolute URL for accessing the build console for the executable, or null if there is no build associated with the executable - * @since TODO + * @since 2.433 */ public static @CheckForNull String getConsoleUrl(Queue.Executable executable) { if (executable == null) { @@ -2194,7 +2196,7 @@ public class Functions { int firstPeriod = part.indexOf("."); return slash > 0 && firstPeriod > 0 && slash < firstPeriod; }).collect(Collectors.joining(" ")); - if (StringUtils.isBlank(views)) { + if (views == null || views.isBlank()) { // fallback to full thread name if there are no apparent views return threadName; } @@ -2242,10 +2244,19 @@ public class Functions { return SystemProperties.getBoolean("hudson.security.WipeOutPermission"); } + @Deprecated public static String createRenderOnDemandProxy(JellyContext context, String attributesToCapture) { return Stapler.getCurrentRequest().createJavaScriptProxy(new RenderOnDemandClosure(context, attributesToCapture)); } + /** + * Called from renderOnDemand.jelly to generate the parameters for the proxy object generation. + */ + @Restricted(NoExternalUse.class) + public static StaplerRequest.RenderOnDemandParameters createRenderOnDemandProxyParameters(JellyContext context, String attributesToCapture) { + return Stapler.getCurrentRequest().createJavaScriptProxyParameters(new RenderOnDemandClosure(context, attributesToCapture)); + } + public static String getCurrentDescriptorByNameUrl() { return Descriptor.getCurrentDescriptorByNameUrl(); } @@ -2406,7 +2417,7 @@ public class Functions { } private static @NonNull String filterIconNameClasses(@NonNull String classNames) { - return Arrays.stream(StringUtils.split(classNames, ' ')) + return Arrays.stream(classNames.split(" ")) .filter(className -> className.startsWith("icon-")) .collect(Collectors.joining(" ")); } diff --git a/core/src/main/java/hudson/Main.java b/core/src/main/java/hudson/Main.java index 625fef0c52..97bd9dfa84 100644 --- a/core/src/main/java/hudson/Main.java +++ b/core/src/main/java/hudson/Main.java @@ -126,10 +126,10 @@ public class Main { } // get a crumb to pass the csrf check - String crumbField = null, crumbValue = null; + String crumbField = null, crumbValue = null, sessionCookies = null; try { HttpURLConnection con = open(new URL(home + - "crumbIssuer/api/xml?xpath=concat(//crumbRequestField,\":\",//crumb)'")); + "crumbIssuer/api/xml?xpath=concat(//crumbRequestField,\":\",//crumb)")); if (auth != null) con.setRequestProperty("Authorization", auth); String line = IOUtils.readFirstLine(con.getInputStream(), "UTF-8"); String[] components = line.split(":"); @@ -137,6 +137,7 @@ public class Main { crumbField = components[0]; crumbValue = components[1]; } + sessionCookies = con.getHeaderField("Set-Cookie"); } catch (IOException e) { // presumably this Hudson doesn't use CSRF protection } @@ -173,10 +174,12 @@ public class Main { if (auth != null) con.setRequestProperty("Authorization", auth); if (crumbField != null && crumbValue != null) { con.setRequestProperty(crumbField, crumbValue); + con.setRequestProperty("Cookie", sessionCookies); } con.setDoOutput(true); // this tells HttpURLConnection not to buffer the whole thing con.setFixedLengthStreamingMode((int) tmpFile.length()); + con.setRequestProperty("Content-Type", "application/xml"); con.connect(); // send the data try (InputStream in = Files.newInputStream(tmpFile.toPath())) { diff --git a/core/src/main/java/hudson/PluginManager.java b/core/src/main/java/hudson/PluginManager.java index a7d74fcee4..d666fc2070 100644 --- a/core/src/main/java/hudson/PluginManager.java +++ b/core/src/main/java/hudson/PluginManager.java @@ -41,6 +41,7 @@ import hudson.PluginWrapper.Dependency; import hudson.init.InitMilestone; import hudson.init.InitStrategy; import hudson.init.InitializerFinder; +import hudson.lifecycle.Lifecycle; import hudson.model.AbstractItem; import hudson.model.AbstractModelObject; import hudson.model.AdministrativeMonitor; @@ -144,7 +145,6 @@ import org.apache.commons.fileupload.servlet.ServletFileUpload; import org.apache.commons.io.FileUtils; import org.apache.commons.io.FilenameUtils; import org.apache.commons.io.IOUtils; -import org.apache.commons.lang.StringUtils; import org.apache.commons.logging.LogFactory; import org.jenkinsci.Symbol; import org.jvnet.hudson.reactor.Executable; @@ -272,7 +272,7 @@ public abstract class PluginManager extends AbstractModelObject implements OnMas */ public static @NonNull PluginManager createDefault(@NonNull Jenkins jenkins) { String pmClassName = SystemProperties.getString(CUSTOM_PLUGIN_MANAGER); - if (!StringUtils.isBlank(pmClassName)) { + if (pmClassName != null && !pmClassName.isBlank()) { LOGGER.log(FINE, String.format("Use of custom plugin manager [%s] requested.", pmClassName)); try { final Class klass = Class.forName(pmClassName).asSubclass(PluginManager.class); @@ -370,7 +370,7 @@ public abstract class PluginManager extends AbstractModelObject implements OnMas throw new UncheckedIOException(e); } String workDir = SystemProperties.getString(PluginManager.class.getName() + ".workDir"); - this.workDir = StringUtils.isBlank(workDir) ? null : new File(workDir); + this.workDir = workDir == null || workDir.isBlank() ? null : new File(workDir); strategy = createPluginStrategy(); } @@ -924,6 +924,9 @@ public abstract class PluginManager extends AbstractModelObject implements OnMas throw new RestartRequiredException(Messages._PluginManager_PluginIsAlreadyInstalled_RestartRequired(sn)); } } + if (!Lifecycle.get().supportsDynamicLoad()) { + throw new RestartRequiredException(Messages._PluginManager_LifecycleDoesNotSupportDynamicLoad_RestartRequired()); + } if (p == null) { p = strategy.createPluginWrapper(arc); } @@ -1429,16 +1432,16 @@ public abstract class PluginManager extends AbstractModelObject implements OnMas for (UpdateSite site : Jenkins.get().getUpdateCenter().getSiteList()) { List sitePlugins = site.getAvailables().stream() .filter(plugin -> { - if (StringUtils.isBlank(query)) { + if (query == null || query.isBlank()) { return true; } - return StringUtils.containsIgnoreCase(plugin.name, query) || - StringUtils.containsIgnoreCase(plugin.title, query) || - StringUtils.containsIgnoreCase(plugin.excerpt, query) || + return (plugin.name != null && plugin.name.toLowerCase().contains(query.toLowerCase())) || + (plugin.title != null && plugin.title.toLowerCase().contains(query.toLowerCase())) || + (plugin.excerpt != null && plugin.excerpt.toLowerCase().contains(query.toLowerCase())) || plugin.hasCategory(query) || plugin.getCategoriesStream() .map(UpdateCenter::getCategoryDisplayName) - .anyMatch(category -> StringUtils.containsIgnoreCase(category, query)) || + .anyMatch(category -> category != null && category.toLowerCase().contains(query.toLowerCase())) || plugin.hasWarnings() && query.equalsIgnoreCase("warning:"); }) .limit(Math.max(limit - plugins.size(), 1)) @@ -1468,7 +1471,7 @@ public abstract class PluginManager extends AbstractModelObject implements OnMas jsonObject.put("title", plugin.title); jsonObject.put("displayName", plugin.getDisplayName()); if (plugin.wiki == null || !(plugin.wiki.startsWith("https://") || plugin.wiki.startsWith("http://"))) { - jsonObject.put("wiki", StringUtils.EMPTY); + jsonObject.put("wiki", ""); } else { jsonObject.put("wiki", plugin.wiki); } @@ -1864,9 +1867,10 @@ public abstract class PluginManager extends AbstractModelObject implements OnMas File tmpDir = Files.createTempDirectory("uploadDir").toFile(); ServletFileUpload upload = new ServletFileUpload(new DiskFileItemFactory(DiskFileItemFactory.DEFAULT_SIZE_THRESHOLD, tmpDir)); List items = upload.parseRequest(req); - if (StringUtils.isNotBlank(items.get(1).getString())) { + String string = items.get(1).getString(); + if (string != null && !string.isBlank()) { // this is a URL deployment - fileName = items.get(1).getString(); + fileName = string; copier = new UrlPluginCopier(fileName); } else { // this is a file upload @@ -1909,7 +1913,7 @@ public abstract class PluginManager extends AbstractModelObject implements OnMas } String deps = m.getMainAttributes().getValue("Plugin-Dependencies"); - if (StringUtils.isNotBlank(deps)) { + if (deps != null && !deps.isBlank()) { // now we get to parse it! String[] plugins = deps.split(","); for (String p : plugins) { @@ -1940,7 +1944,7 @@ public abstract class PluginManager extends AbstractModelObject implements OnMas @Restricted(NoExternalUse.class) @RequirePOST public FormValidation doCheckPluginUrl(StaplerRequest request, @QueryParameter String value) throws IOException { - if (StringUtils.isNotBlank(value)) { + if (value != null && !value.isBlank()) { try { URL url = new URL(value); if (!url.getProtocol().startsWith("http")) { diff --git a/core/src/main/java/hudson/PluginWrapper.java b/core/src/main/java/hudson/PluginWrapper.java index 2161e50108..faae99b04e 100644 --- a/core/src/main/java/hudson/PluginWrapper.java +++ b/core/src/main/java/hudson/PluginWrapper.java @@ -73,7 +73,6 @@ import jenkins.model.Jenkins; import jenkins.plugins.DetachedPluginsUtil; import jenkins.security.UpdateSiteWarningsMonitor; import jenkins.util.URLClassLoader2; -import org.apache.commons.lang.StringUtils; import org.apache.commons.logging.LogFactory; import org.kohsuke.accmod.Restricted; import org.kohsuke.accmod.restrictions.Beta; @@ -497,7 +496,12 @@ public class PluginWrapper implements Comparable, ModelObject { @Override public String getDisplayName() { - return StringUtils.removeStart(getLongName(), "Jenkins "); + String displayName = getLongName(); + String removePrefix = "Jenkins "; + if (displayName != null && displayName.startsWith(removePrefix)) { + return displayName.substring(removePrefix.length()); + } + return displayName; } public Api getApi() { diff --git a/core/src/main/java/hudson/ProxyConfiguration.java b/core/src/main/java/hudson/ProxyConfiguration.java index 6e54fbb0e3..fa4206521e 100644 --- a/core/src/main/java/hudson/ProxyConfiguration.java +++ b/core/src/main/java/hudson/ProxyConfiguration.java @@ -248,7 +248,7 @@ public final class ProxyConfiguration extends AbstractDescribableImplEquivalent to {@code newHttpClientBuilder().followRedirects(HttpClient.Redirect.NORMAL).build()}. * - *

The Jenkins-specific default settings include a proxy server and proxy authentication (as - * configured by {@link ProxyConfiguration}) and a connection timeout (as configured by {@link - * ProxyConfiguration#DEFAULT_CONNECT_TIMEOUT_MILLIS}). - * * @return a new {@link HttpClient} * @since 2.379 + * @see #newHttpClientBuilder */ public static HttpClient newHttpClient() { return newHttpClientBuilder().followRedirects(HttpClient.Redirect.NORMAL).build(); @@ -383,6 +381,12 @@ public final class ProxyConfiguration extends AbstractDescribableImplWarning: if both {@link #getName} and {@link #getUserName} are set + * (meaning that an authenticated proxy is defined), + * you will not be able to pass an {@code Authorization} header to the real server + * when running on Java 17 and later + * (pending JDK-8326949. + * * @return an {@link HttpClient.Builder} * @since 2.379 */ diff --git a/core/src/main/java/hudson/Util.java b/core/src/main/java/hudson/Util.java index 24b04ce308..abe9f97d65 100644 --- a/core/src/main/java/hudson/Util.java +++ b/core/src/main/java/hudson/Util.java @@ -84,6 +84,8 @@ import java.text.NumberFormat; import java.text.ParseException; import java.time.LocalDate; import java.time.ZoneId; +import java.time.ZoneOffset; +import java.time.format.DateTimeFormatter; import java.time.temporal.ChronoUnit; import java.util.ArrayList; import java.util.Collection; @@ -1872,9 +1874,20 @@ public class Util { " - + @@ -53,12 +53,14 @@ THE SOFTWARE. icon="symbol-chevron-down" tooltip="${null}" clazz="jenkins-button--primary"> - + + + diff --git a/core/src/main/resources/hudson/PluginManager/sidepanel.jelly b/core/src/main/resources/hudson/PluginManager/sidepanel.jelly index 21755b3207..0d3fc3fb43 100644 --- a/core/src/main/resources/hudson/PluginManager/sidepanel.jelly +++ b/core/src/main/resources/hudson/PluginManager/sidepanel.jelly @@ -27,7 +27,7 @@ THE SOFTWARE. --> - + diff --git a/core/src/main/resources/hudson/PluginManager/updates.jelly b/core/src/main/resources/hudson/PluginManager/updates.jelly index 2540273127..323799953a 100644 --- a/core/src/main/resources/hudson/PluginManager/updates.jelly +++ b/core/src/main/resources/hudson/PluginManager/updates.jelly @@ -236,11 +236,5 @@ THE SOFTWARE. -

diff --git a/core/src/main/resources/hudson/logging/LogRecorder/index.jelly b/core/src/main/resources/hudson/logging/LogRecorder/index.jelly index b0f3d8cd09..0428fbc5bd 100644 --- a/core/src/main/resources/hudson/logging/LogRecorder/index.jelly +++ b/core/src/main/resources/hudson/logging/LogRecorder/index.jelly @@ -26,7 +26,8 @@ THE SOFTWARE. Log view --> - + @@ -46,19 +47,21 @@ THE SOFTWARE. - ${%Atom feed} + -
- -
- -
- ${%Delete log recorder} -
+ + + +
+ +
+ ${%Delete log recorder} +
+
diff --git a/core/src/main/resources/hudson/logging/LogRecorderManager/feeds.jelly b/core/src/main/resources/hudson/logging/LogRecorderManager/feeds.jelly index 7b0fe2e910..14cfa9c589 100644 --- a/core/src/main/resources/hudson/logging/LogRecorderManager/feeds.jelly +++ b/core/src/main/resources/hudson/logging/LogRecorderManager/feeds.jelly @@ -24,23 +24,14 @@ THE SOFTWARE. - - -
- -
- ${%All} -
- -
- -
- ${%WARNING} -
- -
- -
- ${%SEVERE} -
+ + + + diff --git a/core/src/main/resources/hudson/markup/EscapedMarkupFormatter/config.properties b/core/src/main/resources/hudson/markup/EscapedMarkupFormatter/config.properties index 2b13a6c2f2..0eba945cfe 100644 --- a/core/src/main/resources/hudson/markup/EscapedMarkupFormatter/config.properties +++ b/core/src/main/resources/hudson/markup/EscapedMarkupFormatter/config.properties @@ -1 +1,2 @@ -blurb=Treats all input as plain text. HTML unsafe characters like < and & are escaped to their respective character entities. +blurb=Shows descriptions mostly as written. \ + HTML unsafe characters like < and & are escaped to their respective character entities, and line breaks are converted to their HTML equivalent. diff --git a/core/src/main/resources/hudson/markup/EscapedMarkupFormatter/config_tr.properties b/core/src/main/resources/hudson/markup/EscapedMarkupFormatter/config_tr.properties new file mode 100644 index 0000000000..4e834920c8 --- /dev/null +++ b/core/src/main/resources/hudson/markup/EscapedMarkupFormatter/config_tr.properties @@ -0,0 +1,2 @@ +blurb=Açıklamaları çoğunlukla yazıldığı gibi gösterir. \ + HTML içerisinde bulunması güvenli olmayan < ve & gibi karakterler ilgili karakter varlıklarına, satır sonları da HTML eşdeğerlerine dönüştürülür. diff --git a/core/src/main/resources/hudson/model/AbstractBuild/details.jelly b/core/src/main/resources/hudson/model/AbstractBuild/details.jelly new file mode 100644 index 0000000000..199d7f2ee3 --- /dev/null +++ b/core/src/main/resources/hudson/model/AbstractBuild/details.jelly @@ -0,0 +1,30 @@ + + + + + + ${%on} + + diff --git a/core/src/main/resources/hudson/model/AbstractBuild/index.jelly b/core/src/main/resources/hudson/model/AbstractBuild/index.jelly deleted file mode 100644 index aba425c342..0000000000 --- a/core/src/main/resources/hudson/model/AbstractBuild/index.jelly +++ /dev/null @@ -1,148 +0,0 @@ - - - - - - - - -
- - - -
- ${%startedAgo(it.timestampString)} -
- - ${%beingExecuted(it.executor.timestampString)} - - - ${%Took} ${it.durationString} - - - ${%on} - -
-
- - ${it.displayName} () - -
- -
- - - - - - - - - - - - - - ${%Changes in dependency} -
    - -
  1. - ${dep.project.displayName} - - - - - ${dep.from.displayName} - - - ? - - - - → - - - ${dep.to.displayName} - - (${%detail}) -
  2. -
    -
-
-
- - ${%Not yet determined} - - - ${%Failed to determine} (${%log}) - -
-
- - - - - -
- - - - - - -

${%Upstream Builds}

- -
- - -

${%Downstream Builds}

- -
-
-
-
diff --git a/core/src/main/resources/hudson/model/AbstractBuild/main.jelly b/core/src/main/resources/hudson/model/AbstractBuild/main.jelly new file mode 100644 index 0000000000..0e3954202c --- /dev/null +++ b/core/src/main/resources/hudson/model/AbstractBuild/main.jelly @@ -0,0 +1,93 @@ + + + + + + + +

${%Upstream Builds}

+ +
+ + + +

${%Downstream Builds}

+ +
+ + + + + + + ${%Changes in dependency} +
    + +
  1. + ${dep.project.displayName} + + + + + ${dep.from.displayName} + + + ? + + + + → + + + ${dep.to.displayName} + + (${%detail}) +
  2. +
    +
+
+
diff --git a/core/src/main/resources/hudson/model/AbstractProject/configure-common.jelly b/core/src/main/resources/hudson/model/AbstractProject/configure-common.jelly index 096922c279..25121308d1 100644 --- a/core/src/main/resources/hudson/model/AbstractProject/configure-common.jelly +++ b/core/src/main/resources/hudson/model/AbstractProject/configure-common.jelly @@ -35,7 +35,7 @@ THE SOFTWARE. - @@ -54,7 +54,7 @@ THE SOFTWARE. - + diff --git a/core/src/main/resources/hudson/model/AllView/noJob.groovy b/core/src/main/resources/hudson/model/AllView/noJob.groovy index 6daf221b40..f62125fed9 100644 --- a/core/src/main/resources/hudson/model/AllView/noJob.groovy +++ b/core/src/main/resources/hudson/model/AllView/noJob.groovy @@ -81,7 +81,7 @@ div { // we're in a folder section(class: "empty-state-section") { - h2(_("This folder is empty"), class: "h4") + h2(_("thisFolderIsEmpty"), class: "h4") ul(class: "empty-state-section-list") { li(class: "content-block") { diff --git a/core/src/main/resources/hudson/model/AllView/noJob.properties b/core/src/main/resources/hudson/model/AllView/noJob.properties index 157679ee2c..c5c114df18 100644 --- a/core/src/main/resources/hudson/model/AllView/noJob.properties +++ b/core/src/main/resources/hudson/model/AllView/noJob.properties @@ -26,6 +26,7 @@ setUpDistributedBuilds=Set up a distributed build setUpAgent=Set up an agent setUpCloud=Configure a cloud learnMoreDistributedBuilds=Learn more about distributed builds +thisFolderIsEmpty=This folder is empty startBuilding=Start building your software project createJob=Create a job diff --git a/core/src/main/resources/hudson/model/AllView/noJob_fr.properties b/core/src/main/resources/hudson/model/AllView/noJob_fr.properties index 32d9fa71c5..61cbb6192b 100644 --- a/core/src/main/resources/hudson/model/AllView/noJob_fr.properties +++ b/core/src/main/resources/hudson/model/AllView/noJob_fr.properties @@ -28,3 +28,4 @@ setUpDistributedBuilds=Configurer un build distribué setUpAgent=Mettre en place un agent setUpCloud=Configurer un cloud learnMoreDistributedBuilds=En apprendre plus sur les builds distribués +thisFolderIsEmpty=Ce dossier est vide diff --git a/core/src/main/resources/hudson/model/ComputerSet/_legend.jelly b/core/src/main/resources/hudson/model/ComputerSet/_legend.jelly new file mode 100644 index 0000000000..07871a6fe5 --- /dev/null +++ b/core/src/main/resources/hudson/model/ComputerSet/_legend.jelly @@ -0,0 +1,34 @@ + + +

${%Status}

+ +
+
+ +
+
+ ${%online} +
+ +
+ +
+
+ ${%temp-offline} +
+ +
+ +
+
+ ${%not-accepting} +
+ +
+ +
+
+ ${%offline} +
+
+
diff --git a/core/src/main/resources/hudson/model/ComputerSet/_legend.properties b/core/src/main/resources/hudson/model/ComputerSet/_legend.properties new file mode 100644 index 0000000000..075df2f8a1 --- /dev/null +++ b/core/src/main/resources/hudson/model/ComputerSet/_legend.properties @@ -0,0 +1,11 @@ +online=The agent is online and will accept tasks for execution when it has free executors. +temp-offline=The agent has been explicitly set to temporarily offline by a user. If the agent is still connected any \ + running tasks will finish. No new tasks will start until the agent is put online again by a user. +not-accepting=The agent is not accepting tasks for execution. This usually means that the agents availability settings \ + prevent task execution, e.g. when using Bring this agent online according to a schedule and being outside \ + the time window. The agent might still execute tasks that have started while it was accepting tasks. Cloud agents that are \ + intended to run only a single task might also use this.
\ + Agents will be shown as suspended in the Build Executor Status widgets. +offline=None of the above conditions applies. The agent is not connected or it is still connected but a node monitor decided \ + that the agent is not in a healthy state. An agent in this state should be investigated. +Legend=Icon legend diff --git a/core/src/main/resources/hudson/model/ComputerSet/configure.jelly b/core/src/main/resources/hudson/model/ComputerSet/configure.jelly index 4bbd52b4c4..9e58aeda59 100644 --- a/core/src/main/resources/hudson/model/ComputerSet/configure.jelly +++ b/core/src/main/resources/hudson/model/ComputerSet/configure.jelly @@ -42,7 +42,8 @@ THE SOFTWARE. + instances="${it.monitors}" + forceRowSet="true"/> diff --git a/core/src/main/resources/hudson/model/ComputerSet/configure.properties b/core/src/main/resources/hudson/model/ComputerSet/configure.properties index e2e74cba5e..1df5cc6107 100644 --- a/core/src/main/resources/hudson/model/ComputerSet/configure.properties +++ b/core/src/main/resources/hudson/model/ComputerSet/configure.properties @@ -1 +1,2 @@ -description=Jenkins monitors each attached node for disk space, free temp space, free swap, clock time/sync, and response time. Nodes will be taken offline if any of these values go outside of the configured threshold. +description=Jenkins monitors each attached node for various metrics like free disk space, free temp space, free swap, clock time/sync, response time and others provided by plugins. \ + Some of these monitors are able to mark nodes temporarily offline, when the values go outside the configured thresholds. diff --git a/core/src/main/resources/hudson/model/ComputerSet/index.jelly b/core/src/main/resources/hudson/model/ComputerSet/index.jelly index ec4d2ab978..bc558e822c 100644 --- a/core/src/main/resources/hudson/model/ComputerSet/index.jelly +++ b/core/src/main/resources/hudson/model/ComputerSet/index.jelly @@ -56,8 +56,8 @@ THE SOFTWARE. - - + +
@@ -123,6 +123,17 @@ THE SOFTWARE.
S
+ +
+ +
+
+ + + diff --git a/core/src/spotbugs/excludesFilter.xml b/core/src/spotbugs/excludesFilter.xml index af99fff505..7d36952fc3 100644 --- a/core/src/spotbugs/excludesFilter.xml +++ b/core/src/spotbugs/excludesFilter.xml @@ -53,6 +53,125 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + 4.0.0 org.jenkins-ci jenkins - 1.108 + 1.112 @@ -73,7 +73,7 @@ THE SOFTWARE. - 2.437 + 2.453 -SNAPSHOT @@ -86,7 +86,7 @@ THE SOFTWARE. https://www.jenkins.io/changelog - 3198.v03a_401881f3e + 3206.vb_15dcf73f6a_9 4.13 @@ -98,7 +98,7 @@ THE SOFTWARE. 1.29 false - 6.16 + 6.18 -
- - org.apache.maven.plugins - maven-dependency-plugin - - org.apache.maven.plugins maven-compiler-plugin @@ -161,16 +151,6 @@ THE SOFTWARE. - - org.apache.maven.plugins - maven-gpg-plugin - - - - org.apache.maven.plugins - maven-install-plugin - - org.apache.maven.plugins maven-javadoc-plugin @@ -180,16 +160,6 @@ THE SOFTWARE. true - - org.apache.maven.plugins - maven-jar-plugin - - - - org.apache.maven.plugins - maven-war-plugin - - org.apache.maven.plugins maven-surefire-plugin @@ -218,11 +188,6 @@ THE SOFTWARE. ${hudson.sign.keystore} - - org.apache.maven.plugins - maven-resources-plugin - - org.kohsuke access-modifier-checker @@ -268,21 +233,6 @@ THE SOFTWARE. antlr4-maven-plugin ${antlr.version} - - org.apache.maven.plugins - maven-pmd-plugin - - - - org.jvnet.updatecenter2 - maven-makepkgs-plugin - 0.6.2 - - - org.apache.maven.plugins - maven-enforcer-plugin - - org.eclipse.m2e @@ -332,7 +282,7 @@ THE SOFTWARE. com.puppycrawl.tools checkstyle - 10.12.6 + 10.15.0 @@ -348,7 +298,7 @@ THE SOFTWARE. org.jacoco jacoco-maven-plugin - 0.8.11 + 0.8.12 @@ -426,11 +376,6 @@ THE SOFTWARE. - - org.apache.maven.plugins - maven-checkstyle-plugin - - diff --git a/test/pom.xml b/test/pom.xml index bb493bb4a9..a0657ded8a 100644 --- a/test/pom.xml +++ b/test/pom.xml @@ -22,7 +22,7 @@ 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. --> - + 4.0.0 @@ -73,7 +73,7 @@ THE SOFTWARE. io.jenkins.plugins font-awesome-api - 6.5.1-1 + 6.5.1-3
io.jenkins.plugins @@ -88,7 +88,7 @@ THE SOFTWARE. io.jenkins.plugins plugin-util-api - 3.6.0 + 4.1.0 org.jenkins-ci.plugins @@ -101,22 +101,33 @@ THE SOFTWARE. display-url-api 2.200.vb_9327d658781 + + + org.jenkins-ci.plugins + scm-api + 689.v237b_6d3a_ef7f + org.jenkins-ci.plugins script-security - 1294.v99333c047434 + 1326.vdb_c154de8669 org.jenkins-ci.plugins.workflow workflow-api - 1283.v99c10937efcb_ + 1291.v51fd2a_625da_7 + + + org.jenkins-ci.plugins.workflow + workflow-step-api + 657.v03b_e8115821b_ org.jenkins-ci.plugins.workflow workflow-support - 865.v43e78cc44e0d + 896.v175a_a_9c5b_78f @@ -131,7 +142,7 @@ THE SOFTWARE. ${project.groupId} jenkins-test-harness - 2137.vb_4eb_49f51c4b_ + 2186.v5a_b_e6dd02a_35 test @@ -172,7 +183,7 @@ THE SOFTWARE. org.awaitility awaitility - 4.2.0 + 4.2.1 test @@ -200,43 +211,43 @@ THE SOFTWARE. org.jenkins-ci.plugins cloudbees-folder - 6.858.v898218f3609d + 6.928.v7c780211d66e test org.jenkins-ci.plugins credentials - 1311.vcf0a_900b_37c2 + 1337.v60b_d7b_c7b_c9f test org.jenkins-ci.plugins junit - 1252.vfc2e5efa_294f + 1265.v65b_14fa_f12f0 test org.jenkins-ci.plugins mailer - 463.vedf8358e006b_ + 472.vf7c289a_4b_420 test org.jenkins-ci.plugins matrix-auth - 3.2.1 + 3.2.2 test org.jenkins-ci.plugins matrix-project - 818.v7eb_e657db_924 + 822.824.v14451b_c0fd42 test org.jenkins-ci.plugins structs - 325.vcb_307d2a_2782 + 337.v1b_04ea_4df7c8 test @@ -318,11 +329,27 @@ THE SOFTWARE. io.jenkins.plugins design-library - 91.v257f311ea_1dc + 286.v6643a_81e02b_8 hpi ${project.build.outputDirectory}/plugins design-library.jpi + + io.jenkins.plugins + prism-api + 1.29.0-13 + hpi + ${project.build.outputDirectory}/plugins + prism-api.jpi + + + io.jenkins.plugins + bootstrap5-api + 5.3.3-1 + hpi + ${project.build.outputDirectory}/plugins + bootstrap5-api.jpi + diff --git a/test/src/test/java/hudson/PluginManagerTest.java b/test/src/test/java/hudson/PluginManagerTest.java index dddd9239d9..c2fc5dd872 100644 --- a/test/src/test/java/hudson/PluginManagerTest.java +++ b/test/src/test/java/hudson/PluginManagerTest.java @@ -794,7 +794,7 @@ public class PluginManagerTest { PluginManagerUtil.getCheckForUpdatesButton(p).click(); HtmlPage available = wc.goTo("pluginManager/available"); assertTrue(available.querySelector(".alert-danger") - .getTextContent().contains("This plugin is built for Jenkins 2.999")); + .getTextContent().contains("This plugin is built for Jenkins 9999999")); wc.waitForBackgroundJavaScript(100); HtmlAnchor anchor = available.querySelector(".jenkins-table__link"); diff --git a/test/src/test/java/hudson/ProxyConfigurationManagerGUITest.java b/test/src/test/java/hudson/ProxyConfigurationManagerGUITest.java new file mode 100644 index 0000000000..5309e43d04 --- /dev/null +++ b/test/src/test/java/hudson/ProxyConfigurationManagerGUITest.java @@ -0,0 +1,69 @@ +/* + * The MIT License + * + * Copyright 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 + * 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.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; + +import hudson.util.Secret; +import java.io.File; +import org.apache.commons.io.FileUtils; +import org.junit.Rule; +import org.junit.Test; +import org.jvnet.hudson.test.JenkinsSessionRule; + +public final class ProxyConfigurationManagerGUITest { + + @Rule public JenkinsSessionRule rr = new JenkinsSessionRule(); + + @Test public void configRoundtrip() throws Throwable { + rr.then(r -> { + assertNull(r.jenkins.proxy); + r.jenkins.proxy = new ProxyConfiguration("proxy.mycorp", 80); + r.configRoundtrip(); + FileUtils.copyFile(new File(r.jenkins.root, "proxy.xml"), System.out); + }); + rr.then(r -> { + ProxyConfiguration pc = r.jenkins.proxy; + assertNotNull(pc); + assertEquals("proxy.mycorp", pc.getName()); + assertEquals(80, pc.getPort()); + assertNull(pc.getUserName()); + pc.setUserName("proxyuser"); + pc.setSecretPassword(Secret.fromString("proxypass")); + r.configRoundtrip(); + FileUtils.copyFile(new File(r.jenkins.root, "proxy.xml"), System.out); + }); + rr.then(r -> { + ProxyConfiguration pc = r.jenkins.proxy; + assertEquals("proxy.mycorp", pc.getName()); + assertEquals(80, pc.getPort()); + assertEquals("proxyuser", pc.getUserName()); + assertEquals("proxypass", pc.getSecretPassword().getPlainText()); + }); + } + +} diff --git a/test/src/test/java/hudson/model/AsynchPeopleTest.java b/test/src/test/java/hudson/ProxyConfigurationManagerTest.java similarity index 52% rename from test/src/test/java/hudson/model/AsynchPeopleTest.java rename to test/src/test/java/hudson/ProxyConfigurationManagerTest.java index a3b5ccc540..d68cc19864 100644 --- a/test/src/test/java/hudson/model/AsynchPeopleTest.java +++ b/test/src/test/java/hudson/ProxyConfigurationManagerTest.java @@ -1,7 +1,7 @@ /* * The MIT License * - * Copyright 2013 Jesse Glick. + * Copyright 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 @@ -22,43 +22,29 @@ * THE SOFTWARE. */ -package hudson.model; +package hudson; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; +import static org.junit.Assert.assertNotNull; -import org.htmlunit.html.DomElement; -import org.htmlunit.html.HtmlPage; import org.junit.Rule; import org.junit.Test; -import org.jvnet.hudson.test.For; -import org.jvnet.hudson.test.Issue; import org.jvnet.hudson.test.JenkinsRule; +import org.jvnet.hudson.test.recipes.LocalData; -@For(View.AsynchPeople.class) -public class AsynchPeopleTest { +public class ProxyConfigurationManagerTest { - @Rule public JenkinsRule j = new JenkinsRule(); + @Rule public JenkinsRule r = new JenkinsRule(); - @Issue("JENKINS-18641") - @Test public void display() throws Exception { - User.getById("bob", true); - JenkinsRule.WebClient wc = j.createWebClient(); - - HtmlPage page = wc.goTo("asynchPeople"); - assertEquals(0, wc.waitForBackgroundJavaScript(120000)); - boolean found = false; - for (DomElement table : page.getElementsByTagName("table")) { - if (table.getAttribute("class").contains("progress-bar")) { - found = true; - assertEquals("display: none;", table.getAttribute("style")); - break; - } - } - assertTrue(found); - /* TODO this still fails occasionally, for reasons TBD (I think because User.getAll sometimes is empty): - assertNotNull(page.getElementById("person-bob")); + @LocalData + @Test public void serialForm() throws Exception { + /* Set up this way: + r.jenkins.proxy = new ProxyConfiguration("proxy.mycorp", 80); + r.jenkins.proxy.save(); */ + ProxyConfiguration pc = r.jenkins.proxy; + assertNotNull(pc); + assertEquals("proxy.mycorp", pc.name); } } diff --git a/test/src/test/java/hudson/cli/DisablePluginCommandTest.java b/test/src/test/java/hudson/cli/DisablePluginCommandTest.java index 83c2d94405..220998236b 100644 --- a/test/src/test/java/hudson/cli/DisablePluginCommandTest.java +++ b/test/src/test/java/hudson/cli/DisablePluginCommandTest.java @@ -41,7 +41,7 @@ import hudson.Functions; import hudson.PluginWrapper; import java.io.IOException; import java.util.function.BiPredicate; -import org.apache.commons.lang.StringUtils; +import org.apache.commons.lang3.StringUtils; import org.junit.Assume; import org.junit.Ignore; import org.junit.Rule; diff --git a/test/src/test/java/hudson/cli/Security3315Test.java b/test/src/test/java/hudson/cli/Security3315Test.java new file mode 100644 index 0000000000..83d207a49c --- /dev/null +++ b/test/src/test/java/hudson/cli/Security3315Test.java @@ -0,0 +1,62 @@ +package hudson.cli; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.MatcherAssert.assertThat; + +import java.io.IOException; +import java.net.URL; +import java.util.Arrays; +import java.util.List; +import org.htmlunit.HttpMethod; +import org.htmlunit.Page; +import org.htmlunit.WebRequest; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.jvnet.hudson.test.FlagRule; +import org.jvnet.hudson.test.JenkinsRule; + +@RunWith(Parameterized.class) +public class Security3315Test { + @Rule + public JenkinsRule j = new JenkinsRule(); + + @Rule + public FlagRule escapeHatch; + + private final Boolean allowWs; + + @Parameterized.Parameters + public static List escapeHatchValues() { + return Arrays.asList(null, "true", "false"); + } + + public Security3315Test(String allowWs) { + this.allowWs = allowWs == null ? null : Boolean.valueOf(allowWs); + this.escapeHatch = new FlagRule<>(() -> CLIAction.ALLOW_WEBSOCKET, v -> { CLIAction.ALLOW_WEBSOCKET = v; }, this.allowWs); + } + + @Test + public void test() throws IOException { + try (JenkinsRule.WebClient wc = j.createWebClient().withThrowExceptionOnFailingStatusCode(false)) { + // HTTP 400 is WebSocket "success" (HTMLUnit doesn't support it) + final URL jenkinsUrl = j.getURL(); + WebRequest request = new WebRequest(new URL(jenkinsUrl.toString() + "cli/ws"), HttpMethod.GET); + Page page = wc.getPage(request); + assertThat(page.getWebResponse().getStatusCode(), is(allowWs == Boolean.TRUE ? 400 : 403)); // no Origin header + + request.setAdditionalHeader("Origin", jenkinsUrl.getProtocol() + "://example.org:" + jenkinsUrl.getPort()); + page = wc.getPage(request); + assertThat(page.getWebResponse().getStatusCode(), is(allowWs == Boolean.TRUE ? 400 : 403)); // Wrong Origin host + + request.setAdditionalHeader("Origin", jenkinsUrl.getProtocol() + "://" + jenkinsUrl.getHost()); + page = wc.getPage(request); + assertThat(page.getWebResponse().getStatusCode(), is(allowWs == Boolean.TRUE ? 400 : 403)); // Wrong Origin port + + request.setAdditionalHeader("Origin", jenkinsUrl.getProtocol() + "://" + jenkinsUrl.getHost() + ":" + jenkinsUrl.getPort()); + page = wc.getPage(request); + assertThat(page.getWebResponse().getStatusCode(), is(allowWs == Boolean.FALSE ? 403 : 400)); // Reject correct Origin if ALLOW_WS is explicitly false + } + } +} diff --git a/test/src/test/java/hudson/model/AbstractProjectTest.java b/test/src/test/java/hudson/model/AbstractProjectTest.java index d4108e3a4c..290096ae30 100644 --- a/test/src/test/java/hudson/model/AbstractProjectTest.java +++ b/test/src/test/java/hudson/model/AbstractProjectTest.java @@ -245,12 +245,7 @@ public class AbstractProjectTest { }; } }); - Thread t = new Thread() { - @Override - public void run() { - p.pollSCMChanges(StreamTaskListener.fromStdout()); - } - }; + Thread t = new Thread(() -> p.poll(StreamTaskListener.fromStdout())); try { t.start(); Future f = p.scheduleBuild2(0); diff --git a/test/src/test/java/hudson/model/ApiTest.java b/test/src/test/java/hudson/model/ApiTest.java index da623ad825..7422ec78fd 100644 --- a/test/src/test/java/hudson/model/ApiTest.java +++ b/test/src/test/java/hudson/model/ApiTest.java @@ -70,7 +70,7 @@ public class ApiTest { JSONObject json = JSONObject.fromObject(response.getContentAsString()); assertEquals("p", json.get("name")); - String s = wc.goTo(p.getUrl() + "api/json?tree=name&jsonp=wrap", "application/javascript").getWebResponse().getContentAsString(); + String s = wc.goTo(p.getUrl() + "api/json?tree=name&jsonp=wrap", "text/javascript").getWebResponse().getContentAsString(); assertTrue(s.startsWith("wrap(")); assertEquals(')', s.charAt(s.length() - 1)); json = JSONObject.fromObject(s.substring("wrap(".length(), s.length() - 1)); diff --git a/test/src/test/java/hudson/model/QueueTest.java b/test/src/test/java/hudson/model/QueueTest.java index 5e52bf6416..878b04e28d 100644 --- a/test/src/test/java/hudson/model/QueueTest.java +++ b/test/src/test/java/hudson/model/QueueTest.java @@ -108,6 +108,7 @@ import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import jenkins.model.BlockedBecauseOfBuildInProgress; import jenkins.model.Jenkins; +import jenkins.model.queue.QueueIdStrategy; import jenkins.security.QueueItemAuthenticatorConfiguration; import org.acegisecurity.acls.sid.PrincipalSid; import org.apache.commons.fileupload.FileUploadException; @@ -200,7 +201,7 @@ public class QueueTest { // The current counter should be the id from the item brought back // from the persisted queue.xml. - assertEquals(3, Queue.WaitingItem.getCurrentCounterValue()); + assertEquals(3, QueueIdStrategy.DefaultStrategy.getCurrentCounterValue()); // Clear the queue assertTrue(r.jenkins.getQueue().cancel(r.jenkins.getItemByFullName("test", FreeStyleProject.class))); @@ -213,7 +214,7 @@ public class QueueTest { Queue q = r.jenkins.getQueue(); resetQueueState(); - assertEquals(0, Queue.WaitingItem.getCurrentCounterValue()); + assertEquals(0, QueueIdStrategy.DefaultStrategy.getCurrentCounterValue()); // prevent execution to push stuff into the queue r.jenkins.setNumExecutors(0); @@ -234,7 +235,7 @@ public class QueueTest { assertEquals(0, q.getItems().length); // The counter state should be maintained. - assertEquals(1, Queue.WaitingItem.getCurrentCounterValue()); + assertEquals(1, QueueIdStrategy.DefaultStrategy.getCurrentCounterValue()); } /** diff --git a/test/src/test/java/hudson/model/RunTest.java b/test/src/test/java/hudson/model/RunTest.java index c044082f29..c59245c78c 100644 --- a/test/src/test/java/hudson/model/RunTest.java +++ b/test/src/test/java/hudson/model/RunTest.java @@ -157,7 +157,7 @@ public class RunTest { HtmlPage htmlPage = wc.goTo(upProject.getUrl()); // trigger the tooltip display - htmlPage.executeJavaScript("document.querySelector('#buildHistory table .build-badge img')._tippy.show()"); + htmlPage.executeJavaScript("document.querySelector('#buildHistory table .build-badge svg')._tippy.show()"); wc.waitForBackgroundJavaScript(500); ScriptResult result = htmlPage.executeJavaScript("document.querySelector('.tippy-content').innerHTML;"); Object jsResult = result.getJavaScriptResult(); diff --git a/test/src/test/java/hudson/model/ViewTest.java b/test/src/test/java/hudson/model/ViewTest.java index 3ea19e1ee5..c568964fe1 100644 --- a/test/src/test/java/hudson/model/ViewTest.java +++ b/test/src/test/java/hudson/model/ViewTest.java @@ -127,8 +127,8 @@ public class ViewTest { j.configRoundtrip(view); assertEquals("Some description", view.getDescription()); - assertEquals(true, view.isFilterExecutors()); - assertEquals(true, view.isFilterQueue()); + assertTrue(view.isFilterExecutors()); + assertTrue(view.isFilterQueue()); } @Issue("JENKINS-7100") @@ -700,7 +700,7 @@ public class ViewTest { assertThat(e.getStatusCode(), equalTo(500)); // This should have a different message, but this is the current behavior demonstrating the problem. - assertThat(e.getResponse().getContentAsString(), containsString("A problem occurred while processing the request.")); + assertThat(e.getResponse().getContentAsString(), containsString("A problem occurred while processing the request")); OldDataMonitor odm = ExtensionList.lookupSingleton(OldDataMonitor.class); Map data = odm.getData(); @@ -744,7 +744,7 @@ public class ViewTest { assertThat(e.getStatusCode(), equalTo(500)); // This should have a different message, but this is the current behavior demonstrating the problem. - assertThat(e.getResponse().getContentAsString(), containsString("A problem occurred while processing the request.")); + assertThat(e.getResponse().getContentAsString(), containsString("A problem occurred while processing the request")); OldDataMonitor odm = ExtensionList.lookupSingleton(OldDataMonitor.class); Map data = odm.getData(); @@ -825,7 +825,7 @@ public class ViewTest { Object result = page.executeJavaScript("Array.from(document.querySelectorAll('.label')).filter(el => el.innerText.indexOf('" + customizableTLID.customDisplayName + "') !== -1)[0].parentElement.parentElement").getJavaScriptResult(); assertThat(result, instanceOf(HTMLElement.class)); HTMLElement resultElement = (HTMLElement) result; - assertThat(resultElement.getAttribute("onclick", null), nullValue()); + assertThat(resultElement.getAttribute("onclick"), nullValue()); } @Test diff --git a/test/src/test/java/hudson/security/HudsonPrivateSecurityRealmTest.java b/test/src/test/java/hudson/security/HudsonPrivateSecurityRealmTest.java index b172e0e72e..d0f58f92d5 100644 --- a/test/src/test/java/hudson/security/HudsonPrivateSecurityRealmTest.java +++ b/test/src/test/java/hudson/security/HudsonPrivateSecurityRealmTest.java @@ -56,7 +56,7 @@ import jenkins.security.ApiTokenProperty; import jenkins.security.SecurityListener; import jenkins.security.apitoken.ApiTokenPropertyConfiguration; import jenkins.security.seed.UserSeedProperty; -import org.apache.commons.lang.StringUtils; +import org.apache.commons.lang3.StringUtils; import org.hamcrest.Matcher; import org.htmlunit.FailingHttpStatusCodeException; import org.htmlunit.HttpMethod; diff --git a/test/src/test/java/hudson/slaves/JNLPLauncherTest.java b/test/src/test/java/hudson/slaves/JNLPLauncherTest.java index 813c723645..e71b2fbcce 100644 --- a/test/src/test/java/hudson/slaves/JNLPLauncherTest.java +++ b/test/src/test/java/hudson/slaves/JNLPLauncherTest.java @@ -157,12 +157,21 @@ public class JNLPLauncherTest { ArgumentListBuilder args = new ArgumentListBuilder(); args.add(new File(new File(System.getProperty("java.home")), "bin/java").getPath(), "-jar"); args.add(Which.jarFile(Launcher.class).getAbsolutePath()); - // TODO deprecated mode - args.add("-jnlpUrl", j.getURL() + "computer/" + c.getName() + "/jenkins-agent.jnlp"); + args.add("-url"); + args.add(j.getURL()); + args.add("-name"); + args.add(c.getName()); if (c instanceof SlaveComputer) { SlaveComputer sc = (SlaveComputer) c; + args.add("-secret"); + args.add(sc.getJnlpMac()); ComputerLauncher launcher = sc.getLauncher(); + if (launcher instanceof ComputerLauncherFilter) { + launcher = ((ComputerLauncherFilter) launcher).getCore(); + } else if (launcher instanceof DelegatingComputerLauncher) { + launcher = ((DelegatingComputerLauncher) launcher).getLauncher(); + } if (launcher instanceof JNLPLauncher) { args.add(((JNLPLauncher) launcher).getWorkDirSettings().toCommandLineArgs(sc)); } diff --git a/test/src/test/java/hudson/tasks/ArtifactArchiverTest.java b/test/src/test/java/hudson/tasks/ArtifactArchiverTest.java index 92dee288b4..2157e89492 100644 --- a/test/src/test/java/hudson/tasks/ArtifactArchiverTest.java +++ b/test/src/test/java/hudson/tasks/ArtifactArchiverTest.java @@ -46,6 +46,7 @@ import hudson.model.FreeStyleProject; import hudson.model.Label; import hudson.model.Result; import hudson.model.Run; +import hudson.model.Slave; import hudson.remoting.VirtualChannel; import hudson.slaves.DumbSlave; import java.io.File; @@ -134,8 +135,48 @@ public class ArtifactArchiverTest { @Test @Issue("JENKINS-51913") public void testFileMaskNoMatchesFoundException() throws Exception { + hudson.FilePath.VALIDATE_ANT_FILE_MASK_BOUND = 1; FreeStyleProject project = j.createFreeStyleProject(); - String pattern = "dir/**"; + project.getBuildersList().replaceBy(Collections.singleton(new TestBuilder() { + @Override + public boolean perform(AbstractBuild build, Launcher launcher, BuildListener listener) throws InterruptedException, IOException { + FilePath dir = build.getWorkspace().child("dir"); + dir.child("file").write("content", "UTF-8"); + dir.child("file2").write("content", "UTF-8"); + dir.child("file3").write("content", "UTF-8"); + return true; + } + })); + + String pattern = "dir/*.log"; + ArtifactArchiver aa = new ArtifactArchiver(pattern); + aa.setAllowEmptyArchive(true); + project.getPublishersList().replaceBy(Collections.singleton(aa)); + FreeStyleBuild build = j.buildAndAssertSuccess(project); + assertFalse(project.getBuildByNumber(1).getHasArtifacts()); + j.assertLogContains("No artifacts found that match the file pattern \"" + pattern + "\"", build); + assertThat("No stacktrace shown", build.getLog(31), Matchers.iterableWithSize(lessThan(30))); + } + + @Test + @Issue("JENKINS-71700") + public void testFileMaskNoMatchesFoundExceptionOnAgent() throws Exception { + Slave agent = j.createOnlineSlave(); + hudson.FilePath.VALIDATE_ANT_FILE_MASK_BOUND = 1; + FreeStyleProject project = j.createFreeStyleProject(); + project.setAssignedNode(agent); + project.getBuildersList().replaceBy(Collections.singleton(new TestBuilder() { + @Override + public boolean perform(AbstractBuild build, Launcher launcher, BuildListener listener) throws InterruptedException, IOException { + FilePath dir = build.getWorkspace().child("dir"); + dir.child("file").write("content", "UTF-8"); + dir.child("file2").write("content", "UTF-8"); + dir.child("file3").write("content", "UTF-8"); + return true; + } + })); + + String pattern = "dir/*.log"; ArtifactArchiver aa = new ArtifactArchiver(pattern); aa.setAllowEmptyArchive(true); project.getPublishersList().replaceBy(Collections.singleton(aa)); diff --git a/test/src/test/java/hudson/tasks/FingerprinterTest.java b/test/src/test/java/hudson/tasks/FingerprinterTest.java index fc58b79127..ec792a0f60 100644 --- a/test/src/test/java/hudson/tasks/FingerprinterTest.java +++ b/test/src/test/java/hudson/tasks/FingerprinterTest.java @@ -251,7 +251,6 @@ public class FingerprinterTest { FreeStyleBuild downstreamBuild = j.buildAndAssertSuccess(downstream); String oldUpstreamName = upstream.getName(); - String oldDownstreamName = downstream.getName(); // Verify that owner entry in fingerprint record is changed // after source project is renamed @@ -274,6 +273,8 @@ public class FingerprinterTest { assertNotEquals(f.getOriginal().getName(), oldUpstreamName); } + String oldDownstreamName = downstream.getName(); + // Verify that usage entry in fingerprint record is changed after // sink project is renamed downstream.renameTo(renamedProject2); diff --git a/test/src/test/java/hudson/tasks/MavenTest.java b/test/src/test/java/hudson/tasks/MavenTest.java index bd366adce7..dbf745b672 100644 --- a/test/src/test/java/hudson/tasks/MavenTest.java +++ b/test/src/test/java/hudson/tasks/MavenTest.java @@ -66,6 +66,7 @@ import org.jvnet.hudson.test.ExtractResourceSCM; import org.jvnet.hudson.test.Issue; import org.jvnet.hudson.test.JenkinsRule; import org.jvnet.hudson.test.ToolInstallations; +import org.jvnet.hudson.test.recipes.WithTimeout; import org.kohsuke.stapler.jelly.JellyFacet; /** @@ -190,7 +191,7 @@ public class MavenTest { assertNotNull(isp.installers.get(MavenInstaller.class)); } - @Test public void sensitiveParameters() throws Exception { + @Test @WithTimeout(500) public void sensitiveParameters() throws Exception { FreeStyleProject project = j.createFreeStyleProject(); ParametersDefinitionProperty pdb = new ParametersDefinitionProperty( new StringParameterDefinition("string", "defaultValue", "string description"), diff --git a/test/src/test/java/jenkins/diagnostics/RootUrlNotSetMonitorTest.java b/test/src/test/java/jenkins/diagnostics/RootUrlNotSetMonitorTest.java index decee0412b..bd98175ab9 100644 --- a/test/src/test/java/jenkins/diagnostics/RootUrlNotSetMonitorTest.java +++ b/test/src/test/java/jenkins/diagnostics/RootUrlNotSetMonitorTest.java @@ -25,11 +25,11 @@ package jenkins.diagnostics; import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import hudson.model.AdministrativeMonitor; import jenkins.model.JenkinsLocationConfiguration; -import org.apache.commons.lang.StringUtils; import org.junit.Rule; import org.junit.Test; import org.jvnet.hudson.test.Issue; @@ -45,7 +45,9 @@ public class RootUrlNotSetMonitorTest { public void testWithRootUrl_configured() { // test relies on the default JTH behavior JenkinsLocationConfiguration config = JenkinsLocationConfiguration.get(); - assertTrue(StringUtils.isNotBlank(config.getUrl())); + String url = config.getUrl(); + assertNotNull(url); + assertFalse(url.isBlank()); RootUrlNotSetMonitor monitor = j.jenkins.getExtensionList(AdministrativeMonitor.class).get(RootUrlNotSetMonitor.class); assertFalse("Monitor must not be activated", monitor.isActivated()); diff --git a/test/src/test/java/jenkins/install/InstallUtilTest.java b/test/src/test/java/jenkins/install/InstallUtilTest.java index c018b6e406..9a7d6534f1 100644 --- a/test/src/test/java/jenkins/install/InstallUtilTest.java +++ b/test/src/test/java/jenkins/install/InstallUtilTest.java @@ -112,7 +112,7 @@ public class InstallUtilTest { Assert.assertEquals(InstallState.UPGRADE, InstallUtil.getNextInstallState(InstallState.UNKNOWN)); // Fudge things yet again, changing the stored version to something very very new, faking a downgrade... - InstallUtil.saveLastExecVersion("1000.0"); + InstallUtil.saveLastExecVersion("1000000.0"); Assert.assertEquals(InstallState.DOWNGRADE, InstallUtil.getNextInstallState(InstallState.UNKNOWN)); } diff --git a/test/src/test/java/jenkins/model/JenkinsReloadConfigurationTest.java b/test/src/test/java/jenkins/model/JenkinsReloadConfigurationTest.java index a783ff67f0..3fb166ec92 100644 --- a/test/src/test/java/jenkins/model/JenkinsReloadConfigurationTest.java +++ b/test/src/test/java/jenkins/model/JenkinsReloadConfigurationTest.java @@ -48,7 +48,7 @@ public class JenkinsReloadConfigurationTest { } private void modifyNode(Node node) throws Exception { - replace(node.getNodeName().equals("") ? "config.xml" : String.format("nodes/%s/config.xml", node.getNodeName()), "oldLabel", "newLabel"); + replace(node.getNodeName().isEmpty() ? "config.xml" : String.format("nodes/%s/config.xml", node.getNodeName()), "oldLabel", "newLabel"); assertEquals("oldLabel", node.getLabelString()); diff --git a/test/src/test/java/jenkins/model/NodesTest.java b/test/src/test/java/jenkins/model/NodesTest.java index 25c99ca355..275dd9a3e5 100644 --- a/test/src/test/java/jenkins/model/NodesTest.java +++ b/test/src/test/java/jenkins/model/NodesTest.java @@ -35,6 +35,7 @@ import static org.junit.Assert.assertNotSame; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertThrows; +import edu.umd.cs.findbugs.annotations.NonNull; import hudson.ExtensionList; import hudson.model.Descriptor; import hudson.model.Failure; @@ -48,6 +49,7 @@ import org.junit.Test; import org.jvnet.hudson.test.Issue; import org.jvnet.hudson.test.JenkinsRule; import org.jvnet.hudson.test.TestExtension; +import org.jvnet.hudson.test.recipes.LocalData; public class NodesTest { @@ -204,4 +206,20 @@ public class NodesTest { } } } + + @Test + @LocalData + public void vetoLoad() { + assertNull("one-node should not have been loaded because vetoed by VetoLoadingNodes", Jenkins.get().getNode("one-node")); + } + + @TestExtension("vetoLoad") + public static class VetoLoadingNodes extends NodeListener { + @Override + protected boolean allowLoad(@NonNull Node node) { + // Don't allow loading any node. + return false; + } + } + } diff --git a/test/src/test/java/jenkins/model/ParameterizedJobMixInTest.java b/test/src/test/java/jenkins/model/ParameterizedJobMixInTest.java index f4b11cb2ea..f873a69d30 100644 --- a/test/src/test/java/jenkins/model/ParameterizedJobMixInTest.java +++ b/test/src/test/java/jenkins/model/ParameterizedJobMixInTest.java @@ -25,6 +25,7 @@ package jenkins.model; import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.arrayWithSize; import static org.hamcrest.Matchers.instanceOf; import static org.hamcrest.Matchers.is; import static org.junit.Assert.assertThrows; @@ -90,11 +91,12 @@ public class ParameterizedJobMixInTest { webClient.getPage(webClient.addCrumb(new WebRequest(new URL(j.getURL(), project.getUrl() + "build"), HttpMethod.POST))); long triggerTime = System.currentTimeMillis(); - Queue.Item item = Jenkins.get().getQueue().getItem(1); - assertThat(item, instanceOf(Queue.WaitingItem.class)); - assertThat(item.task, instanceOf(FreeStyleProject.class)); + Queue.Item[] items = Jenkins.get().getQueue().getItems(); + assertThat(items, arrayWithSize(1)); + assertThat(items[0], instanceOf(Queue.WaitingItem.class)); + assertThat(items[0].task, instanceOf(FreeStyleProject.class)); - Queue.WaitingItem waitingItem = (Queue.WaitingItem) item; + Queue.WaitingItem waitingItem = (Queue.WaitingItem) items[0]; Assert.assertTrue(waitingItem.timestamp.getTimeInMillis() - triggerTime > 45000); Jenkins.get().getQueue().doCancelItem(1); diff --git a/test/src/test/java/jenkins/model/RunIdMigratorTest.java b/test/src/test/java/jenkins/model/RunIdMigratorTest.java new file mode 100644 index 0000000000..4ade1afc84 --- /dev/null +++ b/test/src/test/java/jenkins/model/RunIdMigratorTest.java @@ -0,0 +1,100 @@ +/* + * 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 jenkins.model; + +import static hudson.cli.CLICommandInvoker.Matcher.succeededSilently; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +import hudson.cli.CLICommandInvoker; +import hudson.cli.CreateJobCommand; +import hudson.model.FreeStyleProject; +import hudson.model.Item; +import hudson.model.User; +import java.io.ByteArrayInputStream; +import java.io.File; +import java.nio.charset.StandardCharsets; +import org.htmlunit.HttpMethod; +import org.htmlunit.WebRequest; +import org.junit.Ignore; +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.MockAuthorizationStrategy; + +public class RunIdMigratorTest { + + @Rule + public JenkinsRule j = new JenkinsRule(); + + @Test + public void legacyIdsPresent() throws Exception { + FreeStyleProject p = j.createFreeStyleProject(); + File legacyIds = new File(p.getBuildDir(), "legacyIds"); + assertTrue(legacyIds.exists()); + } + + @Ignore("TODO Item#onCreatedFromScratch is not called") + @Issue("JENKINS-64356") + @Test + public void legacyIdsPresentViaRestApi() throws Exception { + User user = User.getById("user", true); + j.jenkins.setSecurityRealm(j.createDummySecurityRealm()); + j.jenkins.setAuthorizationStrategy(new MockAuthorizationStrategy() + .grant(Jenkins.READ, Item.CREATE) + .everywhere() + .to(user.getId())); + String jobName = "test" + j.jenkins.getItems().size(); + try (JenkinsRule.WebClient wc = j.createWebClient()) { + wc.login(user.getId()); + WebRequest req = new WebRequest(wc.createCrumbedUrl("createItem?name=" + jobName), HttpMethod.POST); + req.setAdditionalHeader("Content-Type", "application/xml"); + req.setRequestBody(""); + wc.getPage(req); + } + FreeStyleProject p = j.jenkins.getItemByFullName(jobName, FreeStyleProject.class); + assertNotNull(p); + File legacyIds = new File(p.getBuildDir(), "legacyIds"); + assertTrue(legacyIds.exists()); + } + + @Ignore("TODO Item#onCreatedFromScratch is not called") + @Issue("JENKINS-64356") + @Test + public void legacyIdsPresentViaCli() { + String jobName = "test" + j.jenkins.getItems().size(); + CLICommandInvoker invoker = new CLICommandInvoker(j, new CreateJobCommand()); + CLICommandInvoker.Result result = invoker.withStdin( + new ByteArrayInputStream("".getBytes(StandardCharsets.UTF_8))) + .invokeWithArgs(jobName); + assertThat(result, succeededSilently()); + FreeStyleProject p = j.jenkins.getItemByFullName(jobName, FreeStyleProject.class); + assertNotNull(p); + File legacyIds = new File(p.getBuildDir(), "legacyIds"); + assertTrue(legacyIds.exists()); + } +} diff --git a/test/src/test/java/jenkins/security/ResourceDomainTest.java b/test/src/test/java/jenkins/security/ResourceDomainTest.java index feeed98b5f..42f2a1dbbf 100644 --- a/test/src/test/java/jenkins/security/ResourceDomainTest.java +++ b/test/src/test/java/jenkins/security/ResourceDomainTest.java @@ -1,7 +1,9 @@ package jenkins.security; +import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.containsString; +import static org.junit.Assert.assertThrows; import static org.junit.Assume.assumeFalse; import edu.umd.cs.findbugs.annotations.CheckForNull; @@ -18,6 +20,7 @@ import java.time.Instant; import java.util.UUID; import jenkins.model.Jenkins; import jenkins.model.JenkinsLocationConfiguration; +import org.htmlunit.FailingHttpStatusCodeException; import org.htmlunit.Page; import org.htmlunit.html.HtmlPage; import org.junit.Assert; @@ -394,4 +397,24 @@ public class ResourceDomainTest { return new DirectoryBrowserSupport(jenkins, tempDir, "title", "", true); } } + + @Test + public void authenticatedCannotAccessResourceDomain() throws Exception { + j.jenkins.setSecurityRealm(j.createDummySecurityRealm()); + final MockAuthorizationStrategy authorizationStrategy = new MockAuthorizationStrategy(); + authorizationStrategy.grant(Jenkins.ADMINISTER).everywhere().to("admin").grant(Jenkins.READ).everywhere().toEveryone(); + j.jenkins.setAuthorizationStrategy(authorizationStrategy); + final String resourceUrl; + try (JenkinsRule.WebClient wc = j.createWebClient().withRedirectEnabled(false).withThrowExceptionOnFailingStatusCode(false)) { + final Page htmlPage = wc.goTo("userContent/readme.txt", ""); + resourceUrl = htmlPage.getWebResponse().getResponseHeaderValue("Location"); + } + assertThat(resourceUrl, containsString("static-files/")); + try (JenkinsRule.WebClient wc = j.createWebClient().withBasicApiToken("admin")) { + assertThat(assertThrows(FailingHttpStatusCodeException.class, () -> wc.getPage(new URL(resourceUrl))).getStatusCode(), is(400)); + } + try (JenkinsRule.WebClient wc = j.createWebClient().withBasicCredentials("admin")) { + assertThat(assertThrows(FailingHttpStatusCodeException.class, () -> wc.getPage(new URL(resourceUrl))).getStatusCode(), is(400)); + } + } } diff --git a/test/src/test/java/jenkins/security/Security2278Test.java b/test/src/test/java/jenkins/security/Security2278Test.java index c556d5d079..7811c2afa8 100644 --- a/test/src/test/java/jenkins/security/Security2278Test.java +++ b/test/src/test/java/jenkins/security/Security2278Test.java @@ -70,7 +70,7 @@ public class Security2278Test { assertThat(contentAsString, containsString("Build Executor Status")); assertThat(contentAsString, containsString("Unknown Task")); assertThat(contentAsString, not(containsString("job/foo/job/bar"))); - assertThat(contentAsString, not(containsString("icon-stop"))); + assertThat(contentAsString, not(containsString("stop-button-link"))); } @Test @@ -81,7 +81,7 @@ public class Security2278Test { assertThat(contentAsString, containsString("Build Executor Status")); assertThat(contentAsString, not(containsString("Unknown Task"))); assertThat(contentAsString, containsString("job/foo/job/bar")); - assertThat(contentAsString, containsString("icon-stop")); + assertThat(contentAsString, containsString("stop-button-link")); } @Test diff --git a/test/src/test/java/jenkins/security/Security3314Test.java b/test/src/test/java/jenkins/security/Security3314Test.java new file mode 100644 index 0000000000..4dbdede0a5 --- /dev/null +++ b/test/src/test/java/jenkins/security/Security3314Test.java @@ -0,0 +1,55 @@ +package jenkins.security; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.not; + +import hudson.cli.CLICommandInvoker; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Arrays; +import java.util.List; +import jenkins.model.Jenkins; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.jvnet.hudson.test.JenkinsRule; + +@RunWith(Parameterized.class) +public class Security3314Test { + private String commandName; + + @Rule + public final JenkinsRule j = new JenkinsRule(); + + /** + * connect-node to test the CLICommand behavior + * disable-job to test the CLIRegisterer behavior (@CLIMethod) + */ + @Parameterized.Parameters + public static List commands() { + return Arrays.asList("connect-node", "disable-job"); + } + + public Security3314Test(String commandName) { + this.commandName = commandName; + } + + @Test + public void commandShouldNotParseAt() throws Exception { + CLICommandInvoker command = new CLICommandInvoker(j, commandName); + + Path tempPath = Files.createTempFile("tempFile", ".txt"); + tempPath.toFile().deleteOnExit(); + String content = "AtGotParsed"; + Files.write(tempPath, content.getBytes()); + + final CLICommandInvoker.Result result = command + .authorizedTo(Jenkins.READ) + .invokeWithArgs("@" + tempPath); + + assertThat(result.stderr(), containsString("@" + tempPath)); + assertThat(result.stderr(), not(containsString("AtGotParsed"))); + } +} diff --git a/test/src/test/java/jenkins/security/StackTraceSuppressionTest.java b/test/src/test/java/jenkins/security/StackTraceSuppressionTest.java index 9e9b58777a..b13a5d11c4 100644 --- a/test/src/test/java/jenkins/security/StackTraceSuppressionTest.java +++ b/test/src/test/java/jenkins/security/StackTraceSuppressionTest.java @@ -161,7 +161,7 @@ public class StackTraceSuppressionTest { } private void checBaseResponseContent(String content) { - assertThat(content, containsString("A problem occurred while processing the request.")); + assertThat(content, containsString("A problem occurred while processing the request")); assertThat(content, containsString("Logging ID=")); assertThat(content, containsString("Oops!")); } diff --git a/test/src/test/java/jenkins/security/apitoken/LegacyApiTokenAdministrativeMonitorTest.java b/test/src/test/java/jenkins/security/apitoken/LegacyApiTokenAdministrativeMonitorTest.java index 4f8b4c6e38..8afef45167 100644 --- a/test/src/test/java/jenkins/security/apitoken/LegacyApiTokenAdministrativeMonitorTest.java +++ b/test/src/test/java/jenkins/security/apitoken/LegacyApiTokenAdministrativeMonitorTest.java @@ -34,7 +34,7 @@ import hudson.model.AdministrativeMonitor; import hudson.model.User; import java.io.IOException; import jenkins.security.ApiTokenProperty; -import org.apache.commons.lang.StringUtils; +import org.apache.commons.lang3.StringUtils; import org.hamcrest.Matchers; import org.htmlunit.Page; import org.htmlunit.html.DomNodeList; diff --git a/test/src/test/java/jenkins/security/stapler/GetterMethodFilterTest.java b/test/src/test/java/jenkins/security/stapler/GetterMethodFilterTest.java index e5e227d74e..e02aa4e35a 100644 --- a/test/src/test/java/jenkins/security/stapler/GetterMethodFilterTest.java +++ b/test/src/test/java/jenkins/security/stapler/GetterMethodFilterTest.java @@ -27,8 +27,8 @@ package jenkins.security.stapler; import static org.junit.Assert.assertFalse; import com.cloudbees.hudson.plugins.folder.Folder; +import hudson.AboutJenkins; import hudson.model.TopLevelItem; -import hudson.model.View; import java.awt.Point; import java.util.Arrays; import java.util.Collections; @@ -269,9 +269,9 @@ public class GetterMethodFilterTest extends StaplerAbstractTest { @TestExtension public static class TestWithReturnCoreObject extends AbstractUnprotectedRootAction { - public View.People getPeople() { + public AboutJenkins getPeople() { // provide an index jelly view - return new View.People(Jenkins.get()); + return new AboutJenkins(); } } diff --git a/test/src/test/java/jenkins/security/stapler/Security400Test.java b/test/src/test/java/jenkins/security/stapler/Security400Test.java index 2a26ceda1f..c2b64323c6 100644 --- a/test/src/test/java/jenkins/security/stapler/Security400Test.java +++ b/test/src/test/java/jenkins/security/stapler/Security400Test.java @@ -647,12 +647,6 @@ public class Security400Test { checkPageIsRedirectedToLogin(page); assertThat(page.getWebResponse().getContentAsString(), not(containsString("secretUser"))); assertRequestWasNotBlocked(); - - page = wc.goTo("asynchPeople/", null); - assertEquals(200, page.getWebResponse().getStatusCode()); - // javascript will load the user list asynch - assertThat(page.getWebResponse().getContentAsString(), containsString("Includes all known")); - assertRequestWasNotBlocked(); } authorizationStrategy.setAllowAnonymousRead(false); @@ -665,12 +659,6 @@ public class Security400Test { checkPageIsRedirectedToLogin(page); assertThat(page.getWebResponse().getContentAsString(), not(containsString("secretUser"))); assertRequestWasNotBlocked(); - - // with the restriction we disallow the anon to even read the list of all users - page = wc.goTo("asynchPeople/", null); - checkPageIsRedirectedToLogin(page); - assertThat(page.getWebResponse().getContentAsString(), not(containsString("secretUser"))); - assertRequestWasNotBlocked(); } } diff --git a/test/src/test/java/jenkins/telemetry/TelemetryTest.java b/test/src/test/java/jenkins/telemetry/TelemetryTest.java index 034e9dcdd6..f465fdf2fd 100644 --- a/test/src/test/java/jenkins/telemetry/TelemetryTest.java +++ b/test/src/test/java/jenkins/telemetry/TelemetryTest.java @@ -19,7 +19,6 @@ import java.io.IOException; import java.io.StringWriter; import java.nio.charset.StandardCharsets; import java.time.LocalDate; -import java.time.temporal.ChronoUnit; import java.util.HashSet; import java.util.Set; import java.util.SortedSet; @@ -203,7 +202,7 @@ public class TelemetryTest { @NonNull @Override public LocalDate getStart() { - return LocalDate.now().plus(1, ChronoUnit.DAYS); + return LocalDate.now().plusDays(1); } @NonNull @@ -243,7 +242,7 @@ public class TelemetryTest { @NonNull @Override public LocalDate getEnd() { - return LocalDate.now().minus(1, ChronoUnit.DAYS); + return LocalDate.now().minusDays(1); } @NonNull diff --git a/test/src/test/java/lib/form/ValidateButtonTest.java b/test/src/test/java/lib/form/ValidateButtonTest.java index ac092e1452..da9bc286be 100644 --- a/test/src/test/java/lib/form/ValidateButtonTest.java +++ b/test/src/test/java/lib/form/ValidateButtonTest.java @@ -109,13 +109,16 @@ public class ValidateButtonTest { public void doValidateTest1(@QueryParameter("a") String a, @QueryParameter("b") boolean b, @QueryParameter("c") boolean c, @QueryParameter("d") String d, - @QueryParameter("e") String e) { + @QueryParameter("e") String e, + @QueryParameter("f") String f + ) { try { assertEquals("avalue", a); assertTrue(b); assertFalse(c); assertEquals("dvalue", d); assertEquals("e2", e); + assertEquals("f", f); test1Outcome = null; } catch (RuntimeException t) { test1Outcome = t; diff --git a/test/src/test/java/org/jenkins/ui/symbol/SymbolJenkinsTest.java b/test/src/test/java/org/jenkins/ui/symbol/SymbolJenkinsTest.java index 65d225e292..1609e5ff0e 100644 --- a/test/src/test/java/org/jenkins/ui/symbol/SymbolJenkinsTest.java +++ b/test/src/test/java/org/jenkins/ui/symbol/SymbolJenkinsTest.java @@ -12,7 +12,8 @@ import org.jvnet.hudson.test.RealJenkinsRule; public class SymbolJenkinsTest { @Rule - public RealJenkinsRule rjr = new RealJenkinsRule().addPlugins("plugins/design-library.jpi"); + public RealJenkinsRule rjr = new RealJenkinsRule() + .addPlugins("plugins/design-library.jpi", "plugins/prism-api.jpi", "plugins/bootstrap5-api.jpi"); @Test @DisplayName("When resolving a symbol from a missing plugin, the placeholder is generated instead") diff --git a/test/src/test/java/org/kohsuke/stapler/BindTest.java b/test/src/test/java/org/kohsuke/stapler/BindTest.java new file mode 100644 index 0000000000..b46aa263ff --- /dev/null +++ b/test/src/test/java/org/kohsuke/stapler/BindTest.java @@ -0,0 +1,176 @@ +package org.kohsuke.stapler; + +import static org.hamcrest.CoreMatchers.containsString; +import static org.hamcrest.CoreMatchers.endsWith; +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.startsWith; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.Assert.assertThrows; + +import hudson.ExtensionList; +import hudson.model.InvisibleAction; +import hudson.model.RootAction; +import java.util.Arrays; +import java.util.List; +import org.apache.commons.lang3.StringUtils; +import org.htmlunit.Page; +import org.htmlunit.ScriptException; +import org.htmlunit.html.HtmlPage; +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; +import org.kohsuke.stapler.bind.JavaScriptMethod; +import org.kohsuke.stapler.bind.WithWellKnownURL; + +@RunWith(Parameterized.class) +public class BindTest { + @Rule + public JenkinsRule j = new JenkinsRule(); + + @Parameterized.Parameters + public static List contexts() { + return Arrays.asList("/jenkins", ""); + } + + public BindTest(String contextPath) { + j.contextPath = contextPath; + } + + @Test + public void bindNormal() throws Exception { + final RootActionImpl root = ExtensionList.lookupSingleton(RootActionImpl.class); + try (JenkinsRule.WebClient wc = j.createWebClient()) { + final HtmlPage htmlPage = wc.goTo(root.getUrlName()); + final String scriptUrl = htmlPage + .getElementsByTagName("script") + .stream() + .filter(it -> it.getAttribute("src").startsWith(j.contextPath + "/$stapler/bound/script" + j.contextPath + "/$stapler/bound/")) + .findFirst() + .orElseThrow() + .getAttribute("src"); + + final Page script = wc.goTo(StringUtils.removeStart(scriptUrl, j.contextPath + "/"), "text/javascript"); + final String content = script.getWebResponse().getContentAsString(); + assertThat(content, startsWith("varname = makeStaplerProxy('" + j.contextPath + "/$stapler/bound/")); + assertThat(content, endsWith("','test',['annotatedJsMethod1','byName1']);")); + } + assertThat(root.invocations, is(1)); + } + + @Test + public void bindWithWellKnownURL() throws Exception { + final RootActionWithWellKnownURL root = ExtensionList.lookupSingleton(RootActionWithWellKnownURL.class); + try (JenkinsRule.WebClient wc = j.createWebClient()) { + final HtmlPage htmlPage = wc.goTo(root.getUrlName()); + final String scriptUrl = htmlPage + .getElementsByTagName("script") + .stream() + .filter(it -> it.getAttribute("src").startsWith(j.contextPath + "/$stapler/bound/script" + j.contextPath + "/theWellKnownRoot?")) + .findFirst() + .orElseThrow() + .getAttribute("src"); + + final Page script = wc.goTo(StringUtils.removeStart(scriptUrl, j.contextPath + "/"), "text/javascript"); + assertThat(script.getWebResponse().getContentAsString(), is("varname = makeStaplerProxy('" + j.contextPath + "/theWellKnownRoot','test',['annotatedJsMethod2','byName2']);")); + } + assertThat(root.invocations, is(1)); + } + + @Test + public void bindNull() throws Exception { + final RootActionImpl root = ExtensionList.lookupSingleton(RootActionImpl.class); + try (JenkinsRule.WebClient wc = j.createWebClient()) { + final ScriptException exception = assertThrows(ScriptException.class, () -> wc.goTo(root.getUrlName() + "/null")); + assertThat(exception.getFailingLineNumber(), is(2)); + assertThat(exception.getFailingColumnNumber(), is(0)); + assertThat(exception.getMessage(), containsString("TypeError: Cannot call method \"byName1\" of null")); + + final HtmlPage htmlPage = exception.getPage(); + final String scriptUrl = htmlPage.getElementsByTagName("script").stream().filter(it -> it.getAttribute("src").equals(j.contextPath + "/$stapler/bound/script/null?var=varname")).findFirst().orElseThrow().getAttribute("src"); + + final Page script = wc.goTo(StringUtils.removeStart(scriptUrl, j.contextPath + "/"), "text/javascript"); + final String content = script.getWebResponse().getContentAsString(); + assertThat(content, is("varname = null;")); + } + assertThat(root.invocations, is(0)); + } + + @Test + public void bindUnsafe() throws Exception { + final RootActionImpl root = ExtensionList.lookupSingleton(RootActionImpl.class); + try (JenkinsRule.WebClient wc = j.createWebClient()) { + final HtmlPage htmlPage = wc.goTo(root.getUrlName() + "/unsafe-var"); + final String content = htmlPage + .getElementsByTagName("script") + .stream() + .filter(it -> it.getTextContent().contains("makeStaplerProxy")) + .findFirst() + .orElseThrow() + .getTextContent(); + + assertThat(content, startsWith("window['varname']=makeStaplerProxy('" + j.contextPath + "/$stapler/bound/")); + assertThat(content, endsWith("','test',['annotatedJsMethod1','byName1']);")); + } + assertThat(root.invocations, is(1)); + } + + @Test + public void bindInlineNull() throws Exception { + final RootActionImpl root = ExtensionList.lookupSingleton(RootActionImpl.class); + try (JenkinsRule.WebClient wc = j.createWebClient()) { + final HtmlPage htmlPage = wc.goTo(root.getUrlName() + "/inline-null"); + final String content = htmlPage + .getElementsByTagName("script") + .stream() + .filter(it -> it.getTextContent().contains("var inline")) + .findFirst() + .orElseThrow() + .getTextContent(); + + assertThat(content, containsString("var inline = null")); + } + assertThat(root.invocations, is(0)); + } + + @TestExtension + public static class RootActionImpl extends InvisibleAction implements RootAction { + private int invocations; + + @Override + public String getUrlName() { + return "theRoot"; + } + + @JavaScriptMethod + public void annotatedJsMethod1(String foo) {} + + public void jsByName1() { + invocations++; + } + } + + @TestExtension + public static class RootActionWithWellKnownURL extends InvisibleAction implements RootAction, WithWellKnownURL { + private int invocations; + + @Override + public String getUrlName() { + return "theWellKnownRoot"; + } + + @Override + public String getWellKnownUrl() { + return "/" + getUrlName(); + } + + @JavaScriptMethod + public void annotatedJsMethod2(String foo) {} + + public void jsByName2() { + invocations++; + } + } +} diff --git a/test/src/test/resources/hudson/ProxyConfigurationManagerTest/proxy.xml b/test/src/test/resources/hudson/ProxyConfigurationManagerTest/proxy.xml new file mode 100644 index 0000000000..c61d552a51 --- /dev/null +++ b/test/src/test/resources/hudson/ProxyConfigurationManagerTest/proxy.xml @@ -0,0 +1,5 @@ + + + proxy.mycorp + 80 + \ No newline at end of file diff --git a/test/src/test/resources/jenkins/model/NodesTest/vetoLoad/nodes/one-node/config.xml b/test/src/test/resources/jenkins/model/NodesTest/vetoLoad/nodes/one-node/config.xml new file mode 100644 index 0000000000..6e024ed425 --- /dev/null +++ b/test/src/test/resources/jenkins/model/NodesTest/vetoLoad/nodes/one-node/config.xml @@ -0,0 +1,19 @@ + + + one-node + + /tmp + 1 + NORMAL + + + + false + remoting + false + + true + + + + diff --git a/test/src/test/resources/lib/form/ValidateButtonTest/TestValidateIsCalled/index.jelly b/test/src/test/resources/lib/form/ValidateButtonTest/TestValidateIsCalled/index.jelly index b393c22126..88db0f5a04 100644 --- a/test/src/test/resources/lib/form/ValidateButtonTest/TestValidateIsCalled/index.jelly +++ b/test/src/test/resources/lib/form/ValidateButtonTest/TestValidateIsCalled/index.jelly @@ -50,7 +50,27 @@ THE SOFTWARE. e2 - + + + + + + + + + + + + + + + + + + + + + diff --git a/test/src/test/resources/lib/layout/SvgIconTest/TestRootAction/index.jelly b/test/src/test/resources/lib/layout/SvgIconTest/TestRootAction/index.jelly index 40d7b5b19c..163a2059b2 100644 --- a/test/src/test/resources/lib/layout/SvgIconTest/TestRootAction/index.jelly +++ b/test/src/test/resources/lib/layout/SvgIconTest/TestRootAction/index.jelly @@ -26,7 +26,7 @@ THE SOFTWARE.
- +
diff --git a/test/src/test/resources/org/kohsuke/stapler/BindTest/RootActionImpl/adjunct.js b/test/src/test/resources/org/kohsuke/stapler/BindTest/RootActionImpl/adjunct.js new file mode 100644 index 0000000000..952a9de748 --- /dev/null +++ b/test/src/test/resources/org/kohsuke/stapler/BindTest/RootActionImpl/adjunct.js @@ -0,0 +1,2 @@ +// eslint-disable-next-line no-undef +varname.byName1(); diff --git a/test/src/test/resources/org/kohsuke/stapler/BindTest/RootActionImpl/index.jelly b/test/src/test/resources/org/kohsuke/stapler/BindTest/RootActionImpl/index.jelly new file mode 100644 index 0000000000..1de10c5ac2 --- /dev/null +++ b/test/src/test/resources/org/kohsuke/stapler/BindTest/RootActionImpl/index.jelly @@ -0,0 +1,10 @@ + + + + +

Root Action

+ + +
+
+
diff --git a/test/src/test/resources/org/kohsuke/stapler/BindTest/RootActionImpl/inline-null.jelly b/test/src/test/resources/org/kohsuke/stapler/BindTest/RootActionImpl/inline-null.jelly new file mode 100644 index 0000000000..b99131131c --- /dev/null +++ b/test/src/test/resources/org/kohsuke/stapler/BindTest/RootActionImpl/inline-null.jelly @@ -0,0 +1,11 @@ + + + + +

Root Action

+ +
+
+
diff --git a/test/src/test/resources/org/kohsuke/stapler/BindTest/RootActionImpl/null.jelly b/test/src/test/resources/org/kohsuke/stapler/BindTest/RootActionImpl/null.jelly new file mode 100644 index 0000000000..cb81f08c8d --- /dev/null +++ b/test/src/test/resources/org/kohsuke/stapler/BindTest/RootActionImpl/null.jelly @@ -0,0 +1,10 @@ + + + + +

Root Action with null

+ + +
+
+
diff --git a/test/src/test/resources/org/kohsuke/stapler/BindTest/RootActionImpl/unsafe-var.jelly b/test/src/test/resources/org/kohsuke/stapler/BindTest/RootActionImpl/unsafe-var.jelly new file mode 100644 index 0000000000..ccb261682b --- /dev/null +++ b/test/src/test/resources/org/kohsuke/stapler/BindTest/RootActionImpl/unsafe-var.jelly @@ -0,0 +1,10 @@ + + + + +

Root Action

+ + +
+
+
diff --git a/test/src/test/resources/org/kohsuke/stapler/BindTest/RootActionWithWellKnownURL/adjunct.js b/test/src/test/resources/org/kohsuke/stapler/BindTest/RootActionWithWellKnownURL/adjunct.js new file mode 100644 index 0000000000..268b06ff66 --- /dev/null +++ b/test/src/test/resources/org/kohsuke/stapler/BindTest/RootActionWithWellKnownURL/adjunct.js @@ -0,0 +1,2 @@ +// eslint-disable-next-line no-undef +varname.byName2(); diff --git a/test/src/test/resources/org/kohsuke/stapler/BindTest/RootActionWithWellKnownURL/index.jelly b/test/src/test/resources/org/kohsuke/stapler/BindTest/RootActionWithWellKnownURL/index.jelly new file mode 100644 index 0000000000..563805f488 --- /dev/null +++ b/test/src/test/resources/org/kohsuke/stapler/BindTest/RootActionWithWellKnownURL/index.jelly @@ -0,0 +1,10 @@ + + + + +

Root Action

+ + +
+
+
diff --git a/test/src/test/resources/plugins/security3037-update-center.json b/test/src/test/resources/plugins/security3037-update-center.json index c956749c05..7d33a306ab 100644 --- a/test/src/test/resources/plugins/security3037-update-center.json +++ b/test/src/test/resources/plugins/security3037-update-center.json @@ -50,7 +50,7 @@ updateCenter.post( "previousTimestamp": "2022-05-24T12:27:03.00Z", "previousVersion": "581.v0c46fa_697ffd", "releaseTimestamp": "2022-06-29T21:02:16.00Z", - "requiredCore": "2.999", + "requiredCore": "9999999", "scm": "https://github.com/jenkinsci/workflow-aggregator-plugin", "sha1": "6iNlKTnXvr4W+9Svwy1+ctyqgvo=", "sha256": "P0SFgnqOBFZjxkdKORiQUbrhdlfW5YU7tOpe85bfGWE=", diff --git a/war/package.json b/war/package.json index 5fd76f8acc..6f0bcfd526 100644 --- a/war/package.json +++ b/war/package.json @@ -23,29 +23,29 @@ "lint": "yarn lint:js && yarn lint:css" }, "devDependencies": { - "@babel/cli": "7.23.4", - "@babel/core": "7.23.6", - "@babel/preset-env": "7.23.6", + "@babel/cli": "7.24.1", + "@babel/core": "7.24.3", + "@babel/preset-env": "7.24.3", "babel-loader": "9.1.3", "clean-webpack-plugin": "4.0.0", - "css-loader": "6.8.1", - "css-minimizer-webpack-plugin": "5.0.1", - "eslint": "8.55.0", + "css-loader": "6.10.0", + "css-minimizer-webpack-plugin": "6.0.0", + "eslint": "8.57.0", "eslint-config-prettier": "9.1.0", "handlebars-loader": "1.7.3", - "mini-css-extract-plugin": "2.7.6", - "postcss": "8.4.32", - "postcss-loader": "7.3.3", - "postcss-preset-env": "9.3.0", + "mini-css-extract-plugin": "2.8.1", + "postcss": "8.4.38", + "postcss-loader": "8.1.1", + "postcss-preset-env": "9.5.2", "postcss-scss": "4.0.9", - "prettier": "3.1.1", - "sass": "1.69.5", - "sass-loader": "13.3.2", - "style-loader": "3.3.3", - "stylelint": "16.0.2", - "stylelint-checkstyle-reporter": "0.2.0", - "stylelint-config-standard": "33.0.0", - "webpack": "5.89.0", + "prettier": "3.2.5", + "sass": "1.72.0", + "sass-loader": "14.1.1", + "style-loader": "3.3.4", + "stylelint": "16.3.1", + "stylelint-checkstyle-reporter": "1.0.0", + "stylelint-config-standard": "36.0.0", + "webpack": "5.91.0", "webpack-cli": "5.1.4", "webpack-remove-empty-scripts": "1.0.4" }, @@ -54,7 +54,7 @@ "hotkeys-js": "3.12.2", "jquery": "3.7.1", "lodash": "4.17.21", - "sortablejs": "1.15.1", + "sortablejs": "1.15.2", "tippy.js": "6.3.7", "window-handle": "1.0.1" }, @@ -62,5 +62,5 @@ "defaults", "not IE 11" ], - "packageManager": "yarn@4.0.2" + "packageManager": "yarn@4.1.1" } diff --git a/war/pom.xml b/war/pom.xml index 2808d786b3..8a6a2b81c4 100644 --- a/war/pom.xml +++ b/war/pom.xml @@ -22,7 +22,7 @@ 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. --> - + 4.0.0 @@ -46,12 +46,13 @@ THE SOFTWARE. localhost 8080 - 20.10.0 + 2.12.1-101.v85b_e08b_780dd + 20.12.0 1.22.19 - 4.0.2 - 825003a0f561ad09a3b1ac4a3b3ea6207af2796d54f62a9420520915721f5186 + 4.1.1 + f3cc0eda8e5560e529c7147565b30faa43b4e472d90e8634d7134a37c7f59781 @@ -81,24 +82,6 @@ THE SOFTWARE. org.jenkins-ci.main jenkins-core ${project.version} - - - com.google.code.findbugs - jsr305 - - - - jakarta.servlet.jsp - jakarta.servlet.jsp-api - - - - org.kohsuke.metainf-services - metainf-services - -
org.jenkins-ci.main @@ -289,14 +272,14 @@ THE SOFTWARE. org.jenkins-ci.plugins mailer - 463.vedf8358e006b_ + 472.vf7c289a_4b_420 hpi org.jenkins-ci.plugins matrix-auth - 3.2.1 + 3.2.2 hpi @@ -310,42 +293,42 @@ THE SOFTWARE. org.jenkins-ci.plugins matrix-project - 818.v7eb_e657db_924 + 822.824.v14451b_c0fd42 hpi org.jenkins-ci.plugins script-security - 1294.v99333c047434 + 1326.vdb_c154de8669 hpi org.jenkins-ci.plugins junit - 1240.vf9529b_881428 + 1265.v65b_14fa_f12f0 hpi org.jenkins-ci.plugins.workflow workflow-api - 1283.v99c10937efcb_ + 1291.v51fd2a_625da_7 hpi io.jenkins.plugins plugin-util-api - 3.6.0 + 4.1.0 hpi io.jenkins.plugins bootstrap5-api - 5.3.2-2 + 5.3.3-1 hpi @@ -360,7 +343,7 @@ THE SOFTWARE. org.jenkins-ci.plugins.workflow workflow-support - 865.v43e78cc44e0d + 896.v175a_a_9c5b_78f hpi @@ -368,14 +351,14 @@ THE SOFTWARE. org.jenkins-ci.plugins jackson2-api - 2.15.3-372.v309620682326 + 2.17.0-379.v02de8ec9f64c hpi io.jenkins.plugins echarts-api - 5.4.3-1 + 5.5.0-1 hpi @@ -399,15 +382,7 @@ THE SOFTWARE. io.jenkins.plugins jquery3-api - 3.7.1-1 - hpi - - - - - io.jenkins.plugins - popper2-api - 2.11.6-4 + 3.7.1-2 hpi @@ -415,7 +390,7 @@ THE SOFTWARE. io.jenkins.plugins font-awesome-api - 6.4.2-1 + 6.5.1-3 hpi @@ -423,28 +398,28 @@ THE SOFTWARE. org.jenkins-ci.plugins.workflow workflow-step-api - 639.v6eca_cd8c04a_a_ + 657.v03b_e8115821b_ hpi org.jenkins-ci.plugins scm-api - 683.vb_16722fb_b_80b_ + 689.v237b_6d3a_ef7f hpi org.jenkins-ci.plugins structs - 325.vcb_307d2a_2782 + 337.v1b_04ea_4df7c8 hpi org.jenkins-ci.plugins bouncycastle-api - 2.29 + 2.30.1.77-225.v26ea_c9455fd9 hpi @@ -472,14 +447,14 @@ THE SOFTWARE. org.jenkins-ci.modules sshd - 3.312.v1c601b_c83b_0e + 3.322.v159e91f6a_550 hpi org.jenkins-ci.plugins trilead-api - 2.84.v72119de229b_7 + 2.142.v748523a_76693 hpi @@ -510,6 +485,13 @@ THE SOFTWARE. 4.5.14-208.v438351942757 hpi + + + io.jenkins.plugins + asm-api + 9.6-3.v2e1fa_b_338cd7 + hpi + io.jenkins.plugins @@ -521,7 +503,14 @@ THE SOFTWARE. io.jenkins.plugins commons-text-api - 1.11.0-94.v3e1f4a_926e49 + 1.11.0-95.v22a_d30ee5d36 + hpi + + + + io.jenkins.plugins + gson-api + 2.10.1-15.v0d99f670e0a_7 hpi @@ -535,28 +524,35 @@ THE SOFTWARE. io.jenkins.plugins jakarta-activation-api - 2.0.1-3 + 2.1.3-1 hpi io.jenkins.plugins jakarta-mail-api - 2.0.1-3 + 2.1.3-1 + hpi + + + + io.jenkins.plugins + json-api + 20240303-41.v94e11e6de726 hpi io.jenkins.plugins.mina-sshd-api mina-sshd-api-common - 2.11.0-86.v836f585d47fa_ + ${mina-sshd-api.version} hpi io.jenkins.plugins.mina-sshd-api mina-sshd-api-core - 2.11.0-86.v836f585d47fa_ + ${mina-sshd-api.version} hpi @@ -636,7 +632,7 @@ THE SOFTWARE. org.eclipse.jetty jetty-maven-plugin - 10.0.18 + 10.0.20 - + 4.0.0 diff --git a/websocket/spi/pom.xml b/websocket/spi/pom.xml index 77abcc3b72..a99aada6a4 100644 --- a/websocket/spi/pom.xml +++ b/websocket/spi/pom.xml @@ -22,7 +22,7 @@ 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. --> - + 4.0.0