diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS
new file mode 100644
index 000000000..6cbd86e12
--- /dev/null
+++ b/.github/CODEOWNERS
@@ -0,0 +1 @@
+* @jenkinsci/github-plugin-developers
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
new file mode 100644
index 000000000..dbae4a465
--- /dev/null
+++ b/.github/dependabot.yml
@@ -0,0 +1,10 @@
+version: 2
+updates:
+ - package-ecosystem: maven
+ directory: "/"
+ schedule:
+ interval: weekly
+ open-pull-requests-limit: 10
+ target-branch: master
+ labels:
+ - dependencies
diff --git a/.github/workflows/jenkins-security-scan.yml b/.github/workflows/jenkins-security-scan.yml
new file mode 100644
index 000000000..c7b41fc29
--- /dev/null
+++ b/.github/workflows/jenkins-security-scan.yml
@@ -0,0 +1,21 @@
+name: Jenkins Security Scan
+
+on:
+ push:
+ branches:
+ - master
+ pull_request:
+ types: [ opened, synchronize, reopened ]
+ workflow_dispatch:
+
+permissions:
+ security-events: write
+ contents: read
+ actions: read
+
+jobs:
+ security-scan:
+ uses: jenkins-infra/jenkins-security-scan/.github/workflows/jenkins-security-scan.yaml@v2
+ with:
+ java-cache: 'maven' # Optionally enable use of a build dependency cache. Specify 'maven' or 'gradle' as appropriate.
+ # java-version: 21 # Optionally specify what version of Java to set up for the build, or remove to use a recent default.
diff --git a/.github/workflows/release-drafter.yml b/.github/workflows/release-drafter.yml
new file mode 100644
index 000000000..1f8a181b6
--- /dev/null
+++ b/.github/workflows/release-drafter.yml
@@ -0,0 +1,17 @@
+# Note: additional setup is required, see https://github.com/jenkinsci/.github/blob/master/.github/release-drafter.adoc
+
+name: Release Drafter
+
+on:
+ push:
+ branches:
+ - master
+
+jobs:
+ update_release_draft:
+ runs-on: ubuntu-latest
+ steps:
+ # Drafts your next Release notes as Pull Requests are merged into the default branch
+ - uses: release-drafter/release-drafter@v6
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
diff --git a/.gitignore b/.gitignore
index 99b3c61f0..41dfd3e40 100644
--- a/.gitignore
+++ b/.gitignore
@@ -11,3 +11,4 @@ target
# autogenerated resources
src/main/webapp/css/*
+.vscode/
diff --git a/.mvn/extensions.xml b/.mvn/extensions.xml
index 43d628161..4e0774d51 100644
--- a/.mvn/extensions.xml
+++ b/.mvn/extensions.xml
@@ -2,6 +2,6 @@
io.jenkins.tools.incrementalsgit-changelist-maven-extension
- 1.2
+ 1.8
diff --git a/.mvn/wrapper/MavenWrapperDownloader.java b/.mvn/wrapper/MavenWrapperDownloader.java
deleted file mode 100644
index fa4f7b499..000000000
--- a/.mvn/wrapper/MavenWrapperDownloader.java
+++ /dev/null
@@ -1,110 +0,0 @@
-/*
-Licensed to the Apache Software Foundation (ASF) under one
-or more contributor license agreements. See the NOTICE file
-distributed with this work for additional information
-regarding copyright ownership. The ASF licenses this file
-to you under the Apache License, Version 2.0 (the
-"License"); you may not use this file except in compliance
-with the License. You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing,
-software distributed under the License is distributed on an
-"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-KIND, either express or implied. See the License for the
-specific language governing permissions and limitations
-under the License.
-*/
-
-import java.net.*;
-import java.io.*;
-import java.nio.channels.*;
-import java.util.Properties;
-
-public class MavenWrapperDownloader {
-
- /**
- * Default URL to download the maven-wrapper.jar from, if no 'downloadUrl' is provided.
- */
- private static final String DEFAULT_DOWNLOAD_URL =
- "https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.4.2/maven-wrapper-0.4.2.jar";
-
- /**
- * Path to the maven-wrapper.properties file, which might contain a downloadUrl property to
- * use instead of the default one.
- */
- private static final String MAVEN_WRAPPER_PROPERTIES_PATH =
- ".mvn/wrapper/maven-wrapper.properties";
-
- /**
- * Path where the maven-wrapper.jar will be saved to.
- */
- private static final String MAVEN_WRAPPER_JAR_PATH =
- ".mvn/wrapper/maven-wrapper.jar";
-
- /**
- * Name of the property which should be used to override the default download url for the wrapper.
- */
- private static final String PROPERTY_NAME_WRAPPER_URL = "wrapperUrl";
-
- public static void main(String args[]) {
- System.out.println("- Downloader started");
- File baseDirectory = new File(args[0]);
- System.out.println("- Using base directory: " + baseDirectory.getAbsolutePath());
-
- // If the maven-wrapper.properties exists, read it and check if it contains a custom
- // wrapperUrl parameter.
- File mavenWrapperPropertyFile = new File(baseDirectory, MAVEN_WRAPPER_PROPERTIES_PATH);
- String url = DEFAULT_DOWNLOAD_URL;
- if(mavenWrapperPropertyFile.exists()) {
- FileInputStream mavenWrapperPropertyFileInputStream = null;
- try {
- mavenWrapperPropertyFileInputStream = new FileInputStream(mavenWrapperPropertyFile);
- Properties mavenWrapperProperties = new Properties();
- mavenWrapperProperties.load(mavenWrapperPropertyFileInputStream);
- url = mavenWrapperProperties.getProperty(PROPERTY_NAME_WRAPPER_URL, url);
- } catch (IOException e) {
- System.out.println("- ERROR loading '" + MAVEN_WRAPPER_PROPERTIES_PATH + "'");
- } finally {
- try {
- if(mavenWrapperPropertyFileInputStream != null) {
- mavenWrapperPropertyFileInputStream.close();
- }
- } catch (IOException e) {
- // Ignore ...
- }
- }
- }
- System.out.println("- Downloading from: : " + url);
-
- File outputFile = new File(baseDirectory.getAbsolutePath(), MAVEN_WRAPPER_JAR_PATH);
- if(!outputFile.getParentFile().exists()) {
- if(!outputFile.getParentFile().mkdirs()) {
- System.out.println(
- "- ERROR creating output direcrory '" + outputFile.getParentFile().getAbsolutePath() + "'");
- }
- }
- System.out.println("- Downloading to: " + outputFile.getAbsolutePath());
- try {
- downloadFileFromURL(url, outputFile);
- System.out.println("Done");
- System.exit(0);
- } catch (Throwable e) {
- System.out.println("- Error downloading");
- e.printStackTrace();
- System.exit(1);
- }
- }
-
- private static void downloadFileFromURL(String urlString, File destination) throws Exception {
- URL website = new URL(urlString);
- ReadableByteChannel rbc;
- rbc = Channels.newChannel(website.openStream());
- FileOutputStream fos = new FileOutputStream(destination);
- fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE);
- fos.close();
- rbc.close();
- }
-
-}
diff --git a/.mvn/wrapper/maven-wrapper.jar b/.mvn/wrapper/maven-wrapper.jar
deleted file mode 100755
index 01e679973..000000000
Binary files a/.mvn/wrapper/maven-wrapper.jar and /dev/null differ
diff --git a/.mvn/wrapper/maven-wrapper.properties b/.mvn/wrapper/maven-wrapper.properties
index 00d32aab1..d58dfb70b 100755
--- a/.mvn/wrapper/maven-wrapper.properties
+++ b/.mvn/wrapper/maven-wrapper.properties
@@ -1 +1,19 @@
-distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.5.4/apache-maven-3.5.4-bin.zip
\ No newline at end of file
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+wrapperVersion=3.3.2
+distributionType=only-script
+distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.9/apache-maven-3.9.9-bin.zip
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index c4ecd635f..82d635c21 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -1,16 +1,16 @@
# Functional contribution
-We are welcome for any contribution. But every new feature implemented in this plugin should:
+We welcome any contribution. But every new feature implemented in this plugin should:
-- Be useful enough for lot of people (should not cover only your professional case).
-- Should not break existing use cases and should avoid breaking the backward compatibility in existing APIs.
- - If the compatibility break is required, it should be well justified.
+- Be useful enough for many people (should cover more than just your professional case).
+- Should not break existing use cases and should avoid breaking backward compatibility in existing APIs.
+ - If a compatibility break is required, it should be well justified.
[Guide](https://wiki.eclipse.org/Evolving_Java-based_APIs_2)
- and [jenkins solutions](https://wiki.jenkins-ci.org/display/JENKINS/Hint+on+retaining+backward+compatibility) can help to retain the backward compatibility.
+ and [jenkins solutions](https://wiki.jenkins-ci.org/display/JENKINS/Hint+on+retaining+backward+compatibility) can help to retain backward compatibility.
- Should be easily maintained (so maintainers need some time to think about architecture of implementation).
- Have at least one test for positive use case.
-This plugin is used by lot of people, so it should be stable enough. Please ensure your change is compatible at least with the last LTS line.
+This plugin is used by many people, so it should be stable. Please ensure your change is compatible at least with the last LTS line.
Any core dependency upgrade must be justified.
# Code Style Guidelines
@@ -20,9 +20,9 @@ Checkstyle rules are more important than this document.
## Resulting from long experience
-* To the largest extent possible, all fields shall be private. Use an IDE to generate the getters and setters.
-* If a class has more than one `volatile` member field, it is probable that there are subtle race conditions. Please consider where appropriate encapsulation of the multiple fields into an immutable value object replace the multiple `volatile` member fields with a single `volatile` reference to the value object (or perhaps better yet an `AtomicReference` to allow for `compareAndSet` - if compare-and-set logic is appropriate).
-* If it is `Serializable` it shall have a `serialVersionUID` field. Unless code has shipped to users, the initial value of the `serialVersionUID` field shall be `1L`.
+- To the largest extent possible, all fields should be private. Use an IDE to generate the getters and setters.
+- If a class has more than one `volatile` member field, it is probable that there are subtle race conditions. Please consider, where appropriate, encapsulation of multiple fields into an immutable value object. That is, to replace multiple `volatile` member fields with a single `volatile` reference to the value object (or perhaps better yet an `AtomicReference` to allow for `compareAndSet` - if compare-and-set logic is appropriate).
+- If it is `Serializable`, it should have a `serialVersionUID` field. Unless code has shipped to users, the initial value of the `serialVersionUID` field should be `1L`.
## Indentation
@@ -32,12 +32,12 @@ Checkstyle rules are more important than this document.
## Field Naming Conventions
1. "hungarian"-style notation is banned (e.g. instance variable names preceded by an 'm', etc.).
-2. If the field is `static final` then it shall be named in `ALL_CAPS_WITH_UNDERSCORES`.
+2. If the field is `static final`, then it should be named as `ALL_CAPS_WITH_UNDERSCORES`.
3. Start variable names with a lowercase letter and use camelCase rather than under_scores.
4. Spelling and abbreviations: If the word is widely used in the JVM runtime, stick with the spelling/abbreviation in the JVM runtime, e.g. `color` over `colour`, `sync` over `synch`, `async` over `asynch`, etc.
5. It is acceptable to use `i`, `j`, `k` for loop indices and iterators. If you need more than three, you are likely doing something wrong and as such you shall either use full descriptive names or refactor.
6. It is acceptable to use `e` for the exception in a `try...catch` block.
-7. You shall never use `l` (i.e. lower case `L`) as a variable name.
+7. Never use `l` (i.e. lower case `L`) as a variable name.
## Line Length
@@ -45,32 +45,33 @@ To the greatest extent possible, please wrap lines to ensure that they do not ex
## Maven POM file layout
-* The `pom.xml` file shall use the sequencing of elements as defined by the `mvn tidy:pom` command (after any indenting fix-up).
-* If you are introducing a property to the `pom.xml` the property must be used in at least two distinct places in the model or a comment justifying the use of a property shall be provided.
-* If the `` is in the groupId `org.apache.maven.plugins` you shall omit the ``.
-* All `` entries shall have an explicit version defined unless inherited from the parent.
+- The `pom.xml` file should use the sequencing of elements as defined by the `mvn tidy:pom` command (after any indenting fix-up).
+- If you are introducing a property to the `pom.xml`, the property must be used in at least two distinct places in the model, or a comment justifying the use of a property should be provided.
+- If the `` is in the groupId `org.apache.maven.plugins`, you should omit the ``.
+- All `` entries should have an explicit version defined unless inherited from the parent.
## Java code style
### Imports
-* For code in `src/main`:
- - `*` imports are banned.
- - `static` imports are preferred until not mislead.
-* For code in `src/test`:
- - `*` imports of anything other than JUnit classes and Hamcrest matchers are banned.
+- For code in `src/main`:
+ - `*` imports are banned.
+ - `static` imports are preferred until not mislead.
+- For code in `src/test`:
+ - `*` imports of anything other than JUnit classes and Hamcrest matchers are banned.
### Annotation placement
-* Annotations on classes, interfaces, annotations, enums, methods, fields and local variables shall be on the lines immediately preceding the line where modifier(s) (e.g. `public` / `protected` / `private` / `final`, etc) would be appropriate.
-* Annotations on method arguments shall, to the largest extent possible, be on the same line as the method argument (and, if present, before the `final` modifier).
+- Annotations on classes, interfaces, annotations, enums, methods, fields and local variables should be on the lines immediately preceding the line where modifier(s) (e.g. `public` / `protected` / `private` / `final`, etc) would be appropriate.
+- Annotations on method arguments should, to the largest extent possible, be on the same line as the method argument (and, if present, before the `final` modifier).
### Javadoc
-* Each class shall have a Javadoc comment.
-* Unless the method is `private`, it shall have a Javadoc comment.
-* Getters and Setters shall have a Javadoc comment. The following is prefered:
- ```
+- Each class should have a Javadoc comment.
+- Unless the method is `private`, it should have a Javadoc comment.
+- Getters and Setters should have a Javadoc comment. The following is prefered:
+
+ ```java
/**
* The count of widgets
*/
@@ -94,43 +95,44 @@ To the greatest extent possible, please wrap lines to ensure that they do not ex
this.widgetCount = widgetCount;
}
```
-* When adding a new class / interface / etc, it shall have a `@since` doc comment. The version shall be `FIXME` (or `TODO`) to indicate that the person merging the change should replace the `FIXME` with the next release version number. The fields and methods within a class/interface (but not nested classes) will be assumed to have the `@since` annotation of their class/interface unless a different `@since` annotation is present.
+
+- When adding a new class / interface / etc, it should have a `@since` doc comment. The version should be `FIXME` (or `TODO`) to indicate that the person merging the change should replace the `FIXME` with the next release version number. The fields and methods within a class/interface (but not nested classes) will be assumed to have the `@since` annotation of their class/interface unless a different `@since` annotation is present.
### IDE Configuration
-* Eclipse, by and large the IDE defaults are acceptable with the following changes:
- - Tab policy to `Spaces only`.
- - Indent statements within `switch` body.
- - Maximum line width `120`.
- - Line wrapping, ensure all to `wrap where necessary`.
- - Organize imports alphabetically, no grouping.
-* NetBeans, by and large the IDE defaults are acceptable with the following changes:
- - Tabs and Indents:
- + Change Right Margin to `120`.
- + Indent case statements in switch.
- - Wrapping:
- + Change all the `Never` values to `If Long`.
- + Select the checkbox for Wrap After Assignment Operators.
-* IntelliJ, by and large the IDE defaults are acceptable with the following changes:
- - Wrapping and Braces:
- + Change `Do not wrap` to `Wrap if long`.
- + Change `Do not force` to `Always`.
- - Javadoc:
- + Disable generating `` on empty lines.
- - Imports:
- + Class count to use import with '*': `9999`.
- + Names count to use static import with '*': `99999`.
- + Import Layout:
- * import all other imports.
- * blank line.
- * import static all other imports.
+- Eclipse: by and large the IDE defaults are acceptable with the following changes:
+ - Tab policy to `Spaces only`.
+ - Indent statements within `switch` body.
+ - Maximum line width `120`.
+ - Line wrapping, ensure all to `wrap where necessary`.
+ - Organize imports alphabetically, no grouping.
+- NetBeans: by and large the IDE defaults are acceptable with the following changes:
+ - Tabs and Indents:
+ - Change Right Margin to `120`.
+ - Indent case statements in switch.
+ - Wrapping:
+ - Change all the `Never` values to `If Long`.
+ - Select the checkbox for Wrap After Assignment Operators.
+- IntelliJ: by and large the IDE defaults are acceptable with the following changes:
+ - Wrapping and Braces:
+ - Change `Do not wrap` to `Wrap if long`.
+ - Change `Do not force` to `Always`.
+ - Javadoc:
+ - Disable generating `` on empty lines.
+ - Imports:
+ - Class count to use import with '*': `9999`.
+ - Names count to use static import with '*': `99999`.
+ - Import Layout:
+ - import all other imports.
+ - blank line.
+ - import static all other imports.
## Issues
-This project uses [Jenkins Jira issue tracker](https://issues.jenkins-ci.org)
-with [github-plugin](https://issues.jenkins-ci.org/browse/JENKINS/component/15896) component.
+This project uses the [Jenkins Jira issue tracker](https://issues.jenkins.io/)
+with the [github-plugin](https://issues.jenkins.io/browse/JENKINS/component/15896) component.
## Links
-- https://wiki.jenkins-ci.org/display/JENKINS/contributing
-- https://wiki.jenkins-ci.org/display/JENKINS/Extend+Jenkins
+- https://www.jenkins.io/participate/
+- https://www.jenkins.io/doc/developer/
diff --git a/Jenkinsfile b/Jenkinsfile
index a229fa517..739042f72 100644
--- a/Jenkinsfile
+++ b/Jenkinsfile
@@ -1 +1,4 @@
-buildPlugin()
+buildPlugin(useContainerAgent: true, configurations: [
+ [platform: 'linux', jdk: 21],
+ [platform: 'windows', jdk: 17],
+])
diff --git a/README.md b/README.md
index caeb3f3bc..2bdb9ff06 100644
--- a/README.md
+++ b/README.md
@@ -1,9 +1,9 @@
-# Github Plugin
+# GitHub Plugin
[](https://codecov.io/gh/jenkinsci/github-plugin)
[](LICENSE)
-This plugin integrates Jenkins with [Github](http://github.com/)
+This plugin integrates Jenkins with [GitHub](http://github.com/)
projects.The plugin currently has three major functionalities:
- Create hyperlinks between your Jenkins projects and GitHub
@@ -17,14 +17,14 @@ projects.The plugin currently has three major functionalities:
## Hyperlinks between changes
-The Github plugin decorates Jenkins "Changes" pages to create links to
-your Github commit and issue pages. It adds a sidebar link that links
-back to the Github project page.
+The GitHub plugin decorates Jenkins "Changes" pages to create links to
+your GitHub commit and issue pages. It adds a sidebar link that links
+back to the GitHub project page.


-When creating a job, specify that is connects to git. Under "Github
+When creating a job, specify that is connects to git. Under "GitHub
project", put in: git@github.com:*Person*/*Project*.git Under "Source
Code Management" select Git, and put in
git@github.com:*Person*/*Project*.git
@@ -36,7 +36,7 @@ repositories](https://help.github.com/post-receive-hooks/). This trigger
only kicks git-plugin internal polling algo for every incoming event
against matched repo.
-> This plugin was previously named as "Build when a change is pushed to GitHub"
+> This trigger was previously named as "Build when a change is pushed to GitHub"
## Usage
@@ -103,8 +103,8 @@ only credentials that matched by predefined domains.

**Step 3.** Once that configuration is done, go to the project config of
-each job you want triggered automatically and simply check "Build when a
-change is pushed to GitHub" under "Build Triggers". With this, every new
+each job you want triggered automatically and simply check "GitHub hook trigger for GITScm polling"
+under "Build Triggers". With this, every new
push to the repository automatically triggers a new build.
Note that there's only one URL and it receives all post-receive POSTs
@@ -180,7 +180,7 @@ Additional info:
Jenkins service by right clicking on Jenkins (in the services
window), and hit "Restart".
- Jenkins does not support passphrases for SSH keys. Therefore, if you
- set one while running the initial Github configuration, rerun it and
+ set one while running the initial GitHub configuration, rerun it and
don't set one.
## Pipeline examples
@@ -204,7 +204,7 @@ void setBuildStatus(String message, String state) {
setBuildStatus("Build complete", "SUCCESS");
```
-More complex example (can be used with multiply scm sources in pipeline)
+More complex example (can be used with multiple scm sources in pipeline)
```groovy
def getRepoURL() {
diff --git a/docs/images/changes-2.png b/docs/images/changes-2.png
index de0c2ca73..e55e4e9b2 100644
Binary files a/docs/images/changes-2.png and b/docs/images/changes-2.png differ
diff --git a/docs/images/changes.png b/docs/images/changes.png
index abef8afca..bc8e951cd 100644
Binary files a/docs/images/changes.png and b/docs/images/changes.png differ
diff --git a/docs/images/ghserver-config.png b/docs/images/ghserver-config.png
index 3cb96fe75..471151457 100644
Binary files a/docs/images/ghserver-config.png and b/docs/images/ghserver-config.png differ
diff --git a/docs/images/manage-token.png b/docs/images/manage-token.png
index 81264e6cd..6e506bec3 100644
Binary files a/docs/images/manage-token.png and b/docs/images/manage-token.png differ
diff --git a/docs/images/secret-text.png b/docs/images/secret-text.png
index a30a64761..5109c4f70 100644
Binary files a/docs/images/secret-text.png and b/docs/images/secret-text.png differ
diff --git a/mvnw b/mvnw
index 5551fde8e..19529ddf8 100755
--- a/mvnw
+++ b/mvnw
@@ -19,268 +19,241 @@
# ----------------------------------------------------------------------------
# ----------------------------------------------------------------------------
-# Maven2 Start Up Batch script
-#
-# Required ENV vars:
-# ------------------
-# JAVA_HOME - location of a JDK home dir
+# Apache Maven Wrapper startup batch script, version 3.3.2
#
# Optional ENV vars
# -----------------
-# M2_HOME - location of maven2's installed home dir
-# MAVEN_OPTS - parameters passed to the Java VM when running Maven
-# e.g. to debug Maven itself, use
-# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
-# MAVEN_SKIP_RC - flag to disable loading of mavenrc files
+# JAVA_HOME - location of a JDK home dir, required when download maven via java source
+# MVNW_REPOURL - repo url base for downloading maven distribution
+# MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven
+# MVNW_VERBOSE - true: enable verbose log; debug: trace the mvnw script; others: silence the output
# ----------------------------------------------------------------------------
-if [ -z "$MAVEN_SKIP_RC" ] ; then
-
- if [ -f /etc/mavenrc ] ; then
- . /etc/mavenrc
- fi
+set -euf
+[ "${MVNW_VERBOSE-}" != debug ] || set -x
- if [ -f "$HOME/.mavenrc" ] ; then
- . "$HOME/.mavenrc"
- fi
+# OS specific support.
+native_path() { printf %s\\n "$1"; }
+case "$(uname)" in
+CYGWIN* | MINGW*)
+ [ -z "${JAVA_HOME-}" ] || JAVA_HOME="$(cygpath --unix "$JAVA_HOME")"
+ native_path() { cygpath --path --windows "$1"; }
+ ;;
+esac
-fi
+# set JAVACMD and JAVACCMD
+set_java_home() {
+ # For Cygwin and MinGW, ensure paths are in Unix format before anything is touched
+ if [ -n "${JAVA_HOME-}" ]; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ]; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD="$JAVA_HOME/jre/sh/java"
+ JAVACCMD="$JAVA_HOME/jre/sh/javac"
+ else
+ JAVACMD="$JAVA_HOME/bin/java"
+ JAVACCMD="$JAVA_HOME/bin/javac"
-# OS specific support. $var _must_ be set to either true or false.
-cygwin=false;
-darwin=false;
-mingw=false
-case "`uname`" in
- CYGWIN*) cygwin=true ;;
- MINGW*) mingw=true;;
- Darwin*) darwin=true
- # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home
- # See https://developer.apple.com/library/mac/qa/qa1170/_index.html
- if [ -z "$JAVA_HOME" ]; then
- if [ -x "/usr/libexec/java_home" ]; then
- export JAVA_HOME="`/usr/libexec/java_home`"
- else
- export JAVA_HOME="/Library/Java/Home"
+ if [ ! -x "$JAVACMD" ] || [ ! -x "$JAVACCMD" ]; then
+ echo "The JAVA_HOME environment variable is not defined correctly, so mvnw cannot run." >&2
+ echo "JAVA_HOME is set to \"$JAVA_HOME\", but \"\$JAVA_HOME/bin/java\" or \"\$JAVA_HOME/bin/javac\" does not exist." >&2
+ return 1
fi
fi
- ;;
-esac
+ else
+ JAVACMD="$(
+ 'set' +e
+ 'unset' -f command 2>/dev/null
+ 'command' -v java
+ )" || :
+ JAVACCMD="$(
+ 'set' +e
+ 'unset' -f command 2>/dev/null
+ 'command' -v javac
+ )" || :
-if [ -z "$JAVA_HOME" ] ; then
- if [ -r /etc/gentoo-release ] ; then
- JAVA_HOME=`java-config --jre-home`
+ if [ ! -x "${JAVACMD-}" ] || [ ! -x "${JAVACCMD-}" ]; then
+ echo "The java/javac command does not exist in PATH nor is JAVA_HOME set, so mvnw cannot run." >&2
+ return 1
+ fi
fi
-fi
-
-if [ -z "$M2_HOME" ] ; then
- ## resolve links - $0 may be a link to maven's home
- PRG="$0"
+}
- # need this for relative symlinks
- while [ -h "$PRG" ] ; do
- ls=`ls -ld "$PRG"`
- link=`expr "$ls" : '.*-> \(.*\)$'`
- if expr "$link" : '/.*' > /dev/null; then
- PRG="$link"
- else
- PRG="`dirname "$PRG"`/$link"
- fi
+# hash string like Java String::hashCode
+hash_string() {
+ str="${1:-}" h=0
+ while [ -n "$str" ]; do
+ char="${str%"${str#?}"}"
+ h=$(((h * 31 + $(LC_CTYPE=C printf %d "'$char")) % 4294967296))
+ str="${str#?}"
done
+ printf %x\\n $h
+}
- saveddir=`pwd`
+verbose() { :; }
+[ "${MVNW_VERBOSE-}" != true ] || verbose() { printf %s\\n "${1-}"; }
- M2_HOME=`dirname "$PRG"`/..
+die() {
+ printf %s\\n "$1" >&2
+ exit 1
+}
- # make it fully qualified
- M2_HOME=`cd "$M2_HOME" && pwd`
+trim() {
+ # MWRAPPER-139:
+ # Trims trailing and leading whitespace, carriage returns, tabs, and linefeeds.
+ # Needed for removing poorly interpreted newline sequences when running in more
+ # exotic environments such as mingw bash on Windows.
+ printf "%s" "${1}" | tr -d '[:space:]'
+}
- cd "$saveddir"
- # echo Using m2 at $M2_HOME
-fi
+# parse distributionUrl and optional distributionSha256Sum, requires .mvn/wrapper/maven-wrapper.properties
+while IFS="=" read -r key value; do
+ case "${key-}" in
+ distributionUrl) distributionUrl=$(trim "${value-}") ;;
+ distributionSha256Sum) distributionSha256Sum=$(trim "${value-}") ;;
+ esac
+done <"${0%/*}/.mvn/wrapper/maven-wrapper.properties"
+[ -n "${distributionUrl-}" ] || die "cannot read distributionUrl property in ${0%/*}/.mvn/wrapper/maven-wrapper.properties"
-# For Cygwin, ensure paths are in UNIX format before anything is touched
-if $cygwin ; then
- [ -n "$M2_HOME" ] &&
- M2_HOME=`cygpath --unix "$M2_HOME"`
- [ -n "$JAVA_HOME" ] &&
- JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
- [ -n "$CLASSPATH" ] &&
- CLASSPATH=`cygpath --path --unix "$CLASSPATH"`
-fi
+case "${distributionUrl##*/}" in
+maven-mvnd-*bin.*)
+ MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/
+ case "${PROCESSOR_ARCHITECTURE-}${PROCESSOR_ARCHITEW6432-}:$(uname -a)" in
+ *AMD64:CYGWIN* | *AMD64:MINGW*) distributionPlatform=windows-amd64 ;;
+ :Darwin*x86_64) distributionPlatform=darwin-amd64 ;;
+ :Darwin*arm64) distributionPlatform=darwin-aarch64 ;;
+ :Linux*x86_64*) distributionPlatform=linux-amd64 ;;
+ *)
+ echo "Cannot detect native platform for mvnd on $(uname)-$(uname -m), use pure java version" >&2
+ distributionPlatform=linux-amd64
+ ;;
+ esac
+ distributionUrl="${distributionUrl%-bin.*}-$distributionPlatform.zip"
+ ;;
+maven-mvnd-*) MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ ;;
+*) MVN_CMD="mvn${0##*/mvnw}" _MVNW_REPO_PATTERN=/org/apache/maven/ ;;
+esac
-# For Mingw, ensure paths are in UNIX format before anything is touched
-if $mingw ; then
- [ -n "$M2_HOME" ] &&
- M2_HOME="`(cd "$M2_HOME"; pwd)`"
- [ -n "$JAVA_HOME" ] &&
- JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`"
- # TODO classpath?
-fi
+# apply MVNW_REPOURL and calculate MAVEN_HOME
+# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-,maven-mvnd--}/
+[ -z "${MVNW_REPOURL-}" ] || distributionUrl="$MVNW_REPOURL$_MVNW_REPO_PATTERN${distributionUrl#*"$_MVNW_REPO_PATTERN"}"
+distributionUrlName="${distributionUrl##*/}"
+distributionUrlNameMain="${distributionUrlName%.*}"
+distributionUrlNameMain="${distributionUrlNameMain%-bin}"
+MAVEN_USER_HOME="${MAVEN_USER_HOME:-${HOME}/.m2}"
+MAVEN_HOME="${MAVEN_USER_HOME}/wrapper/dists/${distributionUrlNameMain-}/$(hash_string "$distributionUrl")"
-if [ -z "$JAVA_HOME" ]; then
- javaExecutable="`which javac`"
- if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then
- # readlink(1) is not available as standard on Solaris 10.
- readLink=`which readlink`
- if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then
- if $darwin ; then
- javaHome="`dirname \"$javaExecutable\"`"
- javaExecutable="`cd \"$javaHome\" && pwd -P`/javac"
- else
- javaExecutable="`readlink -f \"$javaExecutable\"`"
- fi
- javaHome="`dirname \"$javaExecutable\"`"
- javaHome=`expr "$javaHome" : '\(.*\)/bin'`
- JAVA_HOME="$javaHome"
- export JAVA_HOME
- fi
- fi
-fi
+exec_maven() {
+ unset MVNW_VERBOSE MVNW_USERNAME MVNW_PASSWORD MVNW_REPOURL || :
+ exec "$MAVEN_HOME/bin/$MVN_CMD" "$@" || die "cannot exec $MAVEN_HOME/bin/$MVN_CMD"
+}
-if [ -z "$JAVACMD" ] ; then
- if [ -n "$JAVA_HOME" ] ; then
- if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
- # IBM's JDK on AIX uses strange locations for the executables
- JAVACMD="$JAVA_HOME/jre/sh/java"
- else
- JAVACMD="$JAVA_HOME/bin/java"
- fi
- else
- JAVACMD="`which java`"
- fi
+if [ -d "$MAVEN_HOME" ]; then
+ verbose "found existing MAVEN_HOME at $MAVEN_HOME"
+ exec_maven "$@"
fi
-if [ ! -x "$JAVACMD" ] ; then
- echo "Error: JAVA_HOME is not defined correctly." >&2
- echo " We cannot execute $JAVACMD" >&2
- exit 1
-fi
+case "${distributionUrl-}" in
+*?-bin.zip | *?maven-mvnd-?*-?*.zip) ;;
+*) die "distributionUrl is not valid, must match *-bin.zip or maven-mvnd-*.zip, but found '${distributionUrl-}'" ;;
+esac
-if [ -z "$JAVA_HOME" ] ; then
- echo "Warning: JAVA_HOME environment variable is not set."
+# prepare tmp dir
+if TMP_DOWNLOAD_DIR="$(mktemp -d)" && [ -d "$TMP_DOWNLOAD_DIR" ]; then
+ clean() { rm -rf -- "$TMP_DOWNLOAD_DIR"; }
+ trap clean HUP INT TERM EXIT
+else
+ die "cannot create temp dir"
fi
-CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher
+mkdir -p -- "${MAVEN_HOME%/*}"
-# traverses directory structure from process work directory to filesystem root
-# first directory with .mvn subdirectory is considered project base directory
-find_maven_basedir() {
+# Download and Install Apache Maven
+verbose "Couldn't find MAVEN_HOME, downloading and installing it ..."
+verbose "Downloading from: $distributionUrl"
+verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName"
- if [ -z "$1" ]
- then
- echo "Path not specified to find_maven_basedir"
- return 1
- fi
+# select .zip or .tar.gz
+if ! command -v unzip >/dev/null; then
+ distributionUrl="${distributionUrl%.zip}.tar.gz"
+ distributionUrlName="${distributionUrl##*/}"
+fi
- basedir="$1"
- wdir="$1"
- while [ "$wdir" != '/' ] ; do
- if [ -d "$wdir"/.mvn ] ; then
- basedir=$wdir
- break
- fi
- # workaround for JBEAP-8937 (on Solaris 10/Sparc)
- if [ -d "${wdir}" ]; then
- wdir=`cd "$wdir/.."; pwd`
- fi
- # end of workaround
- done
- echo "${basedir}"
-}
+# verbose opt
+__MVNW_QUIET_WGET=--quiet __MVNW_QUIET_CURL=--silent __MVNW_QUIET_UNZIP=-q __MVNW_QUIET_TAR=''
+[ "${MVNW_VERBOSE-}" != true ] || __MVNW_QUIET_WGET='' __MVNW_QUIET_CURL='' __MVNW_QUIET_UNZIP='' __MVNW_QUIET_TAR=v
-# concatenates all lines of a file
-concat_lines() {
- if [ -f "$1" ]; then
- echo "$(tr -s '\n' ' ' < "$1")"
- fi
-}
+# normalize http auth
+case "${MVNW_PASSWORD:+has-password}" in
+'') MVNW_USERNAME='' MVNW_PASSWORD='' ;;
+has-password) [ -n "${MVNW_USERNAME-}" ] || MVNW_USERNAME='' MVNW_PASSWORD='' ;;
+esac
-BASE_DIR=`find_maven_basedir "$(pwd)"`
-if [ -z "$BASE_DIR" ]; then
- exit 1;
+if [ -z "${MVNW_USERNAME-}" ] && command -v wget >/dev/null; then
+ verbose "Found wget ... using wget"
+ wget ${__MVNW_QUIET_WGET:+"$__MVNW_QUIET_WGET"} "$distributionUrl" -O "$TMP_DOWNLOAD_DIR/$distributionUrlName" || die "wget: Failed to fetch $distributionUrl"
+elif [ -z "${MVNW_USERNAME-}" ] && command -v curl >/dev/null; then
+ verbose "Found curl ... using curl"
+ curl ${__MVNW_QUIET_CURL:+"$__MVNW_QUIET_CURL"} -f -L -o "$TMP_DOWNLOAD_DIR/$distributionUrlName" "$distributionUrl" || die "curl: Failed to fetch $distributionUrl"
+elif set_java_home; then
+ verbose "Falling back to use Java to download"
+ javaSource="$TMP_DOWNLOAD_DIR/Downloader.java"
+ targetZip="$TMP_DOWNLOAD_DIR/$distributionUrlName"
+ cat >"$javaSource" <<-END
+ public class Downloader extends java.net.Authenticator
+ {
+ protected java.net.PasswordAuthentication getPasswordAuthentication()
+ {
+ return new java.net.PasswordAuthentication( System.getenv( "MVNW_USERNAME" ), System.getenv( "MVNW_PASSWORD" ).toCharArray() );
+ }
+ public static void main( String[] args ) throws Exception
+ {
+ setDefault( new Downloader() );
+ java.nio.file.Files.copy( java.net.URI.create( args[0] ).toURL().openStream(), java.nio.file.Paths.get( args[1] ).toAbsolutePath().normalize() );
+ }
+ }
+ END
+ # For Cygwin/MinGW, switch paths to Windows format before running javac and java
+ verbose " - Compiling Downloader.java ..."
+ "$(native_path "$JAVACCMD")" "$(native_path "$javaSource")" || die "Failed to compile Downloader.java"
+ verbose " - Running Downloader.java ..."
+ "$(native_path "$JAVACMD")" -cp "$(native_path "$TMP_DOWNLOAD_DIR")" Downloader "$distributionUrl" "$(native_path "$targetZip")"
fi
-##########################################################################################
-# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
-# This allows using the maven wrapper in projects that prohibit checking in binary data.
-##########################################################################################
-if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then
- if [ "$MVNW_VERBOSE" = true ]; then
- echo "Found .mvn/wrapper/maven-wrapper.jar"
+# If specified, validate the SHA-256 sum of the Maven distribution zip file
+if [ -n "${distributionSha256Sum-}" ]; then
+ distributionSha256Result=false
+ if [ "$MVN_CMD" = mvnd.sh ]; then
+ echo "Checksum validation is not supported for maven-mvnd." >&2
+ echo "Please disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2
+ exit 1
+ elif command -v sha256sum >/dev/null; then
+ if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | sha256sum -c >/dev/null 2>&1; then
+ distributionSha256Result=true
fi
-else
- if [ "$MVNW_VERBOSE" = true ]; then
- echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..."
- fi
- jarUrl="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.4.2/maven-wrapper-0.4.2.jar"
- while IFS="=" read key value; do
- case "$key" in (wrapperUrl) jarUrl="$value"; break ;;
- esac
- done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties"
- if [ "$MVNW_VERBOSE" = true ]; then
- echo "Downloading from: $jarUrl"
- fi
- wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar"
-
- if command -v wget > /dev/null; then
- if [ "$MVNW_VERBOSE" = true ]; then
- echo "Found wget ... using wget"
- fi
- wget "$jarUrl" -O "$wrapperJarPath"
- elif command -v curl > /dev/null; then
- if [ "$MVNW_VERBOSE" = true ]; then
- echo "Found curl ... using curl"
- fi
- curl -o "$wrapperJarPath" "$jarUrl"
- else
- if [ "$MVNW_VERBOSE" = true ]; then
- echo "Falling back to using Java to download"
- fi
- javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java"
- if [ -e "$javaClass" ]; then
- if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then
- if [ "$MVNW_VERBOSE" = true ]; then
- echo " - Compiling MavenWrapperDownloader.java ..."
- fi
- # Compiling the Java class
- ("$JAVA_HOME/bin/javac" "$javaClass")
- fi
- if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then
- # Running the downloader
- if [ "$MVNW_VERBOSE" = true ]; then
- echo " - Running MavenWrapperDownloader.java ..."
- fi
- ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR")
- fi
- fi
+ elif command -v shasum >/dev/null; then
+ if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | shasum -a 256 -c >/dev/null 2>&1; then
+ distributionSha256Result=true
fi
+ else
+ echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available." >&2
+ echo "Please install either command, or disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2
+ exit 1
+ fi
+ if [ $distributionSha256Result = false ]; then
+ echo "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised." >&2
+ echo "If you updated your Maven version, you need to update the specified distributionSha256Sum property." >&2
+ exit 1
+ fi
fi
-##########################################################################################
-# End of extension
-##########################################################################################
-
-export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"}
-if [ "$MVNW_VERBOSE" = true ]; then
- echo $MAVEN_PROJECTBASEDIR
-fi
-MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS"
-# For Cygwin, switch paths to Windows format before running java
-if $cygwin; then
- [ -n "$M2_HOME" ] &&
- M2_HOME=`cygpath --path --windows "$M2_HOME"`
- [ -n "$JAVA_HOME" ] &&
- JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"`
- [ -n "$CLASSPATH" ] &&
- CLASSPATH=`cygpath --path --windows "$CLASSPATH"`
- [ -n "$MAVEN_PROJECTBASEDIR" ] &&
- MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"`
+# unzip and move
+if command -v unzip >/dev/null; then
+ unzip ${__MVNW_QUIET_UNZIP:+"$__MVNW_QUIET_UNZIP"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -d "$TMP_DOWNLOAD_DIR" || die "failed to unzip"
+else
+ tar xzf${__MVNW_QUIET_TAR:+"$__MVNW_QUIET_TAR"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -C "$TMP_DOWNLOAD_DIR" || die "failed to untar"
fi
+printf %s\\n "$distributionUrl" >"$TMP_DOWNLOAD_DIR/$distributionUrlNameMain/mvnw.url"
+mv -- "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" "$MAVEN_HOME" || [ -d "$MAVEN_HOME" ] || die "fail to move MAVEN_HOME"
-WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
-
-exec "$JAVACMD" \
- $MAVEN_OPTS \
- -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \
- "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \
- ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@"
+clean || :
+exec_maven "$@"
diff --git a/mvnw.cmd b/mvnw.cmd
old mode 100755
new mode 100644
index e5cfb0ae9..249bdf382
--- a/mvnw.cmd
+++ b/mvnw.cmd
@@ -1,3 +1,4 @@
+<# : batch portion
@REM ----------------------------------------------------------------------------
@REM Licensed to the Apache Software Foundation (ASF) under one
@REM or more contributor license agreements. See the NOTICE file
@@ -18,144 +19,131 @@
@REM ----------------------------------------------------------------------------
@REM ----------------------------------------------------------------------------
-@REM Maven2 Start Up Batch script
-@REM
-@REM Required ENV vars:
-@REM JAVA_HOME - location of a JDK home dir
+@REM Apache Maven Wrapper startup batch script, version 3.3.2
@REM
@REM Optional ENV vars
-@REM M2_HOME - location of maven2's installed home dir
-@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands
-@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a key stroke before ending
-@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven
-@REM e.g. to debug Maven itself, use
-@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
-@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files
+@REM MVNW_REPOURL - repo url base for downloading maven distribution
+@REM MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven
+@REM MVNW_VERBOSE - true: enable verbose log; others: silence the output
@REM ----------------------------------------------------------------------------
-@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on'
-@echo off
-@REM set title of command window
-title %0
-@REM enable echoing my setting MAVEN_BATCH_ECHO to 'on'
-@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO%
-
-@REM set %HOME% to equivalent of $HOME
-if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%")
-
-@REM Execute a user defined script before this one
-if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre
-@REM check for pre script, once with legacy .bat ending and once with .cmd ending
-if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat"
-if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd"
-:skipRcPre
-
-@setlocal
-
-set ERROR_CODE=0
-
-@REM To isolate internal variables from possible post scripts, we use another setlocal
-@setlocal
-
-@REM ==== START VALIDATION ====
-if not "%JAVA_HOME%" == "" goto OkJHome
-
-echo.
-echo Error: JAVA_HOME not found in your environment. >&2
-echo Please set the JAVA_HOME variable in your environment to match the >&2
-echo location of your Java installation. >&2
-echo.
-goto error
-
-:OkJHome
-if exist "%JAVA_HOME%\bin\java.exe" goto init
-
-echo.
-echo Error: JAVA_HOME is set to an invalid directory. >&2
-echo JAVA_HOME = "%JAVA_HOME%" >&2
-echo Please set the JAVA_HOME variable in your environment to match the >&2
-echo location of your Java installation. >&2
-echo.
-goto error
-
-@REM ==== END VALIDATION ====
-
-:init
-
-@REM Find the project base dir, i.e. the directory that contains the folder ".mvn".
-@REM Fallback to current working directory if not found.
-
-set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR%
-IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir
-
-set EXEC_DIR=%CD%
-set WDIR=%EXEC_DIR%
-:findBaseDir
-IF EXIST "%WDIR%"\.mvn goto baseDirFound
-cd ..
-IF "%WDIR%"=="%CD%" goto baseDirNotFound
-set WDIR=%CD%
-goto findBaseDir
-
-:baseDirFound
-set MAVEN_PROJECTBASEDIR=%WDIR%
-cd "%EXEC_DIR%"
-goto endDetectBaseDir
-
-:baseDirNotFound
-set MAVEN_PROJECTBASEDIR=%EXEC_DIR%
-cd "%EXEC_DIR%"
-
-:endDetectBaseDir
-
-IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig
-
-@setlocal EnableExtensions EnableDelayedExpansion
-for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a
-@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS%
-
-:endReadAdditionalConfig
-
-SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe"
-set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar"
-set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
-
-set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.4.2/maven-wrapper-0.4.2.jar"
-FOR /F "tokens=1,2 delims==" %%A IN (%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties) DO (
- IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B
-)
-
-@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
-@REM This allows using the maven wrapper in projects that prohibit checking in binary data.
-if exist %WRAPPER_JAR% (
- echo Found %WRAPPER_JAR%
-) else (
- echo Couldn't find %WRAPPER_JAR%, downloading it ...
- echo Downloading from: %DOWNLOAD_URL%
- powershell -Command "(New-Object Net.WebClient).DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"
- echo Finished downloading %WRAPPER_JAR%
+@IF "%__MVNW_ARG0_NAME__%"=="" (SET __MVNW_ARG0_NAME__=%~nx0)
+@SET __MVNW_CMD__=
+@SET __MVNW_ERROR__=
+@SET __MVNW_PSMODULEP_SAVE=%PSModulePath%
+@SET PSModulePath=
+@FOR /F "usebackq tokens=1* delims==" %%A IN (`powershell -noprofile "& {$scriptDir='%~dp0'; $script='%__MVNW_ARG0_NAME__%'; icm -ScriptBlock ([Scriptblock]::Create((Get-Content -Raw '%~f0'))) -NoNewScope}"`) DO @(
+ IF "%%A"=="MVN_CMD" (set __MVNW_CMD__=%%B) ELSE IF "%%B"=="" (echo %%A) ELSE (echo %%A=%%B)
)
-@REM End of extension
-
-%MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %*
-if ERRORLEVEL 1 goto error
-goto end
-
-:error
-set ERROR_CODE=1
-
-:end
-@endlocal & set ERROR_CODE=%ERROR_CODE%
-
-if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost
-@REM check for post script, once with legacy .bat ending and once with .cmd ending
-if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat"
-if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd"
-:skipRcPost
-
-@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on'
-if "%MAVEN_BATCH_PAUSE%" == "on" pause
-
-if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE%
-
-exit /B %ERROR_CODE%
+@SET PSModulePath=%__MVNW_PSMODULEP_SAVE%
+@SET __MVNW_PSMODULEP_SAVE=
+@SET __MVNW_ARG0_NAME__=
+@SET MVNW_USERNAME=
+@SET MVNW_PASSWORD=
+@IF NOT "%__MVNW_CMD__%"=="" (%__MVNW_CMD__% %*)
+@echo Cannot start maven from wrapper >&2 && exit /b 1
+@GOTO :EOF
+: end batch / begin powershell #>
+
+$ErrorActionPreference = "Stop"
+if ($env:MVNW_VERBOSE -eq "true") {
+ $VerbosePreference = "Continue"
+}
+
+# calculate distributionUrl, requires .mvn/wrapper/maven-wrapper.properties
+$distributionUrl = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionUrl
+if (!$distributionUrl) {
+ Write-Error "cannot read distributionUrl property in $scriptDir/.mvn/wrapper/maven-wrapper.properties"
+}
+
+switch -wildcard -casesensitive ( $($distributionUrl -replace '^.*/','') ) {
+ "maven-mvnd-*" {
+ $USE_MVND = $true
+ $distributionUrl = $distributionUrl -replace '-bin\.[^.]*$',"-windows-amd64.zip"
+ $MVN_CMD = "mvnd.cmd"
+ break
+ }
+ default {
+ $USE_MVND = $false
+ $MVN_CMD = $script -replace '^mvnw','mvn'
+ break
+ }
+}
+
+# apply MVNW_REPOURL and calculate MAVEN_HOME
+# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-,maven-mvnd--}/
+if ($env:MVNW_REPOURL) {
+ $MVNW_REPO_PATTERN = if ($USE_MVND) { "/org/apache/maven/" } else { "/maven/mvnd/" }
+ $distributionUrl = "$env:MVNW_REPOURL$MVNW_REPO_PATTERN$($distributionUrl -replace '^.*'+$MVNW_REPO_PATTERN,'')"
+}
+$distributionUrlName = $distributionUrl -replace '^.*/',''
+$distributionUrlNameMain = $distributionUrlName -replace '\.[^.]*$','' -replace '-bin$',''
+$MAVEN_HOME_PARENT = "$HOME/.m2/wrapper/dists/$distributionUrlNameMain"
+if ($env:MAVEN_USER_HOME) {
+ $MAVEN_HOME_PARENT = "$env:MAVEN_USER_HOME/wrapper/dists/$distributionUrlNameMain"
+}
+$MAVEN_HOME_NAME = ([System.Security.Cryptography.MD5]::Create().ComputeHash([byte[]][char[]]$distributionUrl) | ForEach-Object {$_.ToString("x2")}) -join ''
+$MAVEN_HOME = "$MAVEN_HOME_PARENT/$MAVEN_HOME_NAME"
+
+if (Test-Path -Path "$MAVEN_HOME" -PathType Container) {
+ Write-Verbose "found existing MAVEN_HOME at $MAVEN_HOME"
+ Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD"
+ exit $?
+}
+
+if (! $distributionUrlNameMain -or ($distributionUrlName -eq $distributionUrlNameMain)) {
+ Write-Error "distributionUrl is not valid, must end with *-bin.zip, but found $distributionUrl"
+}
+
+# prepare tmp dir
+$TMP_DOWNLOAD_DIR_HOLDER = New-TemporaryFile
+$TMP_DOWNLOAD_DIR = New-Item -Itemtype Directory -Path "$TMP_DOWNLOAD_DIR_HOLDER.dir"
+$TMP_DOWNLOAD_DIR_HOLDER.Delete() | Out-Null
+trap {
+ if ($TMP_DOWNLOAD_DIR.Exists) {
+ try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null }
+ catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" }
+ }
+}
+
+New-Item -Itemtype Directory -Path "$MAVEN_HOME_PARENT" -Force | Out-Null
+
+# Download and Install Apache Maven
+Write-Verbose "Couldn't find MAVEN_HOME, downloading and installing it ..."
+Write-Verbose "Downloading from: $distributionUrl"
+Write-Verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName"
+
+$webclient = New-Object System.Net.WebClient
+if ($env:MVNW_USERNAME -and $env:MVNW_PASSWORD) {
+ $webclient.Credentials = New-Object System.Net.NetworkCredential($env:MVNW_USERNAME, $env:MVNW_PASSWORD)
+}
+[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
+$webclient.DownloadFile($distributionUrl, "$TMP_DOWNLOAD_DIR/$distributionUrlName") | Out-Null
+
+# If specified, validate the SHA-256 sum of the Maven distribution zip file
+$distributionSha256Sum = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionSha256Sum
+if ($distributionSha256Sum) {
+ if ($USE_MVND) {
+ Write-Error "Checksum validation is not supported for maven-mvnd. `nPlease disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties."
+ }
+ Import-Module $PSHOME\Modules\Microsoft.PowerShell.Utility -Function Get-FileHash
+ if ((Get-FileHash "$TMP_DOWNLOAD_DIR/$distributionUrlName" -Algorithm SHA256).Hash.ToLower() -ne $distributionSha256Sum) {
+ Write-Error "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised. If you updated your Maven version, you need to update the specified distributionSha256Sum property."
+ }
+}
+
+# unzip and move
+Expand-Archive "$TMP_DOWNLOAD_DIR/$distributionUrlName" -DestinationPath "$TMP_DOWNLOAD_DIR" | Out-Null
+Rename-Item -Path "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" -NewName $MAVEN_HOME_NAME | Out-Null
+try {
+ Move-Item -Path "$TMP_DOWNLOAD_DIR/$MAVEN_HOME_NAME" -Destination $MAVEN_HOME_PARENT | Out-Null
+} catch {
+ if (! (Test-Path -Path "$MAVEN_HOME" -PathType Container)) {
+ Write-Error "fail to move MAVEN_HOME"
+ }
+} finally {
+ try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null }
+ catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" }
+}
+
+Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD"
diff --git a/pom.xml b/pom.xml
index 998b99053..9f40380f3 100755
--- a/pom.xml
+++ b/pom.xml
@@ -5,7 +5,7 @@
org.jenkins-ci.pluginsplugin
- 4.15
+ 5.28
@@ -36,7 +36,7 @@
- scm:git:git://github.com/${gitHubRepo}.git
+ scm:git:https://github.com/${gitHubRepo}.gitscm:git:git@github.com:${gitHubRepo}.githttps://github.com/${gitHubRepo}${scmTag}
@@ -47,21 +47,15 @@
- 1.33.2
+ 1.45.1-SNAPSHOT
- jenkinsci/github-plugin
- 2.222.4
+ jenkinsci/${project.artifactId}-plugin
+
+ 2.504
+ ${jenkins.baseline}.1false
- true
- 3.0.4
- 2.2
- 1
- 8
- 1.14.2
+ falsev@{project.version}
- Low
- Max
- false
@@ -70,7 +64,7 @@
https://repo.jenkins-ci.org/public/
-
+
repo.jenkins-ci.org
@@ -80,14 +74,16 @@
- org.apache.commons
- commons-lang3
- 3.11
+ io.jenkins.plugins
+ commons-lang3-api
+
+
+ io.jenkins.plugins
+ okhttp-apiorg.jenkins-ci.pluginsgithub-api
- 1.114.2
@@ -123,14 +119,11 @@
org.jenkins-ci.modulesinstance-identity
- 2.2
- provided
- javax.servlet
- javax.servlet-api
- provided
+ io.jenkins.plugins
+ caffeine-api
@@ -143,36 +136,13 @@
- org.hamcrest
- hamcrest
- ${hamcrest.version}
- test
-
-
-
- org.hamcrest
- hamcrest-core
- ${hamcrest.version}
- test
-
-
-
- org.hamcrest
- hamcrest-library
- ${hamcrest.version}
- test
-
-
-
- junit
- junit
+ org.mockito
+ mockito-coretest
-
org.mockito
- mockito-core
- 1.10.19
+ mockito-junit-jupitertest
@@ -199,13 +169,6 @@
org.jenkins-ci.plugins.workflowworkflow-cpstest
-
-
-
- org.jenkins-ci.ui
- jquery-detached
-
- org.jenkins-ci.plugins.workflow
@@ -219,64 +182,18 @@
test
-
- com.tngtech.java
- junit-dataprovider
- 1.10.0
- test
-
-
- com.github.tomakehurst
- wiremock
- 1.57
+ org.wiremock
+ wiremock-standalone
+ 3.12.1test
- standalone
-
-
- org.eclipse.jetty
- jetty
-
-
- com.google.guava
- guava
-
-
- org.apache.httpcomponents
- httpclient
-
-
- xmlunit
- xmlunit
-
-
- com.jayway.jsonpath
- json-path
-
-
- net.sf.jopt-simple
- jopt-simple
-
-
- com.fasterxml.jackson.core
- jackson-annotations
-
-
- com.fasterxml.jackson.core
- jackson-core
-
-
- com.fasterxml.jackson.core
- jackson-databind
-
- io.rest-assuredrest-assured
- 4.3.3
+ 5.3.2test
@@ -286,16 +203,11 @@
io.jenkins.tools.bom
- bom-2.222.x
- 20
+ bom-${jenkins.baseline}.x
+ 4710.v016f0a_07e34dimportpom
-
- org.jenkins-ci
- annotation-indexer
- 1.12
-
@@ -319,7 +231,7 @@
maven-checkstyle-plugin
- 3.1.1
+ 3.6.0checkstyle
@@ -330,7 +242,6 @@
- UTF-8truetruefalse
diff --git a/src/main/java/com/cloudbees/jenkins/Cleaner.java b/src/main/java/com/cloudbees/jenkins/Cleaner.java
index 182ece08e..027083192 100644
--- a/src/main/java/com/cloudbees/jenkins/Cleaner.java
+++ b/src/main/java/com/cloudbees/jenkins/Cleaner.java
@@ -34,7 +34,7 @@ public class Cleaner extends PeriodicWork {
* This queue is thread-safe, so any thread can write or
* fetch names to this queue without additional sync
*/
- private final Queue cleanQueue = new ConcurrentLinkedQueue();
+ private final Queue cleanQueue = new ConcurrentLinkedQueue<>();
/**
* Called when a {@link GitHubPushTrigger} is about to be removed.
@@ -61,8 +61,7 @@ protected void doRun() throws Exception {
URL url = GitHubPlugin.configuration().getHookUrl();
- List items = Jenkins.getInstance().getAllItems(Item.class);
- List aliveRepos = from(items)
+ List aliveRepos = from(Jenkins.get().allItems(Item.class))
.filter(isAlive()) // live repos
.transformAndConcat(associatedNames()).toList();
diff --git a/src/main/java/com/cloudbees/jenkins/Credential.java b/src/main/java/com/cloudbees/jenkins/Credential.java
index d5b801a7b..99e766119 100644
--- a/src/main/java/com/cloudbees/jenkins/Credential.java
+++ b/src/main/java/com/cloudbees/jenkins/Credential.java
@@ -7,7 +7,7 @@
import org.kohsuke.github.GitHub;
import org.kohsuke.stapler.DataBoundConstructor;
-import javax.annotation.CheckForNull;
+import edu.umd.cs.findbugs.annotations.CheckForNull;
import java.io.IOException;
import static org.jenkinsci.plugins.github.util.FluentIterableWrapper.from;
diff --git a/src/main/java/com/cloudbees/jenkins/GitHubCommitNotifier.java b/src/main/java/com/cloudbees/jenkins/GitHubCommitNotifier.java
index a0e662024..9d7663e51 100644
--- a/src/main/java/com/cloudbees/jenkins/GitHubCommitNotifier.java
+++ b/src/main/java/com/cloudbees/jenkins/GitHubCommitNotifier.java
@@ -32,12 +32,10 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import javax.annotation.Nonnull;
import java.io.IOException;
import java.util.Collections;
import static com.cloudbees.jenkins.Messages.GitHubCommitNotifier_DisplayName;
-import static com.google.common.base.Objects.firstNonNull;
import static hudson.model.Result.FAILURE;
import static hudson.model.Result.SUCCESS;
import static hudson.model.Result.UNSTABLE;
@@ -93,17 +91,17 @@ public void setStatusMessage(ExpandableMessage statusMessage) {
/**
* @since 1.10
*/
- @Nonnull
+ @NonNull
public String getResultOnFailure() {
return resultOnFailure != null ? resultOnFailure : getDefaultResultOnFailure().toString();
}
- @Nonnull
+ @NonNull
public static Result getDefaultResultOnFailure() {
return FAILURE;
}
- @Nonnull
+ @NonNull
/*package*/ Result getEffectiveResultOnFailure() {
return Result.fromString(trimToEmpty(resultOnFailure));
}
@@ -125,7 +123,7 @@ public void perform(@NonNull Run, ?> build,
setter.setContextSource(new DefaultCommitContextSource());
- String content = firstNonNull(statusMessage, DEFAULT_MESSAGE).getContent();
+ String content = (statusMessage != null ? statusMessage : DEFAULT_MESSAGE).getContent();
if (isNotBlank(content)) {
setter.setStatusResultSource(new ConditionalStatusResultSource(
diff --git a/src/main/java/com/cloudbees/jenkins/GitHubPushTrigger.java b/src/main/java/com/cloudbees/jenkins/GitHubPushTrigger.java
index 62259c733..4cae5f049 100644
--- a/src/main/java/com/cloudbees/jenkins/GitHubPushTrigger.java
+++ b/src/main/java/com/cloudbees/jenkins/GitHubPushTrigger.java
@@ -2,6 +2,7 @@
import com.google.common.base.Charsets;
import com.google.common.base.Preconditions;
+import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import hudson.Extension;
import hudson.Util;
import hudson.XmlFile;
@@ -38,8 +39,8 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import javax.annotation.Nonnull;
-import javax.inject.Inject;
+import edu.umd.cs.findbugs.annotations.NonNull;
+import jakarta.inject.Inject;
import java.io.File;
import java.io.IOException;
import java.io.PrintStream;
@@ -85,7 +86,7 @@ public void onPost() {
*/
public void onPost(String triggeredByUser) {
onPost(GitHubTriggerEvent.create()
- .withOrigin(SCMEvent.originOf(Stapler.getCurrentRequest()))
+ .withOrigin(SCMEvent.originOf(Stapler.getCurrentRequest2()))
.withTriggeredByUser(triggeredByUser)
.build()
);
@@ -182,7 +183,7 @@ public File getLogFile() {
/**
* Returns the file that records the last/current polling activity.
*/
- private File getLogFileForJob(@Nonnull Job job) throws IOException {
+ private File getLogFileForJob(@NonNull Job job) throws IOException {
return new File(job.getRootDir(), "github-polling.log");
}
@@ -262,7 +263,7 @@ public String getUrlName() {
}
public String getLog() throws IOException {
- return Util.loadFile(getLogFileForJob(job));
+ return Util.loadFile(getLogFileForJob(Objects.requireNonNull(job)));
}
/**
@@ -270,8 +271,16 @@ public String getLog() throws IOException {
*
* @since 1.350
*/
+ @SuppressFBWarnings(
+ value = "RV_RETURN_VALUE_IGNORED",
+ justification =
+ "method signature does not permit plumbing through the return value")
public void writeLogTo(XMLOutput out) throws IOException {
- new AnnotatedLargeText(getLogFileForJob(job), Charsets.UTF_8, true, this)
+ new AnnotatedLargeText(
+ getLogFileForJob(Objects.requireNonNull(job)),
+ Charsets.UTF_8,
+ true,
+ this)
.writeHtmlTo(0, out.asWriter());
}
}
diff --git a/src/main/java/com/cloudbees/jenkins/GitHubRepositoryName.java b/src/main/java/com/cloudbees/jenkins/GitHubRepositoryName.java
index 242fc8851..5cdb857b3 100644
--- a/src/main/java/com/cloudbees/jenkins/GitHubRepositoryName.java
+++ b/src/main/java/com/cloudbees/jenkins/GitHubRepositoryName.java
@@ -17,8 +17,8 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import javax.annotation.CheckForNull;
-import javax.annotation.Nonnull;
+import edu.umd.cs.findbugs.annotations.CheckForNull;
+import edu.umd.cs.findbugs.annotations.NonNull;
import java.io.IOException;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -46,22 +46,22 @@ public class GitHubRepositoryName {
* from URLs that include a '.git' suffix, removing the suffix from the
* repository name.
*/
- Pattern.compile("git@(.+):([^/]+)/([^/]+)\\.git(?:/)?"),
+ Pattern.compile(".+@(.+):([^/]+)/([^/]+)\\.git(?:/)?"),
Pattern.compile("https?://[^/]+@([^/]+)/([^/]+)/([^/]+)\\.git(?:/)?"),
Pattern.compile("https?://([^/]+)/([^/]+)/([^/]+)\\.git(?:/)?"),
Pattern.compile("git://([^/]+)/([^/]+)/([^/]+)\\.git(?:/)?"),
- Pattern.compile("(?:git\\+)?ssh://(?:git@)?([^/]+)/([^/]+)/([^/]+)\\.git(?:/)?"),
+ Pattern.compile("(?:git\\+)?ssh://(?:.+@)?([^/]+)/([^/]+)/([^/]+)\\.git(?:/)?"),
/**
* The second set of patterns extract the host, owner and repository names
* from all other URLs. Note that these patterns must be processed *after*
* the first set, to avoid any '.git' suffix that may be present being included
* in the repository name.
*/
- Pattern.compile("git@(.+):([^/]+)/([^/]+)/?"),
+ Pattern.compile(".+@(.+):([^/]+)/([^/]+)/?"),
Pattern.compile("https?://[^/]+@([^/]+)/([^/]+)/([^/]+)/?"),
Pattern.compile("https?://([^/]+)/([^/]+)/([^/]+)/?"),
Pattern.compile("git://([^/]+)/([^/]+)/([^/]+)/?"),
- Pattern.compile("(?:git\\+)?ssh://(?:git@)?([^/]+)/([^/]+)/([^/]+)/?"),
+ Pattern.compile("(?:git\\+)?ssh://(?:.+@)?([^/]+)/([^/]+)/([^/]+)/?"),
};
/**
@@ -83,7 +83,7 @@ public static GitHubRepositoryName create(String url) {
return ret;
}
}
- LOGGER.warn("Could not match URL {}", url);
+ LOGGER.debug("Could not match URL {}", url);
return null;
}
@@ -222,7 +222,7 @@ public String toString() {
private static Function toGHRepository(final GitHubRepositoryName repoName) {
return new NullSafeFunction() {
@Override
- protected GHRepository applyNullSafe(@Nonnull GitHub gitHub) {
+ protected GHRepository applyNullSafe(@NonNull GitHub gitHub) {
try {
return gitHub.getRepository(format("%s/%s", repoName.getUserName(), repoName.getRepositoryName()));
} catch (IOException e) {
diff --git a/src/main/java/com/cloudbees/jenkins/GitHubSetCommitStatusBuilder.java b/src/main/java/com/cloudbees/jenkins/GitHubSetCommitStatusBuilder.java
index 862d41955..f30ff9136 100644
--- a/src/main/java/com/cloudbees/jenkins/GitHubSetCommitStatusBuilder.java
+++ b/src/main/java/com/cloudbees/jenkins/GitHubSetCommitStatusBuilder.java
@@ -27,7 +27,6 @@
import java.io.IOException;
import java.util.Collections;
-import static com.google.common.base.Objects.firstNonNull;
import static org.apache.commons.lang3.StringUtils.defaultIfEmpty;
import static org.jenkinsci.plugins.github.status.sources.misc.AnyBuildResult.onAnyResult;
@@ -89,7 +88,7 @@ public void perform(@NonNull Run, ?> build,
Collections.singletonList(
onAnyResult(
GHCommitState.PENDING,
- defaultIfEmpty(firstNonNull(statusMessage, DEFAULT_MESSAGE).getContent(),
+ defaultIfEmpty((statusMessage != null ? statusMessage : DEFAULT_MESSAGE).getContent(),
Messages.CommitNotifier_Pending(build.getDisplayName()))
)
)));
diff --git a/src/main/java/com/cloudbees/jenkins/GitHubTriggerEvent.java b/src/main/java/com/cloudbees/jenkins/GitHubTriggerEvent.java
index 364631c9e..fdae66124 100644
--- a/src/main/java/com/cloudbees/jenkins/GitHubTriggerEvent.java
+++ b/src/main/java/com/cloudbees/jenkins/GitHubTriggerEvent.java
@@ -1,5 +1,6 @@
package com.cloudbees.jenkins;
+import jakarta.servlet.http.HttpServletRequest;
import jenkins.scm.api.SCMEvent;
/**
@@ -14,7 +15,7 @@ public class GitHubTriggerEvent {
*/
private final long timestamp;
/**
- * The origin of the event (see {@link SCMEvent#originOf(javax.servlet.http.HttpServletRequest)})
+ * The origin of the event (see {@link SCMEvent#originOf(HttpServletRequest)})
*/
private final String origin;
/**
diff --git a/src/main/java/com/cloudbees/jenkins/GitHubWebHook.java b/src/main/java/com/cloudbees/jenkins/GitHubWebHook.java
index 3033771a2..887a1a366 100644
--- a/src/main/java/com/cloudbees/jenkins/GitHubWebHook.java
+++ b/src/main/java/com/cloudbees/jenkins/GitHubWebHook.java
@@ -25,7 +25,7 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import javax.annotation.Nonnull;
+import edu.umd.cs.findbugs.annotations.NonNull;
import java.net.URL;
import java.util.List;
@@ -52,6 +52,12 @@ public class GitHubWebHook implements UnprotectedRootAction {
// headers used for testing the endpoint configuration
public static final String URL_VALIDATION_HEADER = "X-Jenkins-Validation";
public static final String X_INSTANCE_IDENTITY = "X-Instance-Identity";
+ /**
+ * X-GitHub-Delivery: A globally unique identifier (GUID) to identify the event.
+ * @see Delivery
+ * headers
+ */
+ public static final String X_GITHUB_DELIVERY = "X-GitHub-Delivery";
private final transient SequentialExecutionQueue queue = new SequentialExecutionQueue(threadPoolForRemoting);
@@ -116,9 +122,11 @@ public List reRegisterAllHooks() {
*/
@SuppressWarnings("unused")
@RequirePostWithGHHookPayload
- public void doIndex(@Nonnull @GHEventHeader GHEvent event, @Nonnull @GHEventPayload String payload) {
+ public void doIndex(@NonNull @GHEventHeader GHEvent event, @NonNull @GHEventPayload String payload) {
+ var currentRequest = Stapler.getCurrentRequest2();
+ String eventGuid = currentRequest.getHeader(X_GITHUB_DELIVERY);
GHSubscriberEvent subscriberEvent =
- new GHSubscriberEvent(SCMEvent.originOf(Stapler.getCurrentRequest()), event, payload);
+ new GHSubscriberEvent(eventGuid, SCMEvent.originOf(currentRequest), event, payload);
from(GHEventsSubscriber.all())
.filter(isInterestedIn(event))
.transform(processEvent(subscriberEvent)).toList();
@@ -149,7 +157,7 @@ public static GitHubWebHook get() {
return Jenkins.getInstance().getExtensionList(RootAction.class).get(GitHubWebHook.class);
}
- @Nonnull
+ @NonNull
public static Jenkins getJenkinsInstance() throws IllegalStateException {
Jenkins instance = Jenkins.getInstance();
Validate.validState(instance != null, "Jenkins has not been started, or was already shut down");
diff --git a/src/main/java/com/cloudbees/jenkins/GitHubWebHookCrumbExclusion.java b/src/main/java/com/cloudbees/jenkins/GitHubWebHookCrumbExclusion.java
index e342e1261..39191f388 100644
--- a/src/main/java/com/cloudbees/jenkins/GitHubWebHookCrumbExclusion.java
+++ b/src/main/java/com/cloudbees/jenkins/GitHubWebHookCrumbExclusion.java
@@ -3,10 +3,10 @@
import hudson.Extension;
import hudson.security.csrf.CrumbExclusion;
-import javax.servlet.FilterChain;
-import javax.servlet.ServletException;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
+import jakarta.servlet.FilterChain;
+import jakarta.servlet.ServletException;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
import static org.apache.commons.lang3.StringUtils.isEmpty;
@@ -21,7 +21,7 @@ public boolean process(HttpServletRequest req, HttpServletResponse resp, FilterC
if (isEmpty(pathInfo)) {
return false;
}
- // Github will not follow redirects https://github.com/isaacs/github/issues/574
+ // GitHub will not follow redirects https://github.com/isaacs/github/issues/574
pathInfo = pathInfo.endsWith("/") ? pathInfo : pathInfo + '/';
if (!pathInfo.equals(getExclusionPath())) {
return false;
diff --git a/src/main/java/com/coravy/hudson/plugins/github/GithubLinkAction.java b/src/main/java/com/coravy/hudson/plugins/github/GithubLinkAction.java
index b92c5f4b4..662b714cb 100644
--- a/src/main/java/com/coravy/hudson/plugins/github/GithubLinkAction.java
+++ b/src/main/java/com/coravy/hudson/plugins/github/GithubLinkAction.java
@@ -10,9 +10,9 @@
import java.util.Collections;
/**
- * Add the Github Logo/Icon to the sidebar.
+ * Add the GitHub Logo/Icon to the sidebar.
*
- * @author Stefan Saasen
+ * @author Stefan Saasen
*/
public final class GithubLinkAction implements Action {
@@ -29,7 +29,7 @@ public String getDisplayName() {
@Override
public String getIconFileName() {
- return "/plugin/github/logov3.png";
+ return "symbol-logo-github plugin-github";
}
@Override
diff --git a/src/main/java/com/coravy/hudson/plugins/github/GithubLinkAnnotator.java b/src/main/java/com/coravy/hudson/plugins/github/GithubLinkAnnotator.java
index 388901f02..d96acee40 100644
--- a/src/main/java/com/coravy/hudson/plugins/github/GithubLinkAnnotator.java
+++ b/src/main/java/com/coravy/hudson/plugins/github/GithubLinkAnnotator.java
@@ -7,9 +7,20 @@
import hudson.plugins.git.GitChangeSet;
import hudson.scm.ChangeLogAnnotator;
import hudson.scm.ChangeLogSet.Entry;
+import org.apache.commons.lang3.StringUtils;
+import edu.umd.cs.findbugs.annotations.CheckForNull;
+import edu.umd.cs.findbugs.annotations.CheckReturnValue;
+import edu.umd.cs.findbugs.annotations.NonNull;
+
+import static hudson.Functions.htmlAttributeEscape;
import static java.lang.String.format;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
import java.util.regex.Pattern;
/**
@@ -17,15 +28,22 @@
*
* It's based on the TracLinkAnnotator.
*
- *
- * @author Stefan Saasen
- * @todo Change the annotator to use GithubUrl instead of the String url.
+ * TODO Change the annotator to use GithubUrl instead of the String url.
* Knowledge about the github url structure should be encapsulated in
* GithubUrl.
+ *
+ * @author Stefan Saasen
*/
@Extension
public class GithubLinkAnnotator extends ChangeLogAnnotator {
+ private static final Set ALLOWED_URI_SCHEMES = new HashSet();
+
+ static {
+ ALLOWED_URI_SCHEMES.addAll(
+ Arrays.asList("http", "https"));
+ }
+
@Override
public void annotate(Run, ?> build, Entry change, MarkupText text) {
final GithubProjectProperty p = build.getParent().getProperty(
@@ -38,15 +56,18 @@ public void annotate(Run, ?> build, Entry change, MarkupText text) {
void annotate(final GithubUrl url, final MarkupText text, final Entry change) {
final String base = url.baseUrl();
+ boolean isValid = verifyUrl(base);
+ if (!isValid) {
+ throw new IllegalArgumentException("The provided GitHub URL is not valid");
+ }
for (LinkMarkup markup : MARKUPS) {
markup.process(text, base);
}
-
if (change instanceof GitChangeSet) {
GitChangeSet cs = (GitChangeSet) change;
final String id = cs.getId();
text.wrapBy("", format(" (commit: %s)",
- url.commitId(id),
+ htmlAttributeEscape(url.commitId(id)),
id.substring(0, Math.min(id.length(), 7))));
}
}
@@ -66,7 +87,7 @@ private static final class LinkMarkup {
void process(MarkupText text, String url) {
for (SubText st : text.findTokens(pattern)) {
- st.surroundWith("", "");
+ st.surroundWith("", "");
}
}
@@ -77,5 +98,35 @@ void process(MarkupText text, String url) {
private static final LinkMarkup[] MARKUPS = new LinkMarkup[]{new LinkMarkup(
"(?:C|c)lose(?:s?)\\s(?
+ * @author Stefan Saasen
*/
public final class GithubProjectProperty extends JobProperty> {
@@ -88,7 +89,7 @@ public void setDisplayName(String displayName) {
* @return display name or full job name if field is not defined
* @since 1.14.1
*/
- public static String displayNameFor(@Nonnull Job, ?> job) {
+ public static String displayNameFor(@NonNull Job, ?> job) {
GithubProjectProperty ghProp = job.getProperty(GithubProjectProperty.class);
if (ghProp != null && isNotBlank(ghProp.getDisplayName())) {
return ghProp.getDisplayName();
@@ -98,6 +99,7 @@ public static String displayNameFor(@Nonnull Job, ?> job) {
}
@Extension
+ @Symbol("githubProjectProperty")
public static final class DescriptorImpl extends JobPropertyDescriptor {
/**
* Used to hide property configuration under checkbox,
@@ -114,7 +116,9 @@ public String getDisplayName() {
}
@Override
- public JobProperty> newInstance(@Nonnull StaplerRequest req, JSONObject formData) throws FormException {
+ public JobProperty> newInstance(@NonNull StaplerRequest2 req,
+ JSONObject formData) throws Descriptor.FormException {
+
GithubProjectProperty tpp = req.bindJSON(
GithubProjectProperty.class,
formData.getJSONObject(GITHUB_PROJECT_BLOCK_NAME)
@@ -135,5 +139,5 @@ public JobProperty> newInstance(@Nonnull StaplerRequest req, JSONObject formDa
}
- private static final Logger LOGGER = Logger.getLogger(GitHubPushTrigger.class.getName());
+ private static final Logger LOGGER = Logger.getLogger(GithubProjectProperty.class.getName());
}
diff --git a/src/main/java/com/coravy/hudson/plugins/github/GithubUrl.java b/src/main/java/com/coravy/hudson/plugins/github/GithubUrl.java
index b331adcb3..50e9ad9ed 100644
--- a/src/main/java/com/coravy/hudson/plugins/github/GithubUrl.java
+++ b/src/main/java/com/coravy/hudson/plugins/github/GithubUrl.java
@@ -1,9 +1,9 @@
package com.coravy.hudson.plugins.github;
-import org.apache.commons.lang.StringUtils;
+import org.apache.commons.lang3.StringUtils;
/**
- * @author Stefan Saasen
+ * @author Stefan Saasen
*/
public final class GithubUrl {
diff --git a/src/main/java/org/jenkinsci/plugins/github/GitHubPlugin.java b/src/main/java/org/jenkinsci/plugins/github/GitHubPlugin.java
index 383f82203..4a45fbd2a 100644
--- a/src/main/java/org/jenkinsci/plugins/github/GitHubPlugin.java
+++ b/src/main/java/org/jenkinsci/plugins/github/GitHubPlugin.java
@@ -6,7 +6,9 @@
import org.jenkinsci.plugins.github.config.GitHubPluginConfig;
import org.jenkinsci.plugins.github.migration.Migrator;
-import javax.annotation.Nonnull;
+import edu.umd.cs.findbugs.annotations.NonNull;
+import org.kohsuke.accmod.Restricted;
+import org.kohsuke.accmod.restrictions.DoNotUse;
import static org.apache.commons.lang3.ObjectUtils.defaultIfNull;
@@ -23,6 +25,8 @@ public class GitHubPlugin extends Plugin {
* Launched before plugin starts
* Adds alias for {@link GitHubPlugin} to simplify resulting xml.
*/
+ @Initializer(before = InitMilestone.SYSTEM_CONFIG_LOADED)
+ @Restricted(DoNotUse.class)
public static void addXStreamAliases() {
Migrator.enableCompatibilityAliases();
Migrator.enableAliases();
@@ -39,17 +43,12 @@ public static void runMigrator() throws Exception {
new Migrator().migrate();
}
- @Override
- public void start() throws Exception {
- addXStreamAliases();
- }
-
/**
* Shortcut method for getting instance of {@link GitHubPluginConfig}.
*
* @return configuration of plugin
*/
- @Nonnull
+ @NonNull
public static GitHubPluginConfig configuration() {
return defaultIfNull(
GitHubPluginConfig.all().get(GitHubPluginConfig.class),
diff --git a/src/main/java/org/jenkinsci/plugins/github/admin/GHRepoName.java b/src/main/java/org/jenkinsci/plugins/github/admin/GHRepoName.java
index a96f2d189..52eeb6fef 100644
--- a/src/main/java/org/jenkinsci/plugins/github/admin/GHRepoName.java
+++ b/src/main/java/org/jenkinsci/plugins/github/admin/GHRepoName.java
@@ -3,7 +3,7 @@
import com.cloudbees.jenkins.GitHubRepositoryName;
import org.kohsuke.stapler.AnnotationHandler;
import org.kohsuke.stapler.InjectedParameter;
-import org.kohsuke.stapler.StaplerRequest;
+import org.kohsuke.stapler.StaplerRequest2;
import org.slf4j.Logger;
import java.lang.annotation.Documented;
@@ -37,8 +37,8 @@ class PayloadHandler extends AnnotationHandler {
* @return {@link GitHubRepositoryName} extracted from request or null on any problem
*/
@Override
- public GitHubRepositoryName parse(StaplerRequest req, GHRepoName a, Class type, String param) {
- String repo = notNull(req, "Why StaplerRequest is null?").getParameter(param);
+ public GitHubRepositoryName parse(StaplerRequest2 req, GHRepoName a, Class type, String param) {
+ String repo = notNull(req, "Why StaplerRequest2 is null?").getParameter(param);
LOGGER.trace("Repo url in method {}", repo);
return GitHubRepositoryName.create(repo);
}
diff --git a/src/main/java/org/jenkinsci/plugins/github/admin/GitHubDuplicateEventsMonitor.java b/src/main/java/org/jenkinsci/plugins/github/admin/GitHubDuplicateEventsMonitor.java
new file mode 100644
index 000000000..794f3db04
--- /dev/null
+++ b/src/main/java/org/jenkinsci/plugins/github/admin/GitHubDuplicateEventsMonitor.java
@@ -0,0 +1,216 @@
+package org.jenkinsci.plugins.github.admin;
+
+import java.time.Duration;
+import java.time.Instant;
+import java.util.Set;
+import java.util.logging.Logger;
+import java.util.stream.Collectors;
+
+import com.github.benmanes.caffeine.cache.Cache;
+import com.github.benmanes.caffeine.cache.Caffeine;
+import com.github.benmanes.caffeine.cache.Ticker;
+import com.google.common.annotations.VisibleForTesting;
+
+import hudson.Extension;
+import hudson.ExtensionList;
+import hudson.model.AdministrativeMonitor;
+import hudson.model.Item;
+import jenkins.model.Jenkins;
+import org.jenkinsci.plugins.github.Messages;
+import org.jenkinsci.plugins.github.extension.GHEventsSubscriber;
+import org.jenkinsci.plugins.github.extension.GHSubscriberEvent;
+import org.kohsuke.accmod.Restricted;
+import org.kohsuke.accmod.restrictions.NoExternalUse;
+import org.kohsuke.github.GHEvent;
+import org.kohsuke.stapler.HttpResponse;
+import org.kohsuke.stapler.WebMethod;
+import org.kohsuke.stapler.json.JsonHttpResponse;
+import org.kohsuke.stapler.verb.GET;
+import edu.umd.cs.findbugs.annotations.Nullable;
+import net.sf.json.JSONObject;
+
+@SuppressWarnings("unused")
+@Extension
+public class GitHubDuplicateEventsMonitor extends AdministrativeMonitor {
+
+ @VisibleForTesting
+ static final String LAST_DUPLICATE_CLICK_HERE_ANCHOR_ID = GitHubDuplicateEventsMonitor.class.getName()
+ + ".last-duplicate";
+
+ @Override
+ public String getDisplayName() {
+ return Messages.duplicate_events_administrative_monitor_displayname();
+ }
+
+ public String getDescription() {
+ return Messages.duplicate_events_administrative_monitor_description();
+ }
+
+ public String getBlurb() {
+ return Messages.duplicate_events_administrative_monitor_blurb(
+ LAST_DUPLICATE_CLICK_HERE_ANCHOR_ID, this.getLastDuplicateUrl());
+ }
+
+ @VisibleForTesting
+ String getLastDuplicateUrl() {
+ return this.getUrl() + "/" + "last-duplicate.json";
+ }
+
+ @Override
+ public boolean isActivated() {
+ return ExtensionList.lookupSingleton(DuplicateEventsSubscriber.class).isDuplicateEventSeen();
+ }
+
+ @Override
+ public boolean hasRequiredPermission() {
+ return Jenkins.get().hasPermission(Jenkins.SYSTEM_READ);
+ }
+
+ @Override
+ public void checkRequiredPermission() {
+ Jenkins.get().checkPermission(Jenkins.SYSTEM_READ);
+ }
+
+ @GET
+ @WebMethod(name = "last-duplicate.json")
+ public HttpResponse doGetLastDuplicatePayload() {
+ Jenkins.get().checkPermission(Jenkins.SYSTEM_READ);
+ JSONObject data;
+ var lastDuplicate = ExtensionList.lookupSingleton(DuplicateEventsSubscriber.class).getLastDuplicate();
+ if (lastDuplicate != null) {
+ data = JSONObject.fromObject(lastDuplicate.ghSubscriberEvent().getPayload());
+ } else {
+ data = getLastDuplicateNoEventPayload();
+ }
+ return new JsonHttpResponse(data, 200);
+ }
+
+ @VisibleForTesting
+ static JSONObject getLastDuplicateNoEventPayload() {
+ return new JSONObject().accumulate("payload", "No duplicate events seen yet");
+ }
+
+ /**
+ * Tracks duplicate {@link GHEvent} triggering actions in Jenkins.
+ * Events are tracked for 10 minutes, with the last detected duplicate reference retained for up to 24 hours
+ * (see {@link #isDuplicateEventSeen}).
+ *
+ * Duplicates are stored in-memory only, so a controller restart clears all entries as if none existed.
+ * Persistent storage is omitted for simplicity, since webhook misconfigurations would likely cause new duplicates.
+ */
+ @Extension
+ public static final class DuplicateEventsSubscriber extends GHEventsSubscriber {
+
+ private static final Logger LOGGER = Logger.getLogger(DuplicateEventsSubscriber.class.getName());
+
+ private Ticker ticker = Ticker.systemTicker();
+ /**
+ * Caches GitHub event GUIDs for 10 minutes to track recent events to detect duplicates.
+ *
+ * Only the keys (event GUIDs) are relevant, as Caffeine automatically handles expiration based
+ * on insertion time; the value is irrelevant, we put {@link #DUMMY}, as Caffeine doesn't provide any
+ * Set structures.
+ *
+ * Maximum cache size is set to 24k so it doesn't grow unbound (approx. 1MB). Each key takes 36 bytes, and
+ * timestamp (assuming caffeine internally keeps long) takes 8 bytes; total of 44 bytes
+ * per entry. So the maximum memory consumed by this cache is 24k * 44 = 1056k = 1.056 MB.
+ */
+ private final Cache eventTracker = Caffeine.newBuilder()
+ .maximumSize(24_000L)
+ .expireAfterWrite(Duration.ofMinutes(10))
+ .ticker(() -> ticker.read())
+ .build();
+ private static final Object DUMMY = new Object();
+
+ private volatile TrackedDuplicateEvent lastDuplicate;
+ public record TrackedDuplicateEvent(
+ String eventGuid, Instant lastUpdated, GHSubscriberEvent ghSubscriberEvent) { }
+ private static final Duration TWENTY_FOUR_HOURS = Duration.ofHours(24);
+
+ @VisibleForTesting
+ @Restricted(NoExternalUse.class)
+ void setTicker(Ticker testTicker) {
+ ticker = testTicker;
+ }
+
+ /**
+ * This subscriber is not applicable to any item
+ *
+ * @param item ignored
+ * @return always false
+ */
+ @Override
+ protected boolean isApplicable(@Nullable Item item) {
+ return false;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * Subscribes to events that trigger actions in Jenkins, such as repository scans or builds.
+ *
+ * The {@link GHEvent} enum defines about 63 events, but not all are relevant to Jenkins.
+ * Tracking unnecessary events increases memory usage, and they occur more frequently than those triggering any
+ * work.
+ *
+ *
+ * Documentation reference (also referenced in {@link GHEvent})
+ */
+ @Override
+ protected Set events() {
+ return Set.of(
+ GHEvent.CHECK_RUN, // associated with GitHub action Re-run button to trigger build
+ GHEvent.CHECK_SUITE, // associated with GitHub action Re-run button to trigger build
+ GHEvent.CREATE, // branch or tag creation
+ GHEvent.DELETE, // branch or tag deletion
+ GHEvent.PULL_REQUEST, // PR creation (also PR close or merge)
+ GHEvent.PUSH // commit push
+ );
+ }
+
+ @Override
+ protected void onEvent(final GHSubscriberEvent event) {
+ String eventGuid = event.getEventGuid();
+ LOGGER.fine(() -> "Received event with GUID: " + eventGuid);
+ if (eventGuid == null) {
+ return;
+ }
+ if (eventTracker.getIfPresent(eventGuid) != null) {
+ lastDuplicate = new TrackedDuplicateEvent(eventGuid, getNow(), event);
+ }
+ eventTracker.put(eventGuid, DUMMY);
+ }
+
+ /**
+ * Checks if a duplicate event was recorded in the past 24 hours.
+ *
+ * Events are not stored for 24 hours—only the most recent duplicate is checked within this timeframe.
+ *
+ * @return {@code true} if a duplicate was seen in the last 24 hours, {@code false} otherwise.
+ */
+ public boolean isDuplicateEventSeen() {
+ return lastDuplicate != null
+ && Duration.between(lastDuplicate.lastUpdated(), getNow()).compareTo(TWENTY_FOUR_HOURS) < 0;
+ }
+
+ private Instant getNow() {
+ return Instant.ofEpochSecond(0L, ticker.read());
+ }
+
+ public TrackedDuplicateEvent getLastDuplicate() {
+ return lastDuplicate;
+ }
+
+ /**
+ * Caffeine expired keys are not removed immediately. Method returns the non-expired keys;
+ * required for the tests.
+ */
+ @VisibleForTesting
+ @Restricted(NoExternalUse.class)
+ Set getPresentEventKeys() {
+ return eventTracker.asMap().keySet().stream()
+ .filter(key -> eventTracker.getIfPresent(key) != null)
+ .collect(Collectors.toSet());
+ }
+ }
+}
diff --git a/src/main/java/org/jenkinsci/plugins/github/admin/GitHubHookRegisterProblemMonitor.java b/src/main/java/org/jenkinsci/plugins/github/admin/GitHubHookRegisterProblemMonitor.java
index d502eff59..33dad11a9 100644
--- a/src/main/java/org/jenkinsci/plugins/github/admin/GitHubHookRegisterProblemMonitor.java
+++ b/src/main/java/org/jenkinsci/plugins/github/admin/GitHubHookRegisterProblemMonitor.java
@@ -15,13 +15,13 @@
import org.kohsuke.stapler.HttpRedirect;
import org.kohsuke.stapler.HttpResponse;
import org.kohsuke.stapler.HttpResponses;
-import org.kohsuke.stapler.StaplerRequest;
+import org.kohsuke.stapler.StaplerRequest2;
import org.kohsuke.stapler.interceptor.RequirePOST;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import javax.annotation.Nonnull;
-import javax.inject.Inject;
+import edu.umd.cs.findbugs.annotations.NonNull;
+import jakarta.inject.Inject;
import java.io.File;
import java.io.IOException;
import java.util.List;
@@ -32,7 +32,7 @@
/**
* Administrative monitor to track problems of registering/removing hooks for GH.
- * Holds non-savable map of repo->message and persisted list of ignored projects.
+ * Holds non-savable map of repo->message and persisted list of ignored projects.
* Anyone can register new problem with {@link #registerProblem(GitHubRepositoryName, Throwable)} and check
* repo for problems with {@link #isProblemWith(GitHubRepositoryName)}
*
@@ -64,7 +64,7 @@ public GitHubHookRegisterProblemMonitor() {
}
/**
- * @return Immutable copy of map with repo->problem message content
+ * @return Immutable copy of map with repo->problem message content
*/
public Map getProblems() {
return ImmutableMap.copyOf(problems);
@@ -147,7 +147,7 @@ public boolean isActivated() {
*/
@RequirePOST
@RequireAdminRights
- public HttpResponse doAct(StaplerRequest req) throws IOException {
+ public HttpResponse doAct(StaplerRequest2 req) throws IOException {
if (req.hasParameter("no")) {
disable(true);
return HttpResponses.redirectViaContextPath("/manage");
@@ -166,7 +166,7 @@ public HttpResponse doAct(StaplerRequest req) throws IOException {
@ValidateRepoName
@RequireAdminRights
@RespondWithRedirect
- public void doIgnore(@Nonnull @GHRepoName GitHubRepositoryName repo) {
+ public void doIgnore(@NonNull @GHRepoName GitHubRepositoryName repo) {
if (!ignored.contains(repo)) {
ignored.add(repo);
}
@@ -183,7 +183,7 @@ public void doIgnore(@Nonnull @GHRepoName GitHubRepositoryName repo) {
@ValidateRepoName
@RequireAdminRights
@RespondWithRedirect
- public void doDisignore(@Nonnull @GHRepoName GitHubRepositoryName repo) {
+ public void doDisignore(@NonNull @GHRepoName GitHubRepositoryName repo) {
ignored.remove(repo);
}
@@ -236,7 +236,7 @@ public static class GitHubHookRegisterProblemManagementLink extends ManagementLi
public String getIconFileName() {
return monitor.getProblems().isEmpty() && monitor.ignored.isEmpty()
? null
- : "/plugin/github/img/logo.svg";
+ : "symbol-logo-github plugin-github";
}
@Override
diff --git a/src/main/java/org/jenkinsci/plugins/github/admin/RequireAdminRights.java b/src/main/java/org/jenkinsci/plugins/github/admin/RequireAdminRights.java
index 00a9617cc..953a2fae0 100644
--- a/src/main/java/org/jenkinsci/plugins/github/admin/RequireAdminRights.java
+++ b/src/main/java/org/jenkinsci/plugins/github/admin/RequireAdminRights.java
@@ -1,12 +1,12 @@
package org.jenkinsci.plugins.github.admin;
import jenkins.model.Jenkins;
-import org.kohsuke.stapler.StaplerRequest;
-import org.kohsuke.stapler.StaplerResponse;
+import org.kohsuke.stapler.StaplerRequest2;
+import org.kohsuke.stapler.StaplerResponse2;
import org.kohsuke.stapler.interceptor.Interceptor;
import org.kohsuke.stapler.interceptor.InterceptorAnnotation;
-import javax.servlet.ServletException;
+import jakarta.servlet.ServletException;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import java.lang.reflect.InvocationTargetException;
@@ -29,7 +29,7 @@
class Processor extends Interceptor {
@Override
- public Object invoke(StaplerRequest request, StaplerResponse response, Object instance, Object[] arguments)
+ public Object invoke(StaplerRequest2 request, StaplerResponse2 response, Object instance, Object[] arguments)
throws IllegalAccessException, InvocationTargetException, ServletException {
Jenkins.getInstance().checkPermission(Jenkins.ADMINISTER);
diff --git a/src/main/java/org/jenkinsci/plugins/github/admin/RespondWithRedirect.java b/src/main/java/org/jenkinsci/plugins/github/admin/RespondWithRedirect.java
index bfc4a196d..f0be54946 100644
--- a/src/main/java/org/jenkinsci/plugins/github/admin/RespondWithRedirect.java
+++ b/src/main/java/org/jenkinsci/plugins/github/admin/RespondWithRedirect.java
@@ -1,12 +1,12 @@
package org.jenkinsci.plugins.github.admin;
import org.kohsuke.stapler.HttpRedirect;
-import org.kohsuke.stapler.StaplerRequest;
-import org.kohsuke.stapler.StaplerResponse;
+import org.kohsuke.stapler.StaplerRequest2;
+import org.kohsuke.stapler.StaplerResponse2;
import org.kohsuke.stapler.interceptor.Interceptor;
import org.kohsuke.stapler.interceptor.InterceptorAnnotation;
-import javax.servlet.ServletException;
+import jakarta.servlet.ServletException;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import java.lang.reflect.InvocationTargetException;
@@ -30,7 +30,7 @@
class Processor extends Interceptor {
@Override
- public Object invoke(StaplerRequest request, StaplerResponse response, Object instance, Object[] arguments)
+ public Object invoke(StaplerRequest2 request, StaplerResponse2 response, Object instance, Object[] arguments)
throws IllegalAccessException, InvocationTargetException, ServletException {
target.invoke(request, response, instance, arguments);
throw new InvocationTargetException(new HttpRedirect("."));
diff --git a/src/main/java/org/jenkinsci/plugins/github/admin/ValidateRepoName.java b/src/main/java/org/jenkinsci/plugins/github/admin/ValidateRepoName.java
index 6a7d6a3ba..b4977e418 100644
--- a/src/main/java/org/jenkinsci/plugins/github/admin/ValidateRepoName.java
+++ b/src/main/java/org/jenkinsci/plugins/github/admin/ValidateRepoName.java
@@ -1,12 +1,12 @@
package org.jenkinsci.plugins.github.admin;
import com.cloudbees.jenkins.GitHubRepositoryName;
-import org.kohsuke.stapler.StaplerRequest;
-import org.kohsuke.stapler.StaplerResponse;
+import org.kohsuke.stapler.StaplerRequest2;
+import org.kohsuke.stapler.StaplerResponse2;
import org.kohsuke.stapler.interceptor.Interceptor;
import org.kohsuke.stapler.interceptor.InterceptorAnnotation;
-import javax.servlet.ServletException;
+import jakarta.servlet.ServletException;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import java.lang.reflect.InvocationTargetException;
@@ -16,7 +16,7 @@
import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
-import static javax.servlet.http.HttpServletResponse.SC_BAD_REQUEST;
+import static jakarta.servlet.http.HttpServletResponse.SC_BAD_REQUEST;
import static org.jenkinsci.plugins.github.util.FluentIterableWrapper.from;
import static org.kohsuke.stapler.HttpResponses.errorWithoutStack;
@@ -34,7 +34,7 @@
class Processor extends Interceptor {
@Override
- public Object invoke(StaplerRequest request, StaplerResponse response, Object instance, Object[] arguments)
+ public Object invoke(StaplerRequest2 request, StaplerResponse2 response, Object instance, Object[] arguments)
throws IllegalAccessException, InvocationTargetException, ServletException {
if (!from(newArrayList(arguments)).firstMatch(instanceOf(GitHubRepositoryName.class)).isPresent()) {
diff --git a/src/main/java/org/jenkinsci/plugins/github/common/CombineErrorHandler.java b/src/main/java/org/jenkinsci/plugins/github/common/CombineErrorHandler.java
index 71fec736e..b155a57c3 100644
--- a/src/main/java/org/jenkinsci/plugins/github/common/CombineErrorHandler.java
+++ b/src/main/java/org/jenkinsci/plugins/github/common/CombineErrorHandler.java
@@ -5,7 +5,7 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import javax.annotation.Nonnull;
+import edu.umd.cs.findbugs.annotations.NonNull;
import java.util.ArrayList;
import java.util.List;
@@ -53,7 +53,7 @@ public CombineErrorHandler withHandlers(List extends ErrorHandler> handlers) {
* @return true if exception handled or rethrows it
*/
@Override
- public boolean handle(Exception e, @Nonnull Run, ?> run, @Nonnull TaskListener listener) {
+ public boolean handle(Exception e, @NonNull Run, ?> run, @NonNull TaskListener listener) {
LOG.debug("Exception in {} will be processed with {} handlers",
run.getParent().getName(), handlers.size(), e);
try {
diff --git a/src/main/java/org/jenkinsci/plugins/github/common/ErrorHandler.java b/src/main/java/org/jenkinsci/plugins/github/common/ErrorHandler.java
index 65c4104f1..235caa1db 100644
--- a/src/main/java/org/jenkinsci/plugins/github/common/ErrorHandler.java
+++ b/src/main/java/org/jenkinsci/plugins/github/common/ErrorHandler.java
@@ -3,7 +3,7 @@
import hudson.model.Run;
import hudson.model.TaskListener;
-import javax.annotation.Nonnull;
+import edu.umd.cs.findbugs.annotations.NonNull;
/**
* So you can implement bunch of {@link ErrorHandler}s and log, rethrow, ignore exception.
@@ -26,5 +26,5 @@ public interface ErrorHandler {
* @return true if exception handled successfully
* @throws Exception you can rethrow exception of any type
*/
- boolean handle(Exception e, @Nonnull Run, ?> run, @Nonnull TaskListener listener) throws Exception;
+ boolean handle(Exception e, @NonNull Run, ?> run, @NonNull TaskListener listener) throws Exception;
}
diff --git a/src/main/java/org/jenkinsci/plugins/github/config/GitHubPluginConfig.java b/src/main/java/org/jenkinsci/plugins/github/config/GitHubPluginConfig.java
index f3bb9304f..44ee71060 100644
--- a/src/main/java/org/jenkinsci/plugins/github/config/GitHubPluginConfig.java
+++ b/src/main/java/org/jenkinsci/plugins/github/config/GitHubPluginConfig.java
@@ -4,11 +4,14 @@
import com.google.common.base.Function;
import com.google.common.base.Predicate;
import com.google.common.base.Predicates;
+import edu.umd.cs.findbugs.annotations.NonNull;
+import hudson.BulkChange;
import hudson.Extension;
import hudson.Util;
import hudson.XmlFile;
import hudson.model.Descriptor;
import hudson.model.Item;
+import hudson.security.Permission;
import hudson.util.FormValidation;
import jenkins.model.GlobalConfiguration;
import jenkins.model.Jenkins;
@@ -24,12 +27,12 @@
import org.kohsuke.github.GitHub;
import org.kohsuke.stapler.DataBoundSetter;
import org.kohsuke.stapler.QueryParameter;
-import org.kohsuke.stapler.StaplerRequest;
+import org.kohsuke.stapler.StaplerRequest2;
import org.kohsuke.stapler.interceptor.RequirePOST;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import javax.inject.Inject;
+import jakarta.inject.Inject;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
@@ -189,18 +192,36 @@ protected XmlFile getConfigFile() {
}
@Override
- public boolean configure(StaplerRequest req, JSONObject json) throws FormException {
- hookSecretConfigs = null; // form binding might omit empty lists
+ public boolean configure(StaplerRequest2 req, JSONObject json) throws FormException {
try {
- req.bindJSON(this, json);
+ BulkChange bc = new BulkChange(this);
+ try {
+ if (json.has("configs")) {
+ setConfigs(req.bindJSONToList(GitHubServerConfig.class, json.get("configs")));
+ } else {
+ setConfigs(Collections.emptyList());
+ }
+ if (json.has("hookSecretConfigs")) {
+ setHookSecretConfigs(req.bindJSONToList(HookSecretConfig.class, json.get("hookSecretConfigs")));
+ } else {
+ setHookSecretConfigs(Collections.emptyList());
+ }
+ if (json.optBoolean("isOverrideHookUrl", false) && (json.has("hookUrl"))) {
+ setHookUrl(json.optString("hookUrl"));
+ } else {
+ setHookUrl(null);
+ }
+ req.bindJSON(this, json);
+ clearRedundantCaches(configs);
+ } finally {
+ bc.commit();
+ }
} catch (Exception e) {
LOGGER.debug("Problem while submitting form for GitHub Plugin ({})", e.getMessage(), e);
LOGGER.trace("GH form data: {}", json.toString());
throw new FormException(
format("Malformed GitHub Plugin configuration (%s)", e.getMessage()), e, "github-configuration");
}
- save();
- clearRedundantCaches(configs);
return true;
}
@@ -212,7 +233,7 @@ public String getDisplayName() {
@SuppressWarnings("unused")
@RequirePOST
public FormValidation doReRegister() {
- Jenkins.getActiveInstance().checkPermission(Jenkins.ADMINISTER);
+ Jenkins.getActiveInstance().checkPermission(Jenkins.MANAGE);
if (!GitHubPlugin.configuration().isManageHooks()) {
return FormValidation.warning("Works only when Jenkins manages hooks (one or more creds specified)");
}
@@ -227,7 +248,7 @@ public FormValidation doReRegister() {
@Restricted(DoNotUse.class) // WebOnly
@SuppressWarnings("unused")
public FormValidation doCheckHookUrl(@QueryParameter String value) {
- Jenkins.getActiveInstance().checkPermission(Jenkins.ADMINISTER);
+ Jenkins.getActiveInstance().checkPermission(Jenkins.MANAGE);
try {
HttpURLConnection con = (HttpURLConnection) new URL(value).openConnection();
con.setRequestMethod("POST");
@@ -317,4 +338,10 @@ private URL parseHookUrl(String hookUrl) {
return null;
}
}
+
+ @NonNull
+ @Override
+ public Permission getRequiredGlobalConfigPagePermission() {
+ return Jenkins.MANAGE;
+ }
}
diff --git a/src/main/java/org/jenkinsci/plugins/github/config/GitHubServerConfig.java b/src/main/java/org/jenkinsci/plugins/github/config/GitHubServerConfig.java
index 88bb78ce5..9fed6de8d 100644
--- a/src/main/java/org/jenkinsci/plugins/github/config/GitHubServerConfig.java
+++ b/src/main/java/org/jenkinsci/plugins/github/config/GitHubServerConfig.java
@@ -7,12 +7,14 @@
import com.google.common.base.Optional;
import com.google.common.base.Predicate;
import com.google.common.base.Supplier;
+import edu.umd.cs.findbugs.annotations.CheckForNull;
import edu.umd.cs.findbugs.annotations.NonNull;
import hudson.Extension;
import hudson.Util;
import hudson.model.AbstractDescribableImpl;
import hudson.model.Descriptor;
import hudson.security.ACL;
+import hudson.security.Permission;
import hudson.util.FormValidation;
import hudson.util.ListBoxModel;
import hudson.util.Secret;
@@ -21,8 +23,6 @@
import java.net.URL;
import java.util.Collections;
import java.util.List;
-import javax.annotation.CheckForNull;
-import javax.annotation.Nonnull;
import jenkins.model.Jenkins;
import jenkins.scm.api.SCMName;
import org.apache.commons.lang3.StringUtils;
@@ -215,7 +215,7 @@ public int getClientCacheSize() {
}
/**
- * @param clientCacheSize capacity of cache for GitHub client in MB, set to <= 0 to turn off this feature
+ * @param clientCacheSize capacity of cache for GitHub client in MB, set to <= 0 to turn off this feature
*/
@DataBoundSetter
public void setClientCacheSize(int clientCacheSize) {
@@ -268,7 +268,7 @@ public static Function loginToGithub() {
*
* @return token from creds or default non empty string
*/
- @Nonnull
+ @NonNull
public static String tokenFor(String credentialsId) {
return secretFor(credentialsId).or(new Supplier() {
@Override
@@ -285,7 +285,7 @@ public Secret get() {
*
* @return secret from creds or empty optional
*/
- @Nonnull
+ @NonNull
public static Optional secretFor(String credentialsId) {
List creds = filter(
lookupCredentials(StringCredentials.class,
@@ -297,7 +297,7 @@ public static Optional secretFor(String credentialsId) {
return FluentIterableWrapper.from(creds)
.transform(new NullSafeFunction() {
@Override
- protected Secret applyNullSafe(@Nonnull StringCredentials input) {
+ protected Secret applyNullSafe(@NonNull StringCredentials input) {
return input.getSecret();
}
}).first();
@@ -318,7 +318,7 @@ protected Secret applyNullSafe(@Nonnull StringCredentials input) {
public static Predicate withHost(final String host) {
return new NullSafePredicate() {
@Override
- protected boolean applyNullSafe(@Nonnull GitHubServerConfig github) {
+ protected boolean applyNullSafe(@NonNull GitHubServerConfig github) {
return defaultIfEmpty(github.getApiUrl(), GITHUB_URL).contains(host);
}
};
@@ -346,10 +346,16 @@ public String getDisplayName() {
return "GitHub Server";
}
+ @NonNull
+ @Override
+ public Permission getRequiredGlobalConfigPagePermission() {
+ return Jenkins.MANAGE;
+ }
+
@SuppressWarnings("unused")
public ListBoxModel doFillCredentialsIdItems(@QueryParameter String apiUrl,
@QueryParameter String credentialsId) {
- if (!Jenkins.getInstance().hasPermission(Jenkins.ADMINISTER)) {
+ if (!Jenkins.getInstance().hasPermission(Jenkins.MANAGE)) {
return new StandardListBoxModel().includeCurrentValue(credentialsId);
}
return new StandardListBoxModel()
@@ -368,7 +374,7 @@ public ListBoxModel doFillCredentialsIdItems(@QueryParameter String apiUrl,
public FormValidation doVerifyCredentials(
@QueryParameter String apiUrl,
@QueryParameter String credentialsId) throws IOException {
- Jenkins.getActiveInstance().checkPermission(Jenkins.ADMINISTER);
+ Jenkins.getActiveInstance().checkPermission(Jenkins.MANAGE);
GitHubServerConfig config = new GitHubServerConfig(credentialsId);
config.setApiUrl(apiUrl);
@@ -413,11 +419,13 @@ public FormValidation doCheckApiUrl(@QueryParameter String value) {
*/
private static class ClientCacheFunction extends NullSafeFunction {
@Override
- protected GitHub applyNullSafe(@Nonnull GitHubServerConfig github) {
+ protected GitHub applyNullSafe(@NonNull GitHubServerConfig github) {
if (github.getCachedClient() == null) {
github.setCachedClient(new GitHubLoginFunction().apply(github));
}
return github.getCachedClient();
}
}
+
+
}
diff --git a/src/main/java/org/jenkinsci/plugins/github/config/GitHubTokenCredentialsCreator.java b/src/main/java/org/jenkinsci/plugins/github/config/GitHubTokenCredentialsCreator.java
index 46947b4f2..38cbb73ed 100644
--- a/src/main/java/org/jenkinsci/plugins/github/config/GitHubTokenCredentialsCreator.java
+++ b/src/main/java/org/jenkinsci/plugins/github/config/GitHubTokenCredentialsCreator.java
@@ -28,8 +28,8 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import javax.annotation.Nonnull;
-import javax.annotation.Nullable;
+import edu.umd.cs.findbugs.annotations.NonNull;
+import edu.umd.cs.findbugs.annotations.Nullable;
import java.io.IOException;
import java.net.URI;
import java.util.List;
@@ -92,7 +92,7 @@ public String getDisplayName() {
@SuppressWarnings("unused")
public ListBoxModel doFillCredentialsIdItems(@QueryParameter String apiUrl, @QueryParameter String credentialsId) {
- if (!Jenkins.getInstance().hasPermission(Jenkins.ADMINISTER)) {
+ if (!Jenkins.getInstance().hasPermission(Jenkins.MANAGE)) {
return new StandardUsernameListBoxModel().includeCurrentValue(credentialsId);
}
return new StandardUsernameListBoxModel()
@@ -118,7 +118,7 @@ public ListBoxModel doFillCredentialsIdItems(@QueryParameter String apiUrl, @Que
public FormValidation doCreateTokenByCredentials(
@QueryParameter String apiUrl,
@QueryParameter String credentialsId) {
- Jenkins.getActiveInstance().checkPermission(Jenkins.ADMINISTER);
+ Jenkins.getActiveInstance().checkPermission(Jenkins.MANAGE);
if (isEmpty(credentialsId)) {
return FormValidation.error("Please specify credentials to create token");
}
@@ -167,7 +167,7 @@ public FormValidation doCreateTokenByPassword(
@QueryParameter String apiUrl,
@QueryParameter String login,
@QueryParameter String password) {
- Jenkins.getActiveInstance().checkPermission(Jenkins.ADMINISTER);
+ Jenkins.getActiveInstance().checkPermission(Jenkins.MANAGE);
try {
GHAuthorization token = createToken(login, password, defaultIfBlank(apiUrl, GITHUB_URL));
StandardCredentials credentials = createCredentials(apiUrl, token.getToken(), login);
@@ -190,8 +190,8 @@ public FormValidation doCreateTokenByPassword(
* @return personal token with requested scope
* @throws IOException when can't create token with given creds
*/
- public GHAuthorization createToken(@Nonnull String username,
- @Nonnull String password,
+ public GHAuthorization createToken(@NonNull String username,
+ @NonNull String password,
@Nullable String apiUrl) throws IOException {
GitHub gitHub = new GitHubBuilder()
.withEndpoint(defaultIfBlank(apiUrl, GITHUB_URL))
@@ -236,7 +236,7 @@ public StandardCredentials createCredentials(@Nullable String serverAPIUrl, Stri
*
* @return saved creds
*/
- private StandardCredentials createCredentials(@Nonnull String serverAPIUrl,
+ private StandardCredentials createCredentials(@NonNull String serverAPIUrl,
final StandardCredentials credentials) {
URI serverUri = URI.create(defaultIfBlank(serverAPIUrl, GITHUB_URL));
diff --git a/src/main/java/org/jenkinsci/plugins/github/config/HookSecretConfig.java b/src/main/java/org/jenkinsci/plugins/github/config/HookSecretConfig.java
index f50815ad1..9db733af7 100644
--- a/src/main/java/org/jenkinsci/plugins/github/config/HookSecretConfig.java
+++ b/src/main/java/org/jenkinsci/plugins/github/config/HookSecretConfig.java
@@ -3,17 +3,20 @@
import com.cloudbees.plugins.credentials.CredentialsMatchers;
import com.cloudbees.plugins.credentials.common.StandardListBoxModel;
import com.cloudbees.plugins.credentials.domains.DomainRequirement;
+import edu.umd.cs.findbugs.annotations.NonNull;
import hudson.Extension;
import hudson.model.AbstractDescribableImpl;
import hudson.model.Descriptor;
import hudson.security.ACL;
+import hudson.security.Permission;
import hudson.util.ListBoxModel;
import hudson.util.Secret;
import jenkins.model.Jenkins;
+import org.jenkinsci.plugins.github.webhook.SignatureAlgorithm;
import org.jenkinsci.plugins.plaincredentials.StringCredentials;
import org.kohsuke.stapler.DataBoundConstructor;
-import javax.annotation.Nullable;
+import edu.umd.cs.findbugs.annotations.Nullable;
import java.util.Collections;
import org.kohsuke.stapler.QueryParameter;
@@ -23,10 +26,19 @@
public class HookSecretConfig extends AbstractDescribableImpl {
private String credentialsId;
+ private SignatureAlgorithm signatureAlgorithm;
@DataBoundConstructor
- public HookSecretConfig(String credentialsId) {
+ public HookSecretConfig(String credentialsId, String signatureAlgorithm) {
this.credentialsId = credentialsId;
+ this.signatureAlgorithm = parseSignatureAlgorithm(signatureAlgorithm);
+ }
+
+ /**
+ * Legacy constructor for backwards compatibility.
+ */
+ public HookSecretConfig(String credentialsId) {
+ this(credentialsId, null);
}
/**
@@ -43,6 +55,26 @@ public String getCredentialsId() {
return credentialsId;
}
+ /**
+ * Gets the signature algorithm to use for webhook validation.
+ *
+ * @return the configured signature algorithm, defaults to SHA-256
+ * @since 1.45.0
+ */
+ public SignatureAlgorithm getSignatureAlgorithm() {
+ return signatureAlgorithm != null ? signatureAlgorithm : SignatureAlgorithm.getDefault();
+ }
+
+ /**
+ * Gets the signature algorithm name for UI binding.
+ *
+ * @return the algorithm name as string (e.g., "SHA256", "SHA1")
+ * @since 1.45.0
+ */
+ public String getSignatureAlgorithmName() {
+ return getSignatureAlgorithm().name();
+ }
+
/**
* @param credentialsId a new ID
* @deprecated rather treat this field as final and use {@link GitHubPluginConfig#setHookSecretConfigs}
@@ -52,6 +84,33 @@ public void setCredentialsId(String credentialsId) {
this.credentialsId = credentialsId;
}
+ /**
+ * Ensures backwards compatibility during deserialization.
+ * Sets default algorithm to SHA-256 for existing configurations.
+ */
+ private Object readResolve() {
+ if (signatureAlgorithm == null) {
+ signatureAlgorithm = SignatureAlgorithm.getDefault();
+ }
+ return this;
+ }
+
+ /**
+ * Parses signature algorithm from UI string input.
+ */
+ private SignatureAlgorithm parseSignatureAlgorithm(String algorithmName) {
+ if (algorithmName == null || algorithmName.trim().isEmpty()) {
+ return SignatureAlgorithm.getDefault();
+ }
+
+ try {
+ return SignatureAlgorithm.valueOf(algorithmName.trim().toUpperCase());
+ } catch (IllegalArgumentException e) {
+ // Default to SHA-256 for invalid input
+ return SignatureAlgorithm.getDefault();
+ }
+ }
+
@Extension
public static class DescriptorImpl extends Descriptor {
@@ -60,9 +119,19 @@ public String getDisplayName() {
return "Hook secret configuration";
}
+ /**
+ * Provides dropdown items for signature algorithm selection.
+ */
+ public ListBoxModel doFillSignatureAlgorithmItems() {
+ ListBoxModel items = new ListBoxModel();
+ items.add("SHA-256 (Recommended)", "SHA256");
+ items.add("SHA-1 (Legacy)", "SHA1");
+ return items;
+ }
+
@SuppressWarnings("unused")
public ListBoxModel doFillCredentialsIdItems(@QueryParameter String credentialsId) {
- if (!Jenkins.getInstance().hasPermission(Jenkins.ADMINISTER)) {
+ if (!Jenkins.getInstance().hasPermission(Jenkins.MANAGE)) {
return new StandardListBoxModel().includeCurrentValue(credentialsId);
}
@@ -76,5 +145,11 @@ public ListBoxModel doFillCredentialsIdItems(@QueryParameter String credentialsI
CredentialsMatchers.always()
);
}
+
+ @NonNull
+ @Override
+ public Permission getRequiredGlobalConfigPagePermission() {
+ return Jenkins.MANAGE;
+ }
}
}
diff --git a/src/main/java/org/jenkinsci/plugins/github/extension/GHEventsSubscriber.java b/src/main/java/org/jenkinsci/plugins/github/extension/GHEventsSubscriber.java
index eb458a186..155d8c826 100644
--- a/src/main/java/org/jenkinsci/plugins/github/extension/GHEventsSubscriber.java
+++ b/src/main/java/org/jenkinsci/plugins/github/extension/GHEventsSubscriber.java
@@ -8,7 +8,7 @@
import hudson.model.Job;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
-import javax.annotation.CheckForNull;
+import edu.umd.cs.findbugs.annotations.CheckForNull;
import jenkins.model.Jenkins;
import jenkins.scm.api.SCMEvent;
import org.jenkinsci.plugins.github.util.misc.NullSafeFunction;
@@ -18,8 +18,9 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import javax.annotation.Nonnull;
-import javax.annotation.Nullable;
+import edu.umd.cs.findbugs.annotations.NonNull;
+import edu.umd.cs.findbugs.annotations.Nullable;
+
import java.util.Collections;
import java.util.Set;
@@ -156,7 +157,7 @@ public static ExtensionList all() {
public static Function> extractEvents() {
return new NullSafeFunction>() {
@Override
- protected Set applyNullSafe(@Nonnull GHEventsSubscriber subscriber) {
+ protected Set applyNullSafe(@NonNull GHEventsSubscriber subscriber) {
return defaultIfNull(subscriber.events(), Collections.emptySet());
}
};
@@ -188,7 +189,7 @@ public static Predicate isApplicableFor(final Job, ?> proj
public static Predicate isApplicableFor(final Item item) {
return new NullSafePredicate() {
@Override
- protected boolean applyNullSafe(@Nonnull GHEventsSubscriber subscriber) {
+ protected boolean applyNullSafe(@NonNull GHEventsSubscriber subscriber) {
return subscriber.safeIsApplicable(item);
}
};
@@ -204,7 +205,7 @@ protected boolean applyNullSafe(@Nonnull GHEventsSubscriber subscriber) {
public static Predicate isInterestedIn(final GHEvent event) {
return new NullSafePredicate() {
@Override
- protected boolean applyNullSafe(@Nonnull GHEventsSubscriber subscriber) {
+ protected boolean applyNullSafe(@NonNull GHEventsSubscriber subscriber) {
return defaultIfNull(subscriber.events(), emptySet()).contains(event);
}
};
@@ -221,7 +222,7 @@ protected boolean applyNullSafe(@Nonnull GHEventsSubscriber subscriber) {
*/
@Deprecated
public static Function processEvent(final GHEvent event, final String payload) {
- return processEvent(new GHSubscriberEvent(SCMEvent.originOf(Stapler.getCurrentRequest()), event, payload));
+ return processEvent(new GHSubscriberEvent(SCMEvent.originOf(Stapler.getCurrentRequest2()), event, payload));
}
/**
@@ -235,7 +236,7 @@ public static Function processEvent(final GHEvent even
public static Function processEvent(final GHSubscriberEvent event) {
return new NullSafeFunction() {
@Override
- protected Void applyNullSafe(@Nonnull GHEventsSubscriber subscriber) {
+ protected Void applyNullSafe(@NonNull GHEventsSubscriber subscriber) {
try {
subscriber.onEvent(event);
} catch (Throwable t) {
diff --git a/src/main/java/org/jenkinsci/plugins/github/extension/GHSubscriberEvent.java b/src/main/java/org/jenkinsci/plugins/github/extension/GHSubscriberEvent.java
index f5fc752cc..bde28d6f1 100644
--- a/src/main/java/org/jenkinsci/plugins/github/extension/GHSubscriberEvent.java
+++ b/src/main/java/org/jenkinsci/plugins/github/extension/GHSubscriberEvent.java
@@ -1,10 +1,11 @@
package org.jenkinsci.plugins.github.extension;
+import jakarta.servlet.http.HttpServletRequest;
import jenkins.scm.api.SCMEvent;
import org.kohsuke.github.GHEvent;
-import javax.annotation.CheckForNull;
-import javax.annotation.Nonnull;
+import edu.umd.cs.findbugs.annotations.CheckForNull;
+import edu.umd.cs.findbugs.annotations.NonNull;
/**
* An event for a {@link GHEventsSubscriber}.
@@ -17,16 +18,32 @@ public class GHSubscriberEvent extends SCMEvent {
*/
private final GHEvent ghEvent;
+ private final String eventGuid;
+
+ /**
+ * @deprecated use {@link #GHSubscriberEvent(String, String, GHEvent, String)} instead.
+ */
+ @Deprecated
+ public GHSubscriberEvent(@CheckForNull String origin, @NonNull GHEvent ghEvent, @NonNull String payload) {
+ this(null, origin, ghEvent, payload);
+ }
+
/**
* Constructs a new {@link GHSubscriberEvent}.
- *
- * @param origin the origin (see {@link SCMEvent#originOf(javax.servlet.http.HttpServletRequest)}) or {@code null}.
+ * @param eventGuid the globally unique identifier (GUID) to identify the event; value of
+ * request header {@link com.cloudbees.jenkins.GitHubWebHook#X_GITHUB_DELIVERY}.
+ * @param origin the origin (see {@link SCMEvent#originOf(HttpServletRequest)}) or {@code null}.
* @param ghEvent the type of event received from GitHub.
* @param payload the event payload.
*/
- public GHSubscriberEvent(@CheckForNull String origin, @Nonnull GHEvent ghEvent, @Nonnull String payload) {
+ public GHSubscriberEvent(
+ @CheckForNull String eventGuid,
+ @CheckForNull String origin,
+ @NonNull GHEvent ghEvent,
+ @NonNull String payload) {
super(Type.UPDATED, payload, origin);
this.ghEvent = ghEvent;
+ this.eventGuid = eventGuid;
}
/**
@@ -38,4 +55,8 @@ public GHEvent getGHEvent() {
return ghEvent;
}
+ @CheckForNull
+ public String getEventGuid() {
+ return eventGuid;
+ }
}
diff --git a/src/main/java/org/jenkinsci/plugins/github/extension/status/GitHubCommitShaSource.java b/src/main/java/org/jenkinsci/plugins/github/extension/status/GitHubCommitShaSource.java
index 325261387..5b118fa1c 100644
--- a/src/main/java/org/jenkinsci/plugins/github/extension/status/GitHubCommitShaSource.java
+++ b/src/main/java/org/jenkinsci/plugins/github/extension/status/GitHubCommitShaSource.java
@@ -5,7 +5,7 @@
import hudson.model.Run;
import hudson.model.TaskListener;
-import javax.annotation.Nonnull;
+import edu.umd.cs.findbugs.annotations.NonNull;
import java.io.IOException;
/**
@@ -23,6 +23,6 @@ public abstract class GitHubCommitShaSource extends AbstractDescribableImpl run, @Nonnull TaskListener listener)
+ public abstract String get(@NonNull Run, ?> run, @NonNull TaskListener listener)
throws IOException, InterruptedException;
}
diff --git a/src/main/java/org/jenkinsci/plugins/github/extension/status/GitHubReposSource.java b/src/main/java/org/jenkinsci/plugins/github/extension/status/GitHubReposSource.java
index fa21c9bd9..c231297f7 100644
--- a/src/main/java/org/jenkinsci/plugins/github/extension/status/GitHubReposSource.java
+++ b/src/main/java/org/jenkinsci/plugins/github/extension/status/GitHubReposSource.java
@@ -6,7 +6,7 @@
import hudson.model.TaskListener;
import org.kohsuke.github.GHRepository;
-import javax.annotation.Nonnull;
+import edu.umd.cs.findbugs.annotations.NonNull;
import java.util.List;
/**
@@ -23,5 +23,5 @@ public abstract class GitHubReposSource extends AbstractDescribableImpl repos(@Nonnull Run, ?> run, @Nonnull TaskListener listener);
+ public abstract List repos(@NonNull Run, ?> run, @NonNull TaskListener listener);
}
diff --git a/src/main/java/org/jenkinsci/plugins/github/extension/status/GitHubStatusContextSource.java b/src/main/java/org/jenkinsci/plugins/github/extension/status/GitHubStatusContextSource.java
index f359f1810..bc307d6c7 100644
--- a/src/main/java/org/jenkinsci/plugins/github/extension/status/GitHubStatusContextSource.java
+++ b/src/main/java/org/jenkinsci/plugins/github/extension/status/GitHubStatusContextSource.java
@@ -5,7 +5,7 @@
import hudson.model.Run;
import hudson.model.TaskListener;
-import javax.annotation.Nonnull;
+import edu.umd.cs.findbugs.annotations.NonNull;
/**
* Extension point to provide context of the state. For example `integration-tests` or `build`
@@ -22,5 +22,5 @@ public abstract class GitHubStatusContextSource extends AbstractDescribableImpl<
*
* @return simple short string to represent context of this state
*/
- public abstract String context(@Nonnull Run, ?> run, @Nonnull TaskListener listener);
+ public abstract String context(@NonNull Run, ?> run, @NonNull TaskListener listener);
}
diff --git a/src/main/java/org/jenkinsci/plugins/github/extension/status/GitHubStatusResultSource.java b/src/main/java/org/jenkinsci/plugins/github/extension/status/GitHubStatusResultSource.java
index 81a14b811..620864120 100644
--- a/src/main/java/org/jenkinsci/plugins/github/extension/status/GitHubStatusResultSource.java
+++ b/src/main/java/org/jenkinsci/plugins/github/extension/status/GitHubStatusResultSource.java
@@ -6,7 +6,7 @@
import hudson.model.TaskListener;
import org.kohsuke.github.GHCommitState;
-import javax.annotation.Nonnull;
+import edu.umd.cs.findbugs.annotations.NonNull;
import java.io.IOException;
/**
@@ -24,7 +24,7 @@ public abstract class GitHubStatusResultSource extends AbstractDescribableImpl run, @Nonnull TaskListener listener)
+ public abstract StatusResult get(@NonNull Run, ?> run, @NonNull TaskListener listener)
throws IOException, InterruptedException;
/**
diff --git a/src/main/java/org/jenkinsci/plugins/github/extension/status/misc/ConditionalResult.java b/src/main/java/org/jenkinsci/plugins/github/extension/status/misc/ConditionalResult.java
index c1486b331..cfc9dc624 100644
--- a/src/main/java/org/jenkinsci/plugins/github/extension/status/misc/ConditionalResult.java
+++ b/src/main/java/org/jenkinsci/plugins/github/extension/status/misc/ConditionalResult.java
@@ -10,7 +10,7 @@
import org.kohsuke.github.GHCommitState;
import org.kohsuke.stapler.DataBoundSetter;
-import javax.annotation.Nonnull;
+import edu.umd.cs.findbugs.annotations.NonNull;
/**
* This extension point allows to define when and what to send as state and message.
@@ -56,7 +56,7 @@ public String getMessage() {
*
* @return true if matches
*/
- public abstract boolean matches(@Nonnull Run, ?> run);
+ public abstract boolean matches(@NonNull Run, ?> run);
/**
* Should be extended to and marked as {@link hudson.Extension} to be in list
diff --git a/src/main/java/org/jenkinsci/plugins/github/internal/GitHubClientCacheOps.java b/src/main/java/org/jenkinsci/plugins/github/internal/GitHubClientCacheOps.java
index 60a7e5aa9..7ea4b69a3 100644
--- a/src/main/java/org/jenkinsci/plugins/github/internal/GitHubClientCacheOps.java
+++ b/src/main/java/org/jenkinsci/plugins/github/internal/GitHubClientCacheOps.java
@@ -4,7 +4,7 @@
import com.google.common.base.Function;
import com.google.common.base.Predicate;
import com.google.common.hash.Hashing;
-import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
+import edu.umd.cs.findbugs.annotations.NonNull;
import okhttp3.Cache;
import org.apache.commons.io.FileUtils;
import org.jenkinsci.plugins.github.GitHubPlugin;
@@ -14,9 +14,9 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import javax.annotation.Nonnull;
import java.io.File;
import java.io.IOException;
+import java.nio.charset.StandardCharsets;
import java.nio.file.DirectoryStream;
import java.nio.file.Path;
import java.util.List;
@@ -95,7 +95,6 @@ public static Path getBaseCacheDir() {
*
* @param configs active server configs to exclude caches from cleanup
*/
- @SuppressFBWarnings(value = "RCN_REDUNDANT_NULLCHECK_WOULD_HAVE_BEEN_A_NPE")
public static void clearRedundantCaches(List configs) {
Path baseCacheDir = getBaseCacheDir();
@@ -134,7 +133,7 @@ private static void deleteEveryIn(DirectoryStream caches) {
*/
private static class WithEnabledCache extends NullSafePredicate {
@Override
- protected boolean applyNullSafe(@Nonnull GitHubServerConfig config) {
+ protected boolean applyNullSafe(@NonNull GitHubServerConfig config) {
return config.getClientCacheSize() > 0;
}
}
@@ -147,7 +146,7 @@ private static class ToCacheDir extends NullSafeFunction 0, "Cache can't be with size <= 0");
Path cacheDir = getBaseCacheDir().resolve(hashed(config));
@@ -161,8 +160,8 @@ protected Cache applyNullSafe(@Nonnull GitHubServerConfig config) {
*/
private static String hashed(GitHubServerConfig config) {
return Hashing.murmur3_32().newHasher()
- .putString(trimToEmpty(config.getApiUrl()))
- .putString(trimToEmpty(config.getCredentialsId())).hash().toString();
+ .putString(trimToEmpty(config.getApiUrl()), StandardCharsets.UTF_8)
+ .putString(trimToEmpty(config.getCredentialsId()), StandardCharsets.UTF_8).hash().toString();
}
}
@@ -171,7 +170,7 @@ private static String hashed(GitHubServerConfig config) {
*/
private static class CacheToName extends NullSafeFunction {
@Override
- protected String applyNullSafe(@Nonnull Cache cache) {
+ protected String applyNullSafe(@NonNull Cache cache) {
return cache.directory().getName();
}
}
diff --git a/src/main/java/org/jenkinsci/plugins/github/internal/GitHubLoginFunction.java b/src/main/java/org/jenkinsci/plugins/github/internal/GitHubLoginFunction.java
index dd5cb728b..ecee2d33b 100644
--- a/src/main/java/org/jenkinsci/plugins/github/internal/GitHubLoginFunction.java
+++ b/src/main/java/org/jenkinsci/plugins/github/internal/GitHubLoginFunction.java
@@ -1,6 +1,7 @@
package org.jenkinsci.plugins.github.internal;
import com.cloudbees.jenkins.GitHubWebHook;
+import io.jenkins.plugins.okhttp.api.JenkinsOkHttpClient;
import okhttp3.Cache;
import okhttp3.OkHttpClient;
import jenkins.model.Jenkins;
@@ -15,8 +16,8 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import javax.annotation.CheckForNull;
-import javax.annotation.Nonnull;
+import edu.umd.cs.findbugs.annotations.CheckForNull;
+import edu.umd.cs.findbugs.annotations.NonNull;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.Proxy;
@@ -44,7 +45,7 @@
@Restricted(NoExternalUse.class)
public class GitHubLoginFunction extends NullSafeFunction {
- private static final OkHttpClient BASECLIENT = new OkHttpClient();
+ private static final OkHttpClient BASECLIENT = JenkinsOkHttpClient.newClientBuilder(new OkHttpClient()).build();
private static final Logger LOGGER = LoggerFactory.getLogger(GitHubLoginFunction.class);
/**
@@ -57,7 +58,7 @@ public class GitHubLoginFunction extends NullSafeFunction getErrorHandlers() {
* Gets info from the providers and updates commit status
*/
@Override
- public void perform(@Nonnull Run, ?> run, @Nonnull FilePath workspace, @Nonnull Launcher launcher,
- @Nonnull TaskListener listener) {
+ public void perform(@NonNull Run, ?> run, @NonNull FilePath workspace, @NonNull Launcher launcher,
+ @NonNull TaskListener listener) {
try {
String sha = getCommitShaSource().get(run, listener);
List repos = getReposSource().repos(run, listener);
diff --git a/src/main/java/org/jenkinsci/plugins/github/status/err/ChangingBuildStatusErrorHandler.java b/src/main/java/org/jenkinsci/plugins/github/status/err/ChangingBuildStatusErrorHandler.java
index 1400f9822..348f4084c 100644
--- a/src/main/java/org/jenkinsci/plugins/github/status/err/ChangingBuildStatusErrorHandler.java
+++ b/src/main/java/org/jenkinsci/plugins/github/status/err/ChangingBuildStatusErrorHandler.java
@@ -9,7 +9,7 @@
import org.jenkinsci.plugins.github.extension.status.StatusErrorHandler;
import org.kohsuke.stapler.DataBoundConstructor;
-import javax.annotation.Nonnull;
+import edu.umd.cs.findbugs.annotations.NonNull;
import static hudson.model.Result.FAILURE;
import static hudson.model.Result.UNSTABLE;
@@ -40,7 +40,7 @@ public String getResult() {
* @return true as of it terminating handler
*/
@Override
- public boolean handle(Exception e, @Nonnull Run, ?> run, @Nonnull TaskListener listener) {
+ public boolean handle(Exception e, @NonNull Run, ?> run, @NonNull TaskListener listener) {
Result toSet = Result.fromString(trimToEmpty(result));
listener.error("[GitHub Commit Status Setter] - %s, setting build result to %s", e.getMessage(), toSet);
diff --git a/src/main/java/org/jenkinsci/plugins/github/status/err/ShallowAnyErrorHandler.java b/src/main/java/org/jenkinsci/plugins/github/status/err/ShallowAnyErrorHandler.java
index ed389b7dc..4fb544526 100644
--- a/src/main/java/org/jenkinsci/plugins/github/status/err/ShallowAnyErrorHandler.java
+++ b/src/main/java/org/jenkinsci/plugins/github/status/err/ShallowAnyErrorHandler.java
@@ -7,7 +7,7 @@
import org.jenkinsci.plugins.github.extension.status.StatusErrorHandler;
import org.kohsuke.stapler.DataBoundConstructor;
-import javax.annotation.Nonnull;
+import edu.umd.cs.findbugs.annotations.NonNull;
/**
* Just logs message to the build console and do nothing after it
@@ -25,7 +25,7 @@ public ShallowAnyErrorHandler() {
* @return true as of its terminating handler
*/
@Override
- public boolean handle(Exception e, @Nonnull Run, ?> run, @Nonnull TaskListener listener) {
+ public boolean handle(Exception e, @NonNull Run, ?> run, @NonNull TaskListener listener) {
listener.error("[GitHub Commit Status Setter] Failed to update commit state on GitHub. "
+ "Ignoring exception [%s]", e.getMessage());
return true;
diff --git a/src/main/java/org/jenkinsci/plugins/github/status/sources/AnyDefinedRepositorySource.java b/src/main/java/org/jenkinsci/plugins/github/status/sources/AnyDefinedRepositorySource.java
index 5183de388..b0333d88b 100644
--- a/src/main/java/org/jenkinsci/plugins/github/status/sources/AnyDefinedRepositorySource.java
+++ b/src/main/java/org/jenkinsci/plugins/github/status/sources/AnyDefinedRepositorySource.java
@@ -13,7 +13,7 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import javax.annotation.Nonnull;
+import edu.umd.cs.findbugs.annotations.NonNull;
import java.util.Collection;
import java.util.List;
@@ -37,7 +37,7 @@ public AnyDefinedRepositorySource() {
* @return all repositories which can be found by repo-contributors
*/
@Override
- public List repos(@Nonnull Run, ?> run, @Nonnull TaskListener listener) {
+ public List repos(@NonNull Run, ?> run, @NonNull TaskListener listener) {
final Collection names = GitHubRepositoryNameContributor
.parseAssociatedNames(run.getParent());
@@ -45,7 +45,7 @@ public List repos(@Nonnull Run, ?> run, @Nonnull TaskListener li
return from(names).transformAndConcat(new NullSafeFunction>() {
@Override
- protected Iterable applyNullSafe(@Nonnull GitHubRepositoryName name) {
+ protected Iterable applyNullSafe(@NonNull GitHubRepositoryName name) {
return name.resolve();
}
}).toList();
diff --git a/src/main/java/org/jenkinsci/plugins/github/status/sources/BuildDataRevisionShaSource.java b/src/main/java/org/jenkinsci/plugins/github/status/sources/BuildDataRevisionShaSource.java
index 126122b67..bdec8c467 100644
--- a/src/main/java/org/jenkinsci/plugins/github/status/sources/BuildDataRevisionShaSource.java
+++ b/src/main/java/org/jenkinsci/plugins/github/status/sources/BuildDataRevisionShaSource.java
@@ -9,7 +9,7 @@
import org.jenkinsci.plugins.github.util.BuildDataHelper;
import org.kohsuke.stapler.DataBoundConstructor;
-import javax.annotation.Nonnull;
+import edu.umd.cs.findbugs.annotations.NonNull;
import java.io.IOException;
/**
@@ -28,7 +28,7 @@ public BuildDataRevisionShaSource() {
* @return sha from git's scm build data action
*/
@Override
- public String get(@Nonnull Run, ?> run, @Nonnull TaskListener listener) throws IOException {
+ public String get(@NonNull Run, ?> run, @NonNull TaskListener listener) throws IOException {
return ObjectId.toString(BuildDataHelper.getCommitSHA1(run));
}
diff --git a/src/main/java/org/jenkinsci/plugins/github/status/sources/ConditionalStatusResultSource.java b/src/main/java/org/jenkinsci/plugins/github/status/sources/ConditionalStatusResultSource.java
index 268ee604b..2c7cd6cb5 100644
--- a/src/main/java/org/jenkinsci/plugins/github/status/sources/ConditionalStatusResultSource.java
+++ b/src/main/java/org/jenkinsci/plugins/github/status/sources/ConditionalStatusResultSource.java
@@ -11,7 +11,7 @@
import org.kohsuke.github.GHCommitState;
import org.kohsuke.stapler.DataBoundConstructor;
-import javax.annotation.Nonnull;
+import edu.umd.cs.findbugs.annotations.NonNull;
import java.io.IOException;
import java.util.Collections;
import java.util.List;
@@ -34,7 +34,7 @@ public ConditionalStatusResultSource(List results) {
this.results = results;
}
- @Nonnull
+ @NonNull
public List getResults() {
return defaultIfNull(results, Collections.emptyList());
}
@@ -46,7 +46,7 @@ public List getResults() {
* @return first matched result or pending state with warn msg
*/
@Override
- public StatusResult get(@Nonnull Run, ?> run, @Nonnull TaskListener listener)
+ public StatusResult get(@NonNull Run, ?> run, @NonNull TaskListener listener)
throws IOException, InterruptedException {
for (ConditionalResult conditionalResult : getResults()) {
diff --git a/src/main/java/org/jenkinsci/plugins/github/status/sources/DefaultCommitContextSource.java b/src/main/java/org/jenkinsci/plugins/github/status/sources/DefaultCommitContextSource.java
index fbd1d3ccb..ee4a38694 100644
--- a/src/main/java/org/jenkinsci/plugins/github/status/sources/DefaultCommitContextSource.java
+++ b/src/main/java/org/jenkinsci/plugins/github/status/sources/DefaultCommitContextSource.java
@@ -7,7 +7,7 @@
import org.jenkinsci.plugins.github.extension.status.GitHubStatusContextSource;
import org.kohsuke.stapler.DataBoundConstructor;
-import javax.annotation.Nonnull;
+import edu.umd.cs.findbugs.annotations.NonNull;
import static com.coravy.hudson.plugins.github.GithubProjectProperty.displayNameFor;
@@ -28,7 +28,7 @@ public DefaultCommitContextSource() {
* @see com.coravy.hudson.plugins.github.GithubProjectProperty#displayNameFor(hudson.model.Job)
*/
@Override
- public String context(@Nonnull Run, ?> run, @Nonnull TaskListener listener) {
+ public String context(@NonNull Run, ?> run, @NonNull TaskListener listener) {
return displayNameFor(run.getParent());
}
diff --git a/src/main/java/org/jenkinsci/plugins/github/status/sources/DefaultStatusResultSource.java b/src/main/java/org/jenkinsci/plugins/github/status/sources/DefaultStatusResultSource.java
index c33971aff..e1a1176f7 100644
--- a/src/main/java/org/jenkinsci/plugins/github/status/sources/DefaultStatusResultSource.java
+++ b/src/main/java/org/jenkinsci/plugins/github/status/sources/DefaultStatusResultSource.java
@@ -10,7 +10,7 @@
import org.kohsuke.github.GHCommitState;
import org.kohsuke.stapler.DataBoundConstructor;
-import javax.annotation.Nonnull;
+import edu.umd.cs.findbugs.annotations.NonNull;
import java.io.IOException;
import static hudson.model.Result.FAILURE;
@@ -34,7 +34,7 @@ public DefaultStatusResultSource() {
}
@Override
- public StatusResult get(@Nonnull Run, ?> run, @Nonnull TaskListener listener) throws IOException,
+ public StatusResult get(@NonNull Run, ?> run, @NonNull TaskListener listener) throws IOException,
InterruptedException {
// We do not use `build.getDurationString()` because it appends 'and counting' (build is still running)
diff --git a/src/main/java/org/jenkinsci/plugins/github/status/sources/ManuallyEnteredCommitContextSource.java b/src/main/java/org/jenkinsci/plugins/github/status/sources/ManuallyEnteredCommitContextSource.java
index ee28e2dd7..ae7768918 100644
--- a/src/main/java/org/jenkinsci/plugins/github/status/sources/ManuallyEnteredCommitContextSource.java
+++ b/src/main/java/org/jenkinsci/plugins/github/status/sources/ManuallyEnteredCommitContextSource.java
@@ -10,7 +10,7 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import javax.annotation.Nonnull;
+import edu.umd.cs.findbugs.annotations.NonNull;
/**
* Allows to manually enter context
@@ -36,7 +36,7 @@ public String getContext() {
* Just returns what user entered. Expands env vars and token macro
*/
@Override
- public String context(@Nonnull Run, ?> run, @Nonnull TaskListener listener) {
+ public String context(@NonNull Run, ?> run, @NonNull TaskListener listener) {
try {
return new ExpandableMessage(context).expandAll(run, listener);
} catch (Exception e) {
diff --git a/src/main/java/org/jenkinsci/plugins/github/status/sources/ManuallyEnteredRepositorySource.java b/src/main/java/org/jenkinsci/plugins/github/status/sources/ManuallyEnteredRepositorySource.java
index 0a73f04f3..3493321b2 100644
--- a/src/main/java/org/jenkinsci/plugins/github/status/sources/ManuallyEnteredRepositorySource.java
+++ b/src/main/java/org/jenkinsci/plugins/github/status/sources/ManuallyEnteredRepositorySource.java
@@ -11,7 +11,7 @@
import org.kohsuke.github.GHRepository;
import org.kohsuke.stapler.DataBoundConstructor;
-import javax.annotation.Nonnull;
+import edu.umd.cs.findbugs.annotations.NonNull;
import java.util.Collections;
import java.util.List;
@@ -35,11 +35,11 @@ GitHubRepositoryName createName(String url) {
}
@Override
- public List repos(@Nonnull Run, ?> run, @Nonnull final TaskListener listener) {
+ public List repos(@NonNull Run, ?> run, @NonNull final TaskListener listener) {
List urls = Collections.singletonList(url);
return from(urls).transformAndConcat(new NullSafeFunction>() {
@Override
- protected Iterable applyNullSafe(@Nonnull String url) {
+ protected Iterable applyNullSafe(@NonNull String url) {
GitHubRepositoryName name = createName(url);
if (name != null) {
return name.resolve();
diff --git a/src/main/java/org/jenkinsci/plugins/github/status/sources/ManuallyEnteredShaSource.java b/src/main/java/org/jenkinsci/plugins/github/status/sources/ManuallyEnteredShaSource.java
index 74b353f45..a6055a863 100644
--- a/src/main/java/org/jenkinsci/plugins/github/status/sources/ManuallyEnteredShaSource.java
+++ b/src/main/java/org/jenkinsci/plugins/github/status/sources/ManuallyEnteredShaSource.java
@@ -8,7 +8,7 @@
import org.jenkinsci.plugins.github.extension.status.GitHubCommitShaSource;
import org.kohsuke.stapler.DataBoundConstructor;
-import javax.annotation.Nonnull;
+import edu.umd.cs.findbugs.annotations.NonNull;
import java.io.IOException;
/**
@@ -34,7 +34,7 @@ public String getSha() {
* Expands env vars and token macro in entered sha
*/
@Override
- public String get(@Nonnull Run, ?> run, @Nonnull TaskListener listener) throws IOException, InterruptedException {
+ public String get(@NonNull Run, ?> run, @NonNull TaskListener listener) throws IOException, InterruptedException {
return new ExpandableMessage(sha).expandAll(run, listener);
}
diff --git a/src/main/java/org/jenkinsci/plugins/github/status/sources/misc/AnyBuildResult.java b/src/main/java/org/jenkinsci/plugins/github/status/sources/misc/AnyBuildResult.java
index 947db9075..1f1dcb7fc 100644
--- a/src/main/java/org/jenkinsci/plugins/github/status/sources/misc/AnyBuildResult.java
+++ b/src/main/java/org/jenkinsci/plugins/github/status/sources/misc/AnyBuildResult.java
@@ -6,7 +6,7 @@
import org.kohsuke.github.GHCommitState;
import org.kohsuke.stapler.DataBoundConstructor;
-import javax.annotation.Nonnull;
+import edu.umd.cs.findbugs.annotations.NonNull;
/**
* Allows to set state in any case
@@ -24,7 +24,7 @@ public AnyBuildResult() {
* @return true in any case
*/
@Override
- public boolean matches(@Nonnull Run, ?> run) {
+ public boolean matches(@NonNull Run, ?> run) {
return true;
}
diff --git a/src/main/java/org/jenkinsci/plugins/github/status/sources/misc/BetterThanOrEqualBuildResult.java b/src/main/java/org/jenkinsci/plugins/github/status/sources/misc/BetterThanOrEqualBuildResult.java
index 9600e4b22..8fcd53185 100644
--- a/src/main/java/org/jenkinsci/plugins/github/status/sources/misc/BetterThanOrEqualBuildResult.java
+++ b/src/main/java/org/jenkinsci/plugins/github/status/sources/misc/BetterThanOrEqualBuildResult.java
@@ -9,7 +9,7 @@
import org.kohsuke.stapler.DataBoundConstructor;
import org.kohsuke.stapler.DataBoundSetter;
-import javax.annotation.Nonnull;
+import edu.umd.cs.findbugs.annotations.NonNull;
import static hudson.model.Result.FAILURE;
import static hudson.model.Result.SUCCESS;
@@ -45,7 +45,7 @@ public String getResult() {
* @return matches if run result better than or equal to selected
*/
@Override
- public boolean matches(@Nonnull Run, ?> run) {
+ public boolean matches(@NonNull Run, ?> run) {
return defaultIfNull(run.getResult(), Result.NOT_BUILT).isBetterOrEqualTo(fromString(trimToEmpty(result)));
}
diff --git a/src/main/java/org/jenkinsci/plugins/github/util/BuildDataHelper.java b/src/main/java/org/jenkinsci/plugins/github/util/BuildDataHelper.java
index 118437ec8..b4a8e72bd 100644
--- a/src/main/java/org/jenkinsci/plugins/github/util/BuildDataHelper.java
+++ b/src/main/java/org/jenkinsci/plugins/github/util/BuildDataHelper.java
@@ -7,7 +7,7 @@
import hudson.plugins.git.util.BuildData;
import org.eclipse.jgit.lib.ObjectId;
-import javax.annotation.Nonnull;
+import edu.umd.cs.findbugs.annotations.NonNull;
import java.io.IOException;
import java.util.List;
import java.util.Set;
@@ -15,7 +15,7 @@
/**
* Stores common methods for {@link BuildData} handling.
*
- * @author Oleg Nenashev
+ * @author Oleg Nenashev
* @since 1.10
*/
public final class BuildDataHelper {
@@ -73,8 +73,8 @@ public static BuildData calculateBuildData(
* @return SHA1 of the las
* @throws IOException Cannot get the info about commit ID
*/
- @Nonnull
- public static ObjectId getCommitSHA1(@Nonnull Run, ?> build) throws IOException {
+ @NonNull
+ public static ObjectId getCommitSHA1(@NonNull Run, ?> build) throws IOException {
List buildDataList = build.getActions(BuildData.class);
Job, ?> parent = build.getParent();
diff --git a/src/main/java/org/jenkinsci/plugins/github/util/FluentIterableWrapper.java b/src/main/java/org/jenkinsci/plugins/github/util/FluentIterableWrapper.java
index 8babf4b23..4ccfcde28 100644
--- a/src/main/java/org/jenkinsci/plugins/github/util/FluentIterableWrapper.java
+++ b/src/main/java/org/jenkinsci/plugins/github/util/FluentIterableWrapper.java
@@ -26,10 +26,11 @@
import org.kohsuke.accmod.Restricted;
import org.kohsuke.accmod.restrictions.NoExternalUse;
-import javax.annotation.CheckReturnValue;
import java.util.Iterator;
import java.util.List;
+import edu.umd.cs.findbugs.annotations.CheckReturnValue;
+
import static com.google.common.base.Preconditions.checkNotNull;
/**
diff --git a/src/main/java/org/jenkinsci/plugins/github/util/JobInfoHelpers.java b/src/main/java/org/jenkinsci/plugins/github/util/JobInfoHelpers.java
index c935f2f43..eafbc2c39 100644
--- a/src/main/java/org/jenkinsci/plugins/github/util/JobInfoHelpers.java
+++ b/src/main/java/org/jenkinsci/plugins/github/util/JobInfoHelpers.java
@@ -13,12 +13,11 @@
import jenkins.model.ParameterizedJobMixIn;
import org.jenkinsci.plugins.github.extension.GHEventsSubscriber;
-import javax.annotation.CheckForNull;
+import edu.umd.cs.findbugs.annotations.CheckForNull;
import java.util.Collection;
import java.util.Map;
import static org.jenkinsci.plugins.github.extension.GHEventsSubscriber.isApplicableFor;
-import static org.jenkinsci.plugins.github.util.FluentIterableWrapper.from;
/**
* Utility class which holds converters or predicates (matchers) to filter or convert job lists
@@ -38,11 +37,7 @@ private JobInfoHelpers() {
* @return predicate with true on apply if job contains trigger of given class
*/
public static Predicate withTrigger(final Class extends Trigger> clazz) {
- return new Predicate() {
- public boolean apply(Item item) {
- return triggerFrom(item, clazz) != null;
- }
- };
+ return item -> triggerFrom(item, clazz) != null;
}
/**
@@ -51,22 +46,14 @@ public boolean apply(Item item) {
* @return predicate with true on apply if item is buildable
*/
public static Predicate isBuildable() {
- return new Predicate() {
- public boolean apply(ITEM item) {
- return item instanceof Job ? ((Job, ?>) item).isBuildable() : item instanceof BuildableItem;
- }
- };
+ return item -> item instanceof Job ? ((Job, ?>) item).isBuildable() : item instanceof BuildableItem;
}
/**
* @return function which helps to convert job to repo names associated with this job
*/
public static Function> associatedNames() {
- return new Function>() {
- public Collection apply(ITEM item) {
- return GitHubRepositoryNameContributor.parseAssociatedNames(item);
- }
- };
+ return GitHubRepositoryNameContributor::parseAssociatedNames;
}
/**
@@ -76,12 +63,7 @@ public Collection apply(ITEM item) {
* @return predicate with true if item alive and should have hook
*/
public static Predicate isAlive() {
- return new Predicate() {
- @Override
- public boolean apply(ITEM item) {
- return !from(GHEventsSubscriber.all()).filter(isApplicableFor(item)).toList().isEmpty();
- }
- };
+ return item -> GHEventsSubscriber.all().stream().anyMatch(isApplicableFor(item));
}
/**
diff --git a/src/main/java/org/jenkinsci/plugins/github/util/misc/NullSafeFunction.java b/src/main/java/org/jenkinsci/plugins/github/util/misc/NullSafeFunction.java
index 4ba1df548..3a0918247 100644
--- a/src/main/java/org/jenkinsci/plugins/github/util/misc/NullSafeFunction.java
+++ b/src/main/java/org/jenkinsci/plugins/github/util/misc/NullSafeFunction.java
@@ -2,7 +2,7 @@
import com.google.common.base.Function;
-import javax.annotation.Nonnull;
+import edu.umd.cs.findbugs.annotations.NonNull;
import static com.google.common.base.Preconditions.checkNotNull;
@@ -21,5 +21,5 @@ public T apply(F input) {
/**
* This method will be called inside of {@link #apply(Object)}
*/
- protected abstract T applyNullSafe(@Nonnull F input);
+ protected abstract T applyNullSafe(@NonNull F input);
}
diff --git a/src/main/java/org/jenkinsci/plugins/github/util/misc/NullSafePredicate.java b/src/main/java/org/jenkinsci/plugins/github/util/misc/NullSafePredicate.java
index 5e9987d7c..847753d59 100644
--- a/src/main/java/org/jenkinsci/plugins/github/util/misc/NullSafePredicate.java
+++ b/src/main/java/org/jenkinsci/plugins/github/util/misc/NullSafePredicate.java
@@ -2,7 +2,7 @@
import com.google.common.base.Predicate;
-import javax.annotation.Nonnull;
+import edu.umd.cs.findbugs.annotations.NonNull;
import static com.google.common.base.Preconditions.checkNotNull;
@@ -22,5 +22,5 @@ public boolean apply(T input) {
/**
* This method will be called inside of {@link #apply(Object)}
*/
- protected abstract boolean applyNullSafe(@Nonnull T input);
+ protected abstract boolean applyNullSafe(@NonNull T input);
}
diff --git a/src/main/java/org/jenkinsci/plugins/github/webhook/GHEventHeader.java b/src/main/java/org/jenkinsci/plugins/github/webhook/GHEventHeader.java
index b17f82116..71d19fed6 100644
--- a/src/main/java/org/jenkinsci/plugins/github/webhook/GHEventHeader.java
+++ b/src/main/java/org/jenkinsci/plugins/github/webhook/GHEventHeader.java
@@ -3,10 +3,10 @@
import org.kohsuke.github.GHEvent;
import org.kohsuke.stapler.AnnotationHandler;
import org.kohsuke.stapler.InjectedParameter;
-import org.kohsuke.stapler.StaplerRequest;
+import org.kohsuke.stapler.StaplerRequest2;
import org.slf4j.Logger;
-import javax.servlet.ServletException;
+import jakarta.servlet.ServletException;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
@@ -42,7 +42,7 @@ class PayloadHandler extends AnnotationHandler {
* @return parsed {@link GHEvent} or null on empty header or unknown value
*/
@Override
- public Object parse(StaplerRequest req, GHEventHeader a, Class type, String param) throws ServletException {
+ public Object parse(StaplerRequest2 req, GHEventHeader a, Class type, String param) throws ServletException {
isTrue(GHEvent.class.isAssignableFrom(type),
"Parameter '%s' should has type %s, not %s", param,
GHEvent.class.getName(),
diff --git a/src/main/java/org/jenkinsci/plugins/github/webhook/GHEventPayload.java b/src/main/java/org/jenkinsci/plugins/github/webhook/GHEventPayload.java
index 51e5ecb62..f7f192503 100644
--- a/src/main/java/org/jenkinsci/plugins/github/webhook/GHEventPayload.java
+++ b/src/main/java/org/jenkinsci/plugins/github/webhook/GHEventPayload.java
@@ -8,11 +8,11 @@
import org.jenkinsci.plugins.github.util.misc.NullSafeFunction;
import org.kohsuke.stapler.AnnotationHandler;
import org.kohsuke.stapler.InjectedParameter;
-import org.kohsuke.stapler.StaplerRequest;
+import org.kohsuke.stapler.StaplerRequest2;
import org.slf4j.Logger;
-import javax.annotation.Nonnull;
-import javax.servlet.ServletException;
+import edu.umd.cs.findbugs.annotations.NonNull;
+import jakarta.servlet.ServletException;
import java.io.IOException;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
@@ -46,8 +46,8 @@ class PayloadHandler extends AnnotationHandler {
*
* @see Developer manual
*/
- private static final Map> PAYLOAD_PROCESS =
- ImmutableMap.>builder()
+ private static final Map> PAYLOAD_PROCESS =
+ ImmutableMap.>builder()
.put(APPLICATION_JSON, fromApplicationJson())
.put(FORM_URLENCODED, fromForm())
.build();
@@ -58,8 +58,8 @@ class PayloadHandler extends AnnotationHandler {
* @return String payload extracted from request or null on any problem
*/
@Override
- public Object parse(StaplerRequest req, GHEventPayload a, Class type, String param) throws ServletException {
- if (notNull(req, "Why StaplerRequest is null?").getHeader(GitHubWebHook.URL_VALIDATION_HEADER) != null) {
+ public Object parse(StaplerRequest2 req, GHEventPayload a, Class type, String param) throws ServletException {
+ if (notNull(req, "Why StaplerRequest2 is null?").getHeader(GitHubWebHook.URL_VALIDATION_HEADER) != null) {
// if self test for custom hook url
return null;
}
@@ -82,10 +82,10 @@ public Object parse(StaplerRequest req, GHEventPayload a, Class type, String par
*
* @return function to extract payload from form request parameters
*/
- protected static Function fromForm() {
- return new NullSafeFunction() {
+ protected static Function fromForm() {
+ return new NullSafeFunction() {
@Override
- protected String applyNullSafe(@Nonnull StaplerRequest request) {
+ protected String applyNullSafe(@NonNull StaplerRequest2 request) {
return request.getParameter("payload");
}
};
@@ -96,10 +96,10 @@ protected String applyNullSafe(@Nonnull StaplerRequest request) {
*
* @return function to extract payload from body
*/
- protected static Function fromApplicationJson() {
- return new NullSafeFunction() {
+ protected static Function fromApplicationJson() {
+ return new NullSafeFunction() {
@Override
- protected String applyNullSafe(@Nonnull StaplerRequest request) {
+ protected String applyNullSafe(@NonNull StaplerRequest2 request) {
try {
return IOUtils.toString(request.getInputStream(), Charsets.UTF_8);
} catch (IOException e) {
diff --git a/src/main/java/org/jenkinsci/plugins/github/webhook/GHWebhookSignature.java b/src/main/java/org/jenkinsci/plugins/github/webhook/GHWebhookSignature.java
index 5d434a682..491223c76 100644
--- a/src/main/java/org/jenkinsci/plugins/github/webhook/GHWebhookSignature.java
+++ b/src/main/java/org/jenkinsci/plugins/github/webhook/GHWebhookSignature.java
@@ -2,13 +2,14 @@
import hudson.util.Secret;
import org.apache.commons.codec.binary.Hex;
-import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
+import java.security.MessageDigest;
+
import static com.google.common.base.Preconditions.checkNotNull;
import static java.nio.charset.StandardCharsets.UTF_8;
@@ -22,6 +23,7 @@ public class GHWebhookSignature {
private static final Logger LOGGER = LoggerFactory.getLogger(GHWebhookSignature.class);
private static final String HMAC_SHA1_ALGORITHM = "HmacSHA1";
+ private static final String HMAC_SHA256_ALGORITHM = "HmacSHA256";
public static final String INVALID_SIGNATURE = "COMPUTED_INVALID_SIGNATURE";
private final String payload;
@@ -46,19 +48,42 @@ public static GHWebhookSignature webhookSignature(String payload, Secret secret)
/**
* Computes a RFC 2104-compliant HMAC digest using SHA1 of a payloadFrom with a given key (secret).
*
+ * @deprecated Use {@link #sha256()} for enhanced security
* @return HMAC digest of payloadFrom using secret as key. Will return COMPUTED_INVALID_SIGNATURE
* on any exception during computation.
*/
+ @Deprecated
public String sha1() {
+ return computeSignature(HMAC_SHA1_ALGORITHM);
+ }
+
+ /**
+ * Computes a RFC 2104-compliant HMAC digest using SHA256 of a payload with a given key (secret).
+ * This is the recommended method for webhook signature validation.
+ *
+ * @return HMAC digest of payload using secret as key. Will return COMPUTED_INVALID_SIGNATURE
+ * on any exception during computation.
+ * @since 1.45.0
+ */
+ public String sha256() {
+ return computeSignature(HMAC_SHA256_ALGORITHM);
+ }
+ /**
+ * Computes HMAC signature using the specified algorithm.
+ *
+ * @param algorithm The HMAC algorithm to use (e.g., "HmacSHA1", "HmacSHA256")
+ * @return HMAC digest as hex string, or INVALID_SIGNATURE on error
+ */
+ private String computeSignature(String algorithm) {
try {
- final SecretKeySpec keySpec = new SecretKeySpec(secret.getPlainText().getBytes(UTF_8), HMAC_SHA1_ALGORITHM);
- final Mac mac = Mac.getInstance(HMAC_SHA1_ALGORITHM);
+ final SecretKeySpec keySpec = new SecretKeySpec(secret.getPlainText().getBytes(UTF_8), algorithm);
+ final Mac mac = Mac.getInstance(algorithm);
mac.init(keySpec);
final byte[] rawHMACBytes = mac.doFinal(payload.getBytes(UTF_8));
return Hex.encodeHexString(rawHMACBytes);
} catch (Exception e) {
- LOGGER.error("", e);
+ LOGGER.error("Error computing {} signature", algorithm, e);
return INVALID_SIGNATURE;
}
}
@@ -67,10 +92,45 @@ public String sha1() {
* @param digest computed signature from external place (GitHub)
*
* @return true if computed and provided signatures identical
+ * @deprecated Use {@link #matches(String, SignatureAlgorithm)} for explicit algorithm selection
*/
+ @Deprecated
public boolean matches(String digest) {
- String computed = sha1();
- LOGGER.trace("Signature: calculated={} provided={}", computed, digest);
- return StringUtils.equals(computed, digest);
+ return matches(digest, SignatureAlgorithm.SHA1);
+ }
+
+ /**
+ * Validates a signature using the specified algorithm.
+ * Uses constant-time comparison to prevent timing attacks.
+ *
+ * @param digest the signature to validate (without algorithm prefix)
+ * @param algorithm the signature algorithm to use
+ * @return true if computed and provided signatures match
+ * @since 1.45.0
+ */
+ public boolean matches(String digest, SignatureAlgorithm algorithm) {
+ String computed;
+ switch (algorithm) {
+ case SHA256:
+ computed = sha256();
+ break;
+ case SHA1:
+ computed = sha1();
+ break;
+ default:
+ LOGGER.warn("Unsupported signature algorithm: {}", algorithm);
+ return false;
+ }
+
+ LOGGER.trace("Signature validation: algorithm={} calculated={} provided={}",
+ algorithm, computed, digest);
+ if (digest == null && computed == null) {
+ return true;
+ } else if (digest == null || computed == null) {
+ return false;
+ } else {
+ // Use constant-time comparison to prevent timing attacks
+ return MessageDigest.isEqual(computed.getBytes(UTF_8), digest.getBytes(UTF_8));
+ }
}
}
diff --git a/src/main/java/org/jenkinsci/plugins/github/webhook/RequirePostWithGHHookPayload.java b/src/main/java/org/jenkinsci/plugins/github/webhook/RequirePostWithGHHookPayload.java
index 5ff8c790a..9a36c06f7 100644
--- a/src/main/java/org/jenkinsci/plugins/github/webhook/RequirePostWithGHHookPayload.java
+++ b/src/main/java/org/jenkinsci/plugins/github/webhook/RequirePostWithGHHookPayload.java
@@ -10,14 +10,14 @@
import org.jenkinsci.plugins.github.util.FluentIterableWrapper;
import org.kohsuke.github.GHEvent;
import org.kohsuke.stapler.HttpResponses;
-import org.kohsuke.stapler.StaplerRequest;
-import org.kohsuke.stapler.StaplerResponse;
+import org.kohsuke.stapler.StaplerRequest2;
+import org.kohsuke.stapler.StaplerResponse2;
import org.kohsuke.stapler.interceptor.Interceptor;
import org.kohsuke.stapler.interceptor.InterceptorAnnotation;
import org.slf4j.Logger;
-import javax.servlet.ServletException;
-import javax.servlet.http.HttpServletResponse;
+import jakarta.servlet.ServletException;
+import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.lang.annotation.Retention;
@@ -27,8 +27,6 @@
import java.nio.charset.StandardCharsets;
import java.security.interfaces.RSAPublicKey;
import java.util.List;
-import java.util.Objects;
-import java.util.stream.Collectors;
import static com.cloudbees.jenkins.GitHubWebHook.X_INSTANCE_IDENTITY;
import static com.google.common.base.Charsets.UTF_8;
@@ -37,8 +35,8 @@
import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
-import static javax.servlet.http.HttpServletResponse.SC_BAD_REQUEST;
-import static javax.servlet.http.HttpServletResponse.SC_METHOD_NOT_ALLOWED;
+import static jakarta.servlet.http.HttpServletResponse.SC_BAD_REQUEST;
+import static jakarta.servlet.http.HttpServletResponse.SC_METHOD_NOT_ALLOWED;
import static org.apache.commons.codec.binary.Base64.encodeBase64;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
import static org.apache.commons.lang3.StringUtils.substringAfter;
@@ -61,15 +59,26 @@
class Processor extends Interceptor {
private static final Logger LOGGER = getLogger(Processor.class);
/**
- * Header key being used for the payload signatures.
+ * Header key being used for the legacy SHA-1 payload signatures.
*
* @see Developer manual
+ * @deprecated Use SHA-256 signatures with X-Hub-Signature-256 header
*/
+ @Deprecated
public static final String SIGNATURE_HEADER = "X-Hub-Signature";
- private static final String SHA1_PREFIX = "sha1=";
+ /**
+ * Header key being used for the SHA-256 payload signatures (recommended).
+ *
+ * @see
+ * GitHub Documentation
+ * @since 1.45.0
+ */
+ public static final String SIGNATURE_HEADER_SHA256 = "X-Hub-Signature-256";
+ public static final String SHA1_PREFIX = "sha1=";
+ public static final String SHA256_PREFIX = "sha256=";
@Override
- public Object invoke(StaplerRequest req, StaplerResponse rsp, Object instance, Object[] arguments)
+ public Object invoke(StaplerRequest2 req, StaplerResponse2 rsp, Object instance, Object[] arguments)
throws IllegalAccessException, InvocationTargetException, ServletException {
shouldBePostMethod(req);
@@ -81,13 +90,13 @@ public Object invoke(StaplerRequest req, StaplerResponse rsp, Object instance, O
}
/**
- * Duplicates {@link @org.kohsuke.stapler.interceptor.RequirePOST} precheck.
+ * Duplicates {@link org.kohsuke.stapler.interceptor.RequirePOST} precheck.
* As of it can't guarantee order of multiply interceptor calls,
* it should implement all features of required interceptors in one class
*
* @throws InvocationTargetException if method os not POST
*/
- protected void shouldBePostMethod(StaplerRequest request) throws InvocationTargetException {
+ protected void shouldBePostMethod(StaplerRequest2 request) throws InvocationTargetException {
if (!request.getMethod().equals("POST")) {
throw new InvocationTargetException(error(SC_METHOD_NOT_ALLOWED, "Method POST required"));
}
@@ -96,12 +105,12 @@ protected void shouldBePostMethod(StaplerRequest request) throws InvocationTarge
/**
* Used for {@link GitHubPluginConfig#doCheckHookUrl(String)}}
*/
- protected void returnsInstanceIdentityIfLocalUrlTest(StaplerRequest req) throws InvocationTargetException {
+ protected void returnsInstanceIdentityIfLocalUrlTest(StaplerRequest2 req) throws InvocationTargetException {
if (req.getHeader(GitHubWebHook.URL_VALIDATION_HEADER) != null) {
// when the configuration page provides the self-check button, it makes a request with this header.
throw new InvocationTargetException(new HttpResponses.HttpResponseException() {
@Override
- public void generateResponse(StaplerRequest req, StaplerResponse rsp, Object node)
+ public void generateResponse(StaplerRequest2 req, StaplerResponse2 rsp, Object node)
throws IOException, ServletException {
RSAPublicKey key = new InstanceIdentity().getPublic();
rsp.setStatus(HttpServletResponse.SC_OK);
@@ -139,24 +148,66 @@ protected void shouldContainParseablePayload(Object[] arguments) throws Invocati
* if a hook secret is specified in the GitHub plugin config.
* If no hook secret is configured, then the signature is ignored.
*
+ * Uses the configured signature algorithm (SHA-256 by default, SHA-1 for legacy support).
+ *
* @param req Incoming request.
* @throws InvocationTargetException if any of preconditions is not satisfied
*/
- protected void shouldProvideValidSignature(StaplerRequest req, Object[] args) throws InvocationTargetException {
- List secrets = GitHubPlugin.configuration().getHookSecretConfigs().stream().
- map(HookSecretConfig::getHookSecret).filter(Objects::nonNull).collect(Collectors.toList());
-
- if (!secrets.isEmpty()) {
- Optional signHeader = Optional.fromNullable(req.getHeader(SIGNATURE_HEADER));
- isTrue(signHeader.isPresent(), "Signature was expected, but not provided");
-
- String digest = substringAfter(signHeader.get(), SHA1_PREFIX);
- LOGGER.trace("Trying to verify sign from header {}", signHeader.get());
- isTrue(
- secrets.stream().anyMatch(secret ->
- GHWebhookSignature.webhookSignature(payloadFrom(req, args), secret).matches(digest)),
- String.format("Provided signature [%s] did not match to calculated", digest)
- );
+ protected void shouldProvideValidSignature(StaplerRequest2 req, Object[] args)
+ throws InvocationTargetException {
+ List secretConfigs = GitHubPlugin.configuration().getHookSecretConfigs();
+
+ if (!secretConfigs.isEmpty()) {
+ boolean validSignatureFound = false;
+
+ for (HookSecretConfig config : secretConfigs) {
+ Secret secret = config.getHookSecret();
+ if (secret == null) {
+ continue;
+ }
+
+ SignatureAlgorithm algorithm = config.getSignatureAlgorithm();
+ String headerName = algorithm.getHeaderName();
+ String expectedPrefix = algorithm.getSignaturePrefix();
+
+ Optional signHeader = Optional.fromNullable(req.getHeader(headerName));
+ if (!signHeader.isPresent()) {
+ LOGGER.debug("No signature header {} found for algorithm {}", headerName, algorithm);
+ continue;
+ }
+
+ String fullSignature = signHeader.get();
+ if (!fullSignature.startsWith(expectedPrefix)) {
+ LOGGER.debug("Signature header {} does not start with expected prefix {}",
+ fullSignature, expectedPrefix);
+ continue;
+ }
+
+ String digest = substringAfter(fullSignature, expectedPrefix);
+ LOGGER.trace("Verifying {} signature from header {}", algorithm, fullSignature);
+
+ boolean isValid = GHWebhookSignature.webhookSignature(payloadFrom(req, args), secret)
+ .matches(digest, algorithm);
+
+ if (isValid) {
+ validSignatureFound = true;
+ // Log deprecation warning for SHA-1 usage
+ if (algorithm == SignatureAlgorithm.SHA1) {
+ LOGGER.warn("Using deprecated SHA-1 signature validation. "
+ + "Consider upgrading webhook configuration to use SHA-256 "
+ + "for enhanced security.");
+ } else {
+ LOGGER.debug("Successfully validated {} signature", algorithm);
+ }
+ break;
+ } else {
+ LOGGER.debug("Signature validation failed for algorithm {}", algorithm);
+ }
+ }
+
+ isTrue(validSignatureFound,
+ "No valid signature found. Ensure webhook is configured with a supported signature algorithm "
+ + "(SHA-256 recommended, SHA-1 for legacy compatibility).");
}
}
@@ -166,7 +217,7 @@ protected void shouldProvideValidSignature(StaplerRequest req, Object[] args) th
*
* @return ready-to-hash payload
*/
- protected String payloadFrom(StaplerRequest req, Object[] args) {
+ protected String payloadFrom(StaplerRequest2 req, Object[] args) {
final String parsedPayload = (String) args[1];
if (req.getContentType().equals(GHEventPayload.PayloadHandler.APPLICATION_JSON)) {
diff --git a/src/main/java/org/jenkinsci/plugins/github/webhook/SignatureAlgorithm.java b/src/main/java/org/jenkinsci/plugins/github/webhook/SignatureAlgorithm.java
new file mode 100644
index 000000000..6668f6e81
--- /dev/null
+++ b/src/main/java/org/jenkinsci/plugins/github/webhook/SignatureAlgorithm.java
@@ -0,0 +1,98 @@
+package org.jenkinsci.plugins.github.webhook;
+
+/**
+ * Enumeration of supported webhook signature algorithms.
+ *
+ * @since 1.45.0
+ */
+public enum SignatureAlgorithm {
+ /**
+ * SHA-256 HMAC signature validation (recommended).
+ * Uses X-Hub-Signature-256 header with sha256= prefix.
+ */
+ SHA256("sha256", "X-Hub-Signature-256", "HmacSHA256"),
+
+ /**
+ * SHA-1 HMAC signature validation (legacy).
+ * Uses X-Hub-Signature header with sha1= prefix.
+ *
+ * @deprecated Use SHA256 for enhanced security
+ */
+ @Deprecated
+ SHA1("sha1", "X-Hub-Signature", "HmacSHA1");
+
+ private final String prefix;
+ private final String headerName;
+ private final String javaAlgorithm;
+
+ /**
+ * System property to override default signature algorithm.
+ * Set to "SHA1" to use legacy SHA-1 as default for backwards compatibility.
+ */
+ public static final String DEFAULT_ALGORITHM_PROPERTY = "jenkins.github.webhook.signature.default";
+
+ /**
+ * Gets the default algorithm for new configurations.
+ * Defaults to SHA-256 for security, but can be overridden via system property.
+ * This is evaluated dynamically to respect system property changes.
+ *
+ * @return the default algorithm based on current system property
+ */
+ public static SignatureAlgorithm getDefault() {
+ return getDefaultAlgorithm();
+ }
+
+ SignatureAlgorithm(String prefix, String headerName, String javaAlgorithm) {
+ this.prefix = prefix;
+ this.headerName = headerName;
+ this.javaAlgorithm = javaAlgorithm;
+ }
+
+ /**
+ * @return the prefix used in signature strings (e.g. "sha256", "sha1")
+ */
+ public String getPrefix() {
+ return prefix;
+ }
+
+ /**
+ * @return the HTTP header name for this algorithm
+ */
+ public String getHeaderName() {
+ return headerName;
+ }
+
+ /**
+ * @return the Java algorithm name for HMAC computation
+ */
+ public String getJavaAlgorithm() {
+ return javaAlgorithm;
+ }
+
+ /**
+ * @return the expected signature prefix including equals sign (e.g. "sha256=", "sha1=")
+ */
+ public String getSignaturePrefix() {
+ return prefix + "=";
+ }
+
+ /**
+ * Determines the default signature algorithm based on system property.
+ * Defaults to SHA-256 for security, but allows SHA-1 override for legacy environments.
+ *
+ * @return the default algorithm to use
+ */
+ private static SignatureAlgorithm getDefaultAlgorithm() {
+ String property = System.getProperty(DEFAULT_ALGORITHM_PROPERTY);
+ if (property == null || property.trim().isEmpty()) {
+ // No property set, use secure SHA-256 default
+ return SHA256;
+ }
+ try {
+ return SignatureAlgorithm.valueOf(property.trim().toUpperCase());
+ } catch (IllegalArgumentException e) {
+ // Invalid property value, default to secure SHA-256
+ return SHA256;
+ }
+ }
+}
diff --git a/src/main/java/org/jenkinsci/plugins/github/webhook/WebhookManager.java b/src/main/java/org/jenkinsci/plugins/github/webhook/WebhookManager.java
index 5db84fa3c..e809c8b05 100644
--- a/src/main/java/org/jenkinsci/plugins/github/webhook/WebhookManager.java
+++ b/src/main/java/org/jenkinsci/plugins/github/webhook/WebhookManager.java
@@ -6,7 +6,7 @@
import hudson.model.Item;
import hudson.model.Job;
import hudson.util.Secret;
-import org.apache.commons.lang.Validate;
+import org.apache.commons.lang3.Validate;
import org.jenkinsci.plugins.github.GitHubPlugin;
import org.jenkinsci.plugins.github.admin.GitHubHookRegisterProblemMonitor;
import org.jenkinsci.plugins.github.config.HookSecretConfig;
@@ -21,7 +21,7 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import javax.annotation.Nonnull;
+import edu.umd.cs.findbugs.annotations.NonNull;
import java.io.IOException;
import java.net.URL;
import java.util.Collection;
@@ -174,7 +174,7 @@ private GHRepository repoWithWebhookAccess(GitHubRepositoryName name) {
com.google.common.base.Optional repoWithAdminAccess = reposAllowedtoManageWebhooks
.firstMatch(withAdminAccess());
if (!repoWithAdminAccess.isPresent()) {
- LOGGER.debug("None of the github repos configured have admin access for: {}", name);
+ LOGGER.info("None of the github repos configured have admin access for: {}", name);
return null;
}
GHRepository repo = repoWithAdminAccess.get();
@@ -192,7 +192,7 @@ private GHRepository repoWithWebhookAccess(GitHubRepositoryName name) {
protected Function createHookSubscribedTo(final List events) {
return new NullSafeFunction() {
@Override
- protected GHHook applyNullSafe(@Nonnull GitHubRepositoryName name) {
+ protected GHHook applyNullSafe(@NonNull GitHubRepositoryName name) {
try {
GHRepository repo = repoWithWebhookAccess(name);
if (repo == null) {
@@ -239,7 +239,7 @@ protected GHHook applyNullSafe(@Nonnull GitHubRepositoryName name) {
protected Predicate log(final String format) {
return new NullSafePredicate() {
@Override
- protected boolean applyNullSafe(@Nonnull GHHook input) {
+ protected boolean applyNullSafe(@NonNull GHHook input) {
LOGGER.debug(format("%s {} (events: {})", format), input.getUrl(), input.getEvents());
return true;
}
@@ -254,7 +254,7 @@ protected boolean applyNullSafe(@Nonnull GHHook input) {
protected Predicate withAdminAccess() {
return new NullSafePredicate() {
@Override
- protected boolean applyNullSafe(@Nonnull GHRepository repo) {
+ protected boolean applyNullSafe(@NonNull GHRepository repo) {
return repo.hasAdminAccess();
}
};
@@ -269,7 +269,7 @@ protected boolean applyNullSafe(@Nonnull GHRepository repo) {
*/
protected Predicate serviceWebhookFor(final URL url) {
return new NullSafePredicate() {
- protected boolean applyNullSafe(@Nonnull GHHook hook) {
+ protected boolean applyNullSafe(@NonNull GHHook hook) {
return hook.getName().equals("jenkins")
&& hook.getConfig().get("jenkins_hook_url").equals(url.toExternalForm());
}
@@ -285,7 +285,7 @@ protected boolean applyNullSafe(@Nonnull GHHook hook) {
*/
protected Predicate webhookFor(final URL url) {
return new NullSafePredicate() {
- protected boolean applyNullSafe(@Nonnull GHHook hook) {
+ protected boolean applyNullSafe(@NonNull GHHook hook) {
return hook.getName().equals("web")
&& hook.getConfig().get("url").equals(url.toExternalForm());
}
@@ -298,7 +298,7 @@ protected boolean applyNullSafe(@Nonnull GHHook hook) {
protected Function> eventsFromHook() {
return new NullSafeFunction>() {
@Override
- protected Iterable applyNullSafe(@Nonnull GHHook input) {
+ protected Iterable applyNullSafe(@NonNull GHHook input) {
return input.getEvents();
}
};
@@ -314,7 +314,7 @@ protected Iterable applyNullSafe(@Nonnull GHHook input) {
protected Function> fetchHooks() {
return new NullSafeFunction>() {
@Override
- protected List applyNullSafe(@Nonnull GHRepository repo) {
+ protected List applyNullSafe(@NonNull GHRepository repo) {
try {
return repo.getHooks();
} catch (IOException e) {
@@ -332,7 +332,7 @@ protected List applyNullSafe(@Nonnull GHRepository repo) {
*/
protected Function createWebhook(final URL url, final Set events) {
return new NullSafeFunction() {
- protected GHHook applyNullSafe(@Nonnull GHRepository repo) {
+ protected GHHook applyNullSafe(@NonNull GHRepository repo) {
try {
final HashMap config = new HashMap<>();
config.put("url", url.toExternalForm());
@@ -359,7 +359,7 @@ protected GHHook applyNullSafe(@Nonnull GHRepository repo) {
*/
protected Predicate deleteWebhook() {
return new NullSafePredicate() {
- protected boolean applyNullSafe(@Nonnull GHHook hook) {
+ protected boolean applyNullSafe(@NonNull GHHook hook) {
try {
hook.delete();
return true;
diff --git a/src/main/java/org/jenkinsci/plugins/github/webhook/subscriber/DefaultPushGHEventSubscriber.java b/src/main/java/org/jenkinsci/plugins/github/webhook/subscriber/DefaultPushGHEventSubscriber.java
index 7568af0e9..95180fddb 100644
--- a/src/main/java/org/jenkinsci/plugins/github/webhook/subscriber/DefaultPushGHEventSubscriber.java
+++ b/src/main/java/org/jenkinsci/plugins/github/webhook/subscriber/DefaultPushGHEventSubscriber.java
@@ -73,25 +73,10 @@ protected void onEvent(final GHSubscriberEvent event) {
LOGGER.warn("Received malformed PushEvent: " + event.getPayload(), e);
return;
}
- URL repoUrl = push.getRepository().getUrl();
+ URL htmlUrl = push.getRepository().getHtmlUrl();
final String pusherName = push.getPusher().getName();
- LOGGER.info("Received PushEvent for {} from {}", repoUrl, event.getOrigin());
- GitHubRepositoryName fromEventRepository = GitHubRepositoryName.create(repoUrl.toExternalForm());
-
- if (fromEventRepository == null) {
- // On push event on github.com url === html_url
- // this is not consistent with the API docs and with hosted repositories
- // see https://goo.gl/c1qmY7
- // let's retry with 'html_url'
- URL htmlUrl = push.getRepository().getHtmlUrl();
- fromEventRepository = GitHubRepositoryName.create(htmlUrl.toExternalForm());
- if (fromEventRepository != null) {
- LOGGER.debug("PushEvent handling: 'html_url' field "
- + "has been used to retrieve project information (instead of default 'url' field)");
- }
- }
-
- final GitHubRepositoryName changedRepository = fromEventRepository;
+ LOGGER.info("Received PushEvent for {} from {}", htmlUrl, event.getOrigin());
+ final GitHubRepositoryName changedRepository = GitHubRepositoryName.create(htmlUrl.toExternalForm());
if (changedRepository != null) {
// run in high privilege to see all the projects anonymous users don't see.
@@ -128,7 +113,7 @@ public void run() {
}
} else {
- LOGGER.warn("Malformed repo url {}", repoUrl);
+ LOGGER.warn("Malformed repo html url {}", htmlUrl);
}
}
}
diff --git a/src/main/java/org/jenkinsci/plugins/github/webhook/subscriber/PingGHEventSubscriber.java b/src/main/java/org/jenkinsci/plugins/github/webhook/subscriber/PingGHEventSubscriber.java
index 0d2cbe359..bc7141bf0 100644
--- a/src/main/java/org/jenkinsci/plugins/github/webhook/subscriber/PingGHEventSubscriber.java
+++ b/src/main/java/org/jenkinsci/plugins/github/webhook/subscriber/PingGHEventSubscriber.java
@@ -6,7 +6,7 @@
import java.io.IOException;
import java.io.StringReader;
import java.util.Set;
-import javax.inject.Inject;
+import jakarta.inject.Inject;
import org.jenkinsci.plugins.github.admin.GitHubHookRegisterProblemMonitor;
import org.jenkinsci.plugins.github.extension.GHEventsSubscriber;
import org.kohsuke.github.GHEvent;
diff --git a/src/main/resources/com/cloudbees/jenkins/GitHubPushTrigger/config.groovy b/src/main/resources/com/cloudbees/jenkins/GitHubPushTrigger/config.groovy
index c9a140f5c..768800958 100644
--- a/src/main/resources/com/cloudbees/jenkins/GitHubPushTrigger/config.groovy
+++ b/src/main/resources/com/cloudbees/jenkins/GitHubPushTrigger/config.groovy
@@ -4,17 +4,14 @@ import com.cloudbees.jenkins.GitHubPushTrigger
tr {
td(colspan: 4) {
- div(id: 'gh-hooks-warn')
+ def url = descriptor.getCheckMethod('hookRegistered').toCheckUrl()
+ def input = "input[name='${GitHubPushTrigger.class.getName().replace('.', '-')}']"
+
+ div(id: 'gh-hooks-warn',
+ 'data-url': url,
+ 'data-input': input
+ )
}
}
script(src:"${rootURL}${h.getResourcePath()}/plugin/github/js/warning.js")
-script {
- text("""
-InlineWarning.setup({
- id: 'gh-hooks-warn',
- url: ${descriptor.getCheckMethod('hookRegistered').toCheckUrl()},
- input: 'input[name="${GitHubPushTrigger.class.getName().replace(".", "-")}"]'
-}).start();
-""")
-}
diff --git a/src/main/resources/com/cloudbees/jenkins/GitHubPushTrigger/help.html b/src/main/resources/com/cloudbees/jenkins/GitHubPushTrigger/help.html
index 7a24dd67a..b1d61d307 100644
--- a/src/main/resources/com/cloudbees/jenkins/GitHubPushTrigger/help.html
+++ b/src/main/resources/com/cloudbees/jenkins/GitHubPushTrigger/help.html
@@ -1,2 +1,6 @@
-If Jenkins will receive PUSH GitHub hook from repo defined in Git SCM section it
-will trigger Git SCM polling logic. So polling logic in fact belongs to Git SCM.
+When Jenkins receives a GitHub push hook, GitHub Plugin checks to see
+whether the hook came from a GitHub repository which matches the Git repository defined in SCM/Git section of this job.
+If they match and this option is enabled, GitHub Plugin triggers a one-time polling on GITScm.
+When GITScm polls GitHub, it finds that there is a change and initiates a build.
+The last sentence describes the behavior of Git plugin,
+thus the polling and initiating the build is not a part of GitHub plugin.
diff --git a/src/main/resources/images/symbols/logo-github.svg b/src/main/resources/images/symbols/logo-github.svg
new file mode 100644
index 000000000..4c15b0297
--- /dev/null
+++ b/src/main/resources/images/symbols/logo-github.svg
@@ -0,0 +1 @@
+
diff --git a/src/main/resources/org/jenkinsci/plugins/github/Messages.properties b/src/main/resources/org/jenkinsci/plugins/github/Messages.properties
index 7263d17ac..509773102 100644
--- a/src/main/resources/org/jenkinsci/plugins/github/Messages.properties
+++ b/src/main/resources/org/jenkinsci/plugins/github/Messages.properties
@@ -11,3 +11,9 @@ github.trigger.check.method.warning.details=The webhook for repo {0}/{1} on {2}
More info can be found on the global configuration page. This message will be dismissed if Jenkins receives \
a PING event from repo webhook or if you add the repo to the ignore list in the global configuration.
unknown.error=Unknown error
+duplicate.events.administrative.monitor.displayname=GitHub Duplicate Events
+duplicate.events.administrative.monitor.description=Warns about duplicate events received from GitHub.
+duplicate.events.administrative.monitor.blurb=Duplicate events were received from GitHub, possibly due to \
+ misconfiguration (e.g., multiple webhooks targeting the same Jenkins controller at the repository or organization \
+ level), potentially causing redundant builds or at least wasted work. \
+ Click here to inspect the last tracked duplicate event payload.
diff --git a/src/main/resources/org/jenkinsci/plugins/github/admin/GitHubDuplicateEventsMonitor/description.jelly b/src/main/resources/org/jenkinsci/plugins/github/admin/GitHubDuplicateEventsMonitor/description.jelly
new file mode 100644
index 000000000..11cde3e78
--- /dev/null
+++ b/src/main/resources/org/jenkinsci/plugins/github/admin/GitHubDuplicateEventsMonitor/description.jelly
@@ -0,0 +1,4 @@
+
+
+
+
diff --git a/src/main/resources/org/jenkinsci/plugins/github/admin/GitHubDuplicateEventsMonitor/message.jelly b/src/main/resources/org/jenkinsci/plugins/github/admin/GitHubDuplicateEventsMonitor/message.jelly
new file mode 100644
index 000000000..d67740516
--- /dev/null
+++ b/src/main/resources/org/jenkinsci/plugins/github/admin/GitHubDuplicateEventsMonitor/message.jelly
@@ -0,0 +1,9 @@
+
+
+
Choose the signature algorithm for webhook validation:
+
+
SHA-256 (Recommended): Modern, secure HMAC signature validation using the
+ X-Hub-Signature-256 header. This is GitHub's recommended approach for enhanced security.
+
SHA-1 (Legacy): Legacy HMAC signature validation using the
+ X-Hub-Signature header. Only use this for existing webhooks during migration period.
+
+
Note: When changing algorithms, ensure your GitHub webhook configuration uses the corresponding
+ signature header (X-Hub-Signature-256 for SHA-256 or X-Hub-Signature for SHA-1).
+
System Property Override: The default algorithm can be overridden using the system property
+ -Djenkins.github.webhook.signature.default=SHA1 for backwards compatibility with legacy CI environments.