mirror of https://github.com/jenkinsci/jenkins.git
Merge branch 'master' into add-groups-to-command-palette
This commit is contained in:
commit
0ab3665587
|
@ -12,7 +12,7 @@
|
|||
[](https://app.gitter.im/#/room/#jenkinsci_jenkins:gitter.im)
|
||||
|
||||
In a nutshell, Jenkins is the leading open-source automation server.
|
||||
Built with Java, it provides over 1,800 [plugins](https://plugins.jenkins.io/) to support automating virtually anything,
|
||||
Built with Java, it provides over 2,000 [plugins](https://plugins.jenkins.io/) to support automating virtually anything,
|
||||
so that humans can spend their time doing things machines cannot.
|
||||
|
||||
# What to Use Jenkins for and When to Use It
|
||||
|
|
|
@ -287,11 +287,6 @@ THE SOFTWARE.
|
|||
<artifactId>localizer</artifactId>
|
||||
<version>1.31</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.jvnet.robust-http-client</groupId>
|
||||
<artifactId>robust-http-client</artifactId>
|
||||
<version>1.2</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.jvnet.winp</groupId>
|
||||
<artifactId>winp</artifactId>
|
||||
|
@ -342,7 +337,7 @@ THE SOFTWARE.
|
|||
<!-- provided by jcl-over-slf4j -->
|
||||
<groupId>commons-logging</groupId>
|
||||
<artifactId>commons-logging</artifactId>
|
||||
<version>1.3.4</version>
|
||||
<version>1.3.5</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
|
|
@ -325,10 +325,6 @@ THE SOFTWARE.
|
|||
<groupId>org.jvnet.localizer</groupId>
|
||||
<artifactId>localizer</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.jvnet.robust-http-client</groupId>
|
||||
<artifactId>robust-http-client</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.jvnet.winp</groupId>
|
||||
<artifactId>winp</artifactId>
|
||||
|
|
|
@ -1989,7 +1989,7 @@ public class Functions {
|
|||
*/
|
||||
public static @CheckForNull String getConsoleUrl(WithConsoleUrl withConsoleUrl) {
|
||||
String consoleUrl = withConsoleUrl.getConsoleUrl();
|
||||
return consoleUrl != null ? Stapler.getCurrentRequest().getContextPath() + '/' + consoleUrl : null;
|
||||
return consoleUrl != null ? Stapler.getCurrentRequest2().getContextPath() + '/' + consoleUrl : null;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -70,7 +70,6 @@ import jenkins.security.stapler.StaplerAccessibleType;
|
|||
import jenkins.util.JenkinsJVM;
|
||||
import jenkins.util.SystemProperties;
|
||||
import org.jenkinsci.Symbol;
|
||||
import org.jvnet.robust_http_client.RetryableHttpStream;
|
||||
import org.kohsuke.accmod.Restricted;
|
||||
import org.kohsuke.accmod.restrictions.NoExternalUse;
|
||||
import org.kohsuke.stapler.DataBoundConstructor;
|
||||
|
@ -346,10 +345,10 @@ public final class ProxyConfiguration extends AbstractDescribableImpl<ProxyConfi
|
|||
public static InputStream getInputStream(URL url) throws IOException {
|
||||
final ProxyConfiguration p = get();
|
||||
if (p == null)
|
||||
return new RetryableHttpStream(url);
|
||||
return ((HttpURLConnection) url.openConnection()).getInputStream();
|
||||
|
||||
Proxy proxy = p.createProxy(url.getHost());
|
||||
InputStream is = new RetryableHttpStream(url, proxy);
|
||||
InputStream is = ((HttpURLConnection) url.openConnection(proxy)).getInputStream();
|
||||
if (p.getUserName() != null) {
|
||||
// Add an authenticator which provides the credentials for proxy authentication
|
||||
Authenticator.setDefault(p.authenticator);
|
||||
|
|
|
@ -92,6 +92,7 @@ import java.util.Collection;
|
|||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.EnumSet;
|
||||
import java.util.HexFormat;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
|
@ -115,7 +116,6 @@ import jenkins.model.Jenkins;
|
|||
import jenkins.util.MemoryReductionUtil;
|
||||
import jenkins.util.SystemProperties;
|
||||
import jenkins.util.io.PathRemover;
|
||||
import org.apache.commons.codec.digest.DigestUtils;
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.apache.commons.lang.time.FastDateFormat;
|
||||
import org.apache.tools.ant.BuildException;
|
||||
|
@ -635,7 +635,6 @@ public class Util {
|
|||
* The stream will be closed by this method at the end of this method.
|
||||
* @return
|
||||
* 32-char wide string
|
||||
* @see DigestUtils#md5Hex(InputStream)
|
||||
*/
|
||||
@NonNull
|
||||
public static String getDigestOf(@NonNull InputStream source) throws IOException {
|
||||
|
@ -710,13 +709,7 @@ public class Util {
|
|||
|
||||
@NonNull
|
||||
public static String toHexString(@NonNull byte[] data, int start, int len) {
|
||||
StringBuilder buf = new StringBuilder();
|
||||
for (int i = 0; i < len; i++) {
|
||||
int b = data[start + i] & 0xFF;
|
||||
if (b < 16) buf.append('0');
|
||||
buf.append(Integer.toHexString(b));
|
||||
}
|
||||
return buf.toString();
|
||||
return HexFormat.of().formatHex(data, start, len);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
|
@ -726,12 +719,7 @@ public class Util {
|
|||
|
||||
@NonNull
|
||||
public static byte[] fromHexString(@NonNull String data) {
|
||||
if (data.length() % 2 != 0)
|
||||
throw new IllegalArgumentException("data must have an even number of hexadecimal digits");
|
||||
byte[] r = new byte[data.length() / 2];
|
||||
for (int i = 0; i < data.length(); i += 2)
|
||||
r[i / 2] = (byte) Integer.parseInt(data.substring(i, i + 2), 16);
|
||||
return r;
|
||||
return HexFormat.of().parseHex(data);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1992,9 +1980,18 @@ public class Util {
|
|||
* Returns Hex string of SHA-256 Digest of passed input
|
||||
*/
|
||||
@Restricted(NoExternalUse.class)
|
||||
public static String getHexOfSHA256DigestOf(byte[] input) throws IOException {
|
||||
public static String getHexOfSHA256DigestOf(byte[] input) {
|
||||
//get hex string of sha 256 of payload
|
||||
byte[] payloadDigest = Util.getSHA256DigestOf(input);
|
||||
return (payloadDigest != null) ? Util.toHexString(payloadDigest) : null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns Hex string of SHA-256 Digest of passed string
|
||||
*/
|
||||
@Restricted(NoExternalUse.class)
|
||||
public static String getHexOfSHA256DigestOf(String input) {
|
||||
return getHexOfSHA256DigestOf(input.getBytes(StandardCharsets.UTF_8));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -371,6 +371,11 @@ public final class CronTab {
|
|||
* (e.g. Jun 31) date, or at least a date too rare to be useful. This addresses JENKINS-41864 and was added in 2.49
|
||||
*/
|
||||
public Calendar ceil(Calendar cal) {
|
||||
if (cal.get(Calendar.SECOND) > 0 || cal.get(Calendar.MILLISECOND) > 0) {
|
||||
cal.set(Calendar.SECOND, 0);
|
||||
cal.set(Calendar.MILLISECOND, 0);
|
||||
cal.add(Calendar.MINUTE, 1);
|
||||
}
|
||||
Calendar twoYearsFuture = (Calendar) cal.clone();
|
||||
twoYearsFuture.add(Calendar.YEAR, 2);
|
||||
OUTER:
|
||||
|
@ -440,6 +445,8 @@ public final class CronTab {
|
|||
* (e.g. Jun 31) date, or at least a date too rare to be useful. This addresses JENKINS-41864 and was added in 2.49
|
||||
*/
|
||||
public Calendar floor(Calendar cal) {
|
||||
cal.set(Calendar.SECOND, 0);
|
||||
cal.set(Calendar.MILLISECOND, 0);
|
||||
Calendar twoYearsAgo = (Calendar) cal.clone();
|
||||
twoYearsAgo.add(Calendar.YEAR, -2);
|
||||
|
||||
|
|
|
@ -80,7 +80,7 @@ public interface ConsoleUrlProvider extends Describable<ConsoleUrlProvider> {
|
|||
* @return the URL for the console for the specified build, relative to the web server root
|
||||
*/
|
||||
static @NonNull String getRedirectUrl(Run<?, ?> run) {
|
||||
return Stapler.getCurrentRequest().getContextPath() + '/' + run.getConsoleUrl();
|
||||
return Stapler.getCurrentRequest2().getContextPath() + '/' + run.getConsoleUrl();
|
||||
}
|
||||
|
||||
static List<ConsoleUrlProvider> all() {
|
||||
|
|
|
@ -6,7 +6,7 @@ import java.util.logging.Logger;
|
|||
import net.sf.json.JSONException;
|
||||
import net.sf.json.JSONObject;
|
||||
import org.jenkinsci.Symbol;
|
||||
import org.kohsuke.stapler.StaplerRequest;
|
||||
import org.kohsuke.stapler.StaplerRequest2;
|
||||
|
||||
/**
|
||||
* Configures check interval for computer retention.
|
||||
|
@ -56,7 +56,7 @@ public class GlobalComputerRetentionCheckIntervalConfiguration extends GlobalCon
|
|||
}
|
||||
|
||||
@Override
|
||||
public boolean configure(StaplerRequest req, JSONObject json) throws FormException {
|
||||
public boolean configure(StaplerRequest2 req, JSONObject json) throws FormException {
|
||||
try {
|
||||
final int interval = json.getInt("computerRetentionCheckInterval");
|
||||
setComputerRetentionCheckInterval(interval);
|
||||
|
|
|
@ -67,7 +67,7 @@ public abstract class GlobalConfiguration extends Descriptor<GlobalConfiguration
|
|||
@Override
|
||||
public boolean configure(StaplerRequest2 req, JSONObject json) throws FormException {
|
||||
if (Util.isOverridden(GlobalConfiguration.class, getClass(), "configure", StaplerRequest.class, JSONObject.class)) {
|
||||
return configure(StaplerRequest.fromStaplerRequest2(req), json);
|
||||
return configure(req != null ? StaplerRequest.fromStaplerRequest2(req) : null, json);
|
||||
} else {
|
||||
return configureImpl(req, json);
|
||||
}
|
||||
|
|
|
@ -974,6 +974,9 @@ public class Jenkins extends AbstractCIBase implements DirectlyModifiableTopLeve
|
|||
ClassFilterImpl.register();
|
||||
LOGGER.info("Starting version " + getVersion());
|
||||
|
||||
// Sanity check that we can load the confidential store. Fail fast if we can't.
|
||||
ConfidentialStore.get();
|
||||
|
||||
// initialization consists of ...
|
||||
executeReactor(is,
|
||||
pluginManager.initTasks(is), // loading and preparing plugins
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package jenkins.security;
|
||||
|
||||
import edu.umd.cs.findbugs.annotations.NonNull;
|
||||
import hudson.FilePath;
|
||||
import hudson.Util;
|
||||
import hudson.util.Secret;
|
||||
|
@ -19,19 +20,35 @@ import javax.crypto.CipherInputStream;
|
|||
import javax.crypto.CipherOutputStream;
|
||||
import javax.crypto.SecretKey;
|
||||
import jenkins.model.Jenkins;
|
||||
import jenkins.util.SystemProperties;
|
||||
|
||||
/**
|
||||
* Default portable implementation of {@link ConfidentialStore} that uses
|
||||
* a directory inside $JENKINS_HOME.
|
||||
*
|
||||
* The master key is also stored in this same directory.
|
||||
* <p>
|
||||
* The master key is stored by default in <code>$JENKINS_HOME/secrets/master.key</code> but another location can be provided using the system property <code>jenkins.master.key.file</code>.
|
||||
* <p>
|
||||
* It is also possible to prevent the generation of the master key file using the system property <code>-Djenkins.master.key.readOnly</code>. In this case, the master key file must be provided or startup will fail.
|
||||
*
|
||||
* @author Kohsuke Kawaguchi
|
||||
*/
|
||||
// @MetaInfServices --- not annotated because this is the fallback implementation
|
||||
public class DefaultConfidentialStore extends ConfidentialStore {
|
||||
static final String MASTER_KEY_FILE_SYSTEM_PROPERTY = DefaultConfidentialStore.class.getName() + ".file";
|
||||
static final String MASTER_KEY_READONLY_SYSTEM_PROPERTY_NAME = DefaultConfidentialStore.class.getName() + ".readOnly";
|
||||
|
||||
private final SecureRandom sr = new SecureRandom();
|
||||
|
||||
@NonNull
|
||||
private static File getMasterKeyFile(File rootDir) {
|
||||
var jenkinsMasterKey = SystemProperties.getString(MASTER_KEY_FILE_SYSTEM_PROPERTY);
|
||||
if (jenkinsMasterKey != null) {
|
||||
return new File(jenkinsMasterKey);
|
||||
} else {
|
||||
return new File(rootDir, "master.key");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Directory that stores individual keys.
|
||||
*/
|
||||
|
@ -51,6 +68,10 @@ public class DefaultConfidentialStore extends ConfidentialStore {
|
|||
}
|
||||
|
||||
public DefaultConfidentialStore(File rootDir) throws IOException, InterruptedException {
|
||||
this(rootDir, getMasterKeyFile(rootDir));
|
||||
}
|
||||
|
||||
protected DefaultConfidentialStore(File rootDir, File keyFile) throws IOException, InterruptedException {
|
||||
this.rootDir = rootDir;
|
||||
if (rootDir.mkdirs()) {
|
||||
// protect this directory. but don't change the permission of the existing directory
|
||||
|
@ -58,11 +79,15 @@ public class DefaultConfidentialStore extends ConfidentialStore {
|
|||
new FilePath(rootDir).chmod(0700);
|
||||
}
|
||||
|
||||
TextFile masterSecret = new TextFile(new File(rootDir, "master.key"));
|
||||
TextFile masterSecret = new TextFile(keyFile);
|
||||
if (!masterSecret.exists()) {
|
||||
// we are only going to use small number of bits (since export control limits AES key length)
|
||||
// but let's generate a long enough key anyway
|
||||
masterSecret.write(Util.toHexString(randomBytes(128)));
|
||||
if (SystemProperties.getBoolean(MASTER_KEY_READONLY_SYSTEM_PROPERTY_NAME)) {
|
||||
throw new IOException(masterSecret + " does not exist and system property " + MASTER_KEY_READONLY_SYSTEM_PROPERTY_NAME + " is set. You must provide a valid master key file.");
|
||||
} else {
|
||||
// we are only going to use small number of bits (since export control limits AES key length)
|
||||
// but let's generate a long enough key anyway
|
||||
masterSecret.write(Util.toHexString(randomBytes(128)));
|
||||
}
|
||||
}
|
||||
this.masterKey = Util.toAes128Key(masterSecret.readTrim());
|
||||
}
|
||||
|
|
|
@ -28,6 +28,7 @@ import edu.umd.cs.findbugs.annotations.NonNull;
|
|||
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
|
||||
import hudson.BulkChange;
|
||||
import hudson.Extension;
|
||||
import hudson.Util;
|
||||
import hudson.model.User;
|
||||
import hudson.model.UserProperty;
|
||||
import hudson.model.UserPropertyDescriptor;
|
||||
|
@ -39,7 +40,6 @@ import java.util.Objects;
|
|||
import jenkins.model.Jenkins;
|
||||
import jenkins.security.LastGrantedAuthoritiesProperty;
|
||||
import jenkins.util.SystemProperties;
|
||||
import org.apache.commons.codec.binary.Hex;
|
||||
import org.jenkinsci.Symbol;
|
||||
import org.kohsuke.accmod.Restricted;
|
||||
import org.kohsuke.accmod.restrictions.DoNotUse;
|
||||
|
@ -103,7 +103,7 @@ public class UserSeedProperty extends UserProperty {
|
|||
byte[] bytes = new byte[SEED_NUM_BYTES];
|
||||
while (Objects.equals(newSeed, currentSeed)) {
|
||||
RANDOM.nextBytes(bytes);
|
||||
newSeed = new String(Hex.encodeHex(bytes));
|
||||
newSeed = Util.toHexString(bytes);
|
||||
}
|
||||
this.seed = newSeed;
|
||||
}
|
||||
|
|
|
@ -32,6 +32,7 @@ import hudson.ExtensionList;
|
|||
import hudson.ExtensionPoint;
|
||||
import hudson.PluginWrapper;
|
||||
import hudson.ProxyConfiguration;
|
||||
import hudson.Util;
|
||||
import hudson.model.AsyncPeriodicWork;
|
||||
import hudson.model.TaskListener;
|
||||
import hudson.model.UsageStatistics;
|
||||
|
@ -51,7 +52,6 @@ import java.util.logging.Logger;
|
|||
import jenkins.model.Jenkins;
|
||||
import jenkins.util.SystemProperties;
|
||||
import net.sf.json.JSONObject;
|
||||
import org.apache.commons.codec.digest.DigestUtils;
|
||||
import org.kohsuke.accmod.Restricted;
|
||||
import org.kohsuke.accmod.restrictions.NoExternalUse;
|
||||
|
||||
|
@ -221,7 +221,7 @@ public abstract class Telemetry implements ExtensionPoint {
|
|||
wrappedData.put("type", telemetry.getId());
|
||||
wrappedData.put("payload", data);
|
||||
String correlationId = ExtensionList.lookupSingleton(Correlator.class).getCorrelationId();
|
||||
wrappedData.put("correlator", DigestUtils.sha256Hex(correlationId + telemetry.getId()));
|
||||
wrappedData.put("correlator", Util.getHexOfSHA256DigestOf(correlationId + telemetry.getId()));
|
||||
|
||||
String body = wrappedData.toString();
|
||||
if (LOGGER.isLoggable(Level.FINEST)) {
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package jenkins.util;
|
||||
|
||||
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
|
||||
import hudson.Util;
|
||||
import hudson.util.FormValidation;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.File;
|
||||
|
@ -33,8 +34,6 @@ import java.util.logging.Level;
|
|||
import java.util.logging.Logger;
|
||||
import jenkins.model.Jenkins;
|
||||
import net.sf.json.JSONObject;
|
||||
import org.apache.commons.codec.DecoderException;
|
||||
import org.apache.commons.codec.binary.Hex;
|
||||
import org.apache.commons.io.output.TeeOutputStream;
|
||||
import org.jvnet.hudson.crypto.CertificateUtil;
|
||||
import org.jvnet.hudson.crypto.SignatureOutputStream;
|
||||
|
@ -221,10 +220,10 @@ public class JSONSignatureValidator {
|
|||
// This approach might look unnecessarily clever, but short of having redundant Signature instances,
|
||||
// there doesn't seem to be a better approach for this.
|
||||
try {
|
||||
if (signature.verify(Hex.decodeHex(providedSignature.toCharArray()))) {
|
||||
if (signature.verify(Util.fromHexString(providedSignature))) {
|
||||
return true;
|
||||
}
|
||||
} catch (SignatureException | DecoderException ignore) {
|
||||
} catch (SignatureException | IllegalArgumentException ignore) {
|
||||
// ignore
|
||||
}
|
||||
|
||||
|
@ -242,7 +241,7 @@ public class JSONSignatureValidator {
|
|||
* Utility method supporting both possible digest formats: Base64 and Hex
|
||||
*/
|
||||
private boolean digestMatches(byte[] digest, String providedDigest) {
|
||||
return providedDigest.equalsIgnoreCase(Hex.encodeHexString(digest)) || providedDigest.equalsIgnoreCase(Base64.getEncoder().encodeToString(digest));
|
||||
return providedDigest.equalsIgnoreCase(Util.toHexString(digest)) || providedDigest.equalsIgnoreCase(Base64.getEncoder().encodeToString(digest));
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -0,0 +1,279 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
The MIT License
|
||||
|
||||
Copyright (c) 2004-2009, Sun Microsystems, Inc., Kohsuke Kawaguchi, Tom Huybrechts, id:digerata, Yahoo! Inc.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
-->
|
||||
|
||||
<web-fragment xmlns="http://xmlns.jcp.org/xml/ns/javaee"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-fragment_3_1.xsd"
|
||||
version="3.1">
|
||||
<name>jenkins</name>
|
||||
|
||||
<servlet>
|
||||
<servlet-name>Stapler</servlet-name>
|
||||
<servlet-class>org.kohsuke.stapler.Stapler</servlet-class>
|
||||
<init-param>
|
||||
<param-name>default-encodings</param-name>
|
||||
<param-value>text/html=UTF-8</param-value>
|
||||
</init-param>
|
||||
<init-param>
|
||||
<param-name>diagnosticThreadName</param-name>
|
||||
<param-value>false</param-value>
|
||||
</init-param>
|
||||
<async-supported>true</async-supported>
|
||||
</servlet>
|
||||
|
||||
<servlet-mapping>
|
||||
<servlet-name>Stapler</servlet-name>
|
||||
<url-pattern>/*</url-pattern>
|
||||
</servlet-mapping>
|
||||
|
||||
<filter>
|
||||
<filter-name>suspicious-request-filter</filter-name>
|
||||
<filter-class>jenkins.security.SuspiciousRequestFilter</filter-class>
|
||||
<async-supported>true</async-supported>
|
||||
</filter>
|
||||
<filter>
|
||||
<filter-name>diagnostic-name-filter</filter-name>
|
||||
<filter-class>org.kohsuke.stapler.DiagnosticThreadNameFilter</filter-class>
|
||||
<async-supported>true</async-supported>
|
||||
</filter>
|
||||
<filter>
|
||||
<filter-name>encoding-filter</filter-name>
|
||||
<filter-class>hudson.util.CharacterEncodingFilter</filter-class>
|
||||
<async-supported>true</async-supported>
|
||||
</filter>
|
||||
<filter>
|
||||
<filter-name>uncaught-exception-filter</filter-name>
|
||||
<filter-class>org.kohsuke.stapler.UncaughtExceptionFilter</filter-class>
|
||||
<async-supported>true</async-supported>
|
||||
</filter>
|
||||
<filter>
|
||||
<filter-name>authentication-filter</filter-name>
|
||||
<filter-class>hudson.security.HudsonFilter</filter-class>
|
||||
<async-supported>true</async-supported>
|
||||
</filter>
|
||||
<filter>
|
||||
<filter-name>csrf-filter</filter-name>
|
||||
<filter-class>hudson.security.csrf.CrumbFilter</filter-class>
|
||||
<async-supported>true</async-supported>
|
||||
</filter>
|
||||
<filter>
|
||||
<filter-name>error-attribute-filter</filter-name>
|
||||
<filter-class>jenkins.ErrorAttributeFilter</filter-class>
|
||||
<async-supported>true</async-supported>
|
||||
</filter>
|
||||
<filter>
|
||||
<filter-name>plugins-filter</filter-name>
|
||||
<filter-class>hudson.util.PluginServletFilter</filter-class>
|
||||
<async-supported>true</async-supported>
|
||||
</filter>
|
||||
|
||||
<!--
|
||||
The Headers filter allows us to override headers sent by the container
|
||||
that may be in conflict with what we want. For example, Tomcat will set
|
||||
Cache-Control: no-cache for any files behind the security-constraint
|
||||
below. So if Hudson is on a public server, and you want to only allow
|
||||
authorized users to access it, you may want to pay attention to this.
|
||||
|
||||
See: http://www.nabble.com/No-browser-caching-with-Hudson- -tf4601857.html
|
||||
|
||||
<filter>
|
||||
<filter-name>change-headers-filter</filter-name>
|
||||
<filter-class>hudson.ResponseHeaderFilter</filter-class>
|
||||
<!- The value listed here is for 24 hours. Increase or decrease as you see
|
||||
fit. Value is in seconds. Make sure to keep the public option ->
|
||||
<init-param>
|
||||
<param-name>Cache-Control</param-name>
|
||||
<param-value>max-age=86400, public</param-value>
|
||||
</init-param>
|
||||
<!- It turns out that Tomcat just doesn't want to let
|
||||
go of its cache option. If you override Cache-Control,
|
||||
it starts to send Pragma: no-cache as a backup.
|
||||
->
|
||||
<init-param>
|
||||
<param-name>Pragma</param-name>
|
||||
<param-value>public</param-value>
|
||||
</init-param>
|
||||
</filter>
|
||||
<filter-mapping>
|
||||
<filter-name>change-headers-filter</filter-name>
|
||||
<url-pattern>*.css</url-pattern>
|
||||
</filter-mapping>
|
||||
<filter-mapping>
|
||||
<filter-name>change-headers-filter</filter-name>
|
||||
<url-pattern>*.gif</url-pattern>
|
||||
</filter-mapping>
|
||||
<filter-mapping>
|
||||
<filter-name>change-headers-filter</filter-name>
|
||||
<url-pattern>*.js</url-pattern>
|
||||
</filter-mapping>
|
||||
<filter-mapping>
|
||||
<filter-name>change-headers-filter</filter-name>
|
||||
<url-pattern>*.png</url-pattern>
|
||||
</filter-mapping>
|
||||
-->
|
||||
|
||||
<filter-mapping>
|
||||
<filter-name>suspicious-request-filter</filter-name>
|
||||
<url-pattern>/*</url-pattern>
|
||||
</filter-mapping>
|
||||
<filter-mapping>
|
||||
<filter-name>diagnostic-name-filter</filter-name>
|
||||
<url-pattern>/*</url-pattern>
|
||||
</filter-mapping>
|
||||
<filter-mapping>
|
||||
<filter-name>encoding-filter</filter-name>
|
||||
<url-pattern>/*</url-pattern>
|
||||
</filter-mapping>
|
||||
<filter-mapping>
|
||||
<filter-name>uncaught-exception-filter</filter-name>
|
||||
<url-pattern>/*</url-pattern>
|
||||
</filter-mapping>
|
||||
<filter-mapping>
|
||||
<filter-name>authentication-filter</filter-name>
|
||||
<url-pattern>/*</url-pattern>
|
||||
</filter-mapping>
|
||||
<filter-mapping>
|
||||
<filter-name>csrf-filter</filter-name>
|
||||
<url-pattern>/*</url-pattern>
|
||||
</filter-mapping>
|
||||
<filter-mapping>
|
||||
<filter-name>error-attribute-filter</filter-name>
|
||||
<url-pattern>/*</url-pattern>
|
||||
</filter-mapping>
|
||||
<filter-mapping>
|
||||
<filter-name>plugins-filter</filter-name>
|
||||
<url-pattern>/*</url-pattern>
|
||||
</filter-mapping>
|
||||
|
||||
<listener>
|
||||
<!-- Must be before WebAppMain in order to initialize the context before the first use of this class. -->
|
||||
<listener-class>jenkins.util.SystemProperties$Listener</listener-class>
|
||||
</listener>
|
||||
<listener>
|
||||
<listener-class>hudson.WebAppMain</listener-class>
|
||||
</listener>
|
||||
<listener>
|
||||
<listener-class>jenkins.JenkinsHttpSessionListener</listener-class>
|
||||
</listener>
|
||||
|
||||
<!--
|
||||
JENKINS-1235 suggests containers interpret '*' as "all roles defined in web.xml"
|
||||
as opposed to "all roles defined in the security realm", so we need to list some
|
||||
common names in the hope that users will have at least one of those roles.
|
||||
-->
|
||||
<security-role>
|
||||
<role-name>admin</role-name>
|
||||
</security-role>
|
||||
<security-role>
|
||||
<role-name>user</role-name>
|
||||
</security-role>
|
||||
<security-role>
|
||||
<role-name>hudson</role-name>
|
||||
</security-role>
|
||||
|
||||
<security-constraint>
|
||||
<web-resource-collection>
|
||||
<web-resource-name>Hudson</web-resource-name>
|
||||
<url-pattern>/loginEntry</url-pattern>
|
||||
<!--http-method>GET</http-method-->
|
||||
</web-resource-collection>
|
||||
<auth-constraint>
|
||||
<role-name>**</role-name>
|
||||
</auth-constraint>
|
||||
</security-constraint>
|
||||
|
||||
<!-- Disable TRACE method with security constraint (copied from jetty/webdefaults.xml) -->
|
||||
<security-constraint>
|
||||
<web-resource-collection>
|
||||
<web-resource-name>Disable TRACE</web-resource-name>
|
||||
<url-pattern>/*</url-pattern>
|
||||
<http-method>TRACE</http-method>
|
||||
</web-resource-collection>
|
||||
<auth-constraint />
|
||||
</security-constraint>
|
||||
|
||||
<security-constraint>
|
||||
<web-resource-collection>
|
||||
<web-resource-name>other</web-resource-name>
|
||||
<url-pattern>/*</url-pattern>
|
||||
</web-resource-collection>
|
||||
<!-- no security constraint -->
|
||||
</security-constraint>
|
||||
|
||||
<login-config>
|
||||
<auth-method>FORM</auth-method>
|
||||
<form-login-config>
|
||||
<form-login-page>/login</form-login-page>
|
||||
<form-error-page>/loginError</form-error-page>
|
||||
</form-login-config>
|
||||
</login-config>
|
||||
|
||||
<!-- configure additional extension-content-type mappings -->
|
||||
<mime-mapping>
|
||||
<extension>xml</extension>
|
||||
<mime-type>application/xml</mime-type>
|
||||
</mime-mapping>
|
||||
<!--mime-mapping> commenting out until this works out of the box with JOnAS. See http://www.nabble.com/Error-with-mime-type%2D-%27application-xslt%2Bxml%27-when-deploying-hudson-1.316-in-jonas-td24740489.html
|
||||
<extension>xsl</extension>
|
||||
<mime-type>application/xslt+xml</mime-type>
|
||||
</mime-mapping-->
|
||||
<mime-mapping>
|
||||
<extension>log</extension>
|
||||
<mime-type>text/plain</mime-type>
|
||||
</mime-mapping>
|
||||
<mime-mapping>
|
||||
<extension>war</extension>
|
||||
<mime-type>application/octet-stream</mime-type>
|
||||
</mime-mapping>
|
||||
<mime-mapping>
|
||||
<extension>ear</extension>
|
||||
<mime-type>application/octet-stream</mime-type>
|
||||
</mime-mapping>
|
||||
<mime-mapping>
|
||||
<extension>rar</extension>
|
||||
<mime-type>application/octet-stream</mime-type>
|
||||
</mime-mapping>
|
||||
<mime-mapping>
|
||||
<extension>webm</extension>
|
||||
<mime-type>video/webm</mime-type>
|
||||
</mime-mapping>
|
||||
|
||||
<error-page>
|
||||
<exception-type>java.lang.Throwable</exception-type>
|
||||
<location>/oops</location>
|
||||
</error-page>
|
||||
<error-page>
|
||||
<error-code>404</error-code>
|
||||
<location>/404</location>
|
||||
</error-page>
|
||||
|
||||
<session-config>
|
||||
<cookie-config>
|
||||
<!-- See https://www.owasp.org/index.php/HttpOnly for the discussion of this topic in OWASP -->
|
||||
<http-only>true</http-only>
|
||||
</cookie-config>
|
||||
<!-- Tracking mode is managed by WebAppMain.FORCE_SESSION_TRACKING_BY_COOKIE_PROP -->
|
||||
</session-config>
|
||||
</web-fragment>
|
|
@ -38,7 +38,7 @@ THE SOFTWARE.
|
|||
placeholder="${%Search available plugins}"
|
||||
id="filter-box"
|
||||
autofocus="true"
|
||||
value="${request.getParameter('filter')}" />
|
||||
value="${request2.getParameter('filter')}" />
|
||||
</div>
|
||||
<div class="jenkins-app-bar__controls">
|
||||
<l:isAdmin>
|
||||
|
|
|
@ -43,7 +43,7 @@ THE SOFTWARE.
|
|||
placeholder="${%Search installed plugins}"
|
||||
id="filter-box"
|
||||
autofocus="true"
|
||||
value="${request.getParameter('filter')}"
|
||||
value="${request2.getParameter('filter')}"
|
||||
enabled="${!noPlugins}" />
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -41,7 +41,7 @@ THE SOFTWARE.
|
|||
placeholder="${%Search plugin updates}"
|
||||
id="filter-box"
|
||||
autofocus="true"
|
||||
value="${request.getParameter('filter')}"
|
||||
value="${request2.getParameter('filter')}"
|
||||
enabled="${!empty(list)}" />
|
||||
</div>
|
||||
<div class="jenkins-app-bar__controls">
|
||||
|
|
|
@ -31,7 +31,7 @@ THE SOFTWARE.
|
|||
<h1>
|
||||
${%JVM Memory Usage}
|
||||
</h1>
|
||||
<j:set var="type" value="${request.getParameter('type') ?: 'min'}" />
|
||||
<j:set var="type" value="${request2.getParameter('type') ?: 'min'}" />
|
||||
<div>
|
||||
${%Timespan}:
|
||||
<j:choose>
|
||||
|
|
|
@ -30,14 +30,14 @@ THE SOFTWARE.
|
|||
<j:if test="${it.getPrimaryView() != null}">
|
||||
<j:set var="it" value="${it.getPrimaryView()}"/>
|
||||
</j:if>
|
||||
<j:set var="submissionUrl" value="${request.getParameter('submissionUrl')}" defaultValue="submitDescription"/>
|
||||
<j:set var="submissionUrl" value="${request2.getParameter('submissionUrl')}" defaultValue="submitDescription"/>
|
||||
<j:invokeStatic className="hudson.Util" method="isSafeToRedirectTo" var="isSafeToRedirectTo">
|
||||
<j:arg value="${submissionUrl}"/>
|
||||
</j:invokeStatic>
|
||||
<j:if test="${!isSafeToRedirectTo}">
|
||||
<j:set var="submissionUrl" value="submitDescription"/>
|
||||
</j:if>
|
||||
<j:set var="initialDescription" value="${request.getParameter('description')}" defaultValue="${it.description}"/>
|
||||
<j:set var="initialDescription" value="${request2.getParameter('description')}" defaultValue="${it.description}"/>
|
||||
<l:ajax>
|
||||
<form action="${submissionUrl}" method="post">
|
||||
<table>
|
||||
|
|
|
@ -31,8 +31,8 @@ THE SOFTWARE.
|
|||
<st:include page="sidepanel.jelly" />
|
||||
<l:breadcrumb title="${%Changes}" />
|
||||
<l:main-panel>
|
||||
<j:set var="from" value="${request.getParameter('from')}"/>
|
||||
<j:set var="to" value="${request.getParameter('to')}"/>
|
||||
<j:set var="from" value="${request2.getParameter('from')}"/>
|
||||
<j:set var="to" value="${request2.getParameter('to')}"/>
|
||||
<j:set var="browser" value="${it.scm.effectiveBrowser}"/> <!-- for compatibility; newer project-changes.jelly override this -->
|
||||
|
||||
<h1>
|
||||
|
|
|
@ -111,7 +111,7 @@ div {
|
|||
section(class: "empty-state-section") {
|
||||
ul(class: "empty-state-section-list") {
|
||||
li(class: "content-block") {
|
||||
a(href: "${rootURL}/${app.securityRealm.loginUrl}?from=${request.requestURI}",
|
||||
a(href: "${rootURL}/${app.securityRealm.loginUrl}?from=${request2.requestURI}",
|
||||
class: "content-block__link") {
|
||||
span(_("Log in to Jenkins"))
|
||||
span(class: "trailing-icon") {
|
||||
|
|
|
@ -32,7 +32,7 @@ THE SOFTWARE.
|
|||
<l:main-panel>
|
||||
<f:form method="post" action="doCreateItem" name="config" class="jenkins-form">
|
||||
<f:entry title="${%Name}" help="${requestScope.descriptor.getHelpFile('name')}">
|
||||
<f:textbox name="name" value="${request.getParameter('name')}" clazz="required" checkMessage="${%Name is mandatory}"/>
|
||||
<f:textbox name="name" value="${request2.getParameter('name')}" clazz="required" checkMessage="${%Name is mandatory}"/>
|
||||
</f:entry>
|
||||
|
||||
<!-- main body of the configuration -->
|
||||
|
@ -40,7 +40,7 @@ THE SOFTWARE.
|
|||
<st:include class="${requestScope.descriptor.clazz}" page="configure-entries.jelly" />
|
||||
|
||||
<f:bottomButtonBar>
|
||||
<input type="hidden" name="type" value="${request.getParameter('mode')}"/>
|
||||
<input type="hidden" name="type" value="${request2.getParameter('mode')}"/>
|
||||
|
||||
<f:submit value="${%Save}"/>
|
||||
</f:bottomButtonBar>
|
||||
|
|
|
@ -30,7 +30,7 @@ THE SOFTWARE.
|
|||
<j:invoke var="buildClass" on="${currentThread.contextClassLoader}" method="loadClass">
|
||||
<j:arg value="hudson.model.Run" />
|
||||
</j:invoke>
|
||||
<j:set var="build" value="${request.findAncestorObject(buildClass)}" />
|
||||
<j:set var="build" value="${request2.findAncestorObject(buildClass)}" />
|
||||
<st:include page="sidepanel.jelly" it="${build}" />
|
||||
<l:main-panel>
|
||||
<t:buildCaption it="${build}">${title}</t:buildCaption>
|
||||
|
|
|
@ -40,7 +40,7 @@ THE SOFTWARE.
|
|||
<l:main-panel>
|
||||
<h1>${it.job.pronoun} ${it.job.displayName}</h1>
|
||||
<p class="jenkins-description">${%description}</p>
|
||||
<j:set var="delay" value="${request.getParameter('delay')}" />
|
||||
<j:set var="delay" value="${request2.getParameter('delay')}" />
|
||||
<f:form method="post" action="build${empty(delay)?'':'?delay='+delay}" name="parameters"
|
||||
tableClass="parameters" class="jenkins-form">
|
||||
<j:forEach var="parameterDefinition" items="${it.parameterDefinitions}">
|
||||
|
|
|
@ -20,7 +20,7 @@
|
|||
<tr><td>
|
||||
<div id="delete-error" style="display: none">
|
||||
<pre>
|
||||
${request.getAttribute('stackTraces')}
|
||||
${request2.getAttribute('stackTraces')}
|
||||
</pre>
|
||||
</div>
|
||||
</td></tr>
|
||||
|
|
|
@ -44,7 +44,7 @@ THE SOFTWARE.
|
|||
</l:main-panel>
|
||||
<l:header>
|
||||
<!-- for screen resolution detection -->
|
||||
<script id="screenResolution-script" data-use-secure-cookie="${request.secure}"/>
|
||||
<script id="screenResolution-script" data-use-secure-cookie="${request2.secure}"/>
|
||||
<st:adjunct includes="hudson.model.View.screen-resolution"/>
|
||||
</l:header>
|
||||
</l:layout>
|
||||
|
|
|
@ -27,7 +27,7 @@ THE SOFTWARE.
|
|||
-->
|
||||
<?jelly escape-by-default='true'?>
|
||||
<j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler" xmlns:d="jelly:define" xmlns:l="/lib/layout" xmlns:t="/lib/hudson" xmlns:f="/lib/form" xmlns:i="jelly:fmt">
|
||||
<j:set var="q" value="${request.getParameter('q')}"/>
|
||||
<j:set var="q" value="${request2.getParameter('q')}"/>
|
||||
<j:new var="h" className="hudson.Functions" /><!-- needed for printing title. -->
|
||||
<l:layout title="${%Search for} '${q}'">
|
||||
<l:main-panel>
|
||||
|
@ -48,7 +48,7 @@ THE SOFTWARE.
|
|||
</j:forEach>
|
||||
</ol>
|
||||
<j:if test="${items.hasMoreResults()}">
|
||||
<j:set var="max" value="${request.hasParameter('max')?request.getParameter('max'):100}"/>
|
||||
<j:set var="max" value="${request2.hasParameter('max')?request2.getParameter('max'):100}"/>
|
||||
<j:choose>
|
||||
<j:when test="${max > it.getMaxSearchSize()}">
|
||||
<em>Only showing the first ${it.getMaxSearchSize()} results,
|
||||
|
|
|
@ -10,7 +10,7 @@ def f=namespace(lib.FormTagLib)
|
|||
def l=namespace(lib.LayoutTagLib)
|
||||
def st=namespace("jelly:stapler")
|
||||
|
||||
l.layout(permission:app.SYSTEM_READ, title:my.displayName, cssclass:request.getParameter('decorate'), type:"one-column") {
|
||||
l.layout(permission:app.SYSTEM_READ, title:my.displayName, cssclass:request2.getParameter('decorate'), type:"one-column") {
|
||||
l.main_panel {
|
||||
l.app_bar(title: my.displayName)
|
||||
|
||||
|
|
|
@ -34,13 +34,13 @@ THE SOFTWARE.
|
|||
needed to generate a session if non exists,
|
||||
without it we would add ";jsessionid=" to the url which will result in a 404
|
||||
-->
|
||||
<j:set var="_" value="${request.getSession()}"/>
|
||||
<j:set var="_" value="${request2.getSession()}"/>
|
||||
<x:doctype name="html"/>
|
||||
<!-- in case of error we want to surround the form elements with an error hint -->
|
||||
<j:set var="inputClass" value="${data.errorMessage!=null ? 'jenkins-input--error' : ''}"/>
|
||||
<j:set var="simpleDecorator" value="${h.simplePageDecorator}"/>
|
||||
<j:set var="simpleDecorators" value="${h.simplePageDecorators}"/>
|
||||
<html lang="${request.getLocale().toLanguageTag()}">
|
||||
<html lang="${request2.getLocale().toLanguageTag()}">
|
||||
<head data-rooturl="${rootURL}" data-resurl="${resURL}" data-imagesurl="${imagesURL}" resURL="${resURL}">
|
||||
<title>${%Register} - Jenkins</title>
|
||||
<!-- we do not want bots on this page -->
|
||||
|
|
|
@ -65,7 +65,7 @@ THE SOFTWARE.
|
|||
|
||||
<h2>Request Headers</h2>
|
||||
<table>
|
||||
<j:forEach var="n" items="${request.getHeaderNames()}">
|
||||
<j:forEach var="n" items="${request2.getHeaderNames()}">
|
||||
<j:choose>
|
||||
<j:when test="${it.isHeaderDangerous(n)}" >
|
||||
<tr>
|
||||
|
|
|
@ -32,7 +32,7 @@ THE SOFTWARE.
|
|||
<!-- get default/common page variable -->
|
||||
${h.initPageVariables(context)}
|
||||
<x:doctype name="html" />
|
||||
<html lang="${request.getLocale().toLanguageTag()}">
|
||||
<html lang="${request2.getLocale().toLanguageTag()}">
|
||||
<head data-rooturl="${rootURL}" data-resurl="${resURL}" data-imagesurl="${imagesURL}" resURL="${resURL}">
|
||||
<title>${%Starting Jenkins}</title>
|
||||
<!-- we do not want bots on this page -->
|
||||
|
|
|
@ -32,7 +32,7 @@ THE SOFTWARE.
|
|||
<!-- get default/common page variable -->
|
||||
${h.initPageVariables(context)}
|
||||
<x:doctype name="html" />
|
||||
<html lang="${request.getLocale().toLanguageTag()}">
|
||||
<html lang="${request2.getLocale().toLanguageTag()}">
|
||||
<head data-rooturl="${rootURL}" data-resurl="${resURL}" data-imagesurl="${imagesURL}" resURL="${resURL}">
|
||||
<title>${%Restarting Jenkins}</title>
|
||||
<!-- we do not want bots on this page -->
|
||||
|
|
|
@ -37,7 +37,7 @@ THE SOFTWARE.
|
|||
<st:include it="${requestScope.instance}" class="${requestScope.descriptor.clazz}" page="config.jelly" optional="true" />
|
||||
|
||||
<f:bottomButtonBar>
|
||||
<input type="hidden" name="cloudDescriptorName" value="${request.getParameter('mode')}"/>
|
||||
<input type="hidden" name="cloudDescriptorName" value="${request2.getParameter('mode')}"/>
|
||||
|
||||
<f:submit value="${%Save}"/>
|
||||
</f:bottomButtonBar>
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<l:main-panel>
|
||||
<st:include page="client-scripts" />
|
||||
<form action="${app.instance.securityRealm.authenticationGatewayUrl}" method="POST">
|
||||
<input type="hidden" name="from" value="${request.getParameter('from')}" />
|
||||
<input type="hidden" name="from" value="${request2.getParameter('from')}" />
|
||||
<div class="plugin-setup-wizard bootstrap-3">
|
||||
<div class="modal fade in" style="display: block;">
|
||||
<div class="modal-dialog">
|
||||
|
|
|
@ -28,8 +28,8 @@ THE SOFTWARE.
|
|||
This is the page designated by web.xml to handle 404 errors. Show a nice error message and suggest logging in if applicable.
|
||||
Generally kept very similar to 'oops.jelly'.
|
||||
-->
|
||||
<st:statusCode value="${response.getStatus()}" />
|
||||
${request.session.setAttribute('from', request.getAttribute('jakarta.servlet.error.request_uri'))}
|
||||
<st:statusCode value="${response2.getStatus()}" />
|
||||
${request2.session.setAttribute('from', request2.getAttribute('jakarta.servlet.error.request_uri'))}
|
||||
<l:layout title="${%title(jakarta.servlet.error.message?:'Not Found')}" type="one-column">
|
||||
<l:header />
|
||||
<l:main-panel>
|
||||
|
@ -39,7 +39,7 @@ THE SOFTWARE.
|
|||
</h1>
|
||||
<div id="error-description" style="text-align: center">
|
||||
<h2>${%title(jakarta.servlet.error.message?:'Not Found')}</h2>
|
||||
<j:if test="${!request.getAttribute('jenkins.security.ResourceDomainRootAction.error')}">
|
||||
<j:if test="${!request2.getAttribute('jenkins.security.ResourceDomainRootAction.error')}">
|
||||
<p>
|
||||
<j:choose>
|
||||
<j:when test="${app.useSecurity}">
|
||||
|
|
|
@ -36,7 +36,7 @@ THE SOFTWARE.
|
|||
<st:setHeader name="Referrer-Policy" value="same-origin" />
|
||||
<st:setHeader name="Cross-Origin-Opener-Policy" value="same-origin" />
|
||||
<x:doctype name="html" />
|
||||
<st:statusCode value="${response.getStatus()}" />
|
||||
<st:statusCode value="${response2.getStatus()}" />
|
||||
<html>
|
||||
<head>
|
||||
<title>${%title(jakarta.servlet.error.message?:'Not Found')}</title>
|
||||
|
|
|
@ -29,7 +29,7 @@ THE SOFTWARE.
|
|||
<st:setHeader name="Expires" value="0" />
|
||||
<st:setHeader name="Cache-Control" value="no-cache,no-store,must-revalidate" />
|
||||
<!-- needed for cli -->
|
||||
<j:if test="${request.servletPath=='/' || request.servletPath==''}">
|
||||
<j:if test="${request2.servletPath=='/' || request2.servletPath==''}">
|
||||
${h.advertiseHeaders(response)}
|
||||
<j:forEach var="pd" items="${h.pageDecorators}">
|
||||
<st:include it="${pd}" page="httpHeaders.jelly" optional="true"/>
|
||||
|
@ -39,19 +39,19 @@ THE SOFTWARE.
|
|||
needed to generate a session if non exists,
|
||||
without it we would add ";jsessionid=" to the url which will result in a 404
|
||||
-->
|
||||
<j:set var="_" value="${request.getSession()}"/>
|
||||
<j:set var="_" value="${request2.getSession()}"/>
|
||||
<!-- get default/common page variable -->
|
||||
${h.initPageVariables(context)}
|
||||
<x:doctype name="html" />
|
||||
<!-- needed for the redirect after login -->
|
||||
<j:set var="from" value="${error ? request.session.getAttribute('from') : request.getParameter('from')}"/>
|
||||
<j:set var="from" value="${error ? request2.session.getAttribute('from') : request2.getParameter('from')}"/>
|
||||
<!-- in case of error we want to surround the form elements with an error hint -->
|
||||
<j:set var="inputClass" value="${error ? 'jenkins-input--error' : ''}"/>
|
||||
<j:set var="simpleDecorator" value="${h.simplePageDecorator}"/>
|
||||
<j:set var="simpleDecorators" value="${h.simplePageDecorators}"/>
|
||||
|
||||
<!-- real deal starting here -->
|
||||
<html lang="${request.getLocale().toLanguageTag()}">
|
||||
<html lang="${request2.getLocale().toLanguageTag()}">
|
||||
<head data-rooturl="${rootURL}" data-resurl="${resURL}" data-imagesurl="${imagesURL}" resURL="${resURL}">
|
||||
<title>${%signIn} - Jenkins</title>
|
||||
<!-- we do not want bots on this page -->
|
||||
|
|
|
@ -27,7 +27,7 @@ THE SOFTWARE.
|
|||
<!--
|
||||
This is the page designated by web.xml and UncaughtExceptionHandler to process an exception thrown by us.
|
||||
-->
|
||||
<st:statusCode value="${response.getStatus()}" />
|
||||
<st:statusCode value="${response2.getStatus()}" />
|
||||
<l:layout title="Jenkins" type="one-column">
|
||||
<l:header />
|
||||
<l:main-panel>
|
||||
|
@ -36,12 +36,12 @@ THE SOFTWARE.
|
|||
</h1>
|
||||
<div id="error-description">
|
||||
<h2 style="text-align: center">${%problemHappened}</h2>
|
||||
<p style="text-align: center">Logging ID=${request.getAttribute('jenkins.exception.id')}</p>
|
||||
<p style="text-align: center">Logging ID=${request2.getAttribute('jenkins.exception.id')}</p>
|
||||
</div>
|
||||
<j:if test="${app.shouldShowStackTrace()}">
|
||||
<p>${%checkJIRA} ${%vote} ${%pleaseReport} ${%stackTracePlease} ${%checkML}</p>
|
||||
<h2>${%Stack trace}</h2>
|
||||
<pre style="margin:2em; clear:both">${h.printThrowable(request.getAttribute('jakarta.servlet.error.exception'))}</pre>
|
||||
<pre style="margin:2em; clear:both">${h.printThrowable(request2.getAttribute('jakarta.servlet.error.exception'))}</pre>
|
||||
</j:if>
|
||||
</l:main-panel>
|
||||
</l:layout>
|
||||
|
|
|
@ -44,25 +44,25 @@ THE SOFTWARE.
|
|||
<form action="projectRelationship" method="get">
|
||||
<div class="jenkins-form-item">
|
||||
<label class="jenkins-form-label" for="lhs">${%upstream project}:</label>
|
||||
<f:combobox id="lhs" fillUrl="fillJobNameItems" name="lhs" value="${request.getParameter('lhs')}"/>
|
||||
<f:combobox id="lhs" fillUrl="fillJobNameItems" name="lhs" value="${request2.getParameter('lhs')}"/>
|
||||
</div>
|
||||
|
||||
<div class="jenkins-form-item">
|
||||
<label class="jenkins-form-label" for="rhs">${%downstream project}:</label>
|
||||
<f:combobox id="rhs" fillUrl="fillJobNameItems" name="rhs" value="${request.getParameter('rhs')}"/>
|
||||
<f:combobox id="rhs" fillUrl="fillJobNameItems" name="rhs" value="${request2.getParameter('rhs')}"/>
|
||||
</div>
|
||||
<f:submit value="${%Compare}"/>
|
||||
|
||||
<j:if test="${!empty(request.getParameter('lhs')) and !empty(request.getParameter('rhs'))}">
|
||||
<j:set var="jl" value="${app.getItemByFullName(request.getParameter('lhs'))}"/>
|
||||
<j:set var="jr" value="${app.getItemByFullName(request.getParameter('rhs'))}"/>
|
||||
<j:if test="${!empty(request2.getParameter('lhs')) and !empty(request2.getParameter('rhs'))}">
|
||||
<j:set var="jl" value="${app.getItemByFullName(request2.getParameter('lhs'))}"/>
|
||||
<j:set var="jr" value="${app.getItemByFullName(request2.getParameter('rhs'))}"/>
|
||||
|
||||
<j:choose>
|
||||
<j:when test="${jl==null}">
|
||||
<p class="error">No such project '${request.getParameter('lhs')}'</p>
|
||||
<p class="error">No such project '${request2.getParameter('lhs')}'</p>
|
||||
</j:when>
|
||||
<j:when test="${jr==null}">
|
||||
<p class="error">No such project '${request.getParameter('rhs')}'</p>
|
||||
<p class="error">No such project '${request2.getParameter('rhs')}'</p>
|
||||
</j:when>
|
||||
<j:otherwise>
|
||||
<table width="100%">
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
id="user-seed-property-reset-seed"
|
||||
data-target-url="${descriptor.descriptorFullUrl}/renewSessionSeed"
|
||||
data-confirm="${%resetSeed.confirmation}"
|
||||
data-redirect-url="${request.contextPath}/"
|
||||
data-redirect-url="${request2.contextPath}/"
|
||||
data-confirm-title="${%resetSeed.confirmationTitle}">
|
||||
${%resetSeed.button}
|
||||
</button>
|
||||
|
|
|
@ -41,8 +41,8 @@ THE SOFTWARE.
|
|||
</st:documentation>
|
||||
|
||||
<st:once>
|
||||
<script type="text/javascript" src="${request.contextPath}/scripts/utilities.js"/>
|
||||
<script type="text/javascript" src="${request.contextPath}/scripts/combobox.js"/>
|
||||
<script type="text/javascript" src="${request2.contextPath}/scripts/utilities.js"/>
|
||||
<script type="text/javascript" src="${request2.contextPath}/scripts/combobox.js"/>
|
||||
</st:once>
|
||||
|
||||
<f:prepareDatabinding/>
|
||||
|
|
|
@ -46,7 +46,7 @@ THE SOFTWARE.
|
|||
</p>
|
||||
|
||||
<form action="script" method="post">
|
||||
<textarea id="script" name="script" class="script">${request.getParameter('script')}</textarea>
|
||||
<textarea id="script" name="script" class="script">${request2.getParameter('script')}</textarea>
|
||||
<div align="right">
|
||||
<f:submit value="${%Run}"/>
|
||||
</div>
|
||||
|
|
|
@ -1,10 +1,16 @@
|
|||
/**
|
||||
* Use the `widget-refresh-reference` class on an element with `data-id` and `data-url` attributes.
|
||||
* The content from the URL will be used to replace the element with the specified ID.
|
||||
* Usually the URL content is an element with the same ID as specified here, to allow continuous updates.
|
||||
* This is primarily used for sidepanel widgets, but not exclusively.
|
||||
*/
|
||||
Behaviour.specify(
|
||||
".widget-refresh-reference",
|
||||
"widget-refresh",
|
||||
0,
|
||||
function (e) {
|
||||
var id = e.getAttribute("data-id");
|
||||
var url = e.getAttribute("data-url");
|
||||
let id = e.getAttribute("data-id");
|
||||
let url = e.getAttribute("data-url");
|
||||
refreshPart(id, url);
|
||||
},
|
||||
);
|
||||
|
|
|
@ -41,7 +41,7 @@ THE SOFTWARE.
|
|||
|
||||
<div id="breadcrumbBar" class="jenkins-breadcrumbs" aria-label="breadcrumb">
|
||||
<ol class="jenkins-breadcrumbs__list" id="breadcrumbs">
|
||||
<j:forEach var="anc" items="${request.ancestors}" indexVar="index">
|
||||
<j:forEach var="anc" items="${request2.ancestors}" indexVar="index">
|
||||
<j:if test="${h.isModel(anc.object) and anc.prev.url!=anc.url}">
|
||||
<j:set var="mode" value="breadcrumbs" />
|
||||
<l:breadcrumb title="${anc.object == app ? '%Dashboard' : anc.object.displayName}"
|
||||
|
|
|
@ -90,9 +90,9 @@ THE SOFTWARE.
|
|||
<j:set var="layoutType" value="${attrs.type}"/>
|
||||
</j:if>
|
||||
|
||||
<j:set var="_" value="${request.getSession()}"/>
|
||||
<j:set var="_" value="${request2.getSession()}"/>
|
||||
<j:set var="extensionsAvailable" value="${h.extensionsAvailable}"/>
|
||||
<j:if test="${request.servletPath=='/' || request.servletPath==''}">
|
||||
<j:if test="${request2.servletPath=='/' || request2.servletPath==''}">
|
||||
${h.advertiseHeaders(response)}
|
||||
<j:if test="${extensionsAvailable}">
|
||||
<j:forEach var="pd" items="${h.pageDecorators}">
|
||||
|
|
|
@ -239,13 +239,6 @@
|
|||
<Class name="jenkins.util.xml.XMLUtils"/>
|
||||
</Or>
|
||||
</And>
|
||||
<And>
|
||||
<Bug pattern="INSECURE_COOKIE"/>
|
||||
<Or>
|
||||
<Class name="hudson.security.SecurityRealm"/>
|
||||
<Class name="jenkins.model.Jenkins"/>
|
||||
</Or>
|
||||
</And>
|
||||
<And>
|
||||
<Bug pattern="LI_LAZY_INIT_STATIC"/>
|
||||
<Class name="hudson.triggers.Trigger"/>
|
||||
|
|
|
@ -81,13 +81,16 @@ public class MarkupTextTest {
|
|||
* Start/end tag nesting should be correct regardless of the order tags are added.
|
||||
*/
|
||||
@Test
|
||||
public void adjacent() {
|
||||
public void addMarkupInOrder() {
|
||||
MarkupText text = new MarkupText("abcdef");
|
||||
text.addMarkup(0, 3, "$", "$");
|
||||
text.addMarkup(3, 6, "#", "#");
|
||||
assertEquals("$abc$#def#", text.toString(false));
|
||||
}
|
||||
|
||||
text = new MarkupText("abcdef");
|
||||
@Test
|
||||
public void addMarkupInReversedOrder() {
|
||||
MarkupText text = new MarkupText("abcdef");
|
||||
text.addMarkup(3, 6, "#", "#");
|
||||
text.addMarkup(0, 3, "$", "$");
|
||||
assertEquals("$abc$#def#", text.toString(false));
|
||||
|
|
|
@ -27,18 +27,22 @@ package hudson.scheduler;
|
|||
import static java.util.Calendar.MONDAY;
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.instanceOf;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.assertThrows;
|
||||
|
||||
import antlr.ANTLRException;
|
||||
import java.text.DateFormat;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Calendar;
|
||||
import java.util.GregorianCalendar;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.TimeZone;
|
||||
import java.util.function.Function;
|
||||
import org.junit.Test;
|
||||
import org.jvnet.hudson.test.Issue;
|
||||
|
||||
|
@ -340,6 +344,24 @@ public class CronTabTest {
|
|||
assertEquals("[35, 56]", times.toString());
|
||||
}
|
||||
|
||||
@Test public void floorCeilMinuteGranularity() throws Exception {
|
||||
var tab = new CronTab("*/5 * * * *");
|
||||
assertFloorCeil(tab, new GregorianCalendar(2025, 2, 4, 14, 15, 23), "2025-03-04T14:15:00", "2025-03-04T14:20:00");
|
||||
assertFloorCeil(tab, new GregorianCalendar(2025, 2, 4, 14, 19, 23), "2025-03-04T14:15:00", "2025-03-04T14:20:00");
|
||||
assertFloorCeil(tab, new GregorianCalendar(2025, 2, 4, 14, 16, 0), "2025-03-04T14:15:00", "2025-03-04T14:20:00");
|
||||
assertFloorCeil(tab, new GregorianCalendar(2025, 2, 4, 14, 19, 0), "2025-03-04T14:15:00", "2025-03-04T14:20:00");
|
||||
assertFloorCeil(tab, new GregorianCalendar(2025, 2, 4, 14, 15, 0), "2025-03-04T14:15:00", "2025-03-04T14:15:00");
|
||||
}
|
||||
|
||||
private static void assertFloorCeil(CronTab tab, Calendar now, String expectedFloor, String expectedCeil) {
|
||||
Function<Calendar, String> fmt = c -> ZonedDateTime.ofInstant(c.toInstant(), c.getTimeZone().toZoneId()).format(DateTimeFormatter.ISO_LOCAL_DATE_TIME);
|
||||
var nowFormatted = fmt.apply(now);
|
||||
var nowClone = (Calendar) now.clone();
|
||||
assertThat("floor of " + nowFormatted, fmt.apply(tab.floor(nowClone)), is(expectedFloor));
|
||||
nowClone = (Calendar) now.clone();
|
||||
assertThat("ceil of " + nowFormatted, fmt.apply(tab.ceil(nowClone)), is(expectedCeil));
|
||||
}
|
||||
|
||||
@Issue("SECURITY-790")
|
||||
@Test(timeout = 1000L) public void testLongMonths() throws Exception {
|
||||
Calendar cal = Calendar.getInstance();
|
||||
|
|
|
@ -7,10 +7,14 @@ import static org.junit.Assert.assertTrue;
|
|||
|
||||
import hudson.FilePath;
|
||||
import hudson.Functions;
|
||||
import hudson.Util;
|
||||
import hudson.util.TextFile;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.MalformedInputException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.security.SecureRandom;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.TemporaryFolder;
|
||||
|
@ -20,6 +24,8 @@ public class DefaultConfidentialStoreTest {
|
|||
@Rule
|
||||
public TemporaryFolder tmpRule = new TemporaryFolder();
|
||||
|
||||
private final SecureRandom sr = new SecureRandom();
|
||||
|
||||
@Test
|
||||
public void roundtrip() throws Exception {
|
||||
File tmp = new File(tmpRule.getRoot(), "tmp"); // let ConfidentialStore create a directory
|
||||
|
@ -27,20 +33,8 @@ public class DefaultConfidentialStoreTest {
|
|||
DefaultConfidentialStore store = new DefaultConfidentialStore(tmp);
|
||||
ConfidentialKey key = new ConfidentialKey("test") {};
|
||||
|
||||
// basic roundtrip
|
||||
String str = "Hello world!";
|
||||
store.store(key, str.getBytes(StandardCharsets.UTF_8));
|
||||
assertEquals(str, new String(store.load(key), StandardCharsets.UTF_8));
|
||||
|
||||
// data storage should have some stuff
|
||||
assertTrue(new File(tmp, "test").exists());
|
||||
assertTrue(new File(tmp, "master.key").exists());
|
||||
|
||||
assertThrows(MalformedInputException.class, () -> Files.readString(tmp.toPath().resolve("test"), StandardCharsets.UTF_8)); // the data shouldn't be a plain text, obviously
|
||||
|
||||
if (!Functions.isWindows()) {
|
||||
assertEquals(0700, new FilePath(tmp).mode() & 0777); // should be read only
|
||||
}
|
||||
roundTrip(store, key, tmp);
|
||||
|
||||
// if the master key changes, we should gracefully fail to load the store
|
||||
new File(tmp, "master.key").delete();
|
||||
|
@ -49,4 +43,42 @@ public class DefaultConfidentialStoreTest {
|
|||
assertNull(store2.load(key));
|
||||
}
|
||||
|
||||
private static void roundTrip(DefaultConfidentialStore store, ConfidentialKey key, File tmp) throws IOException, InterruptedException {
|
||||
// basic roundtrip
|
||||
String str = "Hello world!";
|
||||
store.store(key, str.getBytes(StandardCharsets.UTF_8));
|
||||
assertEquals(str, new String(store.load(key), StandardCharsets.UTF_8));
|
||||
|
||||
// data storage should have some stuff
|
||||
assertTrue(new File(tmp, "test").exists());
|
||||
|
||||
assertThrows(MalformedInputException.class, () -> Files.readString(tmp.toPath().resolve("test"), StandardCharsets.UTF_8)); // the data shouldn't be a plain text, obviously
|
||||
|
||||
if (!Functions.isWindows()) {
|
||||
assertEquals(0700, new FilePath(tmp).mode() & 0777); // should be read only
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void masterKeyGeneratedBeforehand() throws IOException, InterruptedException {
|
||||
File external = new File(tmpRule.getRoot(), "external");
|
||||
File tmp = new File(tmpRule.getRoot(), "tmp");
|
||||
var masterKeyFile = new File(external, "master.key");
|
||||
new TextFile(masterKeyFile).write(Util.toHexString(randomBytes(128)));
|
||||
System.setProperty(DefaultConfidentialStore.MASTER_KEY_FILE_SYSTEM_PROPERTY, masterKeyFile.getAbsolutePath());
|
||||
System.setProperty(DefaultConfidentialStore.MASTER_KEY_READONLY_SYSTEM_PROPERTY_NAME, "true");
|
||||
DefaultConfidentialStore store = new DefaultConfidentialStore(tmp);
|
||||
ConfidentialKey key = new ConfidentialKey("test") {};
|
||||
roundTrip(store, key, tmp);
|
||||
// With this configuration, the master key file deletion is fatal
|
||||
masterKeyFile.delete();
|
||||
assertThrows(IOException.class, () -> new DefaultConfidentialStore(tmp));
|
||||
}
|
||||
|
||||
private byte[] randomBytes(int size) {
|
||||
byte[] random = new byte[size];
|
||||
sr.nextBytes(random);
|
||||
return random;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -26,12 +26,12 @@
|
|||
"@babel/cli": "7.26.4",
|
||||
"@babel/core": "7.26.7",
|
||||
"@babel/preset-env": "7.26.7",
|
||||
"@eslint/js": "9.19.0",
|
||||
"@eslint/js": "9.20.0",
|
||||
"babel-loader": "9.2.1",
|
||||
"clean-webpack-plugin": "4.0.0",
|
||||
"css-loader": "7.1.2",
|
||||
"css-minimizer-webpack-plugin": "7.0.0",
|
||||
"eslint": "9.19.0",
|
||||
"eslint": "9.20.0",
|
||||
"eslint-config-prettier": "10.0.1",
|
||||
"eslint-formatter-checkstyle": "8.40.0",
|
||||
"globals": "15.14.0",
|
||||
|
@ -42,7 +42,7 @@
|
|||
"postcss-preset-env": "10.1.3",
|
||||
"postcss-scss": "4.0.9",
|
||||
"prettier": "3.4.2",
|
||||
"sass": "1.83.4",
|
||||
"sass": "1.84.0",
|
||||
"sass-loader": "16.0.4",
|
||||
"style-loader": "4.0.0",
|
||||
"stylelint": "16.14.1",
|
||||
|
|
4
pom.xml
4
pom.xml
|
@ -73,9 +73,9 @@ THE SOFTWARE.
|
|||
</issueManagement>
|
||||
|
||||
<properties>
|
||||
<revision>2.496</revision>
|
||||
<revision>2.498</revision>
|
||||
<changelist>-SNAPSHOT</changelist>
|
||||
<project.build.outputTimestamp>2025-01-28T13:58:52Z</project.build.outputTimestamp>
|
||||
<project.build.outputTimestamp>2025-02-11T14:10:23Z</project.build.outputTimestamp>
|
||||
|
||||
<!-- configuration for patch tracker plugin -->
|
||||
<project.patchManagement.system>github</project.patchManagement.system>
|
||||
|
|
10
test/pom.xml
10
test/pom.xml
|
@ -118,7 +118,7 @@ THE SOFTWARE.
|
|||
<!-- requireUpperBoundDeps via matrix-project and junit -->
|
||||
<groupId>org.jenkins-ci.plugins</groupId>
|
||||
<artifactId>script-security</artifactId>
|
||||
<version>1369.v9b_98a_4e95b_2d</version>
|
||||
<version>1373.vb_b_4a_a_c26fa_00</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.jenkins-ci.plugins.workflow</groupId>
|
||||
|
@ -134,7 +134,7 @@ THE SOFTWARE.
|
|||
<!-- Required by plugin-util-api -->
|
||||
<groupId>org.jenkins-ci.plugins.workflow</groupId>
|
||||
<artifactId>workflow-support</artifactId>
|
||||
<version>944.v5a_859593b_98a_</version>
|
||||
<version>946.v2a_79d8a_4b_e14</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</dependencyManagement>
|
||||
|
@ -224,13 +224,13 @@ THE SOFTWARE.
|
|||
<dependency>
|
||||
<groupId>org.jenkins-ci.plugins</groupId>
|
||||
<artifactId>credentials</artifactId>
|
||||
<version>1405.vb_cda_74a_f8974</version>
|
||||
<version>1408.va_622a_b_f5b_1b_1</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.jenkins-ci.plugins</groupId>
|
||||
<artifactId>junit</artifactId>
|
||||
<version>1312.v1a_235a_b_94a_31</version>
|
||||
<version>1314.vd966e9a_88895</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
|
@ -344,7 +344,7 @@ THE SOFTWARE.
|
|||
<artifactItem>
|
||||
<groupId>io.jenkins.plugins</groupId>
|
||||
<artifactId>design-library</artifactId>
|
||||
<version>354.v87d9d5804b_c1</version>
|
||||
<version>355.v0f007356e15d</version>
|
||||
<type>hpi</type>
|
||||
<outputDirectory>${project.build.outputDirectory}/plugins</outputDirectory>
|
||||
<destFileName>design-library.jpi</destFileName>
|
||||
|
|
|
@ -102,7 +102,7 @@ public class GlobalComputerRetentionCheckIntervalConfigurationTest {
|
|||
JSONObject json = new JSONObject();
|
||||
json.element("computerRetentionCheckInterval", 5);
|
||||
try {
|
||||
c.configure(Stapler.getCurrentRequest(), json);
|
||||
c.configure(Stapler.getCurrentRequest2(), json);
|
||||
} catch (Descriptor.FormException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
@ -121,7 +121,7 @@ public class GlobalComputerRetentionCheckIntervalConfigurationTest {
|
|||
JSONObject json = new JSONObject();
|
||||
json.element("computerRetentionCheckInterval", interval);
|
||||
try {
|
||||
c.configure(Stapler.getCurrentRequest(), json);
|
||||
c.configure(Stapler.getCurrentRequest2(), json);
|
||||
throw new RuntimeException("expected .configure() to throw");
|
||||
} catch (Descriptor.FormException e) {
|
||||
assertEquals(e.getMessage(), message);
|
||||
|
|
|
@ -13,6 +13,7 @@ import static org.junit.Assert.assertTrue;
|
|||
import edu.umd.cs.findbugs.annotations.CheckForNull;
|
||||
import edu.umd.cs.findbugs.annotations.NonNull;
|
||||
import hudson.ExtensionList;
|
||||
import hudson.Util;
|
||||
import hudson.model.UnprotectedRootAction;
|
||||
import hudson.security.csrf.CrumbExclusion;
|
||||
import jakarta.servlet.FilterChain;
|
||||
|
@ -31,7 +32,6 @@ import java.util.concurrent.TimeUnit;
|
|||
import java.util.logging.Level;
|
||||
import java.util.regex.Pattern;
|
||||
import net.sf.json.JSONObject;
|
||||
import org.apache.commons.codec.digest.DigestUtils;
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
|
@ -99,7 +99,7 @@ public class TelemetryTest {
|
|||
.atMost(10, TimeUnit.SECONDS)
|
||||
.until(() -> types, hasItem("test-data"));
|
||||
//90ecf3ce1cd5ba1e5ad3cde7ad08a941e884f2e4d9bd463361715abab8efedc5
|
||||
assertThat(correlators, hasItem(DigestUtils.sha256Hex(correlationId + "test-data")));
|
||||
assertThat(correlators, hasItem(Util.getHexOfSHA256DigestOf(correlationId + "test-data")));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
22
war/pom.xml
22
war/pom.xml
|
@ -309,14 +309,14 @@ THE SOFTWARE.
|
|||
<!-- dependency of command-launcher, junit, matrix-project, and workflow-support -->
|
||||
<groupId>org.jenkins-ci.plugins</groupId>
|
||||
<artifactId>script-security</artifactId>
|
||||
<version>1369.v9b_98a_4e95b_2d</version>
|
||||
<version>1373.vb_b_4a_a_c26fa_00</version>
|
||||
<type>hpi</type>
|
||||
</artifactItem>
|
||||
<artifactItem>
|
||||
<!-- detached after 1.577 -->
|
||||
<groupId>org.jenkins-ci.plugins</groupId>
|
||||
<artifactId>junit</artifactId>
|
||||
<version>1312.v1a_235a_b_94a_31</version>
|
||||
<version>1314.vd966e9a_88895</version>
|
||||
<type>hpi</type>
|
||||
</artifactItem>
|
||||
<artifactItem>
|
||||
|
@ -344,7 +344,7 @@ THE SOFTWARE.
|
|||
<!-- dependency of junit -->
|
||||
<groupId>io.jenkins.plugins</groupId>
|
||||
<artifactId>checks-api</artifactId>
|
||||
<version>2.2.1</version>
|
||||
<version>2.2.2</version>
|
||||
<type>hpi</type>
|
||||
</artifactItem>
|
||||
|
||||
|
@ -352,7 +352,7 @@ THE SOFTWARE.
|
|||
<!-- dependency of checks-api and plugin-util-api -->
|
||||
<groupId>org.jenkins-ci.plugins.workflow</groupId>
|
||||
<artifactId>workflow-support</artifactId>
|
||||
<version>944.v5a_859593b_98a_</version>
|
||||
<version>946.v2a_79d8a_4b_e14</version>
|
||||
<type>hpi</type>
|
||||
</artifactItem>
|
||||
|
||||
|
@ -462,7 +462,7 @@ THE SOFTWARE.
|
|||
<!-- detached after 2.281 -->
|
||||
<groupId>org.jenkins-ci.modules</groupId>
|
||||
<artifactId>sshd</artifactId>
|
||||
<version>3.330.vc866a_8389b_58</version>
|
||||
<version>3.353.v2b_d33c46e970</version>
|
||||
<type>hpi</type>
|
||||
</artifactItem>
|
||||
<artifactItem>
|
||||
|
@ -497,7 +497,7 @@ THE SOFTWARE.
|
|||
<!-- dependency of jdk-tool -->
|
||||
<groupId>org.jenkins-ci.plugins</groupId>
|
||||
<artifactId>apache-httpcomponents-client-4-api</artifactId>
|
||||
<version>4.5.14-208.v438351942757</version>
|
||||
<version>4.5.14-269.vfa_2321039a_83</version>
|
||||
<type>hpi</type>
|
||||
</artifactItem>
|
||||
<artifactItem>
|
||||
|
@ -525,7 +525,7 @@ THE SOFTWARE.
|
|||
<!-- dependency of trilead-api -->
|
||||
<groupId>io.jenkins.plugins</groupId>
|
||||
<artifactId>gson-api</artifactId>
|
||||
<version>2.11.0-109.v1ef91dd0829a_</version>
|
||||
<version>2.12.1-113.v347686d6729f</version>
|
||||
<type>hpi</type>
|
||||
</artifactItem>
|
||||
<artifactItem>
|
||||
|
@ -691,12 +691,10 @@ THE SOFTWARE.
|
|||
<javax.xml.transform.TransformerFactory>com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl</javax.xml.transform.TransformerFactory>
|
||||
</systemProperties>
|
||||
<webApp>
|
||||
<!-- Allows resources to be reloaded, and enable nicer console logging. -->
|
||||
<extraClasspath>${project.basedir}/../core/src/main/resources,${project.basedir}/../core/target/classes,${project.build.directory}/support-log-formatter.jar</extraClasspath>
|
||||
<!-- Enable nicer console logging. -->
|
||||
<extraClasspath>${project.build.directory}/support-log-formatter.jar</extraClasspath>
|
||||
<contextPath>${contextPath}</contextPath>
|
||||
<configurationDiscovered>false</configurationDiscovered>
|
||||
<!-- see https://wiki.eclipse.org/Jetty/Howto/Avoid_slow_deployment -->
|
||||
<webInfIncludeJarPattern>NONE</webInfIncludeJarPattern>
|
||||
<webInfIncludeJarPattern>.*(jenkins-core|target/classes).*</webInfIncludeJarPattern>
|
||||
</webApp>
|
||||
</configuration>
|
||||
</plugin>
|
||||
|
|
|
@ -26,256 +26,7 @@ THE SOFTWARE.
|
|||
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
|
||||
version="3.1"
|
||||
metadata-complete="true">
|
||||
version="3.1">
|
||||
<display-name>Jenkins v${project.version}</display-name>
|
||||
<description>Build management system</description>
|
||||
|
||||
<servlet>
|
||||
<servlet-name>Stapler</servlet-name>
|
||||
<servlet-class>org.kohsuke.stapler.Stapler</servlet-class>
|
||||
<init-param>
|
||||
<param-name>default-encodings</param-name>
|
||||
<param-value>text/html=UTF-8</param-value>
|
||||
</init-param>
|
||||
<init-param>
|
||||
<param-name>diagnosticThreadName</param-name>
|
||||
<param-value>false</param-value>
|
||||
</init-param>
|
||||
<async-supported>true</async-supported>
|
||||
</servlet>
|
||||
|
||||
<servlet-mapping>
|
||||
<servlet-name>Stapler</servlet-name>
|
||||
<url-pattern>/*</url-pattern>
|
||||
</servlet-mapping>
|
||||
|
||||
<filter>
|
||||
<filter-name>suspicious-request-filter</filter-name>
|
||||
<filter-class>jenkins.security.SuspiciousRequestFilter</filter-class>
|
||||
<async-supported>true</async-supported>
|
||||
</filter>
|
||||
<filter>
|
||||
<filter-name>diagnostic-name-filter</filter-name>
|
||||
<filter-class>org.kohsuke.stapler.DiagnosticThreadNameFilter</filter-class>
|
||||
<async-supported>true</async-supported>
|
||||
</filter>
|
||||
<filter>
|
||||
<filter-name>encoding-filter</filter-name>
|
||||
<filter-class>hudson.util.CharacterEncodingFilter</filter-class>
|
||||
<async-supported>true</async-supported>
|
||||
</filter>
|
||||
<filter>
|
||||
<filter-name>uncaught-exception-filter</filter-name>
|
||||
<filter-class>org.kohsuke.stapler.UncaughtExceptionFilter</filter-class>
|
||||
<async-supported>true</async-supported>
|
||||
</filter>
|
||||
<filter>
|
||||
<filter-name>authentication-filter</filter-name>
|
||||
<filter-class>hudson.security.HudsonFilter</filter-class>
|
||||
<async-supported>true</async-supported>
|
||||
</filter>
|
||||
<filter>
|
||||
<filter-name>csrf-filter</filter-name>
|
||||
<filter-class>hudson.security.csrf.CrumbFilter</filter-class>
|
||||
<async-supported>true</async-supported>
|
||||
</filter>
|
||||
<filter>
|
||||
<filter-name>error-attribute-filter</filter-name>
|
||||
<filter-class>jenkins.ErrorAttributeFilter</filter-class>
|
||||
<async-supported>true</async-supported>
|
||||
</filter>
|
||||
<filter>
|
||||
<filter-name>plugins-filter</filter-name>
|
||||
<filter-class>hudson.util.PluginServletFilter</filter-class>
|
||||
<async-supported>true</async-supported>
|
||||
</filter>
|
||||
|
||||
<!--
|
||||
The Headers filter allows us to override headers sent by the container
|
||||
that may be in conflict with what we want. For example, Tomcat will set
|
||||
Cache-Control: no-cache for any files behind the security-constraint
|
||||
below. So if Hudson is on a public server, and you want to only allow
|
||||
authorized users to access it, you may want to pay attention to this.
|
||||
|
||||
See: http://www.nabble.com/No-browser-caching-with-Hudson- -tf4601857.html
|
||||
|
||||
<filter>
|
||||
<filter-name>change-headers-filter</filter-name>
|
||||
<filter-class>hudson.ResponseHeaderFilter</filter-class>
|
||||
<!- The value listed here is for 24 hours. Increase or decrease as you see
|
||||
fit. Value is in seconds. Make sure to keep the public option ->
|
||||
<init-param>
|
||||
<param-name>Cache-Control</param-name>
|
||||
<param-value>max-age=86400, public</param-value>
|
||||
</init-param>
|
||||
<!- It turns out that Tomcat just doesn't want to let
|
||||
go of its cache option. If you override Cache-Control,
|
||||
it starts to send Pragma: no-cache as a backup.
|
||||
->
|
||||
<init-param>
|
||||
<param-name>Pragma</param-name>
|
||||
<param-value>public</param-value>
|
||||
</init-param>
|
||||
</filter>
|
||||
<filter-mapping>
|
||||
<filter-name>change-headers-filter</filter-name>
|
||||
<url-pattern>*.css</url-pattern>
|
||||
</filter-mapping>
|
||||
<filter-mapping>
|
||||
<filter-name>change-headers-filter</filter-name>
|
||||
<url-pattern>*.gif</url-pattern>
|
||||
</filter-mapping>
|
||||
<filter-mapping>
|
||||
<filter-name>change-headers-filter</filter-name>
|
||||
<url-pattern>*.js</url-pattern>
|
||||
</filter-mapping>
|
||||
<filter-mapping>
|
||||
<filter-name>change-headers-filter</filter-name>
|
||||
<url-pattern>*.png</url-pattern>
|
||||
</filter-mapping>
|
||||
-->
|
||||
|
||||
<filter-mapping>
|
||||
<filter-name>suspicious-request-filter</filter-name>
|
||||
<url-pattern>/*</url-pattern>
|
||||
</filter-mapping>
|
||||
<filter-mapping>
|
||||
<filter-name>diagnostic-name-filter</filter-name>
|
||||
<url-pattern>/*</url-pattern>
|
||||
</filter-mapping>
|
||||
<filter-mapping>
|
||||
<filter-name>encoding-filter</filter-name>
|
||||
<url-pattern>/*</url-pattern>
|
||||
</filter-mapping>
|
||||
<filter-mapping>
|
||||
<filter-name>uncaught-exception-filter</filter-name>
|
||||
<url-pattern>/*</url-pattern>
|
||||
</filter-mapping>
|
||||
<filter-mapping>
|
||||
<filter-name>authentication-filter</filter-name>
|
||||
<url-pattern>/*</url-pattern>
|
||||
</filter-mapping>
|
||||
<filter-mapping>
|
||||
<filter-name>csrf-filter</filter-name>
|
||||
<url-pattern>/*</url-pattern>
|
||||
</filter-mapping>
|
||||
<filter-mapping>
|
||||
<filter-name>error-attribute-filter</filter-name>
|
||||
<url-pattern>/*</url-pattern>
|
||||
</filter-mapping>
|
||||
<filter-mapping>
|
||||
<filter-name>plugins-filter</filter-name>
|
||||
<url-pattern>/*</url-pattern>
|
||||
</filter-mapping>
|
||||
|
||||
<listener>
|
||||
<!-- Must be before WebAppMain in order to initialize the context before the first use of this class. -->
|
||||
<listener-class>jenkins.util.SystemProperties$Listener</listener-class>
|
||||
</listener>
|
||||
<listener>
|
||||
<listener-class>hudson.WebAppMain</listener-class>
|
||||
</listener>
|
||||
<listener>
|
||||
<listener-class>jenkins.JenkinsHttpSessionListener</listener-class>
|
||||
</listener>
|
||||
|
||||
<!--
|
||||
JENKINS-1235 suggests containers interpret '*' as "all roles defined in web.xml"
|
||||
as opposed to "all roles defined in the security realm", so we need to list some
|
||||
common names in the hope that users will have at least one of those roles.
|
||||
-->
|
||||
<security-role>
|
||||
<role-name>admin</role-name>
|
||||
</security-role>
|
||||
<security-role>
|
||||
<role-name>user</role-name>
|
||||
</security-role>
|
||||
<security-role>
|
||||
<role-name>hudson</role-name>
|
||||
</security-role>
|
||||
|
||||
<security-constraint>
|
||||
<web-resource-collection>
|
||||
<web-resource-name>Hudson</web-resource-name>
|
||||
<url-pattern>/loginEntry</url-pattern>
|
||||
<!--http-method>GET</http-method-->
|
||||
</web-resource-collection>
|
||||
<auth-constraint>
|
||||
<role-name>**</role-name>
|
||||
</auth-constraint>
|
||||
</security-constraint>
|
||||
|
||||
<!-- Disable TRACE method with security constraint (copied from jetty/webdefaults.xml) -->
|
||||
<security-constraint>
|
||||
<web-resource-collection>
|
||||
<web-resource-name>Disable TRACE</web-resource-name>
|
||||
<url-pattern>/*</url-pattern>
|
||||
<http-method>TRACE</http-method>
|
||||
</web-resource-collection>
|
||||
<auth-constraint />
|
||||
</security-constraint>
|
||||
|
||||
<security-constraint>
|
||||
<web-resource-collection>
|
||||
<web-resource-name>other</web-resource-name>
|
||||
<url-pattern>/*</url-pattern>
|
||||
</web-resource-collection>
|
||||
<!-- no security constraint -->
|
||||
</security-constraint>
|
||||
|
||||
<login-config>
|
||||
<auth-method>FORM</auth-method>
|
||||
<form-login-config>
|
||||
<form-login-page>/login</form-login-page>
|
||||
<form-error-page>/loginError</form-error-page>
|
||||
</form-login-config>
|
||||
</login-config>
|
||||
|
||||
<!-- configure additional extension-content-type mappings -->
|
||||
<mime-mapping>
|
||||
<extension>xml</extension>
|
||||
<mime-type>application/xml</mime-type>
|
||||
</mime-mapping>
|
||||
<!--mime-mapping> commenting out until this works out of the box with JOnAS. See http://www.nabble.com/Error-with-mime-type%2D-%27application-xslt%2Bxml%27-when-deploying-hudson-1.316-in-jonas-td24740489.html
|
||||
<extension>xsl</extension>
|
||||
<mime-type>application/xslt+xml</mime-type>
|
||||
</mime-mapping-->
|
||||
<mime-mapping>
|
||||
<extension>log</extension>
|
||||
<mime-type>text/plain</mime-type>
|
||||
</mime-mapping>
|
||||
<mime-mapping>
|
||||
<extension>war</extension>
|
||||
<mime-type>application/octet-stream</mime-type>
|
||||
</mime-mapping>
|
||||
<mime-mapping>
|
||||
<extension>ear</extension>
|
||||
<mime-type>application/octet-stream</mime-type>
|
||||
</mime-mapping>
|
||||
<mime-mapping>
|
||||
<extension>rar</extension>
|
||||
<mime-type>application/octet-stream</mime-type>
|
||||
</mime-mapping>
|
||||
<mime-mapping>
|
||||
<extension>webm</extension>
|
||||
<mime-type>video/webm</mime-type>
|
||||
</mime-mapping>
|
||||
|
||||
<error-page>
|
||||
<exception-type>java.lang.Throwable</exception-type>
|
||||
<location>/oops</location>
|
||||
</error-page>
|
||||
<error-page>
|
||||
<error-code>404</error-code>
|
||||
<location>/404</location>
|
||||
</error-page>
|
||||
|
||||
<session-config>
|
||||
<cookie-config>
|
||||
<!-- See https://www.owasp.org/index.php/HttpOnly for the discussion of this topic in OWASP -->
|
||||
<http-only>true</http-only>
|
||||
</cookie-config>
|
||||
<!-- Tracking mode is managed by WebAppMain.FORCE_SESSION_TRACKING_BY_COOKIE_PROP -->
|
||||
</session-config>
|
||||
</web-app>
|
||||
|
|
43
yarn.lock
43
yarn.lock
|
@ -1669,6 +1669,15 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@eslint/core@npm:^0.11.0":
|
||||
version: 0.11.0
|
||||
resolution: "@eslint/core@npm:0.11.0"
|
||||
dependencies:
|
||||
"@types/json-schema": "npm:^7.0.15"
|
||||
checksum: 10c0/1e0671d035c908175f445864a7864cf6c6a8b67a5dfba8c47b2ac91e2d3ed36e8c1f2fd81d98a73264f8677055559699d4adb0f97d86588e616fc0dc9a4b86c9
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@eslint/eslintrc@npm:^3.2.0":
|
||||
version: 3.2.0
|
||||
resolution: "@eslint/eslintrc@npm:3.2.0"
|
||||
|
@ -1686,10 +1695,10 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@eslint/js@npm:9.19.0":
|
||||
version: 9.19.0
|
||||
resolution: "@eslint/js@npm:9.19.0"
|
||||
checksum: 10c0/45dc544c8803984f80a438b47a8e578fae4f6e15bc8478a703827aaf05e21380b42a43560374ce4dad0d5cb6349e17430fc9ce1686fed2efe5d1ff117939ff90
|
||||
"@eslint/js@npm:9.20.0":
|
||||
version: 9.20.0
|
||||
resolution: "@eslint/js@npm:9.20.0"
|
||||
checksum: 10c0/10e7b5b9e628b5192e8fc6b0ecd27cf48322947e83e999ff60f9f9e44ac8d499138bcb9383cbfa6e51e780d53b4e76ccc2d1753b108b7173b8404fd484d37328
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
|
@ -3524,16 +3533,16 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"eslint@npm:9.19.0":
|
||||
version: 9.19.0
|
||||
resolution: "eslint@npm:9.19.0"
|
||||
"eslint@npm:9.20.0":
|
||||
version: 9.20.0
|
||||
resolution: "eslint@npm:9.20.0"
|
||||
dependencies:
|
||||
"@eslint-community/eslint-utils": "npm:^4.2.0"
|
||||
"@eslint-community/regexpp": "npm:^4.12.1"
|
||||
"@eslint/config-array": "npm:^0.19.0"
|
||||
"@eslint/core": "npm:^0.10.0"
|
||||
"@eslint/core": "npm:^0.11.0"
|
||||
"@eslint/eslintrc": "npm:^3.2.0"
|
||||
"@eslint/js": "npm:9.19.0"
|
||||
"@eslint/js": "npm:9.20.0"
|
||||
"@eslint/plugin-kit": "npm:^0.2.5"
|
||||
"@humanfs/node": "npm:^0.16.6"
|
||||
"@humanwhocodes/module-importer": "npm:^1.0.1"
|
||||
|
@ -3569,7 +3578,7 @@ __metadata:
|
|||
optional: true
|
||||
bin:
|
||||
eslint: bin/eslint.js
|
||||
checksum: 10c0/3b0dfaeff6a831de086884a3e2432f18468fe37c69f35e1a0a9a2833d9994a65b6dd2a524aaee28f361c849035ad9d15e3841029b67d261d0abd62c7de6d51f5
|
||||
checksum: 10c0/5eb2d9b5ed85a0b022871d19719417d110afb07a4abfedd856ad03d9a821def5f6bc31d7c407ca27f56e5e66e39375300fd2b877017245eb99c44060d6c983bd
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
|
@ -4381,12 +4390,12 @@ __metadata:
|
|||
"@babel/cli": "npm:7.26.4"
|
||||
"@babel/core": "npm:7.26.7"
|
||||
"@babel/preset-env": "npm:7.26.7"
|
||||
"@eslint/js": "npm:9.19.0"
|
||||
"@eslint/js": "npm:9.20.0"
|
||||
babel-loader: "npm:9.2.1"
|
||||
clean-webpack-plugin: "npm:4.0.0"
|
||||
css-loader: "npm:7.1.2"
|
||||
css-minimizer-webpack-plugin: "npm:7.0.0"
|
||||
eslint: "npm:9.19.0"
|
||||
eslint: "npm:9.20.0"
|
||||
eslint-config-prettier: "npm:10.0.1"
|
||||
eslint-formatter-checkstyle: "npm:8.40.0"
|
||||
globals: "npm:15.14.0"
|
||||
|
@ -4401,7 +4410,7 @@ __metadata:
|
|||
postcss-preset-env: "npm:10.1.3"
|
||||
postcss-scss: "npm:4.0.9"
|
||||
prettier: "npm:3.4.2"
|
||||
sass: "npm:1.83.4"
|
||||
sass: "npm:1.84.0"
|
||||
sass-loader: "npm:16.0.4"
|
||||
sortablejs: "npm:1.15.6"
|
||||
style-loader: "npm:4.0.0"
|
||||
|
@ -6423,9 +6432,9 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"sass@npm:1.83.4":
|
||||
version: 1.83.4
|
||||
resolution: "sass@npm:1.83.4"
|
||||
"sass@npm:1.84.0":
|
||||
version: 1.84.0
|
||||
resolution: "sass@npm:1.84.0"
|
||||
dependencies:
|
||||
"@parcel/watcher": "npm:^2.4.1"
|
||||
chokidar: "npm:^4.0.0"
|
||||
|
@ -6436,7 +6445,7 @@ __metadata:
|
|||
optional: true
|
||||
bin:
|
||||
sass: sass.js
|
||||
checksum: 10c0/6f27f0eebfeb50222b14baaeef548ef58a05daf8abd9797e6c499334ed7ad40541767056c8693780d06ca83d8836348ea7396a923d3be439b133507993ca78be
|
||||
checksum: 10c0/4af28c12416b6f1fec2423677cfa8c48af7fb7652a50bd076e0cdd1ea260f0330948ddd6075368a734b8d6cfa16c9af5518292181334f47a9471cb542599bc7b
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
|
|
Loading…
Reference in New Issue