Skip to content

Commit 696a163

Browse files
Merge branch 'master' into navigate-back-to-build-artifact
2 parents e26bd8b + 67db131 commit 696a163

File tree

39 files changed

+1323
-162
lines changed

39 files changed

+1323
-162
lines changed

.github/workflows/changelog.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,15 +44,15 @@ jobs:
4444
runs-on: ubuntu-latest
4545
if: github.repository_owner == 'jenkinsci'
4646
steps:
47-
- uses: actions/create-github-app-token@7e473efe3cb98aa54f8d4bac15400b15fad77d94 # v2.2.0
47+
- uses: actions/create-github-app-token@29824e69f54612133e76f7eaac726eef6c875baf # v2.2.1
4848
id: generate-token
4949
with:
5050
app-id: ${{ secrets.JENKINS_CHANGELOG_UPDATER_APP_ID }}
5151
private-key: ${{ secrets.JENKINS_CHANGELOG_UPDATER_PRIVATE_KEY }}
5252
owner: jenkins-infra
5353
repositories: jenkins.io
5454
- name: Check out
55-
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
55+
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
5656
with:
5757
fetch-depth: 0
5858
- name: Publish jenkins.io changelog draft

.github/workflows/publish-release-artifact.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,9 @@ jobs:
1616
is-lts: ${{ steps.set-version.outputs.is-lts }}
1717
is-rc: ${{ steps.set-version.outputs.is-rc }}
1818
steps:
19-
- uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
19+
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
2020
- name: Set up JDK 21
21-
uses: actions/setup-java@4e7e684fbb6e33f88ecb2cf1e6b3797739cf499b #v 5.0.0
21+
uses: actions/setup-java@f2beeb24e141e01a676f977032f5a29d81c9e27e #v 5.0.0
2222
with:
2323
distribution: "temurin"
2424
java-version: 21

.github/workflows/run-since-updater.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ jobs:
1414
runs-on: ubuntu-latest
1515
if: ${{ github.repository_owner == 'jenkinsci' }}
1616
steps:
17-
- uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
17+
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
1818
with:
1919
fetch-depth: 0
2020
- name: Run update-since-todo.py
@@ -29,7 +29,7 @@ jobs:
2929
id: run_script
3030
shell: bash
3131
- name: Create Pull Request
32-
uses: peter-evans/create-pull-request@84ae59a2cdc2258d6fa0732dd66352dddae2a412 # v7.0.9
32+
uses: peter-evans/create-pull-request@22a9089034f40e5a961c8808d113e2c98fb63676 # v7.0.11
3333
with:
3434
token: ${{ secrets.GITHUB_TOKEN }}
3535
commit-message: Fill in since annotations

bom/pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,7 @@ THE SOFTWARE.
119119
<dependency>
120120
<groupId>commons-io</groupId>
121121
<artifactId>commons-io</artifactId>
122-
<version>2.20.0</version>
122+
<version>2.21.0</version>
123123
</dependency>
124124
<dependency>
125125
<groupId>commons-lang</groupId>

cli/pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@
5353
<dependency>
5454
<groupId>org.bouncycastle</groupId>
5555
<artifactId>bcprov-jdk18on</artifactId>
56-
<version>1.82</version>
56+
<version>1.83</version>
5757
<optional>true</optional>
5858
</dependency>
5959
<dependency>

cli/src/main/java/hudson/cli/PlainCLIProtocol.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -153,15 +153,14 @@ public void run() {
153153
}
154154
} catch (ClosedChannelException x) {
155155
LOGGER.log(Level.FINE, null, x);
156-
side.handleClose();
157156
} catch (IOException x) {
158157
LOGGER.log(Level.WARNING, null, flightRecorder.analyzeCrash(x, "broken stream"));
159158
} catch (ReadPendingException x) {
160159
// in case trick in CLIAction does not work
161160
LOGGER.log(Level.FINE, null, x);
162-
side.handleClose();
163161
} catch (RuntimeException x) {
164162
LOGGER.log(Level.WARNING, null, x);
163+
} finally {
165164
side.handleClose();
166165
}
167166
}

core/src/main/java/hudson/Functions.java

Lines changed: 38 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,12 @@ public class Functions {
203203
private static final AtomicLong iota = new AtomicLong();
204204
private static Logger LOGGER = Logger.getLogger(Functions.class.getName());
205205

206+
/**
207+
* Escape hatch to use the non-recursive f:password masking.
208+
*/
209+
private static /* non-final */ boolean NON_RECURSIVE_PASSWORD_MASKING_PERMISSION_CHECK = SystemProperties.getBoolean(Functions.class.getName() + ".nonRecursivePasswordMaskingPermissionCheck");
210+
211+
206212
public Functions() {
207213
}
208214

@@ -2252,13 +2258,38 @@ public String getPasswordValue(Object o) {
22522258
StaplerRequest2 req = Stapler.getCurrentRequest2();
22532259
if (o instanceof Secret || Secret.BLANK_NONSECRET_PASSWORD_FIELDS_WITHOUT_ITEM_CONFIGURE) {
22542260
if (req != null) {
2255-
Item item = req.findAncestorObject(Item.class);
2256-
if (item != null && !item.hasPermission(Item.CONFIGURE)) {
2257-
return "********";
2258-
}
2259-
Computer computer = req.findAncestorObject(Computer.class);
2260-
if (computer != null && !computer.hasPermission(Computer.CONFIGURE)) {
2261-
return "********";
2261+
if (NON_RECURSIVE_PASSWORD_MASKING_PERMISSION_CHECK) {
2262+
Item item = req.findAncestorObject(Item.class);
2263+
if (item != null && !item.hasPermission(Item.CONFIGURE)) {
2264+
return "********";
2265+
}
2266+
Computer computer = req.findAncestorObject(Computer.class);
2267+
if (computer != null && !computer.hasPermission(Computer.CONFIGURE)) {
2268+
return "********";
2269+
}
2270+
} else {
2271+
List<Ancestor> ancestors = req.getAncestors();
2272+
for (Ancestor ancestor : Iterators.reverse(ancestors)) {
2273+
Object type = ancestor.getObject();
2274+
if (type instanceof Item item) {
2275+
if (!item.hasPermission(Item.CONFIGURE)) {
2276+
return "********";
2277+
}
2278+
break;
2279+
}
2280+
if (type instanceof Computer computer) {
2281+
if (!computer.hasPermission(Computer.CONFIGURE)) {
2282+
return "********";
2283+
}
2284+
break;
2285+
}
2286+
if (type instanceof View view) {
2287+
if (!view.hasPermission(View.CONFIGURE)) {
2288+
return "********";
2289+
}
2290+
break;
2291+
}
2292+
}
22622293
}
22632294
}
22642295
}

core/src/main/java/hudson/cli/CLIAction.java

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -40,11 +40,11 @@
4040
import java.nio.charset.Charset;
4141
import java.nio.charset.UnsupportedCharsetException;
4242
import java.util.ArrayList;
43-
import java.util.HashMap;
4443
import java.util.List;
4544
import java.util.Locale;
4645
import java.util.Map;
4746
import java.util.UUID;
47+
import java.util.concurrent.ConcurrentHashMap;
4848
import java.util.logging.Level;
4949
import java.util.logging.Logger;
5050
import jenkins.model.Jenkins;
@@ -80,7 +80,7 @@ public class CLIAction implements UnprotectedRootAction, StaplerProxy {
8080
*/
8181
/* package-private for testing */ static /* non-final for Script Console */ Boolean ALLOW_WEBSOCKET = SystemProperties.optBoolean(CLIAction.class.getName() + ".ALLOW_WEBSOCKET");
8282

83-
private final transient Map<UUID, FullDuplexHttpService> duplexServices = new HashMap<>();
83+
private final transient Map<UUID, FullDuplexHttpService> duplexServices = new ConcurrentHashMap<>();
8484

8585
@Override
8686
public String getIconFileName() {
@@ -315,8 +315,13 @@ private synchronized void ready() {
315315

316316
void run() throws IOException, InterruptedException {
317317
synchronized (this) {
318-
while (!ready) {
319-
wait();
318+
long end = System.currentTimeMillis() + FullDuplexHttpService.CONNECTION_TIMEOUT;
319+
while (!ready && System.currentTimeMillis() < end) {
320+
wait(1000);
321+
}
322+
if (!ready) {
323+
LOGGER.log(Level.FINE, "CLI timeout waiting for client");
324+
return;
320325
}
321326
}
322327
PrintStream stdout = new PrintStream(streamStdout(), false, encoding);

core/src/main/java/hudson/model/BuildAuthorizationToken.java

Lines changed: 71 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,10 +25,22 @@
2525
package hudson.model;
2626

2727
import com.thoughtworks.xstream.converters.basic.AbstractSingleValueConverter;
28+
import hudson.Extension;
2829
import hudson.Util;
30+
import hudson.diagnosis.OldDataMonitor;
31+
import hudson.model.listeners.ItemListener;
2932
import hudson.security.ACL;
33+
import hudson.util.Secret;
3034
import jakarta.servlet.http.HttpServletResponse;
3135
import java.io.IOException;
36+
import java.nio.charset.StandardCharsets;
37+
import java.security.MessageDigest;
38+
import java.util.logging.Level;
39+
import java.util.logging.Logger;
40+
import jenkins.model.Jenkins;
41+
import jenkins.model.ParameterizedJobMixIn;
42+
import org.kohsuke.accmod.Restricted;
43+
import org.kohsuke.accmod.restrictions.NoExternalUse;
3244
import org.kohsuke.stapler.HttpResponses;
3345
import org.kohsuke.stapler.StaplerRequest;
3446
import org.kohsuke.stapler.StaplerRequest2;
@@ -47,10 +59,25 @@
4759
*/
4860
@Deprecated
4961
public final class BuildAuthorizationToken {
50-
private final String token;
62+
private final Secret token;
5163

64+
private transient boolean fromPlaintext;
65+
66+
/**
67+
* @deprecated since TODO
68+
*/
69+
@Deprecated
5270
public BuildAuthorizationToken(String token) {
71+
this.token = Secret.fromString(token);
72+
this.fromPlaintext = true;
73+
}
74+
75+
/**
76+
* @since TODO
77+
*/
78+
public BuildAuthorizationToken(Secret token) {
5379
this.token = token;
80+
this.fromPlaintext = false;
5481
}
5582

5683
/**
@@ -85,7 +112,7 @@ public static void checkPermission(Job<?, ?> project, BuildAuthorizationToken to
85112
if (token != null && token.token != null) {
86113
//check the provided token
87114
String providedToken = req.getParameter("token");
88-
if (providedToken != null && providedToken.equals(token.token))
115+
if (providedToken != null && MessageDigest.isEqual(providedToken.getBytes(StandardCharsets.UTF_8), token.getToken().getBytes(StandardCharsets.UTF_8)))
89116
return;
90117
if (providedToken != null)
91118
throw new AccessDeniedException(Messages.BuildAuthorizationToken_InvalidTokenProvided());
@@ -110,7 +137,15 @@ public static void checkPermission(Job<?, ?> project, BuildAuthorizationToken to
110137
checkPermission(project, token, StaplerRequest.toStaplerRequest2(req), StaplerResponse.toStaplerResponse2(rsp));
111138
}
112139

140+
@Deprecated
113141
public String getToken() {
142+
return token.getPlainText();
143+
}
144+
145+
/**
146+
* @since TODO
147+
*/
148+
public Secret getEncryptedToken() {
114149
return token;
115150
}
116151

@@ -122,12 +157,44 @@ public boolean canConvert(Class type) {
122157

123158
@Override
124159
public Object fromString(String str) {
125-
return new BuildAuthorizationToken(str);
160+
if (Secret.decrypt(str) == null) {
161+
return new BuildAuthorizationToken(str);
162+
}
163+
return new BuildAuthorizationToken(Secret.fromString(str));
126164
}
127165

128166
@Override
129167
public String toString(Object obj) {
130-
return ((BuildAuthorizationToken) obj).token;
168+
final BuildAuthorizationToken bat = (BuildAuthorizationToken) obj;
169+
// We assume this only gets called when re-saving to its usual destination, so let's clear the in-memory state:
170+
bat.fromPlaintext = false;
171+
return bat.token.getEncryptedValue();
172+
}
173+
}
174+
175+
@Extension
176+
@Restricted(NoExternalUse.class)
177+
public static class ItemListenerImpl extends ItemListener {
178+
private static final Logger LOGGER = Logger.getLogger(ItemListenerImpl.class.getName());
179+
180+
@Override
181+
public void onUpdated(Item item) {
182+
if (item instanceof ParameterizedJobMixIn.ParameterizedJob job) {
183+
BuildAuthorizationToken bat = job.getAuthToken();
184+
if (bat != null) {
185+
if (bat.fromPlaintext) {
186+
OldDataMonitor.report(item, "2.528.3 / 2.541");
187+
LOGGER.log(Level.FINE, "Reporting " + item.getFullName());
188+
} else {
189+
LOGGER.log(Level.FINE, "Skipping reporting of " + item.getFullName());
190+
}
191+
}
192+
}
193+
}
194+
195+
@Override
196+
public void onLoaded() {
197+
Jenkins.get().getAllItems(ParameterizedJobMixIn.ParameterizedJob.class).forEach(this::onUpdated);
131198
}
132199
}
133200
}

core/src/main/java/hudson/security/AuthenticationProcessingFilter2.java

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626

2727
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
2828
import hudson.model.User;
29+
import hudson.security.csrf.CrumbIssuer;
2930
import jakarta.servlet.FilterChain;
3031
import jakarta.servlet.ServletException;
3132
import jakarta.servlet.http.HttpServletRequest;
@@ -34,12 +35,14 @@
3435
import java.io.IOException;
3536
import java.util.logging.Level;
3637
import java.util.logging.Logger;
38+
import jenkins.model.Jenkins;
3739
import jenkins.security.SecurityListener;
3840
import jenkins.security.seed.UserSeedProperty;
3941
import jenkins.util.SystemProperties;
4042
import org.kohsuke.accmod.Restricted;
4143
import org.kohsuke.accmod.restrictions.NoExternalUse;
4244
import org.springframework.http.HttpMethod;
45+
import org.springframework.security.authentication.InsufficientAuthenticationException;
4346
import org.springframework.security.core.Authentication;
4447
import org.springframework.security.core.AuthenticationException;
4548
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
@@ -55,6 +58,13 @@
5558
@Restricted(NoExternalUse.class)
5659
public final class AuthenticationProcessingFilter2 extends UsernamePasswordAuthenticationFilter {
5760

61+
/**
62+
* Escape hatch to disable CSRF protection for login requests.
63+
*/
64+
@Restricted(NoExternalUse.class)
65+
@SuppressFBWarnings(value = "MS_SHOULD_BE_FINAL", justification = "for script console")
66+
public static /* non-final */ boolean SKIP_CSRF_CHECK = SystemProperties.getBoolean(AuthenticationProcessingFilter2.class.getName() + ".skipCSRFCheck");
67+
5868
@SuppressFBWarnings(value = "HARD_CODE_PASSWORD", justification = "This is a password parameter, not a password")
5969
public AuthenticationProcessingFilter2(String authenticationGatewayUrl) {
6070
setRequiresAuthenticationRequestMatcher(PathPatternRequestMatcher.withDefaults().matcher(HttpMethod.POST, "/" + authenticationGatewayUrl));
@@ -63,6 +73,30 @@ public AuthenticationProcessingFilter2(String authenticationGatewayUrl) {
6373
setPasswordParameter("j_password");
6474
}
6575

76+
@Override
77+
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
78+
if (!SKIP_CSRF_CHECK) {
79+
Jenkins jenkins = Jenkins.get();
80+
CrumbIssuer crumbIssuer = jenkins.getCrumbIssuer();
81+
if (crumbIssuer != null) {
82+
String crumbField = crumbIssuer.getCrumbRequestField();
83+
String crumbSalt = crumbIssuer.getDescriptor().getCrumbSalt();
84+
85+
String crumb = request.getParameter(crumbField);
86+
if (crumb == null) {
87+
crumb = request.getHeader(crumbField);
88+
}
89+
90+
if (crumb == null || !crumbIssuer.validateCrumb(request, crumbSalt, crumb)) {
91+
LOGGER.log(Level.FINE, "No valid crumb was included in authentication request from {0}", request.getRemoteAddr());
92+
throw new InsufficientAuthenticationException("No valid crumb was included in the request");
93+
}
94+
}
95+
}
96+
97+
return super.attemptAuthentication(request, response);
98+
}
99+
66100
@Override
67101
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException {
68102
if (SystemProperties.getInteger(SecurityRealm.class.getName() + ".sessionFixationProtectionMode", 1) == 2) {

0 commit comments

Comments
 (0)