提交 bdcb2fb2 编写于 作者: J James Nord

Merge remote-tracking branch 'origin/master' into kill-jsr-305

...@@ -895,5 +895,16 @@ THE SOFTWARE. ...@@ -895,5 +895,16 @@ THE SOFTWARE.
<findbugs.failOnError>true</findbugs.failOnError> <findbugs.failOnError>true</findbugs.failOnError>
</properties> </properties>
</profile> </profile>
<profile>
<id>all-tests</id>
<activation>
<property>
<name>!test</name>
</property>
</activation>
<properties>
<maven.test.redirectTestOutputToFile>true</maven.test.redirectTestOutputToFile>
</properties>
</profile>
</profiles> </profiles>
</project> </project>
...@@ -3279,7 +3279,7 @@ public final class FilePath implements SerializableOnlyOverRemoting { ...@@ -3279,7 +3279,7 @@ public final class FilePath implements SerializableOnlyOverRemoting {
return act(new IsDescendant(potentialChildRelativePath)); return act(new IsDescendant(potentialChildRelativePath));
} }
private class IsDescendant extends SecureFileCallable<Boolean> { private static class IsDescendant extends SecureFileCallable<Boolean> {
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
private String potentialChildRelativePath; private String potentialChildRelativePath;
......
...@@ -360,7 +360,7 @@ public final class TcpSlaveAgentListener extends Thread { ...@@ -360,7 +360,7 @@ public final class TcpSlaveAgentListener extends Thread {
} }
// This is essentially just to be able to pass the parent thread into the callback, as it can't access it otherwise // This is essentially just to be able to pass the parent thread into the callback, as it can't access it otherwise
private abstract class ConnectionHandlerFailureCallback { private static abstract class ConnectionHandlerFailureCallback {
private Thread parentThread; private Thread parentThread;
public ConnectionHandlerFailureCallback(Thread parentThread) { public ConnectionHandlerFailureCallback(Thread parentThread) {
......
...@@ -86,6 +86,7 @@ import java.util.regex.Matcher; ...@@ -86,6 +86,7 @@ import java.util.regex.Matcher;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import edu.umd.cs.findbugs.annotations.CheckForNull; import edu.umd.cs.findbugs.annotations.CheckForNull;
import edu.umd.cs.findbugs.annotations.CheckReturnValue;
import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.NonNull;
import edu.umd.cs.findbugs.annotations.Nullable; import edu.umd.cs.findbugs.annotations.Nullable;
import javax.crypto.SecretKey; import javax.crypto.SecretKey;
...@@ -1205,6 +1206,65 @@ public class Util { ...@@ -1205,6 +1206,65 @@ public class Util {
return createFileSet(baseDir,includes,null); return createFileSet(baseDir,includes,null);
} }
private static void tryToDeleteSymlink(@NonNull File symlink) {
if (!symlink.delete()) {
LogRecord record = new LogRecord(Level.FINE, "Failed to delete temporary symlink {0}");
record.setParameters(new Object[]{symlink.getAbsolutePath()});
LOGGER.log(record);
}
}
private static void reportAtomicFailure(@NonNull Path pathForSymlink, @NonNull Exception ex) {
LogRecord record = new LogRecord(Level.FINE, "Failed to atomically create/replace symlink {0}");
record.setParameters(new Object[]{pathForSymlink.toAbsolutePath().toString()});
record.setThrown(ex);
LOGGER.log(record);
}
/**
* Creates a symlink to targetPath at baseDir+symlinkPath.
*
* @param pathForSymlink
* The absolute path of the symlink itself as a path object.
* @param fileForSymlink
* The absolute path of the symlink itself as a file object.
* @param target
* The path that the symlink should point to. Usually relative to the directory of the symlink but may instead be an absolute path.
* @param symlinkPath
* Where to create a symlink in (relative to {@code baseDir})
*
* Returns true on success
*/
@CheckReturnValue
private static boolean createSymlinkAtomic(@NonNull Path pathForSymlink, @NonNull File fileForSymlink, @NonNull Path target, @NonNull String symlinkPath) {
try {
File symlink = File.createTempFile("symtmp", null, fileForSymlink);
tryToDeleteSymlink(symlink);
Path tempSymlinkPath = symlink.toPath();
Files.createSymbolicLink(tempSymlinkPath, target);
try {
Files.move(tempSymlinkPath, pathForSymlink, java.nio.file.StandardCopyOption.ATOMIC_MOVE);
return true;
} catch (
UnsupportedOperationException |
SecurityException |
IOException ex) {
// If we couldn't perform an atomic move or the setup, we fall through to another approach
reportAtomicFailure(pathForSymlink, ex);
}
// If we didn't return after our atomic move, then we want to clean up our symlink
tryToDeleteSymlink(symlink);
} catch (
SecurityException |
InvalidPathException |
UnsupportedOperationException |
IOException ex) {
// We couldn't perform an atomic move or the setup.
reportAtomicFailure(pathForSymlink, ex);
}
return false;
}
/** /**
* Creates a symlink to targetPath at baseDir+symlinkPath. * Creates a symlink to targetPath at baseDir+symlinkPath.
* <p> * <p>
...@@ -1219,16 +1279,21 @@ public class Util { ...@@ -1219,16 +1279,21 @@ public class Util {
*/ */
public static void createSymlink(@NonNull File baseDir, @NonNull String targetPath, public static void createSymlink(@NonNull File baseDir, @NonNull String targetPath,
@NonNull String symlinkPath, @NonNull TaskListener listener) throws InterruptedException { @NonNull String symlinkPath, @NonNull TaskListener listener) throws InterruptedException {
File fileForSymlink = new File(baseDir, symlinkPath);
try { try {
Path path = fileToPath(new File(baseDir, symlinkPath)); Path pathForSymlink = fileToPath(fileForSymlink);
Path target = Paths.get(targetPath, MemoryReductionUtil.EMPTY_STRING_ARRAY); Path target = Paths.get(targetPath, MemoryReductionUtil.EMPTY_STRING_ARRAY);
if (createSymlinkAtomic(pathForSymlink, fileForSymlink, target, symlinkPath)) {
return;
}
final int maxNumberOfTries = 4; final int maxNumberOfTries = 4;
final int timeInMillis = 100; final int timeInMillis = 100;
for (int tryNumber = 1; tryNumber <= maxNumberOfTries; tryNumber++) { for (int tryNumber = 1; tryNumber <= maxNumberOfTries; tryNumber++) {
Files.deleteIfExists(path); Files.deleteIfExists(pathForSymlink);
try { try {
Files.createSymbolicLink(path, target); Files.createSymbolicLink(pathForSymlink, target);
break; break;
} catch (FileAlreadyExistsException fileAlreadyExistsException) { } catch (FileAlreadyExistsException fileAlreadyExistsException) {
if (tryNumber < maxNumberOfTries) { if (tryNumber < maxNumberOfTries) {
...@@ -1249,7 +1314,7 @@ public class Util { ...@@ -1249,7 +1314,7 @@ public class Util {
return; return;
} }
PrintStream log = listener.getLogger(); PrintStream log = listener.getLogger();
log.printf("ln %s %s failed%n",targetPath, new File(baseDir, symlinkPath)); log.printf("ln %s %s failed%n", targetPath, fileForSymlink);
Functions.printStackTrace(e, log); Functions.printStackTrace(e, log);
} }
} }
......
...@@ -190,7 +190,7 @@ public class CLIAction implements UnprotectedRootAction, StaplerProxy { ...@@ -190,7 +190,7 @@ public class CLIAction implements UnprotectedRootAction, StaplerProxy {
} }
} }
class ServerSideImpl extends PlainCLIProtocol.ServerSide { static class ServerSideImpl extends PlainCLIProtocol.ServerSide {
private Thread runningThread; private Thread runningThread;
private boolean ready; private boolean ready;
private final List<String> args = new ArrayList<>(); private final List<String> args = new ArrayList<>();
......
...@@ -69,7 +69,7 @@ public class WorkspaceSnapshotSCM extends SCM { ...@@ -69,7 +69,7 @@ public class WorkspaceSnapshotSCM extends SCM {
/** /**
* {@link Exception} indicating that the resolution of the job/permalink failed. * {@link Exception} indicating that the resolution of the job/permalink failed.
*/ */
private final class ResolvedFailedException extends Exception { private static final class ResolvedFailedException extends Exception {
private ResolvedFailedException(String message) { private ResolvedFailedException(String message) {
super(message); super(message);
} }
......
...@@ -127,7 +127,7 @@ public class WindowsInstallerLink extends ManagementLink { ...@@ -127,7 +127,7 @@ public class WindowsInstallerLink extends ManagementLink {
sendError("Installation is already complete",req,rsp); sendError("Installation is already complete",req,rsp);
return; return;
} }
if(!DotNet.isInstalled(2,0)) { if(!DotNet.isInstalled(4,0) && !DotNet.isInstalled(2,0)) {
sendError(".NET Framework 2.0 or later is required for this feature",req,rsp); sendError(".NET Framework 2.0 or later is required for this feature",req,rsp);
return; return;
} }
......
...@@ -81,9 +81,15 @@ public class HistoricalSecrets { ...@@ -81,9 +81,15 @@ public class HistoricalSecrets {
*/ */
@Deprecated @Deprecated
/*package*/ static SecretKey getLegacyKey() throws GeneralSecurityException { /*package*/ static SecretKey getLegacyKey() throws GeneralSecurityException {
String secret = Secret.SECRET; if (Secret.SECRET != null) {
if(secret==null) return Jenkins.get().getSecretKeyAsAES128(); return Util.toAes128Key(Secret.SECRET);
return Util.toAes128Key(secret); }
Jenkins j = Jenkins.getInstanceOrNull();
if (j != null) {
return j.getSecretKeyAsAES128();
} else {
return Util.toAes128Key("mock");
}
} }
static final String MAGIC = "::::MAGIC::::"; static final String MAGIC = "::::MAGIC::::";
......
...@@ -31,7 +31,6 @@ import com.thoughtworks.xstream.io.HierarchicalStreamReader; ...@@ -31,7 +31,6 @@ import com.thoughtworks.xstream.io.HierarchicalStreamReader;
import com.thoughtworks.xstream.io.HierarchicalStreamWriter; import com.thoughtworks.xstream.io.HierarchicalStreamWriter;
import jenkins.util.SystemProperties; import jenkins.util.SystemProperties;
import java.util.Arrays; import java.util.Arrays;
import jenkins.model.Jenkins;
import hudson.Util; import hudson.Util;
import jenkins.security.CryptoConfidentialKey; import jenkins.security.CryptoConfidentialKey;
import org.kohsuke.stapler.Stapler; import org.kohsuke.stapler.Stapler;
...@@ -289,8 +288,10 @@ public final class Secret implements Serializable { ...@@ -289,8 +288,10 @@ public final class Secret implements Serializable {
private static final String PROVIDER = SystemProperties.getString(Secret.class.getName()+".provider"); private static final String PROVIDER = SystemProperties.getString(Secret.class.getName()+".provider");
/** /**
* For testing only. Override the secret key so that we can test this class without {@link Jenkins}. * For testing only.
* @deprecated Normally unnecessary.
*/ */
@Deprecated
/*package*/ static String SECRET = null; /*package*/ static String SECRET = null;
/** /**
...@@ -303,6 +304,9 @@ public final class Secret implements Serializable { ...@@ -303,6 +304,9 @@ public final class Secret implements Serializable {
static { static {
Stapler.CONVERT_UTILS.register(new org.apache.commons.beanutils.Converter() { Stapler.CONVERT_UTILS.register(new org.apache.commons.beanutils.Converter() {
public Secret convert(Class type, Object value) { public Secret convert(Class type, Object value) {
if (value == null) {
return null;
}
if (value instanceof Secret) { if (value instanceof Secret) {
return (Secret) value; return (Secret) value;
} }
......
...@@ -327,7 +327,7 @@ public class XStream2 extends XStream { ...@@ -327,7 +327,7 @@ public class XStream2 extends XStream {
mapperInjectionPoint.setDelegate(m); mapperInjectionPoint.setDelegate(m);
} }
final class MapperInjectionPoint extends MapperDelegate { final static class MapperInjectionPoint extends MapperDelegate {
public MapperInjectionPoint(Mapper wrapped) { public MapperInjectionPoint(Mapper wrapped) {
super(wrapped); super(wrapped);
} }
......
...@@ -30,8 +30,6 @@ import org.jinterop.winreg.JIPolicyHandle; ...@@ -30,8 +30,6 @@ import org.jinterop.winreg.JIPolicyHandle;
import org.jinterop.winreg.JIWinRegFactory; import org.jinterop.winreg.JIWinRegFactory;
import java.net.UnknownHostException; import java.net.UnknownHostException;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/** /**
* .NET related code. * .NET related code.
...@@ -39,74 +37,233 @@ import java.util.regex.Pattern; ...@@ -39,74 +37,233 @@ import java.util.regex.Pattern;
* @author Kohsuke Kawaguchi * @author Kohsuke Kawaguchi
*/ */
public class DotNet { public class DotNet {
private static final String PATH10 = "SOFTWARE\\Microsoft\\.NETFramework\\Policy\\v1.0\\3705";
private static final String PATH11 = "SOFTWARE\\Microsoft\\NET Framework Setup\\NDP\\v1.1.4322";
private static final String PATH20 = "SOFTWARE\\Microsoft\\NET Framework Setup\\NDP\\v2.0.50727";
private static final String PATH30 = "SOFTWARE\\Microsoft\\NET Framework Setup\\NDP\\v3.0\\Setup";
private static final String PATH35 = "SOFTWARE\\Microsoft\\NET Framework Setup\\NDP\\v3.5";
private static final String PATH4 = "SOFTWARE\\Microsoft\\NET Framework Setup\\NDP\\v4\\Full";
private static final String VALUE_INSTALL = "Install";
private static final String VALUE_INSTALL_SUCCESS = "InstallSuccess";
private static final String VALUE_RELEASE = "Release";
/** /**
* Returns true if the .NET framework of the given version (or greater) is installed. * Returns true if the .NET framework of a compatible version is installed.
*/ */
public static boolean isInstalled(int major, int minor) { public static boolean isInstalled(int major, int minor) {
try { try {
// see http://support.microsoft.com/?scid=kb;en-us;315291 for the basic algorithm if (major == 4 && minor >= 5) {
// observation in my registry shows that the actual key name can be things like "v2.0 SP1" return isV45PlusInstalled(minor);
// or "v2.0.50727", so the regexp is written to accommodate this. } else if (major == 4 && minor == 0) {
RegistryKey key = RegistryKey.LOCAL_MACHINE.openReadonly("SOFTWARE\\Microsoft\\.NETFramework"); return isV40Installed();
try { } else if (major == 3 && minor == 5) {
for( String keyName : key.getSubKeys() ) { return isV35Installed();
if (matches(keyName, major, minor)) } else if (major == 3 && minor == 0) {
return true; return isV35Installed() || isV30Installed();
} } else if (major == 2 && minor == 0) {
return isV35Installed() || isV30Installed() || isV20Installed();
} else if (major == 1 && minor == 1) {
return isV11Installed();
} else if (major == 1 && minor == 0) {
return isV11Installed() || isV10Installed();
} else {
return false; return false;
} finally {
key.dispose();
} }
} catch (JnaException e) { } catch (JnaException e) {
if(e.getErrorCode()==2) // thrown when openReadonly fails because the key doesn't exist. if (e.getErrorCode() == 2) {
// thrown when openReadonly fails because the key doesn't exist.
return false; return false;
}
throw e; throw e;
} }
} }
private static boolean isV45PlusInstalled(int minor) {
try (RegistryKey key = RegistryKey.LOCAL_MACHINE.openReadonly(PATH4)) {
return key.getIntValue(VALUE_RELEASE) >= GetV45PlusMinRelease(minor);
}
}
private static boolean isV40Installed() {
try (RegistryKey key = RegistryKey.LOCAL_MACHINE.openReadonly(PATH4)) {
return key.getIntValue(VALUE_INSTALL) == 1;
}
}
private static boolean isV35Installed() {
try (RegistryKey key = RegistryKey.LOCAL_MACHINE.openReadonly(PATH35)) {
return key.getIntValue(VALUE_INSTALL) == 1;
}
}
private static boolean isV30Installed() {
try (RegistryKey key = RegistryKey.LOCAL_MACHINE.openReadonly(PATH30)) {
return key.getIntValue(VALUE_INSTALL_SUCCESS) == 1;
}
}
private static boolean isV20Installed() {
try (RegistryKey key = RegistryKey.LOCAL_MACHINE.openReadonly(PATH20)) {
return key.getIntValue(VALUE_INSTALL) == 1;
}
}
private static boolean isV11Installed() {
try (RegistryKey key = RegistryKey.LOCAL_MACHINE.openReadonly(PATH11)) {
return key.getIntValue(VALUE_INSTALL) == 1;
}
}
private static boolean isV10Installed() {
try (RegistryKey key = RegistryKey.LOCAL_MACHINE.openReadonly(PATH10)) {
return key.getStringValue(VALUE_INSTALL) == "1";
}
}
/** /**
* Returns true if the .NET framework of the given version (or grater) is installed * Returns true if the .NET framework of a compatible version is installed on a remote machine.
* on a remote machine.
*/ */
public static boolean isInstalled(int major, int minor, String targetMachine, IJIAuthInfo session) throws JIException, UnknownHostException { public static boolean isInstalled(int major, int minor, String targetMachine, IJIAuthInfo session) throws JIException, UnknownHostException {
IJIWinReg registry = JIWinRegFactory.getSingleTon().getWinreg(session,targetMachine,true); IJIWinReg registry = JIWinRegFactory.getSingleTon().getWinreg(session, targetMachine, true);
JIPolicyHandle hklm=null; JIPolicyHandle hklm = null;
JIPolicyHandle key=null;
try { try {
hklm = registry.winreg_OpenHKLM(); hklm = registry.winreg_OpenHKLM();
key = registry.winreg_OpenKey(hklm,"SOFTWARE\\Microsoft\\.NETFramework", IJIWinReg.KEY_READ ); if (major == 4 && minor >= 5) {
return isV45PlusInstalled(minor, registry, hklm);
for( int i=0; ; i++ ) { } else if (major == 4 && minor == 0) {
String keyName = registry.winreg_EnumKey(key,i)[0]; return isV40Installed(registry, hklm);
if(matches(keyName,major,minor)) } else if (major == 3 && minor == 5) {
return true; return isV35Installed(registry, hklm);
} else if (major == 3 && minor == 0) {
return isV35Installed(registry, hklm) || isV30Installed(registry, hklm);
} else if (major == 2 && minor == 0) {
return isV35Installed(registry, hklm) || isV30Installed(registry, hklm) || isV20Installed(registry, hklm);
} else if (major == 1 && minor == 1) {
return isV11Installed(registry, hklm);
} else if (major == 1 && minor == 0) {
return isV11Installed(registry, hklm) || isV10Installed(registry, hklm);
} else {
return false;
} }
} catch (JIException e) { } catch (JIException e) {
if(e.getErrorCode()==2) if (e.getErrorCode() == 2) {
return false; // not found // not found
return false;
}
throw e; throw e;
} finally { } finally {
if(hklm!=null) if (hklm != null) {
registry.winreg_CloseKey(hklm); registry.winreg_CloseKey(hklm);
if(key!=null) }
registry.winreg_CloseKey(key);
registry.closeConnection(); registry.closeConnection();
} }
} }
private static boolean matches(String keyName, int major, int minor) { private static boolean isV45PlusInstalled(int minor, IJIWinReg registry, JIPolicyHandle hklm) throws JIException {
Matcher m = VERSION_PATTERN.matcher(keyName); JIPolicyHandle key = null;
if(m.matches()) { try {
int mj = Integer.parseInt(m.group(1)); key = registry.winreg_OpenKey(hklm, PATH4, IJIWinReg.KEY_READ);
if(mj>=major) { return GetIntValue(registry, key, VALUE_RELEASE) >= GetV45PlusMinRelease(minor);
int mn = Integer.parseInt(m.group(2)); } finally {
if(mn>=minor) if (key != null) {
return true; registry.winreg_CloseKey(key);
}
}
}
private static boolean isV40Installed(IJIWinReg registry, JIPolicyHandle hklm) throws JIException {
JIPolicyHandle key = null;
try {
key = registry.winreg_OpenKey(hklm, PATH4, IJIWinReg.KEY_READ);
return GetIntValue(registry, key, VALUE_INSTALL) == 1;
} finally {
if (key != null) {
registry.winreg_CloseKey(key);
}
}
}
private static boolean isV35Installed(IJIWinReg registry, JIPolicyHandle hklm) throws JIException {
JIPolicyHandle key = null;
try {
key = registry.winreg_OpenKey(hklm, PATH35, IJIWinReg.KEY_READ);
return GetIntValue(registry, key, VALUE_INSTALL) == 1;
} finally {
if (key != null) {
registry.winreg_CloseKey(key);
}
}
}
private static boolean isV30Installed(IJIWinReg registry, JIPolicyHandle hklm) throws JIException {
JIPolicyHandle key = null;
try {
key = registry.winreg_OpenKey(hklm, PATH30, IJIWinReg.KEY_READ);
return GetIntValue(registry, key, VALUE_INSTALL_SUCCESS) == 1;
} finally {
if (key != null) {
registry.winreg_CloseKey(key);
}
}
}
private static boolean isV20Installed(IJIWinReg registry, JIPolicyHandle hklm) throws JIException {
JIPolicyHandle key = null;
try {
key = registry.winreg_OpenKey(hklm, PATH20, IJIWinReg.KEY_READ);
return GetIntValue(registry, key, VALUE_INSTALL) == 1;
} finally {
if (key != null) {
registry.winreg_CloseKey(key);
} }
} }
return false;
} }
private static final Pattern VERSION_PATTERN = Pattern.compile("v(\\d+)\\.(\\d+).*"); private static boolean isV11Installed(IJIWinReg registry, JIPolicyHandle hklm) throws JIException {
JIPolicyHandle key = null;
try {
key = registry.winreg_OpenKey(hklm, PATH11, IJIWinReg.KEY_READ);
return GetIntValue(registry, key, VALUE_INSTALL) == 1;
} finally {
if (key != null) {
registry.winreg_CloseKey(key);
}
}
}
private static boolean isV10Installed(IJIWinReg registry, JIPolicyHandle hklm) throws JIException {
JIPolicyHandle key = null;
try {
key = registry.winreg_OpenKey(hklm, PATH10, IJIWinReg.KEY_READ);
return GetStringValue(registry, key, VALUE_INSTALL) == "1";
} finally {
if (key != null) {
registry.winreg_CloseKey(key);
}
}
}
private static int GetIntValue(IJIWinReg registry, JIPolicyHandle key, String name) throws JIException {
return RegistryKey.convertBufferToInt((byte[])registry.winreg_QueryValue(key, name, Integer.BYTES)[1]);
}
private static String GetStringValue(IJIWinReg registry, JIPolicyHandle key, String name) throws JIException {
return RegistryKey.convertBufferToString((byte[])registry.winreg_QueryValue(key, name, Character.BYTES * 2)[1]);
}
private static int GetV45PlusMinRelease(int minor) {
switch (minor) {
case 5:
return 378389;
case 6:
return 393295;
case 7:
return 460798;
case 8:
return 528040;
default:
return Integer.MAX_VALUE;
}
}
} }
...@@ -27,7 +27,7 @@ import java.util.TreeSet; ...@@ -27,7 +27,7 @@ import java.util.TreeSet;
* *
* @author Kohsuke Kawaguchi * @author Kohsuke Kawaguchi
*/ */
public class RegistryKey { public class RegistryKey implements AutoCloseable {
/** /**
* 32bit Windows key value. * 32bit Windows key value.
*/ */
...@@ -64,7 +64,7 @@ public class RegistryKey { ...@@ -64,7 +64,7 @@ public class RegistryKey {
* @throws java.io.UnsupportedEncodingException on error * @throws java.io.UnsupportedEncodingException on error
* @return String * @return String
*/ */
private static String convertBufferToString(byte[] buf) { static String convertBufferToString(byte[] buf) {
return new String(buf, 0, buf.length - 2, StandardCharsets.UTF_16LE); return new String(buf, 0, buf.length - 2, StandardCharsets.UTF_16LE);
} }
...@@ -74,7 +74,7 @@ public class RegistryKey { ...@@ -74,7 +74,7 @@ public class RegistryKey {
* @param buf buffer * @param buf buffer
* @return int * @return int
*/ */
private static int convertBufferToInt(byte[] buf) { static int convertBufferToInt(byte[] buf) {
return ((buf[0] & 0xff) + ((buf[1] & 0xff) << 8) + ((buf[2] & 0xff) << 16) + ((buf[3] & 0xff) << 24)); return ((buf[0] & 0xff) + ((buf[1] & 0xff) << 8) + ((buf[2] & 0xff) << 16) + ((buf[3] & 0xff) << 24));
} }
...@@ -287,6 +287,10 @@ public class RegistryKey { ...@@ -287,6 +287,10 @@ public class RegistryKey {
handle = 0; handle = 0;
} }
public void close() {
dispose();
}
// //
// Root keys // Root keys
// //
......
...@@ -20,7 +20,7 @@ import java.util.concurrent.CopyOnWriteArrayList; ...@@ -20,7 +20,7 @@ import java.util.concurrent.CopyOnWriteArrayList;
class FilePathFilterAggregator extends FilePathFilter { class FilePathFilterAggregator extends FilePathFilter {
private final CopyOnWriteArrayList<Entry> all = new CopyOnWriteArrayList<>(); private final CopyOnWriteArrayList<Entry> all = new CopyOnWriteArrayList<>();
private class Entry implements Comparable<Entry> { private static class Entry implements Comparable<Entry> {
final FilePathFilter filter; final FilePathFilter filter;
final double ordinal; final double ordinal;
......
...@@ -48,6 +48,10 @@ public class GlobalBuildDiscarderConfiguration extends GlobalConfiguration { ...@@ -48,6 +48,10 @@ public class GlobalBuildDiscarderConfiguration extends GlobalConfiguration {
return ExtensionList.lookupSingleton(GlobalBuildDiscarderConfiguration.class); return ExtensionList.lookupSingleton(GlobalBuildDiscarderConfiguration.class);
} }
public GlobalBuildDiscarderConfiguration() {
load();
}
private final DescribableList<GlobalBuildDiscarderStrategy, GlobalBuildDiscarderStrategyDescriptor> configuredBuildDiscarders = private final DescribableList<GlobalBuildDiscarderStrategy, GlobalBuildDiscarderStrategyDescriptor> configuredBuildDiscarders =
new DescribableList<>(this, Collections.singletonList(new JobGlobalBuildDiscarderStrategy())); new DescribableList<>(this, Collections.singletonList(new JobGlobalBuildDiscarderStrategy()));
......
...@@ -10,10 +10,14 @@ import org.kohsuke.MetaInfServices; ...@@ -10,10 +10,14 @@ import org.kohsuke.MetaInfServices;
import edu.umd.cs.findbugs.annotations.CheckForNull; import edu.umd.cs.findbugs.annotations.CheckForNull;
import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.NonNull;
import java.io.IOException; import java.io.IOException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom; import java.security.SecureRandom;
import java.util.Iterator; import java.util.Iterator;
import java.util.Map;
import java.util.Random;
import java.util.ServiceConfigurationError; import java.util.ServiceConfigurationError;
import java.util.ServiceLoader; import java.util.ServiceLoader;
import java.util.concurrent.ConcurrentHashMap;
import java.util.logging.Level; import java.util.logging.Level;
import java.util.logging.Logger; import java.util.logging.Logger;
...@@ -51,6 +55,9 @@ public abstract class ConfidentialStore { ...@@ -51,6 +55,9 @@ public abstract class ConfidentialStore {
*/ */
protected abstract @CheckForNull byte[] load(ConfidentialKey key) throws IOException; protected abstract @CheckForNull byte[] load(ConfidentialKey key) throws IOException;
// TODO consider promoting to public, and offering a default implementation of randomBytes (via the usual Util.isOverridden binary compat trick)
abstract SecureRandom secureRandom();
/** /**
* Works like {@link SecureRandom#nextBytes(byte[])}. * Works like {@link SecureRandom#nextBytes(byte[])}.
* *
...@@ -64,7 +71,10 @@ public abstract class ConfidentialStore { ...@@ -64,7 +71,10 @@ public abstract class ConfidentialStore {
public static @NonNull ConfidentialStore get() { public static @NonNull ConfidentialStore get() {
if (TEST!=null) return TEST.get(); if (TEST!=null) return TEST.get();
Jenkins j = Jenkins.get(); Jenkins j = Jenkins.getInstanceOrNull();
if (j == null) {
return Mock.INSTANCE;
}
Lookup lookup = j.lookup; Lookup lookup = j.lookup;
ConfidentialStore cs = lookup.get(ConfidentialStore.class); ConfidentialStore cs = lookup.get(ConfidentialStore.class);
if (cs==null) { if (cs==null) {
...@@ -91,10 +101,60 @@ public abstract class ConfidentialStore { ...@@ -91,10 +101,60 @@ public abstract class ConfidentialStore {
return cs; return cs;
} }
/** /**
* Testing only. Used for testing {@link ConfidentialKey} without {@link Jenkins} * @deprecated No longer needed.
*/ */
@Deprecated
/*package*/ static ThreadLocal<ConfidentialStore> TEST = null; /*package*/ static ThreadLocal<ConfidentialStore> TEST = null;
static final class Mock extends ConfidentialStore {
static final Mock INSTANCE = new Mock();
private final SecureRandom rand;
private final Map<String, byte[]> data = new ConcurrentHashMap<>();
Mock() {
// Use a predictable seed to make tests more reproducible.
try {
rand = SecureRandom.getInstance("SHA1PRNG");
} catch (NoSuchAlgorithmException x) {
throw new AssertionError("https://docs.oracle.com/javase/8/docs/technotes/guides/security/StandardNames.html#SecureRandom", x);
}
rand.setSeed(new byte[] {1, 2, 3, 4});
}
void clear() {
data.clear();
}
@Override
protected void store(ConfidentialKey key, byte[] payload) throws IOException {
LOGGER.fine(() -> "storing " + key.getId() + " " + hudson.Util.getDigestOf(hudson.Util.toHexString(payload)));
data.put(key.getId(), payload);
}
@Override
protected byte[] load(ConfidentialKey key) throws IOException {
byte[] payload = data.get(key.getId());
LOGGER.fine(() -> "loading " + key.getId() + " " + (payload != null ? hudson.Util.getDigestOf(hudson.Util.toHexString(payload)) : "null"));
return payload;
}
@Override
SecureRandom secureRandom() {
return rand;
}
@Override
public byte[] randomBytes(int size) {
byte[] random = new byte[size];
rand.nextBytes(random);
return random;
}
}
private static final Logger LOGGER = Logger.getLogger(ConfidentialStore.class.getName()); private static final Logger LOGGER = Logger.getLogger(ConfidentialStore.class.getName());
} }
...@@ -140,6 +140,11 @@ public class DefaultConfidentialStore extends ConfidentialStore { ...@@ -140,6 +140,11 @@ public class DefaultConfidentialStore extends ConfidentialStore {
return new File(rootDir, key.getId()); return new File(rootDir, key.getId());
} }
@Override
SecureRandom secureRandom() {
return sr;
}
public byte[] randomBytes(int size) { public byte[] randomBytes(int size) {
byte[] random = new byte[size]; byte[] random = new byte[size];
sr.nextBytes(random); sr.nextBytes(random);
......
...@@ -30,7 +30,6 @@ import java.security.KeyFactory; ...@@ -30,7 +30,6 @@ import java.security.KeyFactory;
import java.security.KeyPair; import java.security.KeyPair;
import java.security.KeyPairGenerator; import java.security.KeyPairGenerator;
import java.security.PrivateKey; import java.security.PrivateKey;
import java.security.SecureRandom;
import java.security.interfaces.RSAPrivateCrtKey; import java.security.interfaces.RSAPrivateCrtKey;
import java.security.interfaces.RSAPrivateKey; import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey; import java.security.interfaces.RSAPublicKey;
...@@ -79,7 +78,7 @@ public abstract class RSAConfidentialKey extends ConfidentialKey { ...@@ -79,7 +78,7 @@ public abstract class RSAConfidentialKey extends ConfidentialKey {
byte[] payload = load(); byte[] payload = load();
if (payload == null) { if (payload == null) {
KeyPairGenerator gen = KeyPairGenerator.getInstance("RSA"); KeyPairGenerator gen = KeyPairGenerator.getInstance("RSA");
gen.initialize(2048, new SecureRandom()); // going beyond 2048 requires crypto extension gen.initialize(2048, cs.secureRandom()); // going beyond 2048 requires crypto extension
KeyPair keys = gen.generateKeyPair(); KeyPair keys = gen.generateKeyPair();
priv = (RSAPrivateKey) keys.getPrivate(); priv = (RSAPrivateKey) keys.getPrivate();
pub = (RSAPublicKey) keys.getPublic(); pub = (RSAPublicKey) keys.getPublic();
......
...@@ -33,6 +33,7 @@ import org.kohsuke.accmod.Restricted; ...@@ -33,6 +33,7 @@ import org.kohsuke.accmod.Restricted;
import org.kohsuke.accmod.restrictions.NoExternalUse; import org.kohsuke.accmod.restrictions.NoExternalUse;
import edu.umd.cs.findbugs.annotations.CheckForNull; import edu.umd.cs.findbugs.annotations.CheckForNull;
import edu.umd.cs.findbugs.annotations.NonNull;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
...@@ -766,6 +767,30 @@ public class AntClassLoader extends ClassLoader implements SubBuildListener { ...@@ -766,6 +767,30 @@ public class AntClassLoader extends ClassLoader implements SubBuildListener {
return parent == null ? super.getResourceAsStream(name) : parent.getResourceAsStream(name); return parent == null ? super.getResourceAsStream(name) : parent.getResourceAsStream(name);
} }
@FunctionalInterface
private interface Converter<T> {
T convert(@NonNull JarFile jarFile, @NonNull JarEntry entry) throws IOException;
}
@CheckForNull
private <T> T storeAndConvert(JarFile jarFile, File file, String resourceName, Converter<T> converter) throws IOException {
if (jarFile == null) {
if (file.exists()) {
jarFile = new JarFile(file);
jarFiles.put(file, jarFile);
} else {
return null;
}
//to eliminate a race condition, retrieve the entry
//that is in the hash table under that filename
jarFile = (JarFile) jarFiles.get(file);
}
JarEntry entry = jarFile.getJarEntry(resourceName);
if (entry == null)
return null;
return converter.convert(jarFile, entry);
}
/** /**
* Returns an inputstream to a given resource in the given file which may * Returns an inputstream to a given resource in the given file which may
* either be a directory or a zip file. * either be a directory or a zip file.
...@@ -787,21 +812,7 @@ public class AntClassLoader extends ClassLoader implements SubBuildListener { ...@@ -787,21 +812,7 @@ public class AntClassLoader extends ClassLoader implements SubBuildListener {
return Files.newInputStream(resource.toPath()); return Files.newInputStream(resource.toPath());
} }
} else { } else {
if (jarFile == null) { return storeAndConvert(jarFile, file, resourceName, JarFile::getInputStream);
if (file.exists()) {
jarFile = new JarFile(file);
jarFiles.put(file, jarFile);
} else {
return null;
}
//to eliminate a race condition, retrieve the entry
//that is in the hash table under that filename
jarFile = (JarFile) jarFiles.get(file);
}
JarEntry entry = jarFile.getJarEntry(resourceName);
if (entry != null) {
return jarFile.getInputStream(entry);
}
} }
} catch (Exception e) { } catch (Exception e) {
log("Ignoring Exception " + e.getClass().getName() + ": " + e.getMessage() log("Ignoring Exception " + e.getClass().getName() + ": " + e.getMessage()
...@@ -1019,24 +1030,13 @@ public class AntClassLoader extends ClassLoader implements SubBuildListener { ...@@ -1019,24 +1030,13 @@ public class AntClassLoader extends ClassLoader implements SubBuildListener {
} }
} }
} else { } else {
if (jarFile == null) { return storeAndConvert(jarFile, file, resourceName, (jar, entry) -> {
if (file.exists()) {
jarFile = new JarFile(file);
jarFiles.put(file, jarFile);
} else {
return null;
}
// potential race-condition
jarFile = (JarFile) jarFiles.get(file);
}
JarEntry entry = jarFile.getJarEntry(resourceName);
if (entry != null) {
try { try {
return new URL("jar:" + FILE_UTILS.getFileURL(file) + "!/" + entry); return new URL("jar:" + FILE_UTILS.getFileURL(file) + "!/" + entry);
} catch (MalformedURLException ex) { } catch (MalformedURLException ex) {
return null; return null;
} }
} });
} }
} catch (Exception e) { } catch (Exception e) {
String msg = "Unable to obtain resource from " + file + ": "; String msg = "Unable to obtain resource from " + file + ": ";
......
...@@ -22,5 +22,5 @@ f.advanced(){ ...@@ -22,5 +22,5 @@ f.advanced(){
f.textbox() f.textbox()
} }
f.validateButton(title:_("Validate Proxy"), f.validateButton(title:_("Validate Proxy"),
method:"validateProxy", with:"testUrl,name,port,userName,password,noProxyHost") method:"validateProxy", with:"testUrl,name,port,userName,secretPassword,noProxyHost")
} }
...@@ -37,7 +37,7 @@ import java.io.IOException; ...@@ -37,7 +37,7 @@ import java.io.IOException;
*/ */
public class BulkChangeTest { public class BulkChangeTest {
private class Point implements Saveable { private static class Point implements Saveable {
/** /**
* Don't actually do any save, but just remember how many the actual I/O would have happened. * Don't actually do any save, but just remember how many the actual I/O would have happened.
*/ */
......
...@@ -16,7 +16,7 @@ import org.jvnet.hudson.test.Issue; ...@@ -16,7 +16,7 @@ import org.jvnet.hudson.test.Issue;
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public class AbstractItemTest { public class AbstractItemTest {
private class StubAbstractItem extends AbstractItem { private static class StubAbstractItem extends AbstractItem {
protected StubAbstractItem() { protected StubAbstractItem() {
// sending in null as parent as I don't care for my current tests // sending in null as parent as I don't care for my current tests
...@@ -91,7 +91,7 @@ public class AbstractItemTest { ...@@ -91,7 +91,7 @@ public class AbstractItemTest {
assertEquals(displayName, i.getDisplayName()); assertEquals(displayName, i.getDisplayName());
} }
private class NameNotEditableItem extends AbstractItem { private static class NameNotEditableItem extends AbstractItem {
protected NameNotEditableItem(ItemGroup parent, String name){ protected NameNotEditableItem(ItemGroup parent, String name){
super(parent, name); super(parent, name);
......
...@@ -11,7 +11,7 @@ import static org.mockito.Mockito.when; ...@@ -11,7 +11,7 @@ import static org.mockito.Mockito.when;
public class EnvironmentContributingActionTest { public class EnvironmentContributingActionTest {
class OverrideRun extends InvisibleAction implements EnvironmentContributingAction { static class OverrideRun extends InvisibleAction implements EnvironmentContributingAction {
private boolean wasCalled = false; private boolean wasCalled = false;
@Override @Override
...@@ -24,7 +24,7 @@ public class EnvironmentContributingActionTest { ...@@ -24,7 +24,7 @@ public class EnvironmentContributingActionTest {
} }
} }
class OverrideAbstractBuild extends InvisibleAction implements EnvironmentContributingAction { static class OverrideAbstractBuild extends InvisibleAction implements EnvironmentContributingAction {
private boolean wasCalled = false; private boolean wasCalled = false;
@Override @Override
...@@ -38,7 +38,7 @@ public class EnvironmentContributingActionTest { ...@@ -38,7 +38,7 @@ public class EnvironmentContributingActionTest {
} }
} }
class OverrideBoth extends InvisibleAction implements EnvironmentContributingAction { static class OverrideBoth extends InvisibleAction implements EnvironmentContributingAction {
private boolean wasCalledAbstractBuild = false; private boolean wasCalledAbstractBuild = false;
private boolean wasCalledRun = false; private boolean wasCalledRun = false;
......
...@@ -107,7 +107,7 @@ public class FingerprintCleanupThreadTest { ...@@ -107,7 +107,7 @@ public class FingerprintCleanupThreadTest {
tempDirectory.toFile().deleteOnExit(); tempDirectory.toFile().deleteOnExit();
} }
private class TestTaskListener implements TaskListener { private static class TestTaskListener implements TaskListener {
private ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); private ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
private PrintStream logStream = new PrintStream(outputStream); private PrintStream logStream = new PrintStream(outputStream);
...@@ -126,7 +126,6 @@ public class FingerprintCleanupThreadTest { ...@@ -126,7 +126,6 @@ public class FingerprintCleanupThreadTest {
public TestFingerprintCleanupThread(Fingerprint fingerprintToLoad) throws IOException { public TestFingerprintCleanupThread(Fingerprint fingerprintToLoad) throws IOException {
this.fingerprintToLoad = fingerprintToLoad; this.fingerprintToLoad = fingerprintToLoad;
return;
} }
@Override @Override
...@@ -146,7 +145,7 @@ public class FingerprintCleanupThreadTest { ...@@ -146,7 +145,7 @@ public class FingerprintCleanupThreadTest {
} }
private class TestFingerprint extends Fingerprint { private static class TestFingerprint extends Fingerprint {
private boolean isAlive = true; private boolean isAlive = true;
......
...@@ -101,7 +101,7 @@ public class LoadStatisticsTest { ...@@ -101,7 +101,7 @@ public class LoadStatisticsTest {
assertThat(LoadStatistics.isModern(LoadStatistics.class), is(false)); assertThat(LoadStatistics.isModern(LoadStatistics.class), is(false));
} }
private class Modern extends LoadStatistics { private static class Modern extends LoadStatistics {
protected Modern(int initialOnlineExecutors, int initialBusyExecutors) { protected Modern(int initialOnlineExecutors, int initialBusyExecutors) {
super(initialOnlineExecutors, initialBusyExecutors); super(initialOnlineExecutors, initialBusyExecutors);
......
...@@ -16,7 +16,7 @@ import java.util.Set; ...@@ -16,7 +16,7 @@ import java.util.Set;
*/ */
public class CyclicGraphDetectorTest { public class CyclicGraphDetectorTest {
private class Edge { private static class Edge {
String src,dst; String src,dst;
private Edge(String src, String dst) { private Edge(String src, String dst) {
...@@ -25,7 +25,7 @@ public class CyclicGraphDetectorTest { ...@@ -25,7 +25,7 @@ public class CyclicGraphDetectorTest {
} }
} }
private class Graph extends ArrayList<Edge> { private static class Graph extends ArrayList<Edge> {
Graph e(String src, String dst) { Graph e(String src, String dst) {
add(new Edge(src,dst)); add(new Edge(src,dst));
return this; return this;
......
...@@ -62,7 +62,7 @@ public class IsOverriddenTest { ...@@ -62,7 +62,7 @@ public class IsOverriddenTest {
Util.isOverridden(Base.class, Intermediate.class, "aPrivateMethod"); Util.isOverridden(Base.class, Intermediate.class, "aPrivateMethod");
} }
public abstract class Base<T> { public static abstract class Base<T> {
protected abstract void method(); protected abstract void method();
private void aPrivateMethod() {} private void aPrivateMethod() {}
public void setX(T t) {} public void setX(T t) {}
......
package hudson.util;
import hudson.Util;
import org.junit.rules.ExternalResource;
import java.security.SecureRandom;
/**
* JUnit rule that cleans that sets a temporary {@link Secret#SECRET} value.
*
* @author Kohsuke Kawaguchi
*/
public class MockSecretRule extends ExternalResource {
private String value;
@Override
protected void before() throws Throwable {
byte[] random = new byte[32];
sr.nextBytes(random);
value = Util.toHexString(random);
Secret.SECRET = value;
}
@Override
protected void after() {
if (!Secret.SECRET.equals(value))
throw new IllegalStateException("Someone tinkered with Secret.SECRET");
Secret.SECRET = null;
}
private static final SecureRandom sr = new SecureRandom();
}
...@@ -22,9 +22,6 @@ import org.junit.rules.TemporaryFolder; ...@@ -22,9 +22,6 @@ import org.junit.rules.TemporaryFolder;
public class SecretRewriterTest { public class SecretRewriterTest {
@Rule
public MockSecretRule mockSecretRule = new MockSecretRule();
@Rule @Rule
public ConfidentialStoreRule confidentialStoreRule = new ConfidentialStoreRule(); public ConfidentialStoreRule confidentialStoreRule = new ConfidentialStoreRule();
......
...@@ -43,9 +43,6 @@ public class SecretTest { ...@@ -43,9 +43,6 @@ public class SecretTest {
@Rule @Rule
public ConfidentialStoreRule confidentialStore = new ConfidentialStoreRule(); public ConfidentialStoreRule confidentialStore = new ConfidentialStoreRule();
@Rule
public MockSecretRule mockSecretRule = new MockSecretRule();
private static final Pattern ENCRYPTED_VALUE_PATTERN = Pattern.compile("\\{?[A-Za-z0-9+/]+={0,2}}?"); private static final Pattern ENCRYPTED_VALUE_PATTERN = Pattern.compile("\\{?[A-Za-z0-9+/]+={0,2}}?");
@Test @Test
......
...@@ -136,7 +136,7 @@ public class TarArchiverTest { ...@@ -136,7 +136,7 @@ public class TarArchiverTest {
t1.join(); t1.join();
} }
private class GrowingFileRunnable implements Runnable { private static class GrowingFileRunnable implements Runnable {
private boolean finish = false; private boolean finish = false;
private Exception ex = null; private Exception ex = null;
private File file; private File file;
......
...@@ -2,28 +2,15 @@ package jenkins.security; ...@@ -2,28 +2,15 @@ package jenkins.security;
import org.junit.rules.ExternalResource; import org.junit.rules.ExternalResource;
import org.junit.rules.TemporaryFolder;
/** /**
* Test rule that injects a temporary {@link DefaultConfidentialStore} * Test rule that makes {@link ConfidentialStore#get} be reset for each test.
* @author Kohsuke Kawaguchi
*/ */
public class ConfidentialStoreRule extends ExternalResource { public class ConfidentialStoreRule extends ExternalResource {
private final TemporaryFolder tmp = new TemporaryFolder();
@Override @Override
protected void before() throws Throwable { protected void before() throws Throwable {
tmp.create(); ConfidentialStore.Mock.INSTANCE.clear();
ConfidentialStore.TEST.set(new DefaultConfidentialStore(tmp.getRoot()));
} }
@Override
protected void after() {
ConfidentialStore.TEST.set(null);
tmp.delete();
}
static {
ConfidentialStore.TEST = new ThreadLocal<>();
}
} }
...@@ -84,7 +84,7 @@ public class MarkFindingOutputStreamTest { ...@@ -84,7 +84,7 @@ public class MarkFindingOutputStreamTest {
m.write(s.charAt(i)); m.write(s.charAt(i));
} }
class MarkCountingOutputStream extends MarkFindingOutputStream { static class MarkCountingOutputStream extends MarkFindingOutputStream {
int count = 0; int count = 0;
MarkCountingOutputStream(OutputStream base) { MarkCountingOutputStream(OutputStream base) {
......
...@@ -76,7 +76,7 @@ THE SOFTWARE. ...@@ -76,7 +76,7 @@ THE SOFTWARE.
</issueManagement> </issueManagement>
<properties> <properties>
<revision>2.229</revision> <revision>2.230</revision>
<changelist>-SNAPSHOT</changelist> <changelist>-SNAPSHOT</changelist>
<!-- *.html files are in UTF-8, and *.properties are in iso-8859-1, so this configuration is actually incorrect, <!-- *.html files are in UTF-8, and *.properties are in iso-8859-1, so this configuration is actually incorrect,
......
...@@ -56,7 +56,7 @@ public class ExtensionListListenerTest { ...@@ -56,7 +56,7 @@ public class ExtensionListListenerTest {
Assert.assertEquals(1, listListener.onChangeCallCount); Assert.assertEquals(1, listListener.onChangeCallCount);
} }
private class MyExtensionListListener extends ExtensionListListener { private static class MyExtensionListListener extends ExtensionListListener {
private int onChangeCallCount = 0; private int onChangeCallCount = 0;
@Override @Override
public void onChange() { public void onChange() {
......
...@@ -99,7 +99,7 @@ public class ApiSEC1704Test { ...@@ -99,7 +99,7 @@ public class ApiSEC1704Test {
} }
@ExportedBean @ExportedBean
class CustomData { static class CustomData {
private String secret; private String secret;
CustomData(String secret){ CustomData(String secret){
this.secret = secret; this.secret = secret;
......
...@@ -124,7 +124,7 @@ public class UpdateCenterConnectionStatusTest { ...@@ -124,7 +124,7 @@ public class UpdateCenterConnectionStatusTest {
Assert.assertEquals(ConnectionStatus.FAILED, job.connectionStates.get(ConnectionStatus.UPDATE_SITE)); Assert.assertEquals(ConnectionStatus.FAILED, job.connectionStates.get(ConnectionStatus.UPDATE_SITE));
} }
private class TestConfig extends UpdateCenter.UpdateCenterConfiguration { private static class TestConfig extends UpdateCenter.UpdateCenterConfiguration {
private IOException checkConnectionException; private IOException checkConnectionException;
private IOException checkUpdateCenterException; private IOException checkUpdateCenterException;
......
...@@ -72,7 +72,7 @@ public class SecurityRealmTest { ...@@ -72,7 +72,7 @@ public class SecurityRealmTest {
assertThat(response.getResponseHeaderValue("Expires"), is("0")); assertThat(response.getResponseHeaderValue("Expires"), is("0"));
} }
private class DummyCaptcha extends CaptchaSupport { private static class DummyCaptcha extends CaptchaSupport {
@Override @Override
public boolean validateCaptcha(String id, String text) { public boolean validateCaptcha(String id, String text) {
return false; return false;
......
...@@ -201,7 +201,7 @@ public class WhoAmITest { ...@@ -201,7 +201,7 @@ public class WhoAmITest {
))); )));
} }
private class SecurityRealmImpl extends AbstractPasswordBasedSecurityRealm { private static class SecurityRealmImpl extends AbstractPasswordBasedSecurityRealm {
@Override @Override
protected UserDetails authenticate(String username, String password) throws AuthenticationException { protected UserDetails authenticate(String username, String password) throws AuthenticationException {
......
...@@ -136,7 +136,7 @@ public class ZipExtractionInstallerTest { ...@@ -136,7 +136,7 @@ public class ZipExtractionInstallerTest {
assertThat(lastRequest.getResponseText(), containsString(Messages.ZipExtractionInstaller_malformed_url())); assertThat(lastRequest.getResponseText(), containsString(Messages.ZipExtractionInstaller_malformed_url()));
} }
private class SpyingJavaScriptEngine extends JavaScriptEngine { private static class SpyingJavaScriptEngine extends JavaScriptEngine {
private List<XMLHttpRequest> storedRequests = new ArrayList<>(); private List<XMLHttpRequest> storedRequests = new ArrayList<>();
private String urlToMatch; private String urlToMatch;
private HttpMethod method; private HttpMethod method;
......
...@@ -103,7 +103,7 @@ public class WebSocketAgentsTest { ...@@ -103,7 +103,7 @@ public class WebSocketAgentsTest {
assertNotNull(s.getChannel().call(new FatTask())); assertNotNull(s.getChannel().call(new FatTask()));
FreeStyleProject p = r.createFreeStyleProject(); FreeStyleProject p = r.createFreeStyleProject();
p.setAssignedNode(s); p.setAssignedNode(s);
p.getBuildersList().add(Functions.isWindows() ? new BatchFile("echo hello") : new Shell("dd if=/dev/random count=1024 bs=200")); p.getBuildersList().add(Functions.isWindows() ? new BatchFile("echo hello") : new Shell("echo hello"));
r.buildAndAssertSuccess(p); r.buildAndAssertSuccess(p);
s.toComputer().getLogText().writeLogTo(0, System.out); s.toComputer().getLogText().writeLogTo(0, System.out);
} finally { } finally {
......
...@@ -5,15 +5,35 @@ import hudson.model.FreeStyleProject; ...@@ -5,15 +5,35 @@ import hudson.model.FreeStyleProject;
import hudson.model.Run; import hudson.model.Run;
import hudson.model.TaskListener; import hudson.model.TaskListener;
import hudson.tasks.LogRotator; import hudson.tasks.LogRotator;
import hudson.util.DescribableList;
import org.junit.Assert; import org.junit.Assert;
import org.junit.Rule; import org.junit.Rule;
import org.junit.Test; import org.junit.Test;
import org.jvnet.hudson.test.Issue;
import org.jvnet.hudson.test.JenkinsRule; import org.jvnet.hudson.test.JenkinsRule;
import org.jvnet.hudson.test.recipes.LocalData;
public class GlobalBuildDiscarderTest { public class GlobalBuildDiscarderTest {
@Rule @Rule
public JenkinsRule j = new JenkinsRule(); public JenkinsRule j = new JenkinsRule();
@Test
@LocalData
@Issue("JENKINS-61688")
public void testLoading() throws Exception {
Assert.assertEquals(0, GlobalBuildDiscarderConfiguration.get().getConfiguredBuildDiscarders().size());
}
@Test
@LocalData
@Issue("JENKINS-61688")
public void testLoadingWithDiscarders() throws Exception {
final DescribableList<GlobalBuildDiscarderStrategy, GlobalBuildDiscarderStrategyDescriptor> configuredBuildDiscarders = GlobalBuildDiscarderConfiguration.get().getConfiguredBuildDiscarders();
Assert.assertEquals(2, configuredBuildDiscarders.size());
Assert.assertNotNull(configuredBuildDiscarders.get(JobGlobalBuildDiscarderStrategy.class));
Assert.assertEquals(5, ((LogRotator)configuredBuildDiscarders.get(SimpleGlobalBuildDiscarderStrategy.class).getDiscarder()).getNumToKeep());
}
@Test @Test
public void testJobBuildDiscarder() throws Exception { public void testJobBuildDiscarder() throws Exception {
FreeStyleProject p = j.createFreeStyleProject(); FreeStyleProject p = j.createFreeStyleProject();
......
...@@ -19,9 +19,12 @@ import javax.inject.Inject; ...@@ -19,9 +19,12 @@ import javax.inject.Inject;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.lang.annotation.Annotation; import java.lang.annotation.Annotation;
import java.nio.charset.StandardCharsets;
import java.util.Base64; import java.util.Base64;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import java.util.stream.Stream; import java.util.stream.Stream;
import org.hamcrest.MatcherAssert;
import org.hamcrest.Matchers;
/** /**
* @author Kohsuke Kawaguchi * @author Kohsuke Kawaguchi
...@@ -81,7 +84,7 @@ public class RekeySecretAdminMonitorTest extends HudsonTestCase { ...@@ -81,7 +84,7 @@ public class RekeySecretAdminMonitorTest extends HudsonTestCase {
private void verifyRewrite(File dir) throws Exception { private void verifyRewrite(File dir) throws Exception {
File xml = new File(dir, "foo.xml"); File xml = new File(dir, "foo.xml");
Pattern pattern = Pattern.compile("<foo>"+plain_regex_match+"</foo>"); Pattern pattern = Pattern.compile("<foo>"+plain_regex_match+"</foo>");
assertTrue(pattern.matcher(FileUtils.readFileToString(xml).trim()).matches()); MatcherAssert.assertThat(FileUtils.readFileToString(xml, StandardCharsets.UTF_8).trim(), Matchers.matchesRegex(pattern));
} }
// TODO sometimes fails: "Invalid request submission: {json=[Ljava.lang.String;@2c46358e, .crumb=[Ljava.lang.String;@35661457}" // TODO sometimes fails: "Invalid request submission: {json=[Ljava.lang.String;@2c46358e, .crumb=[Ljava.lang.String;@35661457}"
......
...@@ -73,7 +73,7 @@ public class DoActionFilterTest extends StaplerAbstractTest { ...@@ -73,7 +73,7 @@ public class DoActionFilterTest extends StaplerAbstractTest {
private TestAccessModifier getPrivate() {return new TestAccessModifier();} private TestAccessModifier getPrivate() {return new TestAccessModifier();}
public class TestAccessModifier { public static class TestAccessModifier {
@GET @GET
public String doValue() { public String doValue() {
return "hello"; return "hello";
......
<?xml version='1.1' encoding='UTF-8'?>
<jenkins.model.GlobalBuildDiscarderConfiguration>
<configuredBuildDiscarders/>
</jenkins.model.GlobalBuildDiscarderConfiguration>
\ No newline at end of file
<?xml version='1.1' encoding='UTF-8'?>
<jenkins.model.GlobalBuildDiscarderConfiguration>
<configuredBuildDiscarders>
<jenkins.model.JobGlobalBuildDiscarderStrategy/>
<jenkins.model.SimpleGlobalBuildDiscarderStrategy>
<discarder class="hudson.tasks.LogRotator">
<daysToKeep>-1</daysToKeep>
<numToKeep>5</numToKeep>
<artifactDaysToKeep>-1</artifactDaysToKeep>
<artifactNumToKeep>-1</artifactNumToKeep>
</discarder>
</jenkins.model.SimpleGlobalBuildDiscarderStrategy>
</configuredBuildDiscarders>
</jenkins.model.GlobalBuildDiscarderConfiguration>
\ No newline at end of file
/*
* The MIT License
*
* Copyright (c) 2016, CloudBees, Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
html {
position: relative;
min-height: 100%;
}
body {
margin: 0;
padding: 0 0 40px 0;
}
/* --------------- header --------------- */
#header {
background-color: #000000;
height: 40px;
}
#header div {
display: inline-block;
height: inherit;
}
#header .logo {
margin-left: 16px;
}
#jenkins-home-link {
position: absolute;
height: 40px;
}
#jenkins-head-icon {
position: absolute;
bottom: 0px;
}
#jenkins-name-icon {
position: absolute;
bottom: 3px;
left: 32px;
}
#header .searchbox, #header .login {
float: right;
padding: 6px 11px;
}
#breadcrumbBar, #footer-container, .top-sticker-inner {
background-color: #f6faf2;
}
/* -------------------------------------- */
#page-body.clear:after {
clear: both;
content: "";
display: table;
}
#side-panel {
padding: 15px 15px 40px 15px;
float: left;
width: 320px;
}
#main-panel {
padding: 15px 15px 40px 15px;
}
body.two-column #main-panel {
margin-left: 320px;
display:block;
}
body.full-screen {
padding: 0;
}
body.full-screen #main-panel {
padding: 0;
}
@media (max-width: 970px) {
body.two-column #side-panel {
width: 100%;
float: none;
padding-bottom: 20px;
}
body.two-column #main-panel {
margin-left: 0;
display:block;
}
}
@media (min-width: 1170px) {
body.two-column #side-panel {
width: 360px;
}
body.two-column #main-panel {
margin-left: 360px;
display:block;
}
}
/* --------------- footer --------------- */
footer {
padding: 11px 0;
background-color: #f6faf2;
border-top: 1px solid #d3d7cf;
border-bottom: 1px solid #f6faf2;
width: 100%;
position: absolute;
bottom: 0;
left: 0;
clear: both;
font-size: 12px;
text-align: right;
}
footer span {
margin-left: 15px;
line-height: 14px;
}
/* -------------------------------------- */
此差异已折叠。
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册